Skip to content

Commit

Permalink
Merge pull request #68 from leandroBorgesFerreira/SavingLineEditionCo…
Browse files Browse the repository at this point in the history
…rrectly

Saving line edition correctly
  • Loading branch information
leandroBorgesFerreira authored Jul 14, 2023
2 parents 0ee2418 + ffd0a45 commit 1b6ffa3
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.github.leandroborgesferreira.storyteller.manager

import com.github.leandroborgesferreira.storyteller.model.change.DeleteInfo
import com.github.leandroborgesferreira.storyteller.model.change.LineBreakInfo
import com.github.leandroborgesferreira.storyteller.model.story.LastEdit
import com.github.leandroborgesferreira.storyteller.model.story.StoryState
import com.github.leandroborgesferreira.storyteller.model.story.StoryStep
import com.github.leandroborgesferreira.storyteller.model.story.StoryType
Expand Down Expand Up @@ -35,7 +36,7 @@ class ContentHandler(
)
newMap[position] = newCheck

return StoryState(newMap, newCheck.id)
return StoryState(newMap, LastEdit.Whole, newCheck.id)
}

//Todo: Add unit test
Expand Down Expand Up @@ -93,6 +94,7 @@ class ContentHandler(

(addPosition to secondMessage) to StoryState(
stories = newStory,
lastEdit = LastEdit.Whole,
focusId = secondMessage.id
)
}
Expand All @@ -113,7 +115,7 @@ class ContentHandler(
)

