Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/cache #20

Open
wants to merge 52 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
8d43411
base navigation
LucasPrioste92 Apr 5, 2024
673aeb2
Merge branch 'feature/splash-screen' into feature/navigation
LucasPrioste92 Apr 5, 2024
907f2ce
update colors background and add scaffold to support bottom bar
LucasPrioste92 Apr 8, 2024
14c67a3
bottom bar
LucasPrioste92 Apr 8, 2024
388f4f2
route with arg symbolId on Detail screen
LucasPrioste92 Apr 8, 2024
398453d
initial setup alpaca API
LucasPrioste92 Apr 8, 2024
3f5ed1f
update dimensions
LucasPrioste92 Apr 8, 2024
51bce36
Merge branch 'feature/navigation' into feature/alpaca-api
LucasPrioste92 Apr 8, 2024
31d797e
update alpaca news service
LucasPrioste92 Apr 10, 2024
739ee01
- refactor alpaca news ws service
LucasPrioste92 Apr 11, 2024
201b4b2
- alpaca news API
LucasPrioste92 Apr 11, 2024
b539b0c
- alpaca news API
LucasPrioste92 Apr 11, 2024
2798774
- setup Unit Test libs
LucasPrioste92 Apr 11, 2024
d26ca4f
- faker dependency
LucasPrioste92 Apr 12, 2024
2b44d41
- unit test (NewsRepository)
LucasPrioste92 Apr 12, 2024
734e555
- unit test (ChangeFilterNews UseCase)
LucasPrioste92 Apr 12, 2024
e487af3
- main carousel news
LucasPrioste92 Apr 17, 2024
84360dd
- main carousel news add anim
LucasPrioste92 Apr 18, 2024
aac9174
- show all news
LucasPrioste92 Apr 19, 2024
0b8c085
- real time news
LucasPrioste92 Apr 22, 2024
80c43b2
- pagination news
LucasPrioste92 Apr 22, 2024
dd409ca
- date picker ui
LucasPrioste92 Apr 24, 2024
3f639fa
- logic to apply filters on VM
LucasPrioste92 Apr 26, 2024
9a765df
- unit test
LucasPrioste92 Apr 29, 2024
fa5c993
- update libs
LucasPrioste92 Apr 29, 2024
232d958
- update symbol crypto
LucasPrioste92 Apr 29, 2024
660586c
- assets API
LucasPrioste92 May 2, 2024
9d0225c
- refactor alpaca data API
LucasPrioste92 May 2, 2024
92f6b79
- trades, quotes, bars info
LucasPrioste92 May 2, 2024
faaf727
- use case to get realtime tardes, quotes and bar
LucasPrioste92 May 6, 2024
0616d85
- unit test MarketRemoteDataSource and AssetRemoteDataSource
LucasPrioste92 May 6, 2024
b6235b6
- unit test AssetsRepository
LucasPrioste92 May 6, 2024
107ed0d
- search input assets
LucasPrioste92 May 8, 2024
5f26ec9
- handle error assets list
LucasPrioste92 May 13, 2024
cda4490
- ui test HomeViewModel
LucasPrioste92 May 13, 2024
378bf20
- header detail screen
LucasPrioste92 May 13, 2024
972655e
- base chart
LucasPrioste92 May 15, 2024
c12e93a
- new version chart
LucasPrioste92 May 20, 2024
40285a7
- update chart
LucasPrioste92 May 21, 2024
9c475d8
- update chart
LucasPrioste92 May 23, 2024
14785b8
- get real time quotes
LucasPrioste92 May 24, 2024
2b2ab02
- get trades
LucasPrioste92 May 28, 2024
2312385
- enabled pull to refresh
LucasPrioste92 May 28, 2024
68d5979
- chart just consume horizontal dragging
LucasPrioste92 May 28, 2024
5253271
- unit test DetailViewModel
LucasPrioste92 May 29, 2024
a85b968
setup room db
LucasPrioste92 May 31, 2024
978cf5c
cache all assets
LucasPrioste92 Jun 4, 2024
90efc9b
cache news and logic to clean cache and retrieve news when the app is…
LucasPrioste92 Jun 12, 2024
cb01fec
update popBackStack to navigateUp
LucasPrioste92 Jun 12, 2024
d2e4719
fix bug when user is offline and data is not being loaded from cache
LucasPrioste92 Jun 12, 2024
c593db9
unit test Assets Cached
LucasPrioste92 Jun 12, 2024
88df26d
unit test News Cached
LucasPrioste92 Jun 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
- base chart
LucasPrioste92 committed May 15, 2024
commit 972655e315f58a6a7ab1db8ca594b6b9ad757801
83 changes: 82 additions & 1 deletion app/src/main/java/dev/pinkroom/marketsight/common/Utils.kt
Original file line number Diff line number Diff line change
@@ -6,12 +6,15 @@ import dev.pinkroom.marketsight.R
import dev.pinkroom.marketsight.common.Constants.ALL_SYMBOLS
import dev.pinkroom.marketsight.domain.model.assets.AssetFilter
import dev.pinkroom.marketsight.domain.model.assets.TypeAsset
import dev.pinkroom.marketsight.domain.model.bars_asset.AssetChartInfo
import dev.pinkroom.marketsight.domain.model.bars_asset.BarAsset
import dev.pinkroom.marketsight.domain.model.bars_asset.FilterHistoricalBar
import dev.pinkroom.marketsight.domain.model.bars_asset.TimeFrame
import dev.pinkroom.marketsight.domain.model.common.DateTimeUnit
import dev.pinkroom.marketsight.domain.model.common.SubInfoSymbols
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import java.text.DecimalFormat
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZoneOffset
@@ -57,6 +60,8 @@ fun LocalDateTime.formatToStandardIso(): String = format(DateTimeFormatter.ofPat

fun LocalDate.atEndOfTheDay(): LocalDateTime = atTime(23,59,59).atOffset(ZoneOffset.UTC).toLocalDateTime()

fun Double.formatToString() = DecimalFormat("#.#").format(this)

sealed class ActionAlpaca(val action: String) {
data object Subscribe: ActionAlpaca(action = "subscribe")
data object Unsubscribe: ActionAlpaca(action = "unsubscribe")
@@ -193,4 +198,80 @@ val popularSymbols = listOf(
symbol = "SHIBUSD",
isSubscribed = false,
),
)
)

