diff --git a/android/Omnivore/app/src/main/AndroidManifest.xml b/android/Omnivore/app/src/main/AndroidManifest.xml index a78d2d2f0..8e8ed979a 100644 --- a/android/Omnivore/app/src/main/AndroidManifest.xml +++ b/android/Omnivore/app/src/main/AndroidManifest.xml @@ -51,5 +51,11 @@ android:name=".ui.reader.PDFReaderActivity" android:theme="@style/Theme.AppCompat.NoActionBar" android:windowSoftInputMode="adjustNothing" /> + + + diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt index ededf42ad..2d38bcef9 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt @@ -41,123 +41,6 @@ import kotlinx.coroutines.launch import java.util.* import kotlin.math.roundToInt - -@Composable -fun WebReaderLoadingContainer(slug: String, webReaderViewModel: WebReaderViewModel) { - val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher - - var isMenuExpanded by remember { mutableStateOf(false) } - var showWebPreferencesDialog by remember { mutableStateOf(false ) } - - val webReaderParams: WebReaderParams? by webReaderViewModel.webReaderParamsLiveData.observeAsState(null) - val annotation: String? by webReaderViewModel.annotationLiveData.observeAsState(null) - val shouldPopView: Boolean by webReaderViewModel.shouldPopViewLiveData.observeAsState(false) - - val maxToolbarHeight = 48.dp - val maxToolbarHeightPx = with(LocalDensity.current) { maxToolbarHeight.roundToPx().toFloat() } - val toolbarHeightPx = remember { mutableStateOf(maxToolbarHeightPx) } - - // Create a connection to the nested scroll system and listen to the scroll happening inside child Column - val nestedScrollConnection = remember { - object : NestedScrollConnection { - override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { - val delta = available.y - val newHeight = toolbarHeightPx.value + delta - toolbarHeightPx.value = newHeight.coerceIn(0f, maxToolbarHeightPx) - return Offset.Zero - } - } - } - - if (webReaderParams == null) { - webReaderViewModel.loadItem(slug = slug) - } - - if (webReaderParams != null) { - Box( - modifier = Modifier - .fillMaxSize() - .nestedScroll(nestedScrollConnection) - ) { - Column( - modifier = Modifier - .fillMaxSize() - .verticalScroll(webReaderViewModel.scrollState) - - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .requiredHeight(height = maxToolbarHeight) - ) { - } - WebReader(webReaderParams!!, webReaderViewModel.storedWebPreferences(isSystemInDarkTheme()), webReaderViewModel) - } - - TopAppBar( - modifier = Modifier - .height(height = with(LocalDensity.current) { - webReaderViewModel.currentToolbarHeight = toolbarHeightPx.value.toInt() - toolbarHeightPx.value.roundToInt().toDp() - } ), - backgroundColor = MaterialTheme.colorScheme.surfaceVariant, - title = {}, - actions = { - // Disabling menu until we implement local persistence - IconButton(onClick = { isMenuExpanded = true }) { - Icon( - imageVector = Icons.Filled.Menu, - contentDescription = null - ) - } - IconButton(onClick = { showWebPreferencesDialog = true }) { - Icon( - imageVector = Icons.Filled.Settings, // TODO: set a better icon - contentDescription = null - ) - } - SavedItemContextMenu( - isExpanded = isMenuExpanded, - isArchived = webReaderParams!!.item.isArchived, - onDismiss = { isMenuExpanded = false }, - actionHandler = { webReaderViewModel.handleSavedItemAction(webReaderParams!!.item.savedItemId, it) } - ) - } - ) - - if (showWebPreferencesDialog) { - WebPreferencesDialog( - onDismiss = { - showWebPreferencesDialog = false - }, - webReaderViewModel = webReaderViewModel - ) - } - - if (annotation != null) { - AnnotationEditView( - initialAnnotation = annotation!!, - onSave = { - webReaderViewModel.saveAnnotation(it) - }, - onCancel = { - webReaderViewModel.cancelAnnotationEdit() - } - ) - } - } - - LaunchedEffect(shouldPopView) { - if (shouldPopView) { - onBackPressedDispatcher?.onBackPressed() - } - } - } else { - // TODO: add a proper loading view - Text("Loading...") - } -} - @SuppressLint("SetJavaScriptEnabled") @Composable fun WebReader( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt new file mode 100644 index 000000000..8313adb39 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt @@ -0,0 +1,200 @@ +package app.omnivore.omnivore.ui.reader + +import android.graphics.PointF +import android.os.Bundle +import android.util.Log +import android.view.View +import androidx.activity.compose.LocalOnBackPressedDispatcherOwner +import androidx.activity.compose.setContent +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.TopAppBar +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.dp +import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import app.omnivore.omnivore.R +import app.omnivore.omnivore.ui.root.RootView +import app.omnivore.omnivore.ui.savedItemViews.SavedItemContextMenu +import app.omnivore.omnivore.ui.theme.OmnivoreTheme +import com.pspdfkit.annotations.Annotation +import com.pspdfkit.annotations.HighlightAnnotation +import com.pspdfkit.configuration.PdfConfiguration +import com.pspdfkit.configuration.page.PageScrollDirection +import com.pspdfkit.listeners.DocumentListener +import com.pspdfkit.listeners.OnPreparePopupToolbarListener +import com.pspdfkit.ui.PdfFragment +import com.pspdfkit.ui.PdfThumbnailBar +import com.pspdfkit.ui.search.PdfSearchViewModular +import com.pspdfkit.ui.special_mode.controller.TextSelectionController +import com.pspdfkit.ui.special_mode.manager.TextSelectionManager +import dagger.hilt.android.AndroidEntryPoint +import kotlin.math.roundToInt + + +@AndroidEntryPoint +class WebReaderLoadingContainerActivity: AppCompatActivity() { + val viewModel: WebReaderViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val requestID = intent.getStringExtra("SAVED_ITEM_REQUEST_ID") ?: "" + + setContent { + OmnivoreTheme { + Box( + modifier = Modifier + .fillMaxSize() + .background(color = Color.Black) + ) { + WebReaderLoadingContainer(requestID = requestID, webReaderViewModel = viewModel) + } + } + } + + // animate the view up when keyboard appears + WindowCompat.setDecorFitsSystemWindows(window, false) + val rootView = findViewById(android.R.id.content).rootView + ViewCompat.setOnApplyWindowInsetsListener(rootView) { _, insets -> + val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom + rootView.setPadding(0, 0, 0, imeHeight) + insets + } + } +} + +@Composable +fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, webReaderViewModel: WebReaderViewModel) { + val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher + + var isMenuExpanded by remember { mutableStateOf(false) } + var showWebPreferencesDialog by remember { mutableStateOf(false ) } + + val webReaderParams: WebReaderParams? by webReaderViewModel.webReaderParamsLiveData.observeAsState(null) + val annotation: String? by webReaderViewModel.annotationLiveData.observeAsState(null) + val shouldPopView: Boolean by webReaderViewModel.shouldPopViewLiveData.observeAsState(false) + + val maxToolbarHeight = 48.dp + val maxToolbarHeightPx = with(LocalDensity.current) { maxToolbarHeight.roundToPx().toFloat() } + val toolbarHeightPx = remember { mutableStateOf(maxToolbarHeightPx) } + + // Create a connection to the nested scroll system and listen to the scroll happening inside child Column + val nestedScrollConnection = remember { + object : NestedScrollConnection { + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + val delta = available.y + val newHeight = toolbarHeightPx.value + delta + toolbarHeightPx.value = newHeight.coerceIn(0f, maxToolbarHeightPx) + return Offset.Zero + } + } + } + + if (webReaderParams == null) { + webReaderViewModel.loadItem(slug = slug, requestID = requestID) + } + + if (webReaderParams != null) { + Box( + modifier = Modifier + .fillMaxSize() + .nestedScroll(nestedScrollConnection) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(webReaderViewModel.scrollState) + + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .requiredHeight(height = maxToolbarHeight) + ) { + } + WebReader(webReaderParams!!, webReaderViewModel.storedWebPreferences(isSystemInDarkTheme()), webReaderViewModel) + } + + TopAppBar( + modifier = Modifier + .height(height = with(LocalDensity.current) { + webReaderViewModel.currentToolbarHeight = toolbarHeightPx.value.toInt() + toolbarHeightPx.value.roundToInt().toDp() + } ), + backgroundColor = MaterialTheme.colorScheme.surfaceVariant, + title = {}, + actions = { + // Disabling menu until we implement local persistence + IconButton(onClick = { isMenuExpanded = true }) { + Icon( + imageVector = Icons.Filled.Menu, + contentDescription = null + ) + } + IconButton(onClick = { showWebPreferencesDialog = true }) { + Icon( + imageVector = Icons.Filled.Settings, // TODO: set a better icon + contentDescription = null + ) + } + SavedItemContextMenu( + isExpanded = isMenuExpanded, + isArchived = webReaderParams!!.item.isArchived, + onDismiss = { isMenuExpanded = false }, + actionHandler = { webReaderViewModel.handleSavedItemAction(webReaderParams!!.item.savedItemId, it) } + ) + } + ) + + if (showWebPreferencesDialog) { + WebPreferencesDialog( + onDismiss = { + showWebPreferencesDialog = false + }, + webReaderViewModel = webReaderViewModel + ) + } + + if (annotation != null) { + AnnotationEditView( + initialAnnotation = annotation!!, + onSave = { + webReaderViewModel.saveAnnotation(it) + }, + onCancel = { + webReaderViewModel.cancelAnnotationEdit() + } + ) + } + } + + LaunchedEffect(shouldPopView) { + if (shouldPopView) { + onBackPressedDispatcher?.onBackPressed() + } + } + } else { + // TODO: add a proper loading view + Text("Loading...") + } +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt index affa6166d..eca884c8b 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt @@ -45,7 +45,12 @@ class WebReaderViewModel @Inject constructor( var hasTappedExistingHighlight = false var lastTapCoordinates: TapCoordinates? = null - fun loadItem(slug: String) { + fun loadItem(slug: String?, requestID: String?) { + slug?.let { loadItemUsingSlug(it) } + requestID?.let { loadItemUsingRequestID(it) } + } + + private fun loadItemUsingSlug(slug: String) { viewModelScope.launch { val webReaderParams = loadItemFromServer(slug) @@ -58,6 +63,10 @@ class WebReaderViewModel @Inject constructor( } } + private fun loadItemUsingRequestID(requestID: String) { + // TODO: implement + } + private suspend fun loadItemFromDB(slug: String) { withContext(Dispatchers.IO) { val persistedItem = dataService.db.savedItemDao().getSavedItemWithLabelsAndHighlights(slug) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveContent.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveContent.kt index ee37a0f52..e0818befe 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveContent.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveContent.kt @@ -1,5 +1,7 @@ package app.omnivore.omnivore.ui.save +import android.content.Intent +import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.MaterialTheme @@ -11,13 +13,18 @@ import androidx.compose.material.* import androidx.compose.material.ButtonDefaults import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp +import app.omnivore.omnivore.MainActivity +import app.omnivore.omnivore.ui.reader.PDFReaderActivity +import app.omnivore.omnivore.ui.reader.WebReaderLoadingContainerActivity import kotlinx.coroutines.launch @Composable @OptIn(ExperimentalMaterialApi::class) fun SaveContent(viewModel: SaveViewModel, modalBottomSheetState: ModalBottomSheetState, modifier: Modifier) { val coroutineScope = rememberCoroutineScope() + val context = LocalContext.current Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { Column( @@ -34,7 +41,11 @@ fun SaveContent(viewModel: SaveViewModel, modalBottomSheetState: ModalBottomShee onClick = { coroutineScope.launch { modalBottomSheetState.hide() - // TODO: open app + viewModel.clientRequestID?.let { + val intent = Intent(context, WebReaderLoadingContainerActivity::class.java) + intent.putExtra("SAVED_ITEM_REQUEST_ID", it) + context.startActivity(intent) + } } }, colors = ButtonDefaults.buttonColors( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveViewModel.kt index 0dbdc7222..b02e92f59 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/save/SaveViewModel.kt @@ -29,6 +29,9 @@ class SaveViewModel @Inject constructor( var message by mutableStateOf(null) private set + var clientRequestID by mutableStateOf(null) + private set + private fun getAuthToken(): String? = runBlocking { datastoreRepo.getString(DatastoreKeys.omnivoreAuthToken) } @@ -52,10 +55,12 @@ class SaveViewModel @Inject constructor( .build() try { + clientRequestID = UUID.randomUUID().toString() + val response = apolloClient.mutation( SaveUrlMutation( SaveUrlInput( - clientRequestId = UUID.randomUUID().toString(), + clientRequestId = clientRequestID!!, source = "android", url = url )