diff --git a/.idea/androidTestResultsUserPreferences.xml b/.idea/androidTestResultsUserPreferences.xml
index cb934fbf4..9fe96af18 100644
--- a/.idea/androidTestResultsUserPreferences.xml
+++ b/.idea/androidTestResultsUserPreferences.xml
@@ -82,6 +82,19 @@
+
+
+
+
+
+
+
@@ -160,6 +173,19 @@
+
+
+
+
+
+
+
diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
index d14659320..327ff914c 100644
--- a/.idea/deploymentTargetDropDown.xml
+++ b/.idea/deploymentTargetDropDown.xml
@@ -34,6 +34,12 @@
+
+
+
+
+
+
diff --git a/sample/app_sample/src/androidTest/java/br/com/leandroferreira/app_sample/NoteMenuAndroidTest.kt b/sample/app_sample/src/androidTest/java/br/com/leandroferreira/app_sample/NoteMenuAndroidTest.kt
index 1cd1f404c..ff9bf3f5b 100644
--- a/sample/app_sample/src/androidTest/java/br/com/leandroferreira/app_sample/NoteMenuAndroidTest.kt
+++ b/sample/app_sample/src/androidTest/java/br/com/leandroferreira/app_sample/NoteMenuAndroidTest.kt
@@ -1,13 +1,13 @@
package br.com.leandroferreira.app_sample
-import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performClick
import br.com.leandroferreira.app_sample.navigation.NavigationGraph
+import br.com.leandroferreira.app_sample.robots.DocumentEditRobot
+import br.com.leandroferreira.app_sample.robots.DocumentsMenuRobot
+import com.github.leandroborgesferreira.storyteller.persistence.database.StoryTellerDatabase
import org.junit.Rule
import org.junit.Test
-import java.lang.Thread.sleep
class NoteMenuAndroidTest {
@@ -15,13 +15,42 @@ class NoteMenuAndroidTest {
val composeTestRule = createComposeRule()
@Test
- fun myTest() {
+ fun itShouldBePossibleToAddNote() {
composeTestRule.setContent {
- NavigationGraph()
+ NavigationGraph(
+ database = StoryTellerDatabase.database(
+ LocalContext.current,
+ inMemory = true
+ )
+ )
}
- composeTestRule.onNodeWithTag("addNote").performClick()
+ DocumentsMenuRobot(composeTestRule).goToAddNote()
+ DocumentEditRobot(composeTestRule).verifyItIsInEdition()
+ }
+
+ @Test
+ fun itShouldBePossibleToSaveNoteWithTitle() {
+ composeTestRule.setContent {
+ NavigationGraph(
+ database = StoryTellerDatabase.database(
+ LocalContext.current,
+ inMemory = true
+ )
+ )
+ }
+
+ val noteTitle = "Note1"
+
+ val documentsMenuRobot = DocumentsMenuRobot(composeTestRule)
+ documentsMenuRobot.goToAddNote()
+
+ DocumentEditRobot(composeTestRule).run {
+ verifyItIsInEdition()
+ writeTitle(noteTitle)
+ goBack()
+ }
- composeTestRule.onNodeWithTag("noteEditionScreenTitle").assertIsDisplayed()
+ documentsMenuRobot.assertNoteWithTitle(noteTitle)
}
}
\ No newline at end of file
diff --git a/sample/app_sample/src/androidTest/java/br/com/leandroferreira/app_sample/robots/DocumentEditRobot.kt b/sample/app_sample/src/androidTest/java/br/com/leandroferreira/app_sample/robots/DocumentEditRobot.kt
new file mode 100644
index 000000000..f616947e1
--- /dev/null
+++ b/sample/app_sample/src/androidTest/java/br/com/leandroferreira/app_sample/robots/DocumentEditRobot.kt
@@ -0,0 +1,25 @@
+package br.com.leandroferreira.app_sample.robots
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.ComposeTestRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performTextInput
+import br.com.leandroferreira.editor.NAVIGATE_BACK_TEST_TAG
+import br.com.leandroferreira.editor.NOTE_EDITION_SCREEN_TITLE_TEST_TAG
+import com.github.leandroborgesferreira.storyteller.drawer.content.TITLE_DRAWER_TEST_TAG
+
+class DocumentEditRobot(private val composeTestRule: ComposeTestRule) {
+
+ fun verifyItIsInEdition() {
+ composeTestRule.onNodeWithTag(NOTE_EDITION_SCREEN_TITLE_TEST_TAG).assertIsDisplayed()
+ }
+
+ fun writeTitle(text: String) {
+ composeTestRule.onNodeWithTag(TITLE_DRAWER_TEST_TAG).performTextInput(text)
+ }
+
+ fun goBack() {
+ composeTestRule.onNodeWithTag(NAVIGATE_BACK_TEST_TAG).performClick()
+ }
+}
diff --git a/sample/app_sample/src/androidTest/java/br/com/leandroferreira/app_sample/robots/DocumentsMenuRobot.kt b/sample/app_sample/src/androidTest/java/br/com/leandroferreira/app_sample/robots/DocumentsMenuRobot.kt
new file mode 100644
index 000000000..4ee83b587
--- /dev/null
+++ b/sample/app_sample/src/androidTest/java/br/com/leandroferreira/app_sample/robots/DocumentsMenuRobot.kt
@@ -0,0 +1,19 @@
+package br.com.leandroferreira.app_sample.robots
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.ComposeTestRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import br.com.leandroferreira.note_menu.ui.screen.menu.ADD_NOTE_TEST_TAG
+import br.com.leandroferreira.note_menu.ui.screen.menu.DOCUMENT_ITEM_TEST_TAG
+
+class DocumentsMenuRobot(private val composeTestRule: ComposeTestRule) {
+
+ fun assertNoteWithTitle(title: String) {
+ composeTestRule.onNodeWithTag("$DOCUMENT_ITEM_TEST_TAG$title").assertIsDisplayed()
+ }
+
+ fun goToAddNote() {
+ composeTestRule.onNodeWithTag(ADD_NOTE_TEST_TAG).performClick()
+ }
+}
diff --git a/sample/app_sample/src/main/java/br/com/leandroferreira/app_sample/di/ViewModelFactories.kt b/sample/app_sample/src/main/java/br/com/leandroferreira/app_sample/di/ViewModelFactories.kt
index 0ce5690a1..0cffee7af 100644
--- a/sample/app_sample/src/main/java/br/com/leandroferreira/app_sample/di/ViewModelFactories.kt
+++ b/sample/app_sample/src/main/java/br/com/leandroferreira/app_sample/di/ViewModelFactories.kt
@@ -2,7 +2,7 @@ package br.com.leandroferreira.app_sample.di
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
-import br.com.leandroferreira.editor.NoteDetailsViewModel
+import br.com.leandroferreira.editor.NoteEditorViewModel
import br.com.leandroferreira.note_menu.data.usecase.NotesConfigurationRepository
import br.com.leandroferreira.note_menu.data.usecase.NotesUseCase
import br.com.leandroferreira.note_menu.viewmodel.ChooseNoteViewModel
@@ -15,8 +15,8 @@ class NoteDetailsViewModelFactory(
) : ViewModelProvider.Factory {
override fun create(modelClass: Class): T {
- if (modelClass.isAssignableFrom(NoteDetailsViewModel::class.java)) {
- return NoteDetailsViewModel(storyTellerManager, documentRepository) as T
+ if (modelClass.isAssignableFrom(NoteEditorViewModel::class.java)) {
+ return NoteEditorViewModel(storyTellerManager, documentRepository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
diff --git a/sample/app_sample/src/main/java/br/com/leandroferreira/app_sample/di/injections.kt b/sample/app_sample/src/main/java/br/com/leandroferreira/app_sample/di/injections.kt
index fe7f0000a..1b4061674 100644
--- a/sample/app_sample/src/main/java/br/com/leandroferreira/app_sample/di/injections.kt
+++ b/sample/app_sample/src/main/java/br/com/leandroferreira/app_sample/di/injections.kt
@@ -3,7 +3,7 @@ package br.com.leandroferreira.app_sample.di
import android.content.SharedPreferences
import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
-import br.com.leandroferreira.editor.NoteDetailsViewModel
+import br.com.leandroferreira.editor.NoteEditorViewModel
import br.com.leandroferreira.note_menu.data.usecase.NotesConfigurationRepository
import br.com.leandroferreira.note_menu.data.usecase.NotesUseCase
import br.com.leandroferreira.note_menu.viewmodel.ChooseNoteViewModel
@@ -48,9 +48,9 @@ class NotesInjection(
internal fun provideNoteDetailsViewModel(
documentRepository: DocumentRepository = provideDocumentRepository(),
storyTellerManager: StoryTellerManager = provideStoryTellerManager()
- ): NoteDetailsViewModel {
+ ): NoteEditorViewModel {
return viewModel(initializer = {
- NoteDetailsViewModel(storyTellerManager, documentRepository)
+ NoteEditorViewModel(storyTellerManager, documentRepository)
})
}
}
diff --git a/sample/app_sample/src/main/java/br/com/leandroferreira/app_sample/navigation/NavigationActivity.kt b/sample/app_sample/src/main/java/br/com/leandroferreira/app_sample/navigation/NavigationActivity.kt
index fe0237d21..c516b9f16 100644
--- a/sample/app_sample/src/main/java/br/com/leandroferreira/app_sample/navigation/NavigationActivity.kt
+++ b/sample/app_sample/src/main/java/br/com/leandroferreira/app_sample/navigation/NavigationActivity.kt
@@ -1,12 +1,14 @@
package br.com.leandroferreira.app_sample.navigation
import android.content.Context
+import android.content.SharedPreferences
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavController
+import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
@@ -14,8 +16,8 @@ import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import br.com.leandroferreira.app_sample.di.NotesInjection
import br.com.leandroferreira.app_sample.theme.ApplicationComposeTheme
-import br.com.leandroferreira.editor.NoteDetailsScreen
-import br.com.leandroferreira.note_menu.ui.screen.ChooseNoteScreen
+import br.com.leandroferreira.editor.NoteEditorScreen
+import br.com.leandroferreira.note_menu.ui.screen.menu.ChooseNoteScreen
import com.github.leandroborgesferreira.storyteller.persistence.database.StoryTellerDatabase
import com.github.leandroborgesferreira.storyteller.video.VideoFrameConfig
@@ -32,20 +34,20 @@ class NavigationActivity : AppCompatActivity() {
}
@Composable
-fun NavigationGraph() {
- val navController = rememberNavController()
- val context = LocalContext.current
- val database = StoryTellerDatabase.database(context)
- val sharedPreferences = context.getSharedPreferences(
+fun NavigationGraph(
+ context: Context = LocalContext.current,
+ navController: NavHostController = rememberNavController(),
+ database: StoryTellerDatabase = StoryTellerDatabase.database(context),
+ sharedPreferences: SharedPreferences = context.getSharedPreferences(
"br.com.leandroferreira.storyteller.preferences",
Context.MODE_PRIVATE
- )
-
- val notesInjection = NotesInjection(database, sharedPreferences)
+ ),
+ notesInjection: NotesInjection = NotesInjection(database, sharedPreferences)
+) {
ApplicationComposeTheme {
NavHost(navController = navController, startDestination = Destinations.CHOOSE_NOTE.id) {
- composable(Destinations.CHOOSE_NOTE.id) { backEntry ->
+ composable(Destinations.CHOOSE_NOTE.id) {
val chooseNoteViewModel = notesInjection.provideChooseNoteViewModel()
ChooseNoteScreen(
@@ -66,7 +68,7 @@ fun NavigationGraph() {
if (noteId != null && noteTitle != null) {
val noteDetailsViewModel = notesInjection.provideNoteDetailsViewModel()
- NoteDetailsScreen(
+ NoteEditorScreen(
noteId.takeIf { it != "null" },
noteTitle.takeIf { it != "null" },
noteDetailsViewModel,
@@ -78,10 +80,10 @@ fun NavigationGraph() {
}
composable(route = Destinations.NOTE_DETAILS.id) {
- NoteDetailsScreen(
+ NoteEditorScreen(
documentId = null,
title = null,
- noteDetailsViewModel = notesInjection.provideNoteDetailsViewModel(),
+ noteEditorViewModel = notesInjection.provideNoteDetailsViewModel(),
navigateBack = navController::navigateToNoteMenu
)
}
diff --git a/sample/editor/src/main/java/br/com/leandroferreira/editor/NoteDetailsScreen.kt b/sample/editor/src/main/java/br/com/leandroferreira/editor/NoteEditorScreen.kt
similarity index 70%
rename from sample/editor/src/main/java/br/com/leandroferreira/editor/NoteDetailsScreen.kt
rename to sample/editor/src/main/java/br/com/leandroferreira/editor/NoteEditorScreen.kt
index 784a3bed6..14766a7dc 100644
--- a/sample/editor/src/main/java/br/com/leandroferreira/editor/NoteDetailsScreen.kt
+++ b/sample/editor/src/main/java/br/com/leandroferreira/editor/NoteEditorScreen.kt
@@ -50,12 +50,14 @@ import com.github.leandroborgesferreira.storyteller.uicomponents.EditionScreen
import kotlinx.coroutines.flow.collectLatest
import java.util.UUID
-@OptIn(ExperimentalMaterial3Api::class)
+const val NAVIGATE_BACK_TEST_TAG = "NoteEditorScreenNavigateBack"
+const val NOTE_EDITION_SCREEN_TITLE_TEST_TAG = "noteEditionScreenTitle"
+
@Composable
-fun NoteDetailsScreen(
+fun NoteEditorScreen(
documentId: String?,
title: String?,
- noteDetailsViewModel: NoteDetailsViewModel,
+ noteEditorViewModel: NoteEditorViewModel,
navigateBack: () -> Unit,
) {
val backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
@@ -63,7 +65,7 @@ fun NoteDetailsScreen(
val backCallback = remember {
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
- noteDetailsViewModel.removeNoteIfEmpty(onComplete = navigateBack)
+ noteEditorViewModel.removeNoteIfEmpty(onComplete = navigateBack)
}
}
}
@@ -77,9 +79,9 @@ fun NoteDetailsScreen(
}
if (documentId != null) {
- noteDetailsViewModel.requestDocumentContent(documentId)
+ noteEditorViewModel.requestDocumentContent(documentId)
} else {
- noteDetailsViewModel.createNewNote(
+ noteEditorViewModel.createNewDocument(
UUID.randomUUID().toString(),
stringResource(R.string.untitled)
)
@@ -87,29 +89,10 @@ fun NoteDetailsScreen(
Scaffold(
topBar = {
- TopAppBar(
- title = {
- Text(
- modifier = Modifier.semantics {
- testTag = "noteEditionScreenTitle"
- },
- text = title?.takeIf { it.isNotBlank() }
- ?: stringResource(id = R.string.note),
- color = MaterialTheme.colorScheme.onPrimary
- )
- },
- navigationIcon = {
- Icon(
- modifier = Modifier
- .clip(CircleShape)
- .clickable {
- noteDetailsViewModel.removeNoteIfEmpty(onComplete = navigateBack)
- }
- .padding(10.dp),
- imageVector = Icons.Filled.ArrowBack,
- contentDescription = stringResource(R.string.back),
- tint = MaterialTheme.colorScheme.onPrimary
- )
+ TopBar(
+ title = title?.takeIf { it.isNotBlank() } ?: stringResource(id = R.string.note),
+ navigationClick = {
+ noteEditorViewModel.removeNoteIfEmpty(onComplete = navigateBack)
}
)
},
@@ -120,24 +103,54 @@ fun NoteDetailsScreen(
.fillMaxSize()
.imePadding()
) {
- TextEditor(noteDetailsViewModel)
+ TextEditor(noteEditorViewModel)
- BottomScreen(noteDetailsViewModel)
+ BottomScreen(noteEditorViewModel)
}
}
}
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun TopBar(title: String, navigationClick: () -> Unit) {
+ TopAppBar(
+ title = {
+ Text(
+ modifier = Modifier.semantics {
+ testTag = NOTE_EDITION_SCREEN_TITLE_TEST_TAG
+ },
+ text = title,
+ color = MaterialTheme.colorScheme.onPrimary
+ )
+ },
+ navigationIcon = {
+ Icon(
+ modifier = Modifier
+ .semantics {
+ testTag = NAVIGATE_BACK_TEST_TAG
+ }
+ .clip(CircleShape)
+ .clickable(onClick = navigationClick)
+ .padding(10.dp),
+ imageVector = Icons.Filled.ArrowBack,
+ contentDescription = stringResource(R.string.back),
+ tint = MaterialTheme.colorScheme.onPrimary
+ )
+ }
+ )
+}
+
@Composable
-fun ColumnScope.TextEditor(noteDetailsViewModel: NoteDetailsViewModel) {
- val storyState by noteDetailsViewModel.toDraw.collectAsStateWithLifecycle()
- val editable by noteDetailsViewModel.editModeState.collectAsStateWithLifecycle()
+fun ColumnScope.TextEditor(noteEditorViewModel: NoteEditorViewModel) {
+ val storyState by noteEditorViewModel.toDraw.collectAsStateWithLifecycle()
+ val editable by noteEditorViewModel.editModeState.collectAsStateWithLifecycle()
val listState: LazyListState = rememberLazyListState()
- val position by noteDetailsViewModel.scrollToPosition.collectAsStateWithLifecycle()
+ val position by noteEditorViewModel.scrollToPosition.collectAsStateWithLifecycle()
if (position != null) {
//Todo: Review this. Is a LaunchedEffect the correct way to do this??
LaunchedEffect(position, block = {
- noteDetailsViewModel.scrollToPosition.collectLatest {
+ noteEditorViewModel.scrollToPosition.collectLatest {
listState.animateScrollToItem(position!!, scrollOffset = -100)
}
})
@@ -154,7 +167,7 @@ fun ColumnScope.TextEditor(noteDetailsViewModel: NoteDetailsViewModel) {
listState = listState,
drawers = DefaultDrawers.create(
editable,
- noteDetailsViewModel.storyTellerManager,
+ noteEditorViewModel.storyTellerManager,
defaultBorder = clipShape
// groupsBackgroundColor = MaterialTheme.colorScheme.surface
)
@@ -163,8 +176,8 @@ fun ColumnScope.TextEditor(noteDetailsViewModel: NoteDetailsViewModel) {
@OptIn(ExperimentalAnimationApi::class)
@Composable
-fun BottomScreen(noteDetailsViewModel: NoteDetailsViewModel) {
- val editState by noteDetailsViewModel.isEditState.collectAsStateWithLifecycle()
+fun BottomScreen(noteEditorViewModel: NoteEditorViewModel) {
+ val editState by noteEditorViewModel.isEditState.collectAsStateWithLifecycle()
AnimatedContent(
targetState = editState,
@@ -195,7 +208,7 @@ fun BottomScreen(noteDetailsViewModel: NoteDetailsViewModel) {
)
)
.background(MaterialTheme.colorScheme.primary),
- onDelete = noteDetailsViewModel::deleteSelection
+ onDelete = noteEditorViewModel::deleteSelection
)
} else {
InputScreen(
@@ -209,10 +222,10 @@ fun BottomScreen(noteDetailsViewModel: NoteDetailsViewModel) {
bottomCorner
)
),
- onBackPress = noteDetailsViewModel::undo,
- onForwardPress = noteDetailsViewModel::redo,
- canUndoState = noteDetailsViewModel.canUndo,
- canRedoState = noteDetailsViewModel.canRedo
+ onBackPress = noteEditorViewModel::undo,
+ onForwardPress = noteEditorViewModel::redo,
+ canUndoState = noteEditorViewModel.canUndo,
+ canRedoState = noteEditorViewModel.canRedo
)
}
}
diff --git a/sample/editor/src/main/java/br/com/leandroferreira/editor/NoteDetailsViewModel.kt b/sample/editor/src/main/java/br/com/leandroferreira/editor/NoteEditorViewModel.kt
similarity index 66%
rename from sample/editor/src/main/java/br/com/leandroferreira/editor/NoteDetailsViewModel.kt
rename to sample/editor/src/main/java/br/com/leandroferreira/editor/NoteEditorViewModel.kt
index 31d0fdde1..dffe94960 100644
--- a/sample/editor/src/main/java/br/com/leandroferreira/editor/NoteDetailsViewModel.kt
+++ b/sample/editor/src/main/java/br/com/leandroferreira/editor/NoteEditorViewModel.kt
@@ -6,7 +6,6 @@ import com.github.leandroborgesferreira.storyteller.backstack.BackstackHandler
import com.github.leandroborgesferreira.storyteller.backstack.BackstackInform
import com.github.leandroborgesferreira.storyteller.manager.DocumentRepository
import com.github.leandroborgesferreira.storyteller.manager.StoryTellerManager
-import com.github.leandroborgesferreira.storyteller.model.document.Document
import com.github.leandroborgesferreira.storyteller.model.story.DrawState
import com.github.leandroborgesferreira.storyteller.model.story.StoryState
import com.github.leandroborgesferreira.storyteller.utils.extensions.noContent
@@ -14,14 +13,12 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import java.util.Date
-class NoteDetailsViewModel(
+class NoteEditorViewModel(
val storyTellerManager: StoryTellerManager,
private val documentRepository: DocumentRepository
) : ViewModel(),
@@ -43,31 +40,20 @@ class NoteDetailsViewModel(
initialValue = DrawState(emptyMap())
)
- private val _documentState: MutableStateFlow = MutableStateFlow(null)
- val documentState: StateFlow = _documentState.asStateFlow()
-
fun deleteSelection() {
storyTellerManager.deleteSelection()
}
- fun createNewNote(documentId: String, title: String) {
+ fun createNewDocument(documentId: String, title: String) {
if (storyTellerManager.isInitialized()) return
- storyTellerManager.saveOnStoryChanges(documentId, documentRepository)
- storyTellerManager.newStory()
+ storyTellerManager.newStory(documentId, title)
- //Todo: Move this to
- viewModelScope.launch(Dispatchers.IO) {
- val document = Document(
- id = documentId,
- title = title,
- content = storyTellerManager.currentStory.value.stories,
- createdAt = Date(),
- lastUpdatedAt = Date()
- )
+ viewModelScope.launch {
+ val document = storyTellerManager.currentDocument.stateIn(this).value
documentRepository.saveDocument(document)
- _documentState.value = document
+ storyTellerManager.saveOnStoryChanges(documentRepository)
}
}
@@ -76,28 +62,29 @@ class NoteDetailsViewModel(
viewModelScope.launch(Dispatchers.IO) {
val document = documentRepository.loadDocumentById(documentId)
- val content = document?.content
- _documentState.value = document
- if (content != null) {
- storyTellerManager.saveOnStoryChanges(documentId, documentRepository)
- storyTellerManager.initStories(content)
+ if (document != null) {
+ storyTellerManager.initDocument(document)
+ storyTellerManager.saveOnStoryChanges(documentRepository)
}
}
}
fun removeNoteIfEmpty(onComplete: () -> Unit) {
- val document = _documentState.value
- if (document != null && story.value.stories.noContent()) {
- viewModelScope.launch(Dispatchers.IO) {
+ viewModelScope.launch(Dispatchers.IO) {
+ val document = storyTellerManager.currentDocument.stateIn(this).value
+
+ if (story.value.stories.noContent()) {
documentRepository.deleteDocument(document)
+ withContext(Dispatchers.Main) {
+ onComplete()
+ }
+ } else {
withContext(Dispatchers.Main) {
onComplete()
}
}
- } else {
- onComplete()
}
}
}
diff --git a/sample/note_menu/src/main/java/br/com/leandroferreira/note_menu/ui/screen/menu/ChooseNoteScreen.kt b/sample/note_menu/src/main/java/br/com/leandroferreira/note_menu/ui/screen/menu/ChooseNoteScreen.kt
new file mode 100644
index 000000000..3d44558b2
--- /dev/null
+++ b/sample/note_menu/src/main/java/br/com/leandroferreira/note_menu/ui/screen/menu/ChooseNoteScreen.kt
@@ -0,0 +1,175 @@
+package br.com.leandroferreira.note_menu.ui.screen.menu
+
+import androidx.activity.OnBackPressedCallback
+import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.MoreVert
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FloatingActionButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.testTag
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import br.com.leandroferreira.note_menu.ui.screen.configuration.ConfigurationsMenu
+import br.com.leandroferreira.note_menu.ui.screen.configuration.NotesSelectionMenu
+import br.com.leandroferreira.note_menu.viewmodel.ChooseNoteViewModel
+import br.com.leandroferreira.resourcers.R
+
+const val DOCUMENT_ITEM_TEST_TAG = "DocumentItem_"
+const val ADD_NOTE_TEST_TAG = "addNote"
+
+@Composable
+fun ChooseNoteScreen(
+ chooseNoteViewModel: ChooseNoteViewModel,
+ navigateToNote: (String, String) -> Unit,
+ newNote: () -> Unit,
+ navigateUp: () -> Unit
+) {
+ LaunchedEffect(key1 = "refresh", block = {
+ chooseNoteViewModel.requestDocuments(false)
+ })
+
+ val backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
+
+ val backCallback = remember {
+ object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ if (chooseNoteViewModel.hasSelectedNotes.value) {
+ chooseNoteViewModel.clearSelection()
+ } else {
+ navigateUp()
+ }
+ }
+ }
+ }
+
+ DisposableEffect(key1 = backDispatcher) {
+ backDispatcher?.addCallback(backCallback)
+
+ onDispose {
+ backCallback.remove()
+ }
+ }
+
+ MaterialTheme {
+ Box(modifier = Modifier.fillMaxSize()) {
+ Scaffold(
+ topBar = {
+ TopBar(menuClick = chooseNoteViewModel::editMenu)
+ },
+ floatingActionButton = {
+ FloatingActionButton(newNoteClick = newNote)
+ }
+ ) { paddingValues ->
+ Content(
+ chooseNoteViewModel = chooseNoteViewModel,
+ navigateToNote = navigateToNote,
+ selectionListener = chooseNoteViewModel::selectionListener,
+ paddingValues = paddingValues,
+ )
+ }
+
+ val editState by chooseNoteViewModel.editState.collectAsStateWithLifecycle()
+
+ ConfigurationsMenu(
+ visibilityState = editState,
+ outsideClick = chooseNoteViewModel::cancelMenu,
+ listOptionClick = chooseNoteViewModel::listArrangementSelected,
+ gridOptionClick = chooseNoteViewModel::gridArrangementSelected,
+ sortingSelected = chooseNoteViewModel::sortingSelected
+ )
+
+ val selectionState by chooseNoteViewModel.hasSelectedNotes.collectAsStateWithLifecycle()
+
+ NotesSelectionMenu(
+ visibilityState = selectionState,
+ onCopy = chooseNoteViewModel::copySelectedNotes,
+ onFavorite = chooseNoteViewModel::favoriteSelectedNotes,
+ onDelete = chooseNoteViewModel::deleteSelectedNotes,
+ )
+ }
+ }
+}
+
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun TopBar(menuClick: () -> Unit) {
+ TopAppBar(
+ title = {
+ Text(
+ text = "StoryTeller",
+ color = MaterialTheme.colorScheme.onPrimary
+ )
+ },
+ actions = {
+ Icon(
+ modifier = Modifier
+ .clip(CircleShape)
+ .clickable(onClick = menuClick)
+ .padding(10.dp),
+ imageVector = Icons.Default.MoreVert,
+ contentDescription = stringResource(R.string.more_options),
+ tint = MaterialTheme.colorScheme.onPrimary
+ )
+ }
+ )
+}
+
+@Composable
+private fun FloatingActionButton(newNoteClick: () -> Unit) {
+ FloatingActionButton(
+ modifier = Modifier.semantics {
+ testTag = ADD_NOTE_TEST_TAG
+ },
+ containerColor = MaterialTheme.colorScheme.primary,
+ onClick = newNoteClick,
+ content = {
+ Icon(
+ imageVector = Icons.Default.Add,
+ contentDescription = stringResource(R.string.add_note)
+ )
+ }
+ )
+}
+
+
+@Composable
+private fun Content(
+ chooseNoteViewModel: ChooseNoteViewModel,
+ navigateToNote: (String, String) -> Unit,
+ selectionListener: (String, Boolean) -> Unit,
+ paddingValues: PaddingValues,
+) {
+ Box(
+ modifier = Modifier
+ .padding(paddingValues)
+ .fillMaxSize()
+ ) {
+ Notes(
+ chooseNoteViewModel = chooseNoteViewModel,
+ navigateToNote = navigateToNote,
+ selectionListener = selectionListener
+ )
+ }
+}
diff --git a/sample/note_menu/src/main/java/br/com/leandroferreira/note_menu/ui/screen/ChooseNoteScreen.kt b/sample/note_menu/src/main/java/br/com/leandroferreira/note_menu/ui/screen/menu/NotesScreen.kt
similarity index 59%
rename from sample/note_menu/src/main/java/br/com/leandroferreira/note_menu/ui/screen/ChooseNoteScreen.kt
rename to sample/note_menu/src/main/java/br/com/leandroferreira/note_menu/ui/screen/menu/NotesScreen.kt
index 7e0d1c8d6..e57987416 100644
--- a/sample/note_menu/src/main/java/br/com/leandroferreira/note_menu/ui/screen/ChooseNoteScreen.kt
+++ b/sample/note_menu/src/main/java/br/com/leandroferreira/note_menu/ui/screen/menu/NotesScreen.kt
@@ -1,13 +1,10 @@
-package br.com.leandroferreira.note_menu.ui.screen
+package br.com.leandroferreira.note_menu.ui.screen.menu
-import androidx.activity.OnBackPressedCallback
-import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -16,27 +13,14 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.items
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.FloatingActionButton
-import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
-import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
@@ -46,8 +30,6 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import br.com.leandroferreira.note_menu.ui.dto.DocumentUi
-import br.com.leandroferreira.note_menu.ui.screen.configuration.ConfigurationsMenu
-import br.com.leandroferreira.note_menu.ui.screen.configuration.NotesSelectionMenu
import br.com.leandroferreira.note_menu.viewmodel.ChooseNoteViewModel
import br.com.leandroferreira.note_menu.viewmodel.NotesArrangement
import br.com.leandroferreira.resourcers.R
@@ -59,139 +41,9 @@ import com.github.leandroborgesferreira.storyteller.drawer.preview.MessagePrevie
import com.github.leandroborgesferreira.storyteller.model.story.StoryType
import com.github.leandroborgesferreira.storyteller.uicomponents.SwipeBox
-private fun previewDrawers(): Map =
- mapOf(
- StoryType.MESSAGE.type to MessagePreviewDrawer(),
- StoryType.CHECK_ITEM.type to CheckItemPreviewDrawer()
- )
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun ChooseNoteScreen(
- chooseNoteViewModel: ChooseNoteViewModel,
- navigateToNote: (String, String) -> Unit,
- newNote: () -> Unit,
- navigateUp: () -> Unit
-) {
- LaunchedEffect(key1 = "refresh", block = {
- chooseNoteViewModel.requestDocuments(false)
- })
-
- val backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
-
- val backCallback = remember {
- object : OnBackPressedCallback(true) {
- override fun handleOnBackPressed() {
- if (chooseNoteViewModel.hasSelectedNotes.value) {
- chooseNoteViewModel.clearSelection()
- } else {
- navigateUp()
- }
- }
- }
- }
-
- DisposableEffect(key1 = backDispatcher) {
- backDispatcher?.addCallback(backCallback)
-
- onDispose {
- backCallback.remove()
- }
- }
-
- MaterialTheme {
- Box(modifier = Modifier.fillMaxSize()) {
- Scaffold(
- topBar = {
- TopAppBar(
- title = {
- Text(
- text = "StoryTeller",
- color = MaterialTheme.colorScheme.onPrimary
- )
- },
- actions = {
- Icon(
- modifier = Modifier
- .clip(CircleShape)
- .clickable(onClick = chooseNoteViewModel::editMenu)
- .padding(10.dp),
- imageVector = Icons.Default.MoreVert,
- contentDescription = stringResource(R.string.more_options),
- tint = MaterialTheme.colorScheme.onPrimary
- )
- }
- )
- },
- floatingActionButton = {
- FloatingActionButton(
- modifier = Modifier.semantics {
- testTag = "addNote"
- },
- containerColor = MaterialTheme.colorScheme.primary,
- onClick = newNote,
- content = {
- Icon(
- imageVector = Icons.Default.Add,
- contentDescription = stringResource(R.string.add_note)
- )
- }
- )
- }
- ) { paddingValues ->
- Content(
- chooseNoteViewModel = chooseNoteViewModel,
- navigateToNote = navigateToNote,
- selectionListener = chooseNoteViewModel::selectionListener,
- paddingValues = paddingValues,
- )
- }
-
- val editState by chooseNoteViewModel.editState.collectAsStateWithLifecycle()
-
- ConfigurationsMenu(
- visibilityState = editState,
- outsideClick = chooseNoteViewModel::cancelMenu,
- listOptionClick = chooseNoteViewModel::listArrangementSelected,
- gridOptionClick = chooseNoteViewModel::gridArrangementSelected,
- sortingSelected = chooseNoteViewModel::sortingSelected
- )
-
- val selectionState by chooseNoteViewModel.hasSelectedNotes.collectAsStateWithLifecycle()
-
- NotesSelectionMenu(
- visibilityState = selectionState,
- onCopy = chooseNoteViewModel::copySelectedNotes,
- onFavorite = chooseNoteViewModel::favoriteSelectedNotes,
- onDelete = chooseNoteViewModel::deleteSelectedNotes,
- )
- }
- }
-}
-
-
-@Composable
-private fun Content(
- chooseNoteViewModel: ChooseNoteViewModel,
- navigateToNote: (String, String) -> Unit,
- selectionListener: (String, Boolean) -> Unit,
- paddingValues: PaddingValues,
-) {
- Box(
- modifier = Modifier
- .padding(paddingValues)
- .fillMaxSize()
- ) {
- Notes(
- chooseNoteViewModel = chooseNoteViewModel,
- navigateToNote = navigateToNote,
- selectionListener = selectionListener
- )
- }
-}
-
-@Composable
-private fun Notes(
+internal fun Notes(
chooseNoteViewModel: ChooseNoteViewModel,
navigateToNote: (String, String) -> Unit,
selectionListener: (String, Boolean) -> Unit,
@@ -303,6 +155,9 @@ private fun DocumentItem(
.fillMaxWidth()
.clickable {
documentClick(documentUi.documentId, documentUi.title)
+ }
+ .semantics {
+ testTag = "$DOCUMENT_ITEM_TEST_TAG${documentUi.title}"
},
state = documentUi.selected,
swipeListener = { state -> selectionListener(documentUi.documentId, state) },
@@ -328,7 +183,6 @@ private fun DocumentItem(
}
}
-
@Composable
private fun MockDataScreen(chooseNoteViewModel: ChooseNoteViewModel) {
val context = LocalContext.current
@@ -350,3 +204,9 @@ private fun MockDataScreen(chooseNoteViewModel: ChooseNoteViewModel) {
}
}
}
+
+private fun previewDrawers(): Map =
+ mapOf(
+ StoryType.MESSAGE.type to MessagePreviewDrawer(),
+ StoryType.CHECK_ITEM.type to CheckItemPreviewDrawer()
+ )
\ No newline at end of file
diff --git a/storyteller/src/main/java/com/github/leandroborgesferreira/storyteller/drawer/content/TitleDrawer.kt b/storyteller/src/main/java/com/github/leandroborgesferreira/storyteller/drawer/content/TitleDrawer.kt
index 6ae3a03a5..5b63150eb 100644
--- a/storyteller/src/main/java/com/github/leandroborgesferreira/storyteller/drawer/content/TitleDrawer.kt
+++ b/storyteller/src/main/java/com/github/leandroborgesferreira/storyteller/drawer/content/TitleDrawer.kt
@@ -19,6 +19,8 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardCapitalization
@@ -31,6 +33,8 @@ import com.github.leandroborgesferreira.storyteller.model.change.LineBreakInfo
import com.github.leandroborgesferreira.storyteller.model.story.StoryStep
import com.github.leandroborgesferreira.storyteller.utils.ui.transparentTextInputColors
+const val TITLE_DRAWER_TEST_TAG = "TitleDrawerTextField"
+
/**
* Draw a text that can be edited. The edition of the text is both reflect in this Composable and
* also notified by onTextEdit. It is necessary to reflect here to avoid losing the focus on the
@@ -50,9 +54,13 @@ class TitleDrawer(
fontWeight = FontWeight.Bold
)
- Column(modifier = containerModifier.clickable {
- focusRequester.requestFocus()
- }) {
+ Column(modifier = containerModifier
+ .clickable {
+ focusRequester.requestFocus()
+ }
+ .semantics {
+ testTag = "TitleDrawer"
+ }) {
if (drawInfo.editable) {
var inputText by remember {
val text = step.text ?: ""
@@ -68,7 +76,10 @@ class TitleDrawer(
TextField(
modifier = innerContainerModifier
.focusRequester(focusRequester)
- .fillMaxWidth(),
+ .fillMaxWidth()
+ .semantics {
+ testTag = TITLE_DRAWER_TEST_TAG
+ },
value = inputText,
onValueChange = { value ->
if (value.text.contains("\n")) {
diff --git a/storyteller/src/main/java/com/github/leandroborgesferreira/storyteller/manager/DocumentRepository.kt b/storyteller/src/main/java/com/github/leandroborgesferreira/storyteller/manager/DocumentRepository.kt
index 4c47b74bb..3d881ec85 100644
--- a/storyteller/src/main/java/com/github/leandroborgesferreira/storyteller/manager/DocumentRepository.kt
+++ b/storyteller/src/main/java/com/github/leandroborgesferreira/storyteller/manager/DocumentRepository.kt
@@ -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 {
+interface DocumentRepository : SaveNote {
suspend fun loadDocuments(orderBy: String): List
@@ -16,7 +16,7 @@ interface DocumentRepository {
suspend fun loadDocumentsById(ids: List, orderBy: String): List
- suspend fun saveDocument(document: Document)
+ override suspend fun saveDocument(document: Document)
suspend fun deleteDocument(document: Document)
diff --git a/storyteller/src/main/java/com/github/leandroborgesferreira/storyteller/manager/SaveNote.kt b/storyteller/src/main/java/com/github/leandroborgesferreira/storyteller/manager/SaveNote.kt
new file mode 100644
index 000000000..d19ee6ab6
--- /dev/null
+++ b/storyteller/src/main/java/com/github/leandroborgesferreira/storyteller/manager/SaveNote.kt
@@ -0,0 +1,7 @@
+package com.github.leandroborgesferreira.storyteller.manager
+
+import com.github.leandroborgesferreira.storyteller.model.document.Document
+
+interface SaveNote {
+ suspend fun saveDocument(document: Document)
+}
diff --git a/storyteller/src/main/java/com/github/leandroborgesferreira/storyteller/manager/StoryTellerManager.kt b/storyteller/src/main/java/com/github/leandroborgesferreira/storyteller/manager/StoryTellerManager.kt
index 254fc78bd..5ca9e8c2c 100644
--- a/storyteller/src/main/java/com/github/leandroborgesferreira/storyteller/manager/StoryTellerManager.kt
+++ b/storyteller/src/main/java/com/github/leandroborgesferreira/storyteller/manager/StoryTellerManager.kt
@@ -26,6 +26,7 @@ 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
@@ -65,11 +66,32 @@ class StoryTellerManager(
StoryState(stories = emptyMap())
)
+ private val _documentInfo: MutableStateFlow = MutableStateFlow(DocumentInfo())
+
private val _positionsOnEdit = MutableStateFlow(setOf())
val positionsOnEdit = _positionsOnEdit.asStateFlow()
val currentStory: StateFlow = _currentStory.asStateFlow()
+ @OptIn(ExperimentalCoroutinesApi::class)
+ val currentDocument: Flow = _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,
+ )
+ }
+ }
+
@OptIn(ExperimentalCoroutinesApi::class)
val toDraw = _positionsOnEdit.flatMapLatest { positions ->
currentStory.map { storyState ->
@@ -86,27 +108,17 @@ class StoryTellerManager(
private val isOnSelection: Boolean
get() = _positionsOnEdit.value.isNotEmpty()
- fun saveOnStoryChanges(documentId: String, documentRepository: DocumentRepository) {
+ fun saveOnStoryChanges(saveNote: SaveNote) {
coroutineScope.launch(dispatcher) {
- _currentStory.map { storyState ->
- storyState.stories
- }.collectLatest { content ->
- val document = Document(
- id = documentId,
- title = content.values.firstOrNull { story -> story.isTitle }?.text ?: "",
- content = content,
- createdAt = Date(), // Todo: Fix this!
- lastUpdatedAt = Date()
- )
-
- documentRepository.saveDocument(document)
+ currentDocument.collectLatest { document ->
+ saveNote.saveDocument(document)
}
}
}
fun isInitialized(): Boolean = _currentStory.value.stories.isNotEmpty()
- fun newStory() {
+ fun newStory(documentId: String = UUID.randomUUID().toString(), title: String = "") {
val firstMessage = StoryStep(
localId = UUID.randomUUID().toString(),
type = StoryType.TITLE.type
@@ -114,6 +126,13 @@ class StoryTellerManager(
val stories: Map = mapOf(0 to firstMessage)
val normalized = stepsNormalizer(stories.toEditState())
+ _documentInfo.value = DocumentInfo(
+ id = documentId,
+ title = title,
+ createdAt = Date(),
+ lastUpdatedAt = Date()
+ )
+
_currentStory.value = StoryState(
normalized + normalized,
firstMessage.id
@@ -135,13 +154,15 @@ class StoryTellerManager(
}
}
- fun initStories(stories: Map) {
+ fun initDocument(document: Document) {
if (isInitialized()) return
+ val stories = document.content ?: emptyMap()
_currentStory.value = StoryState(stepsNormalizer(stories.toEditState()), null)
val normalized = stepsNormalizer(stories.toEditState())
_currentStory.value = StoryState(normalized)
+ _documentInfo.value = document.info()
}
fun mergeRequest(info: MergeInfo) {
@@ -402,3 +423,20 @@ class StoryTellerManager(
}
}
+
+/**
+ * Dto class to keep information about the document
+ */
+data class DocumentInfo(
+ val id: String = UUID.randomUUID().toString(),
+ val title: String = "",
+ val createdAt: Date = Date(),
+ val lastUpdatedAt: Date = Date(),
+)
+
+fun Document.info(): DocumentInfo = DocumentInfo(
+ id = this.id,
+ title = this.title,
+ createdAt = this.createdAt,
+ lastUpdatedAt = this.lastUpdatedAt,
+)
\ No newline at end of file
diff --git a/storyteller/src/main/java/com/github/leandroborgesferreira/storyteller/model/document/Document.kt b/storyteller/src/main/java/com/github/leandroborgesferreira/storyteller/model/document/Document.kt
index ce81e8dcf..f48196854 100644
--- a/storyteller/src/main/java/com/github/leandroborgesferreira/storyteller/model/document/Document.kt
+++ b/storyteller/src/main/java/com/github/leandroborgesferreira/storyteller/model/document/Document.kt
@@ -2,11 +2,12 @@ package com.github.leandroborgesferreira.storyteller.model.document
import com.github.leandroborgesferreira.storyteller.model.story.StoryStep
import java.util.Date
+import java.util.UUID
data class Document(
- val id: String,
- val title: String,
- val content: Map?,
- val createdAt: Date,
- val lastUpdatedAt: Date,
+ val id: String = UUID.randomUUID().toString(),
+ val title: String = "",
+ val content: Map? = emptyMap(),
+ val createdAt: Date = Date(),
+ val lastUpdatedAt: Date = Date(),
)
diff --git a/storyteller/src/test/java/com/github/leandroborgesferreira/storyteller/manager/StoryTellerManagerTest.kt b/storyteller/src/test/java/com/github/leandroborgesferreira/storyteller/manager/StoryTellerManagerTest.kt
index ca459a999..50826ed2e 100644
--- a/storyteller/src/test/java/com/github/leandroborgesferreira/storyteller/manager/StoryTellerManagerTest.kt
+++ b/storyteller/src/test/java/com/github/leandroborgesferreira/storyteller/manager/StoryTellerManagerTest.kt
@@ -4,6 +4,7 @@ import com.github.leandroborgesferreira.storyteller.model.change.DeleteInfo
import com.github.leandroborgesferreira.storyteller.model.change.LineBreakInfo
import com.github.leandroborgesferreira.storyteller.model.change.MergeInfo
import com.github.leandroborgesferreira.storyteller.model.change.MoveInfo
+import com.github.leandroborgesferreira.storyteller.model.document.Document
import com.github.leandroborgesferreira.storyteller.model.story.StoryStep
import com.github.leandroborgesferreira.storyteller.model.story.StoryType
import com.github.leandroborgesferreira.storyteller.repository.StoriesRepository
@@ -70,7 +71,7 @@ class StoryTellerManagerTest {
val storyManager = StoryTellerManager(dispatcher = Dispatchers.Main)
val oldSize = messagesRepo.history().size
- storyManager.initStories(messagesRepo.history())
+ storyManager.initDocument(Document(content = messagesRepo.history()))
val newStory = storyManager.currentStory.value.stories
@@ -83,7 +84,7 @@ class StoryTellerManagerTest {
val checkItem = input[0]
val storyManager = StoryTellerManager(dispatcher = Dispatchers.Main).apply {
- initStories(input)
+ initDocument(Document(content = input))
}
val currentStory = storyManager.currentStory.value.stories
@@ -102,7 +103,7 @@ class StoryTellerManagerTest {
@Test
fun `merge request should work`() = runTest {
val storyManager = StoryTellerManager(dispatcher = Dispatchers.Main)
- storyManager.initStories(imagesInLineRepo.history())
+ storyManager.initDocument(Document(content = imagesInLineRepo.history()))
val currentStory = storyManager.currentStory.value.stories
val initialSize = currentStory.size
@@ -140,7 +141,7 @@ class StoryTellerManagerTest {
@Test
fun `merge request should work2`() = runTest {
val storyManager = StoryTellerManager(dispatcher = Dispatchers.Main)
- storyManager.initStories(imagesInLineRepo.history())
+ storyManager.initDocument(Document(content = imagesInLineRepo.history()))
val currentStory = storyManager.currentStory.value.stories
val initialSize = currentStory.size
@@ -171,7 +172,7 @@ class StoryTellerManagerTest {
@Test
fun `multiple merge requests should work`() = runTest {
val storyManager = StoryTellerManager(dispatcher = Dispatchers.Main)
- storyManager.initStories(imagesInLineRepo.history())
+ storyManager.initDocument(Document(content = imagesInLineRepo.history()))
val currentStory = storyManager.currentStory.value.stories
val initialSize = currentStory.size
@@ -236,7 +237,7 @@ class StoryTellerManagerTest {
@Test
fun `it should be possible to merge an image inside a message group`() = runTest {
val storyManager = StoryTellerManager(dispatcher = Dispatchers.Main)
- storyManager.initStories(imageGroupRepo.history())
+ storyManager.initDocument(Document(content = imageGroupRepo.history()))
val currentStory = storyManager.currentStory.value.stories
val initialSize = currentStory.size
@@ -267,7 +268,7 @@ class StoryTellerManagerTest {
@Test
fun `it should be possible to merge an image outside a message group`() = runTest {
val storyManager = StoryTellerManager(dispatcher = Dispatchers.Main)
- storyManager.initStories(imageGroupRepo.history())
+ storyManager.initDocument(Document(content = imageGroupRepo.history()))
val positionTo = 2
val positionFrom = 0
@@ -301,7 +302,7 @@ class StoryTellerManagerTest {
@Ignore
fun `when moving outside of a group, the parent Id should be null now`() = runTest {
val storyManager = StoryTellerManager(dispatcher = Dispatchers.Main)
- storyManager.initStories(imageGroupRepo.history())
+ storyManager.initDocument(Document(content = imageGroupRepo.history()))
val currentStory = storyManager.currentStory.value.stories
@@ -345,7 +346,7 @@ class StoryTellerManagerTest {
@Test
fun `it should be possible to switch images places`() = runTest {
val storyManager = StoryTellerManager(dispatcher = Dispatchers.Main)
- storyManager.initStories(imagesInLineRepo.history())
+ storyManager.initDocument(Document(content = imagesInLineRepo.history()))
val positionFrom = 0
val positionTo = 3
@@ -367,7 +368,7 @@ class StoryTellerManagerTest {
@Test
fun `deleting and leave a single element in a group destroys the group`() = runTest {
val storyManager = StoryTellerManager(dispatcher = Dispatchers.Main)
- storyManager.initStories(imageGroupRepo.history())
+ storyManager.initDocument(Document(content = imageGroupRepo.history()))
val groupPosition = 0
val currentStory = storyManager.currentStory.value.stories
@@ -406,7 +407,7 @@ class StoryTellerManagerTest {
@Test
fun `when deleting a message it should not leave consecutive spaces`() = runTest {
val storyManager = StoryTellerManager(dispatcher = Dispatchers.Main)
- storyManager.initStories(messagesRepo.history())
+ storyManager.initDocument(Document(content = messagesRepo.history()))
storyManager.onDelete(
DeleteInfo(
@@ -429,7 +430,7 @@ class StoryTellerManagerTest {
fun `when a line break happens, a new story unit with the same type should be created - simple`() =
runTest {
val storyManager = StoryTellerManager(dispatcher = Dispatchers.Main)
- storyManager.initStories(singleMessageRepo.history())
+ storyManager.initDocument(Document(content = singleMessageRepo.history()))
val stories = storyManager.currentStory.value.stories
val initialSize = stories.size
@@ -448,7 +449,7 @@ class StoryTellerManagerTest {
fun `when a line break happens, a new story unit with the same type should be created - complex`() =
runTest {
val storyManager = StoryTellerManager(dispatcher = Dispatchers.Main)
- storyManager.initStories(messagesRepo.history())
+ storyManager.initDocument(Document(content = messagesRepo.history()))
val stories = storyManager.currentStory.value.stories
val initialSize = stories.size
@@ -476,7 +477,7 @@ class StoryTellerManagerTest {
* - Check that the correct image was moved correctly
*/
val storyManager = StoryTellerManager(dispatcher = Dispatchers.Main)
- storyManager.initStories(complexMessagesRepository.history())
+ storyManager.initDocument(Document(content = complexMessagesRepository.history()))
val stories = storyManager.currentStory.value.stories
@@ -546,7 +547,7 @@ class StoryTellerManagerTest {
val storyManager = StoryTellerManager(dispatcher = Dispatchers.Main)
val input = MapStoryData.singleCheckItem()
- storyManager.initStories(input)
+ storyManager.initDocument(Document(content = input))
val currentStory = storyManager.currentStory.value.stories
storyManager.onLineBreak(LineBreakInfo(input[0]!!, 0))
@@ -561,7 +562,7 @@ class StoryTellerManagerTest {
val storyManager = StoryTellerManager(dispatcher = Dispatchers.Main)
val input = MapStoryData.singleCheckItem()
- storyManager.initStories(input)
+ storyManager.initDocument(Document(content = input))
val currentStory = storyManager.currentStory.value.stories
storyManager.onLineBreak(LineBreakInfo(input[0]!!, 0))
@@ -597,7 +598,7 @@ class StoryTellerManagerTest {
val input = MapStoryData.singleCheckItem()
storyManager.run {
- initStories(input)
+ initDocument(Document(content = input))
onLineBreak(LineBreakInfo(input[0]!!, 0))
onLineBreak(LineBreakInfo(input[0]!!, 2))
@@ -627,12 +628,12 @@ class StoryTellerManagerTest {
@Test
fun `initializing the stories 2 times should not add the last story unit twice`() = runTest {
val storyManager = StoryTellerManager(dispatcher = Dispatchers.Main)
- storyManager.initStories(complexMessagesRepository.history())
+ storyManager.initDocument(Document(content = complexMessagesRepository.history()))
val stories = storyManager.currentStory.value.stories
assertEquals(StoryType.LARGE_SPACE.type, stories.values.last().type)
- storyManager.initStories(stories)
+ storyManager.initDocument(Document(content = stories))
val newStories = storyManager.currentStory.value.stories
val storyList = newStories.values.toList()
@@ -644,7 +645,7 @@ class StoryTellerManagerTest {
@Test
fun `it should be possible to select messages`() = runTest {
val storyManager = StoryTellerManager(dispatcher = Dispatchers.Main)
- storyManager.initStories(complexMessagesRepository.history())
+ storyManager.initDocument(Document(content = complexMessagesRepository.history()))
storyManager.onSelected(true, 1)
storyManager.onSelected(true, 3)
@@ -656,7 +657,7 @@ class StoryTellerManagerTest {
@Test
fun `it should be possible to delete selected messages`() = runTest {
val storyManager = StoryTellerManager(dispatcher = Dispatchers.Main)
- storyManager.initStories(messagesRepo.history())
+ storyManager.initDocument(Document(content = messagesRepo.history()))
val selectionCount = 3
val selections = buildList {
@@ -697,7 +698,7 @@ class StoryTellerManagerTest {
@Test
fun `it should be possible to undo bulk deletion`() = runTest {
val storyManager = StoryTellerManager(dispatcher = Dispatchers.Main)
- storyManager.initStories(messagesRepo.history())
+ storyManager.initDocument(Document(content = messagesRepo.history()))
val selectionCount = 3
val selections = buildList {
@@ -740,7 +741,7 @@ class StoryTellerManagerTest {
@Test
fun `when clicking in the last position, a message should be added at the bottom`() = runTest {
val storyManager = StoryTellerManager(dispatcher = Dispatchers.Main)
- storyManager.initStories(imagesInLineRepo.history())
+ storyManager.initDocument(Document(content = imagesInLineRepo.history()))
storyManager.clickAtTheEnd()
diff --git a/storyteller_persistence/src/main/java/com/github/leandroborgesferreira/storyteller/persistence/database/StoryTellerDatabase.kt b/storyteller_persistence/src/main/java/com/github/leandroborgesferreira/storyteller/persistence/database/StoryTellerDatabase.kt
index 185e58911..d3f282c47 100644
--- a/storyteller_persistence/src/main/java/com/github/leandroborgesferreira/storyteller/persistence/database/StoryTellerDatabase.kt
+++ b/storyteller_persistence/src/main/java/com/github/leandroborgesferreira/storyteller/persistence/database/StoryTellerDatabase.kt
@@ -31,9 +31,24 @@ abstract class StoryTellerDatabase : RoomDatabase() {
companion object {
private var instance: StoryTellerDatabase? = null
- fun database(context: Context, databaseName: String = DATABASE_NAME): StoryTellerDatabase {
- return instance
- ?: Room.databaseBuilder(
+ fun database(
+ context: Context,
+ databaseName: String = DATABASE_NAME,
+ inMemory: Boolean = false
+ ): StoryTellerDatabase = instance ?: createDatabase(context, databaseName, inMemory)
+
+ private fun createDatabase(
+ context: Context,
+ databaseName: String,
+ inMemory: Boolean = false
+ ): StoryTellerDatabase =
+ if (inMemory) {
+ Room.inMemoryDatabaseBuilder(
+ context.applicationContext,
+ StoryTellerDatabase::class.java
+ ).build()
+ } else {
+ Room.databaseBuilder(
context.applicationContext,
StoryTellerDatabase::class.java,
databaseName
@@ -43,6 +58,6 @@ abstract class StoryTellerDatabase : RoomDatabase() {
.also { database ->
instance = database
}
- }
+ }
}
}