Skip to content

Commit ca801c7

Browse files
authored
Merge pull request #3065 from remychantenay/android-add-link-from-library
Android: Add link from library
2 parents fdad3a2 + cf9532b commit ca801c7

File tree

8 files changed

+214
-17
lines changed

8 files changed

+214
-17
lines changed

android/Omnivore/app/src/main/java/app/omnivore/omnivore/MainActivity.kt

+9-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import app.omnivore.omnivore.ui.components.LabelsViewModel
1818
import app.omnivore.omnivore.ui.library.LibraryViewModel
1919
import app.omnivore.omnivore.ui.library.SearchViewModel
2020
import app.omnivore.omnivore.ui.root.RootView
21+
import app.omnivore.omnivore.ui.save.SaveViewModel
2122
import app.omnivore.omnivore.ui.settings.SettingsViewModel
2223
import app.omnivore.omnivore.ui.theme.OmnivoreTheme
2324
import com.pspdfkit.PSPDFKit
@@ -37,6 +38,7 @@ class MainActivity : ComponentActivity() {
3738
val settingsViewModel: SettingsViewModel by viewModels()
3839
val searchViewModel: SearchViewModel by viewModels()
3940
val labelsViewModel: LabelsViewModel by viewModels()
41+
val saveViewModel: SaveViewModel by viewModels()
4042

4143
val context = this
4244

@@ -57,7 +59,13 @@ class MainActivity : ComponentActivity() {
5759
.fillMaxSize()
5860
.background(color = Color.Black)
5961
) {
60-
RootView(loginViewModel, searchViewModel, libraryViewModel, settingsViewModel, labelsViewModel)
62+
RootView(
63+
loginViewModel,
64+
searchViewModel,
65+
libraryViewModel,
66+
settingsViewModel,
67+
labelsViewModel,
68+
saveViewModel)
6169
}
6270
}
6371
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package app.omnivore.omnivore.ui.components
2+
3+
import android.widget.Toast
4+
import androidx.compose.foundation.*
5+
import androidx.compose.foundation.layout.*
6+
import androidx.compose.foundation.text.KeyboardOptions
7+
import androidx.compose.material.icons.Icons
8+
import androidx.compose.material.icons.filled.Link
9+
import androidx.compose.material3.*
10+
import androidx.compose.runtime.*
11+
import androidx.compose.runtime.livedata.observeAsState
12+
import androidx.compose.ui.Alignment
13+
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.focus.FocusRequester
15+
import androidx.compose.ui.focus.focusRequester
16+
import androidx.compose.ui.platform.ClipboardManager
17+
import androidx.compose.ui.platform.LocalClipboardManager
18+
import androidx.compose.ui.platform.LocalContext
19+
import androidx.compose.ui.res.stringResource
20+
import androidx.compose.ui.text.TextRange
21+
import androidx.compose.ui.text.font.FontWeight
22+
import androidx.compose.ui.text.input.KeyboardType
23+
import androidx.compose.ui.text.input.TextFieldValue
24+
import androidx.compose.ui.unit.dp
25+
import androidx.lifecycle.MutableLiveData
26+
import app.omnivore.omnivore.R
27+
import app.omnivore.omnivore.ui.save.SaveState
28+
import app.omnivore.omnivore.ui.save.SaveViewModel
29+
30+
@Composable
31+
fun AddLinkSheetContent(
32+
saveViewModel: SaveViewModel,
33+
onCancel: () -> Unit,
34+
onLinkAdded: () -> Unit
35+
) {
36+
37+
val context = LocalContext.current
38+
val focusRequester = remember { FocusRequester() }
39+
40+
val clipboardManager: ClipboardManager = LocalClipboardManager.current
41+
val clipboardText = clipboardManager.getText()?.text
42+
43+
var textFieldValue by remember { mutableStateOf(TextFieldValue("")) }
44+
45+
fun showToast(msg: String) {
46+
Toast.makeText(
47+
context,
48+
msg,
49+
Toast.LENGTH_SHORT
50+
).show()
51+
}
52+
53+
val saveState: SaveState by saveViewModel.saveState.observeAsState(SaveState.NONE)
54+
val isSaving = MutableLiveData(false)
55+
56+
when (saveState) {
57+
SaveState.NONE -> {
58+
isSaving.value = false
59+
}
60+
SaveState.SAVING -> {
61+
isSaving.value = true
62+
}
63+
SaveState.ERROR -> {
64+
isSaving.value = false
65+
showToast(context.getString(R.string.add_link_sheet_save_url_error))
66+
}
67+
SaveState.SAVED -> {
68+
isSaving.value = false
69+
showToast(context.getString(R.string.add_link_sheet_save_url_success))
70+
71+
onLinkAdded()
72+
}
73+
}
74+
75+
fun addLink(url: String) {
76+
if (!saveViewModel.validateUrl(url)) {
77+
showToast(context.getString(R.string.add_link_sheet_invalid_url_error))
78+
return
79+
}
80+
81+
saveViewModel.saveURL(url)
82+
}
83+
84+
Surface(
85+
modifier = Modifier
86+
.fillMaxSize()
87+
.background(MaterialTheme.colorScheme.background),
88+
) {
89+
Column(
90+
verticalArrangement = Arrangement.Top,
91+
horizontalAlignment = Alignment.CenterHorizontally,
92+
modifier = Modifier
93+
.fillMaxSize()
94+
.padding(horizontal = 5.dp)
95+
) {
96+
97+
Row(
98+
horizontalArrangement = Arrangement.SpaceBetween,
99+
verticalAlignment = Alignment.CenterVertically,
100+
modifier = Modifier
101+
.fillMaxWidth()
102+
) {
103+
TextButton(onClick = onCancel) {
104+
Text(text = stringResource(R.string.add_link_sheet_action_cancel))
105+
}
106+
107+
Text(stringResource(R.string.add_link_sheet_title), fontWeight = FontWeight.ExtraBold)
108+
109+
TextButton(onClick = { addLink(textFieldValue.text) }) {
110+
Text(stringResource(R.string.add_link_sheet_action_add_link))
111+
}
112+
}
113+
114+
if (isSaving.value == true) {
115+
Spacer(modifier = Modifier.width(16.dp))
116+
CircularProgressIndicator(
117+
modifier = Modifier
118+
.height(16.dp)
119+
.width(16.dp),
120+
strokeWidth = 2.dp,
121+
color = MaterialTheme.colorScheme.primary
122+
)
123+
}
124+
125+
OutlinedTextField(
126+
value = textFieldValue,
127+
placeholder = { Text(stringResource(R.string.add_link_sheet_text_field_placeholder)) },
128+
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
129+
leadingIcon = { Icon(imageVector = Icons.Default.Link, contentDescription = "linkIcon") },
130+
onValueChange = { textFieldValue = it },
131+
modifier = Modifier.focusRequester(focusRequester).padding(top = 24.dp).fillMaxWidth()
132+
)
133+
134+
if (clipboardText != null) {
135+
Button(
136+
modifier = Modifier.padding(top = 10.dp),
137+
onClick = {
138+
textFieldValue = TextFieldValue(
139+
text = clipboardText,
140+
selection = TextRange(clipboardText.length))
141+
}
142+
) {
143+
Text(stringResource(R.string.add_link_sheet_action_paste_from_clipboard))
144+
}
145+
}
146+
}
147+
}
148+
149+
LaunchedEffect(Unit) {
150+
focusRequester.requestFocus()
151+
}
152+
}

