Theme Compose SwipeRefresh indicator like XML version

Also rename some screens/controllers to better represent that they're the list views.
This commit is contained in:
arkon 2022-05-16 23:08:04 -04:00
parent cbc114608b
commit 01e04e31bf
7 changed files with 63 additions and 65 deletions

View File

@ -40,6 +40,7 @@ import com.google.accompanist.swiperefresh.SwipeRefresh
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
import eu.kanade.presentation.browse.components.BaseBrowseItem import eu.kanade.presentation.browse.components.BaseBrowseItem
import eu.kanade.presentation.browse.components.ExtensionIcon import eu.kanade.presentation.browse.components.ExtensionIcon
import eu.kanade.presentation.components.SwipeRefreshIndicator
import eu.kanade.presentation.theme.header import eu.kanade.presentation.theme.header
import eu.kanade.presentation.util.horizontalPadding import eu.kanade.presentation.util.horizontalPadding
import eu.kanade.presentation.util.plus import eu.kanade.presentation.util.plus
@ -73,12 +74,12 @@ fun ExtensionScreen(
SwipeRefresh( SwipeRefresh(
modifier = Modifier.nestedScroll(nestedScrollInterop), modifier = Modifier.nestedScroll(nestedScrollInterop),
state = rememberSwipeRefreshState(isRefreshing), state = rememberSwipeRefreshState(isRefreshing),
indicator = { s, trigger -> SwipeRefreshIndicator(s, trigger) },
onRefresh = onRefresh, onRefresh = onRefresh,
) { ) {
when (state) { when (state) {
is ExtensionState.Initialized -> { is ExtensionState.Initialized -> {
ExtensionContent( ExtensionContent(
nestedScrollInterop = nestedScrollInterop,
items = (state as ExtensionState.Initialized).list, items = (state as ExtensionState.Initialized).list,
onLongClickItem = onLongClickItem, onLongClickItem = onLongClickItem,
onClickItemCancel = onClickItemCancel, onClickItemCancel = onClickItemCancel,
@ -98,7 +99,6 @@ fun ExtensionScreen(
@Composable @Composable
fun ExtensionContent( fun ExtensionContent(
nestedScrollInterop: NestedScrollConnection,
items: List<ExtensionUiModel>, items: List<ExtensionUiModel>,
onLongClickItem: (Extension) -> Unit, onLongClickItem: (Extension) -> Unit,
onClickItemCancel: (Extension) -> Unit, onClickItemCancel: (Extension) -> Unit,
@ -112,7 +112,6 @@ fun ExtensionContent(
) { ) {
val (trustState, setTrustState) = remember { mutableStateOf<Extension.Untrusted?>(null) } val (trustState, setTrustState) = remember { mutableStateOf<Extension.Untrusted?>(null) }
LazyColumn( LazyColumn(
modifier = Modifier.nestedScroll(nestedScrollInterop),
contentPadding = WindowInsets.navigationBars.asPaddingValues() + topPaddingValues, contentPadding = WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
) { ) {
items( items(

View File

@ -22,14 +22,14 @@ import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.components.PreferenceRow import eu.kanade.presentation.components.PreferenceRow
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.source.FilterUiModel import eu.kanade.tachiyomi.ui.browse.source.FilterUiModel
import eu.kanade.tachiyomi.ui.browse.source.SourceFilterPresenter
import eu.kanade.tachiyomi.ui.browse.source.SourceFilterState import eu.kanade.tachiyomi.ui.browse.source.SourceFilterState
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterPresenter
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
@Composable @Composable
fun SourceFilterScreen( fun SourcesFilterScreen(
nestedScrollInterop: NestedScrollConnection, nestedScrollInterop: NestedScrollConnection,
presenter: SourceFilterPresenter, presenter: SourcesFilterPresenter,
onClickLang: (String) -> Unit, onClickLang: (String) -> Unit,
onClickSource: (Source) -> Unit, onClickSource: (Source) -> Unit,
) { ) {
@ -39,7 +39,7 @@ fun SourceFilterScreen(
is SourceFilterState.Loading -> LoadingScreen() is SourceFilterState.Loading -> LoadingScreen()
is SourceFilterState.Error -> Text(text = (state as SourceFilterState.Error).error.message!!) is SourceFilterState.Error -> Text(text = (state as SourceFilterState.Error).error.message!!)
is SourceFilterState.Success -> is SourceFilterState.Success ->
SourceFilterContent( SourcesFilterContent(
nestedScrollInterop = nestedScrollInterop, nestedScrollInterop = nestedScrollInterop,
items = (state as SourceFilterState.Success).models, items = (state as SourceFilterState.Success).models,
onClickLang = onClickLang, onClickLang = onClickLang,
@ -49,7 +49,7 @@ fun SourceFilterScreen(
} }
@Composable @Composable
fun SourceFilterContent( fun SourcesFilterContent(
nestedScrollInterop: NestedScrollConnection, nestedScrollInterop: NestedScrollConnection,
items: List<FilterUiModel>, items: List<FilterUiModel>,
onClickLang: (String) -> Unit, onClickLang: (String) -> Unit,
@ -81,14 +81,14 @@ fun SourceFilterContent(
) { model -> ) { model ->
when (model) { when (model) {
is FilterUiModel.Header -> { is FilterUiModel.Header -> {
SourceFilterHeader( SourcesFilterHeader(
modifier = Modifier.animateItemPlacement(), modifier = Modifier.animateItemPlacement(),
language = model.language, language = model.language,
isEnabled = model.isEnabled, isEnabled = model.isEnabled,
onClickItem = onClickLang, onClickItem = onClickLang,
) )
} }
is FilterUiModel.Item -> SourceFilterItem( is FilterUiModel.Item -> SourcesFilterItem(
modifier = Modifier.animateItemPlacement(), modifier = Modifier.animateItemPlacement(),
source = model.source, source = model.source,
isEnabled = model.isEnabled, isEnabled = model.isEnabled,
@ -100,7 +100,7 @@ fun SourceFilterContent(
} }
@Composable @Composable
fun SourceFilterHeader( fun SourcesFilterHeader(
modifier: Modifier, modifier: Modifier,
language: String, language: String,
isEnabled: Boolean, isEnabled: Boolean,
@ -117,7 +117,7 @@ fun SourceFilterHeader(
} }
@Composable @Composable
fun SourceFilterItem( fun SourcesFilterItem(
modifier: Modifier, modifier: Modifier,
source: Source, source: Source,
isEnabled: Boolean, isEnabled: Boolean,

View File

@ -6,22 +6,26 @@ import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.ColorPainter import androidx.compose.ui.graphics.painter.ColorPainter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.imageResource import androidx.compose.ui.res.imageResource
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import coil.compose.AsyncImage import coil.compose.AsyncImage
import eu.kanade.domain.source.model.Source import eu.kanade.domain.source.model.Source
import eu.kanade.presentation.util.bitmapPainterResource import eu.kanade.presentation.util.bitmapPainterResource
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.ui.browse.extension.Result import eu.kanade.tachiyomi.util.lang.withIOContext
import eu.kanade.tachiyomi.ui.browse.extension.getIcon
private val defaultModifier = Modifier private val defaultModifier = Modifier
.height(40.dp) .height(40.dp)
@ -89,3 +93,27 @@ fun ExtensionIcon(
) )
} }
} }
@Composable
private fun Extension.getIcon(): State<Result<ImageBitmap>> {
val context = LocalContext.current
return produceState<Result<ImageBitmap>>(initialValue = Result.Loading, this) {
withIOContext {
value = try {
Result.Success(
context.packageManager.getApplicationIcon(pkgName)
.toBitmap()
.asImageBitmap(),
)
} catch (e: Exception) {
Result.Error
}
}
}
}
sealed class Result<out T> {
object Loading : Result<Nothing>()
object Error : Result<Nothing>()
data class Success<out T>(val value: T) : Result<T>()
}

View File

@ -0,0 +1,17 @@
package eu.kanade.presentation.components
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.Dp
import com.google.accompanist.swiperefresh.SwipeRefreshState
import com.google.accompanist.swiperefresh.SwipeRefreshIndicator as AccompanistSwipeRefreshIndicator
@Composable
fun SwipeRefreshIndicator(state: SwipeRefreshState, refreshTrigger: Dp) {
AccompanistSwipeRefreshIndicator(
state = state,
refreshTriggerDistance = refreshTrigger,
backgroundColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary,
)
}

View File

@ -1,46 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.extension
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.produceState
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.core.graphics.drawable.toBitmap
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.util.lang.withIOContext
fun Extension.getApplicationIcon(context: Context): Drawable? {
return try {
context.packageManager.getApplicationIcon(pkgName)
} catch (e: PackageManager.NameNotFoundException) {
null
}
}
@Composable
fun Extension.getIcon(): State<Result<ImageBitmap>> {
val context = LocalContext.current
return produceState<Result<ImageBitmap>>(initialValue = Result.Loading, this) {
withIOContext {
value = try {
Result.Success(
context.packageManager.getApplicationIcon(pkgName)
.toBitmap()
.asImageBitmap(),
)
} catch (e: Exception) {
Result.Error
}
}
}
}
sealed class Result<out T> {
object Loading : Result<Nothing>()
object Error : Result<Nothing>()
data class Success<out T>(val value: T) : Result<T>()
}

View File

@ -3,19 +3,19 @@ package eu.kanade.tachiyomi.ui.browse.source
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import eu.kanade.domain.source.model.Source import eu.kanade.domain.source.model.Source
import eu.kanade.presentation.browse.SourceFilterScreen import eu.kanade.presentation.browse.SourcesFilterScreen
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.ComposeController import eu.kanade.tachiyomi.ui.base.controller.ComposeController
class SourceFilterController : ComposeController<SourceFilterPresenter>() { class SourceFilterController : ComposeController<SourcesFilterPresenter>() {
override fun getTitle() = resources?.getString(R.string.label_sources) override fun getTitle() = resources?.getString(R.string.label_sources)
override fun createPresenter(): SourceFilterPresenter = SourceFilterPresenter() override fun createPresenter(): SourcesFilterPresenter = SourcesFilterPresenter()
@Composable @Composable
override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) { override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
SourceFilterScreen( SourcesFilterScreen(
nestedScrollInterop = nestedScrollInterop, nestedScrollInterop = nestedScrollInterop,
presenter = presenter, presenter = presenter,
onClickLang = { language -> onClickLang = { language ->

View File

@ -16,7 +16,7 @@ import kotlinx.coroutines.flow.collectLatest
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
class SourceFilterPresenter( class SourcesFilterPresenter(
private val getLanguagesWithSources: GetLanguagesWithSources = Injekt.get(), private val getLanguagesWithSources: GetLanguagesWithSources = Injekt.get(),
private val toggleSource: ToggleSource = Injekt.get(), private val toggleSource: ToggleSource = Injekt.get(),
private val toggleLanguage: ToggleLanguage = Injekt.get(), private val toggleLanguage: ToggleLanguage = Injekt.get(),