Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a95b05dada | |||
| 10cc064f1f | |||
| 952c8261e9 |
@@ -24,8 +24,8 @@ android {
|
||||
applicationId = "me.khodak.claudeusage"
|
||||
minSdk = 26
|
||||
targetSdk = 34
|
||||
versionCode = 20
|
||||
versionName = "1.19"
|
||||
versionCode = 21
|
||||
versionName = "1.20"
|
||||
}
|
||||
|
||||
val releaseStorePassword = signingCred("KEYSTORE_PASSWORD", "storePassword")
|
||||
|
||||
@@ -84,13 +84,20 @@ class ClaudeUsageWidget : AppWidgetProvider() {
|
||||
return v
|
||||
}
|
||||
|
||||
// Full-size builder follows the user's chosen style; the compact (1-cell-tall) size
|
||||
// always uses bars, since two rings don't fit that height.
|
||||
val rings = prefs.getWidgetStyle() == PreferencesManager.STYLE_RINGS
|
||||
fun buildFull() =
|
||||
if (rings) buildRingViews(context, prefs, apiData)
|
||||
else buildViews(context, prefs, apiData)
|
||||
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
RemoteViews(mapOf(
|
||||
SizeF(50f, 50f) to attach(buildSmallViews(context, prefs, apiData)),
|
||||
SizeF(50f, 120f) to attach(buildViews(context, prefs, apiData))
|
||||
SizeF(50f, 120f) to attach(buildFull())
|
||||
))
|
||||
} else {
|
||||
attach(buildViews(context, prefs, apiData))
|
||||
attach(buildFull())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,6 +259,80 @@ class ClaudeUsageWidget : AppWidgetProvider() {
|
||||
return v
|
||||
}
|
||||
|
||||
/**
|
||||
* Ring style of the full widget — same data/branching as [buildViews] but each metric is a
|
||||
* circular [RingRenderer] gauge (percentage drawn in the center) instead of a bar.
|
||||
*/
|
||||
private fun buildRingViews(context: Context, prefs: PreferencesManager, apiData: UsageData?): RemoteViews {
|
||||
val v = RemoteViews(context.packageName, R.layout.widget_layout_rings)
|
||||
applyPeak(v, showText = true)
|
||||
|
||||
if (!prefs.isLoggedIn()) {
|
||||
v.setImageViewBitmap(R.id.ring_session, RingRenderer.render(0, null, SESSION_FILL, null, "—", "SESSION"))
|
||||
v.setImageViewBitmap(R.id.ring_weekly, RingRenderer.render(0, null, WEEKLY_FILL, null, "—", "WEEKLY"))
|
||||
v.setTextViewText(R.id.tv_session_label, "Not signed in")
|
||||
v.setTextViewText(R.id.tv_weekly_label, "Open app to sign in")
|
||||
v.setTextViewText(R.id.tv_status, "")
|
||||
return v
|
||||
}
|
||||
|
||||
// ── 5-hour session ring ──────────────────────────────────────────
|
||||
val hasUtilization = apiData != null && apiData.fiveHourUtilization >= 0f
|
||||
val hasApiMessages = apiData != null && apiData.effectiveRemaining >= 0 && apiData.messagesLimit > 0
|
||||
val sessionStart = prefs.getSessionStart()
|
||||
when {
|
||||
hasUtilization -> {
|
||||
val pct = apiData!!.fiveHourUtilization.toInt()
|
||||
v.setImageViewBitmap(R.id.ring_session, RingRenderer.render(pct, null, SESSION_FILL, null, "$pct%", "SESSION"))
|
||||
v.setTextViewText(R.id.tv_session_label, formatReset(apiData.utilizationResetAtEpoch))
|
||||
}
|
||||
hasApiMessages -> {
|
||||
val pct = apiData!!.progressPercent
|
||||
v.setImageViewBitmap(R.id.ring_session, RingRenderer.render(pct, null, SESSION_FILL, null, "$pct%", "SESSION"))
|
||||
v.setTextViewText(R.id.tv_session_label, formatReset(apiData.resetAtEpoch))
|
||||
}
|
||||
sessionStart > 0 -> {
|
||||
v.setImageViewBitmap(R.id.ring_session, RingRenderer.render(0, null, SESSION_FILL, null, "·", "SESSION"))
|
||||
v.setTextViewText(R.id.tv_session_label, "session active")
|
||||
}
|
||||
else -> {
|
||||
v.setImageViewBitmap(R.id.ring_session, RingRenderer.render(0, null, SESSION_FILL, null, "—", "SESSION"))
|
||||
v.setTextViewText(R.id.tv_session_label, "")
|
||||
}
|
||||
}
|
||||
|
||||
// ── 7-day weekly ring (with pace tick) ───────────────────────────
|
||||
val hasWeekly = apiData != null && apiData.weeklyUtilization >= 0f
|
||||
if (hasWeekly) {
|
||||
val wPct = apiData!!.weeklyUtilization.toInt()
|
||||
val pace = PaceCalc.compute(apiData.weeklyUtilization, apiData.weeklyResetAtEpoch, PaceCalc.WEEKLY_WINDOW_MS)
|
||||
v.setImageViewBitmap(
|
||||
R.id.ring_weekly,
|
||||
RingRenderer.render(wPct, pace?.markerPct, WEEKLY_FILL, if (pace != null) MARKER_COLOR else null, "$wPct%", "WEEKLY")
|
||||
)
|
||||
v.setTextViewText(R.id.tv_weekly_label, formatResetDay(apiData.weeklyResetAtEpoch))
|
||||
} else {
|
||||
val weeklyDays = Integer.bitCount(prefs.getWeeklyMask())
|
||||
v.setImageViewBitmap(R.id.ring_weekly, RingRenderer.render(0, null, WEEKLY_FILL, null, "${weeklyDays}d", "WEEKLY"))
|
||||
v.setTextViewText(R.id.tv_weekly_label, "active this week")
|
||||
}
|
||||
|
||||
// ── Footer ───────────────────────────────────────────────────────
|
||||
val resetEpoch = apiData?.effectiveResetEpoch ?: -1L
|
||||
val status = when {
|
||||
isRefreshing -> "Refreshing…"
|
||||
apiData?.isRateLimited == true -> "Rate limited · ${formatReset(resetEpoch)}"
|
||||
else -> ""
|
||||
}
|
||||
val updatedMs = apiData?.lastUpdated ?: 0L
|
||||
v.setTextViewText(R.id.tv_status,
|
||||
if (status.isNotBlank()) status else if (updatedMs > 0) formatTime(updatedMs) else "")
|
||||
v.setInt(R.id.btn_refresh, "setColorFilter",
|
||||
if (isRefreshing) 0xFFCC785C.toInt() else 0xFF999999.toInt())
|
||||
v.setFloat(R.id.btn_refresh, "setRotation", currentRotation)
|
||||
return v
|
||||
}
|
||||
|
||||
private const val SESSION_FILL = 0xFFCC785C.toInt()
|
||||
private const val WEEKLY_FILL = 0xFF7B8FCC.toInt()
|
||||
private const val MARKER_COLOR = 0xFFFFFFFF.toInt() // single-color pace marker (weekly only)
|
||||
|
||||
@@ -66,6 +66,7 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
setupNotificationSettings()
|
||||
setupWidgetStyleSetting()
|
||||
|
||||
binding.btnDebug.setOnClickListener {
|
||||
if (binding.tvDebugInfo.visibility == android.view.View.GONE) {
|
||||
@@ -112,6 +113,15 @@ class MainActivity : AppCompatActivity() {
|
||||
if (prefs.isNotifyEnabled()) requestNotificationPermission()
|
||||
}
|
||||
|
||||
/** Bars ⇄ Rings switch for the home-screen widget; redraws any placed widgets immediately. */
|
||||
private fun setupWidgetStyleSetting() {
|
||||
binding.switchRingStyle.isChecked = prefs.getWidgetStyle() == PreferencesManager.STYLE_RINGS
|
||||
binding.switchRingStyle.setOnCheckedChangeListener { _, checked ->
|
||||
prefs.setWidgetStyle(if (checked) PreferencesManager.STYLE_RINGS else PreferencesManager.STYLE_BARS)
|
||||
ClaudeUsageWidget.notifyDataChanged(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestNotificationPermission() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package me.khodak.claudeusage
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.RectF
|
||||
import android.graphics.Typeface
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
/**
|
||||
* Draws a usage gauge as a circular ring Bitmap: rounded track ring + colored progress arc
|
||||
* (sweeping clockwise from 12 o'clock) + an optional radial pace tick + center percentage and
|
||||
* label. The ring counterpart to [BarRenderer] — same Bitmap contract, so it drops into both the
|
||||
* home-screen widget (setImageViewBitmap) and the in-app card and renders identically.
|
||||
*
|
||||
* Design mirrors hamed-elfayome/Claude-Usage-Tracker's ring gauges, using this app's existing
|
||||
* palette (SESSION_FILL clay / WEEKLY_FILL periwinkle, #252525 track, white pace marker).
|
||||
*/
|
||||
object RingRenderer {
|
||||
|
||||
private const val TRACK_COLOR = 0xFF252525.toInt()
|
||||
private const val CENTER_TEXT_COLOR = 0xFFFFFFFF.toInt()
|
||||
private const val LABEL_TEXT_COLOR = 0xFF999999.toInt()
|
||||
|
||||
/**
|
||||
* @param usedPct 0..100 fill of the ring
|
||||
* @param markerPct where you "should be" right now (pace), or null for no tick
|
||||
* @param fillColor ARGB progress-arc color (e.g. SESSION_FILL / WEEKLY_FILL)
|
||||
* @param markerColor ARGB pace-tick color, or null
|
||||
* @param centerText big text in the middle, e.g. "47%" (caller formats it)
|
||||
* @param labelText small caption under the number, e.g. "SESSION" (null hides it)
|
||||
* @param sizePx square bitmap edge; ring scales to fit
|
||||
* @param strokePx ring thickness
|
||||
*/
|
||||
fun render(
|
||||
usedPct: Int,
|
||||
markerPct: Int?,
|
||||
fillColor: Int,
|
||||
markerColor: Int?,
|
||||
centerText: String,
|
||||
labelText: String? = null,
|
||||
sizePx: Int = 360,
|
||||
strokePx: Float = 36f
|
||||
): Bitmap {
|
||||
val bmp = Bitmap.createBitmap(sizePx, sizePx, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bmp)
|
||||
val cx = sizePx / 2f
|
||||
val cy = sizePx / 2f
|
||||
val pad = strokePx / 2f + 2f
|
||||
val arcRect = RectF(pad, pad, sizePx - pad, sizePx - pad)
|
||||
val radius = (sizePx - strokePx) / 2f - 2f
|
||||
|
||||
// Track ring (full circle).
|
||||
val track = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = strokePx
|
||||
strokeCap = Paint.Cap.ROUND
|
||||
color = TRACK_COLOR
|
||||
}
|
||||
canvas.drawArc(arcRect, 0f, 360f, false, track)
|
||||
|
||||
// Progress arc — clockwise from 12 o'clock (-90°).
|
||||
val pct = usedPct.coerceIn(0, 100)
|
||||
if (pct > 0) {
|
||||
val fill = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = strokePx
|
||||
strokeCap = Paint.Cap.ROUND
|
||||
color = fillColor
|
||||
}
|
||||
canvas.drawArc(arcRect, -90f, pct / 100f * 360f, false, fill)
|
||||
}
|
||||
|
||||
// Pace tick — a short radial mark across the ring at the "should be here" angle.
|
||||
if (markerPct != null && markerColor != null) {
|
||||
val m = markerPct.coerceIn(0, 100)
|
||||
val ang = Math.toRadians((-90f + m / 100f * 360f).toDouble())
|
||||
val rInner = radius - strokePx / 2f - 1f
|
||||
val rOuter = radius + strokePx / 2f + 1f
|
||||
val tick = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = strokePx * 0.18f
|
||||
strokeCap = Paint.Cap.ROUND
|
||||
color = markerColor
|
||||
}
|
||||
canvas.drawLine(
|
||||
cx + (rInner * cos(ang)).toFloat(), cy + (rInner * sin(ang)).toFloat(),
|
||||
cx + (rOuter * cos(ang)).toFloat(), cy + (rOuter * sin(ang)).toFloat(),
|
||||
tick
|
||||
)
|
||||
}
|
||||
|
||||
// Center percentage.
|
||||
val hasLabel = !labelText.isNullOrEmpty()
|
||||
val valuePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = CENTER_TEXT_COLOR
|
||||
textAlign = Paint.Align.CENTER
|
||||
textSize = sizePx * 0.27f
|
||||
typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
|
||||
}
|
||||
val vfm = valuePaint.fontMetrics
|
||||
val valueBaselineShift = -(vfm.ascent + vfm.descent) / 2f
|
||||
val valueY = cy + valueBaselineShift - (if (hasLabel) sizePx * 0.07f else 0f)
|
||||
canvas.drawText(centerText, cx, valueY, valuePaint)
|
||||
|
||||
if (hasLabel) {
|
||||
val labelPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = LABEL_TEXT_COLOR
|
||||
textAlign = Paint.Align.CENTER
|
||||
textSize = sizePx * 0.105f
|
||||
letterSpacing = 0.12f
|
||||
typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
|
||||
}
|
||||
canvas.drawText(labelText!!.uppercase(), cx, cy + sizePx * 0.17f, labelPaint)
|
||||
}
|
||||
|
||||
return bmp
|
||||
}
|
||||
}
|
||||
@@ -132,6 +132,12 @@ class PreferencesManager(context: Context) {
|
||||
} catch (e: Exception) { emptyList() }
|
||||
}
|
||||
|
||||
// ── Widget style ─────────────────────────────────────────────────────────
|
||||
|
||||
/** Home-screen widget visual style: [STYLE_BARS] (default) or [STYLE_RINGS]. */
|
||||
fun getWidgetStyle(): String = prefs.getString(KEY_WIDGET_STYLE, STYLE_BARS) ?: STYLE_BARS
|
||||
fun setWidgetStyle(style: String) = prefs.edit().putString(KEY_WIDGET_STYLE, style).apply()
|
||||
|
||||
// ── Notification settings ────────────────────────────────────────────────
|
||||
|
||||
fun isNotifyEnabled(): Boolean = prefs.getBoolean(KEY_NOTIFY_ENABLED, true)
|
||||
@@ -155,6 +161,10 @@ class PreferencesManager(context: Context) {
|
||||
private const val KEY_HISTORY = "usage_history"
|
||||
private const val KEY_NOTIFY_ENABLED = "notify_enabled"
|
||||
private const val KEY_AUTH_FAILS = "auth_fail_count"
|
||||
private const val KEY_WIDGET_STYLE = "widget_style"
|
||||
|
||||
const val STYLE_BARS = "bars"
|
||||
const val STYLE_RINGS = "rings"
|
||||
|
||||
internal const val MIN_HISTORY_GAP_MS = 2 * 60 * 1000L // collapse readings <2 min apart
|
||||
private const val HISTORY_RETENTION_MS = 7 * 24 * 60 * 60 * 1000L // keep 7 days
|
||||
|
||||
@@ -291,6 +291,48 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Widget style card -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/widget_background"
|
||||
android:padding="20dp"
|
||||
android:layout_marginTop="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="RING WIDGET STYLE"
|
||||
android:textColor="#888888"
|
||||
android:textSize="11sp"
|
||||
android:letterSpacing="0.1" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/switchRingStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="Show usage as circular rings instead of bars on the full-size home-screen widget."
|
||||
android:textColor="#AAAAAA"
|
||||
android:textSize="13sp"
|
||||
android:lineSpacingExtra="3dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Notifications card -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Ring style of the full (4x2) widget. Header + footer mirror widget_layout.xml (same view
|
||||
IDs) so ClaudeUsageWidget can attach the same click intents and peak/refresh state; the middle
|
||||
swaps the two horizontal bars for two circular RingRenderer gauges side by side. -->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/widget_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/widget_background"
|
||||
android:padding="14dp">
|
||||
|
||||
<!-- Header -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Claude Pro"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="13sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/img_peak"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:src="@drawable/ic_claude_burst"
|
||||
android:contentDescription="Peak hours" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_peak"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:text=""
|
||||
android:textColor="#CC785C"
|
||||
android:textSize="9sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_refresh"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@android:drawable/ic_menu_rotate"
|
||||
android:background="@android:color/transparent"
|
||||
android:tint="#999999"
|
||||
tools:ignore="UseAppTint"
|
||||
android:contentDescription="Refresh" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Divider -->
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:background="#2A2A2A" />
|
||||
|
||||
<!-- Two ring gauges side by side — each fills its half so the ring grows as large as the
|
||||
placed widget allows (limited by the available height), instead of a fixed 86dp. -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center"
|
||||
android:weightSum="2">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ring_session"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:scaleType="fitCenter"
|
||||
android:adjustViewBounds="false"
|
||||
android:contentDescription="Session usage ring" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_session_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:gravity="center"
|
||||
android:text=""
|
||||
android:textColor="#999999"
|
||||
android:textSize="10sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ring_weekly"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:scaleType="fitCenter"
|
||||
android:adjustViewBounds="false"
|
||||
android:contentDescription="Weekly usage ring" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_weekly_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:gravity="center"
|
||||
android:text=""
|
||||
android:textColor="#999999"
|
||||
android:textSize="10sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Footer -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="6dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_status"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text=""
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="9sp"
|
||||
android:textStyle="bold"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_updated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="9sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
Reference in New Issue
Block a user