Compare commits

..

34 Commits

Author SHA1 Message Date
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
f424315d97 [3.9.8-dev] 2019-11-17 23:17:37 +01:00
c907a8df37 [APIv2] Librus: better error handling. Timetable: fix widget crashing with NPE. 2019-11-17 23:16:13 +01:00
37ea65e3fc [Timetable] Add SwipeToRefresh. Select start&end hours based on lesson ranges. 2019-11-16 21:16:18 +01:00
a3e5f824c8 [UI] Fix messages & homework refresh layout sensitivity. 2019-11-16 18:46:01 +01:00
e0c850a455 [Gradle] Update Tachyon to support creating DayView programmatically. 2019-11-16 17:07:07 +01:00
1c6815f708 [3.9.7-dev] 2019-11-15 22:00:18 +01:00
9a20511935 [UI] Set pull-to-refresh colors. 2019-11-15 21:16:58 +01:00
965f5e73d9 [Bugs] Update gradle. Fix crashes in the timetable widget. 2019-11-15 19:55:46 +01:00
98 changed files with 2519 additions and 1482 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

@ -169,7 +169,7 @@ dependencies {
implementation 'com.github.kuba2k2:RecyclerTabLayout:700f980584' implementation 'com.github.kuba2k2:RecyclerTabLayout:700f980584'
implementation 'com.linkedin.android.tachyon:tachyon:1.0.2' implementation 'com.github.kuba2k2:Tachyon:551943a6b5'
} }
repositories { repositories {
mavenCentral() mavenCentral()

View File

@ -4,7 +4,9 @@ import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Resources
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.* import android.text.*
@ -13,9 +15,11 @@ import android.text.style.StrikethroughSpan
import android.text.style.StyleSpan import android.text.style.StyleSpan
import android.util.LongSparseArray import android.util.LongSparseArray
import android.util.SparseArray import android.util.SparseArray
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.StringRes import androidx.annotation.*
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.util.forEach import androidx.core.util.forEach
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
@ -438,6 +442,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?) {
@ -446,3 +457,49 @@ fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observ
} }
}) })
} }
/**
* Convert a value in dp to pixels.
*/
val Int.dp: Int
get() = (this * Resources.getSystem().displayMetrics.density).toInt()
/**
* Convert a value in pixels to dp.
*/
val Int.px: Int
get() = (this / Resources.getSystem().displayMetrics.density).toInt()
@ColorInt
fun @receiver:AttrRes Int.resolveAttr(context: Context?): Int {
val typedValue = TypedValue()
context?.theme?.resolveAttribute(this, typedValue, true)
return typedValue.data
}
@ColorInt
fun @receiver:ColorRes Int.resolveColor(context: Context): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
context.resources.getColor(this, context.theme)
}
else {
context.resources.getColor(this)
}
}
fun @receiver:DrawableRes Int.resolveDrawable(context: Context): Drawable {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
context.resources.getDrawable(this, context.theme)
}
else {
context.resources.getDrawable(this)
}
}
fun View.findParentById(targetId: Int): View? {
if (id == targetId) {
return this
}
val viewParent = this.parent ?: return null
if (viewParent is View) {
return viewParent.findParentById(targetId)
}
return null
}

View File

@ -124,7 +124,7 @@ class MainActivity : AppCompatActivity() {
// 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, HomeFragment::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)
@ -376,6 +376,11 @@ class MainActivity : AppCompatActivity() {
b.swipeRefreshLayout.isEnabled = true b.swipeRefreshLayout.isEnabled = true
b.swipeRefreshLayout.setOnRefreshListener { this.syncCurrentFeature() } b.swipeRefreshLayout.setOnRefreshListener { this.syncCurrentFeature() }
b.swipeRefreshLayout.setColorSchemeResources(
R.color.md_blue_500,
R.color.md_amber_500,
R.color.md_green_500
)
isStoragePermissionGranted() isStoragePermissionGranted()
@ -429,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)
@ -466,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)
@ -474,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) })
} }
@ -532,9 +537,16 @@ 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().apply {
addProperty("weekStart", 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)
@ -689,10 +701,10 @@ 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) loadProfile(intentProfileId, 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())
@ -185,12 +185,12 @@ class WidgetTimetable : AppWidgetProvider() {
// search for lessons to display // search for lessons to display
val timetableDate = Date.getToday() val timetableDate = Date.getToday()
var checkedDays = 0 var checkedDays = 0
var lessons = lessonList.filter { it.profileId == profile.id && it.displayDate == timetableDate } var lessons = lessonList.filter { it.profileId == profile.id && it.displayDate == timetableDate && it.type != Lesson.TYPE_NO_LESSONS }
while ((lessons.isEmpty() || lessons.none { while ((lessons.isEmpty() || lessons.none {
it.displayDate != today || (it.displayDate == today && it.displayEndTime != null && it.displayEndTime!! >= now) it.displayDate != today || (it.displayDate == today && it.displayEndTime != null && it.displayEndTime!! >= now)
}) && checkedDays < 7) { }) && checkedDays < 7) {
timetableDate.stepForward(0, 0, 1) timetableDate.stepForward(0, 0, 1)
lessons = lessonList.filter { it.profileId == profile.id && it.displayDate == timetableDate } lessons = lessonList.filter { it.profileId == profile.id && it.displayDate == timetableDate && it.type != Lesson.TYPE_NO_LESSONS }
checkedDays++ checkedDays++
} }
@ -214,8 +214,8 @@ class WidgetTimetable : AppWidgetProvider() {
model.lessonId = lesson.id model.lessonId = lesson.id
model.lessonDate = timetableDate model.lessonDate = timetableDate
model.startTime = lesson.startTime model.startTime = lesson.displayStartTime
model.endTime = lesson.endTime model.endTime = lesson.displayEndTime
// check if the lesson has already passed or it's currently in progress // check if the lesson has already passed or it's currently in progress
if (lesson.displayDate == today) { if (lesson.displayDate == today) {

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

@ -39,9 +39,11 @@ 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_LOGIN_DATA_INVALID = 102
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
@ -99,6 +101,13 @@ const val ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_INVALID = 172
const val ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED = 173 const val ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED = 173
const val ERROR_LIBRUS_SYNERGIA_OTHER = 174 const val ERROR_LIBRUS_SYNERGIA_OTHER = 174
const val ERROR_LIBRUS_SYNERGIA_MAINTENANCE = 175 const val ERROR_LIBRUS_SYNERGIA_MAINTENANCE = 175
const val ERROR_LIBRUS_MESSAGES_MAINTENANCE = 176
const val ERROR_LIBRUS_MESSAGES_ERROR = 177
const val ERROR_LIBRUS_MESSAGES_OTHER = 178
const val ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN = 179
const val ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN = 180
const val ERROR_LIBRUS_API_MAINTENANCE = 181
const val ERROR_LIBRUS_PORTAL_MAINTENANCE = 182
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN = 201 const val ERROR_LOGIN_MOBIDZIENNIK_WEB_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,6 +71,8 @@ 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()
} }

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

@ -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 {
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,64 +61,103 @@ 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 id = lessonDate.combineWith(lessonRange.startTime) / 6L * 10L + (lesson.hashCode() and 0xFFFF)
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, id)
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
}
}
}
dates.add(lessonDate.value)
lessons.add(lessonObject)
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, profile?.empty ?: false,
profile?.empty ?: false, profile?.empty ?: false,
System.currentTimeMillis() System.currentTimeMillis()
@ -121,6 +165,23 @@ class IdziennikWebTimetable(override val data: DataIdziennik,
} }
} }
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

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