val normalized = stepsNormalizer(mutableSteps.toEditState())
StoryState(normalized, focusId = previousFocus?.id)
StoryState(normalized, lastEdit = LastEdit.Whole, focusId = previousFocus?.id)
} else {
mutableSteps[deleteInfo.position]?.let { group ->
val newSteps = group.steps.filter { storyUnit ->
Expand All @@ -127,7 +129,7 @@ class ContentHandler(
}

mutableSteps[deleteInfo.position] = newStoryUnit.copy(parentId = null)
StoryState(stepsNormalizer(mutableSteps.toEditState()))
StoryState(stepsNormalizer(mutableSteps.toEditState()), lastEdit = LastEdit.Whole)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import com.github.leandroborgesferreira.storyteller.model.story.StoryStep
* The implementations of this interface shouldn't control order (sorting) or oder configurations,
* those need to be passed as parameters.
*/
interface DocumentRepository : SaveNote {
interface DocumentRepository : DocumentUpdate {

suspend fun loadDocuments(orderBy: String): List<Document>

Expand All @@ -18,6 +18,8 @@ interface DocumentRepository : SaveNote {

override suspend fun saveDocument(document: Document)

override suspend fun saveStoryStep(storyStep: StoryStep, position: Int, documentId: String)

suspend fun deleteDocument(document: Document)

suspend fun deleteDocumentById(ids: Set<String>)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.github.leandroborgesferreira.storyteller.manager

import com.github.leandroborgesferreira.storyteller.model.document.Document
import com.github.leandroborgesferreira.storyteller.model.story.StoryStep

interface SaveNote {
interface DocumentUpdate {
suspend fun saveDocument(document: Document)

suspend fun saveStoryStep(storyStep: StoryStep, position: Int, documentId: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.github.leandroborgesferreira.storyteller.model.change.TextEditInfo
import com.github.leandroborgesferreira.storyteller.model.document.Document
import com.github.leandroborgesferreira.storyteller.model.story.DrawState
import com.github.leandroborgesferreira.storyteller.model.story.DrawStory
import com.github.leandroborgesferreira.storyteller.model.story.LastEdit
import com.github.leandroborgesferreira.storyteller.model.story.StoryState
import com.github.leandroborgesferreira.storyteller.model.story.StoryStep
import com.github.leandroborgesferreira.storyteller.model.story.StoryType
Expand All @@ -24,15 +25,13 @@ import com.github.leandroborgesferreira.storyteller.utils.extensions.toEditState
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import java.util.Date
import java.util.UUID
Expand Down Expand Up @@ -63,7 +62,7 @@ class StoryTellerManager(
val scrollToPosition: StateFlow<Int?> = _scrollToPosition.asStateFlow()

private val _currentStory: MutableStateFlow<StoryState> = MutableStateFlow(
StoryState(stories = emptyMap())
StoryState(stories = emptyMap(), lastEdit = LastEdit.Nothing)
)

private val _documentInfo: MutableStateFlow<DocumentInfo> = MutableStateFlow(DocumentInfo())
Expand All @@ -73,45 +72,73 @@ class StoryTellerManager(

val currentStory: StateFlow<StoryState> = _currentStory.asStateFlow()

@OptIn(ExperimentalCoroutinesApi::class)
val currentDocument: Flow<Document> = _documentInfo.flatMapLatest { info ->
_currentStory.map { state ->
val titleFromContent = state.stories.values.firstOrNull { storyStep ->
//Todo: Change the type of change to allow different types. The client code should decide what is a title
//It is also interesting to inv
storyStep.type == StoryType.TITLE.type
}?.text

Document(
id = info.id,
title = titleFromContent ?: info.title,
content = state.stories,
createdAt = info.createdAt,
lastUpdatedAt = info.lastUpdatedAt,
)
}
val currentDocument: Flow<Document> = combine(_documentInfo, _currentStory) { info, state ->
val titleFromContent = state.stories.values.firstOrNull { storyStep ->
//Todo: Change the type of change to allow different types. The client code should decide what is a title
//It is also interesting to inv
storyStep.type == StoryType.TITLE.type
}?.text

Document(
id = info.id,
title = titleFromContent ?: info.title,
content = state.stories,
createdAt = info.createdAt,
lastUpdatedAt = info.lastUpdatedAt,
)
}

@OptIn(ExperimentalCoroutinesApi::class)
val toDraw = _positionsOnEdit.flatMapLatest { positions ->
currentStory.map { storyState ->
val focus = storyState.focusId
private val _documentEditionState: Flow<Pair<StoryState, DocumentInfo>> =
combine(currentStory, _documentInfo) { storyState, documentInfo ->
storyState to documentInfo
}

val toDrawStories = storyState.stories.mapValues { (position, storyStep) ->
DrawStory(storyStep, positions.contains(position))
}
val toDraw = combine(_positionsOnEdit, currentStory) { positions, storyState ->
val focus = storyState.focusId

DrawState(toDrawStories, focus)
val toDrawStories = storyState.stories.mapValues { (position, storyStep) ->
DrawStory(storyStep, positions.contains(position))
}

DrawState(toDrawStories, focus)
}

private val isOnSelection: Boolean
get() = _positionsOnEdit.value.isNotEmpty()

fun saveOnStoryChanges(saveNote: SaveNote) {
fun saveOnStoryChanges(documentUpdate: DocumentUpdate) {
coroutineScope.launch(dispatcher) {
currentDocument.collectLatest { document ->
saveNote.saveDocument(document)
_documentEditionState.collectLatest { (storyState, documentInfo) ->
when (val lastEdit = storyState.lastEdit) {
is LastEdit.LineEdition -> {
documentUpdate.saveStoryStep(
storyStep = lastEdit.storyStep,
position = lastEdit.position,
documentId = documentInfo.id
)
}

LastEdit.Nothing -> {}

LastEdit.Whole -> {
val stories = storyState.stories
val titleFromContent = stories.values.firstOrNull { storyStep ->
//Todo: Change the type of change to allow different types. The client code should decide what is a title
//It is also interesting to inv
storyStep.type == StoryType.TITLE.type
}?.text

val document = Document(
id = documentInfo.id,
title = titleFromContent ?: documentInfo.title,
content = storyState.stories,
createdAt = documentInfo.createdAt,
lastUpdatedAt = documentInfo.lastUpdatedAt,
)

documentUpdate.saveDocument(document)
}
}
}
}
}
Expand All @@ -135,6 +162,7 @@ class StoryTellerManager(

_currentStory.value = StoryState(
normalized + normalized,
LastEdit.Nothing,
firstMessage.id
)
}
Expand All @@ -158,10 +186,11 @@ class StoryTellerManager(
if (isInitialized()) return

val stories = document.content ?: emptyMap()
_currentStory.value = StoryState(stepsNormalizer(stories.toEditState()), null)
_currentStory.value =
StoryState(stepsNormalizer(stories.toEditState()), LastEdit.Nothing, null)
val normalized = stepsNormalizer(stories.toEditState())

_currentStory.value = StoryState(normalized)
_currentStory.value = StoryState(normalized, LastEdit.Nothing)
_documentInfo.value = document.info()
}

Expand All @@ -171,7 +200,10 @@ class StoryTellerManager(
}

val movedStories = movementHandler.merge(_currentStory.value.stories, info)
_currentStory.value = StoryState(stories = stepsNormalizer(movedStories))
_currentStory.value = StoryState(
stories = stepsNormalizer(movedStories),
lastEdit = LastEdit.Whole
)
}

fun moveRequest(moveInfo: MoveInfo) {
Expand All @@ -180,7 +212,10 @@ class StoryTellerManager(
}

val newStory = movementHandler.move(_currentStory.value.stories, moveInfo)
_currentStory.value = StoryState(stepsNormalizer(newStory.toEditState()))
_currentStory.value = StoryState(
stepsNormalizer(newStory.toEditState()),
lastEdit = LastEdit.Whole
)
}

/**
Expand Down Expand Up @@ -264,7 +299,7 @@ class StoryTellerManager(
acc to StoryStep(type = StoryType.LARGE_SPACE.type),
)

_currentStory.value = StoryState(newStories, newLastMessage.id)
_currentStory.value = StoryState(newStories, LastEdit.Whole, newLastMessage.id)
}
}

Expand All @@ -289,7 +324,10 @@ class StoryTellerManager(
StoryStep(type = StoryType.SPACE.type)
}
).let { newStories ->
StoryState(stepsNormalizer(newStories.toEditState()))
StoryState(
stepsNormalizer(newStories.toEditState()),
lastEdit = LastEdit.Whole
)
}

_currentStory.value = newState
Expand Down Expand Up @@ -319,6 +357,7 @@ class StoryTellerManager(
)
_currentStory.value = StoryState(
newStory,
LastEdit.Whole,
newStory[action.position]?.id
)

Expand Down Expand Up @@ -362,7 +401,7 @@ class StoryTellerManager(
private fun updateStory(position: Int, newStep: StoryStep, focusId: String? = null) {
val newMap = _currentStory.value.stories.toMutableMap()
newMap[position] = newStep
_currentStory.value = StoryState(newMap, focusId)
_currentStory.value = StoryState(newMap, LastEdit.LineEdition(position, newStep), focusId)
}

private fun revertAddText(currentStory: Map<Int, StoryStep>, addText: AddText) {
Expand All @@ -380,7 +419,11 @@ class StoryTellerManager(
mutableSteps[addText.position] =
revertStep.copy(localId = UUID.randomUUID().toString(), text = newText)

_currentStory.value = StoryState(mutableSteps, focusId = revertStep.id)
_currentStory.value = StoryState(
mutableSteps,
lastEdit = LastEdit.Whole,
focusId = revertStep.id
)
}
}

Expand All @@ -390,7 +433,11 @@ class StoryTellerManager(
mutableSteps[position]?.let { editStep ->
val newText = "${editStep.text.toString()}${addText.text}"
mutableSteps[position] = editStep.copy(text = newText)
_currentStory.value = StoryState(mutableSteps, focusId = editStep.id)
_currentStory.value = StoryState(
mutableSteps,
lastEdit = LastEdit.Whole,
focusId = editStep.id
)
}
}

Expand All @@ -414,6 +461,7 @@ class StoryTellerManager(

return StoryState(
stories = newStory,
lastEdit = LastEdit.Whole,
focusId = deleteInfo.storyUnit.id
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.github.leandroborgesferreira.storyteller.model.story

import com.github.leandroborgesferreira.storyteller.model.document.Document

/**
* Last edition. This signs how the last edition in the [Document] was made and it allows the SDK
* to react properly, for example saving just one line of change instead saving the whole document.
*/
sealed class LastEdit {
/**
* No edition was make
*/
object Nothing: LastEdit()

/**
* A whole edition was made like a line break (adding a new line) , a deletion
* o a image upload between content.
* In this case the whole document should be saved.
* It is important to notice that when new content is added between content this will change all
* the values of the positions, so it is necessary to save the whole document again.
*/
object Whole: LastEdit()

/**
* A edition in the line was made, but the positions were not affected. In this case it is
* possible to update just one line.
*/
data class LineEdition(val position: Int, val storyStep: StoryStep): LastEdit()
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ package com.github.leandroborgesferreira.storyteller.model.story
*/
data class StoryState(
val stories: Map<Int, StoryStep>,
val lastEdit: LastEdit,
val focusId: String? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ class DocumentRepositoryImpl(
documentDao.deleteDocuments(*ids.map(DocumentEntity::createById).toTypedArray())
}

override suspend fun saveStoryStep(storyStep: StoryStep, position: Int, documentId: String) {
storyUnitDao.insertStoryUnits(storyStep.toEntity(position, documentId))
}

/**
* This method removes the story units that are not in the root level (they don't have parents)
* and loads the inner steps of the steps that have children.
Expand Down

0 comments on commit 1b6ffa3

Please sign in to comment.