feat(perps): add TradingView candle chart#6513
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an opt-in TradingView candlestick chart and localized market descriptions to perps market details.
Changes:
- Adds TradingView chart rendering, attribution, and a debug preference toggle.
- Persists and displays localized perps market descriptions.
- Adds the Room migration and migration test for descriptions.
Reviewed changes
Copilot reviewed 17 out of 18 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
build.gradle.kts |
Defines the chart library version. |
app/build.gradle.kts |
Adds the TradingView dependency. |
Constants.kt |
Defines the chart preference key. |
TradingViewCandleChart.kt |
Implements the TradingView chart. |
CandleChart.kt |
Selects the configured chart implementation. |
PerpsMarketDetailPage.kt |
Displays localized market information. |
PerpsMarket.kt |
Adds market descriptions. |
PerpsDatabase.kt |
Adds database migration 5→6. |
PerpsMigrationTest.kt |
Tests the migration. |
6.json |
Records Room schema version 6. |
LogAndDebugFragment.kt |
Manages the chart toggle. |
fragment_log_debug.xml |
Adds the toggle UI. |
AboutPage.kt |
Adds Compose attribution. |
AboutFragment.kt |
Adds legacy attribution behavior. |
fragment_about.xml |
Adds legacy attribution UI. |
values/strings.xml |
Adds English labels. |
values-zh-rCN/strings.xml |
Adds Simplified Chinese labels. |
values-zh-rTW/strings.xml |
Adds Traditional Chinese labels. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| CandlestickSeriesOptions( | ||
| upColor = upColor.toArgb().toIntColor(), | ||
| downColor = downColor.toArgb().toIntColor(), | ||
| wickUpColor = upColor.toArgb().toIntColor(), | ||
| wickDownColor = downColor.toArgb().toIntColor(), | ||
| borderVisible = false, | ||
| lastValueVisible = false, | ||
| priceLineVisible = false, | ||
| ), |
| object : GestureDetector.SimpleOnGestureListener() { | ||
| override fun onLongPress(e: MotionEvent) { | ||
| longPressActive = true | ||
| targetView?.requestDisallowInterceptTouchEvent(true) | ||
| } | ||
| }, |
5029c67 to
72497c2
Compare
| <string name="a_video">a video</string> | ||
| <string name="About">About</string> | ||
| <string name="Use_TradingView_Candlestick_Chart">Use TradingView candlestick chart</string> | ||
| <string name="Charts_by_TradingView">Charts by TradingView</string> |
| <TextView | ||
| android:layout_width="wrap_content" | ||
| android:layout_height="wrap_content" | ||
| android:layout_centerVertical="true" | ||
| android:layout_marginStart="@dimen/activity_horizontal_margin" | ||
| android:text="@string/Use_TradingView_Candlestick_Chart" | ||
| android:textColor="@color/colorBlue" | ||
| android:textSize="16sp" | ||
| tools:ignore="RelativeOverlap" /> |
Keep the existing renderer as default and expose TradingView through Log & Debug for comparison.
72497c2 to
ddacb27
Compare
| time = Time.Utc(item.timestamp), | ||
| open = open, | ||
| high = high, | ||
| low = low, | ||
| close = close, | ||
| ) | ||
| } | ||
| } | ||
| val candlesByTimestamp = | ||
| remember(candles) { | ||
| candles.firstOrNull()?.items.orEmpty().associateBy(CandleItem::timestamp) | ||
| } |
| ), | ||
| ) | ||
| subscribeOnChartStateChange { state -> | ||
| errorMessage = (state as? ChartsView.State.Error)?.exception?.localizedMessage |
| <androidx.appcompat.widget.SwitchCompat | ||
| android:id="@+id/trading_view_chart_sc" | ||
| android:layout_width="wrap_content" | ||
| android:layout_height="wrap_content" | ||
| android:layout_alignParentEnd="true" | ||
| android:layout_centerVertical="true" | ||
| android:layout_marginEnd="@dimen/activity_horizontal_margin" | ||
| tools:ignore="RelativeOverlap" /> |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 11 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
app/src/main/java/one/mixin/android/ui/home/web3/trade/TradingViewCandleChart.kt:113
- The candle API timestamps are milliseconds (the iOS client consuming the same endpoint converts
item.timestamp / 1000), but lightweight-charts v4Time.Utcexpects epoch seconds. Passing the raw value places every candle tens of thousands of years in the future. Convert to seconds here and key the crosshair lookup by that converted value as well.
time = Time.Utc(item.timestamp),
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 11 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
app/src/main/java/one/mixin/android/ui/home/web3/trade/TradingViewCandleChart.kt:113
CandleItem.timestampis epoch milliseconds, but TradingView v4'sTime.Utcexpects epoch seconds. Passing the raw value makes chart dates and candle intervals 1,000× too large. Normalize both the series data and the crosshair lookup keys to seconds so tooltip selection still resolves the corresponding candle.
time = Time.Utc(item.timestamp),
| timeFrame: String, | ||
| ): String { | ||
| val millis = if (timestamp < MILLIS_TIMESTAMP_THRESHOLD) timestamp * 1000 else timestamp | ||
| val zoneId = ZoneId.systemDefault() |
| pinch = true | ||
| } | ||
| } | ||
| chartApi = api |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 13 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
app/src/main/java/one/mixin/android/ui/home/web3/trade/TradingViewCandleChart.kt:113
Time.Utcin lightweight-charts v4 expects epoch seconds, while perps candle timestamps are epoch milliseconds. Passing milliseconds places every bar far in the future; normalize both the series time and lookup-map key so crosshair selections still resolve.
time = Time.Utc(item.timestamp),
| val utcTimestamp = normalizeTradingViewTimestamp(timestamp) | ||
| val offsetSeconds = Instant.ofEpochSecond(utcTimestamp).atZone(zoneId).offset.totalSeconds | ||
| return utcTimestamp + offsetSeconds |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 16 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (2)
app/src/main/java/one/mixin/android/ui/home/web3/trade/TradingViewCandleChart.kt:476
- This offset-based conversion collapses distinct candles during the daylight-saving fall-back hour (for example, 05:30Z at UTC-4 and 06:30Z at UTC-5 both become 01:30). That produces duplicate
Time.Utcvalues forsetData, andcandlesByTimestamp.associateByalso overwrites one candle. Keep normalized UTC epoch seconds as the chart/key identity and apply the local zone only when formatting labels.
val utcTimestamp = normalizeTradingViewTimestamp(timestamp)
val offsetSeconds = Instant.ofEpochSecond(utcTimestamp).atZone(zoneId).offset.totalSeconds
return utcTimestamp + offsetSeconds
app/src/main/res/layout/fragment_log_debug.xml:110
- The new switch has no accessible name: its sibling
TextViewis not associated with it vialabelFor, so TalkBack announces only an unlabeled switch state. Give the switch this string as its content description (or explicitly associate the label) so users know what it controls.
<androidx.appcompat.widget.SwitchCompat
| assertEquals( | ||
| utcTimestamp + 8 * 60 * 60, | ||
| tradingViewLocalTimestamp(utcTimestamp, ZoneOffset.ofHours(8)), | ||
| ) |
| assertEquals( | ||
| 1_721_600_000L + 8 * 60 * 60, | ||
| tradingViewLocalTimestamp(utcTimestampMillis, ZoneOffset.ofHours(8)), | ||
| ) |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 16 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
app/src/main/java/one/mixin/android/ui/home/web3/trade/TradingViewCandleChart.kt:475
- Adding each instant's zone offset makes timestamps collide during a daylight-saving fall-back. For example, consecutive 01:00 EDT and 01:00 EST candles both map to the same shifted epoch value;
setDatarequires strictly ascending candle times, andassociateByalso drops one of them. Preserve unique UTC timestamps and localize labels separately (or use another monotonic timezone strategy), with a DST-boundary test.
val utcTimestamp = normalizeTradingViewTimestamp(timestamp)
val offsetSeconds = Instant.ofEpochSecond(utcTimestamp).atZone(zoneId).offset.totalSeconds
return utcTimestamp + offsetSeconds
app/src/main/res/layout/fragment_log_debug.xml:117
- The switch has no accessible label, so a screen reader announces only an unlabeled on/off control. Associate the visible text with this switch or provide an equivalent content description.
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/trading_view_chart_sc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
tools:ignore="RelativeOverlap" />
| internal fun tradingViewTouchCoordinate( | ||
| touchX: Float, | ||
| density: Float, | ||
| ): Float = if (density > 0f) touchX / density else touchX |
No description provided.