Skip to content

Commit

Permalink
Add trending stocks to search page
Browse files Browse the repository at this point in the history
  • Loading branch information
premnirmal committed Oct 10, 2022
1 parent 232d065 commit 04b5d63
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package com.github.premnirmal.ticker.news

import android.content.res.Resources.Theme
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
Expand All @@ -14,11 +11,11 @@ import com.github.premnirmal.ticker.network.data.Quote
import com.github.premnirmal.ticker.news.NewsFeedItem.ArticleNewsFeed
import com.github.premnirmal.ticker.news.NewsFeedItem.TrendingStockNewsFeed
import com.github.premnirmal.ticker.news.TrendingAdapter.TrendingListener
import com.github.premnirmal.ticker.ui.bindStock
import com.github.premnirmal.tickerwidget.R
import com.github.premnirmal.tickerwidget.databinding.ItemNewsBinding
import com.github.premnirmal.tickerwidget.databinding.ItemTrendingStockBinding
import com.github.premnirmal.tickerwidget.databinding.ItemTrendingStocksBinding
import com.robinhood.ticker.TickerUtils

class TrendingAdapter(
private val listener: TrendingListener
Expand Down Expand Up @@ -79,31 +76,6 @@ class TrendingStocksVH(binding: ItemTrendingStocksBinding) : TrendingVH<ItemTren
binding
) {

protected val positiveColor: Int = ContextCompat.getColor(binding.root.context, R.color.positive_green)
protected val negativeColor: Int = ContextCompat.getColor(binding.root.context, R.color.negative_red)
protected val neutralColor: Int by lazy {
try {
val typedValue = TypedValue()
val theme: Theme = binding.root.context.theme
theme.resolveAttribute(
com.google.android.material.R.attr.colorOnSurfaceVariant,
typedValue,
true
)
ContextCompat.getColor(binding.root.context, typedValue.data)
} catch (e: Exception) {
ContextCompat.getColor(binding.root.context, R.color.text_2)
}
}

init {
arrayOf(binding.stock1, binding.stock2, binding.stock3, binding.stock4, binding.stock5, binding.stock6).forEach { stockView ->
stockView.changePercent.setCharacterLists(TickerUtils.provideNumberList())
stockView.changeValue.setCharacterLists(TickerUtils.provideNumberList())
stockView.totalValue.setCharacterLists(TickerUtils.provideNumberList())
}
}

override fun update(
item: NewsFeedItem,
listener: TrendingListener
Expand Down Expand Up @@ -176,38 +148,9 @@ class TrendingStocksVH(binding: ItemTrendingStocksBinding) : TrendingVH<ItemTren
binding: ItemTrendingStockBinding,
listener: TrendingListener
) {
binding.root.setOnClickListener { v ->
binding.bindStock(quote) {
listener.onClickQuote(quote)
}

val tickerView = binding.ticker
val nameView = binding.name

tickerView.text = quote.symbol
nameView.text = quote.name

val totalValueText = binding.totalValue
totalValueText.text = quote.priceFormat.format(quote.lastTradePrice)

val change: Float = quote.change
val changePercent: Float = quote.changeInPercent
val color = when {
(change < 0f || changePercent < 0f) -> {
negativeColor
}
(change == 0f) -> {
neutralColor
}
else -> {
positiveColor
}
}
val changeInPercentView = binding.changePercent
changeInPercentView.text = quote.changePercentStringWithSign()
val changeValueView = binding.changeValue
changeValueView.text = quote.changeStringWithSign()
changeInPercentView.setTextColor(color)
changeValueView.setTextColor(color)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.github.premnirmal.ticker.analytics.ClickEvent
import com.github.premnirmal.ticker.base.BaseFragment
import com.github.premnirmal.ticker.components.InAppMessage
import com.github.premnirmal.ticker.dismissKeyboard
Expand All @@ -26,9 +28,11 @@ import com.github.premnirmal.ticker.news.QuoteDetailActivity
import com.github.premnirmal.ticker.portfolio.search.SuggestionsAdapter.SuggestionClickListener
import com.github.premnirmal.ticker.showDialog
import com.github.premnirmal.ticker.showKeyboard
import com.github.premnirmal.ticker.ui.SpacingDecoration
import com.github.premnirmal.ticker.viewBinding
import com.github.premnirmal.ticker.widget.WidgetDataProvider
import com.github.premnirmal.tickerwidget.R
import com.github.premnirmal.tickerwidget.R.dimen
import com.github.premnirmal.tickerwidget.databinding.FragmentSearchBinding
import com.google.android.material.color.MaterialColors
import dagger.hilt.android.AndroidEntryPoint
Expand All @@ -54,7 +58,8 @@ class SearchFragment : BaseFragment<FragmentSearchBinding>(), ChildFragment, Sug
}

private val viewModel: SearchViewModel by viewModels()
private lateinit var adapter: SuggestionsAdapter
private lateinit var suggestionsAdapter: SuggestionsAdapter
private lateinit var trendingAdapter: TrendingStocksAdapter
override val simpleName: String = "SearchFragment"

private var selectedWidgetId: Int = -1
Expand Down Expand Up @@ -86,24 +91,48 @@ class SearchFragment : BaseFragment<FragmentSearchBinding>(), ChildFragment, Sug
insets
}
}
adapter = SuggestionsAdapter(this)
binding.recyclerView.layoutManager = LinearLayoutManager(activity)
binding.recyclerView.addItemDecoration(DividerItemDecoration(activity, DividerItemDecoration.VERTICAL))
binding.recyclerView.adapter = adapter
trendingAdapter = TrendingStocksAdapter { quote ->
analytics.trackClickEvent(ClickEvent("InstrumentClick"))
val intent = Intent(requireContext(), QuoteDetailActivity::class.java)
intent.putExtra(QuoteDetailActivity.TICKER, quote.symbol)
startActivity(intent)
}
binding.trendingRecyclerView?.layoutManager = GridLayoutManager(activity, 3)
binding.trendingRecyclerView?.addItemDecoration(
SpacingDecoration(requireContext().resources.getDimensionPixelSize(dimen.list_spacing_double))
)
binding.trendingRecyclerView?.adapter = trendingAdapter

