Skip to content

Commit

Permalink
Fix AppScreen stutter
Browse files Browse the repository at this point in the history
 Load app banner using a custom Coil fetcher
  • Loading branch information
AfzalivE committed Sep 10, 2022
1 parent 10e7a95 commit 364b382
Show file tree
Hide file tree
Showing 12 changed files with 107 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.afzaln.besttvlauncher

import android.app.Application
import com.afzaln.besttvlauncher.core.Locator
import com.afzaln.besttvlauncher.image.AppInfoImageLoaderFactory
import logcat.AndroidLogcatLogger
import logcat.LogPriority
import logcat.logcat
Expand All @@ -12,7 +13,8 @@ class BestTvApplication: Application() {
super.onCreate()

Locator.init(this)
AppInfoImageLoaderFactory(this).init()
AndroidLogcatLogger.installOnDebuggableApp(this, minPriority = LogPriority.DEBUG)
logcat { "Initialized locator" }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class AppInfoRepository(private val context: Context) {
AppInfo(
app.loadLabel(packageManager).toString(),
app.activityInfo.packageName,
app.activityInfo.loadBanner(packageManager)
app.activityInfo.name,
)
}.toImmutableList()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ package com.afzaln.besttvlauncher.data.models

import android.content.Context
import android.content.Intent
import android.graphics.drawable.Drawable

data class AppInfo(
val label: String,
val packageName: String,
val banner: Drawable
val activityName: String,
)

fun AppInfo.getLaunchIntent(context: Context): Intent? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import androidx.tvprovider.media.tv.TvContractCompat

data class Program(
val id: Long,
val title: String,
val title: String?,
val description: String,
val genre: String?,
val releaseDate: String?,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.afzaln.besttvlauncher.image

import android.content.ComponentName
import android.content.Context
import android.net.Uri
import coil.ImageLoader
import coil.decode.DataSource
import coil.fetch.DrawableResult
import coil.fetch.FetchResult
import coil.fetch.Fetcher
import coil.request.Options
import com.afzaln.besttvlauncher.data.models.AppInfo

private const val SCHEME_APPINFO = "appinfo"

class AppBannerFetcher(val context: Context, val data: Uri) : Fetcher {
override suspend fun fetch(): FetchResult? {
if (data.scheme != SCHEME_APPINFO) return null

val packageName = data.host ?: return null
val activityName = data.lastPathSegment ?: return null
val packageManager = context.packageManager

val drawable = packageManager.getActivityBanner(ComponentName.createRelative(packageName, activityName))
drawable ?: return null

return DrawableResult(drawable, false, DataSource.DISK)
}

class Factory(private val context: Context) : Fetcher.Factory<Uri> {
override fun create(data: Uri, options: Options, imageLoader: ImageLoader): Fetcher =
AppBannerFetcher(context, data)
}
}

fun AppInfo.toPackageUri() = "$SCHEME_APPINFO://$packageName/$activityName"
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.afzaln.besttvlauncher.image

import android.content.Context
import coil.Coil
import coil.ImageLoader
import coil.ImageLoaderFactory

class AppInfoImageLoaderFactory(val context: Context) : ImageLoaderFactory {
fun init() {
Coil.setImageLoader(this)
}

override fun newImageLoader(): ImageLoader = ImageLoader.Builder(context)
.components {
add(AppBannerFetcher.Factory(context))
}
.build()
}
63 changes: 28 additions & 35 deletions tv/src/main/java/com/afzaln/besttvlauncher/ui/apps/AppsScreen.kt
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
package com.afzaln.besttvlauncher.ui.apps

import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.relocation.bringIntoViewRequester
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.material.Card
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import androidx.tv.foundation.lazy.grid.*
import com.afzaln.besttvlauncher.R
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.afzaln.besttvlauncher.data.models.AppInfo
import com.afzaln.besttvlauncher.data.models.getLaunchIntent
import com.afzaln.besttvlauncher.image.toPackageUri
import com.afzaln.besttvlauncher.ui.theme.AppTheme
import com.afzaln.besttvlauncher.utils.dpadFocusable
import kotlinx.collections.immutable.ImmutableList
Expand All @@ -40,11 +35,9 @@ fun AppsScreen(state: HomeViewModel.State) {
}
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun AppList(appList: ImmutableList<AppInfo>) {
val gridState = rememberTvLazyGridState()
val relocationRequester = remember { BringIntoViewRequester() }

TvLazyVerticalGrid(
modifier = Modifier.background(MaterialTheme.colorScheme.background),
Expand All @@ -60,7 +53,6 @@ fun AppList(appList: ImmutableList<AppInfo>) {
span = { TvGridItemSpan(1) }) { appInfo ->
AppCard(
appInfo,
modifier = Modifier.bringIntoViewRequester(relocationRequester),
onFocus = {}
)
}
Expand Down Expand Up @@ -98,10 +90,10 @@ fun AppCard(
horizontalAlignment = Alignment.CenterHorizontally
) {
Card(shape = AppTheme.cardShape) {
Image(
bitmap = appInfo.banner.toBitmap().asImageBitmap(),
contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxSize(),
AsyncImage(
model = ImageRequest.Builder(context)
.data(appInfo.toPackageUri())
.build(),
contentDescription = "Icon for ${appInfo.label}"
)
}
Expand All @@ -113,21 +105,22 @@ fun AppCard(
fun DefaultAppCard() {
AppTheme {
val resources = LocalContext.current.resources
AppCard(
AppInfo(
label = "App name",
packageName = "com.afzaln.example",
banner = BitmapDrawable(
resources,
BitmapFactory.decodeResource(
resources,
R.drawable.app_icon_your_company
)
)
),
onFocus = {

}
)
// TODO provide a slot API for the image
// AppCard(
// AppInfo(
// label = "App name",
// packageName = "com.afzaln.example",
// activityName = BitmapDrawable(
// resources,
// BitmapFactory.decodeResource(
// resources,
// R.drawable.app_icon_your_company
// )
// )
// ),
// onFocus = {
//
// }
// )
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ fun ProgramInChannelRow(
val context = LocalContext.current

CardRow(
title = channel.displayName.toString(),
title = channel.displayName,
programs = programs,
onClick = { programId ->
onProgramClicked(channel.id, programId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TabRow
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
Expand Down Expand Up @@ -64,6 +65,12 @@ fun TabItem(
val isItemFocused by interactionSource.collectIsFocusedAsState()
val isFocusedOrSelected = isItemFocused || selected

LaunchedEffect(isItemFocused) {
if (isItemFocused) {
onTabSelected()
}
}

val animatedBackground by animateColorAsState(
animationSpec = tween(500),
targetValue = if (isFocusedOrSelected) {
Expand Down Expand Up @@ -98,10 +105,6 @@ fun TabItem(
)
}
)

if (isItemFocused) {
onTabSelected()
}
}

@Preview(showBackground = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ fun HomeScreen() {
val currentDestination = currentBackStack?.destination
val currentTab = tabs.find { it.route == currentDestination?.route } ?: Channels

LaunchedEffect(key1 = Unit, block = {
LaunchedEffect(key1 = Unit) {
viewModel.loadData()
})
}

Column(Modifier.fillMaxSize()) {
TitleBar(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ private fun ConstraintLayoutScope.ItemInfo(
.alpha(animationState.opacity)
) {
Text(
text = program.title,
text = program.title ?: "Empty title",
style = MaterialTheme.typography.headlineLarge,
color = MaterialTheme.colorScheme.onSurface
)
Expand Down
15 changes: 8 additions & 7 deletions tv/src/main/java/com/afzaln/besttvlauncher/ui/theme/Theme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,14 @@ fun AppTheme(
else -> DarkColorScheme
}

val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
(view.context as Activity).window.statusBarColor = colorScheme.primary.toArgb()
ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = darkTheme
}
}
// FIXME What is this for?
// val view = LocalView.current
// if (!view.isInEditMode) {
// SideEffect {
// (view.context as Activity).window.statusBarColor = colorScheme.primary.toArgb()
// ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = darkTheme
// }
// }

CompositionLocalProvider(
LocalCardShape provides AppTheme.cardShape
Expand Down

0 comments on commit 364b382

Please sign in to comment.