Skip to content

Commit

Permalink
Show notes: only dismiss once note is sent and saved
Browse files Browse the repository at this point in the history
  • Loading branch information
UweTrottmann committed Sep 6, 2024
1 parent ba497ee commit 189db55
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ 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.
Expand Down Expand Up @@ -41,36 +44,54 @@ class EditNoteDialog() : AppCompatDialogFragment() {
val binding = DialogEditNoteBinding.inflate(layoutInflater)
.also { this.binding = it }

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

// Prevent editing until current note is loaded
binding.textFieldEditNote.isEnabled = false
// 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 {
model.note.collect {
editText.setText(it)
// Enable editing once note is loaded
binding.textFieldEditNote.isEnabled = true
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)
.setPositiveButton(android.R.string.ok) { _, _ ->
val note = editText.text.toString()
model.saveToDatabase(note)
}
.setNegativeButton(android.R.string.cancel, null /* just dismiss */)
.create()
}

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

override fun onPause() {
super.onPause()
// The EditText would keep the current text on config changes if an ID is set,
// but keep it with the model instead.
model.note.tryEmit(binding!!.textFieldEditNote.editText!!.text.toString())
// Note: can not update using TextWatcher due to infinite loop
model.updateNote(binding?.textFieldEditNote?.editText?.text?.toString())
}

override fun onDestroyView() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,64 @@ import com.battlelancer.seriesguide.SgApp
import com.battlelancer.seriesguide.provider.SgRoomDatabase
import com.battlelancer.seriesguide.shows.database.SgShow2
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch

class EditNoteDialogViewModel(application: Application, private val showId: Long) :
AndroidViewModel(application) {

// Only load the current note text once on init
val note = MutableSharedFlow<String?>(replay = 1)
data class EditNoteDialogUiState(
val noteText: String? = null,
val isEditingEnabled: Boolean = false,
val isNoteSaved: Boolean = false
)

val uiState = MutableStateFlow(EditNoteDialogUiState())

init {
viewModelScope.launch(Dispatchers.IO) {
val show = SgRoomDatabase.getInstance(application)
.sgShow2Helper()
.getShow(showId)
if (show != null) {
note.emit(show.userNote)
uiState.update {
it.copy(
noteText = show.userNote,
isEditingEnabled = true
)
}
}
}
}

fun updateNote(text: String?) {
uiState.update {
it.copy(noteText = text)
}
}

/**
* Saves the note, but only up to the number of allowed characters.
*
* Updates UI state depending on success.
*/
fun saveToDatabase(note: String?) {
SgApp.getServicesComponent(getApplication()).showTools()
.storeUserNote(showId, note?.take(SgShow2.MAX_USER_NOTE_LENGTH))
fun saveNote() {
uiState.update {
it.copy(isEditingEnabled = false)
}
val savedText = uiState.value.noteText?.take(SgShow2.MAX_USER_NOTE_LENGTH)
viewModelScope.launch {
val success = SgApp.getServicesComponent(getApplication()).showTools()
.storeUserNote(showId, savedText)
uiState.update {
it.copy(
noteText = savedText,
isEditingEnabled = true,
isNoteSaved = success
)
}
}
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ class ShowTools2 @Inject constructor(
* Uploads to Cloud and on success saves to local database.
* Does not sanitize the given values.
*/
fun storeUserNote(showId: Long, userNote: String?) = SgApp.coroutineScope.launch {
suspend fun storeUserNote(showId: Long, userNote: String?): Boolean {
// Send to Cloud.
val isCloudFailed = withContext(Dispatchers.Default) {
if (!HexagonSettings.isEnabled(context)) {
Expand All @@ -478,16 +478,13 @@ class ShowTools2 @Inject constructor(
return@withContext !success
}
// Do not save to local database if sending to cloud has failed.
if (isCloudFailed) return@launch
if (isCloudFailed) return false

// Save to local database
withContext(Dispatchers.IO) {
// Change custom release time values
SgRoomDatabase.getInstance(context).sgShow2Helper().updateUserNote(
showId,
userNote
)
SgRoomDatabase.getInstance(context).sgShow2Helper().updateUserNote(showId, userNote)
}
return true
}

private suspend fun notifyAboutSyncing() {
Expand Down
33 changes: 31 additions & 2 deletions app/src/main/res/layout/dialog_edit_note.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,41 @@
android:layout_height="wrap_content"
android:gravity="top"
android:inputType="textMultiLine"
android:maxLines="6"
android:minLines="3"
android:lines="6"
tools:text="This is an example note.\nIt contains a line break. " />

</com.google.android.material.textfield.TextInputLayout>

<androidx.constraintlayout.helper.widget.Flow
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingStart="@dimen/default_padding"
android:paddingTop="@dimen/inline_padding"
android:paddingEnd="@dimen/default_padding"
android:paddingBottom="@dimen/inline_padding"
app:constraint_referenced_ids="buttonNegative,buttonPositive"
app:flow_horizontalBias="1"
app:flow_horizontalStyle="packed"
app:flow_wrapMode="chain"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textFieldEditNote" />

<Button
android:id="@+id/buttonNegative"
style="?attr/buttonBarNegativeButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Button Negative With Long Text" />

<Button
android:id="@+id/buttonPositive"
style="?attr/buttonBarPositiveButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Button Positive" />

</androidx.constraintlayout.widget.ConstraintLayout>

</ScrollView>
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
<string name="action_stream_info">Stream or purchase info</string>
<string name="action_select_region">Select region</string>
<string name="action_reset">Reset</string>
<string name="action_save">Save</string>

<!-- Search -->
<string name="clear_search_history">Clear search history</string>
Expand Down

0 comments on commit 189db55

Please sign in to comment.