android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryNavigationBar.kt

+8
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighligh
2929
fun LibraryNavigationBar(
3030
savedItemViewModel: SavedItemViewModel,
3131
onSearchClicked: () -> Unit,
32+
onAddLinkClicked: () -> Unit,
3233
onSettingsIconClick: () -> Unit
3334
) {
3435
val actionsMenuItem: SavedItemWithLabelsAndHighlights? by savedItemViewModel.actionsMenuItemLiveData.observeAsState(null)
@@ -101,6 +102,13 @@ fun LibraryNavigationBar(
101102
)
102103
}
103104

105+
IconButton(onClick = onAddLinkClicked) {
106+
Icon(
107+
imageVector = Icons.Filled.Add,
108+
contentDescription = null
109+
)
110+
}
111+
104112
IconButton(onClick = onSettingsIconClick) {
105113
Icon(
106114
imageVector = Icons.Default.MoreVert,

android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt

+17-12
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package app.omnivore.omnivore.ui.library
22

33
import android.content.Intent
44
import android.util.Log
5-
import androidx.activity.compose.BackHandler
65
import androidx.compose.foundation.background
76
import androidx.compose.foundation.layout.*
87
import androidx.compose.foundation.lazy.LazyColumn
@@ -11,36 +10,30 @@ import androidx.compose.foundation.lazy.items
1110
import androidx.compose.foundation.lazy.rememberLazyListState
1211
import androidx.compose.foundation.shape.RoundedCornerShape
1312
import androidx.compose.material.*
14-
import androidx.compose.material.DrawerValue
15-
import androidx.compose.material.icons.Icons
16-
import androidx.compose.material.icons.filled.MoreVert
17-
import androidx.compose.material.icons.outlined.Delete
1813
import androidx.compose.material.pullrefresh.PullRefreshIndicator
1914
import androidx.compose.material.pullrefresh.pullRefresh
2015
import androidx.compose.material.pullrefresh.rememberPullRefreshState
2116
import androidx.compose.material3.*
2217
import androidx.compose.material3.MaterialTheme
23-
import androidx.compose.material3.Scaffold
24-
import androidx.compose.material3.SnackbarHost
2518
import androidx.compose.runtime.*
2619
import androidx.compose.runtime.livedata.observeAsState
2720
import androidx.compose.ui.Alignment
2821
import androidx.compose.ui.Modifier
2922
import androidx.compose.ui.draw.clip
3023
import androidx.compose.ui.graphics.Color
3124
import androidx.compose.ui.platform.LocalContext
32-
import androidx.compose.ui.res.painterResource
3325
import androidx.compose.ui.unit.dp
3426
import androidx.navigation.NavHostController
35-
import app.omnivore.omnivore.R
3627
import app.omnivore.omnivore.Routes
3728
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
3829
import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights
30+
import app.omnivore.omnivore.ui.components.AddLinkSheetContent
3931
import app.omnivore.omnivore.ui.components.LabelsSelectionSheetContent
4032
import app.omnivore.omnivore.ui.components.LabelsViewModel
4133
import app.omnivore.omnivore.ui.savedItemViews.SavedItemCard
4234
import app.omnivore.omnivore.ui.reader.PDFReaderActivity
4335
import app.omnivore.omnivore.ui.reader.WebReaderLoadingContainerActivity
36+
import app.omnivore.omnivore.ui.save.SaveViewModel
4437
import kotlinx.coroutines.flow.distinctUntilChanged
4538
import kotlinx.coroutines.launch
4639

@@ -50,18 +43,20 @@ import kotlinx.coroutines.launch
5043
fun LibraryView(
5144
libraryViewModel: LibraryViewModel,
5245
labelsViewModel: LabelsViewModel,
46+
saveViewModel: SaveViewModel,
5347
navController: NavHostController
5448
) {
5549
val scaffoldState: ScaffoldState = rememberScaffoldState()
5650
val showLabelsSelectionSheet: Boolean by libraryViewModel.showLabelsSelectionSheetLiveData.observeAsState(false)
51+
val showAddLinkSheet: Boolean by libraryViewModel.showAddLinkSheetLiveData.observeAsState(false)
5752

5853
val coroutineScope = rememberCoroutineScope()
5954
val modalBottomSheetState = rememberModalBottomSheetState(
6055
ModalBottomSheetValue.Hidden,
6156
confirmStateChange = { it != ModalBottomSheetValue.Hidden }
6257
)
6358

64-
if (showLabelsSelectionSheet) {
59+
if (showLabelsSelectionSheet || showAddLinkSheet) {
6560
coroutineScope.launch {
6661
modalBottomSheetState.show()
6762
}
@@ -82,7 +77,7 @@ fun LibraryView(
8277
sheetBackgroundColor = Color.Transparent,
8378
sheetState = modalBottomSheetState,
8479
sheetContent = {
85-
BottomSheetContent(libraryViewModel, labelsViewModel)
80+
BottomSheetContent(libraryViewModel, labelsViewModel, saveViewModel)
8681
Spacer(modifier = Modifier.weight(1.0F))
8782
}
8883
) {
@@ -92,6 +87,7 @@ fun LibraryView(
9287
LibraryNavigationBar(
9388
savedItemViewModel = libraryViewModel,
9489
onSearchClicked = { navController.navigate(Routes.Search.route) },
90+
onAddLinkClicked = { libraryViewModel.showAddLinkSheetLiveData.value = true },
9591
onSettingsIconClick = { navController.navigate(Routes.Settings.route) }
9692
)
9793
},
@@ -106,8 +102,9 @@ fun LibraryView(
106102
}
107103

108104
@Composable
109-
fun BottomSheetContent(libraryViewModel: LibraryViewModel, labelsViewModel: LabelsViewModel) {
105+
fun BottomSheetContent(libraryViewModel: LibraryViewModel, labelsViewModel: LabelsViewModel, saveViewModel: SaveViewModel) {
110106
val showLabelsSelectionSheet: Boolean by libraryViewModel.showLabelsSelectionSheetLiveData.observeAsState(false)
107+
val showAddLinkSheet: Boolean by libraryViewModel.showAddLinkSheetLiveData.observeAsState(false)
111108
val currentSavedItemData = libraryViewModel.currentSavedItemUnderEdit()
112109
val labels: List<SavedItemLabel> by libraryViewModel.savedItemLabelsLiveData.observeAsState(listOf())
113110

@@ -155,6 +152,14 @@ fun BottomSheetContent(libraryViewModel: LibraryViewModel, labelsViewModel: Labe
155152
)
156153
}
157154
}
155+
} else if (showAddLinkSheet) {
156+
BottomSheetUI {
157+
AddLinkSheetContent(
158+
saveViewModel = saveViewModel,
159+
onCancel = { libraryViewModel.showAddLinkSheetLiveData.value = false },
160+
onLinkAdded = { libraryViewModel.showAddLinkSheetLiveData.value = false }
161+
)
162+
}
158163
}
159164
}
160165

android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class LibraryViewModel @Inject constructor(
6060
val appliedFilterLiveData = MutableLiveData(SavedItemFilter.INBOX)
6161
val appliedSortFilterLiveData = MutableLiveData(SavedItemSortFilter.NEWEST)
6262
val showLabelsSelectionSheetLiveData = MutableLiveData(false)
63+
val showAddLinkSheetLiveData = MutableLiveData(false)
6364
val labelsSelectionCurrentItemLiveData = MutableLiveData<String?>(null)
6465
val savedItemLabelsLiveData = dataService.db.savedItemLabelDao().getSavedItemLabelsLiveData()
6566
val activeLabelsLiveData = MutableLiveData<List<SavedItemLabel>>(listOf())

android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt

+6-3
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,15 @@ import androidx.compose.ui.graphics.Color
1313
import androidx.navigation.compose.NavHost
1414
import androidx.navigation.compose.composable
1515
import androidx.navigation.compose.rememberNavController
16-
import app.omnivore.omnivore.DatastoreRepository
1716
import app.omnivore.omnivore.Routes
18-
import app.omnivore.omnivore.dataService.DataService
1917
import app.omnivore.omnivore.ui.auth.LoginViewModel
2018
import app.omnivore.omnivore.ui.auth.WelcomeScreen
2119
import app.omnivore.omnivore.ui.components.LabelsViewModel
2220
import app.omnivore.omnivore.ui.library.LibraryView
2321
import app.omnivore.omnivore.ui.library.SearchView
2422
import app.omnivore.omnivore.ui.library.LibraryViewModel
2523
import app.omnivore.omnivore.ui.library.SearchViewModel
24+
import app.omnivore.omnivore.ui.save.SaveViewModel
2625
import app.omnivore.omnivore.ui.settings.PolicyWebView
2726
import app.omnivore.omnivore.ui.settings.SettingsViewModel
2827
import com.google.accompanist.systemuicontroller.rememberSystemUiController
@@ -33,7 +32,8 @@ fun RootView(
3332
searchViewModel: SearchViewModel,
3433
libraryViewModel: LibraryViewModel,
3534
settingsViewModel: SettingsViewModel,
36-
labelsViewModel: LabelsViewModel
35+
labelsViewModel: LabelsViewModel,
36+
saveViewModel: SaveViewModel,
3737
) {
3838
val hasAuthToken: Boolean by loginViewModel.hasAuthTokenLiveData.observeAsState(false)
3939
val systemUiController = rememberSystemUiController()
@@ -59,6 +59,7 @@ fun RootView(
5959
libraryViewModel = libraryViewModel,
6060
settingsViewModel = settingsViewModel,
6161
labelsViewModel = labelsViewModel,
62+
saveViewModel = saveViewModel
6263
)
6364
} else {
6465
WelcomeScreen(viewModel = loginViewModel)
@@ -80,6 +81,7 @@ fun PrimaryNavigator(
8081
searchViewModel: SearchViewModel,
8182
settingsViewModel: SettingsViewModel,
8283
labelsViewModel: LabelsViewModel,
84+
saveViewModel: SaveViewModel,
8385
) {
8486
val navController = rememberNavController()
8587

@@ -89,6 +91,7 @@ fun PrimaryNavigator(
8991
libraryViewModel = libraryViewModel,
9092
navController = navController,
9193
labelsViewModel = labelsViewModel,
94+
saveViewModel = saveViewModel,
9295
)
9396
}
9497

0 commit comments

Comments
 (0)