From 52fc21b0ad09e538243cc44f1ed6c9877512593e Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Fri, 9 Dec 2022 16:30:53 -0800 Subject: [PATCH 01/18] add pull to refresh mechanism on android home screen --- .../app/omnivore/omnivore/ui/home/HomeView.kt | 68 +++++++++++++------ .../omnivore/ui/home/HomeViewModel.kt | 15 ++++ 2 files changed, 61 insertions(+), 22 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt index 1663665b0..e3ee5d1f7 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt @@ -7,6 +7,10 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.ExperimentalMaterialApi +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.runtime.* import androidx.compose.runtime.livedata.observeAsState @@ -14,6 +18,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController import app.omnivore.omnivore.Routes import app.omnivore.omnivore.models.LinkedItem @@ -50,6 +55,7 @@ fun HomeView( } } +@OptIn(ExperimentalMaterialApi::class) @Composable fun HomeViewContent( homeViewModel: HomeViewModel, @@ -58,35 +64,53 @@ fun HomeViewContent( ) { val context = LocalContext.current val listState = rememberLazyListState() + + val pullRefreshState = rememberPullRefreshState( + refreshing = homeViewModel.isRefreshing, + onRefresh = { homeViewModel.refresh() } + ) + val linkedItems: List by homeViewModel.itemsLiveData.observeAsState(listOf()) - LazyColumn( - state = listState, - verticalArrangement = Arrangement.Top, - horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier - .background(MaterialTheme.colorScheme.background) + Box( + modifier = Modifier .fillMaxSize() - .padding(horizontal = 6.dp) + .pullRefresh(pullRefreshState) ) { - items(linkedItems) { item -> - LinkedItemCard( - item = item, - onClickHandler = { - if (item.isPDF()) { - val intent = Intent(context, PDFReaderActivity::class.java) - intent.putExtra("LINKED_ITEM_SLUG", item.slug) - context.startActivity(intent) - } else { - navController.navigate("WebReader/${item.slug}") + LazyColumn( + state = listState, + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier + .background(MaterialTheme.colorScheme.background) + .fillMaxSize() + .padding(horizontal = 6.dp) + ) { + items(linkedItems) { item -> + LinkedItemCard( + item = item, + onClickHandler = { + if (item.isPDF()) { + val intent = Intent(context, PDFReaderActivity::class.java) + intent.putExtra("LINKED_ITEM_SLUG", item.slug) + context.startActivity(intent) + } else { + navController.navigate("WebReader/${item.slug}") + } } - } - ) + ) + } } - } - InfiniteListHandler(listState = listState) { - homeViewModel.load() + InfiniteListHandler(listState = listState) { + homeViewModel.load() + } + + PullRefreshIndicator( + refreshing = homeViewModel.isRefreshing, + state = pullRefreshState, + modifier = Modifier.align(Alignment.TopCenter) + ) } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt index 6fc76e977..80aeb4b28 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt @@ -1,5 +1,8 @@ package app.omnivore.omnivore.ui.home +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -7,6 +10,8 @@ import app.omnivore.omnivore.models.LinkedItem import app.omnivore.omnivore.networking.Networker import app.omnivore.omnivore.networking.search import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import javax.inject.Inject @@ -26,6 +31,7 @@ class HomeViewModel @Inject constructor( // Live Data val searchTextLiveData = MutableLiveData("") val itemsLiveData = MutableLiveData>(listOf()) + var isRefreshing by mutableStateOf(false) fun updateSearchText(text: String) { searchTextLiveData.value = text @@ -37,6 +43,11 @@ class HomeViewModel @Inject constructor( } } + fun refresh() { + isRefreshing = true + load(true) + } + fun load(clearPreviousSearch: Boolean = false) { if (clearPreviousSearch) { cursor = null @@ -69,6 +80,10 @@ class HomeViewModel @Inject constructor( items = items.plus(searchResult.items) itemsLiveData.value = items } + + CoroutineScope(Dispatchers.Main).launch { + isRefreshing = false + } } } From 41a978e5fc2db6a0c7005c843648143787185825 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Fri, 9 Dec 2022 16:35:27 -0800 Subject: [PATCH 02/18] use postValue function to update searched items live data --- .../java/app/omnivore/omnivore/ui/home/HomeViewModel.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt index 80aeb4b28..488c330e9 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt @@ -72,13 +72,13 @@ class HomeViewModel @Inject constructor( receivedIdx = thisSearchIdx cursor = searchResult.cursor - if (searchTextLiveData.value != "") { + if (searchTextLiveData.value != "" || clearPreviousSearch) { val previousItems = if (clearPreviousSearch) listOf() else searchedItems searchedItems = previousItems.plus(searchResult.items) - itemsLiveData.value = searchedItems + itemsLiveData.postValue(searchedItems) } else { items = items.plus(searchResult.items) - itemsLiveData.value = items + itemsLiveData.postValue(items) } CoroutineScope(Dispatchers.Main).launch { From 201a736f0f492789515012bf6e9d71d12c8efb61 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Mon, 12 Dec 2022 13:03:34 -0800 Subject: [PATCH 03/18] show context menu on log press in android home cell --- .../app/omnivore/omnivore/ui/home/HomeView.kt | 3 +- .../omnivore/ui/home/HomeViewModel.kt | 17 ++++++ .../omnivore/ui/home/LinkedItemCard.kt | 55 ++++++++++++++++--- 3 files changed, 66 insertions(+), 9 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt index e3ee5d1f7..9389ccb0f 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt @@ -97,7 +97,8 @@ fun HomeViewContent( } else { navController.navigate("WebReader/${item.slug}") } - } + }, + actionHandler = { homeViewModel.handleLinkedItemAction(item.id, it) } ) } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt index 488c330e9..124cc718e 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt @@ -1,5 +1,6 @@ package app.omnivore.omnivore.ui.home +import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -87,6 +88,17 @@ class HomeViewModel @Inject constructor( } } + fun handleLinkedItemAction(itemID: String, action: LinkedItemAction) { + when (action) { + LinkedItemAction.Delete -> { + Log.d("maxx", "delete action for id: $itemID") + } + LinkedItemAction.Archive -> { + Log.d("maxx", "archive action for id: $itemID") + } + } + } + private fun searchQuery(): String { var query = "in:inbox sort:saved" @@ -97,3 +109,8 @@ class HomeViewModel @Inject constructor( return query } } + +enum class LinkedItemAction { + Delete, + Archive +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/LinkedItemCard.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/LinkedItemCard.kt index ffbfec8f2..6258d8474 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/LinkedItemCard.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/LinkedItemCard.kt @@ -1,13 +1,14 @@ package app.omnivore.omnivore.ui.home +import androidx.compose.foundation.* import androidx.compose.foundation.layout.* -import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Divider -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.icons.outlined.Edit +import androidx.compose.material.icons.outlined.List +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -18,8 +19,10 @@ import androidx.compose.ui.unit.sp import app.omnivore.omnivore.models.LinkedItem import coil.compose.rememberAsyncImagePainter +@OptIn(ExperimentalFoundationApi::class) @Composable -fun LinkedItemCard(item: LinkedItem, onClickHandler: () -> Unit) { +fun LinkedItemCard(item: LinkedItem, onClickHandler: () -> Unit, actionHandler: (LinkedItemAction) -> Unit) { + var isMenuExpanded by remember { mutableStateOf(false) } val publisherDisplayName = item.publisherDisplayName() Column { @@ -29,7 +32,11 @@ fun LinkedItemCard(item: LinkedItem, onClickHandler: () -> Unit) { modifier = Modifier .fillMaxWidth() .padding(12.dp) - .clickable(onClick = onClickHandler) + .combinedClickable( + onClick = onClickHandler, + onLongClick = { isMenuExpanded = true } + ) + .background(if (isMenuExpanded) Color.LightGray else Color.Transparent) ) { Column( verticalArrangement = Arrangement.spacedBy(2.dp), @@ -75,5 +82,37 @@ fun LinkedItemCard(item: LinkedItem, onClickHandler: () -> Unit) { } Divider(color = MaterialTheme.colorScheme.outlineVariant, thickness = 1.dp) + + DropdownMenu( + expanded = isMenuExpanded, + onDismissRequest = { isMenuExpanded = false } + ) { + DropdownMenuItem( + text = { Text("Archive") }, + onClick = { + actionHandler(LinkedItemAction.Archive) + isMenuExpanded = false + }, + leadingIcon = { + Icon( + Icons.Outlined.List, // TODO: use more appropriate icon + contentDescription = null + ) + } + ) + DropdownMenuItem( + text = { Text("Remove Item") }, + onClick = { + actionHandler(LinkedItemAction.Delete) + isMenuExpanded = false + }, + leadingIcon = { + Icon( + Icons.Outlined.Delete, + contentDescription = null + ) + } + ) + } } } From f7f2bce1533f2484a7fb4452645314ca5ffa5e57 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Mon, 12 Dec 2022 13:58:34 -0800 Subject: [PATCH 04/18] handle context menu linkeditem action in view model --- .../omnivore/ui/home/HomeViewModel.kt | 30 +++++++++++++++---- .../omnivore/ui/home/LinkedItemCard.kt | 6 ++-- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt index 124cc718e..5c29a8127 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt @@ -8,8 +8,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import app.omnivore.omnivore.models.LinkedItem -import app.omnivore.omnivore.networking.Networker -import app.omnivore.omnivore.networking.search +import app.omnivore.omnivore.networking.* import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -91,11 +90,31 @@ class HomeViewModel @Inject constructor( fun handleLinkedItemAction(itemID: String, action: LinkedItemAction) { when (action) { LinkedItemAction.Delete -> { - Log.d("maxx", "delete action for id: $itemID") + removeItemFromList(itemID) + + viewModelScope.launch { + networker.deleteLinkedItem(itemID) + } } LinkedItemAction.Archive -> { - Log.d("maxx", "archive action for id: $itemID") + removeItemFromList(itemID) + viewModelScope.launch { + networker.archiveLinkedItem(itemID) + } } + LinkedItemAction.Unarchive -> { + removeItemFromList(itemID) + viewModelScope.launch { + networker.unarchiveLinkedItem(itemID) + } + } + } + } + + private fun removeItemFromList(itemID: String) { + itemsLiveData.value?.let { + val newList = it.filter { item -> item.id != itemID } + itemsLiveData.postValue(newList) } } @@ -112,5 +131,6 @@ class HomeViewModel @Inject constructor( enum class LinkedItemAction { Delete, - Archive + Archive, + Unarchive } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/LinkedItemCard.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/LinkedItemCard.kt index 6258d8474..f6667b1b1 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/LinkedItemCard.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/LinkedItemCard.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Delete -import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.List import androidx.compose.material3.* import androidx.compose.runtime.* @@ -88,9 +87,10 @@ fun LinkedItemCard(item: LinkedItem, onClickHandler: () -> Unit, actionHandler: onDismissRequest = { isMenuExpanded = false } ) { DropdownMenuItem( - text = { Text("Archive") }, + text = { Text(if (item.isArchived) "Unarchive" else "Archive") }, onClick = { - actionHandler(LinkedItemAction.Archive) + val action = if (item.isArchived) LinkedItemAction.Unarchive else LinkedItemAction.Archive + actionHandler(action) isMenuExpanded = false }, leadingIcon = { From 6e1182d163b7633cb43045b059309f243a5f012c Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Mon, 12 Dec 2022 13:58:55 -0800 Subject: [PATCH 05/18] add graphql files for archiving and deleting in android --- .../main/graphql/ArchiveLinkedItem.graphql | 12 +++++++++ .../src/main/graphql/DeleteLinkedItem.graphql | 12 +++++++++ .../networking/LinkedItemMutations.kt | 26 +++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 android/Omnivore/app/src/main/graphql/ArchiveLinkedItem.graphql create mode 100644 android/Omnivore/app/src/main/graphql/DeleteLinkedItem.graphql create mode 100644 android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/LinkedItemMutations.kt diff --git a/android/Omnivore/app/src/main/graphql/ArchiveLinkedItem.graphql b/android/Omnivore/app/src/main/graphql/ArchiveLinkedItem.graphql new file mode 100644 index 000000000..7f0bff0a5 --- /dev/null +++ b/android/Omnivore/app/src/main/graphql/ArchiveLinkedItem.graphql @@ -0,0 +1,12 @@ +mutation SetLinkArchived($input: ArchiveLinkInput!) { + setLinkArchived(input: $input) { + ... on ArchiveLinkSuccess { + linkId + message + } + ... on ArchiveLinkError { + message + errorCodes + } + } +} diff --git a/android/Omnivore/app/src/main/graphql/DeleteLinkedItem.graphql b/android/Omnivore/app/src/main/graphql/DeleteLinkedItem.graphql new file mode 100644 index 000000000..cabbc6a0d --- /dev/null +++ b/android/Omnivore/app/src/main/graphql/DeleteLinkedItem.graphql @@ -0,0 +1,12 @@ +mutation SetBookmarkArticle($input: SetBookmarkArticleInput!) { + setBookmarkArticle(input: $input) { + ... on SetBookmarkArticleSuccess { + bookmarkedArticle { + id + } + } + ... on SetBookmarkArticleError { + errorCodes + } + } +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/LinkedItemMutations.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/LinkedItemMutations.kt new file mode 100644 index 000000000..b2ee7a0b2 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/LinkedItemMutations.kt @@ -0,0 +1,26 @@ +package app.omnivore.omnivore.networking + +import app.omnivore.omnivore.graphql.generated.SetBookmarkArticleMutation +import app.omnivore.omnivore.graphql.generated.SetLinkArchivedMutation +import app.omnivore.omnivore.graphql.generated.type.ArchiveLinkInput +import app.omnivore.omnivore.graphql.generated.type.SetBookmarkArticleInput + +suspend fun Networker.deleteLinkedItem(itemID: String): Boolean { + val input = SetBookmarkArticleInput(itemID, false) + val result = authenticatedApolloClient().mutation(SetBookmarkArticleMutation(input)).execute() + return result.data?.setBookmarkArticle?.onSetBookmarkArticleSuccess?.bookmarkedArticle?.id != null +} + +suspend fun Networker.archiveLinkedItem(itemID: String): Boolean { + return updateArchiveStatusLinkedItem(itemID, true) +} + +suspend fun Networker.unarchiveLinkedItem(itemID: String): Boolean { + return updateArchiveStatusLinkedItem(itemID, false) +} + +private suspend fun Networker.updateArchiveStatusLinkedItem(itemID: String, setAsArchived: Boolean): Boolean { + val input = ArchiveLinkInput(setAsArchived, itemID) + val result = authenticatedApolloClient().mutation(SetLinkArchivedMutation(input)).execute() + return result.data?.setLinkArchived?.onArchiveLinkSuccess?.linkId != null +} From e5fec25fd5eefb164200121180c410baa02e4cf7 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Mon, 12 Dec 2022 16:36:30 -0800 Subject: [PATCH 06/18] add context menu to web reader (but keep it disabled for now) --- .../app/omnivore/omnivore/ui/home/HomeView.kt | 2 +- .../omnivore/ui/home/HomeViewModel.kt | 1 - .../LinkedItemCard.kt | 44 +++------------- .../linkedItemViews/LinkedItemContextMenu.kt | 52 +++++++++++++++++++ .../omnivore/omnivore/ui/reader/WebReader.kt | 18 ++++++- .../omnivore/ui/reader/WebReaderViewModel.kt | 33 ++++++++++++ 6 files changed, 111 insertions(+), 39 deletions(-) rename android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/{home => linkedItemViews}/LinkedItemCard.kt (69%) create mode 100644 android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/linkedItemViews/LinkedItemContextMenu.kt diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt index 9389ccb0f..c004fae7b 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt @@ -18,10 +18,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController import app.omnivore.omnivore.Routes import app.omnivore.omnivore.models.LinkedItem +import app.omnivore.omnivore.ui.linkedItemViews.LinkedItemCard import app.omnivore.omnivore.ui.reader.PDFReaderActivity import kotlinx.coroutines.flow.distinctUntilChanged diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt index 5c29a8127..199e8c183 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt @@ -1,6 +1,5 @@ package app.omnivore.omnivore.ui.home -import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/LinkedItemCard.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/linkedItemViews/LinkedItemCard.kt similarity index 69% rename from android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/LinkedItemCard.kt rename to android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/linkedItemViews/LinkedItemCard.kt index f6667b1b1..05bdb58d3 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/LinkedItemCard.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/linkedItemViews/LinkedItemCard.kt @@ -1,11 +1,8 @@ -package app.omnivore.omnivore.ui.home +package app.omnivore.omnivore.ui.linkedItemViews import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Delete -import androidx.compose.material.icons.outlined.List import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -16,6 +13,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import app.omnivore.omnivore.models.LinkedItem +import app.omnivore.omnivore.ui.home.LinkedItemAction import coil.compose.rememberAsyncImagePainter @OptIn(ExperimentalFoundationApi::class) @@ -82,37 +80,11 @@ fun LinkedItemCard(item: LinkedItem, onClickHandler: () -> Unit, actionHandler: Divider(color = MaterialTheme.colorScheme.outlineVariant, thickness = 1.dp) - DropdownMenu( - expanded = isMenuExpanded, - onDismissRequest = { isMenuExpanded = false } - ) { - DropdownMenuItem( - text = { Text(if (item.isArchived) "Unarchive" else "Archive") }, - onClick = { - val action = if (item.isArchived) LinkedItemAction.Unarchive else LinkedItemAction.Archive - actionHandler(action) - isMenuExpanded = false - }, - leadingIcon = { - Icon( - Icons.Outlined.List, // TODO: use more appropriate icon - contentDescription = null - ) - } - ) - DropdownMenuItem( - text = { Text("Remove Item") }, - onClick = { - actionHandler(LinkedItemAction.Delete) - isMenuExpanded = false - }, - leadingIcon = { - Icon( - Icons.Outlined.Delete, - contentDescription = null - ) - } - ) - } + LinkedItemContextMenu( + isExpanded = isMenuExpanded, + isArchived = item.isArchived, + onDismiss = { isMenuExpanded = false }, + actionHandler = actionHandler + ) } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/linkedItemViews/LinkedItemContextMenu.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/linkedItemViews/LinkedItemContextMenu.kt new file mode 100644 index 000000000..ea11a4038 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/linkedItemViews/LinkedItemContextMenu.kt @@ -0,0 +1,52 @@ +package app.omnivore.omnivore.ui.linkedItemViews + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.icons.outlined.List +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import app.omnivore.omnivore.ui.home.LinkedItemAction + +@Composable +fun LinkedItemContextMenu( + isExpanded: Boolean, + isArchived: Boolean, + onDismiss: () -> Unit, + actionHandler: (LinkedItemAction) -> Unit +) { + DropdownMenu( + expanded = isExpanded, + onDismissRequest = onDismiss + ) { + DropdownMenuItem( + text = { Text(if (isArchived) "Unarchive" else "Archive") }, + onClick = { + val action = if (isArchived) LinkedItemAction.Unarchive else LinkedItemAction.Archive + actionHandler(action) + onDismiss() + }, + leadingIcon = { + Icon( + Icons.Outlined.List, // TODO: use more appropriate icon + contentDescription = null + ) + } + ) + DropdownMenuItem( + text = { Text("Remove Item") }, + onClick = { + actionHandler(LinkedItemAction.Delete) + onDismiss() + }, + leadingIcon = { + Icon( + Icons.Outlined.Delete, + contentDescription = null + ) + } + ) + } +} 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 af113b73e..debcfefbb 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 @@ -14,6 +14,7 @@ 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.* import androidx.compose.runtime.* @@ -28,6 +29,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat.getSystemService import app.omnivore.omnivore.R +import app.omnivore.omnivore.ui.linkedItemViews.LinkedItemContextMenu import com.google.gson.Gson import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -39,6 +41,7 @@ import kotlin.math.roundToInt @Composable fun WebReaderLoadingContainer(slug: String, webReaderViewModel: WebReaderViewModel) { + var isMenuExpanded by remember { mutableStateOf(false) } var showWebPreferencesDialog by remember { mutableStateOf(false ) } val webReaderParams: WebReaderParams? by webReaderViewModel.webReaderParamsLiveData.observeAsState(null) @@ -94,12 +97,25 @@ fun WebReaderLoadingContainer(slug: String, webReaderViewModel: WebReaderViewMod 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, + imageVector = Icons.Filled.Settings, // TODO: set a better icon contentDescription = null ) } + LinkedItemContextMenu( + isExpanded = isMenuExpanded, + isArchived = webReaderParams!!.item.isArchived, + onDismiss = { isMenuExpanded = false }, + actionHandler = { webReaderViewModel.handleLinkedItemAction(webReaderParams!!.item.id, it) } + ) } ) 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 acdc45403..3bc3e0084 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 @@ -10,8 +10,11 @@ import app.omnivore.omnivore.DatastoreKeys import app.omnivore.omnivore.DatastoreRepository import app.omnivore.omnivore.models.LinkedItem import app.omnivore.omnivore.networking.* +import app.omnivore.omnivore.ui.home.LinkedItemAction import com.google.gson.Gson import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import java.util.* @@ -62,6 +65,36 @@ class WebReaderViewModel @Inject constructor( } } + fun handleLinkedItemAction(itemID: String, action: LinkedItemAction) { + when (action) { + LinkedItemAction.Delete -> { + viewModelScope.launch { + networker.deleteLinkedItem(itemID) + popToHomeView(itemID) + } + } + LinkedItemAction.Archive -> { + viewModelScope.launch { + networker.archiveLinkedItem(itemID) + popToHomeView(itemID) + } + } + LinkedItemAction.Unarchive -> { + viewModelScope.launch { + networker.unarchiveLinkedItem(itemID) + popToHomeView(itemID) + } + } + } + } + + private fun popToHomeView(itemID: String) { + CoroutineScope(Dispatchers.Main).launch { + // TODO: pop to home + Log.d("maxx", "should pop to home and remove item with ID: $itemID") + } + } + fun handleIncomingWebMessage(actionID: String, jsonString: String) { when (actionID) { "createHighlight" -> { From a0e42986b4bce11d4ba6949a0b1cdbd372a72c7b Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 13 Dec 2022 09:04:57 -0800 Subject: [PATCH 07/18] add segment analytics to android --- android/Omnivore/app/build.gradle | 2 ++ .../Omnivore/app/src/main/AndroidManifest.xml | 1 + .../main/java/app/omnivore/omnivore/AppModule.kt | 16 ++++++++++++++++ .../omnivore/omnivore/ui/home/HomeViewModel.kt | 5 ++++- android/Omnivore/secrets.xml | 1 + 5 files changed, 24 insertions(+), 1 deletion(-) diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index e4c881079..01fbb4b69 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -140,6 +140,8 @@ dependencies { implementation 'com.google.code.gson:gson:2.8.9' implementation 'com.pspdfkit:pspdfkit:8.4.1' + + implementation 'com.segment.analytics.kotlin:android:1.10.0' } apollo { diff --git a/android/Omnivore/app/src/main/AndroidManifest.xml b/android/Omnivore/app/src/main/AndroidManifest.xml index 0bbedccff..a39d804d3 100644 --- a/android/Omnivore/app/src/main/AndroidManifest.xml +++ b/android/Omnivore/app/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ package="app.omnivore.omnivore"> + = listOf() diff --git a/android/Omnivore/secrets.xml b/android/Omnivore/secrets.xml index 89a05bdea..a4729835e 100644 --- a/android/Omnivore/secrets.xml +++ b/android/Omnivore/secrets.xml @@ -1,4 +1,5 @@ unset + unset \ No newline at end of file From 17edcc436ac15a4836118d1a16978acd2f008933 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 13 Dec 2022 14:48:43 -0800 Subject: [PATCH 08/18] add note option at time of highlight --- .../src/main/java/app/omnivore/omnivore/AppModule.kt | 1 + .../java/app/omnivore/omnivore/ui/reader/WebReader.kt | 2 +- .../app/src/main/res/menu/pdf_text_selection_menu.xml | 10 ++++++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/AppModule.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/AppModule.kt index 246dcdd96..7acc7aad2 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/AppModule.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/AppModule.kt @@ -32,6 +32,7 @@ object AppModule { ): Analytics { val writeKey = app.getString(R.string.segment_write_key) + // TODO: abstract analytics to custom class return Analytics(writeKey, app.applicationContext) { trackApplicationLifecycleEvents = true application = app.applicationContext 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 debcfefbb..7b4417258 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 @@ -270,7 +270,7 @@ class OmnivoreWebView(context: Context) : WebView(context) { // Called when the user selects a contextual menu item override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { return when (item.itemId) { - R.id.annotateHighlight -> { + R.id.annotateHighlight, R.id.annotate -> { val script = "var event = new Event('annotate');document.dispatchEvent(event);" evaluateJavascript(script) { mode.finish() diff --git a/android/Omnivore/app/src/main/res/menu/pdf_text_selection_menu.xml b/android/Omnivore/app/src/main/res/menu/pdf_text_selection_menu.xml index 37f081f74..3e0b9a8c0 100644 --- a/android/Omnivore/app/src/main/res/menu/pdf_text_selection_menu.xml +++ b/android/Omnivore/app/src/main/res/menu/pdf_text_selection_menu.xml @@ -1,6 +1,12 @@ + + + From 80e039dca48635ad330afd6678639440d797a0d4 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 13 Dec 2022 15:49:14 -0800 Subject: [PATCH 09/18] implement note+highlight on pdf viewer in android --- .../omnivore/omnivore/ui/reader/PDFReader.kt | 25 +++++++++++++------ .../omnivore/ui/reader/PDFReaderViewModel.kt | 24 ++++++++++++------ .../main/res/menu/pdf_text_selection_menu.xml | 21 ---------------- 3 files changed, 33 insertions(+), 37 deletions(-) delete mode 100644 android/Omnivore/app/src/main/res/menu/pdf_text_selection_menu.xml diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReader.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReader.kt index bf9565765..65c71ad36 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReader.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReader.kt @@ -322,12 +322,13 @@ class PDFReaderActivity: AppCompatActivity(), DocumentListener, TextSelectionMan p0.dismiss() return@OnPopupToolbarItemClickedListener true } -// 2 -> { -// Log.d("pdf", "user selected annotate action") + 2 -> { + Log.d("pdf", "user selected annotate action") + showAnnotationView("") // textSelectionController?.textSelection = null -// p0.dismiss() -// return@OnPopupToolbarItemClickedListener true -// } + p0.dismiss() + return@OnPopupToolbarItemClickedListener true + } 3 -> { val text = textSelectionController?.textSelection?.text ?: "" val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager @@ -349,7 +350,7 @@ class PDFReaderActivity: AppCompatActivity(), DocumentListener, TextSelectionMan p0.menuItems = listOf( PopupToolbarMenuItem(1, R.string.pdf_highlight_menu_action), -// PopupToolbarMenuItem(2, R.string.annotate_menu_action), + PopupToolbarMenuItem(2, R.string.annotate_menu_action), PopupToolbarMenuItem(3, R.string.pdf_highlight_copy), ) } @@ -432,6 +433,7 @@ class PDFReaderActivity: AppCompatActivity(), DocumentListener, TextSelectionMan actionMode = null clickedHighlight = null clickedHighlightPosition = null + textSelectionController?.textSelection = null viewModel.annotationUnderNoteEdit = null } @@ -443,8 +445,15 @@ class PDFReaderActivity: AppCompatActivity(), DocumentListener, TextSelectionMan val annotationEditFragment = AnnotationEditFragment() annotationEditFragment.configure( onSave = { newNote -> - clickedHighlight?.let { highlight -> - viewModel.updateHighlightNote(highlight, newNote) + if (clickedHighlight != null) { + viewModel.updateHighlightNote(clickedHighlight!!, newNote) + } else { + pendingHighlightAnnotation?.let { annotation -> + val quote = textSelectionController?.textSelection?.text ?: "" + fragment.addAnnotationToPage(annotation, false) { + viewModel.syncHighlightUpdates(annotation, quote, listOf(), newNote) + } + } } resetHighlightTap() }, diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReaderViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReaderViewModel.kt index 4f38ae3ca..620e333b0 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReaderViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReaderViewModel.kt @@ -101,7 +101,7 @@ class PDFReaderViewModel @Inject constructor( } } - fun syncHighlightUpdates(newAnnotation: Annotation, quote: String, overlapIds: List) { + fun syncHighlightUpdates(newAnnotation: Annotation, quote: String, overlapIds: List, note: String? = null) { val itemID = pdfReaderParamsLiveData.value?.item?.id ?: return val highlightID = UUID.randomUUID().toString() val shortID = UUID.randomUUID().toString().replace("-","").substring(0,8) @@ -130,7 +130,7 @@ class PDFReaderViewModel @Inject constructor( } } else { val createHighlightInput = CreateHighlightInput( - annotation = Optional.presentIfNotNull(null), + annotation = Optional.presentIfNotNull(note), articleId = itemID, id = highlightID, patch = newAnnotation.toInstantJson(), @@ -141,17 +141,16 @@ class PDFReaderViewModel @Inject constructor( viewModelScope.launch { networker.createHighlight(createHighlightInput) } + + if (note != null) { + storeUpdatedNoteLocally(newAnnotation, note!!) + } } } fun updateHighlightNote(annotation: Annotation, note: String) { // Save the updated note locally - val omnivoreHighlight = annotation.customData?.get("omnivoreHighlight") as? JSONObject - omnivoreHighlight?.put("editedNote", note) - omnivoreHighlight?.let { - Log.d("pdf", "setting custom data: $omnivoreHighlight") - annotation.customData = JSONObject().put("omnivoreHighlight", it) - } + storeUpdatedNoteLocally(annotation, note) // Sync update with data service viewModelScope.launch { @@ -165,6 +164,15 @@ class PDFReaderViewModel @Inject constructor( } } + private fun storeUpdatedNoteLocally(annotation: Annotation, note: String) { + val omnivoreHighlight = annotation.customData?.get("omnivoreHighlight") as? JSONObject + omnivoreHighlight?.put("editedNote", note) + omnivoreHighlight?.let { + Log.d("pdf", "setting custom data: $omnivoreHighlight") + annotation.customData = JSONObject().put("omnivoreHighlight", it) + } + } + fun deleteHighlight(annotation: Annotation) { val highlightID = pluckHighlightID(annotation) ?: return viewModelScope.launch { diff --git a/android/Omnivore/app/src/main/res/menu/pdf_text_selection_menu.xml b/android/Omnivore/app/src/main/res/menu/pdf_text_selection_menu.xml deleted file mode 100644 index 3e0b9a8c0..000000000 --- a/android/Omnivore/app/src/main/res/menu/pdf_text_selection_menu.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - From df7242e5dd27acfbb9af6c0bf8b4f1bcea94b378 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 13 Dec 2022 17:13:18 -0800 Subject: [PATCH 10/18] create event tracker class to inject into view models --- .../main/java/app/omnivore/omnivore/AppModule.kt | 15 +-------------- .../omnivore/omnivore/ui/auth/LoginViewModel.kt | 7 ++++++- .../omnivore/omnivore/ui/home/HomeViewModel.kt | 1 - .../app/omnivore/omnivore/ui/root/RootView.kt | 7 +++++++ 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/AppModule.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/AppModule.kt index 7acc7aad2..ef526af60 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/AppModule.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/AppModule.kt @@ -8,8 +8,6 @@ import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import javax.inject.Singleton -import com.segment.analytics.kotlin.android.Analytics -import com.segment.analytics.kotlin.core.* @Module @InstallIn(SingletonComponent::class) @@ -27,16 +25,5 @@ object AppModule { @Singleton @Provides - fun provideAnalytics( - @ApplicationContext app: Context - ): Analytics { - val writeKey = app.getString(R.string.segment_write_key) - - // TODO: abstract analytics to custom class - return Analytics(writeKey, app.applicationContext) { - trackApplicationLifecycleEvents = true - application = app.applicationContext - useLifecycleObserver = true - } - } + fun provideAnalytics(@ApplicationContext app: Context) = EventTracker(app) } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt index c11f32a6f..84c29d34f 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt @@ -36,7 +36,8 @@ data class PendingEmailUserCreds( @HiltViewModel class LoginViewModel @Inject constructor( - private val datastoreRepo: DatastoreRepository + private val datastoreRepo: DatastoreRepository, + private val eventTracker: EventTracker ): ViewModel() { private var validateUsernameJob: Job? = null @@ -90,6 +91,10 @@ class LoginViewModel @Inject constructor( showSocialLogin() } + fun registerUser() { + // TODO: eventTracker.registerUser() + } + private fun resetState() { validateUsernameJob = null isLoading = false diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt index b2a046dfe..55969a73b 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt @@ -19,7 +19,6 @@ import javax.inject.Inject @HiltViewModel class HomeViewModel @Inject constructor( private val networker: Networker, - private val analytics: com.segment.analytics.kotlin.core.Analytics ): ViewModel() { private var cursor: String? = null private var items: List = listOf() diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt index c669be8b6..b0abce8d2 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt @@ -53,6 +53,13 @@ fun RootView( } else { WelcomeScreen(viewModel = loginViewModel) } + + DisposableEffect(hasAuthToken) { + if (hasAuthToken) { + loginViewModel.registerUser() + } + onDispose {} + } } } From a08acb18fa6ed297b51e45a44b92d84a340fdf5a Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 13 Dec 2022 17:13:30 -0800 Subject: [PATCH 11/18] add eventtracker file --- .../app/omnivore/omnivore/EventTracker.kt | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 android/Omnivore/app/src/main/java/app/omnivore/omnivore/EventTracker.kt diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/EventTracker.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/EventTracker.kt new file mode 100644 index 000000000..ccc797347 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/EventTracker.kt @@ -0,0 +1,33 @@ +package app.omnivore.omnivore + +import android.content.Context +import com.segment.analytics.kotlin.android.Analytics +import com.segment.analytics.kotlin.core.* +import org.json.JSONObject +import javax.inject.Inject + +class EventTracker @Inject constructor(val app: Context) { + val segmentAnalytics: Analytics + + init { + val writeKey = app.getString(R.string.segment_write_key) + + segmentAnalytics = Analytics(writeKey, app.applicationContext) { + trackApplicationLifecycleEvents = true + application = app.applicationContext + useLifecycleObserver = true + } + } + + fun registerUser(userID: String) { + segmentAnalytics.identify(userID) + } + + fun debugMessage(message: String) { + track(message) + } + + fun track(eventName: String, jsonObject: JSONObject = JSONObject()) { + segmentAnalytics.track(eventName, jsonObject) + } +} From faf666d091d7d11688635a52a3cc4852ad3074c5 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 13 Dec 2022 17:34:25 -0800 Subject: [PATCH 12/18] add intercom to android app --- android/Omnivore/app/build.gradle | 1 + .../java/app/omnivore/omnivore/OmnivoreApplication.kt | 10 ++++++++++ android/Omnivore/secrets.xml | 2 ++ 3 files changed, 13 insertions(+) diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index 01fbb4b69..2bd32c548 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -142,6 +142,7 @@ dependencies { implementation 'com.pspdfkit:pspdfkit:8.4.1' implementation 'com.segment.analytics.kotlin:android:1.10.0' + implementation 'io.intercom.android:intercom-sdk-base:14.0.0' } apollo { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/OmnivoreApplication.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/OmnivoreApplication.kt index 7e339793a..3add9d375 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/OmnivoreApplication.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/OmnivoreApplication.kt @@ -2,7 +2,17 @@ package app.omnivore.omnivore import android.app.Application import dagger.hilt.android.HiltAndroidApp +import io.intercom.android.sdk.Intercom @HiltAndroidApp class OmnivoreApplication: Application() { + override fun onCreate() { + super.onCreate() + + Intercom.initialize( + this, + this.getString(R.string.intercom_api_key), + this.getString(R.string.intercom_app_id) + ) + } } diff --git a/android/Omnivore/secrets.xml b/android/Omnivore/secrets.xml index a4729835e..2641c6d57 100644 --- a/android/Omnivore/secrets.xml +++ b/android/Omnivore/secrets.xml @@ -2,4 +2,6 @@ unset unset + unset + unset \ No newline at end of file From 3eb11b7a0471b180533c05df5e281fad5d3c33e0 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 13 Dec 2022 18:29:49 -0800 Subject: [PATCH 13/18] fetch viewer so user id can be passed to segment --- .../app/src/main/graphql/Viewer.graphql | 13 ++++++++++++ .../app/omnivore/omnivore/models/Viewer.kt | 9 +++++++++ .../omnivore/networking/ViewerQuery.kt | 20 +++++++++++++++++++ .../omnivore/ui/auth/LoginViewModel.kt | 12 +++++++++-- 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 android/Omnivore/app/src/main/graphql/Viewer.graphql create mode 100644 android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/Viewer.kt create mode 100644 android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/ViewerQuery.kt diff --git a/android/Omnivore/app/src/main/graphql/Viewer.graphql b/android/Omnivore/app/src/main/graphql/Viewer.graphql new file mode 100644 index 000000000..9bfe75182 --- /dev/null +++ b/android/Omnivore/app/src/main/graphql/Viewer.graphql @@ -0,0 +1,13 @@ +query Viewer { + me { + id + name + isFullUser + profile { + id + username + pictureUrl + bio + } + } +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/Viewer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/Viewer.kt new file mode 100644 index 000000000..505068dd4 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/Viewer.kt @@ -0,0 +1,9 @@ +package app.omnivore.omnivore.models + +data class Viewer( + val id: String, + val name: String, + val username: String, + val pictureUrl: String?, +) + diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/ViewerQuery.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/ViewerQuery.kt new file mode 100644 index 000000000..f187362ee --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/ViewerQuery.kt @@ -0,0 +1,20 @@ +package app.omnivore.omnivore.networking + +import app.omnivore.omnivore.graphql.generated.ViewerQuery +import app.omnivore.omnivore.models.Viewer + +suspend fun Networker.viewer(): Viewer? { + val result = authenticatedApolloClient().query(ViewerQuery()).execute() + val me = result.data?.me + + return if (me != null) { + Viewer( + id = me.id, + name = me.name, + username = me.profile.username, + pictureUrl = me.profile.pictureUrl + ) + } else { + null + } +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt index 84c29d34f..a337bcbaf 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt @@ -8,6 +8,8 @@ import androidx.lifecycle.viewmodel.compose.viewModel import app.omnivore.omnivore.* import app.omnivore.omnivore.graphql.generated.SearchQuery import app.omnivore.omnivore.graphql.generated.ValidateUsernameQuery +import app.omnivore.omnivore.networking.Networker +import app.omnivore.omnivore.networking.viewer import com.apollographql.apollo3.ApolloClient import com.apollographql.apollo3.api.Optional import com.google.android.gms.auth.api.signin.GoogleSignInAccount @@ -37,7 +39,8 @@ data class PendingEmailUserCreds( @HiltViewModel class LoginViewModel @Inject constructor( private val datastoreRepo: DatastoreRepository, - private val eventTracker: EventTracker + private val eventTracker: EventTracker, + private val networker: Networker ): ViewModel() { private var validateUsernameJob: Job? = null @@ -92,7 +95,12 @@ class LoginViewModel @Inject constructor( } fun registerUser() { - // TODO: eventTracker.registerUser() + viewModelScope.launch { + val viewer = networker.viewer() + viewer?.let { + eventTracker.registerUser(viewer.id) + } + } } private fun resetState() { From 4ba02a64b1973b53bc8cb8ea3d52df6bf879e228 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 13 Dec 2022 18:39:37 -0800 Subject: [PATCH 14/18] register user with intercom --- .../app/src/main/java/app/omnivore/omnivore/EventTracker.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/EventTracker.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/EventTracker.kt index ccc797347..953800e2e 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/EventTracker.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/EventTracker.kt @@ -3,6 +3,8 @@ package app.omnivore.omnivore import android.content.Context import com.segment.analytics.kotlin.android.Analytics import com.segment.analytics.kotlin.core.* +import io.intercom.android.sdk.Intercom +import io.intercom.android.sdk.identity.Registration import org.json.JSONObject import javax.inject.Inject @@ -21,6 +23,7 @@ class EventTracker @Inject constructor(val app: Context) { fun registerUser(userID: String) { segmentAnalytics.identify(userID) + Intercom.client().loginIdentifiedUser(Registration.create().withUserId(userID)) } fun debugMessage(message: String) { From 40fec326749c1da9f8350c7dcb848a9a14b9664a Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 13 Dec 2022 18:49:04 -0800 Subject: [PATCH 15/18] add intercom chat --- .../java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt | 2 ++ .../app/omnivore/omnivore/ui/settings/SettingsContent.kt | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt index a337bcbaf..d42ef3a8d 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt @@ -16,6 +16,7 @@ import com.google.android.gms.auth.api.signin.GoogleSignInAccount import com.google.android.gms.common.api.ApiException import com.google.android.gms.tasks.Task import dagger.hilt.android.lifecycle.HiltViewModel +import io.intercom.android.sdk.Intercom import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.distinctUntilChanged @@ -268,6 +269,7 @@ class LoginViewModel @Inject constructor( fun logout() { viewModelScope.launch { datastoreRepo.clear() + Intercom.client().logout() } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/SettingsContent.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/SettingsContent.kt index be6a3db98..2c47411f4 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/SettingsContent.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/SettingsContent.kt @@ -16,6 +16,8 @@ import app.omnivore.omnivore.Routes import app.omnivore.omnivore.ui.auth.LoginViewModel import com.google.android.gms.auth.api.signin.GoogleSignIn import com.google.android.gms.auth.api.signin.GoogleSignInOptions +import io.intercom.android.sdk.Intercom +import io.intercom.android.sdk.IntercomSpace @OptIn(ExperimentalMaterial3Api::class) @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @@ -51,6 +53,11 @@ fun SettingsView( .padding(horizontal = 16.dp) ) { LogoutButton { loginViewModel.logout() } + Button(onClick = { + Intercom.client().present(space = IntercomSpace.Messages) + }) { + Text(text = "Open Help Center") + } } } } From abc7d9ce763d6ae7ea406b979385894676528538 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 13 Dec 2022 18:50:25 -0800 Subject: [PATCH 16/18] bump android version to 0.0.11 --- android/Omnivore/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index 2bd32c548..9f5c869e9 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -18,7 +18,7 @@ android { minSdk 23 targetSdk 33 versionCode 9 - versionName "0.0.10" + versionName "0.0.11" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From 6a98e732ea14271cf3013469009845a99a06a8c8 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 13 Dec 2022 19:05:06 -0800 Subject: [PATCH 17/18] add add id permission to manifest --- android/Omnivore/app/build.gradle | 4 ++-- android/Omnivore/app/src/main/AndroidManifest.xml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index 9f5c869e9..e5c7c3011 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -17,8 +17,8 @@ android { applicationId "app.omnivore.omnivore" minSdk 23 targetSdk 33 - versionCode 9 - versionName "0.0.11" + versionCode 12 + versionName "0.0.12" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/android/Omnivore/app/src/main/AndroidManifest.xml b/android/Omnivore/app/src/main/AndroidManifest.xml index a39d804d3..86233b4a7 100644 --- a/android/Omnivore/app/src/main/AndroidManifest.xml +++ b/android/Omnivore/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ + Date: Tue, 13 Dec 2022 20:57:38 -0800 Subject: [PATCH 18/18] bump android build number to 15 --- android/Omnivore/app/build.gradle | 4 ++-- android/Omnivore/app/src/main/AndroidManifest.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index e5c7c3011..af6df39aa 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -17,8 +17,8 @@ android { applicationId "app.omnivore.omnivore" minSdk 23 targetSdk 33 - versionCode 12 - versionName "0.0.12" + versionCode 15 + versionName "0.0.15" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/android/Omnivore/app/src/main/AndroidManifest.xml b/android/Omnivore/app/src/main/AndroidManifest.xml index 86233b4a7..a78d2d2f0 100644 --- a/android/Omnivore/app/src/main/AndroidManifest.xml +++ b/android/Omnivore/app/src/main/AndroidManifest.xml @@ -5,7 +5,7 @@ - +