suggestionsAdapter = SuggestionsAdapter(this)
binding.searchResultsRecyclerView?.layoutManager = LinearLayoutManager(activity)
binding.searchResultsRecyclerView?.addItemDecoration(DividerItemDecoration(activity, DividerItemDecoration.VERTICAL))
binding.searchResultsRecyclerView?.adapter = suggestionsAdapter

binding.searchView.addTextChangedListener(this)

savedInstanceState?.let { selectedWidgetId = it.getInt(ARG_WIDGET_ID, -1) }

if (viewModel.searchResult.value?.wasSuccessful == true) {
adapter.setData(viewModel.searchResult.value!!.data)
suggestionsAdapter.setData(viewModel.searchResult.value!!.data)
}
viewModel.searchResult.observe(viewLifecycleOwner, Observer {
viewModel.fetchTrendingStocks().observe(viewLifecycleOwner) { quotes ->
if (quotes.isNotEmpty()) trendingAdapter.setData(quotes)
}
viewModel.searchResult.observe(viewLifecycleOwner) {
if (it.wasSuccessful) {
adapter.setData(it.data)
suggestionsAdapter.setData(it.data)
} else {
adapter.setData(listOf(Suggestion(binding.searchView.text?.toString().orEmpty())))
suggestionsAdapter.setData(
listOf(
Suggestion(
binding.searchView.text?.toString()
.orEmpty()
)
)
)
InAppMessage.showToast(requireActivity(), R.string.error_fetching_suggestions)
}
})
}
}

override fun onResume() {
Expand Down Expand Up @@ -155,11 +184,16 @@ class SearchFragment : BaseFragment<FragmentSearchBinding>(), ChildFragment, Sug
.trim { it <= ' ' }
.replace(" ".toRegex(), "")
if (query.isNotEmpty()) {
binding.searchResultsRecyclerView?.isVisible = true
binding.trendingHolder?.isVisible = false
if (requireActivity().isNetworkOnline()) {
viewModel.fetchResults(query)
} else {
InAppMessage.showToast(requireActivity(), R.string.no_network_message)
}
} else {
binding.searchResultsRecyclerView?.isVisible = false
binding.trendingHolder?.isVisible = true
}
}

Expand Down Expand Up @@ -193,7 +227,7 @@ class SearchFragment : BaseFragment<FragmentSearchBinding>(), ChildFragment, Sug
val id = widgetDatas[which].widgetId
addTickerToWidget(ticker, id)
suggestion.exists = viewModel.doesSuggestionExist(suggestion)
adapter.notifyDataSetChanged()
suggestionsAdapter.notifyDataSetChanged()
dialog.dismiss()
}
.create()
Expand Down Expand Up @@ -221,6 +255,7 @@ class SearchFragment : BaseFragment<FragmentSearchBinding>(), ChildFragment, Sug
}

