Skip to content

Commit

Permalink
Add Explore Feed as an Entry point for Places (#4824)
Browse files Browse the repository at this point in the history
* Add Explore Feed as an Entry point for Places

* Add card view

* Add w logo with circle

* Update asset

* Build places card view

* Card client

* Build card behavior

* Update client

* Show card properly

* Update card content and pick places from 1 to 10

* Add to dev preference

* Update card for primary language only

* Update click listener

* Adjust margin

* Update drawable and use proper newIntent

* Update pop up menu logic

* Add location comparasion

* Refresh feed after 500L

* Update card title

* Only refresh when granting permission

* Dont refresh the feed

* Fix lint

* Code review comments

---------

Co-authored-by: Dmitry Brant <[email protected]>
  • Loading branch information
cooltey and dbrant authored Aug 19, 2024
1 parent 5193fcf commit fa4ab72
Show file tree
Hide file tree
Showing 15 changed files with 518 additions and 1 deletion.
6 changes: 6 additions & 0 deletions app/src/main/java/org/wikipedia/feed/FeedContentType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.wikipedia.feed.aggregated.AggregatedFeedContentClient
import org.wikipedia.feed.becauseyouread.BecauseYouReadClient
import org.wikipedia.feed.dataclient.FeedClient
import org.wikipedia.feed.mainpage.MainPageClient
import org.wikipedia.feed.places.PlacesFeedClient
import org.wikipedia.feed.random.RandomClient
import org.wikipedia.feed.suggestededits.SuggestedEditsFeedClient
import org.wikipedia.model.EnumCode
Expand All @@ -32,6 +33,11 @@ enum class FeedContentType(private val code: Int,
return if (isEnabled) AggregatedFeedContentClient.TopReadArticles(coroutineScope, aggregatedClient) else null
}
},
PLACES(11, R.string.places_title, R.string.feed_item_type_places, false) {
override fun newClient(coroutineScope: CoroutineScope, aggregatedClient: AggregatedFeedContentClient, age: Int): FeedClient? {
return if (isEnabled) PlacesFeedClient(coroutineScope) else null
}
},
FEATURED_IMAGE(7, R.string.view_featured_image_card_title, R.string.feed_item_type_featured_image, false) {
override fun newClient(coroutineScope: CoroutineScope, aggregatedClient: AggregatedFeedContentClient, age: Int): FeedClient? {
return if (isEnabled) AggregatedFeedContentClient.FeaturedImage(coroutineScope, aggregatedClient) else null
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/java/org/wikipedia/feed/FeedCoordinatorBase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.wikipedia.feed.model.CardType
import org.wikipedia.feed.news.NewsCard
import org.wikipedia.feed.offline.OfflineCard
import org.wikipedia.feed.onthisday.OnThisDayCard
import org.wikipedia.feed.places.PlacesFeedClient
import org.wikipedia.feed.progress.ProgressCard
import org.wikipedia.feed.suggestededits.SuggestedEditsFeedClient
import org.wikipedia.feed.topread.TopReadListCard
Expand Down Expand Up @@ -101,6 +102,10 @@ abstract class FeedCoordinatorBase(private val context: Context) {
FeedContentType.MAIN_PAGE.isEnabled = false
FeedContentType.saveState()
}
card.type() == CardType.PLACES -> {
FeedContentType.PLACES.isEnabled = false
FeedContentType.saveState()
}
else -> {
addHiddenCard(card)
}
Expand All @@ -120,6 +125,10 @@ abstract class FeedCoordinatorBase(private val context: Context) {
FeedContentType.MAIN_PAGE.isEnabled = true
FeedContentType.saveState()
}
card.type() == CardType.PLACES -> {
FeedContentType.PLACES.isEnabled = true
FeedContentType.saveState()
}
else -> unHideCard(card)
}
insertCard(card, position)
Expand Down Expand Up @@ -266,6 +275,7 @@ abstract class FeedCoordinatorBase(private val context: Context) {
return pendingClient is SuggestedEditsFeedClient ||
pendingClient is AnnouncementClient ||
pendingClient is BecauseYouReadClient ||
pendingClient is PlacesFeedClient ||
pendingClient == null
}

Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/org/wikipedia/feed/model/CardType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import org.wikipedia.feed.mainpage.MainPageCardView
import org.wikipedia.feed.news.NewsCardView
import org.wikipedia.feed.offline.OfflineCardView
import org.wikipedia.feed.onthisday.OnThisDayCardView
import org.wikipedia.feed.places.PlacesCardView
import org.wikipedia.feed.progress.ProgressCardView
import org.wikipedia.feed.random.RandomCardView
import org.wikipedia.feed.searchbar.SearchCardView
Expand Down Expand Up @@ -103,6 +104,11 @@ enum class CardType constructor(private val code: Int,
return AccessibilityCardView(ctx)
}
},
PLACES(23, FeedContentType.PLACES) {
override fun newView(ctx: Context): FeedCardView<*> {
return PlacesCardView(ctx)
}
},
// TODO: refactor this item when the new Modern Event Platform is finished.
ARTICLE_ANNOUNCEMENT(96) {
override fun newView(ctx: Context): FeedCardView<*> {
Expand Down
30 changes: 30 additions & 0 deletions app/src/main/java/org/wikipedia/feed/places/PlacesCard.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.wikipedia.feed.places

import org.wikipedia.R
import org.wikipedia.WikipediaApp
import org.wikipedia.dataclient.WikiSite
import org.wikipedia.dataclient.page.NearbyPage
import org.wikipedia.feed.model.CardType
import org.wikipedia.feed.model.WikiSiteCard
import org.wikipedia.util.DateUtil

class PlacesCard(wiki: WikiSite,
val age: Int,
val nearbyPage: NearbyPage? = null) : WikiSiteCard(wiki) {

override fun type(): CardType {
return CardType.PLACES
}

override fun title(): String {
return WikipediaApp.instance.getString(R.string.places_card_title)
}

override fun subtitle(): String {
return DateUtil.getFeedCardDateString(age)
}

fun footerActionText(): String {
return WikipediaApp.instance.getString(R.string.places_card_action)
}
}
114 changes: 114 additions & 0 deletions app/src/main/java/org/wikipedia/feed/places/PlacesCardView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package org.wikipedia.feed.places

import android.content.Context
import android.location.Location
import android.view.LayoutInflater
import androidx.core.view.isVisible
import org.wikipedia.R
import org.wikipedia.databinding.ViewPlacesCardBinding
import org.wikipedia.feed.view.CardFooterView
import org.wikipedia.feed.view.DefaultFeedCardView
import org.wikipedia.feed.view.FeedAdapter
import org.wikipedia.history.HistoryEntry
import org.wikipedia.page.PageTitle
import org.wikipedia.places.PlacesActivity
import org.wikipedia.readinglist.LongPressMenu
import org.wikipedia.readinglist.database.ReadingListPage
import org.wikipedia.settings.Prefs
import org.wikipedia.util.GeoUtil
import org.wikipedia.util.StringUtil
import org.wikipedia.views.ViewUtil
import java.util.Locale

class PlacesCardView(context: Context) : DefaultFeedCardView<PlacesCard>(context) {

private val binding = ViewPlacesCardBinding.inflate(LayoutInflater.from(context), this, true)

override var card: PlacesCard? = null
set(value) {
field = value
value?.let {
header(it)
footer(it)
updateContents(it)
}
}

override var callback: FeedAdapter.Callback? = null
set(value) {
field = value
binding.cardHeader.setCallback(value)
}

private fun updateContents(card: PlacesCard) {
card.nearbyPage?.let {
binding.placesEnableLocationContainer.isVisible = false
binding.placesArticleContainer.isVisible = true
binding.placesCardTitle.text = StringUtil.fromHtml(it.pageTitle.displayText)
binding.placesCardDescription.text = StringUtil.fromHtml(it.pageTitle.description)
binding.placesCardDescription.isVisible = !it.pageTitle.description.isNullOrEmpty()
it.pageTitle.thumbUrl?.let { url ->
ViewUtil.loadImage(binding.placesCardThumbnail, url, circleShape = true)
}
Prefs.placesLastLocationAndZoomLevel?.first?.let { location ->
if (GeoUtil.isSamePlace(location.latitude, it.location.latitude, location.longitude, it.location.longitude)) {
binding.placesCardDistance.text = context.getString(R.string.places_card_distance_unknown)
} else {
val distanceText = GeoUtil.getDistanceWithUnit(location, it.location, Locale.getDefault())
binding.placesCardDistance.text = context.getString(R.string.places_card_distance_suffix, distanceText)
}
}
binding.placesCardContainer.setOnClickListener { _ ->
goToPlaces(it.pageTitle, it.location)
}
binding.placesCardContainer.setOnLongClickListener { view ->
LongPressMenu(view, openPageInPlaces = true, location = it.location, callback = object : LongPressMenu.Callback {
override fun onOpenInPlaces(entry: HistoryEntry, location: Location) {
goToPlaces(entry.title, location)
}

override fun onOpenInNewTab(entry: HistoryEntry) {
callback?.onSelectPage(card, entry, true)
}

override fun onAddRequest(entry: HistoryEntry, addToDefault: Boolean) {
callback?.onAddPageToList(entry, addToDefault)
}

override fun onMoveRequest(page: ReadingListPage?, entry: HistoryEntry) {
callback?.onMovePageToList(page!!.listId, entry)
}
}).show(HistoryEntry(it.pageTitle, HistoryEntry.SOURCE_FEED_PLACES))

false
}
} ?: run {
binding.placesEnableLocationContainer.isVisible = true
binding.placesArticleContainer.isVisible = false
binding.placesCardContainer.setOnClickListener {
goToPlaces()
}
binding.placesEnableLocationButton.setOnClickListener {
goToPlaces()
}
}
}

private fun header(card: PlacesCard) {
binding.cardHeader.setTitle(card.title())
.setCard(card)
.setLangCode(null)
.setCallback(callback)
}

private fun footer(card: PlacesCard) {
binding.cardFooter.callback = CardFooterView.Callback {
goToPlaces()
}
binding.cardFooter.setFooterActionText(card.footerActionText(), card.wikiSite().languageCode)
}

private fun goToPlaces(pageTitle: PageTitle? = null, location: Location? = null) {
context.startActivity(PlacesActivity.newIntent(context, pageTitle, location))
}
}
53 changes: 53 additions & 0 deletions app/src/main/java/org/wikipedia/feed/places/PlacesFeedClient.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.wikipedia.feed.places

import android.content.Context
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.wikipedia.dataclient.ServiceFactory
import org.wikipedia.dataclient.WikiSite
import org.wikipedia.dataclient.page.NearbyPage
import org.wikipedia.feed.dataclient.FeedClient
import org.wikipedia.page.PageTitle
import org.wikipedia.places.PlacesFragment
import org.wikipedia.settings.Prefs
import org.wikipedia.util.GeoUtil
import org.wikipedia.util.ImageUrlUtil

class PlacesFeedClient(
private val coroutineScope: CoroutineScope
) : FeedClient {

private lateinit var cb: FeedClient.Callback
private var age: Int = 0
private var clientJob: Job? = null

override fun request(context: Context, wiki: WikiSite, age: Int, cb: FeedClient.Callback) {
this.age = age
this.cb = cb

Prefs.placesLastLocationAndZoomLevel?.let {
coroutineScope.launch(CoroutineExceptionHandler { _, throwable ->
cb.error(throwable)
}) {
val location = it.first
val response = ServiceFactory.get(wiki).getGeoSearch("${location.latitude}|${location.longitude}", 10000, 10, 10)
val lastPage = response.query?.pages.orEmpty()
.filter { it.coordinates != null && !GeoUtil.isSamePlace(location.latitude, it.coordinates[0].lat, location.longitude, it.coordinates[0].lon) }
.map {
NearbyPage(it.pageId, PageTitle(it.title, wiki,
if (it.thumbUrl().isNullOrEmpty()) null else ImageUrlUtil.getUrlForPreferredSize(it.thumbUrl()!!, PlacesFragment.THUMB_SIZE),
it.description, it.displayTitle(wiki.languageCode)), it.coordinates!![0].lat, it.coordinates[0].lon)
}[age % 10]
cb.success(listOf(PlacesCard(wiki, age, lastPage)))
}
} ?: run {
cb.success(listOf(PlacesCard(wiki, age)))
}
}

override fun cancel() {
clientJob?.cancel()
}
}
1 change: 1 addition & 0 deletions app/src/main/java/org/wikipedia/history/HistoryEntry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,6 @@ class HistoryEntry(
const val SOURCE_FILE_PAGE = 39
const val SOURCE_SINGLE_WEBVIEW = 40
const val SOURCE_SUGGESTED_EDITS_RECENT_EDITS = 41
const val SOURCE_FEED_PLACES = 42
}
}
11 changes: 11 additions & 0 deletions app/src/main/java/org/wikipedia/readinglist/LongPressMenu.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ import org.wikipedia.util.StringUtil
class LongPressMenu(
private val anchorView: View,
private val existsInAnyList: Boolean = true,
private val openPageInPlaces: Boolean = false,
private var menuRes: Int = R.menu.menu_long_press,
private val location: Location? = null,
private val callback: Callback? = null
) {
interface Callback {
fun onOpenLink(entry: HistoryEntry) { /* ignore by default */ }
fun onOpenInNewTab(entry: HistoryEntry) { /* ignore by default */ }
fun onOpenInPlaces(entry: HistoryEntry, location: Location) { /* ignore by default */ }
fun onAddRequest(entry: HistoryEntry, addToDefault: Boolean)
fun onMoveRequest(page: ReadingListPage?, entry: HistoryEntry)
fun onRemoveRequest() { /* ignore by default */ }
Expand Down Expand Up @@ -85,6 +87,9 @@ class LongPressMenu(
saveItem.isVisible = it.isEmpty()
saveItem.isEnabled = it.isEmpty()
}
val showOpenPageInPlaces = openPageInPlaces && location != null
menu.menu.findItem(R.id.menu_long_press_open_in_places)?.isVisible = showOpenPageInPlaces
menu.menu.findItem(R.id.menu_long_press_open_page)?.isVisible = !showOpenPageInPlaces
menu.menu.findItem(R.id.menu_long_press_get_directions)?.isVisible = location != null
menu.show()
}
Expand Down Expand Up @@ -127,6 +132,12 @@ class LongPressMenu(
entry?.let { callback?.onOpenLink(it) }
true
}
R.id.menu_long_press_open_in_places -> {
location?.let { location ->
entry?.let { callback?.onOpenInPlaces(it, location) }
}
true
}
R.id.menu_long_press_open_in_new_tab -> {
sendPlacesEvent("new_tab_click")
entry?.let { callback?.onOpenInNewTab(it) }
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/org/wikipedia/util/GeoUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.wikipedia.feed.announcement.GeoIPCookieUnmarshaller
import org.wikipedia.settings.Prefs
import java.text.DecimalFormat
import java.util.Locale
import kotlin.math.abs

object GeoUtil {
@Suppress("UnsafeImplicitIntentLaunch")
Expand Down Expand Up @@ -51,4 +52,9 @@ object GeoUtil {
"${formatter.format(distance)} km"
}
}

fun isSamePlace(startLat: Double, endLat: Double, startLon: Double, endLon: Double): Boolean {
val tolerance = 0.0000001
return abs(startLat - endLat) < tolerance && abs(startLon - endLon) < tolerance
}
}
2 changes: 1 addition & 1 deletion app/src/main/java/org/wikipedia/views/ViewUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ object ViewUtil {
private val CENTER_CROP_ROUNDED_CORNERS = MultiTransformation(CenterCrop(), WhiteBackgroundTransformation(), RoundedCorners(roundedDpToPx(2f)))
val ROUNDED_CORNERS = RoundedCorners(roundedDpToPx(15f))
val CENTER_CROP_LARGE_ROUNDED_CORNERS = MultiTransformation(CenterCrop(), WhiteBackgroundTransformation(), ROUNDED_CORNERS)
private val CENTER_CROP_CIRCLE = MultiTransformation(CenterCrop(), WhiteBackgroundTransformation(), RoundedCorners(roundedDpToPx(36f)))
private val CENTER_CROP_CIRCLE = MultiTransformation(CenterCrop(), WhiteBackgroundTransformation(), RoundedCorners(roundedDpToPx(48f)))

fun loadImageWithRoundedCorners(view: ImageView, url: String?, largeRoundedSize: Boolean = false) {
loadImage(view, url, roundedCorners = true, largeRoundedSize = largeRoundedSize)
Expand Down
Loading

0 comments on commit fa4ab72

Please sign in to comment.