Timeline stats per manga
This commit is contained in:
@@ -7,6 +7,7 @@ import androidx.core.graphics.ColorUtils
|
||||
import com.google.android.material.R
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
object Colors {
|
||||
@@ -20,11 +21,24 @@ object Colors {
|
||||
return MaterialColors.harmonize(color, backgroundColor)
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
fun random(seed: Any): Int {
|
||||
val hue = (seed.hashCode() % 360).absoluteValue.toFloat()
|
||||
return ColorUtils.HSLToColor(floatArrayOf(hue, 0.5f, 0.5f))
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
fun of(context: Context, manga: Manga?): Int {
|
||||
val color = if (manga != null) {
|
||||
val hue = (manga.id.absoluteValue % 360).toFloat()
|
||||
ColorUtils.HSLToColor(floatArrayOf(hue, 0.5f, 0.5f))
|
||||
} else {
|
||||
context.getThemeColor(R.attr.colorSurface)
|
||||
}
|
||||
val backgroundColor = context.getThemeColor(R.attr.colorSurfaceContainerHigh)
|
||||
return MaterialColors.harmonize(color, backgroundColor)
|
||||
}
|
||||
|
||||
private fun getHue(hex: String): Float {
|
||||
val r = (hex.substring(0, 2).toInt(16)).toFloat()
|
||||
val g = (hex.substring(2, 4).toInt(16)).toFloat()
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package org.koitharu.kotatsu.stats.data
|
||||
|
||||
import androidx.collection.LongIntMap
|
||||
import androidx.collection.MutableLongIntMap
|
||||
import androidx.room.withTransaction
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.db.entity.toManga
|
||||
import org.koitharu.kotatsu.stats.domain.StatsPeriod
|
||||
import org.koitharu.kotatsu.stats.domain.StatsRecord
|
||||
import java.util.NavigableMap
|
||||
import java.util.TreeMap
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -19,13 +23,14 @@ class StatsRepository @Inject constructor(
|
||||
System.currentTimeMillis() - TimeUnit.DAYS.toMillis(period.days.toLong())
|
||||
}
|
||||
val stats = db.getStatsDao().getDurationStats(fromDate)
|
||||
val minute = TimeUnit.MINUTES.toMillis(1)
|
||||
val mangaDao = db.getMangaDao()
|
||||
val result = ArrayList<StatsRecord>(stats.size)
|
||||
var other = StatsRecord(null, 0)
|
||||
val total = stats.values.sum()
|
||||
for ((mangaId, duration) in stats) {
|
||||
val manga = mangaDao.find(mangaId)?.toManga()
|
||||
if (manga == null || duration < minute) {
|
||||
val percent = duration.toDouble() / total
|
||||
if (manga == null || percent < 0.05) {
|
||||
other = other.copy(duration = other.duration + duration)
|
||||
} else {
|
||||
result += StatsRecord(
|
||||
@@ -51,6 +56,15 @@ class StatsRepository @Inject constructor(
|
||||
time
|
||||
}
|
||||
|
||||
suspend fun getMangaTimeline(mangaId: Long): NavigableMap<Long, Int> {
|
||||
val entities = db.getStatsDao().findAll(mangaId)
|
||||
val map = TreeMap<Long, Int>()
|
||||
for (e in entities) {
|
||||
map[e.startedAt] = e.pages
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
suspend fun clearStats() {
|
||||
db.getStatsDao().clear()
|
||||
}
|
||||
|
||||
@@ -32,16 +32,4 @@ data class StatsRecord(
|
||||
isContinue = false,
|
||||
)
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
fun getColor(context: Context): Int {
|
||||
val color = if (manga != null) {
|
||||
val hue = (manga.id.absoluteValue % 360).toFloat()
|
||||
ColorUtils.HSLToColor(floatArrayOf(hue, 0.5f, 0.5f))
|
||||
} else {
|
||||
context.getThemeColor(R.attr.colorSurface)
|
||||
}
|
||||
val backgroundColor = context.getThemeColor(R.attr.colorSurfaceContainerHigh)
|
||||
return MaterialColors.harmonize(color, backgroundColor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.res.ColorStateList
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.Colors
|
||||
import org.koitharu.kotatsu.databinding.ItemStatsBinding
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.stats.domain.StatsRecord
|
||||
@@ -21,7 +22,7 @@ fun statsAD(
|
||||
bind {
|
||||
binding.textViewTitle.text = item.manga?.title ?: getString(R.string.other_manga)
|
||||
binding.textViewSummary.text = item.time.format(context.resources)
|
||||
binding.imageViewBadge.imageTintList = ColorStateList.valueOf(item.getColor(context))
|
||||
binding.imageViewBadge.imageTintList = ColorStateList.valueOf(Colors.of(context, item.manga))
|
||||
binding.root.isClickable = item.manga != null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.koitharu.kotatsu.core.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.core.ui.BaseListAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
|
||||
import org.koitharu.kotatsu.core.util.Colors
|
||||
import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
@@ -47,6 +48,7 @@ import org.koitharu.kotatsu.list.ui.adapter.ListItemType
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.stats.domain.StatsPeriod
|
||||
import org.koitharu.kotatsu.stats.domain.StatsRecord
|
||||
import org.koitharu.kotatsu.stats.ui.sheet.MangaStatsSheet
|
||||
import org.koitharu.kotatsu.stats.ui.views.PieChartView
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -88,7 +90,7 @@ class StatsActivity : BaseActivity<ActivityStatsBinding>(),
|
||||
value = (v.duration / 1000).toInt(),
|
||||
label = v.manga?.title ?: getString(R.string.other_manga),
|
||||
percent = (v.duration.toDouble() / sum).toFloat(),
|
||||
color = v.getColor(this),
|
||||
color = Colors.of(this, v.manga),
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -99,7 +101,7 @@ class StatsActivity : BaseActivity<ActivityStatsBinding>(),
|
||||
override fun onWindowInsetsChanged(insets: Insets) = Unit
|
||||
|
||||
override fun onItemClick(item: Manga, view: View) {
|
||||
startActivity(DetailsActivity.newIntent(view.context, item))
|
||||
MangaStatsSheet.show(supportFragmentManager, item)
|
||||
}
|
||||
|
||||
override fun onSegmentClick(view: PieChartView, segment: PieChartView.Segment) {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.koitharu.kotatsu.stats.ui.sheet
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.collection.IntList
|
||||
import androidx.collection.LongIntMap
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.viewModels
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.bookmarks.ui.sheet.BookmarksSheet
|
||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
|
||||
import org.koitharu.kotatsu.core.util.Colors
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.showDistinct
|
||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||
import org.koitharu.kotatsu.core.util.ext.withArgs
|
||||
import org.koitharu.kotatsu.databinding.SheetStatsMangaBinding
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.stats.ui.views.BarChartView
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MangaStatsSheet : BaseAdaptiveSheet<SheetStatsMangaBinding>() {
|
||||
|
||||
private val viewModel: MangaStatsViewModel by viewModels()
|
||||
|
||||
override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): SheetStatsMangaBinding {
|
||||
return SheetStatsMangaBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onViewBindingCreated(binding: SheetStatsMangaBinding, savedInstanceState: Bundle?) {
|
||||
super.onViewBindingCreated(binding, savedInstanceState)
|
||||
binding.textViewTitle.text = viewModel.manga.title
|
||||
binding.chartView.barColor = Colors.of(binding.root.context, viewModel.manga)
|
||||
viewModel.stats.observe(viewLifecycleOwner, ::onStatsChanged)
|
||||
viewModel.startDate.observe(viewLifecycleOwner) {
|
||||
binding.textViewStart.textAndVisible = it?.format(resources)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onStatsChanged(stats: IntList) {
|
||||
val chartView = viewBinding?.chartView ?: return
|
||||
if (stats.isEmpty()) {
|
||||
chartView.setData(emptyList())
|
||||
return
|
||||
}
|
||||
val bars = ArrayList<BarChartView.Bar>(stats.size)
|
||||
stats.forEach { pages ->
|
||||
bars.add(
|
||||
BarChartView.Bar(
|
||||
value = pages,
|
||||
label = pages.toString(),
|
||||
),
|
||||
)
|
||||
}
|
||||
chartView.setData(bars)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val ARG_MANGA = "manga"
|
||||
|
||||
private const val TAG = "MangaStatsSheet"
|
||||
|
||||
fun show(fm: FragmentManager, manga: Manga) {
|
||||
MangaStatsSheet().withArgs(1) {
|
||||
putParcelable(ARG_MANGA, ParcelableManga(manga))
|
||||
}.showDistinct(fm, TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.koitharu.kotatsu.stats.ui.sheet
|
||||
|
||||
import androidx.collection.IntList
|
||||
import androidx.collection.LongIntMap
|
||||
import androidx.collection.MutableIntList
|
||||
import androidx.collection.emptyIntList
|
||||
import androidx.collection.emptyLongIntMap
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.ui.model.DateTimeAgo
|
||||
import org.koitharu.kotatsu.core.util.ext.calculateTimeAgo
|
||||
import org.koitharu.kotatsu.core.util.ext.require
|
||||
import org.koitharu.kotatsu.stats.data.StatsRepository
|
||||
import org.koitharu.kotatsu.stats.domain.StatsRecord
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class MangaStatsViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val repository: StatsRepository,
|
||||
) : BaseViewModel() {
|
||||
|
||||
val manga = savedStateHandle.require<ParcelableManga>(MangaStatsSheet.ARG_MANGA).manga
|
||||
|
||||
val stats = MutableStateFlow<IntList>(emptyIntList())
|
||||
val startDate = MutableStateFlow<DateTimeAgo?>(null)
|
||||
|
||||
init {
|
||||
launchLoadingJob(Dispatchers.Default) {
|
||||
val timeline = repository.getMangaTimeline(manga.id)
|
||||
if (timeline.isEmpty()) {
|
||||
startDate.value = null
|
||||
stats.value = emptyIntList()
|
||||
} else {
|
||||
val startDay = TimeUnit.MILLISECONDS.toDays(timeline.firstKey())
|
||||
val endDay = TimeUnit.MILLISECONDS.toDays(timeline.lastKey())
|
||||
val res = MutableIntList((endDay - startDay).toInt() + 1)
|
||||
for (day in startDay..endDay) {
|
||||
val from = TimeUnit.DAYS.toMillis(day)
|
||||
val to = TimeUnit.DAYS.toMillis(day + 1)
|
||||
res.add(timeline.subMap(from, true, to, false).values.sum())
|
||||
}
|
||||
stats.value = res
|
||||
startDate.value = calculateTimeAgo(Instant.ofEpochMilli(timeline.firstKey()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package org.koitharu.kotatsu.stats.ui.views
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.DashPathEffect
|
||||
import android.graphics.Paint
|
||||
import android.graphics.PathEffect
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffXfermode
|
||||
import android.graphics.RectF
|
||||
import android.graphics.Xfermode
|
||||
import android.util.AttributeSet
|
||||
import android.view.GestureDetector
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.collection.MutableIntList
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import androidx.core.graphics.minus
|
||||
import androidx.core.view.GestureDetectorCompat
|
||||
import androidx.core.view.setPadding
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.core.util.ext.resolveDp
|
||||
import org.koitharu.kotatsu.parsers.util.replaceWith
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.max
|
||||
import kotlin.math.sqrt
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
class BarChartView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
) : View(context, attrs, defStyleAttr) {
|
||||
|
||||
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
private val bars = ArrayList<Bar>()
|
||||
private var maxValue: Int = 0
|
||||
private var spacing = context.resources.resolveDp(2f)
|
||||
private val minSpace = context.resources.resolveDp(20f)
|
||||
private val outlineColor = context.getThemeColor(materialR.attr.colorOutline)
|
||||
private val dottedEffect = DashPathEffect(
|
||||
floatArrayOf(
|
||||
context.resources.resolveDp(6f),
|
||||
context.resources.resolveDp(6f),
|
||||
),
|
||||
0f,
|
||||
)
|
||||
private val chartBounds = RectF()
|
||||
|
||||
@ColorInt
|
||||
var barColor: Int = Color.MAGENTA
|
||||
set(value) {
|
||||
field = value
|
||||
invalidate()
|
||||
}
|
||||
|
||||
init {
|
||||
paint.strokeWidth = context.resources.resolveDp(1f)
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
if (bars.isEmpty() || chartBounds.isEmpty) {
|
||||
return
|
||||
}
|
||||
val barWidth = ((chartBounds.width() + spacing) / bars.size.toFloat() - spacing)
|
||||
// dashed horizontal lines
|
||||
paint.color = outlineColor
|
||||
paint.style = Paint.Style.STROKE
|
||||
canvas.drawLine(chartBounds.left, chartBounds.bottom, chartBounds.right, chartBounds.bottom, paint)
|
||||
paint.pathEffect = dottedEffect
|
||||
for (i in (0..maxValue).step(computeValueStep())) {
|
||||
val y = chartBounds.top + (chartBounds.height() * i / maxValue.toFloat())
|
||||
canvas.drawLine(paddingLeft.toFloat(), y, (width - paddingLeft - paddingRight).toFloat(), y, paint)
|
||||
}
|
||||
// bars
|
||||
paint.style = Paint.Style.FILL
|
||||
paint.color = barColor
|
||||
paint.pathEffect = null
|
||||
for ((i, bar) in bars.withIndex()) {
|
||||
val h = chartBounds.height() * bar.value / maxValue.toFloat()
|
||||
val x = i * (barWidth + spacing) + paddingLeft
|
||||
canvas.drawRect(x, chartBounds.bottom - h, x + barWidth, chartBounds.bottom, paint)
|
||||
}
|
||||
// bottom line
|
||||
paint.color = outlineColor
|
||||
paint.style = Paint.Style.STROKE
|
||||
canvas.drawLine(chartBounds.left, chartBounds.bottom, chartBounds.right, chartBounds.bottom, paint)
|
||||
}
|
||||
|
||||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||
super.onLayout(changed, left, top, right, bottom)
|
||||
invalidateBounds()
|
||||
}
|
||||
|
||||
fun setData(value: List<Bar>) {
|
||||
bars.replaceWith(value)
|
||||
maxValue = if (value.isEmpty()) 0 else value.maxOf { it.value }
|
||||
invalidate()
|
||||
}
|
||||
|
||||
private fun computeValueStep(): Int {
|
||||
val h = chartBounds.height()
|
||||
var step = 1
|
||||
while (h / (maxValue / step).toFloat() <= minSpace) {
|
||||
step++
|
||||
}
|
||||
return step
|
||||
}
|
||||
|
||||
private fun invalidateBounds() {
|
||||
val inset = paint.strokeWidth
|
||||
chartBounds.set(
|
||||
paddingLeft.toFloat() + inset,
|
||||
paddingTop.toFloat() + inset,
|
||||
(width - paddingLeft - paddingRight).toFloat() - inset,
|
||||
(height - paddingTop - paddingBottom).toFloat() - inset,
|
||||
)
|
||||
}
|
||||
|
||||
class Bar(
|
||||
val value: Int,
|
||||
val label: String,
|
||||
)
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="@dimen/margin_small"
|
||||
android:paddingBottom="@dimen/margin_normal">
|
||||
|
||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
||||
android:id="@+id/dragHandle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignWithParentIfMissing="true"
|
||||
android:layout_below="@id/dragHandle"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_toStartOf="@id/textView_label"
|
||||
android:paddingHorizontal="@dimen/margin_small"
|
||||
android:paddingBottom="@dimen/margin_small"
|
||||
android:singleLine="true"
|
||||
android:text="@string/grid_size"
|
||||
android:textAppearance="?textAppearanceTitleMedium" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBaseline="@id/textView_title"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:paddingHorizontal="@dimen/margin_small"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?textAppearanceLabelLarge"
|
||||
tools:text="100%" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/button_small"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_alignTop="@id/slider_grid"
|
||||
android:layout_alignBottom="@id/slider_grid"
|
||||
android:layout_alignParentStart="true"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/ic_size_small"
|
||||
android:theme="@style/ThemeOverlay.Kotatsu.MainToolbar" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/button_large"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_alignTop="@id/slider_grid"
|
||||
android:layout_alignBottom="@id/slider_grid"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/ic_size_large"
|
||||
android:theme="@style/ThemeOverlay.Kotatsu.MainToolbar" />
|
||||
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/slider_grid"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/textView_title"
|
||||
android:layout_toStartOf="@id/button_large"
|
||||
android:layout_toEndOf="@id/button_small"
|
||||
android:stepSize="5"
|
||||
android:valueFrom="50"
|
||||
android:valueTo="150"
|
||||
app:labelBehavior="gone"
|
||||
app:tickVisible="false"
|
||||
tools:value="100" />
|
||||
|
||||
</RelativeLayout>
|
||||
41
app/src/main/res/layout/sheet_stats_manga.xml
Normal file
41
app/src/main/res/layout/sheet_stats_manga.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="@dimen/screen_padding">
|
||||
|
||||
<org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetHeaderBar
|
||||
android:id="@+id/headerBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:title="@string/reading_stats" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="@dimen/screen_padding"
|
||||
android:textAppearance="?textAppearanceTitleMedium"
|
||||
tools:text="@tools:sample/lorem[4]" />
|
||||
|
||||
<org.koitharu.kotatsu.stats.ui.views.BarChartView
|
||||
android:id="@+id/chartView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="240dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:paddingHorizontal="@dimen/screen_padding" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_start"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:paddingHorizontal="@dimen/screen_padding"
|
||||
android:textAppearance="?textAppearanceLabelSmall"
|
||||
tools:text="Week ago" />
|
||||
|
||||
</LinearLayout>
|
||||
Reference in New Issue
Block a user