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

Feat/Track order of events in story #133

Merged
merged 25 commits into from
Feb 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
06406a5
Add timeline tool
b-camphart Sep 17, 2021
8a940cf
Can now:
b-camphart Oct 28, 2021
23e2290
Add selection to timeline ruler
b-camphart Nov 1, 2021
e1af827
Can insert time around story events in timeline
b-camphart Nov 3, 2021
023d9d4
Only allow single selection in timeline ruler
b-camphart Nov 4, 2021
2c4786a
Cannot remove time with contained story events
b-camphart Nov 4, 2021
4a7130f
Merge branch 'feat/storyevent/visual-time-adjustment' into feat/manag…
b-camphart Nov 4, 2021
883d416
Nearly Resolve #124 - Awaiting resolution for negative values
b-camphart Nov 8, 2021
54d71d4
Merge branch 'feat/manage-story-events' into feat/storyevent/visual-t…
b-camphart Nov 8, 2021
a3da09c
Merge branch 'feat/storyevent/visual-time-adjustment' into feat/manag…
b-camphart Nov 8, 2021
0541a13
Merge branch 'feat/soyle-stories-124' into feat/storyevent/visual-tim…
b-camphart Nov 8, 2021
9ae4f9e
Merge branch 'feat/storyevent/visual-time-adjustment' into feat/manag…
b-camphart Nov 8, 2021
11d7c03
Story events cannot have negative time
b-camphart Nov 9, 2021
2c29ce1
Can only update story event time through service
b-camphart Nov 10, 2021
cbbec35
Can only create a story event through service
b-camphart Nov 10, 2021
cef88e4
Fix compiler errors caused by internalized methods
b-camphart Nov 10, 2021
9973226
Add functionality to create story event use case
b-camphart Nov 10, 2021
aa3c257
Add functionality to reschedule story event use case
b-camphart Nov 10, 2021
b57c5f1
Add functionality to adjust story events time use case
b-camphart Nov 10, 2021
6a27465
Create Scene functionality is fine for now
b-camphart Nov 10, 2021
04c3509
Adjust adapters and views to new invariant
b-camphart Nov 10, 2021
1c253b4
Update Create Story Event flow
b-camphart Nov 10, 2021
3da3661
Update Adjust Story Events Time Flow
b-camphart Nov 10, 2021
61f6627
Make sure dialog is closed after creating story event
b-camphart Nov 10, 2021
da91569
Update Reschedule Story Event flow
b-camphart Nov 10, 2021
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 change: 1 addition & 0 deletions core/domain/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ dependencies {

testImplementation(Libraries.junit.api)
testImplementation(Libraries.junit.engine)
testImplementation(Libraries.junit.params)

testFixturesApi( Libraries.junit.api)
testFixturesApi( Libraries.junit.engine)
Expand Down
15 changes: 7 additions & 8 deletions core/domain/src/main/kotlin/storyevent/StoryEvent.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.soyle.stories.domain.storyevent

import com.soyle.stories.domain.character.Character
import com.soyle.stories.domain.entities.Entity
import com.soyle.stories.domain.location.Location
import com.soyle.stories.domain.project.Project
import com.soyle.stories.domain.storyevent.events.StoryEventChange
Expand All @@ -11,18 +12,18 @@ import com.soyle.stories.domain.validation.NonBlankString
import java.util.*

class StoryEvent(
val id: Id,
override val id: Id,
val name: NonBlankString,
val time: Long,
val time: ULong,
val projectId: Project.Id,
val previousStoryEventId: Id?,
val nextStoryEventId: Id?,
val linkedLocationId: Location.Id?,
val includedCharacterIds: List<Character.Id>
) {
) : Entity<StoryEvent.Id> {

companion object {
fun create(name: NonBlankString, time: Long, projectId: Project.Id): StoryEventUpdate<StoryEventCreated> {
internal fun create(name: NonBlankString, time: ULong, projectId: Project.Id): StoryEventUpdate<StoryEventCreated> {
val storyEvent = StoryEvent(Id(), name, time, projectId, null, null, null, listOf())
val change = StoryEventCreated(storyEvent.id, name.value, time, projectId)
return Successful(storyEvent, change)
Expand All @@ -41,11 +42,9 @@ class StoryEvent(
)
}

constructor(name: NonBlankString, projectId: Project.Id) : this(Id(), name, 0L, projectId, null, null, null, emptyList())

private fun copy(
name: NonBlankString = this.name,
time: Long = this.time,
time: ULong = this.time,
previousStoryEventId: Id? = this.previousStoryEventId,
nextStoryEventId: Id? = this.nextStoryEventId,
linkedLocationId: Location.Id? = this.linkedLocationId,
Expand All @@ -57,7 +56,7 @@ class StoryEvent(
if (newName == name) return noUpdate()
return Successful(copy(name = newName), StoryEventRenamed(id, newName.value))
}
fun withTime(newTime: Long): StoryEventUpdate<StoryEventRescheduled> {
internal fun withTime(newTime: ULong): StoryEventUpdate<StoryEventRescheduled> {
if (newTime == time) return noUpdate()
return Successful(copy(time = newTime), StoryEventRescheduled(id, newTime, time))
}
Expand Down
10 changes: 10 additions & 0 deletions core/domain/src/main/kotlin/storyevent/StoryEventRepository.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.soyle.stories.domain.storyevent

import com.soyle.stories.domain.project.Project

/**
* Read-only repository to retrieve story events
*/
interface StoryEventRepository {
suspend fun listStoryEventsInProject(projectId: Project.Id): List<StoryEvent>
}
82 changes: 82 additions & 0 deletions core/domain/src/main/kotlin/storyevent/StoryEventTimeService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.soyle.stories.domain.storyevent

import com.soyle.stories.domain.project.Project
import com.soyle.stories.domain.storyevent.events.StoryEventRescheduled
import com.soyle.stories.domain.validation.EntitySet
import com.soyle.stories.domain.validation.NonBlankString
import com.soyle.stories.domain.validation.entitySetOf
import com.soyle.stories.domain.validation.toEntitySet
import kotlin.math.abs

class StoryEventTimeService(
private val storyEventRepository: StoryEventRepository
) {

suspend fun createStoryEvent(
name: NonBlankString,
time: Long,
projectId: Project.Id
): List<StoryEventUpdate<*>> {
if (time < 0) {
val creation = StoryEvent.create(name, 0u, projectId)
return normalizeAllStoryEventsAboveZero(creation.storyEvent, time).plus(creation)
}
return listOf(StoryEvent.create(name, time.toULong(), projectId))
}

suspend fun rescheduleStoryEvent(
storyEvent: StoryEvent,
newTime: Long
): List<StoryEventUpdate<StoryEventRescheduled>> {
return if (newTime < 0) normalizeAllStoryEventsAboveZero(storyEvent, newTime)
else listOf(storyEvent.withTime(newTime.coerceAtLeast(0).toULong()))
}

suspend fun adjustStoryEventTimesBy(
storyEvents: EntitySet<StoryEvent>,
amount: Long
): List<StoryEventUpdate<StoryEventRescheduled>> {
if (storyEvents.isEmpty()) return emptyList()
val projectId = storyEvents.assertPartOfSameProject()

if (storyEvents.any { it.time.toLong() + amount < 0 }) {
return normalizeAllStoryEventsAboveZero(projectId, storyEvents, amount)
} else {
return storyEvents.map { it.withTime(it.time + amount.toULong()) }
}
}

private suspend fun normalizeAllStoryEventsAboveZero(
storyEvent: StoryEvent,
negativeTime: Long
): List<StoryEventUpdate<StoryEventRescheduled>> {
return normalizeAllStoryEventsAboveZero(
storyEvent.projectId,
entitySetOf(storyEvent),
negativeTime - storyEvent.time.toLong()
)
}

private suspend fun normalizeAllStoryEventsAboveZero(
projectId: Project.Id,
storyEvents: EntitySet<StoryEvent>,
adjustment: Long
): List<StoryEventUpdate<StoryEventRescheduled>> {
val normalizedAdjustment = abs(storyEvents.minOf { it.time.toLong() + adjustment })
return storyEventRepository.listStoryEventsInProject(projectId)
.map {
if (storyEvents.containsEntityWithId(it.id)) {
it.withTime((it.time.toLong() + adjustment + normalizedAdjustment).toULong())
} else {
it.withTime(it.time + normalizedAdjustment.toULong())
}
}
}

private fun Collection<StoryEvent>.assertPartOfSameProject(): Project.Id {
val projectId = first().projectId
assert(all { it.projectId == projectId })
return projectId
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ import com.soyle.stories.domain.storyevent.StoryEvent
class StoryEventCreated(
override val storyEventId: StoryEvent.Id,
val name: String,
val time: Long,
val time: ULong,
val projectId: Project.Id
) : StoryEventChange()
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ package com.soyle.stories.domain.storyevent.events

import com.soyle.stories.domain.storyevent.StoryEvent

data class StoryEventRescheduled(override val storyEventId: StoryEvent.Id, val newTime: Long, val originalTime: Long) : StoryEventChange()
data class StoryEventRescheduled(override val storyEventId: StoryEvent.Id, val newTime: ULong, val originalTime: ULong) : StoryEventChange()
11 changes: 11 additions & 0 deletions core/domain/src/main/kotlin/time/Timeline.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.soyle.stories.domain.time

import com.soyle.stories.domain.storyevent.StoryEvent
import com.soyle.stories.domain.time.changes.TimelineUpdate
import com.soyle.stories.domain.time.changes.UnSuccessful

class Timeline {
fun withEvent(eventId: StoryEvent.Id): TimelineUpdate<*> {
return UnSuccessful(this)
}
}
12 changes: 12 additions & 0 deletions core/domain/src/main/kotlin/time/changes/Successful.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.soyle.stories.domain.time.changes

import com.soyle.stories.domain.time.Timeline

typealias SuccessfulTimelineUpdate<E> = Successful<E>

class Successful<E : TimelineChange>(
override val timeline: Timeline,
val change: E
) : TimelineUpdate<E>() {
operator fun component2() = change
}
7 changes: 7 additions & 0 deletions core/domain/src/main/kotlin/time/changes/TimelineChange.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.soyle.stories.domain.time.changes

import com.soyle.stories.domain.project.Project

abstract class TimelineChange {
abstract val projectId: Project.Id
}
9 changes: 9 additions & 0 deletions core/domain/src/main/kotlin/time/changes/TimelineUpdate.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.soyle.stories.domain.time.changes

import com.soyle.stories.domain.time.Timeline

sealed class TimelineUpdate<out E : TimelineChange>() {

abstract val timeline: Timeline
operator fun component1() = timeline
}
7 changes: 7 additions & 0 deletions core/domain/src/main/kotlin/time/changes/UnSuccessful.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.soyle.stories.domain.time.changes

import com.soyle.stories.domain.time.Timeline

typealias UnSuccessfulTimelineUpdate = UnSuccessful

class UnSuccessful(override val timeline: Timeline) : TimelineUpdate<Nothing>()
Loading