Skip to content

Commit

Permalink
add: Video Parsing
Browse files Browse the repository at this point in the history
Shows video in screenshots, UI is not perfect but works for now

Closes #921

Signed-off-by: LooKeR <mohit2002ss@gmail.com>
  • Loading branch information
Iamlooker committed Feb 15, 2025
1 parent 6639ae4 commit ae9a248
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 57 deletions.
35 changes: 19 additions & 16 deletions app/src/main/kotlin/com/looker/droidify/index/IndexV1Parser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.looker.droidify.model.Product.Screenshot.Type.LARGE_TABLET
import com.looker.droidify.model.Product.Screenshot.Type.PHONE
import com.looker.droidify.model.Product.Screenshot.Type.SMALL_TABLET
import com.looker.droidify.model.Product.Screenshot.Type.TV
import com.looker.droidify.model.Product.Screenshot.Type.VIDEO
import com.looker.droidify.model.Product.Screenshot.Type.WEAR
import com.looker.droidify.model.Release
import com.looker.droidify.utility.common.SdkCheck
Expand All @@ -42,6 +43,7 @@ object IndexV1Parser {
}

private class Screenshots(
val video: List<String>,
val phone: List<String>,
val smallTablet: List<String>,
val largeTablet: List<String>,
Expand Down Expand Up @@ -256,6 +258,7 @@ object IndexV1Parser {
private const val KEY_PRODUCT_TEN_INCH_SCREENSHOTS = "tenInchScreenshots"
private const val KEY_PRODUCT_WEAR_SCREENSHOTS = "wearScreenshots"
private const val KEY_PRODUCT_TV_SCREENSHOTS = "tvScreenshots"
private const val KEY_PRODUCT_VIDEO = "video"

private fun JsonParser.parseProduct(repositoryId: Long): Product {
var packageName = ""
Expand Down Expand Up @@ -324,13 +327,15 @@ object IndexV1Parser {
var largeTablet = emptyList<String>()
var wear = emptyList<String>()
var tv = emptyList<String>()
var video = emptyList<String>()
forEachKey {
when {
it.string(KEY_PRODUCT_NAME) -> name = valueAsString
it.string(KEY_PRODUCT_SUMMARY) -> summary = valueAsString
it.string(KEY_PRODUCT_DESCRIPTION) -> description = valueAsString
it.string(KEY_PRODUCT_WHATSNEW) -> whatsNew = valueAsString
it.string(KEY_PRODUCT_ICON) -> metadataIcon = valueAsString
it.string(KEY_PRODUCT_VIDEO) -> video = listOf(valueAsString)
it.array(KEY_PRODUCT_PHONE_SCREENSHOTS) ->
phone = collectDistinctNotEmptyStrings()

Expand All @@ -349,26 +354,23 @@ object IndexV1Parser {
else -> skipChildren()
}
}
val isScreenshotEmpty =
arrayOf(video, phone, smallTablet, largeTablet, wear, tv)
.any { it.isNotEmpty() }
val screenshots =
if (arrayOf(
phone,
smallTablet,
largeTablet,
wear,
tv,
).any { it.isNotEmpty() }
) {
Screenshots(phone, smallTablet, largeTablet, wear, tv)
if (isScreenshotEmpty) {
Screenshots(video, phone, smallTablet, largeTablet, wear, tv)
} else {
null
}
localizedMap[locale] = Localized(
name,
summary,
description,
whatsNew,
metadataIcon.nullIfEmpty()?.let { "$locale/$it" }.orEmpty(),
screenshots
name = name,
summary = summary,
description = description,
whatsNew = whatsNew,
metadataIcon = metadataIcon.nullIfEmpty()?.let { "$locale/$it" }
.orEmpty(),
screenshots = screenshots,
)
} else {
skipChildren()
Expand All @@ -392,7 +394,8 @@ object IndexV1Parser {
val screenshotPairs =
localizedMap.find { key, localized -> localized.screenshots?.let { Pair(key, it) } }
val screenshots = screenshotPairs?.let { (key, screenshots) ->
screenshots.phone.map { Product.Screenshot(key, PHONE, it) } +
screenshots.video.map { Product.Screenshot(key, VIDEO, it) } +
screenshots.phone.map { Product.Screenshot(key, PHONE, it) } +
screenshots.smallTablet.map { Product.Screenshot(key, SMALL_TABLET, it) } +
screenshots.largeTablet.map { Product.Screenshot(key, LARGE_TABLET, it) } +
screenshots.wear.map { Product.Screenshot(key, WEAR, it) } +
Expand Down
14 changes: 13 additions & 1 deletion app/src/main/kotlin/com/looker/droidify/model/Product.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package com.looker.droidify.model

import android.content.Context
import com.looker.droidify.utility.common.extension.camera
import com.looker.droidify.utility.common.extension.getColorFromAttr
import com.looker.droidify.utility.common.extension.videoPlaceHolder
import com.google.android.material.R as MaterialR

data class Product(
var repositoryId: Long,
val packageName: String,
Expand Down Expand Up @@ -36,6 +42,7 @@ data class Product(

class Screenshot(val locale: String, val type: Type, val path: String) {
enum class Type(val jsonName: String) {
VIDEO("video"),
PHONE("phone"),
SMALL_TABLET("smallTablet"),
LARGE_TABLET("largeTablet"),
Expand All @@ -47,15 +54,20 @@ data class Product(
get() = "$locale.${type.name}.$path"

fun url(
context: Context,
repository: Repository,
packageName: String
): String {
): Any {
if (type == Type.VIDEO) return context.videoPlaceHolder.apply {
setTintList(context.getColorFromAttr(MaterialR.attr.colorOnSurfaceInverse))
}
val phoneType = when (type) {
Type.PHONE -> "phoneScreenshots"
Type.SMALL_TABLET -> "sevenInchScreenshots"
Type.LARGE_TABLET -> "tenInchScreenshots"
Type.WEAR -> "wearScreenshots"
Type.TV -> "tvScreenshots"
Type.VIDEO -> error("Should not be here, video url already returned")
}
return "${repository.address}/$packageName/$locale/$phoneType/$path"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks {
val position = screenshots.indexOfFirst { screenshot.identifier == it.identifier }
StfalconImageViewer
.Builder(context, screenshots) { view, current ->
view.load(current.url(product.second, viewModel.packageName))
view.load(current.url(requireContext(), product.second, viewModel.packageName))
}
.withStartPosition(position)
.show()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,48 @@ import coil.load
import coil.size.Dimension
import coil.size.Scale
import com.google.android.material.imageview.ShapeableImageView
import com.looker.droidify.databinding.VideoButtonBinding
import com.looker.droidify.graphics.PaddingDrawable
import com.looker.droidify.model.Product
import com.looker.droidify.model.Repository
import com.looker.droidify.utility.common.extension.aspectRatio
import com.looker.droidify.utility.common.extension.authentication
import com.looker.droidify.utility.common.extension.camera
import com.looker.droidify.utility.common.extension.dp
import com.looker.droidify.utility.common.extension.dpToPx
import com.looker.droidify.utility.common.extension.getColorFromAttr
import com.looker.droidify.utility.common.extension.layoutInflater
import com.looker.droidify.utility.common.extension.openLink
import com.looker.droidify.utility.common.extension.selectableBackground
import com.looker.droidify.graphics.PaddingDrawable
import com.looker.droidify.model.Product
import com.looker.droidify.model.Repository
import com.looker.droidify.widget.StableRecyclerAdapter
import com.google.android.material.R as MaterialR
import com.looker.droidify.R.dimen as dimenRes

class ScreenshotsAdapter(private val onClick: (Product.Screenshot, ImageView) -> Unit) :
StableRecyclerAdapter<ScreenshotsAdapter.ViewType, RecyclerView.ViewHolder>() {
enum class ViewType { SCREENSHOT }
enum class ViewType { SCREENSHOT, VIDEO }

private val items = mutableListOf<Item>()

private val items = mutableListOf<Item.ScreenshotItem>()
class VideoViewHolder(
binding: VideoButtonBinding,
) : RecyclerView.ViewHolder(binding.root) {
val button = binding.videoButton

private class ViewHolder(context: Context) :
RecyclerView.ViewHolder(FrameLayout(context)) {
init {
with(button) {
layoutParams = RecyclerView.LayoutParams(
RecyclerView.LayoutParams.WRAP_CONTENT,
150.dp,
)
}
}
}


private class ScreenshotViewHolder(
context: Context
) : RecyclerView.ViewHolder(FrameLayout(context)) {
val image: ShapeableImageView = object : ShapeableImageView(context) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
Expand Down Expand Up @@ -78,64 +98,91 @@ class ScreenshotsAdapter(private val onClick: (Product.Screenshot, ImageView) ->
screenshots: List<Product.Screenshot>
) {
items.clear()
items += screenshots.map { Item.ScreenshotItem(repository, packageName, it) }
items += screenshots.map {
when (it.type) {
Product.Screenshot.Type.VIDEO -> Item.VideoItem(it.path)
else -> Item.ScreenshotItem(repository, packageName, it)
}
}
notifyItemRangeInserted(0, screenshots.size)
}

override val viewTypeClass: Class<ViewType>
get() = ViewType::class.java

override fun getItemEnumViewType(position: Int): ViewType {
return ViewType.SCREENSHOT
}
override val viewTypeClass: Class<ViewType> get() = ViewType::class.java
override fun getItemCount(): Int = items.size
override fun getItemEnumViewType(position: Int) = items[position].viewType
override fun getItemDescriptor(position: Int): String = items[position].descriptor

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: ViewType
): RecyclerView.ViewHolder {
return ViewHolder(parent.context).apply {
image.setOnClickListener {
onClick(
items[absoluteAdapterPosition].screenshot,
it as ImageView
)
return when (viewType) {
ViewType.VIDEO -> VideoViewHolder(VideoButtonBinding.inflate(parent.context.layoutInflater))
ViewType.SCREENSHOT -> ScreenshotViewHolder(parent.context).apply {
image.setOnClickListener {
val item = items[absoluteAdapterPosition] as Item.ScreenshotItem
onClick(item.screenshot, it as ImageView)
}
}
}
}

override fun getItemDescriptor(position: Int): String = items[position].descriptor
override fun getItemCount(): Int = items.size

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder as ViewHolder
val item = items[position]
with(holder.image) {
load(item.screenshot.url(item.repository, item.packageName)) {
size(Dimension.Undefined, Dimension(150.dp.dpToPx.toInt()))
scale(Scale.FIT)
placeholder(holder.placeholder)
error(holder.placeholder)
authentication(item.repository.authentication)
when (getItemEnumViewType(position)) {
ViewType.SCREENSHOT -> {
holder as ScreenshotViewHolder
item as Item.ScreenshotItem
with(holder.image) {
load(item.screenshot.url(context, item.repository, item.packageName)) {
size(Dimension.Undefined, Dimension(150.dp.dpToPx.toInt()))
scale(Scale.FIT)
placeholder(holder.placeholder)
error(holder.placeholder)
authentication(item.repository.authentication)
}
}
}

ViewType.VIDEO -> {
holder as VideoViewHolder
item as Item.VideoItem
holder.button.setOnClickListener {
it.context?.openLink(item.videoUrl)
}
}
}
}

override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
super.onViewRecycled(holder)
holder as ViewHolder
holder.image.dispose()
if (holder is ScreenshotViewHolder) holder.image.dispose()
}

private sealed class Item {
abstract val descriptor: String
abstract val viewType: ViewType

class ScreenshotItem(
val repository: Repository,
val packageName: String,
val screenshot: Product.Screenshot
) : Item() {
override val viewType: ViewType
get() = ViewType.SCREENSHOT

override val descriptor: String
get() = "screenshot.${repository.id}.${screenshot.identifier}"
}

class VideoItem(
val videoUrl: String,
) : Item() {
override val viewType: ViewType
get() = ViewType.VIDEO

override val descriptor: String
get() = "video"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.app.job.JobScheduler
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.drawable.Drawable
import android.net.ConnectivityManager
Expand All @@ -15,6 +16,7 @@ import androidx.annotation.DrawableRes
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.core.net.toUri
import com.looker.droidify.R

inline val Context.clipboardManager: ClipboardManager?
Expand All @@ -39,6 +41,13 @@ fun Context.copyToClipboard(clip: String) {
clipboardManager?.setPrimaryClip(ClipData.newPlainText(null, clip))
}

fun Context.openLink(url: String) {
val intent = intent(Intent.ACTION_VIEW) {
setData(url.toUri())
}
startActivity(intent)
}

val Context.corneredBackground: Drawable
get() = getDrawableCompat(R.drawable.background_border)

Expand All @@ -57,6 +66,9 @@ val Context.selectableBackground: Drawable
val Context.camera: Drawable
get() = getDrawableCompat(R.drawable.ic_image)

val Context.videoPlaceHolder: Drawable
get() = getDrawableCompat(R.drawable.ic_video)

val Context.aspectRatio: Float
get() = with(resources.displayMetrics) {
(heightPixels / widthPixels).toFloat()
Expand All @@ -75,14 +87,21 @@ private fun Context.getDrawableFromAttr(attrResId: Int): Drawable {
}

fun Context.getDrawableCompat(@DrawableRes resId: Int = R.drawable.background_border): Drawable =
requireNotNull(AppCompatResources.getDrawable(this, resId)) { "Cannot find drawable, ID: $resId" }
requireNotNull(
AppCompatResources.getDrawable(
this,
resId
)
) { "Cannot find drawable, ID: $resId" }

fun Context.getColorFromAttr(@AttrRes attrResId: Int): ColorStateList {
val typedArray = obtainStyledAttributes(intArrayOf(attrResId))
val (colorStateList, resId) = try {
Pair(typedArray.getColorStateList(0), typedArray.getResourceId(0, 0))
return try {
typedArray.getColorStateList(0) ?: run {
val resourceId = typedArray.getResourceId(0, 0)
ContextCompat.getColorStateList(this, resourceId)!!
}
} finally {
typedArray.recycle()
}
return colorStateList ?: ContextCompat.getColorStateList(this, resId)!!
}
Loading

0 comments on commit ae9a248

Please sign in to comment.