fun mockChartData(): AssetChartInfo {
val barsAsset = listOf(
BarAsset(
closingPrice = 429.29,
timestamp = LocalDateTime.of(2020,7,1,4,0,0),
),
BarAsset(
closingPrice = 705.64,
timestamp = LocalDateTime.of(2020,10,1,4,0,0),
),
BarAsset(
closingPrice = 668.01,
timestamp = LocalDateTime.of(2021,1,1,4,0,0),
),
BarAsset(
closingPrice = 679.83,
timestamp = LocalDateTime.of(2021,4,1,4,0,0),
),
BarAsset(
closingPrice = 775.01,
timestamp = LocalDateTime.of(2021,7,1,4,0,0),
),
BarAsset(
closingPrice = 1056.86,
timestamp = LocalDateTime.of(2021,10,1,4,0,0),
),
BarAsset(
closingPrice = 1077.77,
timestamp = LocalDateTime.of(2022,1,1,4,0,0),
),
BarAsset(
closingPrice = 674.5,
timestamp = LocalDateTime.of(2022,4,1,4,0,0),
),
BarAsset(
closingPrice = 265.11,
timestamp = LocalDateTime.of(2022,7,1,4,0,0),
),
BarAsset(
closingPrice = 123.22,
timestamp = LocalDateTime.of(2022,10,1,4,0,0),
),
BarAsset(
closingPrice = 207.41,
timestamp = LocalDateTime.of(2023,1,1,4,0,0),
),
BarAsset(
closingPrice = 261.73,
timestamp = LocalDateTime.of(2023,4,1,4,0,0),
),
BarAsset(
closingPrice = 250.22,
timestamp = LocalDateTime.of(2023,7,1,4,0,0),
),
BarAsset(
closingPrice = 248.46,
timestamp = LocalDateTime.of(2023,10,1,4,0,0),
),
BarAsset(
closingPrice = 275.72,
timestamp = LocalDateTime.of(2024,1,1,4,0,0),
),
BarAsset(
closingPrice = 371.92,
timestamp = LocalDateTime.of(2024,4,1,4,0,0),
),
)
val maxValue = barsAsset.maxOfOrNull { it.closingPrice }!!
val minValue = barsAsset.minOfOrNull { it.closingPrice }!!
return AssetChartInfo(
upperValue = maxValue,
lowerValue = minValue,
barsInfo = barsAsset,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dev.pinkroom.marketsight.domain.model.bars_asset

data class AssetChartInfo(
val upperValue: Double = 0.0,
val lowerValue: Double = 0.0,
val barsInfo: List<BarAsset> = emptyList(),
)
Original file line number Diff line number Diff line change
@@ -3,13 +3,13 @@ package dev.pinkroom.marketsight.domain.model.bars_asset
import java.time.LocalDateTime

data class BarAsset(
val closingPrice: Double,
val highPrice: Double,
val lowPrice: Double,
val tradeCountInBar: Int,
val openingPrice: Double,
val timestamp: LocalDateTime,
val barVolume: Double,
val volumeWeightedAvgPrice: Double,
val closingPrice: Double = 0.0,
val highPrice: Double = 0.0,
val lowPrice: Double = 0.0,
val tradeCountInBar: Int = 0,
val openingPrice: Double = 0.0,
val timestamp: LocalDateTime = LocalDateTime.now(),
val barVolume: Double = 0.0,
val volumeWeightedAvgPrice: Double = 0.0,
val symbol: String? = null,
)
Original file line number Diff line number Diff line change
@@ -71,6 +71,7 @@ class Dimensions(
val assetCardHeight: Dp = 90.dp,
val emptyContentMaxHeight: Float = 0.95f,
val bottomSheetHeight: Float = 0.45f,
val spacingItemChart: Dp = 10.dp
)

val dimens: Dimensions
Original file line number Diff line number Diff line change
@@ -2,14 +2,18 @@ package dev.pinkroom.marketsight.presentation.detail_screen

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import dev.pinkroom.marketsight.common.mockChartData
import dev.pinkroom.marketsight.domain.model.assets.Asset
import dev.pinkroom.marketsight.domain.model.common.StatusUiRequest
import dev.pinkroom.marketsight.presentation.core.components.PullToRefreshLazyColumn
import dev.pinkroom.marketsight.presentation.core.theme.dimens
import dev.pinkroom.marketsight.presentation.detail_screen.components.AssetChart
import dev.pinkroom.marketsight.presentation.detail_screen.components.HeaderDetail

@Composable
@@ -37,6 +41,15 @@ fun DetailScreen(
onBack = onBack,
)
}
item {
AssetChart(
modifier = Modifier
.padding(horizontal = dimens.horizontalPadding, vertical = 10.dp)
.fillMaxWidth()
.height(380.dp),
chartInfo = mockChartData(),
)
}
}
}

Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ package dev.pinkroom.marketsight.presentation.detail_screen

