Android: Add link from library
Signed-off-by: Remy Chantenay <remy.chantenay@gmail.com>
This commit is contained in:
@ -18,6 +18,7 @@ import app.omnivore.omnivore.ui.components.LabelsViewModel
|
||||
import app.omnivore.omnivore.ui.library.LibraryViewModel
|
||||
import app.omnivore.omnivore.ui.library.SearchViewModel
|
||||
import app.omnivore.omnivore.ui.root.RootView
|
||||
import app.omnivore.omnivore.ui.save.SaveViewModel
|
||||
import app.omnivore.omnivore.ui.settings.SettingsViewModel
|
||||
import app.omnivore.omnivore.ui.theme.OmnivoreTheme
|
||||
import com.pspdfkit.PSPDFKit
|
||||
@ -37,6 +38,7 @@ class MainActivity : ComponentActivity() {
|
||||
val settingsViewModel: SettingsViewModel by viewModels()
|
||||
val searchViewModel: SearchViewModel by viewModels()
|
||||
val labelsViewModel: LabelsViewModel by viewModels()
|
||||
val saveViewModel: SaveViewModel by viewModels()
|
||||
|
||||
val context = this
|
||||
|
||||
@ -57,7 +59,13 @@ class MainActivity : ComponentActivity() {
|
||||
.fillMaxSize()
|
||||
.background(color = Color.Black)
|
||||
) {
|
||||
RootView(loginViewModel, searchViewModel, libraryViewModel, settingsViewModel, labelsViewModel)
|
||||
RootView(
|
||||
loginViewModel,
|
||||
searchViewModel,
|
||||
libraryViewModel,
|
||||
settingsViewModel,
|
||||
labelsViewModel,
|
||||
saveViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,152 @@
|
||||
package app.omnivore.omnivore.ui.components
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Link
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.platform.ClipboardManager
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import app.omnivore.omnivore.R
|
||||
import app.omnivore.omnivore.ui.save.SaveState
|
||||
import app.omnivore.omnivore.ui.save.SaveViewModel
|
||||
|
||||
@Composable
|
||||
fun AddLinkSheetContent(
|
||||
saveViewModel: SaveViewModel,
|
||||
onCancel: () -> Unit,
|
||||
onLinkAdded: () -> Unit
|
||||
) {
|
||||
|
||||
val context = LocalContext.current
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
val clipboardManager: ClipboardManager = LocalClipboardManager.current
|
||||
val clipboardText = clipboardManager.getText()?.text
|
||||
|
||||
var textFieldValue by remember { mutableStateOf(TextFieldValue("")) }
|
||||
|
||||
fun showToast(msg: String) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
msg,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
val saveState: SaveState by saveViewModel.saveState.observeAsState(SaveState.NONE)
|
||||
val isSaving = MutableLiveData(false)
|
||||
|
||||
when (saveState) {
|
||||
SaveState.NONE -> {
|
||||
isSaving.value = false
|
||||
}
|
||||
SaveState.SAVING -> {
|
||||
isSaving.value = true
|
||||
}
|
||||
SaveState.ERROR -> {
|
||||
isSaving.value = false
|
||||
showToast(context.getString(R.string.add_link_sheet_save_url_error))
|
||||
}
|
||||
SaveState.SAVED -> {
|
||||
isSaving.value = false
|
||||
showToast(context.getString(R.string.add_link_sheet_save_url_success))
|
||||
|
||||
onLinkAdded()
|
||||
}
|
||||
}
|
||||
|
||||
fun addLink(url: String) {
|
||||
if (!saveViewModel.validateUrl(url)) {
|
||||
showToast(context.getString(R.string.add_link_sheet_invalid_url_error))
|
||||
return
|
||||
}
|
||||
|
||||
saveViewModel.saveURL(url)
|
||||
}
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Top,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 5.dp)
|
||||
) {
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
TextButton(onClick = onCancel) {
|
||||
Text(text = stringResource(R.string.add_link_sheet_action_cancel))
|
||||
}
|
||||
|
||||
Text(stringResource(R.string.add_link_sheet_title), fontWeight = FontWeight.ExtraBold)
|
||||
|
||||
TextButton(onClick = { addLink(textFieldValue.text) }) {
|
||||
Text(stringResource(R.string.add_link_sheet_action_add_link))
|
||||
}
|
||||
}
|
||||
|
||||
if (isSaving.value == true) {
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.height(16.dp)
|
||||
.width(16.dp),
|
||||
strokeWidth = 2.dp,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
|
||||
OutlinedTextField(
|
||||
value = textFieldValue,
|
||||
placeholder = { Text(stringResource(R.string.add_link_sheet_text_field_placeholder)) },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
|
||||
leadingIcon = { Icon(imageVector = Icons.Default.Link, contentDescription = "linkIcon") },
|
||||
onValueChange = { textFieldValue = it },
|
||||
modifier = Modifier.focusRequester(focusRequester).padding(top = 24.dp).fillMaxWidth()
|
||||
)
|
||||
|
||||
if (clipboardText != null) {
|
||||
Button(
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
onClick = {
|
||||
textFieldValue = TextFieldValue(
|
||||
text = clipboardText,
|
||||
selection = TextRange(clipboardText.length))
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.add_link_sheet_action_paste_from_clipboard))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
@ -29,6 +29,7 @@ import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighligh
|
||||
fun LibraryNavigationBar(
|
||||
savedItemViewModel: SavedItemViewModel,
|
||||
onSearchClicked: () -> Unit,
|
||||
onAddLinkClicked: () -> Unit,
|
||||
onSettingsIconClick: () -> Unit
|
||||
) {
|
||||
val actionsMenuItem: SavedItemWithLabelsAndHighlights? by savedItemViewModel.actionsMenuItemLiveData.observeAsState(null)
|
||||
@ -101,6 +102,13 @@ fun LibraryNavigationBar(
|
||||
)
|
||||
}
|
||||
|
||||
IconButton(onClick = onAddLinkClicked) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Add,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
|
||||
IconButton(onClick = onSettingsIconClick) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
|
||||
@ -2,7 +2,6 @@ package app.omnivore.omnivore.ui.library
|
||||
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
@ -11,17 +10,11 @@ import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.DrawerValue
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
@ -29,18 +22,18 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import app.omnivore.omnivore.R
|
||||
import app.omnivore.omnivore.Routes
|
||||
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
|
||||
import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights
|
||||
import app.omnivore.omnivore.ui.components.AddLinkSheetContent
|
||||
import app.omnivore.omnivore.ui.components.LabelsSelectionSheetContent
|
||||
import app.omnivore.omnivore.ui.components.LabelsViewModel
|
||||
import app.omnivore.omnivore.ui.savedItemViews.SavedItemCard
|
||||
import app.omnivore.omnivore.ui.reader.PDFReaderActivity
|
||||
import app.omnivore.omnivore.ui.reader.WebReaderLoadingContainerActivity
|
||||
import app.omnivore.omnivore.ui.save.SaveViewModel
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@ -50,10 +43,12 @@ import kotlinx.coroutines.launch
|
||||
fun LibraryView(
|
||||
libraryViewModel: LibraryViewModel,
|
||||
labelsViewModel: LabelsViewModel,
|
||||
saveViewModel: SaveViewModel,
|
||||
navController: NavHostController
|
||||
) {
|
||||
val scaffoldState: ScaffoldState = rememberScaffoldState()
|
||||
val showLabelsSelectionSheet: Boolean by libraryViewModel.showLabelsSelectionSheetLiveData.observeAsState(false)
|
||||
val showAddLinkSheet: Boolean by libraryViewModel.showAddLinkSheetLiveData.observeAsState(false)
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val modalBottomSheetState = rememberModalBottomSheetState(
|
||||
@ -61,7 +56,7 @@ fun LibraryView(
|
||||
confirmStateChange = { it != ModalBottomSheetValue.Hidden }
|
||||
)
|
||||
|
||||
if (showLabelsSelectionSheet) {
|
||||
if (showLabelsSelectionSheet || showAddLinkSheet) {
|
||||
coroutineScope.launch {
|
||||
modalBottomSheetState.show()
|
||||
}
|
||||
@ -82,7 +77,7 @@ fun LibraryView(
|
||||
sheetBackgroundColor = Color.Transparent,
|
||||
sheetState = modalBottomSheetState,
|
||||
sheetContent = {
|
||||
BottomSheetContent(libraryViewModel, labelsViewModel)
|
||||
BottomSheetContent(libraryViewModel, labelsViewModel, saveViewModel)
|
||||
Spacer(modifier = Modifier.weight(1.0F))
|
||||
}
|
||||
) {
|
||||
@ -92,6 +87,7 @@ fun LibraryView(
|
||||
LibraryNavigationBar(
|
||||
savedItemViewModel = libraryViewModel,
|
||||
onSearchClicked = { navController.navigate(Routes.Search.route) },
|
||||
onAddLinkClicked = { libraryViewModel.showAddLinkSheetLiveData.value = true },
|
||||
onSettingsIconClick = { navController.navigate(Routes.Settings.route) }
|
||||
)
|
||||
},
|
||||
@ -106,8 +102,9 @@ fun LibraryView(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BottomSheetContent(libraryViewModel: LibraryViewModel, labelsViewModel: LabelsViewModel) {
|
||||
fun BottomSheetContent(libraryViewModel: LibraryViewModel, labelsViewModel: LabelsViewModel, saveViewModel: SaveViewModel) {
|
||||
val showLabelsSelectionSheet: Boolean by libraryViewModel.showLabelsSelectionSheetLiveData.observeAsState(false)
|
||||
val showAddLinkSheet: Boolean by libraryViewModel.showAddLinkSheetLiveData.observeAsState(false)
|
||||
val currentSavedItemData = libraryViewModel.currentSavedItemUnderEdit()
|
||||
val labels: List<SavedItemLabel> by libraryViewModel.savedItemLabelsLiveData.observeAsState(listOf())
|
||||
|
||||
@ -155,6 +152,14 @@ fun BottomSheetContent(libraryViewModel: LibraryViewModel, labelsViewModel: Labe
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (showAddLinkSheet) {
|
||||
BottomSheetUI {
|
||||
AddLinkSheetContent(
|
||||
saveViewModel = saveViewModel,
|
||||
onCancel = { libraryViewModel.showAddLinkSheetLiveData.value = false },
|
||||
onLinkAdded = { libraryViewModel.showAddLinkSheetLiveData.value = false }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -60,6 +60,7 @@ class LibraryViewModel @Inject constructor(
|
||||
val appliedFilterLiveData = MutableLiveData(SavedItemFilter.INBOX)
|
||||
val appliedSortFilterLiveData = MutableLiveData(SavedItemSortFilter.NEWEST)
|
||||
val showLabelsSelectionSheetLiveData = MutableLiveData(false)
|
||||
val showAddLinkSheetLiveData = MutableLiveData(false)
|
||||
val labelsSelectionCurrentItemLiveData = MutableLiveData<String?>(null)
|
||||
val savedItemLabelsLiveData = dataService.db.savedItemLabelDao().getSavedItemLabelsLiveData()
|
||||
val activeLabelsLiveData = MutableLiveData<List<SavedItemLabel>>(listOf())
|
||||
|
||||
@ -13,9 +13,7 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import app.omnivore.omnivore.DatastoreRepository
|
||||
import app.omnivore.omnivore.Routes
|
||||
import app.omnivore.omnivore.dataService.DataService
|
||||
import app.omnivore.omnivore.ui.auth.LoginViewModel
|
||||
import app.omnivore.omnivore.ui.auth.WelcomeScreen
|
||||
import app.omnivore.omnivore.ui.components.LabelsViewModel
|
||||
@ -23,6 +21,7 @@ import app.omnivore.omnivore.ui.library.LibraryView
|
||||
import app.omnivore.omnivore.ui.library.SearchView
|
||||
import app.omnivore.omnivore.ui.library.LibraryViewModel
|
||||
import app.omnivore.omnivore.ui.library.SearchViewModel
|
||||
import app.omnivore.omnivore.ui.save.SaveViewModel
|
||||
import app.omnivore.omnivore.ui.settings.PolicyWebView
|
||||
import app.omnivore.omnivore.ui.settings.SettingsViewModel
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
@ -33,7 +32,8 @@ fun RootView(
|
||||
searchViewModel: SearchViewModel,
|
||||
libraryViewModel: LibraryViewModel,
|
||||
settingsViewModel: SettingsViewModel,
|
||||
labelsViewModel: LabelsViewModel
|
||||
labelsViewModel: LabelsViewModel,
|
||||
saveViewModel: SaveViewModel,
|
||||
) {
|
||||
val hasAuthToken: Boolean by loginViewModel.hasAuthTokenLiveData.observeAsState(false)
|
||||
val systemUiController = rememberSystemUiController()
|
||||
@ -59,6 +59,7 @@ fun RootView(
|
||||
libraryViewModel = libraryViewModel,
|
||||
settingsViewModel = settingsViewModel,
|
||||
labelsViewModel = labelsViewModel,
|
||||
saveViewModel = saveViewModel
|
||||
)
|
||||
} else {
|
||||
WelcomeScreen(viewModel = loginViewModel)
|
||||
@ -80,6 +81,7 @@ fun PrimaryNavigator(
|
||||
searchViewModel: SearchViewModel,
|
||||
settingsViewModel: SettingsViewModel,
|
||||
labelsViewModel: LabelsViewModel,
|
||||
saveViewModel: SaveViewModel,
|
||||
) {
|
||||
val navController = rememberNavController()
|
||||
|
||||
@ -89,6 +91,7 @@ fun PrimaryNavigator(
|
||||
libraryViewModel = libraryViewModel,
|
||||
navController = navController,
|
||||
labelsViewModel = labelsViewModel,
|
||||
saveViewModel = saveViewModel,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ package app.omnivore.omnivore.ui.save
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.util.Log
|
||||
import android.util.Patterns
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
@ -52,7 +53,16 @@ class SaveViewModel @Inject constructor(
|
||||
datastoreRepo.getString(DatastoreKeys.omnivoreAuthToken)
|
||||
}
|
||||
|
||||
fun cleanUrl(text: String): String? {
|
||||
/**
|
||||
* Checks whether or not the provided URL is valid.
|
||||
* @param url The potential URL to validate.
|
||||
* @return true if valid, false otherwise.
|
||||
*/
|
||||
fun validateUrl(url: String): Boolean {
|
||||
return Patterns.WEB_URL.matcher(url).matches()
|
||||
}
|
||||
|
||||
private fun cleanUrl(text: String): String? {
|
||||
val pattern = Pattern.compile("\\b(?:https?|ftp)://\\S+")
|
||||
val matcher = pattern.matcher(text)
|
||||
|
||||
|
||||
@ -204,4 +204,14 @@
|
||||
<string name="settings_view_setting_row_terms_and_conditions">Terms and Conditions</string>
|
||||
<string name="settings_view_setting_row_manage_account">Manage Account</string>
|
||||
<string name="settings_view_setting_row_logout">Logout</string>
|
||||
|
||||
<!-- AddLinkSheet -->
|
||||
<string name="add_link_sheet_title">Add Link</string>
|
||||
<string name="add_link_sheet_text_field_placeholder">Add Link</string>
|
||||
<string name="add_link_sheet_action_add_link">Add</string>
|
||||
<string name="add_link_sheet_action_cancel">Cancel</string>
|
||||
<string name="add_link_sheet_action_paste_from_clipboard">Get from clipboard</string>
|
||||
<string name="add_link_sheet_invalid_url_error">Invalid URL</string>
|
||||
<string name="add_link_sheet_save_url_error">Error while saving link!</string>
|
||||
<string name="add_link_sheet_save_url_success">Link successfully saved!</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user