[UI] Add Notes feature. (#99)

* [DB] Add Room schema export location.

* [DB] Add Note entity and migration 96.

* Add correct database schema

* [Notes] Implement basic note list UI.

* [DB] Implement Noteable in Full entities. Add note relation and filtering.

* [Notes] Make Note searchable.

* [UI] Disable onClick listeners in adapters when null.

* [UI] Implement showing note list in dialog.

* [UI] Update note dialogs UI.

* [Notes] Add note details dialog.

* [Notes] Extract note dialogs header into a separate layout.

* [Notes] Add note editor dialog.

* [Notes] Show note icons in dialogs and lists.

* [Notes] Add showing substitute text.

* [Notes] Add replacing notes icon.

* [Notes] Add sharing and receiving notes.

* [Notes] Add notes list UI fragment.

* [Notes] Implement adding notes without owner.

* [Notes] Add color names.

* [Notes] Add notes card on home screen.

* [Notes] Add notes card migration.
This commit is contained in:
Kuba Szczodrzyński 2021-10-28 22:35:30 +02:00 committed by GitHub
parent 8745d7d526
commit 7c925cb88a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
109 changed files with 5504 additions and 188 deletions

View File

@ -31,6 +31,12 @@ android {
cppFlags "-std=c++11" cppFlags "-std=c++11"
} }
} }
kapt {
arguments {
arg("room.schemaLocation", "$projectDir/schemas")
}
}
} }
buildTypes { buildTypes {

File diff suppressed because it is too large Load Diff

View File

@ -35,6 +35,7 @@ import okhttp3.OkHttpClient
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.config.Config import pl.szczodrzynski.edziennik.config.Config
import pl.szczodrzynski.edziennik.data.api.events.ProfileListEmptyEvent import pl.szczodrzynski.edziennik.data.api.events.ProfileListEmptyEvent
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing
import pl.szczodrzynski.edziennik.data.db.AppDb import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.Profile
@ -66,6 +67,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
var devMode = false var devMode = false
} }
val api by lazy { SzkolnyApi(this) }
val notificationChannelsManager by lazy { NotificationChannelsManager(this) } val notificationChannelsManager by lazy { NotificationChannelsManager(this) }
val userActionManager by lazy { UserActionManager(this) } val userActionManager by lazy { UserActionManager(this) }
val gradesManager by lazy { GradesManager(this) } val gradesManager by lazy { GradesManager(this) }
@ -77,6 +79,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
val availabilityManager by lazy { AvailabilityManager(this) } val availabilityManager by lazy { AvailabilityManager(this) }
val textStylingManager by lazy { TextStylingManager(this) } val textStylingManager by lazy { TextStylingManager(this) }
val messageManager by lazy { MessageManager(this) } val messageManager by lazy { MessageManager(this) }
val noteManager by lazy { NoteManager(this) }
val db val db
get() = App.db get() = App.db

View File

@ -4,8 +4,11 @@
package pl.szczodrzynski.edziennik package pl.szczodrzynski.edziennik
import android.graphics.Paint import android.graphics.Paint
import android.view.View
import android.widget.TextView import android.widget.TextView
import androidx.core.view.isVisible
import androidx.databinding.BindingAdapter import androidx.databinding.BindingAdapter
import pl.szczodrzynski.edziennik.ext.dp
object Binding { object Binding {
@JvmStatic @JvmStatic
@ -17,4 +20,64 @@ object Binding {
textView.paintFlags = textView.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv() textView.paintFlags = textView.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
} }
} }
@JvmStatic
@BindingAdapter("android:isVisible")
fun isVisible(view: View, isVisible: Boolean) {
view.isVisible = isVisible
}
private fun resizeDrawable(textView: TextView, index: Int, size: Int) {
val drawables = textView.compoundDrawables
drawables[index]?.setBounds(0, 0, size, size)
textView.setCompoundDrawables(drawables[0], drawables[1], drawables[2], drawables[3])
}
@JvmStatic
@BindingAdapter("android:drawableLeftAutoSize")
fun drawableLeftAutoSize(textView: TextView, enable: Boolean) = resizeDrawable(
textView,
index = 0,
size = textView.textSize.toInt(),
)
@JvmStatic
@BindingAdapter("android:drawableRightAutoSize")
fun drawableRightAutoSize(textView: TextView, enable: Boolean) = resizeDrawable(
textView,
index = 2,
size = textView.textSize.toInt(),
)
@JvmStatic
@BindingAdapter("android:drawableLeftSize")
fun drawableLeftSize(textView: TextView, sizeDp: Int) = resizeDrawable(
textView,
index = 0,
size = sizeDp.dp,
)
@JvmStatic
@BindingAdapter("android:drawableTopSize")
fun drawableTopSize(textView: TextView, sizeDp: Int) = resizeDrawable(
textView,
index = 1,
size = sizeDp.dp,
)
@JvmStatic
@BindingAdapter("android:drawableRightSize")
fun drawableRightSize(textView: TextView, sizeDp: Int) = resizeDrawable(
textView,
index = 2,
size = sizeDp.dp,
)
@JvmStatic
@BindingAdapter("android:drawableBottomSize")
fun drawableBottomSize(textView: TextView, sizeDp: Int) = resizeDrawable(
textView,
index = 3,
size = sizeDp.dp,
)
} }

View File

@ -72,6 +72,7 @@ import pl.szczodrzynski.edziennik.ui.login.LoginActivity
import pl.szczodrzynski.edziennik.ui.messages.compose.MessagesComposeFragment import pl.szczodrzynski.edziennik.ui.messages.compose.MessagesComposeFragment
import pl.szczodrzynski.edziennik.ui.messages.list.MessagesFragment import pl.szczodrzynski.edziennik.ui.messages.list.MessagesFragment
import pl.szczodrzynski.edziennik.ui.messages.single.MessageFragment import pl.szczodrzynski.edziennik.ui.messages.single.MessageFragment
import pl.szczodrzynski.edziennik.ui.notes.NotesFragment
import pl.szczodrzynski.edziennik.ui.notifications.NotificationsListFragment import pl.szczodrzynski.edziennik.ui.notifications.NotificationsListFragment
import pl.szczodrzynski.edziennik.ui.settings.ProfileManagerFragment import pl.szczodrzynski.edziennik.ui.settings.ProfileManagerFragment
import pl.szczodrzynski.edziennik.ui.settings.SettingsFragment import pl.szczodrzynski.edziennik.ui.settings.SettingsFragment
@ -118,6 +119,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
const val DRAWER_ITEM_NOTIFICATIONS = 20 const val DRAWER_ITEM_NOTIFICATIONS = 20
const val DRAWER_ITEM_MORE = 21 const val DRAWER_ITEM_MORE = 21
const val DRAWER_ITEM_TEACHERS = 22 const val DRAWER_ITEM_TEACHERS = 22
const val DRAWER_ITEM_NOTES = 23
const val DRAWER_ITEM_SETTINGS = 101 const val DRAWER_ITEM_SETTINGS = 101
const val DRAWER_ITEM_DEBUG = 102 const val DRAWER_ITEM_DEBUG = 102
@ -134,6 +136,13 @@ class MainActivity : AppCompatActivity(), CoroutineScope {
val list: MutableList<NavTarget> = mutableListOf() val list: MutableList<NavTarget> = mutableListOf()
val moreList: MutableList<NavTarget> = mutableListOf() val moreList: MutableList<NavTarget> = mutableListOf()
moreList += NavTarget(
id = DRAWER_ITEM_NOTES,
name = R.string.menu_notes,
fragmentClass = NotesFragment::class)
.withIcon(CommunityMaterial.Icon3.cmd_text_box_multiple_outline)
.isStatic(true)
moreList += NavTarget(DRAWER_ITEM_TEACHERS, moreList += NavTarget(DRAWER_ITEM_TEACHERS,
R.string.menu_teachers, R.string.menu_teachers,
TeachersListFragment::class) TeachersListFragment::class)

View File

@ -18,7 +18,7 @@ import kotlin.coroutines.CoroutineContext
class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List<ConfigEntry>) : CoroutineScope, AbstractConfig { class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List<ConfigEntry>) : CoroutineScope, AbstractConfig {
companion object { companion object {
const val DATA_VERSION = 2 const val DATA_VERSION = 3
} }
private val job = Job() private val job = Job()

View File

@ -7,6 +7,8 @@ package pl.szczodrzynski.edziennik.config.utils
import pl.szczodrzynski.edziennik.config.ProfileConfig import pl.szczodrzynski.edziennik.config.ProfileConfig
import pl.szczodrzynski.edziennik.data.db.entity.Notification import pl.szczodrzynski.edziennik.data.db.entity.Notification
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.AGENDA_DEFAULT import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.AGENDA_DEFAULT
import pl.szczodrzynski.edziennik.ui.home.HomeCard
import pl.szczodrzynski.edziennik.ui.home.HomeCardModel
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED
import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES
@ -33,5 +35,13 @@ class ProfileConfigMigration(config: ProfileConfig) {
dataVersion = 2 dataVersion = 2
} }
if (dataVersion < 3) {
ui.homeCards = ui.homeCards.toMutableList().also {
it.add(HomeCardModel(config.profileId, HomeCard.CARD_NOTES))
}
dataVersion = 3
}
}} }}
} }

View File

@ -23,10 +23,7 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.SignatureIntercep
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing
import pl.szczodrzynski.edziennik.data.api.szkolny.request.* import pl.szczodrzynski.edziennik.data.api.szkolny.request.*
import pl.szczodrzynski.edziennik.data.api.szkolny.response.* import pl.szczodrzynski.edziennik.data.api.szkolny.response.*
import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.*
import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage
import pl.szczodrzynski.edziennik.data.db.entity.Notification
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.ext.keys import pl.szczodrzynski.edziennik.ext.keys
import pl.szczodrzynski.edziennik.ext.md5 import pl.szczodrzynski.edziennik.ext.md5
@ -199,7 +196,12 @@ class SzkolnyApi(val app: App) : CoroutineScope {
} }
@Throws(Exception::class) @Throws(Exception::class)
fun getEvents(profiles: List<Profile>, notifications: List<Notification>, blacklistedIds: List<Long>, lastSyncTime: Long): List<EventFull> { fun getEvents(
profiles: List<Profile>,
notifications: List<Notification>,
blacklistedIds: List<Long>,
lastSyncTime: Long,
): Pair<List<EventFull>, List<Note>> {
val teams = app.db.teamDao().allNow val teams = app.db.teamDao().allNow
val users = profiles.mapNotNull { profile -> val users = profiles.mapNotNull { profile ->
@ -225,7 +227,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
lastSync = lastSyncTime, lastSync = lastSyncTime,
notifications = notifications.map { ServerSyncRequest.Notification(it.profileName ?: "", it.type, it.text) } notifications = notifications.map { ServerSyncRequest.Notification(it.profileName ?: "", it.type, it.text) }
)).execute() )).execute()
val (events, hasBrowsers) = parseResponse(response, updateDeviceHash = true) val (events, notes, hasBrowsers) = parseResponse(response, updateDeviceHash = true)
hasBrowsers?.let { hasBrowsers?.let {
app.config.sync.webPushEnabled = it app.config.sync.webPushEnabled = it
@ -237,6 +239,7 @@ class SzkolnyApi(val app: App) : CoroutineScope {
} }
val eventList = mutableListOf<EventFull>() val eventList = mutableListOf<EventFull>()
val noteList = mutableListOf<Note>()
events.forEach { event -> events.forEach { event ->
// skip blacklisted events // skip blacklisted events
@ -247,9 +250,13 @@ class SzkolnyApi(val app: App) : CoroutineScope {
if (event.color == -1) if (event.color == -1)
event.color = null event.color = null
val eventSharedBy = event.sharedBy
// create the event for every matching team and profile // create the event for every matching team and profile
teams.filter { it.code == event.teamCode }.onEach { team -> teams.filter { it.code == event.teamCode }.onEach { team ->
val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@onEach val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@onEach
if (!profile.canShare)
return@forEach
eventList += EventFull(event).apply { eventList += EventFull(event).apply {
profileId = team.profileId profileId = team.profileId
@ -261,42 +268,107 @@ class SzkolnyApi(val app: App) : CoroutineScope {
if (profile.userCode == event.sharedBy) { if (profile.userCode == event.sharedBy) {
sharedBy = "self" sharedBy = "self"
addedManually = true addedManually = true
} else {
sharedBy = eventSharedBy
} }
} }
} }
} }
return eventList notes.forEach { note ->
val noteSharedBy = note.sharedBy
// create the note for every matching team and profile
teams.filter { it.code == note.teamCode }.onEach { team ->
val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@onEach
if (!profile.canShare)
return@forEach
note.profileId = team.profileId
if (profile.userCode == note.sharedBy) {
note.sharedBy = "self"
} else {
note.sharedBy = noteSharedBy
}
if (app.noteManager.hasValidOwner(note))
noteList += note
}
}
return eventList to noteList
} }
@Throws(Exception::class) @Throws(Exception::class)
fun shareEvent(event: EventFull) { fun shareEvent(event: EventFull) {
val profile = app.db.profileDao().getByIdNow(event.profileId)
?: throw NullPointerException("Profile is not found")
val team = app.db.teamDao().getByIdNow(event.profileId, event.teamId) val team = app.db.teamDao().getByIdNow(event.profileId, event.teamId)
?: throw NullPointerException("Team is not found")
val response = api.shareEvent(EventShareRequest( val response = api.shareEvent(EventShareRequest(
deviceId = app.deviceId, deviceId = app.deviceId,
device = getDevice(), device = getDevice(),
sharedByName = event.sharedByName, userCode = profile.userCode,
shareTeamCode = team.code, studentNameLong = profile.studentNameLong,
event = event shareTeamCode = team.code,
event = event
)).execute() )).execute()
parseResponse(response, updateDeviceHash = true) parseResponse(response, updateDeviceHash = true)
} }
@Throws(Exception::class) @Throws(Exception::class)
fun unshareEvent(event: Event) { fun unshareEvent(event: Event) {
val profile = app.db.profileDao().getByIdNow(event.profileId)
?: throw NullPointerException("Profile is not found")
val team = app.db.teamDao().getByIdNow(event.profileId, event.teamId) val team = app.db.teamDao().getByIdNow(event.profileId, event.teamId)
?: throw NullPointerException("Team is not found")
val response = api.shareEvent(EventShareRequest( val response = api.shareEvent(EventShareRequest(
deviceId = app.deviceId, deviceId = app.deviceId,
device = getDevice(), device = getDevice(),
sharedByName = event.sharedByName, userCode = profile.userCode,
unshareTeamCode = team.code, studentNameLong = profile.studentNameLong,
eventId = event.id unshareTeamCode = team.code,
eventId = event.id
)).execute() )).execute()
parseResponse(response, updateDeviceHash = true) parseResponse(response, updateDeviceHash = true)
} }
@Throws(Exception::class)
fun shareNote(note: Note) {
val profile = app.db.profileDao().getByIdNow(note.profileId)
?: throw NullPointerException("Profile is not found")
val team = app.db.teamDao().getClassNow(note.profileId)
?: throw NullPointerException("TeamClass is not found")
val response = api.shareNote(NoteShareRequest(
deviceId = app.deviceId,
device = getDevice(),
userCode = profile.userCode,
studentNameLong = profile.studentNameLong,
shareTeamCode = team.code,
note = note,
)).execute()
parseResponse(response)
}
@Throws(Exception::class)
fun unshareNote(note: Note) {
val profile = app.db.profileDao().getByIdNow(note.profileId)
?: throw NullPointerException("Profile is not found")
val team = app.db.teamDao().getClassNow(note.profileId)
?: throw NullPointerException("TeamClass is not found")
val response = api.shareNote(NoteShareRequest(
deviceId = app.deviceId,
device = getDevice(),
userCode = profile.userCode,
studentNameLong = profile.studentNameLong,
unshareTeamCode = team.code,
noteId = note.id,
)).execute()
parseResponse(response)
}
/*fun eventEditRequest(requesterName: String, event: Event): ApiResponse<Nothing>? { /*fun eventEditRequest(requesterName: String, event: Event): ApiResponse<Nothing>? {
}*/ }*/

View File

