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 } - } + } } }