@ -32,6 +32,13 @@ open class LibrusApi(open val data: DataLibrus) {
val callback = object : JsonCallbackHandler() { val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) { override fun onSuccess(json: JsonObject?, response: Response?) {
if (response?.code() == HTTP_UNAVAILABLE) {
data.error(ApiError(tag, ERROR_LIBRUS_API_MAINTENANCE)
.withApiResponse(json)
.withResponse(response))
return
}
if (json == null && response?.parserErrorBody == null) { if (json == null && response?.parserErrorBody == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response)) .withResponse(response))
@ -104,6 +111,7 @@ open class LibrusApi(open val data: DataLibrus) {
.allowErrorCode(HTTP_BAD_REQUEST) .allowErrorCode(HTTP_BAD_REQUEST)
.allowErrorCode(HTTP_FORBIDDEN) .allowErrorCode(HTTP_FORBIDDEN)
.allowErrorCode(HTTP_UNAUTHORIZED) .allowErrorCode(HTTP_UNAUTHORIZED)
.allowErrorCode(HTTP_UNAVAILABLE)
.callback(callback) .callback(callback)
.build() .build()
.enqueue() .enqueue()

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

@ -15,7 +15,6 @@ import org.jsoup.parser.Parser
import pl.szczodrzynski.edziennik.api.v2.* import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import java.io.StringWriter import java.io.StringWriter
import javax.xml.parsers.DocumentBuilderFactory import javax.xml.parsers.DocumentBuilderFactory
@ -43,19 +42,19 @@ open class LibrusMessages(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?) {
if (text.isNullOrEmpty()) { if (text.isNullOrEmpty()) {
data.error(ApiError(LibrusSynergia.TAG, ERROR_RESPONSE_EMPTY) data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response)) .withResponse(response))
return return
} }
// TODO: Finish error handling when {
text.contains("<message>Niepoprawny login i/lub hasło.</message>") -> data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN, response, text)
if ("error" in text) { text.contains("stop.png") -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text)
when ("<type>(.*)</type>".toRegex().find(text)?.get(1)) { text.contains("eAccessDeny") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
"eAccessDeny" -> data.error(ApiError(tag, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED) text.contains("OffLine") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_MAINTENANCE, response, text)
.withResponse(response) text.contains("<status>error</status>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ERROR, response, text)
.withApiResponse(text)) text.contains("<type>eVarWhitThisNameNotExists</type>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
} text.contains("<error>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text)
} }
try { try {

View File

@ -14,7 +14,7 @@ import pl.szczodrzynski.edziennik.utils.Utils.d
open class LibrusSynergia(open val data: DataLibrus) { open class LibrusSynergia(open val data: DataLibrus) {
companion object { companion object {
const val TAG = "LibrusSynergia" private const val TAG = "LibrusSynergia"
} }
val profileId val profileId

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))
@ -176,7 +184,7 @@ class LibrusApiTimetables(override val data: DataLibrus,
} }
} }
if (lessonObject.type != Lesson.TYPE_NORMAL) { if (lessonObject.type != Lesson.TYPE_NORMAL && lessonDate >= Date.getToday()) {
data.metadataList.add( data.metadataList.add(
Metadata( Metadata(
data.profileId, data.profileId,

View File

@ -16,8 +16,7 @@ import pl.szczodrzynski.edziennik.getInt
import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.getUnixDate import pl.szczodrzynski.edziennik.getUnixDate
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection.HTTP_BAD_REQUEST import java.net.HttpURLConnection.*
import java.net.HttpURLConnection.HTTP_UNAUTHORIZED
class LibrusLoginApi { class LibrusLoginApi {
companion object { companion object {
@ -117,6 +116,13 @@ class LibrusLoginApi {
private val tokenCallback = object : JsonCallbackHandler() { private val tokenCallback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) { override fun onSuccess(json: JsonObject?, response: Response?) {
if (response?.code() == HTTP_UNAVAILABLE) {
data.error(ApiError(TAG, ERROR_LIBRUS_API_MAINTENANCE)
.withApiResponse(json)
.withResponse(response))
return
}
if (json == null) { if (json == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response)) .withResponse(response))
@ -176,6 +182,7 @@ class LibrusLoginApi {
.post() .post()
.allowErrorCode(HTTP_BAD_REQUEST) .allowErrorCode(HTTP_BAD_REQUEST)
.allowErrorCode(HTTP_UNAUTHORIZED) .allowErrorCode(HTTP_UNAUTHORIZED)
.allowErrorCode(HTTP_UNAVAILABLE)
.callback(tokenCallback) .callback(tokenCallback)
.build() .build()
.enqueue() .enqueue()

View File

@ -6,20 +6,57 @@ package pl.szczodrzynski.edziennik.api.v2.librus.login
import im.wangchao.mhttp.Request import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response import im.wangchao.mhttp.Response
import im.wangchao.mhttp.body.MediaTypeUtils
import im.wangchao.mhttp.callback.TextCallbackHandler import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie import okhttp3.Cookie
import pl.szczodrzynski.edziennik.api.v2.* import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.getUnixDate import pl.szczodrzynski.edziennik.getUnixDate
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import java.io.StringWriter
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) { class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
companion object { companion object {
private const val TAG = "LoginLibrusMessages" private const val TAG = "LoginLibrusMessages"
} }
private val callback by lazy { object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) {
val location = response?.headers()?.get("Location")
when {
location?.contains("MultiDomainLogon") == true -> loginWithSynergia(location)
location?.contains("AutoLogon") == true -> {
saveSessionId(response, text)
onSuccess()
}
text?.contains("<status>ok</status>") == true -> {
saveSessionId(response, text)
onSuccess()
}
text?.contains("<message>Niepoprawny login i/lub hasło.</message>") == true -> data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN, response, text)
text?.contains("stop.png") == true -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text)
text?.contains("eAccessDeny") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
text?.contains("OffLine") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_MAINTENANCE, response, text)
text?.contains("<status>error</status>") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ERROR, response, text)
text?.contains("<type>eVarWhitThisNameNotExists</type>") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
text?.contains("<error>") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text)
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}}
init { run { init { run {
if (data.profile == null) { if (data.profile == null) {
data.error(ApiError(TAG, ERROR_PROFILE_MISSING)) data.error(ApiError(TAG, ERROR_PROFILE_MISSING))
@ -41,7 +78,7 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_SYNERGIA)) { if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_SYNERGIA)) {
loginWithSynergia() loginWithSynergia()
} }
else if (data.apiLogin != null && data.apiPassword != null && false) { else if (data.apiLogin != null && data.apiPassword != null) {
loginWithCredentials() loginWithCredentials()
} }
else { else {
@ -54,7 +91,44 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
* XML (Flash messages website) login method. Uses a Synergia login and password. * XML (Flash messages website) login method. Uses a Synergia login and password.
*/ */
private fun loginWithCredentials() { private fun loginWithCredentials() {
d(TAG, "Request: Librus/Login/Messages - $LIBRUS_MESSAGES_URL/Login")
val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
val doc = docBuilder.newDocument()
val serviceElement = doc.createElement("service")
val headerElement = doc.createElement("header")
val dataElement = doc.createElement("data")
val loginElement = doc.createElement("login")
loginElement.appendChild(doc.createTextNode(data.apiLogin))
dataElement.appendChild(loginElement)
val passwordElement = doc.createElement("login")
passwordElement.appendChild(doc.createTextNode(data.apiPassword))
dataElement.appendChild(passwordElement)
val keyStrokeElement = doc.createElement("KeyStroke")
val keysElement = doc.createElement("Keys")
val upElement = doc.createElement("Up")
keysElement.appendChild(upElement)
val downElement = doc.createElement("Down")
keysElement.appendChild(downElement)
keyStrokeElement.appendChild(keysElement)
dataElement.appendChild(keyStrokeElement)
serviceElement.appendChild(headerElement)
serviceElement.appendChild(dataElement)
doc.appendChild(serviceElement)
val transformer = TransformerFactory.newInstance().newTransformer()
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")
val stringWriter = StringWriter()
transformer.transform(DOMSource(doc), StreamResult(stringWriter))
val requestXml = stringWriter.toString()
Request.builder()
.url("$LIBRUS_MESSAGES_URL/Login")
.userAgent(SYNERGIA_USER_AGENT)
.setTextBody(requestXml, MediaTypeUtils.APPLICATION_XML)
.post()
.callback(callback)
.build()
.enqueue()
} }
/** /**
@ -63,37 +137,6 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
private fun loginWithSynergia(url: String = "https://synergia.librus.pl/wiadomosci2") { private fun loginWithSynergia(url: String = "https://synergia.librus.pl/wiadomosci2") {
d(TAG, "Request: Librus/Login/Messages - $url") d(TAG, "Request: Librus/Login/Messages - $url")
val callback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) {
val location = response?.headers()?.get("Location")
when {
location?.contains("MultiDomainLogon") == true -> loginWithSynergia(location)
location?.contains("AutoLogon") == true -> {
var sessionId = data.app.cookieJar.getCookie("wiadomosci.librus.pl", "DZIENNIKSID")
sessionId = sessionId?.replace("-MAINT", "")
if (sessionId == null) {
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_NO_SESSION_ID)
.withResponse(response)
.withApiResponse(text))
return
}
data.messagesSessionId = sessionId
data.messagesSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45min */
onSuccess()
}
text?.contains("eAccessDeny") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
text?.contains("stop.png") == true -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text)
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder() Request.builder()
.url(url) .url(url)
.userAgent(SYNERGIA_USER_AGENT) .userAgent(SYNERGIA_USER_AGENT)
@ -103,4 +146,17 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
.build() .build()
.enqueue() .enqueue()
} }
private fun saveSessionId(response: Response?, text: String?) {
var sessionId = data.app.cookieJar.getCookie("wiadomosci.librus.pl", "DZIENNIKSID")
sessionId = sessionId?.replace("-MAINT", "") // dunno what's this
if (sessionId == null) {
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_NO_SESSION_ID)
.withResponse(response)
.withApiResponse(text))
return
}
data.messagesSessionId = sessionId
data.messagesSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45min */
}
} }

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)
@ -120,7 +128,7 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
override fun onFailure(response: Response, throwable: Throwable) { override fun onFailure(response: Response, throwable: Throwable) {
if (response.code() == 403 || response.code() == 401) { if (response.code() == 403 || response.code() == 401) {
data.error(ApiError(TAG, ERROR_LOGIN_DATA_INVALID) data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN)
.withResponse(response) .withResponse(response)
.withThrowable(throwable)) .withThrowable(throwable))
return return

View File

@ -7,7 +7,6 @@ package pl.szczodrzynski.edziennik.api.v2.librus.login
import com.google.gson.JsonObject import com.google.gson.JsonObject
import im.wangchao.mhttp.Request import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response import im.wangchao.mhttp.Response
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 pl.szczodrzynski.edziennik.api.v2.* import pl.szczodrzynski.edziennik.api.v2.*
@ -16,7 +15,6 @@ import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.getUnixDate import pl.szczodrzynski.edziennik.getUnixDate
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection import java.net.HttpURLConnection
@ -86,6 +84,13 @@ class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Un
val callback = object : TextCallbackHandler() { val callback = object : TextCallbackHandler() {
override fun onSuccess(json: String?, response: Response?) { override fun onSuccess(json: String?, response: Response?) {
val location = response?.headers()?.get("Location") val location = response?.headers()?.get("Location")
if (location?.endsWith("przerwa_techniczna") == true) {
data.error(ApiError(TAG, ERROR_LIBRUS_SYNERGIA_MAINTENANCE)
.withApiResponse(json)
.withResponse(response))
return
}
if (location?.endsWith("centrum_powiadomien") == true) { if (location?.endsWith("centrum_powiadomien") == true) {
val sessionId = data.app.cookieJar.getCookie("synergia.librus.pl", "DZIENNIKSID") val sessionId = data.app.cookieJar.getCookie("synergia.librus.pl", "DZIENNIKSID")
if (sessionId == null) { if (sessionId == null) {

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,7 +65,11 @@ 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() {

View File

@ -75,7 +75,7 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
} }
} }
if (it.type != Lesson.TYPE_NORMAL) { if (it.type != Lesson.TYPE_NORMAL && date >= Date.getToday()) {
data.metadataList.add( data.metadataList.add(
Metadata( Metadata(
data.profileId, data.profileId,
@ -195,4 +195,4 @@ 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

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

@ -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,197 @@ 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 getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d
val weekStart = Date.fromY_m_d(getDate) val weekStart = Date.fromY_m_d(getDate)
val weekEnd = weekStart.clone().stepForward(0, 0, 6)
apiGet(TAG, VULCAN_API_ENDPOINT_TIMETABLE, parameters = mapOf( if (Date.getToday().weekDay > 4 && weekStart == currentWeekStart) {
"DataPoczatkowa" to weekStart.stringY_m_d, weekStart.stepForward(0, 0, 7)
"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 weekEnd = weekStart.clone().stepForward(0, 0, 6)
val lessonDate = Date.fromY_m_d(lesson.getString("DzienTekst"))
val lessonNumber = lesson.getInt("NumerLekcji")
val lessonRange = data.lessonRanges.singleOrNull { it.lessonNumber == lessonNumber }
val startTime = lessonRange?.startTime
val endTime = lessonRange?.endTime
val teacherId = lesson.getLong("IdPracownik")
val teamId = data.studentClassId.toLong()
val classroom = lesson.getString("Classroom")
val oldTeacherId = lesson.getLong("IdPracownikOld") 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 id = lessonDate.combineWith(startTime) / 6L * 10L + (lesson.hashCode() and 0xFFFF)
val lessonObject = Lesson(profileId, id).apply {
this.type = type
when (type) {
Lesson.TYPE_NORMAL, Lesson.TYPE_CHANGE, Lesson.TYPE_SHIFTED_TARGET -> {
this.date = lessonDate
this.lessonNumber = lessonNumber
this.startTime = startTime
this.endTime = endTime
this.subjectId = subjectId
this.teacherId = teacherId
this.teamId = teamId
this.classroom = classroom
this.oldTeacherId = oldTeacherId
}
Lesson.TYPE_CANCELLED, Lesson.TYPE_SHIFTED_SOURCE -> {
this.oldDate = lessonDate
this.oldLessonNumber = lessonNumber
this.oldStartTime = startTime
this.oldEndTime = endTime
this.oldSubjectId = subjectId
this.oldTeacherId = teacherId
this.oldTeamId = teamId
this.oldClassroom = classroom
} }
} }
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) {
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() if (type != Lesson.TYPE_NORMAL && lessonDate >= Date.getToday()) {
while (date <= weekEnd) { data.metadataList.add(Metadata(
if (!dates.contains(date.value)) { profileId,
lessons.add(Lesson(profileId, date.value.toLong()).apply { Metadata.TYPE_LESSON_CHANGE,
this.type = Lesson.TYPE_NO_LESSONS id,
this.date = date.clone() profile.empty,
}) profile.empty,
} System.currentTimeMillis()
))
date.stepForward(0, 0, 1)
} }
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

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

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

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,10 +53,14 @@ 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)
return@run
job = Job() job = Job()
onShowListener?.invoke(TAG) onShowListener?.invoke(TAG)
b = DialogEventManualV2Binding.inflate(activity.layoutInflater) b = DialogEventManualV2Binding.inflate(activity.layoutInflater)
@ -76,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
@ -112,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
@ -133,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()
}} }}
@ -203,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) {
@ -221,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
} }
@ -244,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")
@ -274,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
@ -300,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
} }
@ -336,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
@ -343,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) {
@ -381,4 +481,4 @@ class EventManualV2Dialog(
private fun saveEvent() { private fun saveEvent() {
} }
} }

View File

@ -33,6 +33,8 @@ class LessonDetailsDialog(
private lateinit var dialog: AlertDialog private lateinit var dialog: AlertDialog
init { run { init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG) onShowListener?.invoke(TAG)
b = DialogLessonDetailsBinding.inflate(activity.layoutInflater) b = DialogLessonDetailsBinding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity) dialog = MaterialAlertDialogBuilder(activity)
@ -130,28 +132,28 @@ 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

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

@ -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,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
) {
private lateinit var timetableTimer: Timer
private lateinit var b: CardTimetableBinding
private var bellSyncTime: Time? = null
private companion object {
const val TIME_TILL = 0
const val TIME_LEFT = 1
}
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

@ -9,11 +9,12 @@ 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
import pl.szczodrzynski.edziennik.databinding.FragmentHomeworkBinding
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.databinding.FragmentHomeworkBinding
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment
import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.Themes
@ -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) }
@ -84,7 +85,11 @@ class HomeworkFragment : Fragment() {
b.viewPager.currentItem = pageSelection b.viewPager.currentItem = pageSelection
b.viewPager.clearOnPageChangeListeners() b.viewPager.clearOnPageChangeListeners()
b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {} override fun onPageScrollStateChanged(state: Int) {
if (b.refreshLayout != null) {
b.refreshLayout.isEnabled = state == ViewPager.SCROLL_STATE_IDLE
}
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
pageSelection = position pageSelection = position

View File

@ -19,7 +19,7 @@ import pl.szczodrzynski.edziennik.api.v2.models.ApiError;
import pl.szczodrzynski.edziennik.databinding.FragmentLoginLibrusBinding; import pl.szczodrzynski.edziennik.databinding.FragmentLoginLibrusBinding;
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar; import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar;
import static pl.szczodrzynski.edziennik.api.v2.ErrorsKt.ERROR_LOGIN_DATA_INVALID; import static pl.szczodrzynski.edziennik.api.v2.ErrorsKt.ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN;
import static pl.szczodrzynski.edziennik.api.v2.ErrorsKt.ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED; import static pl.szczodrzynski.edziennik.api.v2.ErrorsKt.ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED;
import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_LIBRUS; import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_LIBRUS;
@ -57,7 +57,7 @@ public class LoginLibrusFragment extends Fragment {
ApiError error = LoginActivity.error; ApiError error = LoginActivity.error;
if (error != null) { if (error != null) {
switch (error.getErrorCode()) { switch (error.getErrorCode()) {
case ERROR_LOGIN_DATA_INVALID: case ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN:
b.loginPasswordLayout.setError(getString(R.string.login_error_incorrect_login_or_password)); b.loginPasswordLayout.setError(getString(R.string.login_error_incorrect_login_or_password));
break; break;
case ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED: case ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED:

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
@ -224,17 +225,18 @@ 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 var icon: IIcon = CommunityMaterial.Icon.cmd_file_outline
when (Utils.getExtensionFromFileName(name)) { when (Utils.getExtensionFromFileName(name)) {
"txt" -> icon = CommunityMaterial.Icon.cmd_file_document "txt" -> icon = CommunityMaterial.Icon.cmd_file_document_outline
"doc", "docx", "odt", "rtf" -> icon = CommunityMaterial.Icon.cmd_file_word "doc", "docx", "odt", "rtf" -> icon = SzkolnyFont.Icon.szf_file_word_outline
"xls", "xlsx", "ods" -> icon = CommunityMaterial.Icon.cmd_file_excel "xls", "xlsx", "ods" -> icon = SzkolnyFont.Icon.szf_file_excel_outline
"ppt", "pptx", "odp" -> icon = CommunityMaterial.Icon.cmd_file_powerpoint "ppt", "pptx", "odp" -> icon = SzkolnyFont.Icon.szf_file_powerpoint_outline
"pdf" -> icon = CommunityMaterial.Icon.cmd_file_pdf "pdf" -> icon = SzkolnyFont.Icon.szf_file_pdf_outline
"mp3", "wav", "aac" -> icon = CommunityMaterial.Icon.cmd_file_music "mp3", "wav", "aac" -> icon = SzkolnyFont.Icon.szf_file_music_outline
"mp4", "avi", "3gp", "mkv", "flv" -> icon = CommunityMaterial.Icon.cmd_file_video "mp4", "avi", "3gp", "mkv", "flv" -> icon = SzkolnyFont.Icon.szf_file_video_outline
"jpg", "jpeg", "png", "bmp", "gif" -> icon = CommunityMaterial.Icon.cmd_file_image "jpg", "jpeg", "png", "bmp", "gif" -> icon = SzkolnyFont.Icon.szf_file_image_outline
"zip", "rar", "tar", "7z" -> icon = CommunityMaterial.Icon.cmd_file_lock "zip", "rar", "tar", "7z" -> icon = SzkolnyFont.Icon.szf_zip_box_outline
"html", "cpp", "c", "h", "css", "java", "py" -> icon = SzkolnyFont.Icon.szf_file_code_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)))

View File

@ -77,7 +77,11 @@ class MessagesFragment : Fragment() {
b.viewPager.currentItem = pageSelection b.viewPager.currentItem = pageSelection
b.viewPager.clearOnPageChangeListeners() b.viewPager.clearOnPageChangeListeners()
b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {} override fun onPageScrollStateChanged(state: Int) {
if (b.refreshLayout != null) {
b.refreshLayout.isEnabled = state == ViewPager.SCROLL_STATE_IDLE
}
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
pageSelection = position pageSelection = position

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,30 +4,47 @@ 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 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
class TimetableFragment : Fragment() { class TimetableFragment : Fragment(), CoroutineScope {
companion object { companion object {
private const val TAG = "TimetableFragment" private const val TAG = "TimetableFragment"
const val ACTION_SCROLL_TO_DATE = "pl.szczodrzynski.edziennik.timetable.SCROLL_TO_DATE" const val ACTION_SCROLL_TO_DATE = "pl.szczodrzynski.edziennik.timetable.SCROLL_TO_DATE"
const val DEFAULT_START_HOUR = 6
const val DEFAULT_END_HOUR = 19
var pageSelection: Date? = null
} }
private lateinit var app: App private lateinit var app: App
private lateinit var activity: MainActivity private lateinit var activity: MainActivity
private lateinit var b: FragmentTimetableV2Binding private lateinit var b: FragmentTimetableV2Binding
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private var fabShown = false private var fabShown = false
private val items = mutableListOf<Date>() private val items = mutableListOf<Date>()
@ -36,11 +53,13 @@ class TimetableFragment : Fragment() {
if (context == null) if (context == null)
return null return null
app = activity.application as App app = activity.application as App
job = Job()
context!!.theme.applyStyle(Themes.appTheme, true) context!!.theme.applyStyle(Themes.appTheme, true)
if (app.profile == null) if (app.profile == null)
return inflater.inflate(R.layout.fragment_loading, container, false) return inflater.inflate(R.layout.fragment_loading, container, false)
// activity, context and profile is valid // activity, context and profile is valid
b = FragmentTimetableV2Binding.inflate(inflater) b = FragmentTimetableV2Binding.inflate(inflater)
b.refreshLayout.setParent(activity.swipeRefreshLayout)
return b.root return b.root
} }
@ -62,48 +81,64 @@ class TimetableFragment : Fragment() {
activity.unregisterReceiver(broadcastReceiver) activity.unregisterReceiver(broadcastReceiver)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { launch {
// TODO check if app, activity, b can be null // TODO check if app, activity, b can be null
if (app.profile == null || !isAdded) if (app.profile == null || !isAdded)
return return@launch
if (app.profile.loginStoreType == LOGIN_TYPE_LIBRUS && app.profile.getLoginData("timetableNotPublic", false)) { if (app.profile.loginStoreType == LOGIN_TYPE_LIBRUS && app.profile.getLoginData("timetableNotPublic", false)) {
b.timetableLayout.visibility = View.GONE b.timetableLayout.visibility = View.GONE
b.timetableNotPublicLayout.visibility = View.VISIBLE b.timetableNotPublicLayout.visibility = View.VISIBLE
return return@launch
} }
b.timetableLayout.visibility = View.VISIBLE b.timetableLayout.visibility = View.VISIBLE
b.timetableNotPublicLayout.visibility = View.GONE b.timetableNotPublicLayout.visibility = View.GONE
items.clear()
val monthDayCount = listOf(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
val today = Date.getToday().value val today = Date.getToday().value
val yearStart = app.profile.dateSemester1Start?.clone() ?: return var startHour = DEFAULT_START_HOUR
val yearEnd = app.profile.dateYearEnd ?: return var endHour = DEFAULT_END_HOUR
while (yearStart.value <= yearEnd.value) { val deferred = async(Dispatchers.Default) {
items += yearStart.clone() items.clear()
var maxDays = monthDayCount[yearStart.month-1]
if (yearStart.month == 2 && yearStart.isLeap)
maxDays++
yearStart.day++
if (yearStart.day > maxDays) {
yearStart.day = 1
yearStart.month++
}
if (yearStart.month > 12) {
yearStart.month = 1
yearStart.year++
}
}
val pagerAdapter = TimetablePagerAdapter(fragmentManager ?: return, items) val monthDayCount = listOf(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
val yearStart = app.profile.dateSemester1Start?.clone() ?: return@async
val yearEnd = app.profile.dateYearEnd ?: return@async
while (yearStart.value <= yearEnd.value) {
items += yearStart.clone()
var maxDays = monthDayCount[yearStart.month-1]
if (yearStart.month == 2 && yearStart.isLeap)
maxDays++
yearStart.day++
if (yearStart.day > maxDays) {
yearStart.day = 1
yearStart.month++
}
if (yearStart.month > 12) {
yearStart.month = 1
yearStart.year++
}
}
val lessonRanges = app.db.lessonRangeDao().getAllNow(App.profileId)
startHour = lessonRanges.map { it.startTime.hour }.min() ?: DEFAULT_START_HOUR
endHour = lessonRanges.map { it.endTime.hour }.max()?.plus(1) ?: DEFAULT_END_HOUR
}
deferred.await()
val pagerAdapter = TimetablePagerAdapter(
fragmentManager ?: return@launch,
items,
startHour,
endHour
)
b.viewPager.offscreenPageLimit = 2 b.viewPager.offscreenPageLimit = 2
b.viewPager.adapter = pagerAdapter b.viewPager.adapter = pagerAdapter
b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) { override fun onPageScrollStateChanged(state: Int) {
if (b.refreshLayout != null) {
b.refreshLayout.isEnabled = state == ViewPager.SCROLL_STATE_IDLE
}
} }
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
@ -111,6 +146,7 @@ class TimetableFragment : Fragment() {
} }
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
pageSelection = items[position]
activity.navView.bottomBar.fabEnable = items[position].value != today activity.navView.bottomBar.fabEnable = items[position].value != today
if (activity.navView.bottomBar.fabEnable && !fabShown) { if (activity.navView.bottomBar.fabEnable && !fabShown) {
activity.gainAttentionFAB() activity.gainAttentionFAB()
@ -122,11 +158,40 @@ class TimetableFragment : Fragment() {
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 == 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(pl.szczodrzynski.edziennik.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

@ -8,19 +8,26 @@ import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.day.TimetableDayFragme
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Week import pl.szczodrzynski.edziennik.utils.models.Week
class TimetablePagerAdapter(val fragmentManager: FragmentManager, val items: List<Date>) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { class TimetablePagerAdapter(
fragmentManager: FragmentManager,
private val items: List<Date>,
private val startHour: Int,
private val endHour: Int
) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
companion object { companion object {
private const val TAG = "TimetablePagerAdapter" private const val TAG = "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 {
return TimetableDayFragment().apply { return TimetableDayFragment().apply {
arguments = Bundle().apply { arguments = Bundle().apply {
putInt("date", items[position].value) putInt("date", items[position].value)
putInt("startHour", startHour)
putInt("endHour", endHour)
} }
} }
/*return TimetableDayFragment().apply { /*return TimetableDayFragment().apply {
@ -42,4 +49,4 @@ class TimetablePagerAdapter(val fragmentManager: FragmentManager, val items: Lis
} }
return pageTitle return pageTitle
} }
} }

View File

@ -7,42 +7,97 @@ import android.util.Log
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.FrameLayout
import android.widget.TextView import android.widget.TextView
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
import androidx.core.view.setPadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.linkedin.android.tachyon.DayView import com.linkedin.android.tachyon.DayView
import com.linkedin.android.tachyon.DayViewConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
import pl.szczodrzynski.edziennik.api.v2.LOGIN_TYPE_LIBRUS import pl.szczodrzynski.edziennik.api.v2.LOGIN_TYPE_LIBRUS
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.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.FragmentTimetableV2DayBinding
import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding
import pl.szczodrzynski.edziennik.databinding.TimetableNoTimetableBinding
import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog
import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.TimetableFragment.Companion.DEFAULT_END_HOUR
import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.TimetableFragment.Companion.DEFAULT_START_HOUR
import pl.szczodrzynski.edziennik.utils.ListenerScrollView
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.navlib.getColorFromAttr import pl.szczodrzynski.navlib.getColorFromAttr
import java.util.* import java.util.*
import kotlin.coroutines.CoroutineContext
import kotlin.math.min import kotlin.math.min
class TimetableDayFragment() : Fragment() { class TimetableDayFragment : Fragment(), CoroutineScope {
companion object { companion object {
private const val TAG = "TimetableDayFragment" private const val TAG = "TimetableDayFragment"
} }
private lateinit var app: App private lateinit var app: App
private lateinit var activity: MainActivity private lateinit var activity: MainActivity
private lateinit var b: FragmentTimetableV2DayBinding private lateinit var inflater: AsyncLayoutInflater
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private lateinit var date: Date private lateinit var date: Date
private var startHour = DEFAULT_START_HOUR
private var endHour = DEFAULT_END_HOUR
private var firstEventMinute = 24*60
// find SwipeRefreshLayout in the hierarchy
private val refreshLayout by lazy { view?.findParentById(R.id.refreshLayout) }
// the day ScrollView
private val dayScrollDelegate = lazy {
val dayScroll = ListenerScrollView(context!!)
dayScroll.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
dayScroll.setOnRefreshLayoutEnabledListener { enabled ->
refreshLayout?.isEnabled = enabled
}
dayScroll
}
private val dayScroll by dayScrollDelegate
// the lesson DayView
private val dayView by lazy {
val dayView = DayView(context!!, DayViewConfig(
startHour = startHour,
endHour = endHour,
dividerHeight = 1.dp,
halfHourHeight = 60.dp,
hourDividerColor = R.attr.hourDividerColor.resolveAttr(context),
halfHourDividerColor = R.attr.halfHourDividerColor.resolveAttr(context),
hourLabelWidth = 40.dp,
hourLabelMarginEnd = 10.dp,
eventMargin = 2.dp
), true)
dayView.setPadding(10.dp)
dayScroll.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
dayScroll.addView(dayView)
dayView
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as MainActivity?) ?: return null activity = (getActivity() as MainActivity?) ?: return null
if (context == null) if (context == null)
return null return null
app = activity.application as App app = activity.application as App
b = FragmentTimetableV2DayBinding.inflate(inflater) job = Job()
this.inflater = AsyncLayoutInflater(context!!)
date = arguments?.getInt("date")?.let { Date.fromValue(it) } ?: Date.getToday() date = arguments?.getInt("date")?.let { Date.fromValue(it) } ?: Date.getToday()
return b.root startHour = arguments?.getInt("startHour") ?: DEFAULT_START_HOUR
endHour = arguments?.getInt("endHour") ?: DEFAULT_END_HOUR
return FrameLayout(activity).apply {
layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
}
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -52,45 +107,42 @@ class TimetableDayFragment() : Fragment() {
Log.d(TAG, "onViewCreated, date=$date") Log.d(TAG, "onViewCreated, date=$date")
// Inflate a label view for each hour the day view will display // observe lesson database
val hourLabelViews = ArrayList<View>()
for (i in b.day.startHour..b.day.endHour) {
val hourLabelView = layoutInflater.inflate(R.layout.timetable_hour_label, b.day, false) as TextView
hourLabelView.text = "$i:00"
hourLabelViews.add(hourLabelView)
}
b.day.setHourLabelViews(hourLabelViews)
app.db.timetableDao().getForDate(App.profileId, date).observe(this, Observer<List<LessonFull>> { lessons -> app.db.timetableDao().getForDate(App.profileId, date).observe(this, Observer<List<LessonFull>> { lessons ->
buildLessonViews(lessons) processLessonList(lessons)
}) })
} }
private fun buildLessonViews(lessons: List<LessonFull>) { private fun processLessonList(lessons: List<LessonFull>) {
// no lessons - timetable not downloaded yet
if (lessons.isEmpty()) { if (lessons.isEmpty()) {
b.dayScroll.visibility = View.GONE inflater.inflate(R.layout.timetable_no_timetable, view as FrameLayout) { view, _, parent ->
b.noTimetableLayout.visibility = View.VISIBLE parent?.removeAllViews()
b.noLessonsLayout.visibility = View.GONE parent?.addView(view)
val weekStart = date.clone().stepForward(0, 0, -date.weekDay).stringY_m_d val b = TimetableNoTimetableBinding.bind(view)
b.noTimetableSync.onClick { val weekStart = date.weekStart.stringY_m_d
it.isEnabled = false b.noTimetableSync.onClick {
EdziennikTask.syncProfile( it.isEnabled = false
profileId = App.profileId, EdziennikTask.syncProfile(
viewIds = listOf( profileId = App.profileId,
DRAWER_ITEM_TIMETABLE to 0 viewIds = listOf(
), DRAWER_ITEM_TIMETABLE to 0
arguments = JsonObject( ),
"weekStart" to weekStart arguments = JsonObject(
) "weekStart" to weekStart
).enqueue(activity) )
).enqueue(activity)
}
b.noTimetableWeek.setText(R.string.timetable_no_timetable_week, weekStart)
} }
b.noTimetableWeek.setText(R.string.timetable_no_timetable_week, weekStart)
return return
} }
// one lesson indicating a day without lessons
if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) { if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) {
b.dayScroll.visibility = View.GONE inflater.inflate(R.layout.timetable_no_lessons, view as FrameLayout) { view, _, parent ->
b.noTimetableLayout.visibility = View.GONE parent?.removeAllViews()
b.noLessonsLayout.visibility = View.VISIBLE parent?.addView(view)
}
return return
} }
@ -101,23 +153,37 @@ class TimetableDayFragment() : Fragment() {
return return
} }
b.dayScroll.visibility = View.VISIBLE // clear the root view and add the ScrollView
b.noTimetableLayout.visibility = View.GONE (view as FrameLayout).removeAllViews()
b.noLessonsLayout.visibility = View.GONE (view as FrameLayout).addView(dayScroll)
var firstEventMinute = 24*60 // Inflate a label view for each hour the day view will display
val hourLabelViews = ArrayList<View>()
for (i in dayView.startHour..dayView.endHour) {
val hourLabelView = layoutInflater.inflate(R.layout.timetable_hour_label, dayView, false) as TextView
hourLabelView.text = "$i:00"
hourLabelViews.add(hourLabelView)
}
dayView.setHourLabelViews(hourLabelViews)
buildLessonViews(lessons)
}
private fun buildLessonViews(lessons: List<LessonFull>) {
if (!isAdded)
return
val eventViews = mutableListOf<View>() val eventViews = mutableListOf<View>()
val eventTimeRanges = mutableListOf<DayView.EventTimeRange>() val eventTimeRanges = mutableListOf<DayView.EventTimeRange>()
// Reclaim all of the existing event views so we can reuse them if needed, this process // Reclaim all of the existing event views so we can reuse them if needed, this process
// can be useful if your day view is hosted in a recycler view for example // can be useful if your day view is hosted in a recycler view for example
val recycled = b.day.removeEventViews() val recycled = dayView.removeEventViews()
var remaining = recycled?.size ?: 0 var remaining = recycled?.size ?: 0
val arrowRight = "" val arrowRight = ""
val bullet = "" val bullet = ""
val colorSecondary = getColorFromAttr(activity, android.R.attr.textColorSecondary) val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
for (lesson in lessons) { for (lesson in lessons) {
val startTime = lesson.displayStartTime ?: continue val startTime = lesson.displayStartTime ?: continue
@ -127,7 +193,7 @@ class TimetableDayFragment() : Fragment() {
// Try to recycle an existing event view if there are enough left, otherwise inflate // Try to recycle an existing event view if there are enough left, otherwise inflate
// a new one // a new one
val eventView = (if (remaining > 0) recycled?.get(--remaining) else layoutInflater.inflate(R.layout.timetable_lesson, b.day, false)) val eventView = (if (remaining > 0) recycled?.get(--remaining) else layoutInflater.inflate(R.layout.timetable_lesson, dayView, false))
?: continue ?: continue
val lb = TimetableLessonBinding.bind(eventView) val lb = TimetableLessonBinding.bind(eventView)
eventViews += eventView eventViews += eventView
@ -181,6 +247,8 @@ class TimetableDayFragment() : Fragment() {
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 -> {
@ -274,9 +342,16 @@ class TimetableDayFragment() : Fragment() {
eventTimeRanges.add(DayView.EventTimeRange(startMinute, endMinute)) eventTimeRanges.add(DayView.EventTimeRange(startMinute, endMinute))
} }
val minuteHeight = (b.day.getHourTop(1) - b.day.getHourTop(0)).toFloat() / 60f dayView.setEventViews(eventViews, eventTimeRanges)
val firstEventTop = (firstEventMinute - b.day.startHour * 60) * minuteHeight val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight
b.day.setEventViews(eventViews, eventTimeRanges) dayScroll.scrollTo(0, firstEventTop.toInt())
b.dayScroll.scrollTo(0, firstEventTop.toInt()) }
override fun onResume() {
super.onResume()
if (dayScrollDelegate.isInitialized()) {
val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight
dayScroll.scrollTo(0, firstEventTop.toInt())
}
} }
} }

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-16.
*/
package pl.szczodrzynski.edziennik.utils
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.ScrollView
class ListenerScrollView(
context: Context,
attrs: AttributeSet? = null
) : ScrollView(context, attrs) {
private var onScrollChangedListener: ((v: ListenerScrollView, scrollX: Int, scrollY: Int, oldScrollX: Int, oldScrollY: Int) -> Unit)? = null
private var onRefreshLayoutEnabledListener: ((enabled: Boolean) -> Unit)? = null
private var refreshLayoutEnabled = true
init {
setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_UP) {
refreshLayoutEnabled = scrollY < 10
onRefreshLayoutEnabledListener?.invoke(refreshLayoutEnabled)
}
false
}
}
override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
onScrollChangedListener?.invoke(this, l, t, oldl, oldt)
if (t > 10 && refreshLayoutEnabled) {
refreshLayoutEnabled = false
onRefreshLayoutEnabledListener?.invoke(refreshLayoutEnabled)
}
}
fun setOnScrollChangedListener(l: ((v: ListenerScrollView, scrollX: Int, scrollY: Int, oldScrollX: Int, oldScrollY: Int) -> Unit)?) {
onScrollChangedListener = l
}
fun setOnRefreshLayoutEnabledListener(l: ((enabled: Boolean) -> Unit)?) {
onRefreshLayoutEnabledListener = l
}
}

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:

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

@ -173,7 +173,8 @@ public class WidgetTimetableListProvider implements RemoteViewsService.RemoteVie
intent.putExtra("endTime", lesson.endTime.getStringValue()); intent.putExtra("endTime", lesson.endTime.getStringValue());
views.setOnClickFillInIntent(R.id.widgetTimetableRoot, intent); views.setOnClickFillInIntent(R.id.widgetTimetableRoot, intent);
views.setTextViewText(R.id.widgetTimetableTime, lesson.startTime.getStringHM() + " - " + lesson.endTime.getStringHM()); if (lesson.startTime != null && lesson.endTime != null)
views.setTextViewText(R.id.widgetTimetableTime, lesson.startTime.getStringHM() + " - " + lesson.endTime.getStringHM());
views.setViewVisibility(R.id.widgetTimetableEvent1, View.GONE); views.setViewVisibility(R.id.widgetTimetableEvent1, View.GONE);
views.setViewVisibility(R.id.widgetTimetableEvent2, View.GONE); views.setViewVisibility(R.id.widgetTimetableEvent2, View.GONE);

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>

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

@ -4,11 +4,17 @@
--> -->
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout <LinearLayout
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:focusable="true" android:focusable="true"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
android:padding="24dp"> android:padding="24dp">
@ -46,41 +52,63 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_team"> android:hint="@string/dialog_event_manual_team">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown <pl.szczodrzynski.edziennik.utils.TextInputDropDown
android:id="@+id/teamDropdown" android:id="@+id/teamDropdown"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:enabled="false" android:enabled="false"
tools:text="2b3T"/> tools:text="2b3T" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.switchmaterial.SwitchMaterial
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense" android:id="@+id/shareSwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dialog_event_manual_share_enabled" />
<TextView
android:id="@+id/shareDetails"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dialog_event_manual_share_first_notice"
android:visibility="gone"
tools:visibility="visible" />
<LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_subject"> android:baselineAligned="false"
<pl.szczodrzynski.edziennik.utils.TextInputDropDown android:orientation="horizontal">
android:id="@+id/subjectDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
tools:text="2b3T"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_teacher">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
android:id="@+id/teacherDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:enabled="false" android:layout_weight="1"
tools:text="2b3T"/> android:hint="@string/dialog_event_manual_type">
</com.google.android.material.textfield.TextInputLayout>
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
android:id="@+id/typeDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
tools:text="2b3T" />
</com.google.android.material.textfield.TextInputLayout>
<FrameLayout
android:id="@+id/typeColor"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical"
android:layout_marginTop="2dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:background="@drawable/bg_circle"
android:foreground="?selectableItemBackgroundBorderless" />
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
@ -89,7 +117,7 @@
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_topic"> android:hint="@string/dialog_event_manual_topic">
<com.google.android.material.textfield.TextInputEditText <pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit
android:id="@+id/topic" android:id="@+id/topic"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -98,5 +126,64 @@
tools:text="2b3T" /> tools:text="2b3T" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.mikepenz.iconics.view.IconicsCheckableTextView
android:id="@+id/showMore"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:text="@string/dialog_event_manual_more_options"
android:background="?selectableItemBackground"
app:iiv_end_icon="cmd-chevron-down"
app:iiv_end_color="?android:textColorSecondary"
app:iiv_end_size="16dp"
app:iiv_end_checked_icon="cmd-chevron-up"
app:iiv_end_checked_color="?android:textColorSecondary"
app:iiv_end_checked_size="16dp"/>
<LinearLayout
android:id="@+id/moreLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_subject">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
android:id="@+id/subjectDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
tools:text="2b3T" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_teacher">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
android:id="@+id/teacherDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
tools:text="2b3T" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</LinearLayout> </LinearLayout>
</ScrollView>
</layout> </layout>

View File

@ -21,217 +21,207 @@
<variable name="oldTeamName" type="String" /> <variable name="oldTeamName" type="String" />
<variable name="teamName" type="String" /> <variable name="teamName" type="String" />
</data> </data>
<LinearLayout <ScrollView
android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:padding="24dp">
<LinearLayout <LinearLayout
android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:padding="24dp">
android:baselineAligned="false">
<LinearLayout <LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{oldSubjectName}"
android:textIsSelectable="true"
android:textAppearance="@style/NavView.TextView.Medium"
android:textColor="?android:textColorTertiary"
android:visibility="@{oldSubjectName == null ? View.GONE : View.VISIBLE}"
app:strikeThrough="@{true}"
tools:text="pracownia urządzeń techniki komputerowej" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{subjectName}"
android:textIsSelectable="true"
android:textAppearance="@style/NavView.TextView.Title"
android:visibility="@{subjectName == null ? View.GONE : View.VISIBLE}"
tools:text="pracownia urządzeń techniki komputerowej" />
<TextView
android:id="@+id/lessonDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
android:textAppearance="@style/NavView.TextView.Subtitle"
tools:text="czwartek, 14 listopada 2019"
tools:visibility="visible" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_lesson_details_number"
android:visibility="@{lesson.displayLessonNumber == null ? View.GONE : View.VISIBLE}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:text="@{lesson.displayLessonNumber.toString()}"
android:textSize="36sp"
android:visibility="@{lesson.displayLessonNumber == null ? View.GONE : View.VISIBLE}"
tools:text="4" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{lesson.displayStartTime.stringHM + ` - ` + lesson.displayEndTime.stringHM}"
tools:text="14:55 - 15:40" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/shiftedLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:baselineAligned="false"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/shiftedText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="16sp"
android:textStyle="italic"
tools:text="Lekcja przeniesiona na czwartek, 17 października" />
<Button
android:id="@+id/shiftedGoTo"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Przejdź" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_lesson_details_teacher"
android:visibility="@{teacherName != null || oldTeacherName != null ? View.VISIBLE : View.GONE}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@{oldTeacherName}"
android:textIsSelectable="true"
android:visibility="@{oldTeacherName != null ? View.VISIBLE : View.GONE}"
app:strikeThrough="@{true}"
tools:text="Janósz Kowalski" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{teacherName}"
android:textIsSelectable="true"
android:visibility="@{teacherName != null ? View.VISIBLE : View.GONE}"
tools:text="Janósz Kowalski" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_lesson_details_classroom"
android:visibility="@{classroom != null || oldClassroom != null ? View.VISIBLE : View.GONE}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@{oldClassroom}"
android:textIsSelectable="true"
android:visibility="@{oldClassroom != null ? View.VISIBLE : View.GONE}"
app:strikeThrough="@{true}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{classroom}"
android:textIsSelectable="true"
android:visibility="@{classroom != null ? View.VISIBLE : View.GONE}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_lesson_details_team"
android:visibility="@{teamName != null || oldTeamName != null ? View.VISIBLE : View.GONE}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@{oldTeamName}"
android:textIsSelectable="true"
android:visibility="@{oldTeamName != null ? View.VISIBLE : View.GONE}"
app:strikeThrough="@{true}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{teamName}"
android:textIsSelectable="true"
android:visibility="@{teamName != null ? View.VISIBLE : View.GONE}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
android:visibility="@{App.devMode ? View.VISIBLE : View.GONE}"
android:text="@string/dialog_lesson_details_id" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:text="@{Long.toString(lesson.id)}"
android:textIsSelectable="true"
android:visibility="@{App.devMode ? View.VISIBLE : View.GONE}"
tools:text="12345" />
<!--<androidx.core.widget.NestedScrollView
android:id="@+id/gradeHistoryNest"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="@{historyVisible ? View.VISIBLE : View.GONE}">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/gradeHistoryList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:listitem="@layout/row_grades_list_item" /> android:orientation="horizontal"
android:baselineAligned="false">
</androidx.core.widget.NestedScrollView>--> <LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1">
</LinearLayout> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{oldSubjectName}"
android:textIsSelectable="true"
android:textAppearance="@style/NavView.TextView.Medium"
android:textColor="?android:textColorTertiary"
android:visibility="@{oldSubjectName == null ? View.GONE : View.VISIBLE}"
app:strikeThrough="@{true}"
tools:text="pracownia urządzeń techniki komputerowej" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{subjectName}"
android:textIsSelectable="true"
android:textAppearance="@style/NavView.TextView.Title"
android:visibility="@{subjectName == null ? View.GONE : View.VISIBLE}"
tools:text="pracownia urządzeń techniki komputerowej" />
<TextView
android:id="@+id/lessonDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
android:textAppearance="@style/NavView.TextView.Subtitle"
tools:text="czwartek, 14 listopada 2019"
tools:visibility="visible" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_lesson_details_number"
android:visibility="@{lesson.displayLessonNumber == null ? View.GONE : View.VISIBLE}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:text="@{lesson.displayLessonNumber.toString()}"
android:textSize="36sp"
android:visibility="@{lesson.displayLessonNumber == null ? View.GONE : View.VISIBLE}"
tools:text="4" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{lesson.displayStartTime.stringHM + ` - ` + lesson.displayEndTime.stringHM}"
tools:text="14:55 - 15:40" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/shiftedLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:baselineAligned="false"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/shiftedText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="16sp"
android:textStyle="italic"
tools:text="Lekcja przeniesiona na czwartek, 17 października" />
<Button
android:id="@+id/shiftedGoTo"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Przejdź" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_lesson_details_teacher"
android:visibility="@{teacherName != null || oldTeacherName != null ? View.VISIBLE : View.GONE}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@{oldTeacherName}"
android:textIsSelectable="true"
android:visibility="@{oldTeacherName != null ? View.VISIBLE : View.GONE}"
app:strikeThrough="@{true}"
tools:text="Janósz Kowalski" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{teacherName}"
android:textIsSelectable="true"
android:visibility="@{teacherName != null ? View.VISIBLE : View.GONE}"
tools:text="Janósz Kowalski" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_lesson_details_classroom"
android:visibility="@{classroom != null || oldClassroom != null ? View.VISIBLE : View.GONE}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@{oldClassroom}"
android:textIsSelectable="true"
android:visibility="@{oldClassroom != null ? View.VISIBLE : View.GONE}"
app:strikeThrough="@{true}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{classroom}"
android:textIsSelectable="true"
android:visibility="@{classroom != null ? View.VISIBLE : View.GONE}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_lesson_details_team"
android:visibility="@{teamName != null || oldTeamName != null ? View.VISIBLE : View.GONE}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@{oldTeamName}"
android:textIsSelectable="true"
android:visibility="@{oldTeamName != null ? View.VISIBLE : View.GONE}"
app:strikeThrough="@{true}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{teamName}"
android:textIsSelectable="true"
android:visibility="@{teamName != null ? View.VISIBLE : View.GONE}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
android:visibility="@{App.devMode ? View.VISIBLE : View.GONE}"
android:text="@string/dialog_lesson_details_id" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:text="@{Long.toString(lesson.id)}"
android:textIsSelectable="true"
android:visibility="@{App.devMode ? View.VISIBLE : View.GONE}"
tools:text="12345" />
</LinearLayout>
</ScrollView>
</layout> </layout>

View File

@ -1,35 +1,29 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" <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">
<androidx.constraintlayout.widget.ConstraintLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/notificationsView" android:id="@+id/notificationsView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
tools:listitem="@layout/row_notifications_item"
</androidx.recyclerview.widget.RecyclerView> tools:visibility="gone" />
<TextView <TextView
android:id="@+id/notificationsNoData" android:id="@+id/notificationsNoData"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_gravity="center"
android:layout_marginLeft="8dp" android:drawableTop="@drawable/ic_no_notifications"
android:layout_marginTop="8dp" android:drawablePadding="16dp"
android:layout_marginEnd="8dp" android:fontFamily="sans-serif-light"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:text="@string/notifications_no_data" android:text="@string/notifications_no_data"
android:textSize="18sp" android:textSize="24sp"
android:textStyle="italic"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" tools:visibility="visible" />
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </FrameLayout>
</layout> </layout>

View File

@ -2,89 +2,96 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<FrameLayout
<pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator
android:id="@+id/refreshLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout <FrameLayout
android:id="@+id/timetableLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
tools:visibility="gone">
<com.google.android.material.appbar.AppBarLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent" android:id="@+id/timetableLayout"
android:layout_height="wrap_content"
android:background="?colorSurface"
style="@style/Widget.MaterialComponents.AppBarLayout.Surface">
<com.nshmura.recyclertablayout.RecyclerTabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@color/colorSurface_6dp"
app:rtl_tabTextAppearance="@style/rtl_RecyclerTabLayout.Tab"
app:rtl_tabIndicatorColor="?colorPrimary"
app:rtl_tabMinWidth="90dp"
app:rtl_tabMaxWidth="300dp"
app:rtl_tabSelectedTextColor="?colorPrimary"
app:rtl_tabPaddingStart="16dp"
app:rtl_tabPaddingEnd="16dp"
app:rtl_tabPaddingTop="12dp"
app:rtl_tabPaddingBottom="12dp"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" /> tools:visibility="gone">
</androidx.coordinatorlayout.widget.CoordinatorLayout> <com.google.android.material.appbar.AppBarLayout
style="@style/Widget.MaterialComponents.AppBarLayout.Surface"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorSurface">
<LinearLayout <com.nshmura.recyclertablayout.RecyclerTabLayout
android:id="@+id/timetableNotPublicLayout" android:id="@+id/tabLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="48dp"
android:orientation="vertical" android:background="@color/colorSurface_6dp"
android:visibility="gone" app:rtl_tabIndicatorColor="?colorPrimary"
android:gravity="center" app:rtl_tabMaxWidth="300dp"
tools:visibility="visible"> app:rtl_tabMinWidth="90dp"
app:rtl_tabPaddingBottom="12dp"
app:rtl_tabPaddingEnd="16dp"
app:rtl_tabPaddingStart="16dp"
app:rtl_tabPaddingTop="12dp"
app:rtl_tabSelectedTextColor="?colorPrimary"
app:rtl_tabTextAppearance="@style/rtl_RecyclerTabLayout.Tab" />
</com.google.android.material.appbar.AppBarLayout>
<ImageView <androidx.viewpager.widget.ViewPager
android:layout_width="wrap_content" android:id="@+id/viewPager"
android:layout_height="wrap_content" android:layout_width="match_parent"
app:srcCompat="@drawable/ic_no_timetable" /> android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<TextView </androidx.coordinatorlayout.widget.CoordinatorLayout>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_not_public_title"
android:textSize="24sp" />
<TextView <LinearLayout
android:layout_width="wrap_content" android:id="@+id/timetableNotPublicLayout"
android:layout_height="wrap_content" android:layout_width="match_parent"
android:layout_marginTop="16dp" android:layout_height="match_parent"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:gravity="center" android:gravity="center"
android:text="@string/timetable_not_public_text" android:orientation="vertical"
android:textSize="16sp" /> android:visibility="gone"
tools:visibility="visible">
<TextView <ImageView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" app:srcCompat="@drawable/ic_no_timetable" />
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/timetable_not_public_hint"
android:textSize="14sp" />
</LinearLayout> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_not_public_title"
android:textSize="24sp" />
</FrameLayout> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/timetable_not_public_text"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/timetable_not_public_hint"
android:textSize="14sp" />
</LinearLayout>
</FrameLayout>
</pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator>
</layout> </layout>

View File

@ -28,126 +28,5 @@
tools:visibility="gone"/> tools:visibility="gone"/>
</ScrollView> </ScrollView>
<LinearLayout
android:id="@+id/noLessonsLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone"
android:gravity="center"
tools:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_timetable" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_no_lessons_title"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/freeDayLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone"
android:gravity="center"
tools:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_sunbed" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_free_day_title"
android:textSize="24sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timetable_free_day_text"
android:textSize="14sp" />
<TextView
android:id="@+id/freeDayText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:gravity="center"
tools:text="Dzień wolny dla szkoły z puli dyrektorskiej z okazji obchodów Światowego Dnia Wtorku w mieście Poznań i na przedmieśiach"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/freeDayShowTimetable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timetable_free_day_show" />
</LinearLayout>
<LinearLayout
android:id="@+id/noTimetableLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone"
android:gravity="center"
tools:visibility="visible">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_sync" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_no_timetable_title"
android:textSize="24sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/timetable_no_timetable_text"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/noTimetableSync"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timetable_no_timetable_sync" />
<TextView
android:id="@+id/noTimetableWeek"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="@string/timetable_no_timetable_week"
android:textSize="12sp"
android:textStyle="italic"/>
</LinearLayout>
</FrameLayout> </FrameLayout>
</layout> </layout>

View File

@ -1,75 +1,35 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout 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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical"
android:padding="8dp"
android:background="?selectableItemBackground">
<com.google.android.material.card.MaterialCardView <TextView
android:id="@+id/notificationsItemCard" android:id="@+id/title"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:textAppearance="@style/NavView.TextView.Medium"
android:background="?selectableItemBackground" tools:text="Dzisiaj 1 to szczęśliwy numerek" />
app:cardElevation="4dp"
app:cardCornerRadius="5dp"
android:paddingLeft="10dp"
android:paddingTop="5dp"
android:paddingRight="10dp"
android:paddingBottom="3dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content">
android:background="?selectableItemBackground"
android:orientation="vertical"
android:padding="8dp">
<TextView <TextView
android:id="@+id/notificationsItemDate" android:id="@+id/profileDate"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:layout_weight="1"
android:textSize="16sp" tools:text="Władca Androida • 22 listopada" />
tools:text="date" />
<LinearLayout <TextView
android:layout_width="match_parent" android:id="@+id/type"
android:layout_height="wrap_content" android:layout_width="wrap_content"
android:orientation="horizontal"> android:layout_height="wrap_content"
tools:text="Szczęśliwy numerek" />
<TextView </LinearLayout>
android:id="@+id/notificationsItemText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:autoLink="all"
tools:text="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/notificationsItemTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Small"
tools:text="title" />
<TextView
android:id="@+id/notificationsItemType"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Small"
tools:text="type" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-15.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/freeDayLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="@drawable/ic_sunbed"
android:drawablePadding="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_free_day_title"
android:textSize="24sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timetable_free_day_text"
android:textSize="14sp" />
<TextView
android:id="@+id/freeDayText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:textSize="16sp"
tools:text="Dzień wolny dla szkoły z puli dyrektorskiej z okazji obchodów Światowego Dnia Wtorku w mieście Poznań i na przedmieśiach" />
<com.google.android.material.button.MaterialButton
android:id="@+id/freeDayShowTimetable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timetable_free_day_show" />
</LinearLayout>

View File

@ -11,6 +11,9 @@
<variable <variable
name="lessonNumber" name="lessonNumber"
type="Integer" /> type="Integer" />
<variable
name="unread"
type="boolean" />
</data> </data>
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -70,6 +73,17 @@
tools:maxLines="2" tools:maxLines="2"
tools:text="pracownia urządzeń techniki komputerowej" /> tools:text="pracownia urządzeń techniki komputerowej" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
android:visibility="@{unread ? View.VISIBLE : View.GONE}"
app:srcCompat="@drawable/unread_red_circle" />
<ImageView <ImageView
android:id="@+id/attendanceIcon" android:id="@+id/attendanceIcon"
android:layout_width="24dp" android:layout_width="24dp"
@ -136,4 +150,4 @@
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>
</layout> </layout>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-15.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:drawableTop="@drawable/ic_timetable"
android:drawablePadding="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_no_lessons_title"
android:textSize="24sp" />

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-15.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:id="@+id/noTimetableLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="@drawable/ic_sync"
android:drawablePadding="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_no_timetable_title"
android:textSize="24sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/timetable_no_timetable_text"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/noTimetableSync"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timetable_no_timetable_sync" />
<TextView
android:id="@+id/noTimetableWeek"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textStyle="italic"
tools:text="@string/timetable_no_timetable_week" />
</LinearLayout>
</layout>

View File

@ -547,7 +547,7 @@
<string name="settings_about_licenses_text">Open-source licenses</string> <string name="settings_about_licenses_text">Open-source licenses</string>
<string name="settings_about_privacy_policy_text">Privacy policy</string> <string name="settings_about_privacy_policy_text">Privacy policy</string>
<string name="settings_about_register_title_text">E-register</string> <string name="settings_about_register_title_text">E-register</string>
<string name="settings_about_title_subtext">© Kuba Szczodrzyński September 2018 - October 2019</string> <string name="settings_about_title_subtext">© Kuba Szczodrzyński &amp;&amp; Kacper Ziubryniewicz\nSeptember 2018 - November 2019</string>
<string name="settings_about_update_subtext">Click to check for updates</string> <string name="settings_about_update_subtext">Click to check for updates</string>
<string name="settings_about_update_text">Update</string> <string name="settings_about_update_text">Update</string>
<string name="settings_about_version_text">Version</string> <string name="settings_about_version_text">Version</string>

View File

@ -72,6 +72,12 @@
<string name="error_173" translatable="false">ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED</string> <string name="error_173" translatable="false">ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED</string>
<string name="error_174" translatable="false">ERROR_LIBRUS_SYNERGIA_OTHER</string> <string name="error_174" translatable="false">ERROR_LIBRUS_SYNERGIA_OTHER</string>
<string name="error_175" translatable="false">ERROR_LIBRUS_SYNERGIA_MAINTENANCE</string> <string name="error_175" translatable="false">ERROR_LIBRUS_SYNERGIA_MAINTENANCE</string>
<string name="error_176" translatable="false">ERROR_LIBRUS_MESSAGES_MAINTENANCE</string>
<string name="error_177" translatable="false">ERROR_LIBRUS_MESSAGES_ERROR</string>
<string name="error_178" translatable="false">ERROR_LIBRUS_MESSAGES_OTHER</string>
<string name="error_179" translatable="false">ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN</string>
<string name="error_180" translatable="false">ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN</string>
<string name="error_181" translatable="false">ERROR_LIBRUS_API_MAINTENANCE</string>
<string name="error_201" translatable="false">ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN</string> <string name="error_201" translatable="false">ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN</string>
<string name="error_202" translatable="false">ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD</string> <string name="error_202" translatable="false">ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD</string>
@ -165,7 +171,7 @@
<string name="error_127_reason">Wymagana akceptacja regulaminu</string> <string name="error_127_reason">Wymagana akceptacja regulaminu</string>
<string name="error_128_reason">Błąd zmiany hasła</string> <string name="error_128_reason">Błąd zmiany hasła</string>
<string name="error_129_reason">Wymagana zmiana hasła</string> <string name="error_129_reason">Wymagana zmiana hasła</string>
<string name="error_130_reason">Nieprawidłowe dane logowania</string> <string name="error_130_reason">Librus API: nieprawidłowe dane logowania</string>
<string name="error_131_reason">Inny błąd logowania do API</string> <string name="error_131_reason">Inny błąd logowania do API</string>
<string name="error_132_reason">Brak tokenu CSRF</string> <string name="error_132_reason">Brak tokenu CSRF</string>
<string name="error_133_reason">Konto LIBRUS nie zostało aktywowane</string> <string name="error_133_reason">Konto LIBRUS nie zostało aktywowane</string>
@ -206,7 +212,13 @@
<string name="error_172_reason">ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_INVALID</string> <string name="error_172_reason">ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_INVALID</string>
<string name="error_173_reason">ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED</string> <string name="error_173_reason">ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED</string>
<string name="error_174_reason">ERROR_LIBRUS_SYNERGIA_OTHER</string> <string name="error_174_reason">ERROR_LIBRUS_SYNERGIA_OTHER</string>
<string name="error_175_reason">ERROR_LIBRUS_SYNERGIA_MAINTENANCE</string> <string name="error_175_reason">Librus Synergia: przerwa techniczna</string>
<string name="error_176_reason">Librus Wiadomości: przerwa techniczna</string>
<string name="error_177_reason">ERROR_LIBRUS_MESSAGES_ERROR</string>
<string name="error_178_reason">ERROR_LIBRUS_MESSAGES_OTHER</string>
<string name="error_179_reason">Librus Wiadomości: nieprawidłowe dane logowania</string>
<string name="error_180_reason">Librus Portal: nieprawidłowe dane logowania</string>
<string name="error_181_reason">Librus API: przerwa techniczna</string>
<string name="error_201_reason">ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN</string> <string name="error_201_reason">ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN</string>
<string name="error_202_reason">ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD</string> <string name="error_202_reason">ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD</string>

View File

@ -593,7 +593,7 @@
<string name="settings_about_licenses_text">Licencje open-source</string> <string name="settings_about_licenses_text">Licencje open-source</string>
<string name="settings_about_privacy_policy_text">Polityka prywatności</string> <string name="settings_about_privacy_policy_text">Polityka prywatności</string>
<string name="settings_about_register_title_text">E-dziennik</string> <string name="settings_about_register_title_text">E-dziennik</string>
<string name="settings_about_title_subtext">© Kuba Szczodrzyński wrzesień 2018 - październik 2019</string> <string name="settings_about_title_subtext">© Kuba Szczodrzyński &amp;&amp; Kacper Ziubryniewicz\nwrzesień 2018 - listopad 2019</string>
<string name="settings_about_update_subtext">Kliknij, aby sprawdzić aktualizacje</string> <string name="settings_about_update_subtext">Kliknij, aby sprawdzić aktualizacje</string>
<string name="settings_about_update_text">Aktualizacja</string> <string name="settings_about_update_text">Aktualizacja</string>
<string name="settings_about_version_text">Wersja</string> <string name="settings_about_version_text">Wersja</string>
@ -1026,4 +1026,9 @@
<string name="snackbar_error_text">Wystąpił błąd</string> <string name="snackbar_error_text">Wystąpił błąd</string>
<string name="dialog_sync_view_list_title">Synchronizacja ręczna</string> <string name="dialog_sync_view_list_title">Synchronizacja ręczna</string>
<string name="timetable_no_subject_name">(brak nazwy)</string> <string name="timetable_no_subject_name">(brak nazwy)</string>
<string name="dialog_event_manual_more_options">Więcej opcji</string>
<string name="edziennik_progress_endpoint_grade_comments">Pobieranie komentarzy ocen...</string>
<string name="menu_remove_notifications">Usuń wszystkie</string>
<string name="menu_remove_notifications_success">Wyczyszczono powiadomienia</string>
<string name="timetable_select_day">Wybierz dzień</string>
</resources> </resources>

View File

@ -101,6 +101,8 @@
<item name="timetable_lesson_change_color">#ffb300</item> <item name="timetable_lesson_change_color">#ffb300</item>
<item name="timetable_lesson_shifted_source_color">#A1887F</item> <item name="timetable_lesson_shifted_source_color">#A1887F</item>
<item name="timetable_lesson_shifted_target_color">#4caf50</item> <item name="timetable_lesson_shifted_target_color">#4caf50</item>
<item name="hourDividerColor">#b0b0b0</item>
<item name="halfHourDividerColor">#e0e0e0</item>
</style> </style>
<style name="AppTheme.Dark" parent="NavView.Dark"> <style name="AppTheme.Dark" parent="NavView.Dark">
<item name="colorPrimary">#64b5f6</item> <item name="colorPrimary">#64b5f6</item>
@ -131,6 +133,8 @@
<item name="timetable_lesson_change_color">#ffb300</item> <item name="timetable_lesson_change_color">#ffb300</item>
<item name="timetable_lesson_shifted_source_color">#A1887F</item> <item name="timetable_lesson_shifted_source_color">#A1887F</item>
<item name="timetable_lesson_shifted_target_color">#4caf50</item> <item name="timetable_lesson_shifted_target_color">#4caf50</item>
<item name="hourDividerColor">#7fffffff</item>
<item name="halfHourDividerColor">#40ffffff</item>
</style> </style>

View File

@ -5,8 +5,8 @@ buildscript {
kotlin_version = '1.3.50' kotlin_version = '1.3.50'
release = [ release = [
versionName: "3.9.6-dev", versionName: "3.9.10-dev",
versionCode: 3090600 versionCode: 3091000
] ]
setup = [ setup = [
@ -59,7 +59,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.5.1' classpath 'com.android.tools.build:gradle:3.5.2'
classpath 'me.tatarka:gradle-retrolambda:3.7.0' classpath 'me.tatarka:gradle-retrolambda:3.7.0'
classpath 'com.google.gms:google-services:4.3.1' classpath 'com.google.gms:google-services:4.3.1'
classpath 'io.fabric.tools:gradle:1.28.1' classpath 'io.fabric.tools:gradle:1.28.1'

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip

View File

@ -1,4 +1,4 @@
include ':app', ':agendacalendarview', ':mhttp', ':material-about-library', ':cafebar', ':szkolny-font', ':nachos' include ':app', ':agendacalendarview', ':mhttp', ':material-about-library', ':cafebar', ':szkolny-font', ':nachos', ':community-material'
/* /*
include ':Navigation' include ':Navigation'
project(':Navigation').projectDir = new File(settingsDir, '../Navigation/navlib')*/ project(':Navigation').projectDir = new File(settingsDir, '../Navigation/navlib')*/

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2014 Mike Penz * Copyright 2019 Mike Penz
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -26,8 +26,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
consumerProguardFiles 'consumer-proguard-rules.pro' consumerProguardFiles 'consumer-proguard-rules.pro'
versionCode 1 versionCode 11
versionName "1.0" versionName "1.1"
} }
buildTypes { buildTypes {
release { release {
@ -47,5 +47,5 @@ if (project.hasProperty('pushall') || project.hasProperty('SzkolnyFontonly')) {
dependencies { dependencies {
implementation "com.mikepenz:iconics-core:${iconics}" implementation "com.mikepenz:iconics-core:${iconics}"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
} }

View File

@ -1,6 +1,5 @@
/* /*
* Copyright 2014 Mike Penz * Copyright 2019 Mike Penz
* Copyright 2015 Haruki Hasegawa
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,21 +18,18 @@ package com.mikepenz.iconics.typeface.library.szkolny.font
import com.mikepenz.iconics.typeface.IIcon import com.mikepenz.iconics.typeface.IIcon
import com.mikepenz.iconics.typeface.ITypeface import com.mikepenz.iconics.typeface.ITypeface
import com.mikepenz.iconics.typeface.library.szkolny.R import com.mikepenz.iconics.typeface.library.szkolny.R
import java.util.LinkedList import java.util.*
@Suppress("EnumEntryName") @Suppress("EnumEntryName")
object SzkolnyFont : ITypeface { object SzkolnyFont : ITypeface {
override val fontRes: Int override val fontRes: Int
get() = R.font.szkolny_font_font_v1_0 get() = R.font.szkolny_font_font_v1_1
override val characters: Map<String, Char> by lazy { override val characters: Map<String, Char> by lazy {
mutableMapOf<String, Char>().apply { Icon.values().associate { it.name to it.character }
SzkolnyFont.Icon.values().associateTo(this) { it.name to it.character }
//Icon2.values().associateTo(this) { it.name to it.character }
}
} }
override val mappingPrefix: String override val mappingPrefix: String
get() = "szf" get() = "szf"
@ -41,7 +37,7 @@ object SzkolnyFont : ITypeface {
get() = "Szkolny Font" get() = "Szkolny Font"
override val version: String override val version: String
get() = "1.0" get() = "1.1"
override val iconCount: Int override val iconCount: Int
get() = characters.size get() = characters.size
@ -64,17 +60,28 @@ object SzkolnyFont : ITypeface {
override val licenseUrl: String override val licenseUrl: String
get() = "" get() = ""
override fun getIcon(key: String): IIcon { override fun getIcon(key: String): IIcon = Icon.valueOf(key)
return SzkolnyFont.Icon.valueOf(key)
}
enum class Icon constructor(override val character: Char) : IIcon { enum class Icon constructor(override val character: Char) : IIcon {
szf_eye_check('\ue800'), szf_alarm_bell_outline('\ue800'),
szf_calendar_off('\ue801'), szf_calendar_plus_outline('\ue801'),
szf_file_document_edit('\ue802'), szf_calendar_today_outline('\ue802'),
szf_message_off('\ue803'), szf_clipboard_list_outline('\ue803'),
szf_numeric_0_box_multiple_outline_off('\ue804'); szf_delete_empty_outline('\ue804'),
szf_discord_outline('\ue805'),
szf_file_code_outline('\ue806'),
szf_file_excel_outline('\ue807'),
szf_file_image_outline('\ue808'),
szf_file_music_outline('\ue809'),
szf_file_pdf_outline('\ue80a'),
szf_file_percent_outline('\ue80b'),
szf_file_powerpoint_outline('\ue80c'),
szf_file_video_outline('\ue80d'),
szf_file_word_outline('\ue80e'),
szf_message_processing_outline('\ue80f'),
szf_notebook_outline('\ue810'),
szf_zip_box_outline('\ue811');
override val typeface: ITypeface by lazy { SzkolnyFont } override val typeface: ITypeface by lazy { SzkolnyFont }
} }
} }