@ -18,6 +18,9 @@ interface SzkolnyService {
@POST("share") @POST("share")
fun shareEvent(@Body request: EventShareRequest): Call<ApiResponse<Unit>> fun shareEvent(@Body request: EventShareRequest): Call<ApiResponse<Unit>>
@POST("share")
fun shareNote(@Body request: NoteShareRequest): Call<ApiResponse<Unit>>
@POST("webPush") @POST("webPush")
fun webPush(@Body request: WebPushRequest): Call<ApiResponse<WebPushResponse>> fun webPush(@Body request: WebPushRequest): Call<ApiResponse<WebPushResponse>>

View File

@ -12,8 +12,9 @@ data class EventShareRequest (
val action: String = "event", val action: String = "event",
/* If null, the server shows an error */ val userCode: String,
val sharedByName: String?, val studentNameLong: String,
val shareTeamCode: String? = null, val shareTeamCode: String? = null,
val unshareTeamCode: String? = null, val unshareTeamCode: String? = null,
val requesterName: String? = null, val requesterName: String? = null,

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-10-26.
*/
package pl.szczodrzynski.edziennik.data.api.szkolny.request
import pl.szczodrzynski.edziennik.data.db.entity.Note
data class NoteShareRequest (
val deviceId: String,
val device: Device? = null,
val action: String = "note",
val userCode: String,
val studentNameLong: String,
val shareTeamCode: String? = null,
val unshareTeamCode: String? = null,
val requesterName: String? = null,
val noteId: Long? = null,
val note: Note? = null
)

View File

@ -4,9 +4,11 @@
package pl.szczodrzynski.edziennik.data.api.szkolny.response package pl.szczodrzynski.edziennik.data.api.szkolny.response
import pl.szczodrzynski.edziennik.data.db.entity.Note
import pl.szczodrzynski.edziennik.data.db.full.EventFull import pl.szczodrzynski.edziennik.data.db.full.EventFull
data class ServerSyncResponse( data class ServerSyncResponse(
val events: List<EventFull>, val events: List<EventFull>,
val notes: List<Note>,
val hasBrowsers: Boolean? = null val hasBrowsers: Boolean? = null
) )

View File

@ -30,7 +30,7 @@ class AppSync(val app: App, val notifications: MutableList<Notification>, val pr
*/ */
fun run(lastSyncTime: Long, markAsSeen: Boolean = false): Int { fun run(lastSyncTime: Long, markAsSeen: Boolean = false): Int {
val blacklistedIds = app.db.eventDao().blacklistedIds val blacklistedIds = app.db.eventDao().blacklistedIds
val events = try { val (events, notes) = try {
api.getEvents(profiles, notifications, blacklistedIds, lastSyncTime) api.getEvents(profiles, notifications, blacklistedIds, lastSyncTime)
} catch (e: SzkolnyApiException) { } catch (e: SzkolnyApiException) {
if (e.toErrorCode() == ERROR_API_INVALID_SIGNATURE) if (e.toErrorCode() == ERROR_API_INVALID_SIGNATURE)
@ -40,6 +40,10 @@ class AppSync(val app: App, val notifications: MutableList<Notification>, val pr
app.config.sync.lastAppSync = System.currentTimeMillis() app.config.sync.lastAppSync = System.currentTimeMillis()
if (notes.isNotEmpty()) {
app.db.noteDao().addAll(notes)
}
if (events.isNotEmpty()) { if (events.isNotEmpty()) {
val today = Date.getToday() val today = Date.getToday()
app.db.metadataDao().addAllIgnore(events.map { event -> app.db.metadataDao().addAllIgnore(events.map { event ->
@ -54,6 +58,6 @@ class AppSync(val app: App, val notifications: MutableList<Notification>, val pr
}) })
return app.db.eventDao().upsertAll(events).size return app.db.eventDao().upsertAll(events).size
} }
return 0; return 0
} }
} }

View File

@ -31,7 +31,7 @@ class SzkolnyTask(val app: App, val syncingProfiles: List<Profile>) : IApiTask(-
val notifications = Notifications(app, notificationList, profiles) val notifications = Notifications(app, notificationList, profiles)
notifications.run() notifications.run()
val appSyncProfiles = profiles.filter { it.registration == Profile.REGISTRATION_ENABLED && !it.archived } val appSyncProfiles = profiles.filter { it.canShare }
// App Sync conditions: // App Sync conditions:
// - every 24 hours && any profile is registered // - every 24 hours && any profile is registered
// - if there are new notifications && any browser is paired // - if there are new notifications && any browser is paired

View File

@ -42,8 +42,9 @@ import pl.szczodrzynski.edziennik.data.db.migration.*
ConfigEntry::class, ConfigEntry::class,
LibrusLesson::class, LibrusLesson::class,
TimetableManual::class, TimetableManual::class,
Note::class,
Metadata::class Metadata::class
], version = 96) ], version = 97)
@TypeConverters( @TypeConverters(
ConverterTime::class, ConverterTime::class,
ConverterDate::class, ConverterDate::class,
@ -82,6 +83,7 @@ abstract class AppDb : RoomDatabase() {
abstract fun configDao(): ConfigDao abstract fun configDao(): ConfigDao
abstract fun librusLessonDao(): LibrusLessonDao abstract fun librusLessonDao(): LibrusLessonDao
abstract fun timetableManualDao(): TimetableManualDao abstract fun timetableManualDao(): TimetableManualDao
abstract fun noteDao(): NoteDao
abstract fun metadataDao(): MetadataDao abstract fun metadataDao(): MetadataDao
companion object { companion object {
@ -182,6 +184,7 @@ abstract class AppDb : RoomDatabase() {
Migration94(), Migration94(),
Migration95(), Migration95(),
Migration96(), Migration96(),
Migration97(),
).allowMainThreadQueries().build() ).allowMainThreadQueries().build()
} }
} }

View File

@ -12,15 +12,19 @@ import pl.szczodrzynski.edziennik.data.db.entity.Keepable
@Dao @Dao
interface BaseDao<T : Keepable, F : T> { interface BaseDao<T : Keepable, F : T> {
@Transaction
@RawQuery @RawQuery
fun getRaw(query: SupportSQLiteQuery): LiveData<List<F>> fun getRaw(query: SupportSQLiteQuery): LiveData<List<F>>
fun getRaw(query: String) = getRaw(SimpleSQLiteQuery(query)) fun getRaw(query: String) = getRaw(SimpleSQLiteQuery(query))
@Transaction
@RawQuery @RawQuery
fun getOne(query: SupportSQLiteQuery): LiveData<F?> fun getOne(query: SupportSQLiteQuery): LiveData<F?>
fun getOne(query: String) = getOne(SimpleSQLiteQuery(query)) fun getOne(query: String) = getOne(SimpleSQLiteQuery(query))
@Transaction
@RawQuery @RawQuery
fun getRawNow(query: SupportSQLiteQuery): List<F> fun getRawNow(query: SupportSQLiteQuery): List<F>
fun getRawNow(query: String) = getRawNow(SimpleSQLiteQuery(query)) fun getRawNow(query: String) = getRawNow(SimpleSQLiteQuery(query))
@Transaction
@RawQuery @RawQuery
fun getOneNow(query: SupportSQLiteQuery): F? fun getOneNow(query: SupportSQLiteQuery): F?
fun getOneNow(query: String) = getOneNow(SimpleSQLiteQuery(query)) fun getOneNow(query: String) = getOneNow(SimpleSQLiteQuery(query))

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-10-23.
*/
package pl.szczodrzynski.edziennik.data.db.dao
import androidx.lifecycle.LiveData
import androidx.room.*
import pl.szczodrzynski.edziennik.data.db.entity.Note
@Dao
interface NoteDao {
companion object {
private const val ORDER_BY = "ORDER BY addedDate DESC"
}
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun add(note: Note)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun addAll(noteList: List<Note>)
@Delete
fun delete(note: Note)
@Query("DELETE FROM notes WHERE profileId = :profileId AND noteId = :noteId")
fun remove(profileId: Int, noteId: Long)
@Query("SELECT * FROM notes WHERE profileId = :profileId AND noteId = :noteId $ORDER_BY")
fun get(profileId: Int, noteId: Long): LiveData<Note?>
@Query("SELECT * FROM notes WHERE profileId = :profileId AND noteId = :noteId $ORDER_BY")
fun getNow(profileId: Int, noteId: Long): Note?
@Query("SELECT * FROM notes WHERE profileId = :profileId $ORDER_BY")
fun getAll(profileId: Int): LiveData<List<Note>>
@Query("SELECT * FROM notes WHERE profileId = :profileId AND noteOwnerType = :ownerType AND noteOwnerId = :ownerId $ORDER_BY")
fun getAllFor(profileId: Int, ownerType: Note.OwnerType, ownerId: Long): LiveData<List<Note>>
@Query("SELECT * FROM notes WHERE profileId = :profileId AND noteOwnerType IS NULL $ORDER_BY")
fun getAllNoOwner(profileId: Int): LiveData<List<Note>>
}

View File

@ -0,0 +1,149 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-10-16.
*/
package pl.szczodrzynski.edziennik.data.db.entity
import androidx.room.*
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.ui.search.Searchable
import pl.szczodrzynski.edziennik.utils.html.BetterHtml
@Entity(
tableName = "notes",
indices = [
Index(value = ["profileId", "noteOwnerType", "noteOwnerId"]),
],
)
data class Note(
var profileId: Int,
@PrimaryKey
@ColumnInfo(name = "noteId")
val id: Long,
@ColumnInfo(name = "noteOwnerType")
val ownerType: OwnerType?,
@ColumnInfo(name = "noteOwnerId")
val ownerId: Long?,
@ColumnInfo(name = "noteReplacesOriginal")
val replacesOriginal: Boolean = false,
@ColumnInfo(name = "noteTopic")
val topic: String?,
@ColumnInfo(name = "noteBody")
val body: String,
@ColumnInfo(name = "noteColor")
val color: Long?,
@ColumnInfo(name = "noteSharedBy")
var sharedBy: String? = null,
@ColumnInfo(name = "noteSharedByName")
val sharedByName: String? = null,
val addedDate: Long = System.currentTimeMillis(),
) : Searchable<Note> {
enum class OwnerType(
val isShareable: Boolean,
val canReplace: Boolean,
) {
/**
* The [NONE] type is only for usage in the UI and should not be saved.
*/
NONE(isShareable = true, canReplace = false),
EVENT(isShareable = true, canReplace = true),
DAY(isShareable = true, canReplace = false),
LESSON(isShareable = true, canReplace = true),
MESSAGE(isShareable = true, canReplace = false),
EVENT_SUBJECT(isShareable = true, canReplace = false),
LESSON_SUBJECT(isShareable = true, canReplace = false),
GRADE(isShareable = false, canReplace = true),
ATTENDANCE(isShareable = false, canReplace = true),
BEHAVIOR(isShareable = false, canReplace = false),
ANNOUNCEMENT(isShareable = true, canReplace = false),
}
enum class Color(val value: Long?, val stringRes: Int) {
NONE(null, R.string.color_none),
RED(0xffff1744, R.string.color_red),
ORANGE(0xffff9100, R.string.color_orange),
YELLOW(0xffffea00, R.string.color_yellow),
GREEN(0xff00c853, R.string.color_green),
TEAL(0xff00bfa5, R.string.color_teal),
BLUE(0xff0091ea, R.string.color_blue),
DARK_BLUE(0xff304ffe, R.string.color_dark_blue),
PURPLE(0xff6200ea, R.string.color_purple),
PINK(0xffd500f9, R.string.color_pink),
BROWN(0xff795548, R.string.color_brown),
GREY(0xff9e9e9e, R.string.color_grey),
BLACK(0xff000000, R.string.color_black),
}
val isShared
get() = sharedBy != null && sharedByName != null
val canEdit
get() = !isShared || sharedBy == "self"
// used when receiving notes
@Ignore
var teamCode: String? = null
@delegate:Ignore
@delegate:Transient
val topicHtml by lazy {
topic?.let {
BetterHtml.fromHtml(context = null, it, nl2br = true)
}
}
@delegate:Ignore
@delegate:Transient
val bodyHtml by lazy {
BetterHtml.fromHtml(context = null, body, nl2br = true)
}
@Ignore
@Transient
var isCategoryItem = false
@Ignore
@Transient
override var searchPriority = 0
@Ignore
@Transient
override var searchHighlightText: String? = null
@delegate:Ignore
@delegate:Transient
override val searchKeywords by lazy {
if (isCategoryItem)
return@lazy emptyList()
listOf(
listOf(topicHtml?.toString(), bodyHtml.toString()),
listOf(sharedByName),
)
}
override fun compareTo(other: Searchable<*>): Int {
if (other !is Note)
return 0
val order = ownerType?.ordinal ?: -1
val otherOrder = other.ownerType?.ordinal ?: -1
return when {
// custom ascending sorting
order > otherOrder -> 1
order < otherOrder -> -1
// all category elements stay at their original position
isCategoryItem -> 0
other.isCategoryItem -> 0
// ascending sorting
searchPriority > other.searchPriority -> 1
searchPriority < other.searchPriority -> -1
// descending sorting
addedDate > other.addedDate -> -1
addedDate < other.addedDate -> 1
else -> 0
}
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-10-17.
*/
package pl.szczodrzynski.edziennik.data.db.entity
interface Noteable {
fun getNoteType(): Note.OwnerType
fun getNoteOwnerProfileId(): Int
fun getNoteOwnerId(): Long
var notes: MutableList<Note>
fun filterNotes() {
val type = getNoteType()
val profileId = getNoteOwnerProfileId()
notes.removeAll {
it.profileId != profileId || it.ownerType != type
}
}
fun hasNotes() = notes.isNotEmpty()
fun hasReplacingNotes() = notes.any { it.replacesOriginal }
fun getNoteSubstituteText(showNotes: Boolean): CharSequence? {
if (!showNotes)
return null
val note = notes.firstOrNull {
it.replacesOriginal
}
return note?.topicHtml ?: note?.bodyHtml
}
}

View File

@ -56,6 +56,7 @@ data class Notification(
const val TYPE_FEEDBACK_MESSAGE = 16 const val TYPE_FEEDBACK_MESSAGE = 16
const val TYPE_AUTO_ARCHIVING = 17 const val TYPE_AUTO_ARCHIVING = 17
const val TYPE_TEACHER_ABSENCE = 19 const val TYPE_TEACHER_ABSENCE = 19
const val TYPE_NEW_SHARED_NOTE = 20
fun buildId(profileId: Int, type: Int, itemId: Long): Long { fun buildId(profileId: Int, type: Int, itemId: Long): Long {
return 1000000000000 + profileId*10000000000 + type*100000000 + itemId; return 1000000000000 + profileId*10000000000 + type*100000000 + itemId;
@ -112,6 +113,7 @@ data class Notification(
TYPE_NEW_ATTENDANCE -> CommunityMaterial.Icon.cmd_calendar_remove_outline TYPE_NEW_ATTENDANCE -> CommunityMaterial.Icon.cmd_calendar_remove_outline
TYPE_LUCKY_NUMBER -> CommunityMaterial.Icon.cmd_emoticon_excited_outline TYPE_LUCKY_NUMBER -> CommunityMaterial.Icon.cmd_emoticon_excited_outline
TYPE_NEW_ANNOUNCEMENT -> CommunityMaterial.Icon.cmd_bullhorn_outline TYPE_NEW_ANNOUNCEMENT -> CommunityMaterial.Icon.cmd_bullhorn_outline
TYPE_NEW_SHARED_NOTE -> CommunityMaterial.Icon3.cmd_playlist_edit
else -> CommunityMaterial.Icon.cmd_bell_ring_outline else -> CommunityMaterial.Icon.cmd_bell_ring_outline
} }
} }

View File

@ -145,6 +145,9 @@ open class Profile(
else -> "unknown" else -> "unknown"
} }
val canShare
get() = registration == REGISTRATION_ENABLED && !archived
override fun getImageDrawable(context: Context): Drawable { override fun getImageDrawable(context: Context): Drawable {
if (archived) { if (archived) {
return context.getDrawableFromRes(pl.szczodrzynski.edziennik.R.drawable.profile_archived).also { return context.getDrawableFromRes(pl.szczodrzynski.edziennik.R.drawable.profile_archived).also {

View File

@ -3,7 +3,10 @@
*/ */
package pl.szczodrzynski.edziennik.data.db.full package pl.szczodrzynski.edziennik.data.db.full
import androidx.room.Relation
import pl.szczodrzynski.edziennik.data.db.entity.Announcement import pl.szczodrzynski.edziennik.data.db.entity.Announcement
import pl.szczodrzynski.edziennik.data.db.entity.Note
import pl.szczodrzynski.edziennik.data.db.entity.Noteable
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
class AnnouncementFull( class AnnouncementFull(
@ -16,10 +19,16 @@ class AnnouncementFull(
subject, text, subject, text,
startDate, endDate, startDate, endDate,
teacherId, addedDate teacherId, addedDate
) { ), Noteable {
var teacherName: String? = null var teacherName: String? = null
// metadata // metadata
var seen = false var seen = false
var notified = false var notified = false
@Relation(parentColumn = "announcementId", entityColumn = "noteOwnerId", entity = Note::class)
override lateinit var notes: MutableList<Note>
override fun getNoteType() = Note.OwnerType.ANNOUNCEMENT
override fun getNoteOwnerProfileId() = profileId
override fun getNoteOwnerId() = id
} }

View File

@ -3,7 +3,10 @@
*/ */
package pl.szczodrzynski.edziennik.data.db.full package pl.szczodrzynski.edziennik.data.db.full
import androidx.room.Relation
import pl.szczodrzynski.edziennik.data.db.entity.Attendance import pl.szczodrzynski.edziennik.data.db.entity.Attendance
import pl.szczodrzynski.edziennik.data.db.entity.Note
import pl.szczodrzynski.edziennik.data.db.entity.Noteable
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
@ -17,7 +20,7 @@ class AttendanceFull(
baseType, typeName, typeShort, typeSymbol, typeColor, baseType, typeName, typeShort, typeSymbol, typeColor,
date, startTime, semester, date, startTime, semester,
teacherId, subjectId, addedDate teacherId, subjectId, addedDate
) { ), Noteable {
var teacherName: String? = null var teacherName: String? = null
var subjectLongName: String? = null var subjectLongName: String? = null
var subjectShortName: String? = null var subjectShortName: String? = null
@ -26,4 +29,10 @@ class AttendanceFull(
var seen = false var seen = false
get() = field || baseType == TYPE_PRESENT get() = field || baseType == TYPE_PRESENT
var notified = false var notified = false
@Relation(parentColumn = "attendanceId", entityColumn = "noteOwnerId", entity = Note::class)
override lateinit var notes: MutableList<Note>
override fun getNoteType() = Note.OwnerType.ATTENDANCE
override fun getNoteOwnerProfileId() = profileId
override fun getNoteOwnerId() = id
} }

View File

@ -4,8 +4,11 @@
package pl.szczodrzynski.edziennik.data.db.full package pl.szczodrzynski.edziennik.data.db.full
import androidx.room.Ignore import androidx.room.Ignore
import androidx.room.Relation
import pl.szczodrzynski.edziennik.data.db.entity.Event import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Note
import pl.szczodrzynski.edziennik.data.db.entity.Noteable
import pl.szczodrzynski.edziennik.ui.search.Searchable import pl.szczodrzynski.edziennik.ui.search.Searchable
import pl.szczodrzynski.edziennik.utils.html.BetterHtml import pl.szczodrzynski.edziennik.utils.html.BetterHtml
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
@ -19,7 +22,7 @@ class EventFull(
profileId, id, date, time, profileId, id, date, time,
topic, color, type, topic, color, type,
teacherId, subjectId, teamId, addedDate teacherId, subjectId, teamId, addedDate
), Searchable<EventFull> { ), Searchable<EventFull>, Noteable {
constructor(event: Event, metadata: Metadata? = null) : this( constructor(event: Event, metadata: Metadata? = null) : this(
event.profileId, event.id, event.date, event.time, event.profileId, event.id, event.date, event.time,
event.topic, event.color, event.type, event.topic, event.color, event.type,
@ -109,4 +112,10 @@ class EventFull(
val eventColor val eventColor
get() = color ?: typeColor ?: 0xff2196f3.toInt() get() = color ?: typeColor ?: 0xff2196f3.toInt()
@Relation(parentColumn = "eventId", entityColumn = "noteOwnerId", entity = Note::class)
override lateinit var notes: MutableList<Note>
override fun getNoteType() = Note.OwnerType.EVENT
override fun getNoteOwnerProfileId() = profileId
override fun getNoteOwnerId() = id
} }

View File

@ -3,7 +3,10 @@
*/ */
package pl.szczodrzynski.edziennik.data.db.full package pl.szczodrzynski.edziennik.data.db.full
import androidx.room.Relation
import pl.szczodrzynski.edziennik.data.db.entity.Grade import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Note
import pl.szczodrzynski.edziennik.data.db.entity.Noteable
class GradeFull( class GradeFull(
profileId: Int, id: Long, name: String, type: Int, profileId: Int, id: Long, name: String, type: Int,
@ -15,7 +18,7 @@ class GradeFull(
value, weight, color, value, weight, color,
category, description, comment, category, description, comment,
semester, teacherId, subjectId, addedDate semester, teacherId, subjectId, addedDate
) { ), Noteable {
var teacherName: String? = null var teacherName: String? = null
var subjectLongName: String? = null var subjectLongName: String? = null
var subjectShortName: String? = null var subjectShortName: String? = null
@ -23,4 +26,10 @@ class GradeFull(
// metadata // metadata
var seen = false var seen = false
var notified = false var notified = false
@Relation(parentColumn = "gradeId", entityColumn = "noteOwnerId", entity = Note::class)
override lateinit var notes: MutableList<Note>
override fun getNoteType() = Note.OwnerType.GRADE
override fun getNoteOwnerProfileId() = profileId
override fun getNoteOwnerId() = id
} }

View File

@ -4,15 +4,18 @@
package pl.szczodrzynski.edziennik.data.db.full package pl.szczodrzynski.edziennik.data.db.full
import android.content.Context import android.content.Context
import androidx.room.Relation
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.data.db.entity.Note
import pl.szczodrzynski.edziennik.data.db.entity.Noteable
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
class LessonFull( class LessonFull(
profileId: Int, id: Long profileId: Int, id: Long
) : Lesson( ) : Lesson(
profileId, id profileId, id
) { ), Noteable {
var subjectName: String? = null var subjectName: String? = null
var teacherName: String? = null var teacherName: String? = null
var teamName: String? = null var teamName: String? = null
@ -133,4 +136,10 @@ class LessonFull(
// metadata // metadata
var seen: Boolean = false var seen: Boolean = false
var notified: Boolean = false var notified: Boolean = false
@Relation(parentColumn = "id", entityColumn = "noteOwnerId", entity = Note::class)
override lateinit var notes: MutableList<Note>
override fun getNoteType() = Note.OwnerType.LESSON
override fun getNoteOwnerProfileId() = profileId
override fun getNoteOwnerId() = id
} }

View File

@ -7,6 +7,8 @@ import androidx.room.Ignore
import androidx.room.Relation import androidx.room.Relation
import pl.szczodrzynski.edziennik.data.db.entity.Message import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient
import pl.szczodrzynski.edziennik.data.db.entity.Note
import pl.szczodrzynski.edziennik.data.db.entity.Noteable
import pl.szczodrzynski.edziennik.ui.search.Searchable import pl.szczodrzynski.edziennik.ui.search.Searchable
import pl.szczodrzynski.edziennik.utils.html.BetterHtml import pl.szczodrzynski.edziennik.utils.html.BetterHtml
@ -18,7 +20,7 @@ class MessageFull(
profileId, id, type, profileId, id, type,
subject, body, senderId, subject, body, senderId,
addedDate addedDate
), Searchable<MessageFull> { ), Searchable<MessageFull>, Noteable {
var senderName: String? = null var senderName: String? = null
@Relation(parentColumn = "messageId", entityColumn = "messageId", entity = MessageRecipient::class) @Relation(parentColumn = "messageId", entityColumn = "messageId", entity = MessageRecipient::class)
var recipients: MutableList<MessageRecipientFull>? = null var recipients: MutableList<MessageRecipientFull>? = null
@ -83,4 +85,10 @@ class MessageFull(
// metadata // metadata
var seen = false var seen = false
var notified = false var notified = false
@Relation(parentColumn = "messageId", entityColumn = "noteOwnerId", entity = Note::class)
override lateinit var notes: MutableList<Note>
override fun getNoteType() = Note.OwnerType.MESSAGE
override fun getNoteOwnerProfileId() = profileId
override fun getNoteOwnerId() = id
} }

View File

@ -3,6 +3,9 @@
*/ */
package pl.szczodrzynski.edziennik.data.db.full package pl.szczodrzynski.edziennik.data.db.full
import androidx.room.Relation
import pl.szczodrzynski.edziennik.data.db.entity.Note
import pl.szczodrzynski.edziennik.data.db.entity.Noteable
import pl.szczodrzynski.edziennik.data.db.entity.Notice import pl.szczodrzynski.edziennik.data.db.entity.Notice
class NoticeFull( class NoticeFull(
@ -13,10 +16,16 @@ class NoticeFull(
profileId, id, type, semester, profileId, id, type, semester,
text, category, points, text, category, points,
teacherId, addedDate teacherId, addedDate
) { ), Noteable {
var teacherName: String? = null var teacherName: String? = null
// metadata // metadata
var seen = false var seen = false
var notified = false var notified = false
@Relation(parentColumn = "noticeId", entityColumn = "noteOwnerId", entity = Note::class)
override lateinit var notes: MutableList<Note>
override fun getNoteType() = Note.OwnerType.BEHAVIOR
override fun getNoteOwnerProfileId() = profileId
override fun getNoteOwnerId() = id
} }

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-10-16.
*/
package pl.szczodrzynski.edziennik.data.db.migration
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration97 : Migration(96, 97) {
override fun migrate(database: SupportSQLiteDatabase) {
// notes
database.execSQL("""CREATE TABLE notes (
profileId INTEGER NOT NULL,
noteId INTEGER NOT NULL,
noteOwnerType TEXT,
noteOwnerId INTEGER,
noteReplacesOriginal INTEGER NOT NULL,
noteTopic TEXT,
noteBody TEXT NOT NULL,
noteColor INTEGER,
noteSharedBy TEXT,
noteSharedByName TEXT,
addedDate INTEGER NOT NULL,
PRIMARY KEY(noteId)
);""")
database.execSQL("CREATE INDEX IF NOT EXISTS index_notes_profileId_noteOwnerType_noteOwnerId ON notes (profileId, noteOwnerType, noteOwnerId);")
}
}

View File

@ -47,6 +47,15 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
message.data.getLong("eventId") ?: return@run, message.data.getLong("eventId") ?: return@run,
message.data.getString("message") ?: return@run message.data.getString("message") ?: return@run
) )
"sharedNote" -> sharedNote(
message.data.getString("shareTeamCode") ?: return@run,
message.data.getString("note") ?: return@run,
message.data.getString("message") ?: return@run,
)
"unsharedNote" -> unsharedNote(
message.data.getString("unshareTeamCode") ?: return@run,
message.data.getLong("noteId") ?: return@run,
)
"serverMessage", "serverMessage",
"unpairedBrowser" -> serverMessage( "unpairedBrowser" -> serverMessage(
message.data.getString("title") ?: "", message.data.getString("title") ?: "",
@ -119,7 +128,7 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
teams.filter { it.code == teamCode }.distinctBy { it.profileId }.forEach { team -> teams.filter { it.code == teamCode }.distinctBy { it.profileId }.forEach { team ->
val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@forEach val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@forEach
if (profile.registration != Profile.REGISTRATION_ENABLED) if (!profile.canShare)
return@forEach return@forEach
val event = Event( val event = Event(
profileId = team.profileId, profileId = team.profileId,
@ -186,7 +195,7 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
teams.filter { it.code == teamCode }.distinctBy { it.profileId }.forEach { team -> teams.filter { it.code == teamCode }.distinctBy { it.profileId }.forEach { team ->
val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@forEach val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@forEach
if (profile.registration != Profile.REGISTRATION_ENABLED) if (!profile.canShare)
return@forEach return@forEach
val notificationFilter = app.config.getFor(team.profileId).sync.notificationFilter val notificationFilter = app.config.getFor(team.profileId).sync.notificationFilter
@ -209,4 +218,71 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
PostNotifications(app, notificationList) PostNotifications(app, notificationList)
} }
} }
private fun sharedNote(teamCode: String, jsonStr: String, message: String) {
val note = app.gson.fromJson(jsonStr, Note::class.java)
val noteSharedBy = note.sharedBy
val teams = app.db.teamDao().allNow
// not used, as the server provides a sharing message
//val eventTypes = app.db.eventTypeDao().allNow
val notes = mutableListOf<Note>()
val notificationList = mutableListOf<Notification>()
teams.filter { it.code == teamCode }.distinctBy { it.profileId }.forEach { team ->
val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@forEach
if (!profile.canShare)
return@forEach
note.profileId = team.profileId
if (profile.userCode == note.sharedBy) {
note.sharedBy = "self"
} else {
note.sharedBy = noteSharedBy
}
if (!app.noteManager.hasValidOwner(note))
return@forEach
notes += note
val hadNote = app.db.noteDao().getNow(note.profileId, note.id) != null
// skip creating notifications
if (hadNote)
return@forEach
val type = Notification.TYPE_NEW_SHARED_NOTE
val notificationFilter = app.config.getFor(note.profileId).sync.notificationFilter
if (!notificationFilter.contains(type) && note.sharedBy != "self") {
val notification = Notification(
id = Notification.buildId(note.profileId, type, note.id),
title = app.getNotificationTitle(type),
text = message,
type = type,
profileId = profile.id,
profileName = profile.name,
viewId = MainActivity.DRAWER_ITEM_HOME,
addedDate = note.addedDate
).addExtra("noteId", note.id)
notificationList += notification
}
}
app.db.noteDao().addAll(notes)
if (notificationList.isNotEmpty()) {
app.db.notificationDao().addAll(notificationList)
PostNotifications(app, notificationList)
}
}
private fun unsharedNote(teamCode: String, noteId: Long) {
val teams = app.db.teamDao().allNow
teams.filter { it.code == teamCode }.distinctBy { it.profileId }.forEach { team ->
val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@forEach
if (!profile.canShare)
return@forEach
app.db.noteDao().remove(team.profileId, noteId)
}
}
} }

View File

@ -63,6 +63,7 @@ fun Context.getNotificationTitle(type: Int): String {
Notification.TYPE_AUTO_ARCHIVING -> R.string.notification_type_auto_archiving Notification.TYPE_AUTO_ARCHIVING -> R.string.notification_type_auto_archiving
Notification.TYPE_TEACHER_ABSENCE -> R.string.notification_type_new_teacher_absence Notification.TYPE_TEACHER_ABSENCE -> R.string.notification_type_new_teacher_absence
Notification.TYPE_GENERAL -> R.string.notification_type_general Notification.TYPE_GENERAL -> R.string.notification_type_general
Notification.TYPE_NEW_SHARED_NOTE -> R.string.notification_type_new_shared_note
else -> R.string.notification_type_general else -> R.string.notification_type_general
}) })
} }

View File

@ -4,13 +4,22 @@
package pl.szczodrzynski.edziennik.ext package pl.szczodrzynski.edziennik.ext
import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.text.InputType import android.text.InputType
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.core.graphics.ColorUtils
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.dialog.MaterialDialogs
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.DialogEditTextBinding import pl.szczodrzynski.edziennik.databinding.DialogEditTextBinding
fun MaterialAlertDialogBuilder.input( fun MaterialAlertDialogBuilder.input(
@ -20,7 +29,7 @@ fun MaterialAlertDialogBuilder.input(
value: CharSequence? = null, value: CharSequence? = null,
changeListener: ((editText: TextInputEditText, input: String) -> Boolean)? = null, changeListener: ((editText: TextInputEditText, input: String) -> Boolean)? = null,
positiveButton: Int? = null, positiveButton: Int? = null,
positiveListener: ((editText: TextInputEditText, input: String) -> Boolean)? = null positiveListener: ((editText: TextInputEditText, input: String) -> Boolean)? = null,
): MaterialAlertDialogBuilder { ): MaterialAlertDialogBuilder {
val b = DialogEditTextBinding.inflate(LayoutInflater.from(context), null, false) val b = DialogEditTextBinding.inflate(LayoutInflater.from(context), null, false)
b.title.text = message b.title.text = message
@ -43,12 +52,53 @@ fun MaterialAlertDialogBuilder.input(
return this return this
} }
fun MaterialAlertDialogBuilder.setTitle(@StringRes resId: Int, vararg formatArgs: Any): MaterialAlertDialogBuilder { fun MaterialAlertDialogBuilder.setTitle(
@StringRes resId: Int,
vararg formatArgs: Any,
): MaterialAlertDialogBuilder {
setTitle(context.getString(resId, *formatArgs)) setTitle(context.getString(resId, *formatArgs))
return this return this
} }
fun MaterialAlertDialogBuilder.setMessage(@StringRes resId: Int, vararg formatArgs: Any): MaterialAlertDialogBuilder { fun MaterialAlertDialogBuilder.setMessage(
@StringRes resId: Int,
vararg formatArgs: Any,
): MaterialAlertDialogBuilder {
setMessage(context.getString(resId, *formatArgs)) setMessage(context.getString(resId, *formatArgs))
return this return this
} }
@SuppressLint("RestrictedApi")
fun AlertDialog.overlayBackgroundColor(color: Int, alpha: Int) {
// this is absolutely horrible
val colorSurface16dp = ColorUtils.compositeColors(
R.color.colorSurface_16dp.resolveColor(context),
MaterialColors.getColor(
context,
R.attr.colorSurface,
javaClass.canonicalName,
)
)
val colorDialogBackground = MaterialColors.layer(colorSurface16dp, color, alpha / 255f)
val backgroundInsets = MaterialDialogs.getDialogBackgroundInsets(
context,
R.attr.alertDialogStyle,
R.style.MaterialAlertDialog_MaterialComponents,
)
val background = MaterialShapeDrawable(
context,
null,
R.attr.alertDialogStyle,
R.style.MaterialAlertDialog_MaterialComponents
)
with(background) {
initializeElevationOverlay(context)
fillColor = ColorStateList.valueOf(colorDialogBackground)
/*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
setCornerSize(android.R.attr.dialogCornerRadius.resolveDimenAttr(context))
}*/
elevation = ViewCompat.getElevation(window?.decorView ?: return@with)
}
val insetDrawable = MaterialDialogs.insetDrawable(background, backgroundInsets)
window?.setBackgroundDrawable(insetDrawable)
}

View File

@ -10,10 +10,7 @@ import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter import android.graphics.PorterDuffColorFilter
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.util.TypedValue import android.util.TypedValue
import androidx.annotation.AttrRes import androidx.annotation.*
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import com.mikepenz.iconics.typeface.IIcon import com.mikepenz.iconics.typeface.IIcon
import pl.szczodrzynski.navlib.ImageHolder import pl.szczodrzynski.navlib.ImageHolder
@ -60,6 +57,12 @@ fun @receiver:AttrRes Int.resolveAttr(context: Context?): Int {
context?.theme?.resolveAttribute(this, typedValue, true) context?.theme?.resolveAttribute(this, typedValue, true)
return typedValue.data return typedValue.data
} }
@Dimension
fun @receiver:AttrRes Int.resolveDimenAttr(context: Context): Float {
val typedValue = TypedValue()
context.theme?.resolveAttribute(this, typedValue, true)
return typedValue.getDimension(context.resources.displayMetrics)
}
@ColorInt @ColorInt
fun @receiver:ColorRes Int.resolveColor(context: Context): Int { fun @receiver:ColorRes Int.resolveColor(context: Context): Int {
return ResourcesCompat.getColor(context.resources, this, context.theme) return ResourcesCompat.getColor(context.resources, this, context.theme)

View File

@ -26,7 +26,9 @@ import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.ui.event.EventDetailsDialog import pl.szczodrzynski.edziennik.ui.event.EventDetailsDialog
import pl.szczodrzynski.edziennik.ui.event.EventListAdapter import pl.szczodrzynski.edziennik.ui.event.EventListAdapter
import pl.szczodrzynski.edziennik.ui.event.EventManualDialog import pl.szczodrzynski.edziennik.ui.event.EventManualDialog
import pl.szczodrzynski.edziennik.ui.notes.setupNotesButton
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import pl.szczodrzynski.edziennik.utils.managers.NoteManager
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
@ -36,6 +38,7 @@ class DayDialog(
private val profileId: Int, private val profileId: Int,
private val date: Date, private val date: Date,
private val eventTypeId: Long? = null, private val eventTypeId: Long? = null,
private val showNotes: Boolean = true,
onShowListener: ((tag: String) -> Unit)? = null, onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null, onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<DialogDayBinding>(activity, onShowListener, onDismissListener) { ) : BindingDialog<DialogDayBinding>(activity, onShowListener, onDismissListener) {
@ -151,7 +154,7 @@ class DayDialog(
showTime = true, showTime = true,
showSubject = true, showSubject = true,
markAsSeen = true, markAsSeen = true,
onItemClick = { onEventClick = {
EventDetailsDialog( EventDetailsDialog(
activity, activity,
it, it,
@ -171,6 +174,10 @@ class DayDialog(
) )
app.db.eventDao().getAllByDate(profileId, date).observe(activity) { events -> app.db.eventDao().getAllByDate(profileId, date).observe(activity) { events ->
events.forEach {
it.filterNotes()
}
adapter.setAllItems( adapter.setAllItems(
if (eventTypeId != null) if (eventTypeId != null)
events.filter { it.type == eventTypeId } events.filter { it.type == eventTypeId }
@ -197,5 +204,15 @@ class DayDialog(
b.eventsNoData.visibility = View.VISIBLE b.eventsNoData.visibility = View.VISIBLE
} }
} }
b.notesButton.isVisible = showNotes
b.notesButton.setupNotesButton(
activity = activity,
owner = date,
onShowListener = onShowListener,
onDismissListener = onDismissListener,
)
if (showNotes)
NoteManager.setLegendText(date, b.legend)
} }
} }

View File

@ -16,11 +16,13 @@ import pl.szczodrzynski.edziennik.data.db.entity.Lesson
import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding
import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.ext.*
import pl.szczodrzynski.edziennik.utils.managers.NoteManager
import pl.szczodrzynski.navlib.getColorFromAttr import pl.szczodrzynski.navlib.getColorFromAttr
class LessonChangesAdapter( class LessonChangesAdapter(
val context: Context, val context: Context,
private val onItemClick: ((lesson: LessonFull) -> Unit)? = null private val showNotes: Boolean = true,
private val onLessonClick: ((lesson: LessonFull) -> Unit)? = null
) : RecyclerView.Adapter<LessonChangesAdapter.ViewHolder>() { ) : RecyclerView.Adapter<LessonChangesAdapter.ViewHolder>() {
var items = listOf<LessonFull>() var items = listOf<LessonFull>()
@ -39,8 +41,10 @@ class LessonChangesAdapter(
val lesson = items[position] val lesson = items[position]
val b = holder.b val b = holder.b
b.root.onClick { if (onLessonClick != null) {
onItemClick?.invoke(lesson) b.root.onClick {
onLessonClick.invoke(lesson)
}
} }
val startTime = lesson.displayStartTime ?: return val startTime = lesson.displayStartTime ?: return
@ -84,7 +88,8 @@ class LessonChangesAdapter(
b.lessonNumber = lesson.displayLessonNumber b.lessonNumber = lesson.displayLessonNumber
b.subjectName.text = lesson.displaySubjectName?.let { val lessonText = lesson.getNoteSubstituteText(showNotes) ?: lesson.displaySubjectName
b.subjectName.text = lessonText?.let {
if (lesson.type == Lesson.TYPE_CANCELLED || lesson.type == Lesson.TYPE_SHIFTED_SOURCE) if (lesson.type == Lesson.TYPE_CANCELLED || lesson.type == Lesson.TYPE_SHIFTED_SOURCE)
it.asStrikethroughSpannable().asColoredSpannable(colorSecondary) it.asStrikethroughSpannable().asColoredSpannable(colorSecondary)
else else
@ -93,6 +98,9 @@ class LessonChangesAdapter(
b.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).concat(bullet) b.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).concat(bullet)
b.detailsSecond.text = listOfNotEmpty(teacherInfo, teamInfo).concat(bullet) b.detailsSecond.text = listOfNotEmpty(teacherInfo, teamInfo).concat(bullet)
if (showNotes)
NoteManager.prependIcon(lesson, b.subjectName)
//lb.subjectName.typeface = Typeface.create("sans-serif-light", Typeface.BOLD) //lb.subjectName.typeface = Typeface.create("sans-serif-light", Typeface.BOLD)
when (lesson.type) { when (lesson.type) {
Lesson.TYPE_NORMAL -> { Lesson.TYPE_NORMAL -> {

View File

@ -35,7 +35,7 @@ class LessonChangesDialog(
val adapter = LessonChangesAdapter( val adapter = LessonChangesAdapter(
activity, activity,
onItemClick = { onLessonClick = {
LessonDetailsDialog( LessonDetailsDialog(
activity, activity,
it, it,

View File

@ -10,6 +10,7 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil; import androidx.databinding.DataBindingUtil;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -24,13 +25,14 @@ public class AnnouncementsAdapter extends RecyclerView.Adapter<AnnouncementsAdap
private Context context; private Context context;
public List<AnnouncementFull> announcementList; public List<AnnouncementFull> announcementList;
@Nullable
public OnAnnouncementClickListener onClick; public OnAnnouncementClickListener onClick;
public interface OnAnnouncementClickListener { public interface OnAnnouncementClickListener {
void onClick(View v, AnnouncementFull announcement); void onClick(View v, AnnouncementFull announcement);
} }
public AnnouncementsAdapter(Context context, List<AnnouncementFull> announcementList, OnAnnouncementClickListener onClick) { public AnnouncementsAdapter(Context context, List<AnnouncementFull> announcementList, @Nullable OnAnnouncementClickListener onClick) {
//setHasStableIds(true); //setHasStableIds(true);
this.context = context; this.context = context;
@ -54,11 +56,12 @@ public class AnnouncementsAdapter extends RecyclerView.Adapter<AnnouncementsAdap
AnnouncementFull item = announcementList.get(groupPosition); AnnouncementFull item = announcementList.get(groupPosition);
RowAnnouncementsItemBinding b = holder.b; RowAnnouncementsItemBinding b = holder.b;
b.announcementsItem.setOnClickListener((v -> { if (onClick != null) {
if (onClick != null) { b.announcementsItem.setOnClickListener(v -> onClick.onClick(v, item));
onClick.onClick(v, item); }
} else {
})); b.announcementsItem.setOnClickListener(null);
}
b.announcementsItemSender.setText(item.getTeacherName()); b.announcementsItemSender.setText(item.getTeacherName());
b.announcementsItemTitle.setText(item.getSubject()); b.announcementsItemTitle.setText(item.getSubject());
b.announcementsItemText.setText(item.getText()); b.announcementsItemText.setText(item.getText());

View File

@ -107,6 +107,10 @@ public class AnnouncementsFragment extends Fragment {
if (app == null || activity == null || b == null || !isAdded()) if (app == null || activity == null || b == null || !isAdded())
return; return;
for (AnnouncementFull it : announcements) {
it.filterNotes();
}
if (announcements == null) { if (announcements == null) {
recyclerView.setVisibility(View.GONE); recyclerView.setVisibility(View.GONE);
b.announcementsNoData.setVisibility(View.VISIBLE); b.announcementsNoData.setVisibility(View.VISIBLE);

View File

@ -28,6 +28,7 @@ import kotlin.coroutines.CoroutineContext
class AttendanceAdapter( class AttendanceAdapter(
val activity: AppCompatActivity, val activity: AppCompatActivity,
val type: Int, val type: Int,
val showNotes: Boolean = true,
var onAttendanceClick: ((item: AttendanceFull) -> Unit)? = null var onAttendanceClick: ((item: AttendanceFull) -> Unit)? = null
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), CoroutineScope { ) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), CoroutineScope {
companion object { companion object {
@ -175,7 +176,10 @@ class AttendanceAdapter(
holder is EmptyViewHolder && item is AttendanceEmpty -> holder.onBind(activity, app, item, position, this) holder is EmptyViewHolder && item is AttendanceEmpty -> holder.onBind(activity, app, item, position, this)
} }
holder.itemView.setOnClickListener(onClickListener) if (item !is AttendanceFull || onAttendanceClick != null)
holder.itemView.setOnClickListener(onClickListener)
else
holder.itemView.setOnClickListener(null)
} }
fun notifyItemChanged(model: Any) { fun notifyItemChanged(model: Any) {

View File

@ -7,17 +7,21 @@ package pl.szczodrzynski.edziennik.ui.attendance
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import androidx.core.view.isVisible
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.full.AttendanceFull import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull
import pl.szczodrzynski.edziennik.databinding.AttendanceDetailsDialogBinding import pl.szczodrzynski.edziennik.databinding.AttendanceDetailsDialogBinding
import pl.szczodrzynski.edziennik.ext.setTintColor import pl.szczodrzynski.edziennik.ext.setTintColor
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.ui.notes.setupNotesButton
import pl.szczodrzynski.edziennik.utils.BetterLink import pl.szczodrzynski.edziennik.utils.BetterLink
import pl.szczodrzynski.edziennik.utils.managers.NoteManager
class AttendanceDetailsDialog( class AttendanceDetailsDialog(
activity: AppCompatActivity, activity: AppCompatActivity,
private val attendance: AttendanceFull, private val attendance: AttendanceFull,
private val showNotes: Boolean = true,
onShowListener: ((tag: String) -> Unit)? = null, onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null, onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<AttendanceDetailsDialogBinding>(activity, onShowListener, onDismissListener) { ) : BindingDialog<AttendanceDetailsDialogBinding>(activity, onShowListener, onDismissListener) {
@ -48,5 +52,15 @@ class AttendanceDetailsDialog(
onActionSelected = dialog::dismiss onActionSelected = dialog::dismiss
) )
} }
b.notesButton.isVisible = showNotes
b.notesButton.setupNotesButton(
activity = activity,
owner = attendance,
onShowListener = onShowListener,
onDismissListener = onDismissListener,
)
if (showNotes)
NoteManager.setLegendText(attendance, b.legend)
} }
} }

View File

@ -66,6 +66,10 @@ class AttendanceListFragment : LazyFragment(), CoroutineScope {
app.db.attendanceDao().getAll(App.profileId).observe(this@AttendanceListFragment, Observer { items -> this@AttendanceListFragment.launch { app.db.attendanceDao().getAll(App.profileId).observe(this@AttendanceListFragment, Observer { items -> this@AttendanceListFragment.launch {
if (!isAdded) return@launch if (!isAdded) return@launch
items.forEach {
it.filterNotes()
}
// load & configure the adapter // load & configure the adapter
adapter.items = withContext(Dispatchers.Default) { processAttendance(items) } adapter.items = withContext(Dispatchers.Default) { processAttendance(items) }
if (adapter.items.isNotNullNorEmpty() && b.list.adapter == null) { if (adapter.items.isNotNullNorEmpty() && b.list.adapter == null) {

View File

@ -75,6 +75,10 @@ class AttendanceSummaryFragment : LazyFragment(), CoroutineScope {
app.db.attendanceDao().getAll(App.profileId).observe(this@AttendanceSummaryFragment, Observer { items -> this@AttendanceSummaryFragment.launch { app.db.attendanceDao().getAll(App.profileId).observe(this@AttendanceSummaryFragment, Observer { items -> this@AttendanceSummaryFragment.launch {
if (!isAdded) return@launch if (!isAdded) return@launch
items.forEach {
it.filterNotes()
}
// load & configure the adapter // load & configure the adapter
attendance = items attendance = items
adapter.items = withContext(Dispatchers.Default) { processAttendance() } adapter.items = withContext(Dispatchers.Default) { processAttendance() }

View File

@ -19,6 +19,7 @@ import pl.szczodrzynski.edziennik.ui.attendance.models.AttendanceDayRange
import pl.szczodrzynski.edziennik.ui.attendance.models.AttendanceMonth import pl.szczodrzynski.edziennik.ui.attendance.models.AttendanceMonth
import pl.szczodrzynski.edziennik.ui.grades.models.ExpandableItemModel import pl.szczodrzynski.edziennik.ui.grades.models.ExpandableItemModel
import pl.szczodrzynski.edziennik.ui.grades.viewholder.BindableViewHolder import pl.szczodrzynski.edziennik.ui.grades.viewholder.BindableViewHolder
import pl.szczodrzynski.edziennik.utils.managers.NoteManager
import pl.szczodrzynski.edziennik.utils.models.Week import pl.szczodrzynski.edziennik.utils.models.Week
class AttendanceViewHolder( class AttendanceViewHolder(
@ -38,7 +39,10 @@ class AttendanceViewHolder(
b.attendanceView.setAttendance(item, manager, bigView = true) b.attendanceView.setAttendance(item, manager, bigView = true)
b.type.text = item.typeName b.type.text = item.typeName
b.subjectName.text = item.subjectLongName ?: item.lessonTopic b.subjectName.text = item.getNoteSubstituteText(adapter.showNotes) ?: item.subjectLongName
?: item.lessonTopic
if (adapter.showNotes)
NoteManager.prependIcon(item, b.subjectName)
b.dateTime.text = listOf( b.dateTime.text = listOf(
Week.getFullDayName(item.date.weekDay), Week.getFullDayName(item.date.weekDay),
item.date.formattedStringShort, item.date.formattedStringShort,

View File

@ -19,7 +19,6 @@ import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.ERROR_APP_CRASH import pl.szczodrzynski.edziennik.data.api.ERROR_APP_CRASH
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ErrorReportRequest import pl.szczodrzynski.edziennik.data.api.szkolny.request.ErrorReportRequest
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.ext.resolveColor import pl.szczodrzynski.edziennik.ext.resolveColor
import pl.szczodrzynski.edziennik.utils.Themes.appTheme import pl.szczodrzynski.edziennik.utils.Themes.appTheme
import pl.szczodrzynski.edziennik.utils.html.BetterHtml import pl.szczodrzynski.edziennik.utils.html.BetterHtml
@ -116,7 +115,7 @@ class CrashActivity : AppCompatActivity(), CoroutineScope {
content = content.replace(packageName.toRegex(), "<font color='#4caf50'>$packageName</font>") content = content.replace(packageName.toRegex(), "<font color='#4caf50'>$packageName</font>")
content = content.replace("\n".toRegex(), "<br>") content = content.replace("\n".toRegex(), "<br>")
contentPlain += "\n" + Build.MANUFACTURER + "\n" + Build.BRAND + "\n" + Build.MODEL + "\n" + Build.DEVICE + "\n" contentPlain += "\n" + Build.MANUFACTURER + "\n" + Build.BRAND + "\n" + Build.MODEL + "\n" + Build.DEVICE + "\n"
if (app.profile.registration == Profile.REGISTRATION_ENABLED) { if (!app.profile.canShare) {
contentPlain += "U: " + app.profile.userCode + "\nS: " + app.profile.studentNameLong + "\n" contentPlain += "U: " + app.profile.userCode + "\nS: " + app.profile.studentNameLong + "\n"
} }
contentPlain += BuildConfig.VERSION_NAME + " " + BuildConfig.BUILD_TYPE contentPlain += BuildConfig.VERSION_NAME + " " + BuildConfig.BUILD_TYPE

View File

@ -9,7 +9,6 @@ import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_ENABLED
import pl.szczodrzynski.edziennik.databinding.DialogConfigAgendaBinding import pl.szczodrzynski.edziennik.databinding.DialogConfigAgendaBinding
import pl.szczodrzynski.edziennik.ext.onChange import pl.szczodrzynski.edziennik.ext.onChange
import pl.szczodrzynski.edziennik.ui.dialogs.base.ConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.base.ConfigDialog
@ -40,9 +39,9 @@ class AgendaConfigDialog(
b.isAgendaMode = profileConfig.agendaViewType == Profile.AGENDA_DEFAULT b.isAgendaMode = profileConfig.agendaViewType == Profile.AGENDA_DEFAULT
b.eventSharingEnabled.isChecked = b.eventSharingEnabled.isChecked =
app.profile.enableSharedEvents && app.profile.registration == REGISTRATION_ENABLED app.profile.enableSharedEvents && app.profile.canShare
b.eventSharingEnabled.onChange { _, isChecked -> b.eventSharingEnabled.onChange { _, isChecked ->
if (isChecked && app.profile.registration != REGISTRATION_ENABLED) { if (isChecked && !app.profile.canShare) {
b.eventSharingEnabled.isChecked = false b.eventSharingEnabled.isChecked = false
val dialog = RegistrationConfigDialog( val dialog = RegistrationConfigDialog(
activity, activity,

View File

@ -19,7 +19,7 @@ import kotlin.coroutines.CoroutineContext
class RegistrationConfigDialog( class RegistrationConfigDialog(
val activity: AppCompatActivity, val activity: AppCompatActivity,
val profile: Profile, val profile: Profile,
val onChangeListener: ((enabled: Boolean) -> Unit)? = null, val onChangeListener: (suspend (enabled: Boolean) -> Unit)? = null,
val onShowListener: ((tag: String) -> Unit)? = null, val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope { ) : CoroutineScope {
@ -57,6 +57,21 @@ class RegistrationConfigDialog(
.show() .show()
} }
fun showNoteShareDialog() {
onShowListener?.invoke(TAG + "NoteShare")
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.registration_config_note_sharing_title)
.setMessage(R.string.registration_config_note_sharing_text)
.setPositiveButton(R.string.i_agree) { _, _ ->
enableRegistration()
}
.setNegativeButton(R.string.cancel, null)
.setOnDismissListener {
onDismissListener?.invoke(TAG + "NoteShare")
}
.show()
}
fun showEnableDialog() { fun showEnableDialog() {
onShowListener?.invoke(TAG + "Enable") onShowListener?.invoke(TAG + "Enable")
dialog = MaterialAlertDialogBuilder(activity) dialog = MaterialAlertDialogBuilder(activity)

View File

@ -27,6 +27,7 @@ import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.databinding.DialogEventDetailsBinding import pl.szczodrzynski.edziennik.databinding.DialogEventDetailsBinding
import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.ext.*
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.ui.notes.setupNotesButton
import pl.szczodrzynski.edziennik.ui.timetable.TimetableFragment import pl.szczodrzynski.edziennik.ui.timetable.TimetableFragment
import pl.szczodrzynski.edziennik.utils.BetterLink import pl.szczodrzynski.edziennik.utils.BetterLink
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
@ -36,6 +37,7 @@ class EventDetailsDialog(
activity: AppCompatActivity, activity: AppCompatActivity,
// this event is observed for changes // this event is observed for changes
private var event: EventFull, private var event: EventFull,
private val showNotes: Boolean = true,
onShowListener: ((tag: String) -> Unit)? = null, onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null, onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<DialogEventDetailsBinding>(activity, onShowListener, onDismissListener) { ) : BindingDialog<DialogEventDetailsBinding>(activity, onShowListener, onDismissListener) {
@ -96,6 +98,8 @@ class EventDetailsDialog(
manager.markAsSeen(event) manager.markAsSeen(event)
} }
event.filterNotes()
val bullet = "" val bullet = ""
val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity) val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
@ -104,7 +108,7 @@ class EventDetailsDialog(
} }
catch (_: Exception) {} catch (_: Exception) {}
manager.setLegendText(b.legend, event) manager.setLegendText(b.legend, event, showNotes)
b.typeColor.background?.setTintColor(event.eventColor) b.typeColor.background?.setTintColor(event.eventColor)
@ -257,6 +261,14 @@ class EventDetailsDialog(
it.putStringArray("attachmentNames", event.attachmentNames!!.toTypedArray()) it.putStringArray("attachmentNames", event.attachmentNames!!.toTypedArray())
}, owner = event) }, owner = event)
} }
b.notesButton.isVisible = showNotes
b.notesButton.setupNotesButton(
activity = activity,
owner = event,
onShowListener = onShowListener,
onDismissListener = onDismissListener,
)
} }
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true) @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)

View File

@ -22,12 +22,14 @@ class EventListAdapter(
val simpleMode: Boolean = false, val simpleMode: Boolean = false,
val showWeekDay: Boolean = false, val showWeekDay: Boolean = false,
val showDate: Boolean = false, val showDate: Boolean = false,
val showColor: Boolean = true,
val showType: Boolean = true, val showType: Boolean = true,
val showTime: Boolean = true, val showTime: Boolean = true,
val showSubject: Boolean = true, val showSubject: Boolean = true,
val markAsSeen: Boolean = true, val markAsSeen: Boolean = true,
val showNotes: Boolean = true,
isReversed: Boolean = false, isReversed: Boolean = false,
val onItemClick: ((event: EventFull) -> Unit)? = null, val onEventClick: ((event: EventFull) -> Unit)? = null,
val onEventEditClick: ((event: EventFull) -> Unit)? = null, val onEventEditClick: ((event: EventFull) -> Unit)? = null,
) : SearchableAdapter<EventFull>(isReversed), CoroutineScope { ) : SearchableAdapter<EventFull>(isReversed), CoroutineScope {
companion object { companion object {

View File

@ -13,9 +13,6 @@ import androidx.appcompat.app.AppCompatActivity
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.ColorPickerDialog
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.sizeDp
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
@ -31,7 +28,6 @@ import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.data.db.full.LessonFull import pl.szczodrzynski.edziennik.data.db.full.LessonFull
import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding
import pl.szczodrzynski.edziennik.ext.* import pl.szczodrzynski.edziennik.ext.*
import pl.szczodrzynski.edziennik.ui.dialogs.StyledTextDialog
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.ui.dialogs.settings.RegistrationConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.settings.RegistrationConfigDialog
import pl.szczodrzynski.edziennik.ui.views.TimeDropdown.Companion.DISPLAY_LESSONS import pl.szczodrzynski.edziennik.ui.views.TimeDropdown.Companion.DISPLAY_LESSONS
@ -123,20 +119,13 @@ class EventManualDialog(
} }
} }
b.topicLayout.endIconDrawable = IconicsDrawable(activity, CommunityMaterial.Icon3.cmd_open_in_new).apply { textStylingManager.attachToField(
sizeDp = 24 activity = activity,
} textLayout = b.topicLayout,
b.topicLayout.setEndIconOnClickListener { textEdit = b.topic,
StyledTextDialog( onShowListener = onShowListener,
activity, onDismissListener = onDismissListener,
initialText = b.topic.text, )
onSuccess = {
b.topic.text = it
},
onShowListener,
onDismissListener
).show()
}
stylingConfig = StylingConfigBase(editText = b.topic, htmlMode = SIMPLE) stylingConfig = StylingConfigBase(editText = b.topic, htmlMode = SIMPLE)
@ -414,7 +403,7 @@ class EventManualDialog(
val share = b.shareSwitch.isChecked val share = b.shareSwitch.isChecked
if (share && profile.registration != Profile.REGISTRATION_ENABLED) { if (share && !profile.canShare) {
RegistrationConfigDialog(activity, profile, onChangeListener = { enabled -> RegistrationConfigDialog(activity, profile, onChangeListener = { enabled ->
if (enabled) if (enabled)
saveEvent() saveEvent()

View File

@ -36,14 +36,16 @@ class EventViewHolder(
) { ) {
val manager = app.eventManager val manager = app.eventManager
b.root.onClick { if (adapter.onEventClick != null) {
adapter.onItemClick?.invoke(item) b.root.onClick {
if (!item.seen) { adapter.onEventClick.invoke(item)
manager.markAsSeen(item) if (!item.seen) {
} manager.markAsSeen(item)
if (item.showAsUnseen == true) { }
item.showAsUnseen = false if (item.showAsUnseen == true) {
adapter.notifyItemChanged(item) item.showAsUnseen = false
adapter.notifyItemChanged(item)
}
} }
} }
@ -52,7 +54,7 @@ class EventViewHolder(
b.simpleMode = adapter.simpleMode b.simpleMode = adapter.simpleMode
manager.setEventTopic(b.topic, item, showType = false) manager.setEventTopic(b.topic, item, showType = false, showNotes = adapter.showNotes)
b.topic.text = adapter.highlightSearchText( b.topic.text = adapter.highlightSearchText(
item = item, item = item,
text = b.topic.text, text = b.topic.text,
@ -67,13 +69,13 @@ class EventViewHolder(
if (adapter.showDate) if (adapter.showDate)
item.date.getRelativeString(activity, 7) ?: item.date.formattedStringShort item.date.getRelativeString(activity, 7) ?: item.date.formattedStringShort
else null, else null,
if (adapter.showType) if (adapter.showType && item.typeName != null)
item.typeName item.typeName
else null, else null,
if (adapter.showTime) if (adapter.showTime)
item.time?.stringHM ?: app.getString(R.string.event_all_day) item.time?.stringHM ?: app.getString(R.string.event_all_day)
else null, else null,
if (adapter.showSubject) if (adapter.showSubject && item.subjectLongName != null)
adapter.highlightSearchText( adapter.highlightSearchText(
item = item, item = item,
text = item.subjectLongName ?: "", text = item.subjectLongName ?: "",
@ -111,13 +113,19 @@ class EventViewHolder(
b.attachmentIcon.isVisible = item.hasAttachments b.attachmentIcon.isVisible = item.hasAttachments
b.typeColor.background?.setTintColor(item.eventColor) b.typeColor.background?.setTintColor(item.eventColor)
b.typeColor.isVisible = adapter.showType b.typeColor.isVisible = adapter.showType && adapter.showColor
b.editButton.isVisible = !adapter.simpleMode && item.addedManually && !item.isDone b.editButton.isVisible = !adapter.simpleMode
b.editButton.onClick { && item.addedManually
adapter.onEventEditClick?.invoke(item) && !item.isDone
&& adapter.onEventEditClick != null
if (adapter.onEventEditClick != null) {
b.editButton.onClick {
adapter.onEventEditClick.invoke(item)
}
b.editButton.attachToastHint(R.string.hint_edit_event)
} }
b.editButton.attachToastHint(R.string.hint_edit_event)
if (item.showAsUnseen == null) if (item.showAsUnseen == null)
item.showAsUnseen = !item.seen item.showAsUnseen = !item.seen

View File

@ -15,12 +15,15 @@ import pl.szczodrzynski.edziennik.ext.onClick
import pl.szczodrzynski.edziennik.ext.setTintColor import pl.szczodrzynski.edziennik.ext.setTintColor
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.ui.dialogs.settings.GradesConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.settings.GradesConfigDialog
import pl.szczodrzynski.edziennik.ui.notes.setupNotesButton
import pl.szczodrzynski.edziennik.utils.BetterLink import pl.szczodrzynski.edziennik.utils.BetterLink
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import pl.szczodrzynski.edziennik.utils.managers.NoteManager
class GradeDetailsDialog( class GradeDetailsDialog(
activity: AppCompatActivity, activity: AppCompatActivity,
private val grade: GradeFull, private val grade: GradeFull,
private val showNotes: Boolean = true,
onShowListener: ((tag: String) -> Unit)? = null, onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null, onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<DialogGradeDetailsBinding>(activity, onShowListener, onDismissListener) { ) : BindingDialog<DialogGradeDetailsBinding>(activity, onShowListener, onDismissListener) {
@ -71,23 +74,34 @@ class GradeDetailsDialog(
val historyList = withContext(Dispatchers.Default) { val historyList = withContext(Dispatchers.Default) {
app.db.gradeDao().getByParentIdNow(App.profileId, grade.id) app.db.gradeDao().getByParentIdNow(App.profileId, grade.id)
} }
if (historyList.isEmpty()) {
b.historyVisible = false
return
}
b.historyVisible = true
//b.gradeHistoryNest.isNestedScrollingEnabled = false
b.gradeHistoryList.adapter = GradesAdapter(activity, { historyList.forEach {
GradeDetailsDialog(activity, it).show() it.filterNotes()
}).also {
it.items = historyList.toMutableList()
} }
b.gradeHistoryList.apply { b.historyVisible = historyList.isNotEmpty()
setHasFixedSize(true) if (historyList.isNotEmpty()) {
layoutManager = LinearLayoutManager(context) b.gradeHistoryList.adapter = GradesAdapter(activity, onGradeClick = {
addItemDecoration(SimpleDividerItemDecoration(context)) GradeDetailsDialog(activity, it).show()
}).also {
it.items = historyList.toMutableList()
}
b.gradeHistoryList.apply {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
addItemDecoration(SimpleDividerItemDecoration(context))
}
} }
b.notesButton.isVisible = showNotes
b.notesButton.setupNotesButton(
activity = activity,
owner = grade,
onShowListener = onShowListener,
onDismissListener = onDismissListener,
)
if (showNotes)
NoteManager.setLegendText(grade, b.legend)
} }
} }

View File

@ -25,6 +25,7 @@ import kotlin.coroutines.CoroutineContext
class GradesAdapter( class GradesAdapter(
val activity: AppCompatActivity, val activity: AppCompatActivity,
val showNotes: Boolean = true,
var onGradeClick: ((item: GradeFull) -> Unit)? = null, var onGradeClick: ((item: GradeFull) -> Unit)? = null,
var onGradesEditorClick: ((subject: GradesSubject, semester: GradesSemester) -> Unit)? = null var onGradesEditorClick: ((subject: GradesSubject, semester: GradesSemester) -> Unit)? = null
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), CoroutineScope { ) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), CoroutineScope {
@ -218,7 +219,10 @@ class GradesAdapter(
} }
} }
holder.itemView.setOnClickListener(onClickListener) if (item !is GradeFull || onGradeClick != null)
holder.itemView.setOnClickListener(onClickListener)
else
holder.itemView.setOnClickListener(null)
} }
fun notifyItemChanged(model: Any) { fun notifyItemChanged(model: Any) {

View File

@ -76,6 +76,10 @@ class GradesListFragment : Fragment(), CoroutineScope {
app.db.gradeDao().getAllOrderBy(App.profileId, app.gradesManager.getOrderByString()).observe(viewLifecycleOwner, Observer { grades -> this@GradesListFragment.launch { app.db.gradeDao().getAllOrderBy(App.profileId, app.gradesManager.getOrderByString()).observe(viewLifecycleOwner, Observer { grades -> this@GradesListFragment.launch {
if (!isAdded) return@launch if (!isAdded) return@launch
grades.forEach {
it.filterNotes()
}
val items = when { val items = when {
app.config.forProfile().grades.hideSticksFromOld && App.devMode -> grades.filter { it.value != 1.0f } app.config.forProfile().grades.hideSticksFromOld && App.devMode -> grades.filter { it.value != 1.0f }
else -> grades else -> grades

View File

@ -15,6 +15,7 @@ import pl.szczodrzynski.edziennik.data.db.full.GradeFull
import pl.szczodrzynski.edziennik.databinding.GradesItemGradeBinding import pl.szczodrzynski.edziennik.databinding.GradesItemGradeBinding
import pl.szczodrzynski.edziennik.ui.grades.GradesAdapter import pl.szczodrzynski.edziennik.ui.grades.GradesAdapter
import pl.szczodrzynski.edziennik.ui.grades.models.GradesSubject import pl.szczodrzynski.edziennik.ui.grades.models.GradesSubject
import pl.szczodrzynski.edziennik.utils.managers.NoteManager
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
class GradeViewHolder( class GradeViewHolder(
@ -33,14 +34,16 @@ class GradeViewHolder(
b.gradeName.setGrade(grade, manager, bigView = true) b.gradeName.setGrade(grade, manager, bigView = true)
if (grade.description.isNullOrBlank()) { if (grade.description.isNullOrBlank()) {
b.gradeDescription.text = grade.category b.gradeDescription.text =
grade.getNoteSubstituteText(adapter.showNotes) ?: grade.category
b.gradeCategory.text = b.gradeCategory.text =
if (grade.isImprovement) if (grade.isImprovement)
app.getString(R.string.grades_improvement_category_format, "") app.getString(R.string.grades_improvement_category_format, "")
else else
"" ""
} else { } else {
b.gradeDescription.text = grade.description b.gradeDescription.text =
grade.getNoteSubstituteText(adapter.showNotes) ?: grade.description
b.gradeCategory.text = b.gradeCategory.text =
if (grade.isImprovement) if (grade.isImprovement)
app.getString(R.string.grades_improvement_category_format, grade.category) app.getString(R.string.grades_improvement_category_format, grade.category)
@ -48,6 +51,9 @@ class GradeViewHolder(
grade.category grade.category
} }
if (adapter.showNotes)
NoteManager.prependIcon(grade, b.gradeDescription)
val weightText = manager.getWeightString(activity, grade, showClassAverage = true) val weightText = manager.getWeightString(activity, grade, showClassAverage = true)
b.gradeWeight.text = weightText b.gradeWeight.text = weightText
b.gradeWeight.isVisible = weightText != null b.gradeWeight.isVisible = weightText != null

View File

@ -24,6 +24,7 @@ interface HomeCard {
const val CARD_TIMETABLE = 2 const val CARD_TIMETABLE = 2
const val CARD_GRADES = 3 const val CARD_GRADES = 3
const val CARD_EVENTS = 4 const val CARD_EVENTS = 4
const val CARD_NOTES = 5
} }
val id: Int val id: Int

View File

@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.ui.dialogs.base.BaseDialog
import pl.szczodrzynski.edziennik.ui.home.HomeCard.Companion.CARD_EVENTS import pl.szczodrzynski.edziennik.ui.home.HomeCard.Companion.CARD_EVENTS
import pl.szczodrzynski.edziennik.ui.home.HomeCard.Companion.CARD_GRADES import pl.szczodrzynski.edziennik.ui.home.HomeCard.Companion.CARD_GRADES
import pl.szczodrzynski.edziennik.ui.home.HomeCard.Companion.CARD_LUCKY_NUMBER import pl.szczodrzynski.edziennik.ui.home.HomeCard.Companion.CARD_LUCKY_NUMBER
import pl.szczodrzynski.edziennik.ui.home.HomeCard.Companion.CARD_NOTES
import pl.szczodrzynski.edziennik.ui.home.HomeCard.Companion.CARD_TIMETABLE import pl.szczodrzynski.edziennik.ui.home.HomeCard.Companion.CARD_TIMETABLE
class HomeConfigDialog( class HomeConfigDialog(
@ -32,6 +33,7 @@ class HomeConfigDialog(
R.string.card_type_timetable to CARD_TIMETABLE, R.string.card_type_timetable to CARD_TIMETABLE,
R.string.card_type_grades to CARD_GRADES, R.string.card_type_grades to CARD_GRADES,
R.string.card_type_events to CARD_EVENTS, R.string.card_type_events to CARD_EVENTS,
R.string.card_type_notes to CARD_NOTES,
).mapKeys { (resId, _) -> activity.getString(resId) } ).mapKeys { (resId, _) -> activity.getString(resId) }
override fun getDefaultSelectedItems() = override fun getDefaultSelectedItems() =

View File

@ -144,7 +144,8 @@ class HomeFragment : Fragment(), CoroutineScope {
HomeCardModel(app.profile.id, HomeCard.CARD_LUCKY_NUMBER), HomeCardModel(app.profile.id, HomeCard.CARD_LUCKY_NUMBER),
HomeCardModel(app.profile.id, HomeCard.CARD_TIMETABLE), HomeCardModel(app.profile.id, HomeCard.CARD_TIMETABLE),
HomeCardModel(app.profile.id, HomeCard.CARD_EVENTS), HomeCardModel(app.profile.id, HomeCard.CARD_EVENTS),
HomeCardModel(app.profile.id, HomeCard.CARD_GRADES) HomeCardModel(app.profile.id, HomeCard.CARD_GRADES),
HomeCardModel(app.profile.id, HomeCard.CARD_NOTES),
) )
app.config.forProfile().ui.homeCards = app.config.forProfile().ui.homeCards.toMutableList().also { it.addAll(cards) } app.config.forProfile().ui.homeCards = app.config.forProfile().ui.homeCards.toMutableList().also { it.addAll(cards) }
} }
@ -157,6 +158,7 @@ class HomeFragment : Fragment(), CoroutineScope {
HomeCard.CARD_TIMETABLE -> HomeTimetableCard(it.cardId, app, activity, this, app.profile) HomeCard.CARD_TIMETABLE -> HomeTimetableCard(it.cardId, app, activity, this, app.profile)
HomeCard.CARD_GRADES -> HomeGradesCard(it.cardId, app, activity, this, app.profile) HomeCard.CARD_GRADES -> HomeGradesCard(it.cardId, app, activity, this, app.profile)
HomeCard.CARD_EVENTS -> HomeEventsCard(it.cardId, app, activity, this, app.profile) HomeCard.CARD_EVENTS -> HomeEventsCard(it.cardId, app, activity, this, app.profile)
HomeCard.CARD_NOTES -> HomeNotesCard(it.cardId, app, activity, this, app.profile)
else -> null else -> null
} as HomeCard? } as HomeCard?
} }

View File

@ -66,7 +66,7 @@ class HomeEventsCard(
showTime = false, showTime = false,
showSubject = false, showSubject = false,
markAsSeen = false, markAsSeen = false,
onItemClick = { onEventClick = {
EventDetailsDialog( EventDetailsDialog(
activity, activity,
it it
@ -82,6 +82,10 @@ class HomeEventsCard(
) )
app.db.eventDao().getNearestNotDone(profile.id, Date.getToday(), 4).observe(activity, Observer { events -> app.db.eventDao().getNearestNotDone(profile.id, Date.getToday(), 4).observe(activity, Observer { events ->
events.forEach {
it.filterNotes()
}
adapter.setAllItems(events) adapter.setAllItems(events)
if (b.eventsView.adapter == null) { if (b.eventsView.adapter == null) {
b.eventsView.adapter = adapter b.eventsView.adapter = adapter

View File

@ -0,0 +1,122 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-10-28.
*/
package pl.szczodrzynski.edziennik.ui.home.cards
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.isVisible
import androidx.core.view.plusAssign
import androidx.core.view.setMargins
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.data.db.entity.Note
import pl.szczodrzynski.edziennik.data.db.entity.Noteable
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.databinding.CardHomeNotesBinding
import pl.szczodrzynski.edziennik.ext.dp
import pl.szczodrzynski.edziennik.ext.onClick
import pl.szczodrzynski.edziennik.ui.home.HomeCard
import pl.szczodrzynski.edziennik.ui.home.HomeCardAdapter
import pl.szczodrzynski.edziennik.ui.home.HomeFragment
import pl.szczodrzynski.edziennik.ui.notes.NoteDetailsDialog
import pl.szczodrzynski.edziennik.ui.notes.NoteEditorDialog
import pl.szczodrzynski.edziennik.ui.notes.NoteListAdapter
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import kotlin.coroutines.CoroutineContext
class HomeNotesCard(
override val id: Int,
val app: App,
val activity: MainActivity,
val fragment: HomeFragment,
val profile: Profile,
) : HomeCard, CoroutineScope {
companion object {
private const val TAG = "HomeNotesCard"
}
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private val manager
get() = app.noteManager
private lateinit var adapter: NoteListAdapter
private fun onNoteClick(note: Note) = launch {
val owner = withContext(Dispatchers.IO) {
manager.getOwner(note)
} as? Noteable
NoteDetailsDialog(
activity = activity,
owner = owner,
note = note,
).show()
}
private fun onNoteAddClick(view: View?) {
NoteEditorDialog(
activity = activity,
owner = null,
editingNote = null,
profileId = profile.id,
).show()
}
override fun bind(position: Int, holder: HomeCardAdapter.ViewHolder) { launch {
holder.root.removeAllViews()
val b = CardHomeNotesBinding.inflate(LayoutInflater.from(holder.root.context))
b.root.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
setMargins(8.dp)
}
holder.root += b.root
adapter = NoteListAdapter(
activity = activity,
onNoteClick = this@HomeNotesCard::onNoteClick,
onNoteEditClick = null,
)
app.db.noteDao().getAllNoOwner(profileId = profile.id).observe(activity) { notes ->
// show/hide relevant views
b.list.isVisible = notes.isNotEmpty()
b.noData.isVisible = notes.isEmpty()
if (notes.isEmpty()) {
return@observe
}
// apply the new note list
adapter.setAllItems(notes.take(4))
// configure the adapter & recycler view
if (b.list.adapter == null) {
b.list.adapter = adapter
b.list.apply {
//setHasFixedSize(true)
isNestedScrollingEnabled = false
layoutManager = LinearLayoutManager(context)
addItemDecoration(SimpleDividerItemDecoration(context))
}
} else {
adapter.notifyDataSetChanged()
}
}
b.addNote.onClick(this@HomeNotesCard::onNoteAddClick)
holder.root.onClick {
activity.loadTarget(MainActivity.DRAWER_ITEM_AGENDA)
}
}}
override fun unbind(position: Int, holder: HomeCardAdapter.ViewHolder) = Unit
}

View File

@ -64,7 +64,7 @@ class HomeworkListFragment : LazyFragment(), CoroutineScope {
showSubject = true, showSubject = true,
markAsSeen = true, markAsSeen = true,
isReversed = homeworkDate == HomeworkDate.PAST, isReversed = homeworkDate == HomeworkDate.PAST,
onItemClick = { onEventClick = {
EventDetailsDialog( EventDetailsDialog(
activity, activity,
it it
@ -82,6 +82,10 @@ class HomeworkListFragment : LazyFragment(), CoroutineScope {
app.db.eventDao().getAllByType(App.profileId, Event.TYPE_HOMEWORK, filter).observe(this@HomeworkListFragment, Observer { events -> app.db.eventDao().getAllByType(App.profileId, Event.TYPE_HOMEWORK, filter).observe(this@HomeworkListFragment, Observer { events ->
if (!isAdded) return@Observer if (!isAdded) return@Observer
events.forEach {
it.filterNotes()
}
// show/hide relevant views // show/hide relevant views
setSwipeToRefresh(events.isEmpty()) setSwipeToRefresh(events.isEmpty())
b.progressBar.isVisible = false b.progressBar.isVisible = false

View File

@ -19,6 +19,7 @@ import pl.szczodrzynski.edziennik.ext.onClick
import pl.szczodrzynski.edziennik.ext.resolveAttr import pl.szczodrzynski.edziennik.ext.resolveAttr
import pl.szczodrzynski.edziennik.ui.grades.viewholder.BindableViewHolder import pl.szczodrzynski.edziennik.ui.grades.viewholder.BindableViewHolder
import pl.szczodrzynski.edziennik.ui.messages.MessagesUtils import pl.szczodrzynski.edziennik.ui.messages.MessagesUtils
import pl.szczodrzynski.edziennik.utils.managers.NoteManager
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
class MessageViewHolder( class MessageViewHolder(
@ -73,7 +74,10 @@ class MessageViewHolder(
color = colorHighlight color = colorHighlight
) )
adapter.onItemClick?.let { listener -> if (adapter.showNotes)
NoteManager.prependIcon(item, b.messageSubject)
adapter.onMessageClick?.let { listener ->
b.root.onClick { listener(item) } b.root.onClick { listener(item) }
} }
adapter.onStarClick?.let { listener -> adapter.onStarClick?.let { listener ->

View File

@ -13,7 +13,8 @@ import pl.szczodrzynski.edziennik.ui.search.SearchableAdapter
class MessagesAdapter( class MessagesAdapter(
val activity: AppCompatActivity, val activity: AppCompatActivity,
val teachers: List<Teacher>, val teachers: List<Teacher>,
val onItemClick: ((item: MessageFull) -> Unit)? = null, val showNotes: Boolean = true,
val onMessageClick: ((item: MessageFull) -> Unit)? = null,
val onStarClick: ((item: MessageFull) -> Unit)? = null, val onStarClick: ((item: MessageFull) -> Unit)? = null,
) : SearchableAdapter<MessageFull>() { ) : SearchableAdapter<MessageFull>() {
companion object { companion object {

View File

@ -62,7 +62,7 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
app.db.teacherDao().getAllNow(App.profileId) app.db.teacherDao().getAllNow(App.profileId)
} }
adapter = MessagesAdapter(activity, teachers, onItemClick = { adapter = MessagesAdapter(activity, teachers, onMessageClick = {
val (target, args) = val (target, args) =
if (it.isDraft) { if (it.isDraft) {
TARGET_MESSAGES_COMPOSE to Bundle("message" to app.gson.toJson(it)) TARGET_MESSAGES_COMPOSE to Bundle("message" to app.gson.toJson(it))
@ -81,6 +81,8 @@ class MessagesListFragment : LazyFragment(), CoroutineScope {
return@Observer return@Observer
messages.forEach { message -> messages.forEach { message ->
message.filterNotes()
// uh oh, so these are the workarounds ?? // uh oh, so these are the workarounds ??
message.recipients?.removeAll { it.profileId != message.profileId } message.recipients?.removeAll { it.profileId != message.profileId }
message.recipients?.forEach { recipient -> message.recipients?.forEach { recipient ->

View File

@ -30,6 +30,7 @@ import pl.szczodrzynski.edziennik.ext.*
import pl.szczodrzynski.edziennik.ui.dialogs.settings.MessagesConfigDialog import pl.szczodrzynski.edziennik.ui.dialogs.settings.MessagesConfigDialog
import pl.szczodrzynski.edziennik.ui.messages.MessagesUtils import pl.szczodrzynski.edziennik.ui.messages.MessagesUtils
import pl.szczodrzynski.edziennik.ui.messages.list.MessagesFragment import pl.szczodrzynski.edziennik.ui.messages.list.MessagesFragment
import pl.szczodrzynski.edziennik.ui.notes.setupNotesButton
import pl.szczodrzynski.edziennik.utils.Anim import pl.szczodrzynski.edziennik.utils.Anim
import pl.szczodrzynski.edziennik.utils.BetterLink import pl.szczodrzynski.edziennik.utils.BetterLink
import pl.szczodrzynski.edziennik.utils.html.BetterHtml import pl.szczodrzynski.edziennik.utils.html.BetterHtml
@ -266,6 +267,13 @@ class MessageFragment : Fragment(), CoroutineScope {
b.progress.visibility = View.GONE b.progress.visibility = View.GONE
Anim.fadeIn(b.content, 200, null) Anim.fadeIn(b.content, 200, null)
MessagesFragment.pageSelection = min(message.type, 1) MessagesFragment.pageSelection = min(message.type, 1)
b.notesButton.setupNotesButton(
activity = activity,
owner = message,
onShowListener = null,
onDismissListener = null,
)
} }
private fun showAttachments() { private fun showAttachments() {

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-10-27.
*/
package pl.szczodrzynski.edziennik.ui.notes
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.Binding
import pl.szczodrzynski.edziennik.data.db.entity.Note
import pl.szczodrzynski.edziennik.databinding.NoteListCategoryItemBinding
import pl.szczodrzynski.edziennik.ext.resolveDrawable
import pl.szczodrzynski.edziennik.ui.grades.viewholder.BindableViewHolder
class NoteCategoryViewHolder(
inflater: LayoutInflater,
parent: ViewGroup,
val b: NoteListCategoryItemBinding = NoteListCategoryItemBinding.inflate(
inflater,
parent,
false,
),
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<Note, NoteListAdapter> {
companion object {
private const val TAG = "NoteCategoryViewHolder"
}
override fun onBind(
activity: AppCompatActivity,
app: App,
item: Note,
position: Int,
adapter: NoteListAdapter,
) {
val manager = app.noteManager
val title = b.root as? TextView ?: return
val ownerType = item.ownerType ?: return
title.setText(manager.getOwnerTypeText(ownerType))
title.setCompoundDrawables(
manager.getOwnerTypeImage(ownerType).resolveDrawable(activity),
null,
null,
null,
)
Binding.drawableLeftAutoSize(title, enable = true)
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-10-24.
*/
package pl.szczodrzynski.edziennik.ui.notes
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Note
import pl.szczodrzynski.edziennik.data.db.entity.Noteable
import pl.szczodrzynski.edziennik.databinding.NoteDetailsDialogBinding
import pl.szczodrzynski.edziennik.ext.*
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.utils.models.Date
class NoteDetailsDialog(
activity: AppCompatActivity,
private val owner: Noteable?,
private var note: Note,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<NoteDetailsDialogBinding>(activity, onShowListener, onDismissListener) {
override val TAG = "NoteDetailsDialog"
override fun getTitleRes(): Int? = null
override fun inflate(layoutInflater: LayoutInflater) =
NoteDetailsDialogBinding.inflate(layoutInflater)
override fun getPositiveButtonText() = R.string.close
override fun getNeutralButtonText() = if (note.canEdit) R.string.homework_edit else null
private val manager
get() = app.noteManager
override suspend fun onNeutralClick(): Boolean {
NoteEditorDialog(
activity = activity,
owner = owner,
editingNote = note,
onShowListener = onShowListener,
onDismissListener = onDismissListener,
).show()
return NO_DISMISS
}
override suspend fun onShow() {
manager.configureHeader(activity, owner, b.header)
b.idsLayout.isVisible = App.devMode
// watch the note for changes
app.db.noteDao().get(note.profileId, note.id).observe(activity) {
if (it == null) {
dismiss()
return@observe
}
note = it
update()
}
}
private fun update() {
b.note = note
if (note.color != null) {
dialog.overlayBackgroundColor(note.color!!.toInt(), 0x50)
} else {
dialog.overlayBackgroundColor(0, 0)
}
b.addedBy.setText(
when (note.sharedBy) {
null -> R.string.notes_added_by_you_format
"self" -> R.string.event_details_shared_by_self_format
else -> R.string.event_details_shared_by_format
},
Date.fromMillis(note.addedDate).formattedString,
note.sharedByName ?: "",
)
}
}

View File

@ -0,0 +1,197 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-10-24.
*/
package pl.szczodrzynski.edziennik.ui.notes
import android.view.LayoutInflater
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Note
import pl.szczodrzynski.edziennik.data.db.entity.Noteable
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.databinding.NoteEditorDialogBinding
import pl.szczodrzynski.edziennik.ext.isNotNullNorBlank
import pl.szczodrzynski.edziennik.ext.resolveString
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.ui.dialogs.settings.RegistrationConfigDialog
import pl.szczodrzynski.edziennik.utils.TextInputDropDown
import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.HtmlMode
import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.StylingConfigBase
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class NoteEditorDialog(
activity: AppCompatActivity,
private val owner: Noteable?,
private val editingNote: Note?,
private val profileId: Int =
owner?.getNoteOwnerProfileId()
?: editingNote?.profileId
?: 0,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<NoteEditorDialogBinding>(activity, onShowListener, onDismissListener) {
override val TAG = "NoteEditorDialog"
override fun getTitleRes(): Int? = null
override fun inflate(layoutInflater: LayoutInflater) =
NoteEditorDialogBinding.inflate(layoutInflater)
override fun isCancelable() = false
override fun getPositiveButtonText() = R.string.save
override fun getNeutralButtonText() = if (editingNote != null) R.string.remove else null
override fun getNegativeButtonText() = R.string.cancel
private lateinit var topicStylingConfig: StylingConfigBase
private lateinit var bodyStylingConfig: StylingConfigBase
private val manager
get() = app.noteManager
private val textStylingManager
get() = app.textStylingManager
private var progressDialog: AlertDialog? = null
override suspend fun onPositiveClick(): Boolean {
val profile = withContext(Dispatchers.IO) {
app.db.profileDao().getByIdNow(profileId)
} ?: return NO_DISMISS
val note = buildNote(profile) ?: return NO_DISMISS
if (note.isShared && !profile.canShare) {
RegistrationConfigDialog(activity, profile, onChangeListener = { enabled ->
if (enabled)
onPositiveClick()
}).showNoteShareDialog()
return NO_DISMISS
}
if (note.isShared || editingNote?.isShared == true) {
progressDialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.please_wait)
.setMessage(when (note.isShared) {
true -> R.string.notes_editor_progress_sharing
false -> R.string.notes_editor_progress_unsharing
})
.setCancelable(false)
.show()
}
val success = manager.saveNote(activity, note, wasShared = editingNote?.isShared ?: false)
progressDialog?.dismiss()
return success
}
override suspend fun onNeutralClick(): Boolean {
// editingNote cannot be null, as the button is visible
val confirmation = suspendCoroutine<Boolean> { cont ->
var result = false
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.are_you_sure)
.setMessage(R.string.notes_editor_confirmation_text)
.setPositiveButton(R.string.yes) { _, _ -> result = true }
.setNegativeButton(R.string.no, null)
.setOnDismissListener { cont.resume(result) }
.show()
}
if (!confirmation)
return NO_DISMISS
if (editingNote?.isShared == true) {
progressDialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.please_wait)
.setMessage(R.string.notes_editor_progress_unsharing)
.setCancelable(false)
.show()
}
val success = manager.deleteNote(activity, editingNote ?: return NO_DISMISS)
progressDialog?.dismiss()
return success
}
override suspend fun onShow() {
manager.configureHeader(activity, owner, b.header)
topicStylingConfig = StylingConfigBase(editText = b.topic, htmlMode = HtmlMode.SIMPLE)
bodyStylingConfig = StylingConfigBase(editText = b.body, htmlMode = HtmlMode.SIMPLE)
b.ownerType = owner?.getNoteType() ?: Note.OwnerType.NONE
b.editingNote = editingNote
b.color.clear().append(Note.Color.values().map { color ->
TextInputDropDown.Item(
id = color.value ?: 0L,
text = color.stringRes.resolveString(activity),
tag = color,
icon = if (color.value != null)
IconicsDrawable(activity).apply {
icon = CommunityMaterial.Icon.cmd_circle
sizeDp = 24
colorInt = color.value.toInt()
} else null,
)
})
b.color.select(id = editingNote?.color ?: 0L)
textStylingManager.attachToField(
activity = activity,
textLayout = b.topicLayout,
textEdit = b.topic,
onShowListener = onShowListener,
onDismissListener = onDismissListener,
)
textStylingManager.attachToField(
activity = activity,
textLayout = b.bodyLayout,
textEdit = b.body,
onShowListener = onShowListener,
onDismissListener = onDismissListener,
)
}
private fun buildNote(profile: Profile): Note? {
val ownerType = owner?.getNoteType() ?: Note.OwnerType.NONE
val topic = b.topic.text?.toString()
val body = b.body.text?.toString()
val color = b.color.selected?.tag as? Note.Color
val share = b.shareSwitch.isChecked && ownerType.isShareable
val replace = b.replaceSwitch.isChecked && ownerType.canReplace
if (body.isNullOrBlank()) {
b.bodyLayout.error = app.getString(R.string.notes_editor_body_error)
b.body.requestFocus()
return null
}
val topicHtml = if (topic.isNotNullNorBlank())
textStylingManager.getHtmlText(topicStylingConfig)
else null
val bodyHtml = textStylingManager.getHtmlText(bodyStylingConfig)
return Note(
profileId = profile.id,
id = editingNote?.id ?: System.currentTimeMillis(),
ownerType = owner?.getNoteType(),
ownerId = owner?.getNoteOwnerId(),
replacesOriginal = replace,
topic = topicHtml,
body = bodyHtml,
color = color?.value,
sharedBy = if (share) "self" else null,
sharedByName = if (share) profile.studentNameLong else null,
)
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-10-23.
*/
package pl.szczodrzynski.edziennik.ui.notes
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.data.db.entity.Note
import pl.szczodrzynski.edziennik.ui.search.SearchableAdapter
class NoteListAdapter(
val activity: AppCompatActivity,
val onNoteClick: ((note: Note) -> Unit)? = null,
val onNoteEditClick: ((note: Note) -> Unit)? = null,
) : SearchableAdapter<Note>() {
companion object {
private const val TAG = "NoteListAdapter"
private const val ITEM_TYPE_NOTE = 0
private const val ITEM_TYPE_CATEGORY = 1
}
private val app = activity.applicationContext as App
override fun getItemViewType(item: Note) = when {
item.isCategoryItem -> ITEM_TYPE_CATEGORY
else -> ITEM_TYPE_NOTE
}
override fun onBindViewHolder(
holder: RecyclerView.ViewHolder,
position: Int,
item: Note,
) {
when (holder) {
is NoteViewHolder -> holder.onBind(activity, app, item, position, this)
is NoteCategoryViewHolder -> holder.onBind(activity, app, item, position, this)
}
}
override fun onCreateViewHolder(
inflater: LayoutInflater,
parent: ViewGroup,
viewType: Int,
): RecyclerView.ViewHolder = when (viewType) {
ITEM_TYPE_CATEGORY -> NoteCategoryViewHolder(inflater, parent)
else -> NoteViewHolder(inflater, parent)
}
}

View File

@ -0,0 +1,104 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-10-23.
*/
package pl.szczodrzynski.edziennik.ui.notes
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Noteable
import pl.szczodrzynski.edziennik.databinding.NoteListDialogBinding
import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
class NoteListDialog(
activity: AppCompatActivity,
private val owner: Noteable,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<NoteListDialogBinding>(activity, onShowListener, onDismissListener) {
override val TAG = "NoteListDialog"
override fun getTitleRes(): Int? = null
override fun inflate(layoutInflater: LayoutInflater) =
NoteListDialogBinding.inflate(layoutInflater)
override fun getPositiveButtonText() = R.string.close
override fun getNeutralButtonText() = R.string.add
private val manager
get() = app.noteManager
private lateinit var adapter: NoteListAdapter
override suspend fun onNeutralClick(): Boolean {
NoteEditorDialog(
activity = activity,
owner = owner,
editingNote = null,
onShowListener = onShowListener,
onDismissListener = onDismissListener,
).show()
return NO_DISMISS
}
override suspend fun onShow() {
manager.configureHeader(activity, owner, b.header)
adapter = NoteListAdapter(
activity = activity,
onNoteClick = {
NoteDetailsDialog(
activity = activity,
owner = owner,
note = it,
onShowListener = onShowListener,
onDismissListener = onDismissListener,
).show()
},
onNoteEditClick = {
NoteEditorDialog(
activity = activity,
owner = owner,
editingNote = it,
onShowListener = onShowListener,
onDismissListener = onDismissListener,
).show()
},
)
app.db.noteDao().getAllFor(
profileId = owner.getNoteOwnerProfileId(),
ownerType = owner.getNoteType(),
ownerId = owner.getNoteOwnerId()
).observe(activity) { notes ->
// show/hide relevant views
b.noteListLayout.isVisible = notes.isNotEmpty()
b.noData.isVisible = notes.isEmpty()
if (notes.isEmpty()) {
return@observe
}
// apply the new note list
adapter.setAllItems(notes)
// configure the adapter & recycler view
if (b.noteList.adapter == null) {
b.noteList.adapter = adapter
b.noteList.apply {
//setHasFixedSize(true)
isNestedScrollingEnabled = false
layoutManager = LinearLayoutManager(context)
addItemDecoration(SimpleDividerItemDecoration(context))
}
} else {
adapter.notifyDataSetChanged()
}
}
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-10-23.
*/
package pl.szczodrzynski.edziennik.ui.notes
import android.graphics.drawable.ColorDrawable
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.ColorUtils
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Note
import pl.szczodrzynski.edziennik.databinding.NoteListItemBinding
import pl.szczodrzynski.edziennik.ext.*
import pl.szczodrzynski.edziennik.ui.grades.viewholder.BindableViewHolder
import pl.szczodrzynski.edziennik.utils.models.Date
class NoteViewHolder(
inflater: LayoutInflater,
parent: ViewGroup,
val b: NoteListItemBinding = NoteListItemBinding.inflate(inflater, parent, false),
) : RecyclerView.ViewHolder(b.root), BindableViewHolder<Note, NoteListAdapter> {
companion object {
private const val TAG = "NoteViewHolder"
}
override fun onBind(
activity: AppCompatActivity,
app: App,
item: Note,
position: Int,
adapter: NoteListAdapter,
) {
val colorHighlight = R.attr.colorControlHighlight.resolveAttr(activity)
val addedDate = Date.fromMillis(item.addedDate).formattedString
b.topic.text = adapter.highlightSearchText(
item = item,
text = item.topicHtml ?: item.bodyHtml,
color = colorHighlight,
)
if (item.color != null) {
b.colorLayout.background =
ColorDrawable(ColorUtils.setAlphaComponent(item.color.toInt(), 0x50))
} else {
b.colorLayout.background = null
}
if (item.sharedBy != null && item.sharedByName != null) {
b.addedBy.text = listOf<CharSequence>(
"{cmd-share-variant}",
item.sharedByName,
"",
addedDate,
).concat(" ")
// workaround for the span data lost during setText above
val sharedBySpanned = adapter.highlightSearchText(
item = item,
text = item.sharedByName,
color = colorHighlight,
)
b.addedBy.text = b.addedBy.text.replaceSpanned(item.sharedByName, sharedBySpanned)
} else {
b.addedBy.setText(R.string.notes_added_by_you_format, addedDate)
}
b.editButton.isVisible = item.canEdit && adapter.onNoteEditClick != null
if (adapter.onNoteClick != null)
b.root.onClick {
adapter.onNoteClick.invoke(item)
}
if (adapter.onNoteEditClick != null)
b.editButton.onClick {
adapter.onNoteEditClick.invoke(item)
}
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-10-23.
*/
package pl.szczodrzynski.edziennik.ui.notes
import android.view.Gravity
import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import com.google.android.material.button.MaterialButton
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Noteable
import pl.szczodrzynski.edziennik.ext.dp
import pl.szczodrzynski.edziennik.ext.onClick
fun MaterialButton.setupNotesButton(
activity: AppCompatActivity,
owner: Noteable,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) {
if (!isVisible)
return
icon = IconicsDrawable(activity, CommunityMaterial.Icon3.cmd_playlist_edit)
setText(R.string.notes_button)
iconPadding = 8.dp
iconSize = 24.dp
updateLayoutParams<LinearLayout.LayoutParams> {
gravity = Gravity.CENTER_HORIZONTAL
}
updatePadding(left = 12.dp)
onClick {
NoteListDialog(
activity = activity,
owner = owner,
onShowListener = onShowListener,
onDismissListener = onDismissListener,
).show()
}
}

View File

@ -0,0 +1,156 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-10-27.
*/
package pl.szczodrzynski.edziennik.ui.notes
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Note
import pl.szczodrzynski.edziennik.data.db.entity.Noteable
import pl.szczodrzynski.edziennik.databinding.NotesFragmentBinding
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import kotlin.coroutines.CoroutineContext
class NotesFragment : Fragment(), CoroutineScope {
companion object {
private const val TAG = "NotesFragment"
}
private lateinit var app: App
private lateinit var activity: MainActivity
private lateinit var b: NotesFragmentBinding
private val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private val manager
get() = app.noteManager
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
activity = getActivity() as? MainActivity ?: return null
context ?: return null
app = activity.application as App
b = NotesFragmentBinding.inflate(inflater)
return b.root
}
private fun onNoteClick(note: Note) = launch {
val owner = withContext(Dispatchers.IO) {
manager.getOwner(note)
} as? Noteable
NoteDetailsDialog(
activity = activity,
owner = owner,
note = note,
).show()
}
private fun onNoteEditClick(note: Note) = launch {
val owner = withContext(Dispatchers.IO) {
manager.getOwner(note)
} as? Noteable
NoteEditorDialog(
activity = activity,
owner = owner,
editingNote = note,
profileId = App.profileId,
).show()
}
private fun onNoteAddClick(view: View?) {
NoteEditorDialog(
activity = activity,
owner = null,
editingNote = null,
profileId = App.profileId,
).show()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
if (!isAdded) return
activity.navView.apply {
bottomBar.apply {
fabEnable = true
fabExtendedText = getString(R.string.notes_action_add)
fabIcon = CommunityMaterial.Icon3.cmd_text_box_plus_outline
}
setFabOnClickListener(this@NotesFragment::onNoteAddClick)
}
activity.gainAttentionFAB()
val adapter = NoteListAdapter(
activity = activity,
onNoteClick = this::onNoteClick,
onNoteEditClick = this::onNoteEditClick,
)
app.db.noteDao().getAll(profileId = App.profileId).observe(activity) { allNotes ->
if (!isAdded) return@observe
// show/hide relevant views
b.progressBar.isVisible = false
b.list.isVisible = allNotes.isNotEmpty()
b.noData.isVisible = allNotes.isEmpty()
if (allNotes.isEmpty()) {
return@observe
}
val notes = allNotes.groupBy { it.ownerType }.flatMap { (ownerType, notes) ->
if (ownerType != null) {
// construct a dummy note, used as the category separator
val categoryItem = Note(
profileId = 0,
id = 0,
ownerType = ownerType,
ownerId = 0,
topic = null,
body = "",
color = null,
)
categoryItem.isCategoryItem = true
val mutableNotes = notes.toMutableList()
mutableNotes.add(0, categoryItem)
return@flatMap mutableNotes
}
return@flatMap notes
}
// apply the new note list
adapter.setAllItems(notes, addSearchField = true)
// configure the adapter & recycler view
if (b.list.adapter == null) {
b.list.adapter = adapter
b.list.apply {
//setHasFixedSize(true)
isNestedScrollingEnabled = false
layoutManager = LinearLayoutManager(context)
addItemDecoration(SimpleDividerItemDecoration(context))
}
}
// reapply the filter
adapter.getSearchField()?.applyTo(adapter)
}
}
}

View File

@ -59,14 +59,15 @@ class SearchFilter<T : Searchable<T>>(
} }
val newItems = allItems.mapNotNull { item -> val newItems = allItems.mapNotNull { item ->
if (item is SearchField) { // get all keyword sets from the entity
val searchKeywords = item.searchKeywords
// keep the SearchField and items having no keywords
if (item is SearchField || searchKeywords.isEmpty()) {
return@mapNotNull item return@mapNotNull item
} }
item.searchPriority = NO_MATCH item.searchPriority = NO_MATCH
item.searchHighlightText = null item.searchHighlightText = null
// get all keyword sets from the entity
val searchKeywords = item.searchKeywords
// a temporary variable for the loops below // a temporary variable for the loops below
var matchWeight: Int var matchWeight: Int

View File

@ -10,7 +10,6 @@ import eu.szkolny.font.SzkolnyFont
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.REGISTRATION_ENABLED
import pl.szczodrzynski.edziennik.ext.after import pl.szczodrzynski.edziennik.ext.after
import pl.szczodrzynski.edziennik.ui.dialogs.settings.* import pl.szczodrzynski.edziennik.ui.dialogs.settings.*
import pl.szczodrzynski.edziennik.ui.settings.SettingsCard import pl.szczodrzynski.edziennik.ui.settings.SettingsCard
@ -83,36 +82,39 @@ class SettingsRegisterCard(util: SettingsUtil) : SettingsCard(util) {
AttendanceConfigDialog(activity, reloadOnDismiss = false).show() AttendanceConfigDialog(activity, reloadOnDismiss = false).show()
}, },
util.createPropertyItem( if (app.profile.archived)
text = R.string.settings_register_allow_registration_text, null
subText = R.string.settings_register_allow_registration_subtext, else
icon = CommunityMaterial.Icon.cmd_account_circle_outline, util.createPropertyItem(
value = app.profile.registration == REGISTRATION_ENABLED, text = R.string.settings_register_allow_registration_text,
beforeChange = { item, value -> subText = R.string.settings_register_allow_registration_subtext,
if (app.profile.registration == REGISTRATION_ENABLED == value) icon = CommunityMaterial.Icon.cmd_account_circle_outline,
// allow the switch to change - needed for util.refresh() to change the visual state value = app.profile.canShare,
return@createPropertyItem true beforeChange = { item, value ->
val dialog = if (app.profile.canShare == value)
RegistrationConfigDialog(activity, app.profile, onChangeListener = { enabled -> // allow the switch to change - needed for util.refresh() to change the visual state
if (item.isChecked == enabled) return@createPropertyItem true
return@RegistrationConfigDialog val dialog =
item.isChecked = enabled RegistrationConfigDialog(activity, app.profile, onChangeListener = { enabled ->
if (value) { if (item.isChecked == enabled)
card.items.after(item, sharedEventsItem) return@RegistrationConfigDialog
} else { item.isChecked = enabled
card.items.remove(sharedEventsItem) if (value) {
} card.items.after(item, sharedEventsItem)
util.refresh() } else {
}) card.items.remove(sharedEventsItem)
if (value) }
dialog.showEnableDialog() util.refresh()
else })
dialog.showDisableDialog() if (value)
false dialog.showEnableDialog()
} else
) { _, _ -> }, dialog.showDisableDialog()
false
}
) { _, _ -> },
if (app.profile.registration == REGISTRATION_ENABLED) if (app.profile.canShare)
sharedEventsItem sharedEventsItem
else else
null null

View File

@ -26,8 +26,10 @@ import pl.szczodrzynski.edziennik.ui.dialogs.base.BindingDialog
import pl.szczodrzynski.edziennik.ui.event.EventDetailsDialog import pl.szczodrzynski.edziennik.ui.event.EventDetailsDialog
import pl.szczodrzynski.edziennik.ui.event.EventListAdapter import pl.szczodrzynski.edziennik.ui.event.EventListAdapter
import pl.szczodrzynski.edziennik.ui.event.EventManualDialog import pl.szczodrzynski.edziennik.ui.event.EventManualDialog
import pl.szczodrzynski.edziennik.ui.notes.setupNotesButton
import pl.szczodrzynski.edziennik.utils.BetterLink import pl.szczodrzynski.edziennik.utils.BetterLink
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
import pl.szczodrzynski.edziennik.utils.managers.NoteManager
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
@ -35,6 +37,7 @@ class LessonDetailsDialog(
activity: AppCompatActivity, activity: AppCompatActivity,
private val lesson: LessonFull, private val lesson: LessonFull,
private val attendance: AttendanceFull? = null, private val attendance: AttendanceFull? = null,
private val showNotes: Boolean = true,
onShowListener: ((tag: String) -> Unit)? = null, onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null, onDismissListener: ((tag: String) -> Unit)? = null,
) : BindingDialog<DialogLessonDetailsBinding>(activity, onShowListener, onDismissListener) { ) : BindingDialog<DialogLessonDetailsBinding>(activity, onShowListener, onDismissListener) {
@ -186,7 +189,7 @@ class LessonDetailsDialog(
showTime = true, showTime = true,
showSubject = true, showSubject = true,
markAsSeen = true, markAsSeen = true,
onItemClick = { onEventClick = {
EventDetailsDialog( EventDetailsDialog(
activity, activity,
it, it,
@ -210,6 +213,10 @@ class LessonDetailsDialog(
lessonDate, lessonDate,
lessonTime lessonTime
).observe(activity) { events -> ).observe(activity) { events ->
events.forEach {
it.filterNotes()
}
adapter.setAllItems(events) adapter.setAllItems(events)
if (b.eventsView.adapter == null) { if (b.eventsView.adapter == null) {
b.eventsView.adapter = adapter b.eventsView.adapter = adapter
@ -244,5 +251,15 @@ class LessonDetailsDialog(
onActionSelected = dialog::dismiss onActionSelected = dialog::dismiss
) )
} }
b.notesButton.isVisible = showNotes
b.notesButton.setupNotesButton(
activity = activity,
owner = lesson,
onShowListener = onShowListener,
onDismissListener = onDismissListener,
)
if (showNotes)
NoteManager.setLegendText(lesson, b.legend)
} }
} }

View File

@ -33,6 +33,7 @@ import pl.szczodrzynski.edziennik.ext.*
import pl.szczodrzynski.edziennik.ui.base.lazypager.LazyFragment import pl.szczodrzynski.edziennik.ui.base.lazypager.LazyFragment
import pl.szczodrzynski.edziennik.ui.timetable.TimetableFragment.Companion.DEFAULT_END_HOUR import pl.szczodrzynski.edziennik.ui.timetable.TimetableFragment.Companion.DEFAULT_END_HOUR
import pl.szczodrzynski.edziennik.ui.timetable.TimetableFragment.Companion.DEFAULT_START_HOUR import pl.szczodrzynski.edziennik.ui.timetable.TimetableFragment.Companion.DEFAULT_START_HOUR
import pl.szczodrzynski.edziennik.utils.managers.NoteManager
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 java.util.* import java.util.*
@ -103,6 +104,10 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
// observe lesson database // observe lesson database
app.db.timetableDao().getAllForDate(App.profileId, date).observe(this) { lessons -> app.db.timetableDao().getAllForDate(App.profileId, date).observe(this) { lessons ->
launch { launch {
lessons.forEach {
it.filterNotes()
}
val events = withContext(Dispatchers.Default) { val events = withContext(Dispatchers.Default) {
app.db.eventDao().getAllByDateNow(App.profileId, date) app.db.eventDao().getAllByDateNow(App.profileId, date)
} }
@ -281,7 +286,9 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
lb.lessonNumber = lesson.displayLessonNumber lb.lessonNumber = lesson.displayLessonNumber
lb.subjectName.text = lesson.displaySubjectName?.let { val lessonText =
lesson.getNoteSubstituteText(showNotes = true) ?: lesson.displaySubjectName
lb.subjectName.text = lessonText?.let {
if (lesson.type == Lesson.TYPE_CANCELLED || lesson.type == Lesson.TYPE_SHIFTED_SOURCE) if (lesson.type == Lesson.TYPE_CANCELLED || lesson.type == Lesson.TYPE_SHIFTED_SOURCE)
it.asStrikethroughSpannable().asColoredSpannable(colorSecondary) it.asStrikethroughSpannable().asColoredSpannable(colorSecondary)
else else
@ -290,6 +297,8 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope {
lb.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).concat(bullet) lb.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).concat(bullet)
lb.detailsSecond.text = listOfNotEmpty(teacherInfo, teamInfo).concat(bullet) lb.detailsSecond.text = listOfNotEmpty(teacherInfo, teamInfo).concat(bullet)
NoteManager.prependIcon(lesson, lb.subjectName)
lb.attendanceIcon.isVisible = attendance?.let { lb.attendanceIcon.isVisible = attendance?.let {
val icon = attendanceManager.getAttendanceIcon(it) ?: return@let false val icon = attendanceManager.getAttendanceIcon(it) ?: return@let false
val color = attendanceManager.getAttendanceColor(it) val color = attendanceManager.getAttendanceColor(it)

View File

@ -48,12 +48,16 @@ class EventManager(val app: App) : CoroutineScope {
title: TextView, title: TextView,
event: EventFull, event: EventFull,
showType: Boolean = true, showType: Boolean = true,
showNotes: Boolean = true,
doneIconColor: Int? = null doneIconColor: Int? = null
) { ) {
val topicSpan = event.topicHtml val topicSpan = event.getNoteSubstituteText(showNotes) ?: event.topicHtml
val hasReplacingNotes = event.hasReplacingNotes()
title.text = listOfNotNull( title.text = listOfNotNull(
if (event.addedManually) "{cmd-clipboard-edit-outline} " else null, if (event.addedManually) "{cmd-clipboard-edit-outline} " else null,
if (event.hasNotes() && hasReplacingNotes && showNotes) "{cmd-swap-horizontal} " else null,
if (event.hasNotes() && !hasReplacingNotes && showNotes) "{cmd-playlist-edit} " else null,
if (showType) "${event.typeName ?: "wydarzenie"} - " else null, if (showType) "${event.typeName ?: "wydarzenie"} - " else null,
topicSpan, topicSpan,
).concat() ).concat()
@ -70,10 +74,11 @@ class EventManager(val app: App) : CoroutineScope {
) )
} }
fun setLegendText(legend: IconicsTextView, event: EventFull) { fun setLegendText(legend: IconicsTextView, event: EventFull, showNotes: Boolean = true) {
legend.text = listOfNotNull( legend.text = listOfNotNull(
if (event.addedManually) R.string.legend_event_added_manually else null, if (event.addedManually) R.string.legend_event_added_manually else null,
if (event.isDone) R.string.legend_event_is_done else null if (event.isDone) R.string.legend_event_is_done else null,
if (showNotes) NoteManager.getLegendText(event) else null,
).map { legend.context.getString(it) }.join("\n") ).map { legend.context.getString(it) }.join("\n")
legend.isVisible = legend.text.isNotBlank() legend.isVisible = legend.text.isNotBlank()
} }

View File

@ -0,0 +1,307 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-10-17.
*/
package pl.szczodrzynski.edziennik.utils.managers
import android.annotation.SuppressLint
import android.text.SpannableStringBuilder
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.mikepenz.iconics.view.IconicsTextView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Note
import pl.szczodrzynski.edziennik.data.db.entity.Note.OwnerType
import pl.szczodrzynski.edziennik.data.db.entity.Noteable
import pl.szczodrzynski.edziennik.data.db.full.*
import pl.szczodrzynski.edziennik.databinding.NoteDialogHeaderBinding
import pl.szczodrzynski.edziennik.ext.resolveDrawable
import pl.szczodrzynski.edziennik.ui.agenda.DayDialog
import pl.szczodrzynski.edziennik.ui.agenda.lessonchanges.LessonChangesAdapter
import pl.szczodrzynski.edziennik.ui.announcements.AnnouncementsAdapter
import pl.szczodrzynski.edziennik.ui.attendance.AttendanceAdapter
import pl.szczodrzynski.edziennik.ui.attendance.AttendanceDetailsDialog
import pl.szczodrzynski.edziennik.ui.attendance.AttendanceFragment
import pl.szczodrzynski.edziennik.ui.behaviour.NoticesAdapter
import pl.szczodrzynski.edziennik.ui.event.EventDetailsDialog
import pl.szczodrzynski.edziennik.ui.event.EventListAdapter
import pl.szczodrzynski.edziennik.ui.grades.GradeDetailsDialog
import pl.szczodrzynski.edziennik.ui.grades.GradesAdapter
import pl.szczodrzynski.edziennik.ui.messages.list.MessagesAdapter
import pl.szczodrzynski.edziennik.ui.timetable.LessonDetailsDialog
import pl.szczodrzynski.edziennik.utils.models.Date
class NoteManager(private val app: App) {
companion object {
private const val TAG = "NoteManager"
@SuppressLint("SetTextI18n")
fun prependIcon(owner: Noteable, textView: IconicsTextView) {
if (owner.hasNotes())
textView.text = SpannableStringBuilder(
if (owner.hasReplacingNotes())
"{cmd-swap-horizontal} "
else
"{cmd-playlist-edit} "
).append(textView.text)
}
fun getLegendText(owner: Noteable): Int? = when {
owner.hasReplacingNotes() -> R.string.legend_notes_added_replaced
owner.hasNotes() -> R.string.legend_notes_added
else -> null
}
fun setLegendText(owner: Noteable, textView: IconicsTextView) {
textView.isVisible = owner.hasNotes()
textView.setText(getLegendText(owner) ?: return)
}
}
fun getOwner(note: Note): Any? {
if (note.ownerId == null)
return null
return when (note.ownerType) {
OwnerType.ANNOUNCEMENT ->
app.db.announcementDao().getByIdNow(note.profileId, note.ownerId)
OwnerType.ATTENDANCE ->
app.db.attendanceDao().getByIdNow(note.profileId, note.ownerId)
OwnerType.BEHAVIOR ->
app.db.noticeDao().getByIdNow(note.profileId, note.ownerId)
OwnerType.EVENT ->
app.db.eventDao().getByIdNow(note.profileId, note.ownerId)
OwnerType.EVENT_SUBJECT, OwnerType.LESSON_SUBJECT ->
app.db.subjectDao().getByIdNow(note.profileId, note.ownerId)
OwnerType.GRADE ->
app.db.gradeDao().getByIdNow(note.profileId, note.ownerId)
OwnerType.LESSON ->
app.db.timetableDao().getByIdNow(note.profileId, note.ownerId)
OwnerType.MESSAGE ->
app.db.messageDao().getByIdNow(note.profileId, note.ownerId)
else -> null
}
}
fun hasValidOwner(note: Note): Boolean {
if (note.ownerType == null || note.ownerType == OwnerType.DAY)
return true
return getOwner(note) != null
}
suspend fun saveNote(activity: AppCompatActivity, note: Note, wasShared: Boolean): Boolean {
val success = when {
!note.isShared && wasShared -> unshareNote(activity, note)
note.isShared -> shareNote(activity, note)
else -> true
}
if (!success)
return false
withContext(Dispatchers.IO) {
app.db.noteDao().add(note)
}
return true
}
suspend fun deleteNote(activity: AppCompatActivity, note: Note): Boolean {
val success = when {
note.isShared -> unshareNote(activity, note)
else -> true
}
if (!success)
return false
withContext(Dispatchers.IO) {
app.db.noteDao().delete(note)
}
return true
}
private suspend fun shareNote(activity: AppCompatActivity, note: Note): Boolean {
return app.api.runCatching(activity) {
shareNote(note)
} != null
}
private suspend fun unshareNote(activity: AppCompatActivity, note: Note): Boolean {
return app.api.runCatching(activity) {
unshareNote(note)
} != null
}
private fun getAdapterForItem(
activity: AppCompatActivity,
item: Noteable,
): RecyclerView.Adapter<*>? {
return when (item) {
is AnnouncementFull -> AnnouncementsAdapter(activity, mutableListOf(item), null)
is AttendanceFull -> AttendanceAdapter(
activity,
showNotes = false,
onAttendanceClick = {
showItemDetailsDialog(activity, it)
},
type = AttendanceFragment.VIEW_LIST
).also {
it.items = mutableListOf(item)
}
is NoticeFull -> {
NoticesAdapter(activity, listOf(item))
}
is Date -> {
TODO("Date adapter is not yet implemented.")
}
is EventFull -> EventListAdapter(
activity = activity,
simpleMode = true,
showDate = true,
showColor = false,
showTime = false,
markAsSeen = false,
showNotes = false,
onEventClick = {
showItemDetailsDialog(activity, it)
},
).also {
it.setAllItems(listOf(item))
}
is GradeFull -> GradesAdapter(activity, showNotes = false, onGradeClick = {
showItemDetailsDialog(activity, it)
}).also {
it.items = mutableListOf(item)
}
is LessonFull -> LessonChangesAdapter(activity, showNotes = false, onLessonClick = {
showItemDetailsDialog(activity, it)
}).also {
it.items = listOf(item)
}
is MessageFull -> MessagesAdapter(
activity = activity,
teachers = listOf(),
showNotes = false,
onMessageClick = null,
).also {
it.setAllItems(listOf(item))
}
else -> null
}
}
private fun showItemDetailsDialog(
activity: AppCompatActivity,
item: Noteable,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) {
when (item) {
is AnnouncementFull -> return
is AttendanceFull -> AttendanceDetailsDialog(
activity = activity,
attendance = item,
showNotes = false,
onShowListener = onShowListener,
onDismissListener = onDismissListener,
).show()
is NoticeFull -> return
is Date -> DayDialog(
activity = activity,
profileId = App.profileId,
date = item,
showNotes = false,
onShowListener = onShowListener,
onDismissListener = onDismissListener,
).show()
is EventFull -> EventDetailsDialog(
activity = activity,
event = item,
showNotes = false,
onShowListener = onShowListener,
onDismissListener = onDismissListener,
).show()
is GradeFull -> GradeDetailsDialog(
activity = activity,
grade = item,
showNotes = false,
onShowListener = onShowListener,
onDismissListener = onDismissListener,
).show()
is LessonFull -> LessonDetailsDialog(
activity = activity,
lesson = item,
showNotes = false,
onShowListener = onShowListener,
onDismissListener = onDismissListener,
).show()
is MessageFull -> return
}
}
fun getOwnerTypeText(owner: OwnerType) = when (owner) {
OwnerType.ANNOUNCEMENT -> R.string.notes_type_announcement
OwnerType.ATTENDANCE -> R.string.notes_type_attendance
OwnerType.BEHAVIOR -> R.string.notes_type_behavior
OwnerType.DAY -> R.string.notes_type_day
OwnerType.EVENT -> R.string.notes_type_event
OwnerType.EVENT_SUBJECT -> TODO()
OwnerType.GRADE -> R.string.notes_type_grade
OwnerType.LESSON -> R.string.notes_type_lesson
OwnerType.LESSON_SUBJECT -> TODO()
OwnerType.MESSAGE -> R.string.notes_type_message
OwnerType.NONE -> throw Exception("NONE is not a valid OwnerType.")
}
fun getOwnerTypeImage(owner: OwnerType) = when (owner) {
OwnerType.ANNOUNCEMENT -> R.drawable.ic_announcement
OwnerType.ATTENDANCE -> R.drawable.ic_attendance
OwnerType.BEHAVIOR -> R.drawable.ic_behavior
OwnerType.DAY -> R.drawable.ic_calendar_day
OwnerType.EVENT -> R.drawable.ic_calendar_event
OwnerType.EVENT_SUBJECT -> TODO()
OwnerType.GRADE -> R.drawable.ic_grade
OwnerType.LESSON -> R.drawable.ic_timetable
OwnerType.LESSON_SUBJECT -> TODO()
OwnerType.MESSAGE -> R.drawable.ic_message
OwnerType.NONE -> throw Exception("NONE is not a valid OwnerType.")
}
fun configureHeader(
activity: AppCompatActivity,
noteOwner: Noteable?,
b: NoteDialogHeaderBinding,
) {
if (noteOwner == null) {
b.title.isVisible = false
b.divider.isVisible = false
b.ownerItemList.isVisible = false
return
}
b.ownerItemList.apply {
adapter = getAdapterForItem(activity, noteOwner)
isNestedScrollingEnabled = false
//setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
}
b.title.setText(getOwnerTypeText(noteOwner.getNoteType()))
b.title.setCompoundDrawables(
getOwnerTypeImage(noteOwner.getNoteType()).resolveDrawable(activity),
null,
null,
null,
)
}
}

View File

@ -13,16 +13,22 @@ import android.text.style.UnderlineSpan
import android.widget.Button import android.widget.Button
import android.widget.TextView import android.widget.TextView
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.button.MaterialButtonToggleGroup import com.google.android.material.button.MaterialButtonToggleGroup
import com.google.android.material.textfield.TextInputLayout
import com.mikepenz.iconics.IconicsDrawable
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.utils.sizeDp
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.ext.attachToastHint import pl.szczodrzynski.edziennik.ext.attachToastHint
import pl.szczodrzynski.edziennik.ext.hasSet import pl.szczodrzynski.edziennik.ext.hasSet
import pl.szczodrzynski.edziennik.ext.replaceSpan import pl.szczodrzynski.edziennik.ext.replaceSpan
import pl.szczodrzynski.edziennik.ui.dialogs.StyledTextDialog
import pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit import pl.szczodrzynski.edziennik.utils.TextInputKeyboardEdit
import pl.szczodrzynski.edziennik.utils.html.BetterHtml import pl.szczodrzynski.edziennik.utils.html.BetterHtml
import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.HtmlMode.* import pl.szczodrzynski.edziennik.utils.managers.TextStylingManager.HtmlMode.*
@ -271,4 +277,28 @@ class TextStylingManager(private val app: App) {
it.button.isEnabled = enable it.button.isEnabled = enable
} }
} }
fun attachToField(
activity: AppCompatActivity,
textLayout: TextInputLayout,
textEdit: TextInputKeyboardEdit,
onShowListener: ((tag: String) -> Unit)? = null,
onDismissListener: ((tag: String) -> Unit)? = null,
) {
textLayout.endIconDrawable = IconicsDrawable(
activity,
CommunityMaterial.Icon3.cmd_open_in_new
).apply { sizeDp = 24 }
textLayout.setEndIconOnClickListener {
StyledTextDialog(
activity,
initialText = textEdit.text,
onSuccess = {
textEdit.text = it
},
onShowListener,
onDismissListener
).show()
}
}
} }

View File

@ -6,14 +6,18 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.text.DateFormat; import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.TimeZone; import java.util.TimeZone;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.db.entity.Note;
import pl.szczodrzynski.edziennik.data.db.entity.Noteable;
import pl.szczodrzynski.edziennik.ext.TextExtensionsKt; import pl.szczodrzynski.edziennik.ext.TextExtensionsKt;
public class Date implements Comparable<Date> { public class Date implements Comparable<Date>, Noteable {
public int year = 0; public int year = 0;
public int month = 0; public int month = 0;
public int day = 0; public int day = 0;
@ -374,4 +378,51 @@ public class Date implements Comparable<Date> {
result = 31 * result + day; result = 31 * result + day;
return result; return result;
} }
@NonNull
@Override
public Note.OwnerType getNoteType() {
return Note.OwnerType.DAY;
}
@Override
public int getNoteOwnerProfileId() {
return 0;
}
@Override
public long getNoteOwnerId() {
return 0;
}
@Nullable
@Override
public CharSequence getNoteSubstituteText(boolean showNotes) {
return null;
}
@NonNull
@Override
public List<Note> getNotes() {
return new ArrayList();
}
@Override
public void setNotes(@NonNull List<Note> notes) {
}
@Override
public void filterNotes() {
Noteable.DefaultImpls.filterNotes(this);
}
@Override
public boolean hasNotes() {
return Noteable.DefaultImpls.hasNotes(this);
}
@Override
public boolean hasReplacingNotes() {
return Noteable.DefaultImpls.hasReplacingNotes(this);
}
} }

View File

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M17.4,33H15v-4h4l0.4,1.5C19.7,31.8 18.7,33 17.4,33zM37,36c0,0 -11.8,-7 -18,-7V15c5.8,0 18,-7 18,-7V36z"
android:fillColor="#90CAF9"/>
<path
android:pathData="M9,17A5,5 0,1 0,9 27,5 5,0 1,0 9,17zM40,19h-3v6h3c1.7,0 3,-1.3 3,-3S41.7,19 40,19z"
android:fillColor="#283593"/>
<path
android:pathData="M18.6,41.2c-0.9,0.6 -2.5,1.2 -4.6,1.4c-0.6,0.1 -1.2,-0.3 -1.4,-1L8.2,27.9c0,0 8.8,-6.2 8.8,1.1c0,5.5 1.5,8.4 2.2,9.5c0.5,0.7 0.5,1.6 0,2.3C19,41 18.8,41.1 18.6,41.2z"
android:fillColor="#283593"/>
<path
android:pathData="M9,29h10V15H9c-1.1,0 -2,0.9 -2,2v10C7,28.1 7.9,29 9,29z"
android:fillColor="#3F51B5"/>
<path
android:pathData="M38,38L38,38c-1.1,0 -2,-0.9 -2,-2V8c0,-1.1 0.9,-2 2,-2h0c1.1,0 2,0.9 2,2v28C40,37.1 39.1,38 38,38z"
android:fillColor="#42A5F5"/>
</vector>

View File

@ -0,0 +1,33 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M40,45L8,45 8,3 30,3 40,13z"
android:fillColor="#90CAF9"/>
<path
android:pathData="M38.5,14L29,14 29,4.5z"
android:fillColor="#E1F5FE"/>
<path
android:pathData="M13,28H25V30H13zM13,20H25V22H13zM13,36H25V38H13z"
android:fillColor="#1976D2"/>
<path
android:pathData="M29,40h6v-6h-6V40z"
android:fillColor="#0D47A1"/>
<path
android:pathData="M31,36H33V38H31z"
android:fillColor="#BBDEFB"/>
<path
android:pathData="M32.91,30.59L31.5,32 28,28.5 29.41,27.09 31.5,29.18z"
android:fillColor="#0D47A1"/>
<path
android:pathData="M35.972,27.528L31.5,32 30.09,30.59 31.5,29.18 34.562,26.118z"
android:fillColor="#0D47A1"/>
<path
android:pathData="M28.9997,19.414L30.4139,17.9998L34.9996,22.5853L33.5854,23.9995z"
android:fillColor="#0D47A1"/>
<path
android:pathData="M28.9998,22.5861L33.5853,18.0004L34.9995,19.4146L30.414,24.0003z"
android:fillColor="#0D47A1"/>
</vector>

View File

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M44,24c0,11 -9,20 -20,20S4,35 4,24S13,4 24,4S44,13 44,24z"
android:fillColor="#FFCA28"/>
<path
android:pathData="M33,27c0,0.86 -0.27,4.11 -1.69,7.01H16.65c-1.42,-2.9 -1.7,-6.15 -1.65,-7.01H33z"
android:fillColor="#5D4037"/>
<path
android:pathData="M13.8,21.6l-1.6,-1.2C12.3,20.3 14,18 17,18s4.7,2.3 4.8,2.4l-1.6,1.2c0,0 -1.3,-1.6 -3.2,-1.6S13.8,21.6 13.8,21.6zM27.8,21.6l-1.6,-1.2C26.3,20.3 28,18 31,18s4.7,2.3 4.8,2.4l-1.6,1.2c0,0 -1.3,-1.6 -3.2,-1.6S27.8,21.6 27.8,21.6z"
android:fillColor="#B76C09"/>
<path
android:pathData="M31.31,34.01C30.04,36.65 27.81,39 24,39s-6.05,-2.35 -7.35,-4.99c0,0 1.975,-2.01 7.35,-2.01S31.31,34.01 31.31,34.01z"
android:fillColor="#C62828"/>
</vector>

View File

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M43,38c0,2.209 -1.791,4 -4,4H9c-2.209,0 -4,-1.791 -4,-4V10c0,-2.209 1.791,-4 4,-4h30c2.209,0 4,1.791 4,4V38z"
android:fillColor="#CFD8DC"/>
<path
android:pathData="M10,10H38V14H10z"
android:fillColor="#F44336"/>
<path
android:pathData="M16,22H38V26H16zM16,28H38V32H16zM16,34H38V38H16zM10,22H14V26H10zM10,28H14V32H10zM10,34H14V38H10zM16,16H38V20H16zM10,16H14V20H10z"
android:fillColor="#90A4AE"/>
</vector>

View File

@ -0,0 +1,27 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M5,39V15h38v24c0,2.2 -1.8,4 -4,4H9C6.8,43 5,41.2 5,39"
android:fillColor="#CFD8DC"/>
<path
android:pathData="M5,25h38v8H5V25z"
android:fillColor="#ECEFF1"/>
<path
android:pathData="M13,27h4v4h-4V27zM19,27h4v4h-4V27zM31,27h4v4h-4V27z"
android:fillColor="#B0BEC5"/>
<path
android:pathData="M13,21h4v4h-4V21zM19,21h4v4h-4V21zM25,21h4v4h-4V21zM31,21h4v4h-4V21zM13,33h4v4h-4V33zM19,33h4v4h-4V33zM25,33h4v4h-4V33zM31,33h4v4h-4V33z"
android:fillColor="#90A4AE"/>
<path
android:pathData="M25,27h4v4h-4V27zM43,11v6H5v-6c0,-2.2 1.8,-4 4,-4h30C41.2,7 43,8.8 43,11"
android:fillColor="#F44336"/>
<path
android:pathData="M36,11c0,1.7 -1.3,3 -3,3s-3,-1.3 -3,-3s1.3,-3 3,-3C34.7,8 36,9.3 36,11M18,11c0,1.7 -1.3,3 -3,3s-3,-1.3 -3,-3s1.3,-3 3,-3S18,9.3 18,11"
android:fillColor="#B71C1C"/>
<path
android:pathData="M33,4c-1.1,0 -2,0.9 -2,2v5c0,1.1 0.9,2 2,2s2,-0.9 2,-2V6C35,4.9 34.1,4 33,4M15,4c-1.1,0 -2,0.9 -2,2v5c0,1.1 0.9,2 2,2s2,-0.9 2,-2V6C17,4.9 16.1,4 15,4"
android:fillColor="#B0BEC5"/>
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M44,24c0,11.045 -8.955,20 -20,20S4,35.045 4,24S12.955,4 24,4S44,12.955 44,24z"
android:fillColor="#F44336"/>
<path
android:pathData="M24,11l3.898,7.898l8.703,1.301l-6.301,6.102l1.5,8.699L24,30.898L16.199,35l1.5,-8.699l-6.301,-6.102l8.703,-1.301L24,11z"
android:fillColor="#FFCA28"/>
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M40,10H8c-2.209,0 -4,1.791 -4,4v20c0,2.209 1.791,4 4,4h32c2.209,0 4,-1.791 4,-4V14C44,11.791 42.209,10 40,10z"
android:fillColor="#2196F3"/>
<path
android:pathData="M44,14.025c0,-0.465 -0.095,-0.904 -0.24,-1.32L24,27.025L4.241,12.705C4.095,13.121 4,13.561 4,14.025V15l20,14.495L44,15V14.025z"
android:fillColor="#0D47A1"/>
</vector>

View File

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M8,39.001v-30c0,-2.2 1.8,-4 4,-4h24c2.2,0 4,1.8 4,4v30c0,2.2 -1.8,4 -4,4H12C9.8,43.001 8,41.201 8,39.001z"
android:fillColor="#42a5f5"/>
<path
android:pathData="M14,8.001c0,-1.105 -0.895,-2 -2,-2s-2,0.895 -2,2s0.895,2 2,2S14,9.105 14,8.001zM22,8.001c0,-1.105 -0.895,-2 -2,-2s-2,0.895 -2,2s0.895,2 2,2S22,9.105 22,8.001zM30,8.001c0,-1.105 -0.895,-2 -2,-2s-2,0.895 -2,2s0.895,2 2,2S30,9.105 30,8.001zM38,8.001c0,-1.105 -0.895,-2 -2,-2s-2,0.895 -2,2s0.895,2 2,2S38,9.105 38,8.001z"
android:fillColor="#1e88e5"/>
<path
android:pathData="M11,8.001v-4c0,-0.6 0.4,-1 1,-1l0,0c0.6,0 1,0.4 1,1v4c0,0.6 -0.4,1 -1,1l0,0C11.4,9.001 11,8.601 11,8.001zM19,8.001v-4c0,-0.6 0.4,-1 1,-1l0,0c0.6,0 1,0.4 1,1v4c0,0.6 -0.4,1 -1,1l0,0C19.4,9.001 19,8.601 19,8.001zM27,8.001v-4c0,-0.6 0.4,-1 1,-1l0,0c0.6,0 1,0.4 1,1v4c0,0.6 -0.4,1 -1,1l0,0C27.4,9.001 27,8.601 27,8.001zM35,8.001v-4c0,-0.6 0.4,-1 1,-1l0,0c0.6,0 1,0.4 1,1v4c0,0.6 -0.4,1 -1,1l0,0C35.4,9.001 35,8.601 35,8.001z"
android:fillColor="#cfd8dc"/>
<path
android:pathData="M14,19.001h19.993v2H14V19.001zM14.007,15.001H34v2H14.007V15.001zM14,30.001h17v2H14V30.001zM27,34.001h6.993v2H27V34.001zM14,34.001h11v2H14V34.001zM21,23.001h-7v2h7V23.001z"
android:fillColor="#1565c0"/>
</vector>

View File

@ -1,30 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="128dp" android:width="128dp"
android:height="128dp" android:height="128dp"
android:viewportWidth="128" android:viewportWidth="48"
android:viewportHeight="128"> android:viewportHeight="48">
<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="M42,38c0,2.209 -1.791,4 -4,4H10c-2.209,0 -4,-1.791 -4,-4V10c0,-2.209 1.791,-4 4,-4h28c2.209,0 4,1.791 4,4V38z"
android:fillColor="#ffc662"/> android:fillColor="#CFD8DC"/>
<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="M11,18H16V23H11zM18,18H23V23H18zM25,18H30V23H25zM32,18H37V23H32zM32,25H37V30H32zM11,25H16V30H11zM11,32H16V37H11zM18,25H23V30H18zM25,25H30V30H25zM18,32H23V37H18zM25,32H30V37H25zM32,32H37V37H32z"
android:fillColor="#ff634f"/> android:fillColor="#90A4AE"/>
<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="M11,11H16V16H11zM18,11H23V16H18zM25,11H30V16H25zM32,11H37V16H32z"
android:fillColor="#888"/> android:fillColor="#F44336"/>
<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" android:pathData="M38,26c-0.338,0 -0.669,0.023 -1,0.05V30h-5v-2.382c-1.817,1.052 -3.329,2.565 -4.382,4.382H30v5h-3.95C26.023,37.331 26,37.662 26,38c0,1.405 0.254,2.747 0.697,4H38c2.209,0 4,-1.791 4,-4V26.697C40.747,26.254 39.405,26 38,26z"
android:fillColor="#888"/> android:fillColor="#ECEFF1"/>
<path <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" android:pathData="M37,30v-3.95c-1.812,0.15 -3.506,0.703 -5,1.568V30H37zM30,32h-2.382c-0.865,1.494 -1.418,3.188 -1.568,5H30V32z"
android:fillColor="#ffe79f"/> android:fillColor="#CFD8DC"/>
<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="M48,38c0,5.5 -4.5,10 -10,10s-10,-4.5 -10,-10s4.5,-10 10,-10S48,32.5 48,38"
android:fillColor="#fff"/> android:fillColor="#F44336"/>
<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="M45,38c0,3.9 -3.1,7 -7,7s-7,-3.1 -7,-7s3.1,-7 7,-7S45,34.1 45,38"
android:fillColor="#1f80e5"/> android:fillColor="#EEEEEE"/>
<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:fillColor="#FF000000"
android:fillColor="#919191"/> android:pathData="M42.4,41.1l-2.9,-2.9c0,-0.1 0,-0.1 0,-0.2c0,-0.4 -0.2,-0.8 -0.5,-1.1V33h-2v3.9c-0.3,0.3 -0.5,0.7 -0.5,1.1c0,0.8 0.7,1.5 1.5,1.5h0.1l2.9,2.9L42.4,41.1z"/>
</vector> </vector>

View File

@ -86,6 +86,14 @@
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
<com.mikepenz.iconics.view.IconicsTextView
android:id="@+id/legend"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
tools:text="[ - ] dodano notatki" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -230,6 +238,15 @@
tools:text="12345" /> tools:text="12345" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/notesButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:text="@string/notes_button" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
</layout> </layout>

View File

@ -52,7 +52,7 @@
tools:text="Nieobecność nieusprawiedliwiona" /> tools:text="Nieobecność nieusprawiedliwiona" />
<TextView <com.mikepenz.iconics.view.IconicsTextView
android:id="@+id/subjectName" android:id="@+id/subjectName"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-10-28.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:layout_margin="8dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/card_notes_header_title"
android:textAppearance="@style/NavView.TextView.Title" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/noData"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/notes_no_data"
android:textSize="16sp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:clipToPadding="false"
tools:itemCount="3"
tools:listitem="@layout/note_list_item"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/addNote"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/notes_action_add" />
</LinearLayout>

View File

@ -31,13 +31,24 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp" android:layout_marginHorizontal="8dp"
android:layout_marginBottom="8dp"
android:textAppearance="@style/NavView.TextView.Helper" android:textAppearance="@style/NavView.TextView.Helper"
android:textIsSelectable="true" android:textIsSelectable="true"
android:visibility="gone" android:visibility="gone"
tools:text="8:00 - 14:20 (7 lekcji, 6 godzin, 20 minut)" tools:text="8:00 - 14:20 (7 lekcji, 6 godzin, 20 minut)"
tools:visibility="visible" /> tools:visibility="visible" />
<com.mikepenz.iconics.view.IconicsTextView
android:id="@+id/legend"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
tools:text="[ - ] dodano notatki" />
<View
android:layout_width="match_parent"
android:layout_height="8dp" />
<FrameLayout <FrameLayout
android:id="@+id/lessonChangesFrame" android:id="@+id/lessonChangesFrame"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -74,7 +85,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp" android:layout_marginHorizontal="8dp"
android:orientation="vertical" android:orientation="vertical"
android:paddingVertical="16dp" android:paddingTop="16dp"
android:paddingBottom="8dp"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible"> tools:visibility="visible">
@ -106,5 +118,13 @@
android:clipToPadding="false" android:clipToPadding="false"
tools:listitem="@layout/event_list_item" tools:listitem="@layout/event_list_item"
tools:visibility="visible" /> tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/notesButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/notes_button" />
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View File

@ -156,7 +156,7 @@
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper" android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_event_details_topic"/> android:text="@string/dialog_event_details_topic"/>
@ -172,7 +172,7 @@
android:id="@+id/bodyTitle" android:id="@+id/bodyTitle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="8dp"
android:text="@string/dialog_event_details_body" android:text="@string/dialog_event_details_body"
android:textAppearance="@style/NavView.TextView.Helper" /> android:textAppearance="@style/NavView.TextView.Helper" />
@ -197,7 +197,7 @@
android:id="@+id/attachmentsTitle" android:id="@+id/attachmentsTitle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="8dp"
android:text="@string/dialog_event_details_attachments" android:text="@string/dialog_event_details_attachments"
android:textAppearance="@style/NavView.TextView.Helper" /> android:textAppearance="@style/NavView.TextView.Helper" />
@ -279,6 +279,14 @@
android:text="\uf436" android:text="\uf436"
android:textSize="20sp" /> android:textSize="20sp" />
</com.google.android.flexbox.FlexboxLayout> </com.google.android.flexbox.FlexboxLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/notesButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/notes_button" />
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
</layout> </layout>

View File

@ -113,6 +113,14 @@
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
<com.mikepenz.iconics.view.IconicsTextView
android:id="@+id/legend"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
tools:text="[ - ] dodano notatki" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -307,6 +315,15 @@
android:minHeight="0dp" android:minHeight="0dp"
android:text="@string/configure" /> android:text="@string/configure" />
</LinearLayout> </LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/notesButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:text="@string/notes_button" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
</layout> </layout>

View File

@ -155,6 +155,14 @@
</LinearLayout> </LinearLayout>
<com.mikepenz.iconics.view.IconicsTextView
android:id="@+id/legend"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
tools:text="[ - ] dodano notatki" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -356,8 +364,9 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp" android:layout_marginHorizontal="8dp"
android:paddingVertical="16dp"
android:orientation="vertical" android:orientation="vertical"
android:paddingTop="16dp"
android:paddingBottom="8dp"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible"> tools:visibility="visible">
@ -390,6 +399,13 @@
tools:visibility="visible" tools:visibility="visible"
tools:listitem="@layout/event_list_item" /> tools:listitem="@layout/event_list_item" />
<com.google.android.material.button.MaterialButton
android:id="@+id/notesButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/notes_button" />
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
</layout> </layout>

View File

@ -36,7 +36,7 @@
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal">
<TextView <com.mikepenz.iconics.view.IconicsTextView
android:id="@+id/gradeDescription" android:id="@+id/gradeDescription"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -198,7 +198,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginHorizontal="8dp" android:layout_marginHorizontal="8dp"
android:layout_marginVertical="8dp" android:layout_marginTop="8dp"
android:orientation="horizontal" android:orientation="horizontal"
android:visibility="visible" android:visibility="visible"
tools:visibility="visible"> tools:visibility="visible">
@ -260,6 +260,14 @@
android:visibility="gone" android:visibility="gone"
tools:drawableTop="@android:drawable/stat_sys_download" /> tools:drawableTop="@android:drawable/stat_sys_download" />
</LinearLayout> </LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/notesButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/notes_button" />
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
</LinearLayout> </LinearLayout>

View File

@ -34,7 +34,7 @@
tools:text="JP" tools:text="JP"
tools:visibility="visible" /> tools:visibility="visible" />
<TextView <com.mikepenz.iconics.view.IconicsTextView
android:id="@+id/messageSubject" android:id="@+id/messageSubject"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -0,0 +1,143 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) Kuba Szczodrzyński 2021-10-23.
-->
<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">
<data>
<import type="pl.szczodrzynski.edziennik.data.db.entity.Note" />
<variable
name="note"
type="Note" />
</data>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp">
<include
android:id="@+id/header"
layout="@layout/note_dialog_header" />
<include
layout="@layout/note_dialog_subtitle"
app:text="@{@string/notes_details_dialog_title}" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/dialog_event_details_added_by"
android:textAppearance="@style/NavView.TextView.Helper" />
<com.mikepenz.iconics.view.IconicsTextView
android:id="@+id/addedBy"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textIsSelectable="true"
tools:text="18 grudnia, 23:17 przez Janósz Kowalski" />
<LinearLayout
android:id="@+id/idsLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:orientation="horizontal"
android:visibility="gone">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/notes_details_id"
android:textAppearance="@style/NavView.TextView.Helper" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{String.valueOf(note.id)}"
android:textIsSelectable="true"
tools:text="1635073956543" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/notes_details_owner_id"
android:textAppearance="@style/NavView.TextView.Helper" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{String.valueOf(note.ownerId)}"
android:textIsSelectable="true"
tools:text="3875623" />
</LinearLayout>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:isVisible="@{note.topic != null}"
android:text="@string/dialog_event_details_topic"
android:textAppearance="@style/NavView.TextView.Helper" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:isVisible="@{note.topic != null}"
android:text="@{note.topicHtml}"
android:textAppearance="@style/NavView.TextView.Medium"
android:textIsSelectable="true"
tools:text="Rozdział II: Panowanie Piastów i Jagiellonów.Przeniesiony z 11 grudnia." />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/dialog_event_details_body"
android:textAppearance="@style/NavView.TextView.Helper" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{note.bodyHtml}"
android:textAppearance="@style/NavView.TextView.Medium"
android:textIsSelectable="true"
tools:text="Rozdział II: Panowanie Piastów i Jagiellonów.Przeniesiony z 11 grudnia." />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</layout>

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