import dev.pinkroom.marketsight.common.historicalBarFilters
import dev.pinkroom.marketsight.domain.model.assets.Asset
import dev.pinkroom.marketsight.domain.model.bars_asset.BarAsset
import dev.pinkroom.marketsight.domain.model.bars_asset.AssetChartInfo
import dev.pinkroom.marketsight.domain.model.bars_asset.FilterHistoricalBar
import dev.pinkroom.marketsight.domain.model.common.StatusUiRequest

@@ -14,5 +14,5 @@ data class DetailUiState(
val asset: Asset = Asset(),
val filtersHistoricalBar: List<FilterHistoricalBar> = historicalBarFilters,
val selectedFilter: FilterHistoricalBar = filtersHistoricalBar.first(),
val bars: List<BarAsset> = emptyList(),
val assetCharInfo: AssetChartInfo = AssetChartInfo(),
)
Original file line number Diff line number Diff line change
@@ -78,10 +78,13 @@ class DetailViewModel @Inject constructor(
)
when(response) {
is Resource.Success -> {
val maxValue = response.data.maxOfOrNull { it.closingPrice } ?: 0.0
val minValue = response.data.minOfOrNull { it.closingPrice } ?: 0.0

_uiState.update {
it.copy(
statusHistoricalBars = it.statusHistoricalBars.copy(isLoading = false, errorMessage = null),
bars = response.data,
assetCharInfo = it.assetCharInfo.copy(upperValue = maxValue, lowerValue = minValue, barsInfo = response.data),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
package dev.pinkroom.marketsight.presentation.detail_screen.components

import android.graphics.Paint
import android.graphics.Paint.Align
import android.graphics.PointF
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.asAndroidPath
import androidx.compose.ui.graphics.asComposePath
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import dev.pinkroom.marketsight.common.formatToString
import dev.pinkroom.marketsight.domain.model.bars_asset.AssetChartInfo
import dev.pinkroom.marketsight.domain.model.bars_asset.BarAsset
import dev.pinkroom.marketsight.presentation.core.theme.Green
import dev.pinkroom.marketsight.presentation.core.theme.dimens
import java.time.LocalDateTime

@Composable
fun AssetChart(
modifier: Modifier,
chartInfo: AssetChartInfo,
graphColor: Color = Green,
) {
val density = LocalDensity.current

val transparentGraphColor = remember { graphColor.copy(alpha = 0.5f) }
val textPaintY = remember(density) {
Paint().apply {
color = android.graphics.Color.BLACK
textAlign = Align.LEFT
textSize = density.run { 12.sp.toPx() }
}
}
val textPaintX = remember(density) {
Paint().apply {
color = android.graphics.Color.BLACK
textAlign = Align.CENTER
textSize = density.run { 10.sp.toPx() }
}
}

val coordinates = mutableListOf<PointF>()
val controlPoints1 = mutableListOf<PointF>()
val controlPoints2 = mutableListOf<PointF>()

Canvas(
modifier = modifier
//.background(color = Green)
) {
// Placing x axis info
val addedYears = mutableListOf<Int>()
val spacingStart = 2
val spacePerHour = size.width / (chartInfo.barsInfo.size+1)
(0 until chartInfo.barsInfo.size).forEach { i ->
val info = chartInfo.barsInfo[i]
val hour = info.timestamp.year
if (!addedYears.contains(hour)) {
addedYears.add(hour)
drawContext.canvas.nativeCanvas.apply {
drawText(
hour.toString(),
((i+spacingStart) * spacePerHour),
size.height - 55,
textPaintX
)
}
}
}

// Placing y axis info
val heightAxisY = size.height + 8
val maxItemsY = 6
val yAxisSpace = ( size.height / (maxItemsY+1) )
val priceStep = (chartInfo.upperValue - chartInfo.lowerValue) / maxItemsY.toFloat()
(0..maxItemsY).forEach { i ->
drawContext.canvas.nativeCanvas.apply {
drawText(
(chartInfo.lowerValue + priceStep * i).formatToString(),
0f,
heightAxisY - yAxisSpace * (i+1),
textPaintY
)
}
}

// Placing points
val heightPoints = size.height
val xAxisSpacePoint = (size.width) / (chartInfo.barsInfo.size+1)
(0 until chartInfo.barsInfo.size).forEach { i ->
val info = chartInfo.barsInfo[i]
val lowerValue = chartInfo.lowerValue
val upperValue = chartInfo.upperValue

val ratio = (info.closingPrice - lowerValue) / (upperValue - lowerValue)
val x1 = xAxisSpacePoint * (i + spacingStart)
val y1 = (heightPoints-yAxisSpace) - (ratio * (heightPoints-yAxisSpace)).toFloat()
coordinates.add(PointF(x1, y1))

drawCircle(
color = Color.Black,
radius = 6f,
center = Offset(x1, y1),
)
}

/** calculating the connection points */
for (i in 1 until coordinates.size) {
controlPoints1.add(PointF((coordinates[i].x + coordinates[i - 1].x) / 2, coordinates[i - 1].y))
controlPoints2.add(PointF((coordinates[i].x + coordinates[i - 1].x) / 2, coordinates[i].y))
}

// Drawing the path
val strokePath = Path().apply {
reset()
moveTo(coordinates.first().x, coordinates.first().y)
for (i in 0 until coordinates.size - 1) {
cubicTo(
controlPoints1[i].x,controlPoints1[i].y,
controlPoints2[i].x,controlPoints2[i].y,
coordinates[i + 1].x,coordinates[i + 1].y
)
}
}

drawPath(
path = strokePath,
color = Color.Red,
style = Stroke(
width = 2f,
cap = StrokeCap.Round
)
)

/** filling the area under the path */
val fillPath = android.graphics.Path(strokePath.asAndroidPath())
.asComposePath()
.apply {
lineTo(coordinates.last().x, size.height - yAxisSpace)
lineTo(coordinates.first().x, size.height - yAxisSpace)
close()
}
drawPath(
fillPath,
brush = Brush.verticalGradient(
listOf(
Color.Cyan,
Color.Transparent,
),
endY = size.height - yAxisSpace
),
)
}
}

@Preview(
showSystemUi = true,
showBackground = true,
)
@Composable
fun AssetCharPreview() {
val barsAsset = listOf(
BarAsset(
closingPrice = 429.29,
timestamp = LocalDateTime.of(2020,7,1,4,0,0),
),
BarAsset(
closingPrice = 705.64,
timestamp = LocalDateTime.of(2020,10,1,4,0,0),
),
BarAsset(
closingPrice = 668.01,
timestamp = LocalDateTime.of(2021,1,1,4,0,0),
),
BarAsset(
closingPrice = 679.83,
timestamp = LocalDateTime.of(2021,4,1,4,0,0),
),
BarAsset(
closingPrice = 775.01,
timestamp = LocalDateTime.of(2021,7,1,4,0,0),
),
BarAsset(
closingPrice = 1056.86,
timestamp = LocalDateTime.of(2021,10,1,4,0,0),
),
BarAsset(
closingPrice = 1077.77,
timestamp = LocalDateTime.of(2022,1,1,4,0,0),
),
BarAsset(
closingPrice = 674.5,
timestamp = LocalDateTime.of(2022,4,1,4,0,0),
),
BarAsset(
closingPrice = 265.11,
timestamp = LocalDateTime.of(2022,7,1,4,0,0),
),
BarAsset(
closingPrice = 123.22,
timestamp = LocalDateTime.of(2022,10,1,4,0,0),
),
BarAsset(
closingPrice = 207.41,
timestamp = LocalDateTime.of(2023,1,1,4,0,0),
),
BarAsset(
closingPrice = 261.73,
timestamp = LocalDateTime.of(2023,4,1,4,0,0),
),
BarAsset(
closingPrice = 250.22,
timestamp = LocalDateTime.of(2023,7,1,4,0,0),
),
BarAsset(
closingPrice = 248.46,
timestamp = LocalDateTime.of(2023,10,1,4,0,0),
),
BarAsset(
closingPrice = 275.72,
timestamp = LocalDateTime.of(2024,1,1,4,0,0),
),
BarAsset(
closingPrice = 371.92,
timestamp = LocalDateTime.of(2024,4,1,4,0,0),
),
)
val maxValue = barsAsset.maxOfOrNull { it.closingPrice }!!
val minValue = barsAsset.minOfOrNull { it.closingPrice }!!
AssetChart(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = dimens.horizontalPadding, vertical = 10.dp)
.height(380.dp),
chartInfo = AssetChartInfo(
upperValue = maxValue,
lowerValue = minValue,
barsInfo = barsAsset,
),
)
}