Skip to content

Commit

Permalink
Merge branch 'main' into feature/signin-persistence-offline
Browse files Browse the repository at this point in the history
  • Loading branch information
loggerz authored Dec 20, 2024
2 parents 79684cd + 878f492 commit 80a9cea
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ constructor(

private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
private val _needReload = MutableStateFlow(false)
val needReload: StateFlow<Boolean> = _needReload.asStateFlow()
private val _documents = MutableStateFlow<List<DocumentContainer>>(emptyList())
val documents: StateFlow<List<DocumentContainer>> = _documents.asStateFlow()
private val _selectedDocument = MutableStateFlow<DocumentContainer?>(null)
Expand Down Expand Up @@ -89,11 +91,18 @@ constructor(

val result = documentsManager.getDocument(mimeType, title, ref, documentFile)
result.invokeOnCompletion {
if (it != null) {
Log.e("DocumentViewModel", "Failed to download document", it)
} else {
_documentUri.value = result.getCompleted()
Log.d("DocumentViewModel", "Document retrieved as ${result.getCompleted()}")
when (it) {
null -> {
_documentUri.value = result.getCompleted()
Log.d("DocumentViewModel", "Document retrieved as ${result.getCompleted()}")
}
is DocumentsManager.FileCreationException -> {
Log.i(
"DocumentViewModel",
"Failed to create document file in same directory. Re-asking permission")
resetSaveDocumentFolder().invokeOnCompletion { _needReload.value = true }
}
else -> Log.e("DocumentViewModel", "Failed to download document", it)
}
}
}
Expand Down Expand Up @@ -126,6 +135,12 @@ constructor(
}
}

private fun resetSaveDocumentFolder(): Deferred<Unit> {
return CoroutineScope(Dispatchers.Default).async {
dataStore.edit { preferences -> preferences.remove(SAVE_DOCUMENT_FOLDER) }
}
}

fun getSaveDocumentFolder(): Deferred<Uri?> {
return CoroutineScope(Dispatchers.Default).async {
dataStore.data
Expand Down Expand Up @@ -172,6 +187,14 @@ constructor(
})
}

fun disableLoading() {
_isLoading.value = false
}

fun resetNeedReload() {
_needReload.value = false
}

/**
* Uploads a file to the selected travel.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ open class DocumentsManager(

if (file == null) {
Log.e("DocumentViewModel", "Failed to create document file in specified directory")
throw Exception("Failed to create document file in specified directory")
throw FileCreationException("Failed to create document file in specified directory")
}

Log.d(tag, "Downloading file to ${file.uri}")
Expand Down Expand Up @@ -173,4 +173,6 @@ open class DocumentsManager(
false
}
}

class FileCreationException(message: String) : Exception(message)
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material.icons.Icons
Expand Down Expand Up @@ -51,7 +50,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.unit.dp
import androidx.core.net.toFile
import androidx.lifecycle.viewmodel.compose.viewModel
Expand All @@ -77,6 +75,7 @@ fun DocumentListScreen(
navigationActions: NavigationActions,
onNavigateToDocumentPreview: () -> Unit
) {
documentViewModel.disableLoading()
val isLoading = documentViewModel.isLoading.collectAsState()
val documents = documentViewModel.documents.collectAsState()
documentViewModel.getDocuments()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
// Portions of this code were generated and or inspired by the help of GitHub Copilot or Chatgpt
package com.github.se.travelpouch.ui.documents

import android.annotation.SuppressLint
import android.content.Intent
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
Expand Down Expand Up @@ -59,25 +57,26 @@ import com.rizzi.bouquet.VerticalPDFReader
import com.rizzi.bouquet.rememberVerticalPdfReaderState
import java.util.Calendar
import java.util.GregorianCalendar
import kotlinx.coroutines.launch

/**
* Composable function for previewing a document.
*
* @param documentViewModel the document view model with the current document set as selected.
*/
@SuppressLint("StateFlowValueCalledInComposition")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DocumentPreview(
documentViewModel: DocumentViewModel,
navigationActions: NavigationActions,
activityViewModel: ActivityViewModel
) {
var openDialog by remember { mutableStateOf(false) }
documentViewModel.resetNeedReload()

var openDialog by remember { mutableStateOf(false) }
val documentContainer: DocumentContainer =
documentViewModel.selectedDocument.collectAsState().value!!
val activities = activityViewModel.activities.collectAsState().value
val needReload = documentViewModel.needReload.collectAsState().value
val uri = documentViewModel.documentUri.value
val context = LocalContext.current
val openDirectoryLauncher =
Expand All @@ -98,7 +97,7 @@ fun DocumentPreview(
}
}
}
LaunchedEffect(documentContainer) {
LaunchedEffect(needReload) {
val documentFileUri = documentViewModel.getSaveDocumentFolder().await()
if (documentFileUri == null) {
openDirectoryLauncher.launch(null)
Expand Down Expand Up @@ -133,9 +132,7 @@ fun DocumentPreview(
IconButton(
onClick = {
val activitiesLinkedToDocument =
activityViewModel.activities.value.filter {
it.documentsNeeded.contains(documentContainer)
}
activities.filter { it.documentsNeeded.contains(documentContainer) }
documentViewModel.deleteDocumentById(
documentContainer, activitiesLinkedToDocument)
navigationActions.goBack()
Expand All @@ -146,7 +143,7 @@ fun DocumentPreview(

IconButton(
onClick = {
if (activityViewModel.activities.value.isEmpty()) {
if (activities.isEmpty()) {
Toast.makeText(
context,
"Cannot link image if there are no activities",
Expand Down Expand Up @@ -213,8 +210,8 @@ fun DocumentPreview(
.testTag("activitiesList"),
verticalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(vertical = 8.dp)) {
items(activityViewModel.activities.value.size) { i ->
val activity = activityViewModel.activities.value[i]
items(activities.size) { i ->
val activity = activities[i]
val calendar = GregorianCalendar().apply { time = activity.date.toDate() }

Card(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.util.Log
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.documentfile.provider.DocumentFile
import com.github.se.travelpouch.model.travels.Location
import com.github.se.travelpouch.model.travels.Participant
import com.github.se.travelpouch.model.travels.Role
Expand All @@ -15,11 +16,17 @@ import com.google.firebase.firestore.DocumentReference
import java.io.ByteArrayInputStream
import java.io.InputStream
import kotlin.random.Random
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withContext
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Assert.assertFalse
import org.junit.Assert.assertThrows
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Test
Expand All @@ -35,11 +42,11 @@ import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.times
import org.mockito.kotlin.whenever
import org.robolectric.RobolectricTestRunner
import org.robolectric.shadows.ShadowLog

@RunWith(RobolectricTestRunner::class)
@ExperimentalCoroutinesApi
class DocumentViewModelTest {
Expand Down Expand Up @@ -269,4 +276,68 @@ class DocumentViewModelTest {
verify(spyDocumentViewModel)
.uploadDocument(anyString(), org.mockito.kotlin.any(), org.mockito.kotlin.any())
}

@Test
fun getSelectedDocument_illegalArgument() {
val document = documentContainer
val mockDocumentFile = mock(DocumentFile::class.java)

documentViewModel.selectDocument(document)
assertThrows(IllegalArgumentException::class.java) {
documentViewModel.getSelectedDocument(mockDocumentFile)
}
}

@Test
fun getSelectedDocument_anyException() {
val document = documentContainer
val mockDocumentFile = mock(DocumentFile::class.java)
whenever(documentReference.id).thenReturn("documentId")
val deferred = CompletableDeferred<Uri>()
whenever(
documentsManager.getDocument(
anyString(), anyString(), anyString(), eq(mockDocumentFile)))
.thenReturn(deferred)

mockStatic(Log::class.java).use { logMock: MockedStatic<Log> ->
logMock.`when`<Int> { Log.e(anyString(), anyString(), any()) }.thenReturn(0)

documentViewModel.selectDocument(document)
val exception = Exception("Test")
deferred.completeExceptionally(exception)
documentViewModel.getSelectedDocument(mockDocumentFile)

logMock.verify { Log.e("DocumentViewModel", "Failed to download document", exception) }
}
}

@Test
fun getSelectedDocument_FileCreationException() {
val document = documentContainer
val mockDocumentFile = mock(DocumentFile::class.java)
whenever(documentReference.id).thenReturn("documentId")
val deferred = CompletableDeferred<Uri>()
whenever(
documentsManager.getDocument(
anyString(), anyString(), anyString(), eq(mockDocumentFile)))
.thenReturn(deferred)

runBlocking { whenever(dataStore.edit { any() }).thenAnswer {} }
mockStatic(Log::class.java).use { logMock: MockedStatic<Log> ->
logMock.`when`<Int> { Log.i(anyString(), anyString()) }.thenReturn(0)

documentViewModel.selectDocument(document)
val exception = DocumentsManager.FileCreationException("Test")
deferred.completeExceptionally(exception)
documentViewModel.getSelectedDocument(mockDocumentFile)
logMock.verify {
Log.i(
"DocumentViewModel",
"Failed to create document file in same directory. Re-asking permission")
}
}
documentViewModel.resetNeedReload()
assertFalse(documentViewModel.needReload.value)
}
}

0 comments on commit 80a9cea

Please sign in to comment.