override fun scrollToTop() {
binding.recyclerView.smoothScrollToPosition(0)
binding.searchResultsRecyclerView?.smoothScrollToPosition(0)
binding.trendingRecyclerView?.smoothScrollToPosition(0)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ package com.github.premnirmal.ticker.portfolio.search
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.liveData
import androidx.lifecycle.viewModelScope
import com.github.premnirmal.ticker.model.FetchResult
import com.github.premnirmal.ticker.model.StocksProvider
import com.github.premnirmal.ticker.network.NewsProvider
import com.github.premnirmal.ticker.network.StocksApi
import com.github.premnirmal.ticker.network.data.Quote
import com.github.premnirmal.ticker.network.data.Suggestion
import com.github.premnirmal.ticker.network.data.SuggestionsNet.SuggestionNet
import com.github.premnirmal.ticker.widget.WidgetData
Expand All @@ -24,7 +27,8 @@ import javax.inject.Inject
class SearchViewModel @Inject constructor(
private val stocksApi: StocksApi,
private val widgetDataProvider: WidgetDataProvider,
private val stocksProvider: StocksProvider
private val stocksProvider: StocksProvider,
private val newsProvider: NewsProvider
) : ViewModel() {

val searchResult: LiveData<FetchResult<List<Suggestion>>>
Expand Down Expand Up @@ -58,6 +62,13 @@ class SearchViewModel @Inject constructor(
}
}

fun fetchTrendingStocks(): LiveData<List<Quote>> = liveData {
val trendingResult = newsProvider.fetchTrendingStocks(true)
if (trendingResult.wasSuccessful) {
emit(trendingResult.data)
} else emit(emptyList())
}

fun doesSuggestionExist(sug: Suggestion) =
stocksProvider.hasTicker(sug.symbol) && widgetDataProvider.widgetCount <= 1

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.github.premnirmal.ticker.portfolio.search

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.github.premnirmal.ticker.network.data.Quote
import com.github.premnirmal.ticker.ui.bindStock
import com.github.premnirmal.tickerwidget.databinding.ItemTrendingStockBinding

class TrendingStocksAdapter(private val listener: (Quote) -> Unit) : RecyclerView.Adapter<TrendingStockVH>() {

private val data = ArrayList<Quote>()

fun setData(quotes: List<Quote>) {
data.clear()
data.addAll(quotes)
notifyDataSetChanged()
}

override fun getItemCount(): Int = data.size

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): TrendingStockVH {
return TrendingStockVH(
ItemTrendingStockBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
}

override fun onBindViewHolder(
holder: TrendingStockVH,
position: Int
) {
holder.update(data[position], listener)
}
}

class TrendingStockVH(private val binding: ItemTrendingStockBinding) : RecyclerView.ViewHolder(
binding.root
) {

fun update(
quote: Quote,
listener: (Quote) -> Unit
) {
binding.bindStock(quote, listener)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.github.premnirmal.ticker.ui

import android.content.res.Resources.Theme
import android.util.TypedValue
import androidx.core.content.ContextCompat
import com.github.premnirmal.ticker.network.data.Quote
import com.github.premnirmal.tickerwidget.R
import com.github.premnirmal.tickerwidget.databinding.ItemTrendingStockBinding
import com.robinhood.ticker.TickerUtils

fun ItemTrendingStockBinding.bindStock(
quote: Quote,
listener: (Quote) -> Unit
) {
val positiveColor: Int = ContextCompat.getColor(root.context, R.color.positive_green)
val negativeColor: Int = ContextCompat.getColor(root.context, R.color.negative_red)
val neutralColor: Int by lazy {
try {
val typedValue = TypedValue()
val theme: Theme = root.context.theme
theme.resolveAttribute(
com.google.android.material.R.attr.colorOnSurfaceVariant,
typedValue,
true
)
ContextCompat.getColor(root.context, typedValue.data)
} catch (e: Exception) {
ContextCompat.getColor(root.context, R.color.text_2)
}
}
this.changePercent.setCharacterLists(TickerUtils.provideNumberList())
this.changeValue.setCharacterLists(TickerUtils.provideNumberList())
this.totalValue.setCharacterLists(TickerUtils.provideNumberList())

root.setOnClickListener {
listener.invoke(quote)
}

val tickerView = ticker
val nameView = name

tickerView.text = quote.symbol
nameView.text = quote.name

val totalValueText = totalValue
totalValueText.text = quote.priceFormat.format(quote.lastTradePrice)

val change: Float = quote.change
val changePercent: Float = quote.changeInPercent
val color = when {
(change < 0f || changePercent < 0f) -> {
negativeColor
}
(change == 0f) -> {
neutralColor
}
else -> {
positiveColor
}
}
val changeInPercentView = this.changePercent
changeInPercentView.text = quote.changePercentStringWithSign()
val changeValueView = changeValue
changeValueView.text = quote.changeStringWithSign()
changeInPercentView.setTextColor(color)
changeValueView.setTextColor(color)
}
Loading

0 comments on commit 04b5d63

Please sign in to comment.