Historical data (#57)

* wip - historical data graphs

* finished implementing graphs using alpha vantage api
This commit is contained in:
Prem Nirmal
2018-04-18 17:30:41 -04:00
committed by GitHub
parent 22c504949a
commit d3e8e30489
39 changed files with 921 additions and 91 deletions
+1
View File
@@ -208,6 +208,7 @@ dependencies {
implementation "uk.co.chrisjenx:calligraphy:2.1.0"
implementation "com.jakewharton.timber:timber:4.3.1"
implementation "saschpe.android:customtabs:1.1.1"
implementation 'com.github.PhilJay:MPAndroidChart:v3.0.3'
prodImplementation("com.crashlytics.sdk.android:crashlytics:2.6.7@aar") {
transitive = true
+4 -1
View File
@@ -71,4 +71,7 @@
-keepattributes Signature
-keepattributes Exceptions
-dontwarn okio.**
-keepattributes EnclosingMethod
-keepattributes EnclosingMethod
# MPAndroidChart
-keep class com.github.mikephil.charting.** { *; }
+14 -2
View File
@@ -21,6 +21,7 @@
<activity
android:name="com.github.premnirmal.ticker.home.SplashActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:screenOrientation="portrait"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -31,25 +32,31 @@
android:name="com.github.premnirmal.ticker.home.ParanormalActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:theme="@style/ParanormalActivityTheme"/>
<activity
android:name="com.github.premnirmal.ticker.portfolio.search.TickerSelectorActivity"
android:label="@string/add_ticker"
android:screenOrientation="portrait"
android:theme="@style/TickerSelectorActivityTheme"
android:windowSoftInputMode="adjustResize"/>
<activity
android:name="com.github.premnirmal.ticker.portfolio.AddPositionActivity"
android:label="@string/add_position"/>
android:label="@string/add_position"
android:screenOrientation="portrait"/>
<activity
android:name="com.github.premnirmal.ticker.portfolio.EditPositionActivity"
android:label="@string/edit_position"/>
android:label="@string/edit_position"
android:screenOrientation="portrait"/>
<activity
android:name="com.github.premnirmal.ticker.settings.SettingsActivity"
android:label="@string/app_settings"
android:screenOrientation="portrait"
android:theme="@style/SettingsActivityTheme"/>
<activity
android:name="com.github.premnirmal.ticker.settings.WidgetSettingsActivity"
android:label="@string/action_Settings"
android:screenOrientation="portrait"
android:theme="@style/WidgetSettingsActivityTheme">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
@@ -58,7 +65,12 @@
<activity
android:name="com.github.premnirmal.ticker.news.NewsFeedActivity"
android:label="@string/news_feed"
android:screenOrientation="portrait"
android:theme="@style/NewsFeedActivityTheme"/>
<activity
android:name="com.github.premnirmal.ticker.news.GraphActivity"
android:screenOrientation="landscape"
android:theme="@style/GraphActivityTheme"/>
<receiver
android:name="com.github.premnirmal.ticker.UpdateReceiver"
@@ -133,7 +133,9 @@ class AppPreferences @Inject constructor() {
const val DARK = 2
const val LIGHT = 3
val TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm")!!
val TIME_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
val DATE_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern("MM/dd/yy")
val AXIS_DATE_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern("MM/dd-yyyy")
val DECIMAL_FORMAT: Format = DecimalFormat("0.00")
@@ -3,6 +3,7 @@ package com.github.premnirmal.ticker
import android.app.Application
import android.content.Context
import android.content.pm.PackageManager
import android.net.ConnectivityManager
import android.util.Base64
import com.github.premnirmal.ticker.components.Analytics
import com.github.premnirmal.ticker.components.AppComponent
@@ -6,15 +6,16 @@ import android.content.Context
import android.content.DialogInterface
import android.content.DialogInterface.OnClickListener
import android.graphics.Rect
import android.net.ConnectivityManager
import android.os.Build
import android.os.Bundle
import android.os.PersistableBundle
import android.support.v7.app.AppCompatActivity
import android.view.View
import com.github.premnirmal.ticker.components.InAppMessage
import com.github.premnirmal.ticker.components.RxBus
import com.github.premnirmal.ticker.events.ErrorEvent
import com.github.premnirmal.ticker.portfolio.search.TickerSelectorActivity
import com.github.premnirmal.tickerwidget.R.string
import com.trello.rxlifecycle2.android.ActivityEvent
import com.trello.rxlifecycle2.android.RxLifecycleAndroid
import io.reactivex.Observable
@@ -33,20 +34,6 @@ abstract class BaseActivity : AppCompatActivity() {
// Extension functions.
fun Activity.isNetworkOnline(): Boolean {
try {
val connectivityManager = this.getSystemService(
Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val i = connectivityManager.activeNetworkInfo ?: return false
if (!i.isConnected) return false
if (!i.isAvailable) return false
return true
} catch (e: Exception) {
e.printStackTrace()
return false
}
}
fun Activity.getStatusBarHeight(): Int {
val result: Int
val resourceId: Int = this.resources.getIdentifier("status_bar_height", "dimen", "android")
@@ -94,7 +81,8 @@ abstract class BaseActivity : AppCompatActivity() {
private val lifecycleSubject = BehaviorSubject.create<ActivityEvent>()
@Inject internal lateinit var bus: RxBus
@Inject
internal lateinit var bus: RxBus
private fun lifecycle(): Observable<ActivityEvent> = lifecycleSubject
@@ -157,4 +145,9 @@ abstract class BaseActivity : AppCompatActivity() {
toolbar.paddingRight, toolbar.paddingBottom)
}
}
protected fun showErrorAndFinish() {
InAppMessage.showToast(this, string.error_symbol)
finish()
}
}
@@ -0,0 +1,91 @@
package com.github.premnirmal.ticker.base
import android.graphics.Color
import com.github.mikephil.charting.charts.LineChart
import com.github.mikephil.charting.components.XAxis
import com.github.mikephil.charting.components.YAxis
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet
import com.github.premnirmal.ticker.network.data.DataPoint
import com.github.premnirmal.ticker.network.data.Quote
import com.github.premnirmal.ticker.ui.DateAxisFormatter
import com.github.premnirmal.ticker.ui.MultilineXAxisRenderer
import com.github.premnirmal.ticker.ui.TextMarkerView
import com.github.premnirmal.ticker.ui.ValueAxisFormatter
import com.github.premnirmal.tickerwidget.R
abstract class BaseGraphActivity : BaseActivity() {
protected var dataPoints: List<DataPoint>? = null
protected lateinit var quote: Quote
protected fun setupGraphView(graphView: LineChart) {
graphView.isDoubleTapToZoomEnabled = false
graphView.axisLeft.setDrawGridLines(false)
graphView.axisLeft.setDrawAxisLine(false)
graphView.axisLeft.isEnabled = false
graphView.axisRight.setDrawGridLines(false)
graphView.axisRight.setDrawAxisLine(true)
graphView.axisRight.isEnabled = true
graphView.xAxis.setDrawGridLines(false)
graphView.setXAxisRenderer(MultilineXAxisRenderer(graphView.viewPortHandler,
graphView.xAxis,
graphView.getTransformer(YAxis.AxisDependency.RIGHT)))
graphView.extraBottomOffset = resources.getDimension(R.dimen.graph_bottom_offset)
graphView.legend.isEnabled = false
graphView.description = null
graphView.setNoDataTextColor(resources.getColor(R.color.colorAccent))
graphView.setNoDataText("")
graphView.marker = TextMarkerView(this)
}
protected fun loadGraph(graphView: LineChart) {
if (dataPoints == null || dataPoints!!.isEmpty()) {
onNoGraphData(graphView)
graphView.setNoDataText(getString(R.string.no_data))
graphView.invalidate()
return
}
graphView.setNoDataText("")
graphView.lineData?.clearValues()
val series = LineDataSet(dataPoints, quote.symbol)
series.setDrawHorizontalHighlightIndicator(false)
series.setDrawValues(false)
val colorAccent = resources.getColor(R.color.color_accent)
series.setDrawFilled(true)
series.color = colorAccent
series.fillColor = colorAccent
series.fillAlpha = 150
series.setDrawCircles(true)
series.mode = LineDataSet.Mode.CUBIC_BEZIER
series.cubicIntensity = 0.07f
series.lineWidth = 2f
series.setDrawCircles(false)
series.highLightColor = Color.GRAY
val lineData = LineData(series)
graphView.data = lineData
val xAxis: XAxis = graphView.xAxis
val yAxis: YAxis = graphView.axisRight
xAxis.valueFormatter = DateAxisFormatter()
yAxis.valueFormatter = ValueAxisFormatter()
xAxis.position = XAxis.XAxisPosition.BOTTOM
xAxis.textSize = 10f
yAxis.textSize = 10f
xAxis.textColor = Color.GRAY
yAxis.textColor = Color.GRAY
xAxis.setLabelCount(5, true)
yAxis.setLabelCount(5, true)
yAxis.setPosition(YAxis.YAxisLabelPosition.OUTSIDE_CHART)
xAxis.setDrawAxisLine(true)
yAxis.setDrawAxisLine(true)
xAxis.setDrawGridLines(false)
yAxis.setDrawGridLines(false)
graphView.invalidate()
onGraphDataAdded(graphView)
}
protected abstract fun onGraphDataAdded(graphView: LineChart)
protected abstract fun onNoGraphData(graphView: LineChart)
}
@@ -7,6 +7,7 @@ import com.github.premnirmal.ticker.home.HomePagerAdapter
import com.github.premnirmal.ticker.home.ParanormalActivity
import com.github.premnirmal.ticker.home.SplashActivity
import com.github.premnirmal.ticker.model.AlarmScheduler
import com.github.premnirmal.ticker.model.HistoryProvider
import com.github.premnirmal.ticker.model.RefreshService
import com.github.premnirmal.ticker.model.StocksProvider
import com.github.premnirmal.ticker.model.StocksStorage
@@ -14,6 +15,7 @@ import com.github.premnirmal.ticker.network.NewsProvider
import com.github.premnirmal.ticker.network.RequestInterceptor
import com.github.premnirmal.ticker.network.StocksApi
import com.github.premnirmal.ticker.network.UserAgentInterceptor
import com.github.premnirmal.ticker.news.GraphActivity
import com.github.premnirmal.ticker.news.NewsFeedActivity
import com.github.premnirmal.ticker.portfolio.AddPositionActivity
import com.github.premnirmal.ticker.portfolio.EditPositionActivity
@@ -56,6 +58,8 @@ interface AppComponent {
fun inject(newsFeedActivity: NewsFeedActivity)
fun inject(graphActivity: GraphActivity)
// Components
fun inject(stocksStorage: StocksStorage)
@@ -64,6 +68,8 @@ interface AppComponent {
fun inject(stocksProvider: StocksProvider)
fun inject(historicalDataProvider: HistoryProvider)
fun inject(alarmScheduler: AlarmScheduler)
fun inject(updateReceiver: UpdateReceiver)
@@ -0,0 +1,23 @@
package com.github.premnirmal.ticker.components
import android.content.Context
import android.net.ConnectivityManager
fun Context.isNetworkOnline(): Boolean {
try {
val connectivityManager = this.getSystemService(
Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val i = connectivityManager.activeNetworkInfo ?: return false
if (!i.isConnected) return false
if (!i.isAvailable) return false
return true
} catch (e: Exception) {
e.printStackTrace()
return false
}
}
fun Long.minutesInMs(): Long {
return this * 60 * 1000
}
@@ -22,6 +22,7 @@ import com.github.premnirmal.ticker.AppPreferences
import com.github.premnirmal.ticker.base.BaseActivity
import com.github.premnirmal.ticker.components.InAppMessage
import com.github.premnirmal.ticker.components.Injector
import com.github.premnirmal.ticker.components.isNetworkOnline
import com.github.premnirmal.ticker.model.IStocksProvider
import com.github.premnirmal.ticker.network.SimpleSubscriber
import com.github.premnirmal.ticker.network.data.Quote
@@ -269,7 +270,7 @@ class ParanormalActivity : BaseActivity(), PortfolioFragment.Callback {
override fun onError(e: Throwable) {
attemptingFetch = false
swipe_container?.isRefreshing = false
InAppMessage.showMessage(this@ParanormalActivity, getString(R.string.refresh_failed))
InAppMessage.showMessage(this@ParanormalActivity, getString(string.refresh_failed))
}
override fun onNext(result: List<Quote>) {
@@ -280,12 +281,12 @@ class ParanormalActivity : BaseActivity(), PortfolioFragment.Callback {
})
} else {
attemptingFetch = false
InAppMessage.showMessage(this, getString(R.string.refresh_failed))
InAppMessage.showMessage(this, getString(string.refresh_failed))
swipe_container?.isRefreshing = false
}
} else {
attemptingFetch = false
InAppMessage.showMessage(this, getString(R.string.no_network_message))
InAppMessage.showMessage(this, getString(string.no_network_message))
swipe_container?.isRefreshing = false
}
}
@@ -0,0 +1,68 @@
package com.github.premnirmal.ticker.model
import com.github.premnirmal.ticker.components.Injector
import com.github.premnirmal.ticker.model.IHistoryProvider.Range
import com.github.premnirmal.ticker.network.HistoricalDataApi
import com.github.premnirmal.ticker.network.data.DataPoint
import com.github.premnirmal.tickerwidget.R
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import org.threeten.bp.LocalDate
import org.threeten.bp.format.DateTimeFormatter
import javax.inject.Inject
class HistoryProvider : IHistoryProvider {
@Inject
internal lateinit var historicalDataApi: HistoricalDataApi
private val apiKey = Injector.appComponent.appContext().getString(R.string.alpha_vantage_api_key)
private var cachedData: Pair<String, List<DataPoint>>? = null
init {
Injector.appComponent.inject(this)
}
override fun getHistoricalDataShort(symbol: String): Observable<List<DataPoint>> {
return historicalDataApi.getHistoricalData(apiKey = apiKey, symbol = symbol).map {
val points = ArrayList<DataPoint>()
it.timeSeries.forEach { k, v ->
val epochDate = LocalDate.parse(k, DateTimeFormatter.ISO_LOCAL_DATE).toEpochDay()
points.add(DataPoint(epochDate.toFloat(), v.close.toFloat()))
}
points.sort()
points as List<DataPoint>
}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
override fun getHistoricalDataByRange(symbol: String, range: Range): Observable<List<DataPoint>> {
val observable: Observable<List<DataPoint>>
if (symbol == cachedData?.first) {
observable = Observable.fromCallable {
val filtered = cachedData!!.second.filter {
it.getDate().isAfter(range.end)
}.toMutableList()
filtered.sort()
filtered
}
} else {
cachedData = null
observable = historicalDataApi.getHistoricalDataFull(apiKey = apiKey, symbol = symbol).map {
val points = ArrayList<DataPoint>()
it.timeSeries.forEach { k, v ->
val epochDate = LocalDate.parse(k, DateTimeFormatter.ISO_LOCAL_DATE).toEpochDay()
points.add(DataPoint(epochDate.toFloat(),
v.close.toFloat()))
}
cachedData = Pair(symbol, points)
val filtered = points.filter { it.getDate().isAfter(range.end) }.toMutableList()
filtered.sort()
filtered
}
}
return observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
}
@@ -0,0 +1,23 @@
package com.github.premnirmal.ticker.model
import com.github.premnirmal.ticker.network.data.DataPoint
import io.reactivex.Observable
import org.threeten.bp.LocalDate
import java.io.Serializable
interface IHistoryProvider {
fun getHistoricalDataShort(symbol: String): Observable<List<DataPoint>>
fun getHistoricalDataByRange(symbol: String, range: Range): Observable<List<DataPoint>>
sealed class Range(val end: LocalDate) : Serializable {
class DateRange(end: LocalDate): Range(end)
companion object {
val ONE_MONTH = DateRange(LocalDate.now().minusMonths(1))
val THREE_MONTH = DateRange(LocalDate.now().minusMonths(3))
val ONE_YEAR = DateRange(LocalDate.now().minusYears(1))
val MAX = DateRange(LocalDate.now().minusYears(20))
}
}
}
@@ -18,6 +18,8 @@ interface IStocksProvider {
fun schedule()
fun scheduleSoon()
fun getTickers(): List<String>
fun getStock(ticker: String): Quote?
@@ -5,6 +5,7 @@ import android.app.job.JobService
import android.os.Build.VERSION_CODES
import android.support.annotation.RequiresApi
import com.github.premnirmal.ticker.components.Injector
import com.github.premnirmal.ticker.components.isNetworkOnline
import com.github.premnirmal.ticker.network.SimpleSubscriber
import com.github.premnirmal.ticker.network.data.Quote
import timber.log.Timber
@@ -23,22 +24,27 @@ class RefreshService : JobService() {
override fun onStartJob(params: JobParameters): Boolean {
Timber.i("onStartJob " + params.jobId)
stocksProvider.fetch().subscribe(
object : SimpleSubscriber<List<Quote>>() {
override fun onError(e: Throwable) {
// StocksProvider will handle rescheduling the job
val needsReschedule = false
jobFinished(params, needsReschedule)
}
if (isNetworkOnline()) {
stocksProvider.fetch().subscribe(
object : SimpleSubscriber<List<Quote>>() {
override fun onError(e: Throwable) {
// StocksProvider will handle rescheduling the job
val needsReschedule = false
jobFinished(params, needsReschedule)
}
override fun onComplete() {
// doesn't need reschedule
val needsReschedule = false
jobFinished(params, needsReschedule)
}
})
// additional work is being performed
return true
override fun onComplete() {
// doesn't need reschedule
val needsReschedule = false
jobFinished(params, needsReschedule)
}
})
// additional work is being performed
return true
} else {
stocksProvider.scheduleSoon()
return false
}
}
override fun onStopJob(params: JobParameters): Boolean {
@@ -9,6 +9,7 @@ import com.github.premnirmal.ticker.components.AppClock
import com.github.premnirmal.ticker.components.InAppMessage
import com.github.premnirmal.ticker.components.Injector
import com.github.premnirmal.ticker.components.RxBus
import com.github.premnirmal.ticker.components.minutesInMs
import com.github.premnirmal.ticker.events.ErrorEvent
import com.github.premnirmal.ticker.events.RefreshEvent
import com.github.premnirmal.ticker.network.RobindahoodException
@@ -287,6 +288,10 @@ class StocksProvider @Inject constructor() : IStocksProvider {
scheduleUpdate()
}
override fun scheduleSoon() {
scheduleUpdateWithMs(5L.minutesInMs(), true)
}
override fun addStock(ticker: String): Collection<String> {
synchronized(quoteList, {
if (!tickerList.contains(ticker)) {
@@ -0,0 +1,22 @@
package com.github.premnirmal.ticker.network
import com.github.premnirmal.ticker.network.data.HistoricalData
import io.reactivex.Observable
import retrofit2.http.GET
import retrofit2.http.Query
const val TIME_SERIES_DAILY = "TIME_SERIES_DAILY"
interface HistoricalDataApi {
@GET("query")
fun getHistoricalData(@Query(value = "function") function: String = TIME_SERIES_DAILY,
@Query(value = "apikey") apiKey: String,
@Query(value = "symbol") symbol: String): Observable<HistoricalData>
@GET("query")
fun getHistoricalDataFull(@Query(value = "function") function: String = TIME_SERIES_DAILY,
@Query(value = "outputsize") outputSize: String = "full",
@Query(value = "apikey") apiKey: String,
@Query(value = "symbol") symbol: String): Observable<HistoricalData>
}
@@ -3,6 +3,8 @@ package com.github.premnirmal.ticker.network
import android.content.Context
import com.github.premnirmal.ticker.components.RxBus
import com.github.premnirmal.ticker.model.AlarmScheduler
import com.github.premnirmal.ticker.model.HistoryProvider
import com.github.premnirmal.ticker.model.IHistoryProvider
import com.github.premnirmal.ticker.model.IStocksProvider
import com.github.premnirmal.ticker.model.StocksProvider
import com.github.premnirmal.ticker.widget.WidgetDataProvider
@@ -36,7 +38,7 @@ class NetworkModule {
internal const val READ_TIMEOUT: Long = 20000
}
@Provides @Singleton @Named("yahooClient")
@Provides @Singleton @Named("client")
internal fun provideHttpClientForYahoo(): OkHttpClient {
val logger = HttpLoggingInterceptor()
logger.level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE
@@ -49,19 +51,6 @@ class NetworkModule {
return okHttpClient
}
@Provides @Singleton @Named("newsClient")
internal fun provideHttpClientForNews(): OkHttpClient {
val logger = HttpLoggingInterceptor()
logger.level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(UserAgentInterceptor())
.addInterceptor(logger)
.readTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS)
.connectTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
.build()
return okHttpClient
}
@Provides @Singleton @Named("robindahoodClient")
internal fun provideHttpClientForRobindahood(context: Context, bus: RxBus): OkHttpClient {
val logger = HttpLoggingInterceptor()
@@ -98,7 +87,7 @@ class NetworkModule {
@Provides @Singleton
internal fun provideSuggestionsApi(context: Context,
@Named("yahooClient") okHttpClient: OkHttpClient,
@Named("client") okHttpClient: OkHttpClient,
gson: Gson, converterFactory: GsonConverterFactory,
rxJavaFactory: RxJava2CallAdapterFactory): SuggestionApi {
val Retrofit = Retrofit.Builder()
@@ -146,7 +135,7 @@ class NetworkModule {
@Provides @Singleton
internal fun provideNewsApi(context: Context,
@Named("newsClient") okHttpClient: OkHttpClient,
@Named("client") okHttpClient: OkHttpClient,
gson: Gson, converterFactory: GsonConverterFactory,
rxJavaFactory: RxJava2CallAdapterFactory): NewsApi {
val Retrofit = Retrofit.Builder()
@@ -159,12 +148,30 @@ class NetworkModule {
return newsApi
}
@Provides @Singleton
internal fun provideHistoricalDataApi(context: Context,
@Named("client") okHttpClient: OkHttpClient,
gson: Gson, converterFactory: GsonConverterFactory,
rxJavaFactory: RxJava2CallAdapterFactory): HistoricalDataApi {
val Retrofit = Retrofit.Builder()
.client(okHttpClient)
.baseUrl(context.getString(R.string.alpha_vantage_endpoint))
.addCallAdapterFactory(rxJavaFactory)
.addConverterFactory(converterFactory)
.build()
val api = Retrofit.create(HistoricalDataApi::class.java)
return api
}
@Provides @Singleton
internal fun provideNewsProvider(): NewsProvider = NewsProvider()
@Provides @Singleton
internal fun provideStocksProvider(): IStocksProvider = StocksProvider()
@Provides @Singleton
internal fun provideHistoricalDataProvider(): IHistoryProvider = HistoryProvider()
@Provides @Singleton
internal fun provideAlarmScheduler(): AlarmScheduler = AlarmScheduler()
@@ -17,10 +17,12 @@ import javax.inject.Singleton
@Singleton
class StocksApi @Inject constructor() {
@Inject internal lateinit var gson: Gson
@Inject internal lateinit var financeApi: Robindahood
@Inject internal lateinit var clock: AppClock
@Inject
internal lateinit var gson: Gson
@Inject
internal lateinit var financeApi: Robindahood
@Inject
internal lateinit var clock: AppClock
var lastFetched: Long = 0
init {
@@ -66,5 +68,4 @@ class StocksApi @Inject constructor() {
}
}
}
}
@@ -0,0 +1,38 @@
package com.github.premnirmal.ticker.network.data
import android.os.Parcel
import android.os.Parcelable
import com.github.mikephil.charting.data.Entry
import com.github.premnirmal.ticker.network.data.HistoricalData.HistoricalValue
import org.threeten.bp.LocalDate
import org.threeten.bp.format.DateTimeFormatter
import java.io.Serializable
class DataPoint : Entry, Serializable, Comparable<DataPoint> {
constructor(x: Float, y: Float) : super(x, y)
constructor(x: Float, y: Float, data: HistoricalValue) : super(x, y, data)
constructor(source: Parcel) : super(source)
fun getDate(): LocalDate = LocalDate.ofEpochDay(x.toLong())
override fun compareTo(other: DataPoint): Int = x.compareTo(other.x)
companion object {
private val FORMATTER: DateTimeFormatter by lazy { DateTimeFormatter.ofPattern("MMMM d") }
private const val serialVersionUID = 42L
val CREATOR: Parcelable.Creator<DataPoint> = object : Parcelable.Creator<DataPoint> {
override fun createFromParcel(source: Parcel): DataPoint {
return DataPoint(source)
}
override fun newArray(size: Int): Array<DataPoint?> {
return arrayOfNulls(size)
}
}
}
}
@@ -0,0 +1,21 @@
package com.github.premnirmal.ticker.network.data
import com.google.gson.annotations.SerializedName
data class HistoricalData(
@field:SerializedName("Time Series (Daily)")
var timeSeries: Map<String, HistoricalValue> = LinkedHashMap()) {
data class HistoricalValue(
@field:SerializedName("1. open")
var open: String = "",
@field:SerializedName("4. close")
var close: String = "",
@field:SerializedName("3. low")
var low: String = "",
@field:SerializedName("2. high")
var high: String = "",
@field:SerializedName("5. volume")
var volume: String = "")
}
@@ -94,6 +94,6 @@ data class Quote(var symbol: String = "",
}
}
override operator fun compareTo(other: Quote): Int = java.lang.Float.compare(
other.changeInPercent, changeInPercent)
override operator fun compareTo(other: Quote): Int =
other.changeInPercent.compareTo(changeInPercent)
}
@@ -0,0 +1,164 @@
package com.github.premnirmal.ticker.news
import android.content.DialogInterface
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import com.github.mikephil.charting.animation.Easing
import com.github.mikephil.charting.charts.LineChart
import com.github.premnirmal.ticker.base.BaseGraphActivity
import com.github.premnirmal.ticker.components.Injector
import com.github.premnirmal.ticker.components.isNetworkOnline
import com.github.premnirmal.ticker.model.IHistoryProvider
import com.github.premnirmal.ticker.model.IHistoryProvider.Range
import com.github.premnirmal.ticker.model.IStocksProvider
import com.github.premnirmal.ticker.network.SimpleSubscriber
import com.github.premnirmal.ticker.network.data.DataPoint
import com.github.premnirmal.ticker.network.data.Quote
import com.github.premnirmal.tickerwidget.R
import kotlinx.android.synthetic.main.activity_graph.desc
import kotlinx.android.synthetic.main.activity_graph.graphActivityRoot
import kotlinx.android.synthetic.main.activity_graph.graphView
import kotlinx.android.synthetic.main.activity_graph.graph_holder
import kotlinx.android.synthetic.main.activity_graph.max
import kotlinx.android.synthetic.main.activity_graph.one_month
import kotlinx.android.synthetic.main.activity_graph.one_year
import kotlinx.android.synthetic.main.activity_graph.progress
import kotlinx.android.synthetic.main.activity_graph.three_month
import kotlinx.android.synthetic.main.activity_graph.tickerName
import timber.log.Timber
import javax.inject.Inject
class GraphActivity : BaseGraphActivity() {
companion object {
const val TICKER = "TICKER"
private const val DATA_POINTS = "DATA_POINTS"
private const val RANGE = "RANGE"
private const val DURATION = 2000
}
private var range = Range.THREE_MONTH
private lateinit var ticker: String
@Inject
internal lateinit var historyProvider: IHistoryProvider
@Inject
internal lateinit var stocksProvider: IStocksProvider
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Injector.appComponent.inject(this)
setContentView(R.layout.activity_graph)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
graphActivityRoot.setPadding(graphActivityRoot.paddingLeft, getStatusBarHeight(),
graphActivityRoot.paddingRight, graphActivityRoot.paddingBottom)
}
if (Build.VERSION.SDK_INT < 16) {
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN)
} else {
val decorView = window.decorView
decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
}
setupGraphView(graphView)
val q: Quote?
if (intent.hasExtra(TICKER) && intent.getStringExtra(TICKER) != null) {
ticker = intent.getStringExtra(TICKER)
q = stocksProvider.getStock(ticker)
if (q == null) {
showErrorAndFinish()
}
} else {
ticker = ""
showErrorAndFinish()
return
}
quote = q!!
tickerName.text = ticker
desc.text = quote.name
savedInstanceState?.let {
dataPoints = it.getParcelableArrayList(DATA_POINTS)
}
var view: View? = null
when (range) {
Range.ONE_MONTH -> view = one_month
Range.THREE_MONTH -> view = three_month
Range.ONE_YEAR -> view = one_year
Range.MAX -> view = max
}
view?.isEnabled = false
}
override fun onStart() {
super.onStart()
if (dataPoints == null) {
getData()
} else {
loadGraph(graphView)
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
dataPoints?.let {
outState.putParcelableArrayList(DATA_POINTS, ArrayList(it))
}
outState.putSerializable(RANGE, range)
}
private fun getData() {
if (isNetworkOnline()) {
graph_holder.visibility = View.GONE
progress.visibility = View.VISIBLE
val observable = historyProvider.getHistoricalDataByRange(ticker, range)
bind(observable).subscribe(object : SimpleSubscriber<List<DataPoint>>() {
override fun onError(e: Throwable) {
Timber.w(e)
showDialog(getString(R.string.error_loading_graph),
DialogInterface.OnClickListener { _, _ -> finish() })
}
override fun onNext(result: List<DataPoint>) {
dataPoints = result
loadGraph(graphView)
}
})
} else {
showDialog(getString(R.string.no_network_message),
DialogInterface.OnClickListener { _, _ -> finish() })
}
}
override fun onGraphDataAdded(graphView: LineChart) {
progress.visibility = View.GONE
graph_holder.visibility = View.VISIBLE
graphView.animateX(DURATION, Easing.EasingOption.EaseInOutCubic)
}
override fun onNoGraphData(graphView: LineChart) {
progress.visibility = View.GONE
graph_holder.visibility = View.VISIBLE
}
/**
* xml OnClick
* @param v
*/
fun updateRange(v: View) {
when (v.id) {
R.id.one_month -> range = Range.ONE_MONTH
R.id.three_month -> range = Range.THREE_MONTH
R.id.one_year -> range = Range.ONE_YEAR
R.id.max -> range = Range.MAX
}
val parent = v.parent as ViewGroup
(0 until parent.childCount)
.map { parent.getChildAt(it) }
.forEach { it.isEnabled = it != v }
getData()
}
}
@@ -13,9 +13,11 @@ import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import com.github.premnirmal.ticker.base.BaseActivity
import com.github.premnirmal.ticker.components.InAppMessage
import com.github.mikephil.charting.charts.LineChart
import com.github.premnirmal.ticker.base.BaseGraphActivity
import com.github.premnirmal.ticker.components.Injector
import com.github.premnirmal.ticker.network.data.DataPoint
import com.github.premnirmal.ticker.model.IHistoryProvider
import com.github.premnirmal.ticker.model.IStocksProvider
import com.github.premnirmal.ticker.network.NewsProvider
import com.github.premnirmal.ticker.network.SimpleSubscriber
@@ -23,37 +25,39 @@ import com.github.premnirmal.ticker.network.data.NewsArticle
import com.github.premnirmal.ticker.network.data.Quote
import com.github.premnirmal.ticker.portfolio.EditPositionActivity
import com.github.premnirmal.tickerwidget.R
import com.github.premnirmal.tickerwidget.R.string
import kotlinx.android.synthetic.main.activity_news_feed.average_price
import kotlinx.android.synthetic.main.activity_news_feed.change
import kotlinx.android.synthetic.main.activity_news_feed.description
import kotlinx.android.synthetic.main.activity_news_feed.edit_positions
import kotlinx.android.synthetic.main.activity_news_feed.equityValue
import kotlinx.android.synthetic.main.activity_news_feed.exchange
import kotlinx.android.synthetic.main.activity_news_feed.graphView
import kotlinx.android.synthetic.main.activity_news_feed.graph_container
import kotlinx.android.synthetic.main.activity_news_feed.lastTradePrice
import kotlinx.android.synthetic.main.activity_news_feed.news_container
import kotlinx.android.synthetic.main.activity_news_feed.numShares
import kotlinx.android.synthetic.main.activity_news_feed.progress
import kotlinx.android.synthetic.main.activity_news_feed.tickerName
import kotlinx.android.synthetic.main.activity_news_feed.toolbar
import kotlinx.android.synthetic.main.activity_news_feed.total_gain_loss
import saschpe.android.customtabs.CustomTabsHelper
import saschpe.android.customtabs.WebViewFallback
import timber.log.Timber
import javax.inject.Inject
class NewsFeedActivity : BaseActivity() {
class NewsFeedActivity : BaseGraphActivity() {
companion object {
const val TICKER = "TICKER"
private const val DATA_POINTS = "DATA_POINTS"
}
@Inject
internal lateinit var stocksProvider: IStocksProvider
@Inject
lateinit var newsProvider: NewsProvider
internal lateinit var newsProvider: NewsProvider
@Inject
internal lateinit var historyProvider: IHistoryProvider
private lateinit var ticker: String
private lateinit var quote: Quote
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -63,6 +67,9 @@ class NewsFeedActivity : BaseActivity() {
toolbar.setNavigationOnClickListener {
finish()
}
graph_container.layoutParams.height = (resources.displayMetrics.widthPixels * 0.5625f).toInt()
graph_container.requestLayout()
setupGraphView(graphView)
val q: Quote?
if (intent.hasExtra(TICKER) && intent.getStringExtra(TICKER) != null) {
ticker = intent.getStringExtra(TICKER)
@@ -96,18 +103,30 @@ class NewsFeedActivity : BaseActivity() {
intent.putExtra(EditPositionActivity.TICKER, quote.symbol)
startActivity(intent)
}
savedInstanceState?.let {
dataPoints = it.getParcelableArrayList(DATA_POINTS)
}
}
if (news_container.childCount <= 1) {
bind(newsProvider.getNews(quote.newsQuery())).subscribe(
object : SimpleSubscriber<List<NewsArticle>>() {
override fun onNext(result: List<NewsArticle>) {
setUpArticles(result)
}
private fun fetchData() {
bind(historyProvider.getHistoricalDataShort(quote.symbol)).subscribe(
object : SimpleSubscriber<List<DataPoint>>() {
override fun onNext(result: List<DataPoint>) {
dataPoints = result
loadGraph(graphView)
}
override fun onError(e: Throwable) {
Timber.w(e)
}
})
override fun onError(e: Throwable) {
progress.visibility = View.GONE
graphView.setNoDataText(getString(R.string.no_data))
}
})
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
dataPoints?.let {
outState.putParcelableArrayList(DATA_POINTS, ArrayList(it))
}
}
@@ -150,6 +169,18 @@ class NewsFeedActivity : BaseActivity() {
}
}
override fun onStart() {
super.onStart()
if (news_container.childCount <= 1) {
fetchNews()
}
if (dataPoints == null) {
fetchData()
} else {
loadGraph(graphView)
}
}
override fun onResume() {
super.onResume()
numShares.text = quote.numSharesString()
@@ -170,9 +201,34 @@ class NewsFeedActivity : BaseActivity() {
}
}
private fun showErrorAndFinish() {
InAppMessage.showToast(this, string.error_symbol)
finish()
private fun fetchNews() {
bind(newsProvider.getNews(quote.newsQuery())).subscribe(
object : SimpleSubscriber<List<NewsArticle>>() {
override fun onNext(result: List<NewsArticle>) {
setUpArticles(result)
}
override fun onError(e: Throwable) {
news_container.visibility = View.GONE
}
})
}
override fun onGraphDataAdded(graphView: LineChart) {
progress.visibility = View.GONE
}
override fun onNoGraphData(graphView: LineChart) {
progress.visibility = View.GONE
}
/**
* Called via xml
*/
fun openGraph(v: View) {
val intent = Intent(this, GraphActivity::class.java)
intent.putExtra(GraphActivity.TICKER, ticker)
startActivity(intent)
}
private fun Drawable.toBitmap(): Bitmap {
@@ -19,6 +19,7 @@ import android.view.ViewTreeObserver
import com.github.premnirmal.ticker.base.BaseActivity
import com.github.premnirmal.ticker.components.InAppMessage
import com.github.premnirmal.ticker.components.Injector
import com.github.premnirmal.ticker.components.isNetworkOnline
import com.github.premnirmal.ticker.network.SimpleSubscriber
import com.github.premnirmal.ticker.network.SuggestionApi
import com.github.premnirmal.ticker.network.data.Suggestions.Suggestion
@@ -0,0 +1,40 @@
package com.github.premnirmal.ticker.ui
import android.graphics.Canvas
import com.github.mikephil.charting.components.AxisBase
import com.github.mikephil.charting.components.XAxis
import com.github.mikephil.charting.formatter.IAxisValueFormatter
import com.github.mikephil.charting.renderer.XAxisRenderer
import com.github.mikephil.charting.utils.MPPointF
import com.github.mikephil.charting.utils.Transformer
import com.github.mikephil.charting.utils.Utils
import com.github.mikephil.charting.utils.ViewPortHandler
import com.github.premnirmal.ticker.AppPreferences
import org.threeten.bp.LocalDate
class DateAxisFormatter : IAxisValueFormatter {
override fun getFormattedValue(value: Float, axis: AxisBase): String {
val date = LocalDate.ofEpochDay(value.toLong())
return date.format(AppPreferences.AXIS_DATE_FORMATTER)
}
}
class ValueAxisFormatter : IAxisValueFormatter {
override fun getFormattedValue(value: Float,
axis: AxisBase): String = "$${AppPreferences.DECIMAL_FORMAT.format(value)}"
}
class MultilineXAxisRenderer(viewPortHandler: ViewPortHandler?, xAxis: XAxis?,
trans: Transformer?) : XAxisRenderer(viewPortHandler, xAxis, trans) {
override fun drawLabel(c: Canvas, formattedLabel: String, x: Float, y: Float, anchor: MPPointF,
angleDegrees: Float) {
val lines = formattedLabel.split("-")
for (i in 0 until lines.size) {
val vOffset = i * mAxisLabelPaint.textSize
Utils.drawXAxisValue(c, lines[i], x, y + vOffset, mAxisLabelPaint, anchor, angleDegrees)
}
}
}
@@ -0,0 +1,30 @@
package com.github.premnirmal.ticker.ui
import android.content.Context
import android.widget.TextView
import com.github.mikephil.charting.components.MarkerView
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.highlight.Highlight
import com.github.mikephil.charting.utils.MPPointF
import com.github.premnirmal.ticker.AppPreferences
import com.github.premnirmal.ticker.AppPreferences.Companion.DATE_FORMATTER
import com.github.premnirmal.ticker.network.data.DataPoint
import com.github.premnirmal.tickerwidget.R
class TextMarkerView(context: Context) : MarkerView(context, R.layout.text_marker_layout) {
private var tvContent: TextView = findViewById(R.id.tvContent)
private val offsetPoint by lazy {
MPPointF((-(width / 2)).toFloat(), (-height).toFloat())
}
override fun refreshContent(e: Entry?, highlight: Highlight?) {
val dataPoint = e as DataPoint
val price = AppPreferences.DECIMAL_FORMAT.format(dataPoint.y)
val date = dataPoint.getDate().format(DATE_FORMATTER)
tvContent.text = "$${price}\n$date"
super.refreshContent(e, highlight)
}
override fun getOffset(): MPPointF = offsetPoint
}
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp">
<path
android:pathData="M0 0h24v24H0z"/>
<path
android:fillColor="#ffffff"
android:pathData="M19 19H5V5h7V3H5c-1.11 0-2 0.9-2 2v14c0 1.1 0.89 2 2 2h14c1.1 0 2-0.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/>
</vector>
+119
View File
@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/graphActivityRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
android:orientation="vertical"
>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.88"
android:padding="15dp"
>
<ProgressBar
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
/>
<RelativeLayout
android:id="@+id/graph_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
>
<TextView
android:id="@+id/tickerName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_margin="3dp"
android:textSize="20dp"
tools:text="GOOG"
style="@style/BoldTextView"
/>
<TextView
android:id="@+id/desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tickerName"
android:layout_centerHorizontal="true"
android:layout_margin="3dp"
android:textSize="14dp"
tools:text="GOOG"
style="@style/BoldTextView"
/>
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/graphView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
</FrameLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_gravity="center_horizontal"
android:layout_weight="0.12"
android:orientation="horizontal"
>
<Button
android:id="@+id/one_month"
android:layout_width="100dp"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:gravity="center"
android:onClick="updateRange"
android:text="@string/one_month"
style="@style/ButtonStyle"
/>
<Button
android:id="@+id/three_month"
android:layout_width="100dp"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:gravity="center"
android:onClick="updateRange"
android:text="@string/three_month"
style="@style/ButtonStyle"
/>
<Button
android:id="@+id/one_year"
android:layout_width="100dp"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:gravity="center"
android:onClick="updateRange"
android:text="@string/one_year"
style="@style/ButtonStyle"
/>
<Button
android:id="@+id/max"
android:layout_width="100dp"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:gravity="center"
android:onClick="updateRange"
android:text="@string/max"
style="@style/ButtonStyle"
/>
</LinearLayout>
</LinearLayout>
+36 -3
View File
@@ -41,6 +41,39 @@
style="@style/BoldTextView"
/>
<ImageView
android:id="@+id/expand_graph"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|end"
android:contentDescription="@string/expand"
android:onClick="openGraph"
android:padding="8dp"
android:src="@drawable/ic_expand"
/>
<FrameLayout
android:id="@+id/graph_container"
android:layout_width="match_parent"
android:layout_height="200dp"
android:padding="8dp"
>
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/graphView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<ProgressBar
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="20dp"
/>
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -192,10 +225,10 @@
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_weight="1"
app:center_text="true"
app:name="@string/average_price"
app:or="horizontal"
app:size="@dimen/larger_text"
app:center_text="true"
/>
<com.github.premnirmal.ticker.ui.StockFieldView
@@ -204,10 +237,10 @@
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_weight="1"
app:center_text="true"
app:name="@string/gain_loss"
app:or="horizontal"
app:size="@dimen/larger_text"
app:center_text="true"
/>
</LinearLayout>
@@ -227,12 +260,12 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_margin="8dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:text="@string/recent_news"
android:textSize="@dimen/thin_title_text"
style="@style/ThinTitleTextView"
android:layout_margin="8dp"
/>
</LinearLayout>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="40dp"
>
<TextView
android:id="@+id/tvContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:background="@drawable/translucent_widget_bg"
android:gravity="center"
android:padding="5dp"
android:text=""
android:textSize="@dimen/small_text"
style="@style/BoldTextView"
/>
</RelativeLayout>
+7
View File
@@ -103,6 +103,13 @@
<string name="set">Fijado</string>
<string name="positions">Posiciones</string>
<string name="recent_news">Noticias recientes</string>
<string name="no_data">No datos</string>
<string name="expand">Expande</string>
<string name="one_month">1 Mes</string>
<string name="three_month">3 Meses</string>
<string name="one_year">1 Año</string>
<string name="max">Max</string>
<string name="error_loading_graph">Error al cargar los datos del gráfico</string>
<string name="tutorial">Tutorial</string>
<string name="how_to_title">Cómo agregar un widget</string>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<string-array name="whats_new">
<item>Correcciones visuales</item>
<item>¡Los gráficos están de vuelta!</item>
</string-array>
</resources>
+1 -1
View File
@@ -9,7 +9,7 @@
<dimen name="text_size">15sp</dimen>
<dimen name="button_text">13sp</dimen>
<dimen name="graph_bottom_offset">8dp</dimen>
<integer name="text_size_small">12</integer>
<integer name="text_size_medium">14</integer>
+9
View File
@@ -5,6 +5,8 @@
<string name="suggestions_endpoint" translatable="false">https://s.yimg.com/aq/</string>
<string name="news_endpoint" translatable="false">https://newsapi.org/</string>
<string name="news_api_key" translatable="false">05bb573fdcc943f0a34bb0517f05e09b</string>
<string name="alpha_vantage_endpoint" translatable="false">https://www.alphavantage.co/</string>
<string name="alpha_vantage_api_key" translatable="false">9VQGQDBGD0L1B3IH</string>
<string name="portfolio">Portfolio</string>
<string name="widget_label">StocksWidget</string>
@@ -110,6 +112,13 @@
<string name="set">Set</string>
<string name="positions">Positions</string>
<string name="recent_news">Recent News</string>
<string name="no_data">No data</string>
<string name="expand">Expand</string>
<string name="one_month">1 Month</string>
<string name="three_month">3 Months</string>
<string name="one_year">1 Year</string>
<string name="max">Max</string>
<string name="error_loading_graph">Error loading graph</string>
<string name="tutorial">Tutorial</string>
<string name="how_to_title">How to add a widget</string>
+2
View File
@@ -36,6 +36,8 @@
<style name="NewsFeedActivityTheme" parent="AppTheme"/>
<style name="GraphActivityTheme" parent="AppTheme"/>
<style name="TextViewStyle" parent="android:Widget.TextView">
<item name="android:textSize">@dimen/text_size</item>
<item name="fontPath">fonts/Ubuntu-Regular.ttf</item>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<string-array name="whats_new">
<item>UI fixes</item>
<item>Graphs are back! Check out the news feed.</item>
</string-array>
</resources>
@@ -2,7 +2,9 @@ package com.github.premnirmal.ticker.mock
import android.content.Context
import com.github.premnirmal.ticker.components.RxBus
import com.github.premnirmal.ticker.model.IHistoryProvider
import com.github.premnirmal.ticker.model.IStocksProvider
import com.github.premnirmal.ticker.network.HistoricalDataApi
import com.github.premnirmal.ticker.network.NewsApi
import com.github.premnirmal.ticker.network.NewsProvider
import com.github.premnirmal.ticker.network.Robindahood
@@ -71,4 +73,13 @@ class MockNetworkModule {
@Provides @Singleton
internal fun provideNewsProvider(): NewsProvider = Mocker.provide(NewsProvider::class)
@Provides @Singleton
internal fun provideHistoricalDataApi(context: Context, okHttpClient: OkHttpClient,
gson: Gson, converterFactory: GsonConverterFactory,
rxJavaFactory: RxJava2CallAdapterFactory): HistoricalDataApi = Mocker.provide(HistoricalDataApi::class)
@Provides @Singleton
internal fun provideHistoricalDataProvider(): IHistoryProvider =
Mocker.provide(IHistoryProvider::class)
}
+2 -2
View File
@@ -1,2 +1,2 @@
versionName=2.4.06
versionCode=236
versionName=2.5.02
versionCode=239
-1
View File
@@ -14,5 +14,4 @@
# org.gradle.parallel=true
org.gradle.jvmargs=-Xmx2560m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.caching=true
android.enableAapt2=false
android.enableBuildCache=true