Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show notes #1050

Draft
wants to merge 26 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5be9396
Show notes: add new column to SgShow2
UweTrottmann Aug 22, 2024
9fe8601
Show notes: restore when adding, sync
UweTrottmann Aug 22, 2024
2e13f97
Show notes: prepare storing note
UweTrottmann Aug 22, 2024
9e7a383
Show notes: add note button and display to ShowFragment
UweTrottmann Aug 22, 2024
6ba2e72
Show notes: add EditNoteDialog
UweTrottmann Aug 22, 2024
7be047c
Show notes: make note a card
UweTrottmann Aug 22, 2024
22191b8
Update trakt-java [6.14.0 -> 6.15.0]
UweTrottmann Sep 5, 2024
5a7bed6
FIXME Update trakt-java [6.15.0 -> 6.15.1-SNAPSHOT]
UweTrottmann Sep 5, 2024
714b4a1
Rename .java to .kt
UweTrottmann Sep 5, 2024
8723c62
Kotlin: convert TraktSettings
UweTrottmann Sep 5, 2024
1621ebd
TraktSettings: restore copyright, use upper case for Trakt
UweTrottmann Sep 5, 2024
97bcb16
TraktSettings: remove some unused methods, add notes
UweTrottmann Sep 5, 2024
465dd57
TraktSettings: change to isInitialSync, inline reset method
UweTrottmann Sep 5, 2024
958afc8
TraktSync: extract connection check
UweTrottmann Sep 5, 2024
aaf8ab6
TraktSync: remove superfluous credentials checks
UweTrottmann Sep 5, 2024
e831256
Show notes: sync with Trakt
UweTrottmann Sep 5, 2024
0a018d6
TraktV2: use getPageCount helper method
UweTrottmann Sep 6, 2024
34e7036
Show notes: only dismiss once note is sent and saved
UweTrottmann Sep 6, 2024
d1bd4f6
TraktTools2: remove unused SearchResult class
UweTrottmann Sep 12, 2024
72a5816
Copyright: restore for TraktTools2
UweTrottmann Sep 12, 2024
d73ab6d
Show notes: add note Trakt ID column
UweTrottmann Sep 12, 2024
4341f70
HexagonShowSync: add docs on upload() return value
UweTrottmann Nov 1, 2024
a58455a
Show notes: send to Trakt
UweTrottmann Sep 11, 2024
936b07a
Show notes: use empty instead of null string to store no note
UweTrottmann Nov 7, 2024
e22e82f
Show notes: do not trust database, fold blank text to empty string
UweTrottmann Nov 7, 2024
996276c
Show notes: fail to save if text is too long instead of truncating
UweTrottmann Nov 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,653 changes: 1,653 additions & 0 deletions app/schemas/com.battlelancer.seriesguide.provider.SgRoomDatabase/54.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ public void showDefaultValuesImport() {
assertThat(show.getCustomReleaseTime()).isNull();
assertThat(show.getCustomReleaseDayOffset()).isNull();
assertThat(show.getCustomReleaseTimeZone()).isNull();
assertThat(show.getUserNote()).isNull();
assertThat(show.getUserNoteTraktId()).isNull();
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ object ShowTestHelper {
posterSmall = "poster.jpg",
// set desired language, might not be the content language if fallback used above.
language = LanguageTools.LANGUAGE_EN,
lastUpdatedMs = System.currentTimeMillis() // now
lastUpdatedMs = System.currentTimeMillis(), // now
userNote = null,
userNoteTraktId = null
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ object ImportTools {
notify = notify ?: true,
hidden = hidden,
lastWatchedMs = last_watched_ms,
userNote = user_note?.take(SgShow2.MAX_USER_NOTE_LENGTH),
userNoteTraktId = user_note_trakt_id
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,9 @@ class JsonExportTask(
show.trakt_id = sgShow.traktId
show.first_aired = sgShow.firstRelease
show.rating_user = sgShow.ratingUser
// Do not export blank text, if null export empty string to allow easy editing of JSON
show.user_note = sgShow.userNote?.ifBlank { "" } ?: ""
show.user_note_trakt_id = sgShow.userNoteTraktId
if (isFullDump) {
show.overview = sgShow.overview
show.rating_tmdb = sgShow.ratingTmdb
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,8 @@ public class Show {

public long last_watched_ms;

@Nullable public String user_note;
@Nullable public Long user_note_trakt_id;

public List<Season> seasons;
}
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,7 @@ class TraktEpisodeJob(
}
WatchedEpisode(number, season)
}
val pageCount = response.headers()["x-pagination-page-count"]?.toIntOrNull()
?: 1
val pageCount = TraktV2.getPageCount(response) ?: 1
Ok(HistoryPage(episodes, pageCount))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,16 @@ public interface SgShow2Columns extends BaseColumns {
*/
String UNWATCHED_COUNT = "series_unwatched_count";

/**
* See {@link SgShow2#getUserNote()}.
*/
String USER_NOTE = "series_user_note";

/**
* See {@link SgShow2#getUserNoteTraktId()}.
*/
String USER_NOTE_TRAKT_ID = "series_user_note_trakt_id";

String SELECTION_FAVORITES = FAVORITE + "=1";
String SELECTION_NOT_FAVORITES = FAVORITE + "=0";
String SELECTION_HIDDEN = HIDDEN + "=1";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ import timber.log.Timber
AutoMigration(
from = SgRoomDatabase.VERSION_52_WATCH_PROVIDER_FILTERS,
to = SgRoomDatabase.VERSION_53_SHOW_TMDB_RATINGS
),
AutoMigration(
from = SgRoomDatabase.VERSION_53_SHOW_TMDB_RATINGS,
to = SgRoomDatabase.VERSION_54_SHOW_NOTES
)
]
)
Expand Down Expand Up @@ -139,7 +143,13 @@ abstract class SgRoomDatabase : RoomDatabase() {
* - Add [SgEpisode2.ratingTmdb] and [SgEpisode2.ratingTmdbVotes].
*/
const val VERSION_53_SHOW_TMDB_RATINGS = 53
const val VERSION = VERSION_53_SHOW_TMDB_RATINGS

/**
* - Add [SgShow2.userNote] and [SgShow2.userNoteTraktId].
*/
const val VERSION_54_SHOW_NOTES = 54

const val VERSION = VERSION_54_SHOW_NOTES

@Volatile
private var instance: SgRoomDatabase? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,11 @@ import com.battlelancer.seriesguide.provider.SeriesGuideContract.SgShow2Columns.
import com.battlelancer.seriesguide.provider.SeriesGuideContract.SgShow2Columns.TRAKT_ID
import com.battlelancer.seriesguide.provider.SeriesGuideContract.SgShow2Columns.TVDB_ID
import com.battlelancer.seriesguide.provider.SeriesGuideContract.SgShow2Columns.UNWATCHED_COUNT
import com.battlelancer.seriesguide.provider.SeriesGuideContract.SgShow2Columns.USER_NOTE
import com.battlelancer.seriesguide.provider.SeriesGuideContract.SgShow2Columns.USER_NOTE_TRAKT_ID
import com.battlelancer.seriesguide.provider.SeriesGuideContract.SgShow2Columns._ID
import com.battlelancer.seriesguide.provider.SgRoomDatabase
import com.battlelancer.seriesguide.shows.database.SgShow2.Companion.MAX_USER_NOTE_LENGTH
import com.battlelancer.seriesguide.shows.tools.NextEpisodeUpdater
import com.battlelancer.seriesguide.shows.tools.ShowStatus
import com.battlelancer.seriesguide.util.TimeTools
Expand Down Expand Up @@ -163,6 +166,22 @@ data class SgShow2(
@ColumnInfo(name = CUSTOM_RELEASE_TIME) var customReleaseTime: Int?,
@ColumnInfo(name = CUSTOM_RELEASE_DAY_OFFSET) var customReleaseDayOffset: Int?,
@ColumnInfo(name = CUSTOM_RELEASE_TIME_ZONE) var customReleaseTimeZone: String?,
/**
* A user editable text note for this show.
*
* Default `null`. Should be at most [MAX_USER_NOTE_LENGTH].
*
* Added with [SgRoomDatabase.VERSION_54_SHOW_NOTES].
*/
@ColumnInfo(name = USER_NOTE) var userNote: String?,
/**
* Trakt ID for [userNote].
*
* Default `null`.
*
* Added with [SgRoomDatabase.VERSION_54_SHOW_NOTES].
*/
@ColumnInfo(name = USER_NOTE_TRAKT_ID) val userNoteTraktId: Long?
) {
val releaseTimeOrDefault: Int
get() = releaseTime ?: -1
Expand All @@ -179,6 +198,12 @@ data class SgShow2(
val statusOrUnknown: Int
get() = status ?: ShowStatus.UNKNOWN

/**
* Maps blank or null to empty string.
*/
val userNoteOrEmpty: String
get() = userNote?.ifBlank { "" } ?: ""

companion object {
/**
* Used if the number of remaining episodes to watch for a show is not (yet) known.
Expand All @@ -197,5 +222,11 @@ data class SgShow2(
* Maximum absolute (so positive or negative) value of the [customReleaseDayOffset].
*/
const val MAX_CUSTOM_DAY_OFFSET = 28

/**
* Max note length to support Trakt and Hexagon
* (Trakt supports up to 500 characters, Hexagon up to 2000).
*/
const val MAX_USER_NOTE_LENGTH = 500
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,24 @@ interface SgShow2Helper {
@Query("UPDATE sg_show SET series_tmdb_id = :tmdbId WHERE _id = :id")
fun updateTmdbId(id: Long, tmdbId: Int): Int

@Query("SELECT _id FROM sg_show WHERE series_user_note IS NOT NULL AND series_user_note != ''")
fun getShowIdsWithNotes(): MutableList<Long>

@Query("SELECT _id, series_tmdb_id, series_user_note FROM sg_show WHERE _id = :id")
fun getShowWithNote(id: Long): SgShow2WithNote?

@Query("UPDATE sg_show SET series_user_note = :note, series_user_note_trakt_id = :traktId WHERE _id = :id")
fun updateUserNote(id: Long, note: String, traktId: Long?)

data class NoteUpdate(val text: String, val traktId: Long?)

@Transaction
fun updateUserNotes(notesById: Map<Long, NoteUpdate>) {
for (entry in notesById) {
updateUserNote(entry.key, entry.value.text, entry.value.traktId)
}
}

@Query("DELETE FROM sg_show")
fun deleteAllShows()

Expand All @@ -163,10 +181,10 @@ interface SgShow2Helper {
@Query("UPDATE sg_show SET series_syncenabled = 1 WHERE _id = :id")
fun setHexagonMergeCompleted(id: Long)

@Query("SELECT _id, series_tmdb_id, series_language, series_favorite, series_hidden, series_notify, series_custom_release_time, series_custom_day_offset, series_custom_timezone, series_lastupdate FROM sg_show WHERE _id = :id")
@Query("SELECT _id, series_tmdb_id, series_language, series_favorite, series_hidden, series_notify, series_custom_release_time, series_custom_day_offset, series_custom_timezone, series_lastupdate, series_user_note FROM sg_show WHERE _id = :id")
fun getForCloudUpdate(id: Long): SgShow2CloudUpdate?

@Query("SELECT _id, series_tmdb_id, series_language, series_favorite, series_hidden, series_notify, series_custom_release_time, series_custom_day_offset, series_custom_timezone, series_lastupdate FROM sg_show")
@Query("SELECT _id, series_tmdb_id, series_language, series_favorite, series_hidden, series_notify, series_custom_release_time, series_custom_day_offset, series_custom_timezone, series_lastupdate, series_user_note FROM sg_show")
fun getForCloudUpdate(): List<SgShow2CloudUpdate>

@Update(entity = SgShow2::class)
Expand Down Expand Up @@ -365,11 +383,18 @@ data class SgShow2CloudUpdate(
@ColumnInfo(name = SgShow2Columns.CUSTOM_RELEASE_TIME) var customReleaseTime: Int?,
@ColumnInfo(name = SgShow2Columns.CUSTOM_RELEASE_DAY_OFFSET) var customReleaseDayOffset: Int?,
@ColumnInfo(name = SgShow2Columns.CUSTOM_RELEASE_TIME_ZONE) var customReleaseTimeZone: String?,
@ColumnInfo(name = SgShow2Columns.LASTUPDATED) var lastUpdatedMs: Long
@ColumnInfo(name = SgShow2Columns.LASTUPDATED) var lastUpdatedMs: Long,
@ColumnInfo(name = SgShow2Columns.USER_NOTE) var userNote: String?,
)

data class ShowLastWatchedInfo(
val lastWatchedMs: Long,
val episodeSeason: Int,
val episodeNumber: Int
)

data class SgShow2WithNote(
@ColumnInfo(name = SgShow2Columns._ID) val id: Long,
@ColumnInfo(name = SgShow2Columns.TMDB_ID) val tmdbId: Int?,
@ColumnInfo(name = SgShow2Columns.USER_NOTE) var userNote: String?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Uwe Trottmann

package com.battlelancer.seriesguide.shows.overview

import android.app.Dialog
import android.os.Bundle
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.core.os.bundleOf
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.battlelancer.seriesguide.R
import com.battlelancer.seriesguide.databinding.DialogEditNoteBinding
import com.battlelancer.seriesguide.shows.database.SgShow2
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch
import timber.log.Timber

/**
* Edits a note of a show, enforcing a maximum length, allows to save or discard changes.
*/
class EditNoteDialog() : AppCompatDialogFragment() {

constructor(showId: Long) : this() {
arguments = bundleOf(
ARG_SHOW_ID to showId
)
}

private var binding: DialogEditNoteBinding? = null
private val model: EditNoteDialogViewModel by viewModels(
extrasProducer = {
EditNoteDialogViewModel.creationExtras(
defaultViewModelCreationExtras,
requireArguments().getLong(ARG_SHOW_ID)
)
},
factoryProducer = { EditNoteDialogViewModel.Factory }
)

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val binding = DialogEditNoteBinding.inflate(layoutInflater)
.also { this.binding = it }

// Text field
binding.textFieldEditNote.counterMaxLength = SgShow2.MAX_USER_NOTE_LENGTH
val editText = binding.textFieldEditNote.editText!!

// Buttons
binding.buttonPositive.apply {
setText(R.string.action_save)
setOnClickListener {
model.updateNote(editText.text?.toString())
model.saveNote()
}
}
binding.buttonNegative.apply {
setText(android.R.string.cancel)
setOnClickListener { dismiss() }
}

// UI state
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
model.uiState.collect { state ->
Timber.d("Display note")
editText.setText(state.noteText)
setViewsEnabled(state.isEditingEnabled)
if (state.isNoteSaved) {
dismiss()
}
}
}
}

return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.title_note)
.setView(binding.root)
.create()
}

private fun setViewsEnabled(enabled: Boolean) {
binding?.apply {
textFieldEditNote.isEnabled = enabled
buttonPositive.isEnabled = enabled
}
}

override fun onPause() {
super.onPause()
// Note: can not update using TextWatcher due to infinite loop
model.updateNote(binding?.textFieldEditNote?.editText?.text?.toString())
}

override fun onDestroyView() {
super.onDestroyView()
binding = null
}

companion object {
private const val ARG_SHOW_ID = "showId"
}

}
Loading
Loading