From 03cb641080806df1c34d6073f48a12885e563b99 Mon Sep 17 00:00:00 2001 From: Xilin Jia <6257601+XilinJia@users.noreply.github.com> Date: Mon, 30 Sep 2024 23:11:04 +0100 Subject: [PATCH] 6.8.3 commit --- app/build.gradle | 7 +- .../actionbutton/EpisodeActionButton.kt | 2 +- .../ui/activity/ShareReceiverActivity.kt | 4 +- .../ac/mdiq/podcini/ui/compose/AppTheme.kt | 28 +- .../ac/mdiq/podcini/ui/compose/Composables.kt | 10 +- .../ac/mdiq/podcini/ui/compose/Episodes.kt | 87 +- .../podcini/ui/compose/FeedEpisodesHeader.kt | 12 +- .../ui/fragment/AudioPlayerFragment.kt | 32 +- .../ui/fragment/EpisodeInfoFragment.kt | 18 +- .../ui/fragment/FeedSettingsFragment.kt | 85 +- .../podcini/ui/fragment/QueuesFragment.kt | 8 +- .../ui/fragment/SubscriptionsFragment.kt | 1220 ++++++++--------- .../main/res/layout/downloads_fragment.xml | 3 - .../res/layout/fragment_subscriptions.xml | 109 +- app/src/main/res/layout/subscription_item.xml | 114 -- .../res/layout/subscription_item_brief.xml | 125 -- .../main/res/menu/feed_action_speeddial.xml | 43 - app/src/main/res/menu/subscriptions.xml | 5 +- app/src/main/res/values/strings.xml | 1 + changelog.md | 7 + .../android/en-US/changelogs/3020260.txt | 6 + gradle/libs.versions.toml | 8 +- 22 files changed, 720 insertions(+), 1214 deletions(-) delete mode 100644 app/src/main/res/layout/subscription_item.xml delete mode 100644 app/src/main/res/layout/subscription_item_brief.xml delete mode 100644 app/src/main/res/menu/feed_action_speeddial.xml create mode 100644 fastlane/metadata/android/en-US/changelogs/3020260.txt diff --git a/app/build.gradle b/app/build.gradle index 0130575e..2ede6dd6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -31,8 +31,8 @@ android { testApplicationId "ac.mdiq.podcini.tests" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - versionCode 3020259 - versionName "6.8.2" + versionCode 3020260 + versionName "6.8.3" applicationId "ac.mdiq.podcini.R" def commit = "" @@ -171,6 +171,7 @@ android { dependencies { implementation libs.androidx.material3.android + implementation libs.androidx.material3 /** Desugaring for using VistaGuide **/ coreLibraryDesugaring libs.desugar.jdk.libs.nio @@ -179,7 +180,7 @@ dependencies { def composeBom = libs.androidx.compose.bom implementation composeBom androidTestImplementation composeBom - implementation libs.androidx.material +// implementation libs.androidx.material implementation libs.androidx.ui.tooling.preview debugImplementation libs.androidx.ui.tooling implementation libs.androidx.constraintlayout.compose diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/actionbutton/EpisodeActionButton.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/actionbutton/EpisodeActionButton.kt index a9ba5744..46e02571 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/actionbutton/EpisodeActionButton.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/actionbutton/EpisodeActionButton.kt @@ -17,7 +17,7 @@ import android.widget.ImageView import androidx.compose.foundation.Image import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.* +import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/ShareReceiverActivity.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/ShareReceiverActivity.kt index ac0886f1..72e18f54 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/ShareReceiverActivity.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/ShareReceiverActivity.kt @@ -18,7 +18,7 @@ import androidx.annotation.OptIn import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.* +import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -108,7 +108,7 @@ class ShareReceiverActivity : AppCompatActivity() { ) Text( text = stringResource(R.string.pref_video_mode_audio_only), - style = MaterialTheme.typography.body1.merge(), + style = MaterialTheme.typography.bodyLarge.merge(), ) } Button(onClick = { diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/AppTheme.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/AppTheme.kt index e8abadad..8905748b 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/AppTheme.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/AppTheme.kt @@ -9,13 +9,13 @@ import android.util.TypedValue import androidx.annotation.AttrRes import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Shapes -import androidx.compose.material.darkColors -import androidx.compose.material.lightColors +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Shapes +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color -import androidx.compose.material.Typography +import androidx.compose.material3.Typography import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight @@ -26,12 +26,12 @@ import androidx.core.content.ContextCompat private val TAG = "AppTheme" val Typography = Typography( - h1 = TextStyle( + displayLarge = TextStyle( fontFamily = FontFamily.Default, fontWeight = FontWeight.Bold, fontSize = 30.sp ), - body1 = TextStyle( + bodyLarge = TextStyle( fontFamily = FontFamily.Default, fontWeight = FontWeight.Normal, fontSize = 16.sp @@ -65,10 +65,10 @@ fun getSecondaryColor(context: Context): Color { return Color(getColorFromAttr(context, R.attr.colorSecondary)) } -val LightColors = lightColors( +val LightColors = lightColorScheme( primary = Color(0xFF6200EE), - primaryVariant = Color(0xFF3700B3), - secondary = Color(0xFF03DAC6), + secondary = Color(0xFF3700B3), + tertiary = Color(0xFF03DAC6), background = Color(0xFFFFFFFF), surface = Color(0xFFFFFFFF), error = Color(0xFFB00020), @@ -79,10 +79,10 @@ val LightColors = lightColors( onError = Color(0xFFFFFFFF) ) -val DarkColors = darkColors( +val DarkColors = darkColorScheme( primary = Color(0xFFBB86FC), - primaryVariant = Color(0xFF3700B3), - secondary = Color(0xFF03DAC6), + secondary = Color(0xFF3700B3), + tertiary = Color(0xFF03DAC6), background = Color(0xFF121212), surface = Color(0xFF121212), error = Color(0xFFCF6679), @@ -119,7 +119,7 @@ fun CustomTheme(context: Context, content: @Composable () -> Unit) { } MaterialTheme( - colors = colors, + colorScheme = colors, typography = Typography, shapes = Shapes, content = content diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Composables.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Composables.kt index 4b7ceba6..183f1b8c 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Composables.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Composables.kt @@ -1,10 +1,10 @@ package ac.mdiq.podcini.ui.compose -import androidx.compose.material.* +import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.platform.LocalContext -@OptIn(ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable fun Spinner( items: List, @@ -33,14 +33,12 @@ fun Spinner( onDismissRequest = { expanded = false } ) { items.forEach { item -> - DropdownMenuItem( + DropdownMenuItem(text = { Text(item) }, onClick = { onItemSelected(item) expanded = false } - ) { - Text(text = item) - } + ) } } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Episodes.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Episodes.kt index 592432dd..96e7e270 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Episodes.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Episodes.kt @@ -33,9 +33,9 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit +import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.Alignment @@ -64,14 +64,14 @@ import kotlin.math.roundToInt @Composable fun InforBar(text: MutableState, leftAction: MutableState, rightAction: MutableState, actionConfig: () -> Unit) { - val textColor = MaterialTheme.colors.onSurface + val textColor = MaterialTheme.colorScheme.onSurface Logd("InforBar", "textState: ${text.value}") Row { Icon(painter = painterResource(leftAction.value?.getActionIcon() ?:R.drawable.ic_questionmark), tint = textColor, contentDescription = "left_action_icon", modifier = Modifier.width(24.dp).height(24.dp).clickable(onClick = actionConfig)) Icon(painter = painterResource(R.drawable.baseline_arrow_left_alt_24), tint = textColor, contentDescription = "left_arrow", modifier = Modifier.width(24.dp).height(24.dp)) Spacer(modifier = Modifier.weight(1f)) - Text(text.value, color = textColor, style = MaterialTheme.typography.body2) + Text(text.value, color = textColor, style = MaterialTheme.typography.bodyMedium) Spacer(modifier = Modifier.weight(1f)) Icon(painter = painterResource(R.drawable.baseline_arrow_right_alt_24), tint = textColor, contentDescription = "right_arrow", modifier = Modifier.width(24.dp).height(24.dp)) Icon(painter = painterResource(rightAction.value?.getActionIcon() ?:R.drawable.ic_questionmark), tint = textColor, contentDescription = "right_action_icon", @@ -177,11 +177,11 @@ fun EpisodeSpeedDial(activity: MainActivity, selected: SnapshotStateList - FloatingActionButton(modifier = Modifier.padding(start = 4.dp, bottom = 6.dp).height(50.dp), - backgroundColor = Color.LightGray, + FloatingActionButton(modifier = Modifier.padding(start = 4.dp, bottom = 6.dp).height(40.dp), + containerColor = Color.LightGray, onClick = {}) { button() } } - FloatingActionButton(backgroundColor = Color.Green, + FloatingActionButton(containerColor = Color.Green, onClick = { isExpanded = !isExpanded }) { Icon(Icons.Filled.Edit, "Edit") } } } @@ -191,7 +191,6 @@ fun EpisodeSpeedDial(activity: MainActivity, selected: SnapshotStateList, leftSwipeCB: (Episode) -> Unit, rightSwipeCB: (Episode) -> Unit, actionButton_: ((Episode)->EpisodeActionButton)? = null) { val TAG = "EpisodeLazyColumn" var selectMode by remember { mutableStateOf(false) } -// val selectedIds = remember { mutableSetOf() } var selectedSize by remember { mutableStateOf(0) } val selected = remember { mutableStateListOf() } val coroutineScope = rememberCoroutineScope() @@ -263,31 +262,28 @@ fun EpisodeLazyColumn(activity: MainActivity, episodes: SnapshotStateList - velocityTracker.addPosition(change.uptimeMillis, change.position) - coroutineScope.launch { offsetX.snapTo(offsetX.value + dragAmount) } - }, - onDragEnd = { - coroutineScope.launch { - val velocity = velocityTracker.calculateVelocity().x - if (velocity > 1000f || velocity < -1000f) { - if (velocity > 0) rightSwipeCB(episodes[index]) - else leftSwipeCB(episodes[index]) - } - offsetX.animateTo( - targetValue = 0f, // Back to the initial position - animationSpec = tween(500) // Adjust animation duration as needed - ) + modifier = Modifier.fillMaxWidth().pointerInput(Unit) { + detectHorizontalDragGestures( + onDragStart = { velocityTracker.resetTracking() }, + onHorizontalDrag = { change, dragAmount -> + velocityTracker.addPosition(change.uptimeMillis, change.position) + coroutineScope.launch { offsetX.snapTo(offsetX.value + dragAmount) } + }, + onDragEnd = { + coroutineScope.launch { + val velocity = velocityTracker.calculateVelocity().x + if (velocity > 1000f || velocity < -1000f) { + if (velocity > 0) rightSwipeCB(episodes[index]) + else leftSwipeCB(episodes[index]) } + offsetX.animateTo( + targetValue = 0f, // Back to the initial position + animationSpec = tween(500) // Adjust animation duration as needed + ) } - ) - } - .offset { IntOffset(offsetX.value.roundToInt(), 0) } + } + ) + }.offset { IntOffset(offsetX.value.roundToInt(), 0) } ) { var isSelected by remember { mutableStateOf(false) } LaunchedEffect(key1 = selectMode, key2 = selectedSize) { @@ -296,16 +292,11 @@ fun EpisodeLazyColumn(activity: MainActivity, episodes: SnapshotStateList 0) Formatter.formatShortFileSize(LocalContext.current, episode.media!!.size) else "" - Text(dateSizeText, color = textColor, style = MaterialTheme.typography.body2) + Text(dateSizeText, color = textColor, style = MaterialTheme.typography.bodyMedium) } Text(episode.title?:"", color = textColor, maxLines = 2, overflow = TextOverflow.Ellipsis) if (InTheatre.isCurMedia(episode.media) || inProgressState) { @@ -371,13 +359,14 @@ fun EpisodeLazyColumn(activity: MainActivity, episodes: SnapshotStateList 0 && pos >= 0 && dur >= pos) 1.0f * pos / dur else 0f Row { - Text(DurationConverter.getDurationStringLong(pos), color = textColor, style = MaterialTheme.typography.caption) + Text(DurationConverter.getDurationStringLong(pos), color = textColor, style = MaterialTheme.typography.bodySmall) LinearProgressIndicator(progress = prog, modifier = Modifier.weight(1f).height(4.dp).align(Alignment.CenterVertically)) - Text(DurationConverter.getDurationStringLong(dur), color = textColor, style = MaterialTheme.typography.caption) + Text(DurationConverter.getDurationStringLong(dur), color = textColor, style = MaterialTheme.typography.bodySmall) } } } var actionButton by remember { mutableStateOf(if (actionButton_ == null) EpisodeActionButton.forItem(episodes[index]) else actionButton_(episodes[index])) } + val actionRes by mutableIntStateOf(actionButton.getDrawable()) var showAltActionsDialog by remember { mutableStateOf(false) } val dls = remember { DownloadServiceInterface.get() } var dlPercent by remember { mutableIntStateOf(0) } @@ -400,7 +389,7 @@ fun EpisodeLazyColumn(activity: MainActivity, episodes: SnapshotStateList= 0) CircularProgressIndicator(progress = 0.01f * dlPercent, strokeWidth = 4.dp, color = textColor) } if (showAltActionsDialog) actionButton.AltActionsDialog(activity, showAltActionsDialog, onDismiss = { showAltActionsDialog = false }) @@ -412,10 +401,8 @@ fun EpisodeLazyColumn(activity: MainActivity, episodes: SnapshotStateListUnit, filterLongClickCB: ()->Unit) { val TAG = "FeedEpisodesHeader" - val textColor = MaterialTheme.colors.onSurface + val textColor = MaterialTheme.colorScheme.onSurface ConstraintLayout(modifier = Modifier.fillMaxWidth().height(120.dp)) { val (controlRow, image1, image2, imgvCover, taColumn) = createRefs() Row(Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 2.dp).background(colorResource(id = R.color.image_readability_tint)) @@ -47,7 +47,7 @@ fun FeedEpisodesHeader(activity: MainActivity, feed: Feed?, filterButColor: Colo } })) Spacer(modifier = Modifier.weight(1f)) - Text(feed?.episodes?.size?.toString()?:"", textAlign = TextAlign.Center, color = Color.White, style = MaterialTheme.typography.body1) + Text(feed?.episodes?.size?.toString()?:"", textAlign = TextAlign.Center, color = Color.White, style = MaterialTheme.typography.bodyLarge) } Image(painter = painterResource(R.drawable.ic_rounded_corner_left), contentDescription = "left_corner", Modifier.width(12.dp).height(12.dp).constrainAs(image1) { @@ -69,8 +69,8 @@ fun FeedEpisodesHeader(activity: MainActivity, feed: Feed?, filterButColor: Colo Column(Modifier.constrainAs(taColumn) { top.linkTo(imgvCover.top) start.linkTo(imgvCover.end) }) { - Text(feed?.title?:"", color = textColor, style = MaterialTheme.typography.body1, maxLines = 2, overflow = TextOverflow.Ellipsis) - Text(feed?.author?:"", color = textColor, style = MaterialTheme.typography.body2, maxLines = 1, overflow = TextOverflow.Ellipsis) + Text(feed?.title?:"", color = textColor, style = MaterialTheme.typography.bodyLarge, maxLines = 2, overflow = TextOverflow.Ellipsis) + Text(feed?.author?:"", color = textColor, style = MaterialTheme.typography.bodyMedium, maxLines = 1, overflow = TextOverflow.Ellipsis) } } } \ No newline at end of file diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt index d2ae471a..f536606a 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt @@ -57,10 +57,10 @@ import android.widget.Toast import androidx.appcompat.widget.Toolbar import androidx.compose.foundation.* import androidx.compose.foundation.layout.* -import androidx.compose.material.Icon -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Slider -import androidx.compose.material.Text +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Slider +import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -199,8 +199,8 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener { @Composable fun PlayerUI() { Column(modifier = Modifier.fillMaxWidth().height(133.dp)) { - val textColor = MaterialTheme.colors.onSurface - Text(titleText, maxLines = 1, color = textColor, style = MaterialTheme.typography.body2) + val textColor = MaterialTheme.colorScheme.onSurface + Text(titleText, maxLines = 1, color = textColor, style = MaterialTheme.typography.bodyMedium) var tempSliderValue by remember { mutableStateOf(sliderValue) } Slider(value = tempSliderValue, valueRange = 0f..duration.toFloat(), modifier = Modifier.height(15.dp), onValueChange = { @@ -213,10 +213,10 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener { if (playbackService?.isServiceReady() == true) seekTo(currentPosition) }) Row { - Text(DurationConverter.getDurationStringLong(currentPosition), color = textColor, style = MaterialTheme.typography.body2) + Text(DurationConverter.getDurationStringLong(currentPosition), color = textColor, style = MaterialTheme.typography.bodyMedium) Spacer(Modifier.weight(1f)) showTimeLeft = UserPreferences.shouldShowRemainingTime() - Text(txtvLengtTexth, color = textColor, style = MaterialTheme.typography.body2, modifier = Modifier.clickable { + Text(txtvLengtTexth, color = textColor, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.clickable { if (controller == null) return@clickable showTimeLeft = !showTimeLeft UserPreferences.setShowRemainTimeSetting(showTimeLeft) @@ -255,7 +255,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener { modifier = Modifier.width(48.dp).height(48.dp).clickable(onClick = { VariableSpeedDialog.newInstance(booleanArrayOf(true, true, true), null)?.show(childFragmentManager, null) })) - Text(txtvPlaybackSpeed, color = textColor, style = MaterialTheme.typography.body2) + Text(txtvPlaybackSpeed, color = textColor, style = MaterialTheme.typography.bodyMedium) } Spacer(Modifier.weight(0.1f)) Column(horizontalAlignment = Alignment.CenterHorizontally) { @@ -268,7 +268,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener { }, onLongClick = { SkipPreferenceDialog.showSkipPreference(requireContext(), SkipPreferenceDialog.SkipDirection.SKIP_REWIND) })) - Text(NumberFormat.getInstance().format(UserPreferences.rewindSecs.toLong()), color = textColor, style = MaterialTheme.typography.body2) + Text(NumberFormat.getInstance().format(UserPreferences.rewindSecs.toLong()), color = textColor, style = MaterialTheme.typography.bodyMedium) } Spacer(Modifier.weight(0.1f)) Icon(painter = painterResource(playButRes), tint = textColor, @@ -301,7 +301,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener { }, onLongClick = { SkipPreferenceDialog.showSkipPreference(requireContext(), SkipPreferenceDialog.SkipDirection.SKIP_FORWARD) })) - Text(NumberFormat.getInstance().format(UserPreferences.fastForwardSecs.toLong()), color = textColor, style = MaterialTheme.typography.body2) + Text(NumberFormat.getInstance().format(UserPreferences.fastForwardSecs.toLong()), color = textColor, style = MaterialTheme.typography.bodyMedium) } Spacer(Modifier.weight(0.1f)) Column(horizontalAlignment = Alignment.CenterHorizontally) { @@ -323,7 +323,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener { }, onLongClick = { activity?.sendBroadcast(MediaButtonReceiver.createIntent(requireContext(), KeyEvent.KEYCODE_MEDIA_NEXT)) })) - if (UserPreferences.speedforwardSpeed > 0.1f) Text(NumberFormat.getInstance().format(UserPreferences.speedforwardSpeed), color = textColor, style = MaterialTheme.typography.body2) + if (UserPreferences.speedforwardSpeed > 0.1f) Text(NumberFormat.getInstance().format(UserPreferences.speedforwardSpeed), color = textColor, style = MaterialTheme.typography.bodyMedium) } Spacer(Modifier.weight(0.1f)) } @@ -335,7 +335,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener { fun DetailUI() { val scrollState = rememberScrollState() Column(modifier = Modifier.fillMaxWidth().verticalScroll(scrollState)) { - val textColor = MaterialTheme.colors.onSurface + val textColor = MaterialTheme.colorScheme.onSurface fun copyText(text: String): Boolean { val clipboardManager: ClipboardManager? = ContextCompat.getSystemService(requireContext(), ClipboardManager::class.java) clipboardManager?.setPrimaryClip(ClipData.newPlainText("Podcini", text)) @@ -344,7 +344,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener { } return true } - Text(txtvPodcastTitle, textAlign = TextAlign.Center, color = textColor, style = MaterialTheme.typography.h5, + Text(txtvPodcastTitle, textAlign = TextAlign.Center, color = textColor, style = MaterialTheme.typography.headlineSmall, modifier = Modifier.fillMaxWidth().padding(top = 2.dp, bottom = 5.dp).combinedClickable(onClick = { if (currentMedia is EpisodeMedia) { if (currentItem?.feedId != null) { @@ -353,8 +353,8 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener { } } }, onLongClick = { copyText(currentMedia?.getFeedTitle()?:"") })) - Text(episodeDate, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth().padding(top = 2.dp, bottom = 2.dp), color = textColor, style = MaterialTheme.typography.body2) - Text(titleText, textAlign = TextAlign.Center, color = textColor, style = MaterialTheme.typography.h6, modifier = Modifier.fillMaxWidth().padding(top = 2.dp, bottom = 5.dp) + Text(episodeDate, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth().padding(top = 2.dp, bottom = 2.dp), color = textColor, style = MaterialTheme.typography.bodyMedium) + Text(titleText, textAlign = TextAlign.Center, color = textColor, style = MaterialTheme.typography.titleLarge, modifier = Modifier.fillMaxWidth().padding(top = 2.dp, bottom = 5.dp) .combinedClickable(onClick = {}, onLongClick = { copyText(currentItem?.title?:"") })) fun restoreFromPreference(): Boolean { if ((activity as MainActivity).bottomSheet.state != BottomSheetBehavior.STATE_EXPANDED) return false diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt index b45fa9fe..8b7de8d7 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt @@ -50,9 +50,9 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Icon -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -188,14 +188,14 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener { @Composable fun InfoView() { Column { - val textColor = MaterialTheme.colors.onSurface + val textColor = MaterialTheme.colorScheme.onSurface Row(modifier = Modifier.padding(start = 16.dp, end = 16.dp), verticalAlignment = Alignment.CenterVertically) { val imgLoc = if (episode != null) ImageResourceUtils.getEpisodeListImageLocation(episode!!) else null AsyncImage(model = imgLoc, contentDescription = "imgvCover", Modifier.width(56.dp).height(56.dp).clickable(onClick = { openPodcast() })) Column(modifier = Modifier.padding(start = 10.dp)) { - Text(txtvPodcast, color = textColor, style = MaterialTheme.typography.body1, modifier = Modifier.clickable { openPodcast() }) - Text(txtvTitle, color = textColor, style = MaterialTheme.typography.body1.copy(fontWeight = FontWeight.Bold), maxLines = 5, overflow = TextOverflow.Ellipsis) - Text(txtvPublished + " · " + txtvDuration + " · " + txtvSize, color = textColor, style = MaterialTheme.typography.body2) + Text(txtvPodcast, color = textColor, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.clickable { openPodcast() }) + Text(txtvTitle, color = textColor, style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold), maxLines = 5, overflow = TextOverflow.Ellipsis) + Text(txtvPublished + " · " + txtvDuration + " · " + txtvSize, color = textColor, style = MaterialTheme.typography.bodyMedium) } } Row(verticalAlignment = Alignment.CenterVertically) { @@ -237,7 +237,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener { } Spacer(modifier = Modifier.weight(1f)) } - if (!hasMedia) Text("noMediaLabel", color = textColor, style = MaterialTheme.typography.body2) + if (!hasMedia) Text("noMediaLabel", color = textColor, style = MaterialTheme.typography.bodyMedium) val scrollState = rememberScrollState() Column(modifier = Modifier.fillMaxWidth().verticalScroll(scrollState)) { AndroidView(modifier = Modifier.fillMaxSize(), factory = { context -> @@ -251,7 +251,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener { }, update = { it.loadDataWithBaseURL("https://127.0.0.1", webviewData, "text/html", "utf-8", "about:blank") }) - Text(itemLink, color = textColor, style = MaterialTheme.typography.caption) + Text(itemLink, color = textColor, style = MaterialTheme.typography.bodySmall) } } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedSettingsFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedSettingsFragment.kt index f81f5601..aa0f8ab8 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedSettingsFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedSettingsFragment.kt @@ -12,7 +12,8 @@ import ac.mdiq.podcini.storage.database.Feeds.persistFeedPreferences import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk import ac.mdiq.podcini.storage.model.* -import ac.mdiq.podcini.storage.model.FeedPreferences.* +import ac.mdiq.podcini.storage.model.FeedPreferences.AutoDeleteAction +import ac.mdiq.podcini.storage.model.FeedPreferences.AutoDownloadPolicy import ac.mdiq.podcini.storage.model.FeedPreferences.Companion.FeedAutoDeleteOptions import ac.mdiq.podcini.ui.adapter.SimpleChipAdapter import ac.mdiq.podcini.ui.compose.CustomTheme @@ -37,7 +38,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.* +import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -45,11 +46,7 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.OffsetMapping -import androidx.compose.ui.text.input.TransformedText -import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.fragment.app.Fragment @@ -95,7 +92,7 @@ class FeedSettingsFragment : Fragment() { binding.composeView.setContent { CustomTheme(requireContext()) { - val textColor = MaterialTheme.colors.onSurface + val textColor = MaterialTheme.colorScheme.onSurface Column( modifier = Modifier.padding(start = 20.dp, end = 16.dp, top = 10.dp, bottom = 10.dp), verticalArrangement = Arrangement.spacedBy(8.dp) @@ -108,7 +105,7 @@ class FeedSettingsFragment : Fragment() { Spacer(modifier = Modifier.width(20.dp)) Text( text = stringResource(R.string.keep_updated), - style = MaterialTheme.typography.h6, + style = MaterialTheme.typography.titleLarge, color = textColor ) Spacer(modifier = Modifier.weight(1f)) @@ -126,7 +123,7 @@ class FeedSettingsFragment : Fragment() { } Text( text = stringResource(R.string.keep_updated_summary), - style = MaterialTheme.typography.body2, + style = MaterialTheme.typography.bodyMedium, color = textColor ) } @@ -139,7 +136,7 @@ class FeedSettingsFragment : Fragment() { Spacer(modifier = Modifier.width(20.dp)) Text( text = stringResource(R.string.feed_video_mode_label), - style = MaterialTheme.typography.h6, + style = MaterialTheme.typography.titleLarge, color = textColor, modifier = Modifier.clickable(onClick = { val composeView = ComposeView(requireContext()).apply { @@ -156,7 +153,7 @@ class FeedSettingsFragment : Fragment() { } Text( text = stringResource(videoModeSummaryResId), - style = MaterialTheme.typography.body2, + style = MaterialTheme.typography.bodyMedium, color = textColor ) } @@ -169,7 +166,7 @@ class FeedSettingsFragment : Fragment() { Spacer(modifier = Modifier.width(20.dp)) Text( text = stringResource(R.string.pref_stream_over_download_title), - style = MaterialTheme.typography.h6, + style = MaterialTheme.typography.titleLarge, color = textColor ) Spacer(modifier = Modifier.weight(1f)) @@ -189,7 +186,7 @@ class FeedSettingsFragment : Fragment() { } Text( text = stringResource(R.string.pref_stream_over_download_sum), - style = MaterialTheme.typography.body2, + style = MaterialTheme.typography.bodyMedium, color = textColor ) } @@ -202,7 +199,7 @@ class FeedSettingsFragment : Fragment() { Spacer(modifier = Modifier.width(20.dp)) Text( text = stringResource(R.string.pref_feed_associated_queue), - style = MaterialTheme.typography.h6, + style = MaterialTheme.typography.titleLarge, color = textColor, modifier = Modifier.clickable(onClick = { val selectedOption = feed?.preferences?.queueText ?: "Default" @@ -220,7 +217,7 @@ class FeedSettingsFragment : Fragment() { } Text( text = curPrefQueue + " : " + stringResource(R.string.pref_feed_associated_queue_sum), - style = MaterialTheme.typography.body2, + style = MaterialTheme.typography.bodyMedium, color = textColor ) } @@ -234,7 +231,7 @@ class FeedSettingsFragment : Fragment() { Spacer(modifier = Modifier.width(20.dp)) Text( text = stringResource(R.string.audo_add_new_queue), - style = MaterialTheme.typography.h6, + style = MaterialTheme.typography.titleLarge, color = textColor ) Spacer(modifier = Modifier.weight(1f)) @@ -252,7 +249,7 @@ class FeedSettingsFragment : Fragment() { } Text( text = stringResource(R.string.audo_add_new_queue_summary), - style = MaterialTheme.typography.body2, + style = MaterialTheme.typography.bodyMedium, color = textColor ) } @@ -265,7 +262,7 @@ class FeedSettingsFragment : Fragment() { Spacer(modifier = Modifier.width(20.dp)) Text( text = stringResource(R.string.auto_delete_label), - style = MaterialTheme.typography.h6, + style = MaterialTheme.typography.titleLarge, color = textColor, modifier = Modifier.clickable(onClick = { val composeView = ComposeView(requireContext()).apply { @@ -282,7 +279,7 @@ class FeedSettingsFragment : Fragment() { } Text( text = stringResource(autoDeleteSummaryResId), - style = MaterialTheme.typography.body2, + style = MaterialTheme.typography.bodyMedium, color = textColor ) } @@ -294,7 +291,7 @@ class FeedSettingsFragment : Fragment() { Spacer(modifier = Modifier.width(20.dp)) Text( text = stringResource(R.string.feed_tags_label), - style = MaterialTheme.typography.h6, + style = MaterialTheme.typography.titleLarge, color = textColor, modifier = Modifier.clickable(onClick = { val dialog = TagSettingsDialog.newInstance(listOf(feed!!)) @@ -304,7 +301,7 @@ class FeedSettingsFragment : Fragment() { } Text( text = stringResource(R.string.feed_tags_summary), - style = MaterialTheme.typography.body2, + style = MaterialTheme.typography.bodyMedium, color = textColor ) } @@ -315,7 +312,7 @@ class FeedSettingsFragment : Fragment() { Spacer(modifier = Modifier.width(20.dp)) Text( text = stringResource(R.string.playback_speed), - style = MaterialTheme.typography.h6, + style = MaterialTheme.typography.titleLarge, color = textColor, modifier = Modifier.clickable(onClick = { PlaybackSpeedDialog().show() @@ -324,7 +321,7 @@ class FeedSettingsFragment : Fragment() { } Text( text = stringResource(R.string.pref_feed_playback_speed_sum), - style = MaterialTheme.typography.body2, + style = MaterialTheme.typography.bodyMedium, color = textColor ) } @@ -335,7 +332,7 @@ class FeedSettingsFragment : Fragment() { Spacer(modifier = Modifier.width(20.dp)) Text( text = stringResource(R.string.pref_feed_skip), - style = MaterialTheme.typography.h6, + style = MaterialTheme.typography.titleLarge, color = textColor, modifier = Modifier.clickable(onClick = { val composeView = ComposeView(requireContext()).apply { @@ -352,7 +349,7 @@ class FeedSettingsFragment : Fragment() { } Text( text = stringResource(R.string.pref_feed_skip_sum), - style = MaterialTheme.typography.body2, + style = MaterialTheme.typography.bodyMedium, color = textColor ) } @@ -363,7 +360,7 @@ class FeedSettingsFragment : Fragment() { Spacer(modifier = Modifier.width(20.dp)) Text( text = stringResource(R.string.feed_volume_adapdation), - style = MaterialTheme.typography.h6, + style = MaterialTheme.typography.titleLarge, color = textColor, modifier = Modifier.clickable(onClick = { val composeView = ComposeView(requireContext()).apply { @@ -380,7 +377,7 @@ class FeedSettingsFragment : Fragment() { } Text( text = stringResource(R.string.feed_volume_adaptation_summary), - style = MaterialTheme.typography.body2, + style = MaterialTheme.typography.bodyMedium, color = textColor ) } @@ -392,7 +389,7 @@ class FeedSettingsFragment : Fragment() { Spacer(modifier = Modifier.width(20.dp)) Text( text = stringResource(R.string.authentication_label), - style = MaterialTheme.typography.h6, + style = MaterialTheme.typography.titleLarge, color = textColor, modifier = Modifier.clickable(onClick = { val composeView = ComposeView(requireContext()).apply { @@ -410,7 +407,7 @@ class FeedSettingsFragment : Fragment() { } Text( text = stringResource(R.string.authentication_descr), - style = MaterialTheme.typography.body2, + style = MaterialTheme.typography.bodyMedium, color = textColor ) } @@ -422,7 +419,7 @@ class FeedSettingsFragment : Fragment() { Row(Modifier.fillMaxWidth()) { Text( text = stringResource(R.string.auto_download_label), - style = MaterialTheme.typography.h6, + style = MaterialTheme.typography.titleLarge, color = textColor ) Spacer(modifier = Modifier.weight(1f)) @@ -438,7 +435,7 @@ class FeedSettingsFragment : Fragment() { if (!isEnableAutodownload) { Text( text = stringResource(R.string.auto_download_disabled_globally), - style = MaterialTheme.typography.body2, + style = MaterialTheme.typography.bodyMedium, color = textColor ) } @@ -449,7 +446,7 @@ class FeedSettingsFragment : Fragment() { Row(Modifier.fillMaxWidth()) { Text( text = stringResource(R.string.feed_auto_download_policy), - style = MaterialTheme.typography.h6, + style = MaterialTheme.typography.titleLarge, color = textColor, modifier = Modifier.clickable(onClick = { val composeView = ComposeView(requireContext()).apply { @@ -471,7 +468,7 @@ class FeedSettingsFragment : Fragment() { Row(Modifier.fillMaxWidth()) { Text( text = stringResource(R.string.pref_episode_cache_title), - style = MaterialTheme.typography.h6, + style = MaterialTheme.typography.titleLarge, color = textColor, modifier = Modifier.clickable(onClick = { val composeView = ComposeView(requireContext()).apply { @@ -489,7 +486,7 @@ class FeedSettingsFragment : Fragment() { } Text( text = stringResource(R.string.pref_episode_cache_summary), - style = MaterialTheme.typography.body2, + style = MaterialTheme.typography.bodyMedium, color = textColor ) } @@ -498,7 +495,7 @@ class FeedSettingsFragment : Fragment() { Row(Modifier.fillMaxWidth()) { Text( text = stringResource(R.string.pref_auto_download_counting_played_title), - style = MaterialTheme.typography.h6, + style = MaterialTheme.typography.titleLarge, color = textColor ) Spacer(modifier = Modifier.weight(1f)) @@ -518,7 +515,7 @@ class FeedSettingsFragment : Fragment() { } Text( text = stringResource(R.string.pref_auto_download_counting_played_summary), - style = MaterialTheme.typography.body2, + style = MaterialTheme.typography.bodyMedium, color = textColor ) } @@ -527,7 +524,7 @@ class FeedSettingsFragment : Fragment() { Row(Modifier.fillMaxWidth()) { Text( text = stringResource(R.string.episode_inclusive_filters_label), - style = MaterialTheme.typography.h6, + style = MaterialTheme.typography.titleLarge, color = textColor, modifier = Modifier.clickable(onClick = { object : AutoDownloadFilterPrefDialog(requireContext(), @@ -545,7 +542,7 @@ class FeedSettingsFragment : Fragment() { } Text( text = stringResource(R.string.episode_filters_description), - style = MaterialTheme.typography.body2, + style = MaterialTheme.typography.bodyMedium, color = textColor ) } @@ -554,7 +551,7 @@ class FeedSettingsFragment : Fragment() { Row(Modifier.fillMaxWidth()) { Text( text = stringResource(R.string.episode_exclusive_filters_label), - style = MaterialTheme.typography.h6, + style = MaterialTheme.typography.titleLarge, color = textColor, modifier = Modifier.clickable(onClick = { object : AutoDownloadFilterPrefDialog(requireContext(), @@ -572,7 +569,7 @@ class FeedSettingsFragment : Fragment() { } Text( text = stringResource(R.string.episode_filters_description), - style = MaterialTheme.typography.body2, + style = MaterialTheme.typography.bodyMedium, color = textColor ) } @@ -657,7 +654,7 @@ class FeedSettingsFragment : Fragment() { ) Text( text = text, - style = MaterialTheme.typography.body1.merge(), + style = MaterialTheme.typography.bodyLarge.merge(), // color = textColor, modifier = Modifier.padding(start = 16.dp) ) @@ -727,7 +724,7 @@ class FeedSettingsFragment : Fragment() { ) Text( text = text, - style = MaterialTheme.typography.body1.merge(), + style = MaterialTheme.typography.bodyLarge.merge(), // color = textColor, modifier = Modifier.padding(start = 16.dp) ) @@ -774,7 +771,7 @@ class FeedSettingsFragment : Fragment() { ) Text( text = stringResource(item.resId), - style = MaterialTheme.typography.body1.merge(), + style = MaterialTheme.typography.bodyLarge.merge(), // color = textColor, modifier = Modifier.padding(start = 16.dp) ) @@ -822,7 +819,7 @@ class FeedSettingsFragment : Fragment() { ) Text( text = stringResource(item.resId), - style = MaterialTheme.typography.body1.merge(), + style = MaterialTheme.typography.bodyLarge.merge(), // color = textColor, modifier = Modifier.padding(start = 16.dp) ) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt index 755215e5..0c2f3ece 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt @@ -55,10 +55,10 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Button -import androidx.compose.material.Card -import androidx.compose.material.Text -import androidx.compose.material.TextField +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.Text +import androidx.compose.material3.TextField import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt index 28ea84c8..fa2b4a21 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt @@ -15,63 +15,72 @@ import ac.mdiq.podcini.storage.database.RealmDB.upsert import ac.mdiq.podcini.storage.model.* import ac.mdiq.podcini.storage.model.FeedPreferences.AutoDeleteAction import ac.mdiq.podcini.storage.model.FeedPreferences.Companion.FeedAutoDeleteOptions -import ac.mdiq.podcini.storage.utils.ImageResourceUtils import ac.mdiq.podcini.ui.activity.MainActivity -import ac.mdiq.podcini.ui.adapter.SelectableAdapter import ac.mdiq.podcini.ui.compose.CustomTheme import ac.mdiq.podcini.ui.compose.Spinner import ac.mdiq.podcini.ui.dialog.FeedSortDialog import ac.mdiq.podcini.ui.dialog.RemoveFeedDialog import ac.mdiq.podcini.ui.dialog.TagSettingsDialog import ac.mdiq.podcini.ui.fragment.FeedSettingsFragment.Companion.queueSettingOptions -import ac.mdiq.podcini.ui.utils.CoverLoader import ac.mdiq.podcini.ui.utils.EmptyViewHandler -import ac.mdiq.podcini.ui.utils.LiftOnScrollListener -import ac.mdiq.podcini.util.MiscFormatter.formatAbbrev -import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.EventFlow import ac.mdiq.podcini.util.FlowEvent +import ac.mdiq.podcini.util.Logd +import ac.mdiq.podcini.util.MiscFormatter.formatAbbrev import android.app.Activity.RESULT_OK import android.app.Dialog import android.content.ActivityNotFoundException import android.content.Context import android.content.DialogInterface import android.content.Intent -import android.graphics.Rect -import android.graphics.drawable.Drawable import android.net.Uri import android.os.Bundle import android.util.Log -import android.view.* +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup import android.widget.* import androidx.activity.result.ActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.OptIn -import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.widget.Toolbar -import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable +import androidx.compose.foundation.* import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.* +import androidx.compose.material3.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AddCircle +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +//import androidx.compose.material3.pullrefresh.pullRefresh +//import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.runtime.* +import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog +import androidx.constraintlayout.compose.ConstraintLayout import androidx.core.util.Consumer import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.media3.common.util.UnstableApi -import androidx.recyclerview.widget.GridLayoutManager -import androidx.recyclerview.widget.RecyclerView import coil.compose.AsyncImage import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.bottomsheet.BottomSheetBehavior @@ -79,11 +88,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.button.MaterialButtonToggleGroup import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.google.android.material.elevation.SurfaceColors -import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.snackbar.Snackbar -import com.leinardi.android.speeddial.SpeedDialActionItem -import com.leinardi.android.speeddial.SpeedDialView import kotlinx.coroutines.* import kotlinx.coroutines.flow.collectLatest import org.apache.commons.lang3.StringUtils @@ -94,24 +99,13 @@ import java.util.* /** * Fragment for displaying feed subscriptions */ -class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAdapter.OnSelectModeListener { +class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener { - private val chooseOpmlExportPathLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - result: ActivityResult -> - if (result.resultCode != RESULT_OK || result.data == null) return@registerForActivityResult - val uri = result.data!!.data - multiSelectHandler?.exportOPML(uri) - } - - private var multiSelectHandler: FeedMultiSelectActionHandler? = null private var _binding: FragmentSubscriptionsBinding? = null private val binding get() = _binding!! - private lateinit var recyclerView: RecyclerView - private lateinit var adapter: SubscriptionsAdapter<*> private lateinit var emptyView: EmptyViewHandler private lateinit var toolbar: MaterialToolbar - private lateinit var speedDialView: SpeedDialView private val tags: MutableList = mutableListOf() private val queueIds: MutableList = mutableListOf() @@ -127,12 +121,18 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec private var displayedFolder: String = "" private var displayUpArrow = false - private var feedList: MutableList = mutableListOf() - private var feedListFiltered: List = mutableListOf() + private var txtvInformation by mutableStateOf("") + private var feedCount by mutableStateOf("") + private var feedSorted by mutableIntStateOf(0) + + private var feedList: MutableList = mutableListOf() + private var feedListFiltered = mutableStateListOf() + + private var useGrid by mutableStateOf(null) + private val useGridLayout by mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefFeedGridLayout.name, false)) + + private var selectMode by mutableStateOf(false) - private var useGrid: Boolean? = null - private val useGridLayout: Boolean - get() = appPrefs.getBoolean(UserPreferences.Prefs.prefFeedGridLayout.name, false) private val swipeToRefresh: Boolean get() = appPrefs.getBoolean(UserPreferences.Prefs.prefSwipeToRefreshAll.name, true) @@ -147,11 +147,6 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec Logd(TAG, "fragment onCreateView") toolbar = binding.toolbar toolbar.setOnMenuItemClickListener(this) - toolbar.setOnLongClickListener { - recyclerView.scrollToPosition(5) - recyclerView.post { recyclerView.smoothScrollToPosition(0) } - false - } displayUpArrow = parentFragmentManager.backStackEntryCount != 0 if (savedInstanceState != null) displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW) @@ -163,12 +158,16 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec toolbar.title = displayedFolder } - recyclerView = binding.subscriptionsGrid - recyclerView.addItemDecoration(GridDividerItemDecorator()) - registerForContextMenu(recyclerView) - recyclerView.addOnScrollListener(LiftOnScrollListener(binding.appbar)) - - initAdapter() + binding.infobar.setContent { + CustomTheme(requireContext()) { + InforBar() + } + } + binding.lazyColumn.setContent { + CustomTheme(requireContext()) { + LazyList() + } + } setupEmptyView() resetTags() @@ -201,99 +200,32 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec override fun onNothingSelected(parent: AdapterView<*>?) {} } -// val searchBox = binding.searchBox -// searchBox.setOnEditorActionListener { _, actionId, _ -> -// if (actionId == EditorInfo.IME_ACTION_SEARCH) { -// val text = searchBox.text.toString().lowercase(Locale.getDefault()) -// val resultList = feedListFiltered.filter { -// it.title?.lowercase(Locale.getDefault())?.contains(text)?:false || it.author?.lowercase(Locale.getDefault())?.contains(text)?:false -// } -// adapter.setItems(resultList) -// true -// } else false -// } - -// binding.progressBar.visibility = View.VISIBLE -// binding.progressBar.visibility = View.GONE - - val subscriptionAddButton: FloatingActionButton = binding.subscriptionsAdd - subscriptionAddButton.setOnClickListener { - if (activity is MainActivity) (activity as MainActivity).loadChildFragment(OnlineSearchFragment()) - } - - binding.count.text = feedListFiltered.size.toString() + " / " + feedList.size.toString() - - val speedDialBinding = MultiSelectSpeedDialBinding.bind(binding.root) - speedDialView = speedDialBinding.fabSD - speedDialView.overlayLayout = speedDialBinding.fabSDOverlay - speedDialView.inflate(R.menu.feed_action_speeddial) - speedDialView.setOnChangeListener(object : SpeedDialView.OnChangeListener { - override fun onMainActionSelected(): Boolean { - return false - } - override fun onToggleChanged(isOpen: Boolean) {} - }) - speedDialView.setOnActionSelectedListener { actionItem: SpeedDialActionItem -> - multiSelectHandler = FeedMultiSelectActionHandler(activity as MainActivity, adapter.selectedItems) - multiSelectHandler?.handleAction(actionItem.id) - true - } + feedCount = feedListFiltered.size.toString() + " / " + feedList.size.toString() loadSubscriptions() return binding.root } - private fun setSwipeRefresh() { - if (swipeToRefresh) { - binding.swipeRefresh.isEnabled = true - binding.swipeRefresh.setProgressViewEndTarget(false, 0) - binding.swipeRefresh.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance)) - binding.swipeRefresh.setOnRefreshListener { - Logd(TAG, "running FeedUpdateManager") - FeedUpdateManager.runOnceOrAsk(requireContext()) - } - } else binding.swipeRefresh.isEnabled = false - } - - override fun onResume() { - Logd(TAG, "onResume() called") - super.onResume() - setSwipeRefresh() - } - - private fun initAdapter() { - if (useGrid != useGridLayout) { - useGrid = useGridLayout - var spanCount = 1 - if (useGrid!!) { - adapter = GridAdapter() - spanCount = 3 - } else adapter = ListAdapter() - recyclerView.layoutManager = GridLayoutManager(context, spanCount, RecyclerView.VERTICAL, false) - adapter.setOnSelectModeListener(this) - recyclerView.adapter = adapter - adapter.setItems(feedListFiltered) - } - } +// override fun onResume() { +// Logd(TAG, "onResume() called") +// super.onResume() +// } override fun onStart() { Logd(TAG, "onStart()") super.onStart() - initAdapter() procFlowEvents() } override fun onStop() { Logd(TAG, "onStop()") super.onStop() - adapter.endSelectMode() cancelFlowEvents() } override fun onDestroyView() { Logd(TAG, "onDestroyView") feedList = mutableListOf() - feedListFiltered = mutableListOf() - adapter.clearData() + feedListFiltered.clear() _binding = null super.onDestroyView() } @@ -361,9 +293,9 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec when (event) { is FlowEvent.FeedUpdatingEvent -> { Logd(TAG, "FeedUpdateRunningEvent: ${event.isRunning}") - infoTextUpdate = if (event.isRunning) " " + this@SubscriptionsFragment.getString(R.string.refreshing_label) else "" - binding.txtvInformation.text = (infoTextFiltered + infoTextUpdate) - if (swipeToRefresh) binding.swipeRefresh.isRefreshing = event.isRunning + infoTextUpdate = if (event.isRunning) " " + getString(R.string.refreshing_label) else "" + txtvInformation = (infoTextFiltered + infoTextUpdate) +// if (swipeToRefresh) binding.swipeRefresh.isRefreshing = event.isRunning if (!event.isRunning && event.id != prevFeedUpdatingEvent?.id) loadSubscriptions() prevFeedUpdatingEvent = event } @@ -384,6 +316,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec R.id.action_search -> (activity as MainActivity).loadChildFragment(SearchFragment.newInstance()) R.id.subscriptions_sort -> FeedSortDialog().show(childFragmentManager, "FeedSortDialog") R.id.refresh_item -> FeedUpdateManager.runOnceOrAsk(requireContext()) + R.id.toggle_grid_list -> useGrid = if (useGrid == null) !useGridLayout else !useGrid!! else -> return false } return true @@ -394,7 +327,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec emptyView.setIcon(R.drawable.ic_subscriptions) emptyView.setTitle(R.string.no_subscriptions_head_label) emptyView.setMessage(R.string.no_subscriptions_label) - emptyView.attachToRecyclerView(recyclerView) +// emptyView.attachToRecyclerView(recyclerView) } private var loadItemsRunning = false @@ -411,25 +344,14 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec } withContext(Dispatchers.Main) { // We have fewer items. This can result in items being selected that are no longer visible. - if (feedListFiltered.size > feedList.size) adapter.endSelectMode() +// if (feedListFiltered.size > feedList.size) adapter.endSelectMode() // filterOnTag() - feedListFiltered = feedList - binding.count.text = feedListFiltered.size.toString() + " / " + feedList.size.toString() - adapter.setItems(feedListFiltered) -// binding.progressBar.visibility = View.GONE - adapter.setItems(feedListFiltered) - binding.count.text = feedListFiltered.size.toString() + " / " + feedList.size.toString() + feedListFiltered.clear() + feedListFiltered.addAll(feedList) + feedCount = feedListFiltered.size.toString() + " / " + feedList.size.toString() infoTextFiltered = " " - binding.txtvInformation.setOnClickListener {} - if (feedsFilter.isNotEmpty()) { - val filter = FeedFilter(feedsFilter) - infoTextFiltered = getString(R.string.filtered_label) - binding.txtvInformation.setOnClickListener { - val dialog = FeedFilterDialog.newInstance(filter) - dialog.show(childFragmentManager, null) - } - } - binding.txtvInformation.text = (infoTextFiltered + infoTextUpdate) + if (feedsFilter.isNotEmpty()) infoTextFiltered = getString(R.string.filtered_label) + txtvInformation = (infoTextFiltered + infoTextUpdate) emptyView.updateVisibility() } } catch (e: Throwable) { Log.e(TAG, Log.getStackTraceString(e)) @@ -549,6 +471,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec } } synchronized(feedList_) { feedList = feedList_.sortedWith(comparator).toMutableList() } + feedSorted++ } private fun comparator(counterMap: Map, dir: Int): Comparator { @@ -564,162 +487,30 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec } } - override fun onEndSelectMode() { - speedDialView.close() - speedDialView.visibility = View.GONE - adapter.setItems(feedListFiltered) - } - - override fun onStartSelectMode() { - speedDialView.visibility = View.VISIBLE - val feedsOnly: MutableList = ArrayList(feedListFiltered) -// feedsOnly.addAll(feedListFiltered) - adapter.setItems(feedsOnly) - } - @Composable - fun FeedExpanded(feed: Feed) { - Row { - val imgLoc = "" - AsyncImage(model = imgLoc, contentDescription = "imgvCover", - placeholder = painterResource(R.mipmap.ic_launcher), - modifier = Modifier.width(80.dp).height(80.dp) - .clickable(onClick = { - Logd(TAG, "icon clicked!") -// if (selectMode) toggleSelected() -// else activity.loadChildFragment(FeedInfoFragment.newInstance(episode.feed!!)) - })) - val textColor = MaterialTheme.colors.onSurface - Column(Modifier.fillMaxWidth()) { - Text("titleLabel", color = textColor, maxLines = 1, overflow = TextOverflow.Ellipsis) - Text("producerLabel", color = textColor, maxLines = 1, overflow = TextOverflow.Ellipsis) - Row { - Text("episodeCount", color = textColor, ) - Spacer(modifier = Modifier.weight(1f)) - Text("sortInfo", color = textColor, ) + fun InforBar() { + Row(Modifier.padding(start = 20.dp, end = 20.dp)) { + val textColor = MaterialTheme.colorScheme.onSurface + Icon(painter = painterResource(R.drawable.ic_info), contentDescription = "info", tint = textColor) + Spacer(Modifier.weight(1f)) + Text(txtvInformation, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.clickable { + if (feedsFilter.isNotEmpty()) { + val filter = FeedFilter(feedsFilter) + val dialog = FeedFilterDialog.newInstance(filter) + dialog.show(childFragmentManager, null) } - } - Icon(painter = painterResource(R.drawable.ic_error), contentDescription = "error") + } ) + Spacer(Modifier.weight(1f)) + Text(feedCount, color = textColor) } } @Composable - fun FeedCompact(feed: Feed) { - - } - @UnstableApi - private inner class FeedMultiSelectActionHandler(private val activity: MainActivity, private val selectedItems: List) { - fun handleAction(id: Int) { - when (id) { - R.id.remove_feed -> RemoveFeedDialog.show(activity, selectedItems) - R.id.keep_updated -> keepUpdatedPrefHandler() - R.id.autodownload -> autoDownloadPrefHandler() - R.id.autoDeleteDownload -> autoDeleteEpisodesPrefHandler() - R.id.playback_speed -> playbackSpeedPrefHandler() - R.id.export_opml -> openExportPathPicker() - R.id.associate_queue -> associatedQueuePrefHandler() - R.id.edit_tags -> TagSettingsDialog.newInstance(selectedItems).show(activity.supportFragmentManager, TAG) - else -> Log.e(TAG, "Unrecognized speed dial action item. Do nothing. id=$id") - } - } - private fun openExportPathPicker() { - val exportType = Export.OPML_SELECTED - val title = String.format(exportType.outputNameTemplate, SimpleDateFormat("yyyy-MM-dd", Locale.US).format(Date())) - val intentPickAction = Intent(Intent.ACTION_CREATE_DOCUMENT) - .addCategory(Intent.CATEGORY_OPENABLE) - .setType(exportType.contentType) - .putExtra(Intent.EXTRA_TITLE, title) - try { - chooseOpmlExportPathLauncher.launch(intentPickAction) - return - } catch (e: ActivityNotFoundException) { - Log.e(TAG, "No activity found. Should never happen...") - } - // if on SDK lower than API 21 or the implicit intent failed, fallback to the legacy export process - exportOPML(null) - } - fun exportOPML(uri: Uri?) { - try { - runBlocking { - Logd(TAG, "selectedFeeds: ${selectedItems.size}") - if (uri == null) ExportWorker(OpmlWriter(), requireContext()).exportFile(selectedItems) - else { - val worker = DocumentFileExportWorker(OpmlWriter(), requireContext(), uri) - worker.exportFile(selectedItems) - } - } - } catch (e: Exception) { - Log.e(TAG, "exportOPML error: ${e.message}") - } - } - private fun autoDownloadPrefHandler() { - val preferenceSwitchDialog = PreferenceSwitchDialog(activity, activity.getString(R.string.auto_download_settings_label), activity.getString(R.string.auto_download_label)) - preferenceSwitchDialog.setOnPreferenceChangedListener(@UnstableApi object: PreferenceSwitchDialog.OnPreferenceChangedListener { - override fun preferenceChanged(enabled: Boolean) { - saveFeedPreferences { it: FeedPreferences -> it.autoDownload = enabled } - } - }) - preferenceSwitchDialog.openDialog() - } - @UnstableApi private fun playbackSpeedPrefHandler() { - val vBinding = PlaybackSpeedFeedSettingDialogBinding.inflate(activity.layoutInflater) - vBinding.seekBar.setProgressChangedListener { speed: Float? -> - vBinding.currentSpeedLabel.text = String.format(Locale.getDefault(), "%.2fx", speed) - } - vBinding.useGlobalCheckbox.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> - vBinding.seekBar.isEnabled = !isChecked - vBinding.seekBar.alpha = if (isChecked) 0.4f else 1f - vBinding.currentSpeedLabel.alpha = if (isChecked) 0.4f else 1f - } - vBinding.seekBar.updateSpeed(1.0f) - MaterialAlertDialogBuilder(activity) - .setTitle(R.string.playback_speed) - .setView(vBinding.root) - .setPositiveButton("OK") { _: DialogInterface?, _: Int -> - val newSpeed = if (vBinding.useGlobalCheckbox.isChecked) FeedPreferences.SPEED_USE_GLOBAL - else vBinding.seekBar.currentSpeed - saveFeedPreferences { it: FeedPreferences -> - it.playSpeed = newSpeed - } - } - .setNegativeButton(R.string.cancel_label, null) - .show() - } - private fun autoDeleteEpisodesPrefHandler() { - val composeView = ComposeView(activity).apply { - setContent { - val showDialog = remember { mutableStateOf(true) } - CustomTheme(activity) { - AutoDeleteDialog(showDialog.value, onDismissRequest = { showDialog.value = false }) - } - } - } - (activity.window.decorView as ViewGroup).addView(composeView) - } - private fun associatedQueuePrefHandler() { - val composeView = ComposeView(activity).apply { - setContent { - val showDialog = remember { mutableStateOf(true) } - CustomTheme(activity) { - SetAssociatedQueue(showDialog.value, onDismissRequest = { showDialog.value = false }) - } - } - } - (activity.window.decorView as ViewGroup).addView(composeView) - } - private fun keepUpdatedPrefHandler() { - val composeView = ComposeView(activity).apply { - setContent { - val showDialog = remember { mutableStateOf(true) } - CustomTheme(activity) { - KeepUpdatedDialog(showDialog.value, onDismissRequest = { showDialog.value = false }) - } - } - } - (activity.window.decorView as ViewGroup).addView(composeView) - } - @UnstableApi private fun saveFeedPreferences(preferencesConsumer: Consumer) { - for (feed in selectedItems) { + fun EpisodeSpeedDial(activity: MainActivity, selected: SnapshotStateList, modifier: Modifier = Modifier) { + val TAG = "EpisodeSpeedDial ${selected.size}" + var isExpanded by remember { mutableStateOf(false) } + fun saveFeedPreferences(preferencesConsumer: Consumer) { + for (feed in selected) { if (feed.preferences == null) continue runOnIOScope { upsert(feed) { @@ -727,152 +518,45 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec } } } - val numItems = selectedItems.size + val numItems = selected.size activity.showSnackbarAbovePlayer(activity.resources.getQuantityString(R.plurals.updated_feeds_batch_label, numItems, numItems), Snackbar.LENGTH_LONG) } - @Composable - fun KeepUpdatedDialog(showDialog: Boolean, onDismissRequest: () -> Unit) { - if (showDialog) { - Dialog(onDismissRequest = { onDismissRequest() }) { - Card( - modifier = Modifier - .wrapContentSize(align = Alignment.Center) - .padding(16.dp), - shape = RoundedCornerShape(16.dp), - ) { - Column( - modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.Center - ) { - Row(Modifier.fillMaxWidth()) { - Icon(ImageVector.vectorResource(id = R.drawable.ic_refresh), "") - Spacer(modifier = Modifier.width(20.dp)) - Text( - text = stringResource(R.string.keep_updated), - style = MaterialTheme.typography.h6 - ) - Spacer(modifier = Modifier.weight(1f)) - var checked by remember { mutableStateOf(false) } - Switch( - checked = checked, - onCheckedChange = { - checked = it - saveFeedPreferences { pref: FeedPreferences -> - pref.keepUpdated = checked - } - } - ) - } - Text( - text = stringResource(R.string.keep_updated_summary), - style = MaterialTheme.typography.body2 - ) - } - } - } - } - } - @Composable - fun AutoDeleteDialog(showDialog: Boolean, onDismissRequest: () -> Unit) { - if (showDialog) { - val (selectedOption, _) = remember { mutableStateOf("") } - Dialog(onDismissRequest = { onDismissRequest() }) { - Card( - modifier = Modifier - .wrapContentSize(align = Alignment.Center) - .padding(16.dp), - shape = RoundedCornerShape(16.dp), - ) { - Column( - modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Column { - FeedAutoDeleteOptions.forEach { text -> - Row( - Modifier - .fillMaxWidth() - .selectable( - selected = (text == selectedOption), - onClick = { - if (text != selectedOption) { - val autoDeleteAction: AutoDeleteAction = AutoDeleteAction.fromTag(text) - saveFeedPreferences { it: FeedPreferences -> - it.autoDeleteAction = autoDeleteAction + fun autoDeleteEpisodesPrefHandler() { + val composeView = ComposeView(activity).apply { + setContent { + val showDialog = remember { mutableStateOf(true) } + CustomTheme(activity) { + if (showDialog.value) { + val (selectedOption, _) = remember { mutableStateOf("") } + Dialog(onDismissRequest = { showDialog.value = false }) { + Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) { + Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Column { + FeedAutoDeleteOptions.forEach { text -> + Row( + Modifier.fillMaxWidth().padding(horizontal = 16.dp).selectable( + selected = (text == selectedOption), + onClick = { + if (text != selectedOption) { + val autoDeleteAction: AutoDeleteAction = AutoDeleteAction.fromTag(text) + saveFeedPreferences { it: FeedPreferences -> + it.autoDeleteAction = autoDeleteAction + } + showDialog.value = false + } } - onDismissRequest() - } + ), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton(selected = (text == selectedOption), onClick = { }) + Text( + text = text, + style = MaterialTheme.typography.bodyLarge.merge(), + modifier = Modifier.padding(start = 16.dp) + ) } - ) - .padding(horizontal = 16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - RadioButton( - selected = (text == selectedOption), - onClick = { } - ) - Text( - text = text, - style = MaterialTheme.typography.body1.merge(), - modifier = Modifier.padding(start = 16.dp) - ) - } - } - } - } - } - } - } - } - @Composable - private fun SetAssociatedQueue(showDialog: Boolean, onDismissRequest: () -> Unit) { - var selected by remember {mutableStateOf("")} - if (showDialog) { - Dialog(onDismissRequest = { onDismissRequest() }) { - Card(modifier = Modifier - .wrapContentSize(align = Alignment.Center) - .padding(16.dp), - shape = RoundedCornerShape(16.dp), - ) { - Column(modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - queueSettingOptions.forEach { option -> - Row(modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - Checkbox(checked = option == selected, - onCheckedChange = { isChecked -> - selected = option - if (isChecked) Logd(TAG, "$option is checked") - when (selected) { - "Default" -> { - saveFeedPreferences { it: FeedPreferences -> it.queueId = 0L } - onDismissRequest() - } - "Active" -> { - saveFeedPreferences { it: FeedPreferences -> it.queueId = -1L } - onDismissRequest() - } - "None" -> { - saveFeedPreferences { it: FeedPreferences -> it.queueId = -2L } - onDismissRequest() - } - "Custom" -> {} } } - ) - Text(option) - } - } - if (selected == "Custom") { - val queues = realm.query(PlayQueue::class).find() - Spinner(items = queues.map { it.name }, selectedItem = "Default") { name -> - Logd(TAG, "Queue selected: $name") - val q = queues.firstOrNull { it.name == name } - if (q != null) { - saveFeedPreferences { it: FeedPreferences -> it.queueId = q.id } - onDismissRequest() } } } @@ -880,260 +564,456 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec } } } + (activity.window.decorView as ViewGroup).addView(composeView) } - } - - @OptIn(UnstableApi::class) - private abstract inner class SubscriptionsAdapter : SelectableAdapter(activity as MainActivity) { - protected var feedList: List - var selectedItem: Feed? = null - protected var longPressedPosition: Int = 0 // used to init actionMode - val selectedItems: List - get() { - val items = ArrayList() - for (i in 0 until itemCount) { - if (isSelected(i)) items.add(feedList[i]) - } - return items - } - - init { - this.feedList = ArrayList() - setHasStableIds(true) - } - fun clearData() { - feedList = listOf() - } - fun getItem(position: Int): Any { - return feedList[position] - } - override fun getItemCount(): Int { - return feedList.size - } - override fun getItemId(position: Int): Long { - if (position >= feedList.size) return RecyclerView.NO_ID // Dummy views - return feedList[position].id - } - fun setItems(listItems: List) { - this.feedList = listItems - notifyDataSetChanged() - } - } - - private inner class ListAdapter : SubscriptionsAdapter() { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderExpanded { - val itemView: View = LayoutInflater.from(activity).inflate(R.layout.subscription_item, parent, false) - return ViewHolderExpanded(itemView) - } - @UnstableApi override fun onBindViewHolder(holder: ViewHolderExpanded, position: Int) { - val feed: Feed = feedList[position] - holder.bind(feed) - if (inActionMode()) { - holder.selectCheckbox.visibility = View.VISIBLE - holder.selectView.visibility = View.VISIBLE - - holder.selectCheckbox.setChecked(isSelected(position)) - holder.selectCheckbox.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> - setSelected(holder.bindingAdapterPosition, isChecked) - } - holder.coverImage.alpha = 0.6f - holder.count.visibility = View.GONE - } else { - holder.selectView.visibility = View.GONE - holder.coverImage.alpha = 1.0f - } - holder.coverImage.setOnClickListener { - if (inActionMode()) holder.selectCheckbox.setChecked(!isSelected(holder.bindingAdapterPosition)) - else { - val fragment: Fragment = FeedInfoFragment.newInstance(feed) - (activity as MainActivity).loadChildFragment(fragment) - } - } - holder.infoCard.setOnClickListener { - if (inActionMode()) holder.selectCheckbox.setChecked(!isSelected(holder.bindingAdapterPosition)) - else { - val fragment: Fragment = FeedEpisodesFragment.newInstance(feed.id) - (activity as MainActivity).loadChildFragment(fragment) - } - } -// holder.infoCard.setOnCreateContextMenuListener(this) - holder.infoCard.setOnLongClickListener { - longPressedPosition = holder.bindingAdapterPosition - selectedItem = feed - startSelectMode(longPressedPosition) - true - } - holder.itemView.setOnTouchListener { _: View?, e: MotionEvent -> - if (e.isFromSource(InputDevice.SOURCE_MOUSE) && e.buttonState == MotionEvent.BUTTON_SECONDARY) { - if (!inActionMode()) { - longPressedPosition = holder.bindingAdapterPosition - selectedItem = feed + fun associatedQueuePrefHandler() { + val composeView = ComposeView(activity).apply { + setContent { + val showDialog = remember { mutableStateOf(true) } + CustomTheme(activity) { + var selectedOption by remember {mutableStateOf("")} + if (showDialog.value) { + Dialog(onDismissRequest = { showDialog.value = false }) { + Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) { + Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + queueSettingOptions.forEach { option -> + Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { + Checkbox(checked = option == selectedOption, + onCheckedChange = { isChecked -> + selectedOption = option + if (isChecked) Logd(Companion.TAG, "$option is checked") + when (selectedOption) { + "Default" -> { + saveFeedPreferences { it: FeedPreferences -> it.queueId = 0L } + showDialog.value = false + } + "Active" -> { + saveFeedPreferences { it: FeedPreferences -> it.queueId = -1L } + showDialog.value = false + } + "None" -> { + saveFeedPreferences { it: FeedPreferences -> it.queueId = -2L } + showDialog.value = false + } + "Custom" -> {} + } + } + ) + Text(option) + } + } + if (selectedOption == "Custom") { + val queues = realm.query(PlayQueue::class).find() + Spinner(items = queues.map { it.name }, selectedItem = "Default") { name -> + Logd(Companion.TAG, "Queue selected: $name") + val q = queues.firstOrNull { it.name == name } + if (q != null) { + saveFeedPreferences { it: FeedPreferences -> it.queueId = q.id } + showDialog.value = false + } + } + } + } + } + } + } } } - false } - holder.itemView.setOnClickListener { - if (inActionMode()) holder.selectCheckbox.setChecked(!isSelected(holder.bindingAdapterPosition)) - else { -// val fragment: Fragment = FeedEpisodesFragment.newInstance(feed.id) -// mainActivityRef.get()?.loadChildFragment(fragment) - } + (activity.window.decorView as ViewGroup).addView(composeView) + } + val options = listOf<@Composable () -> Unit>( + { Row(modifier = Modifier.padding(horizontal = 16.dp) + .clickable { + isExpanded = false + Logd(TAG, "ic_delete: ${selected.size}") + RemoveFeedDialog.show(activity, selected) + }, verticalAlignment = Alignment.CenterVertically + ) { + Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_delete), "") + Text(stringResource(id = R.string.remove_feed_label)) + } }, + { Row(modifier = Modifier.padding(horizontal = 16.dp) + .clickable { + isExpanded = false + Logd(TAG, "ic_refresh: ${selected.size}") + val composeView = ComposeView(activity).apply { + setContent { + val showDialog = remember { mutableStateOf(true) } + CustomTheme(activity) { + if (showDialog.value) { + Dialog(onDismissRequest = { showDialog.value = false }) { + Card( + modifier = Modifier + .wrapContentSize(align = Alignment.Center) + .padding(16.dp), + shape = RoundedCornerShape(16.dp), + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.Center + ) { + Row(Modifier.fillMaxWidth()) { + Icon(ImageVector.vectorResource(id = R.drawable.ic_refresh), "") + Spacer(modifier = Modifier.width(20.dp)) + Text( + text = stringResource(R.string.keep_updated), + style = MaterialTheme.typography.titleLarge + ) + Spacer(modifier = Modifier.weight(1f)) + var checked by remember { mutableStateOf(false) } + Switch( + checked = checked, + onCheckedChange = { + checked = it + saveFeedPreferences { pref: FeedPreferences -> + pref.keepUpdated = checked + } + } + ) + } + Text( + text = stringResource(R.string.keep_updated_summary), + style = MaterialTheme.typography.bodyMedium + ) + } + } + } + } + } + } + } + (activity.window.decorView as ViewGroup).addView(composeView) + }, verticalAlignment = Alignment.CenterVertically + ) { + Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_refresh), "") + Text(stringResource(id = R.string.keep_updated)) + } }, + { Row(modifier = Modifier.padding(horizontal = 16.dp) + .clickable { + isExpanded = false + Logd(TAG, "ic_download: ${selected.size}") + val preferenceSwitchDialog = PreferenceSwitchDialog(activity, activity.getString(R.string.auto_download_settings_label), activity.getString(R.string.auto_download_label)) + preferenceSwitchDialog.setOnPreferenceChangedListener(@UnstableApi object: PreferenceSwitchDialog.OnPreferenceChangedListener { + override fun preferenceChanged(enabled: Boolean) { + saveFeedPreferences { it: FeedPreferences -> it.autoDownload = enabled } + } + }) + preferenceSwitchDialog.openDialog() + }, verticalAlignment = Alignment.CenterVertically + ) { + Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_download), "") + Text(stringResource(id = R.string.auto_download_label)) + } }, + { Row(modifier = Modifier.padding(horizontal = 16.dp) + .clickable { + isExpanded = false + Logd(TAG, "ic_delete_auto: ${selected.size}") + autoDeleteEpisodesPrefHandler() + }, verticalAlignment = Alignment.CenterVertically + ) { + Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_delete_auto), "") + Text(stringResource(id = R.string.auto_delete_label)) + } }, + { Row(modifier = Modifier.padding(horizontal = 16.dp) + .clickable { + isExpanded = false + Logd(TAG, "ic_playback_speed: ${selected.size}") + val vBinding = PlaybackSpeedFeedSettingDialogBinding.inflate(activity.layoutInflater) + vBinding.seekBar.setProgressChangedListener { speed: Float? -> + vBinding.currentSpeedLabel.text = String.format(Locale.getDefault(), "%.2fx", speed) + } + vBinding.useGlobalCheckbox.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + vBinding.seekBar.isEnabled = !isChecked + vBinding.seekBar.alpha = if (isChecked) 0.4f else 1f + vBinding.currentSpeedLabel.alpha = if (isChecked) 0.4f else 1f + } + vBinding.seekBar.updateSpeed(1.0f) + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.playback_speed) + .setView(vBinding.root) + .setPositiveButton("OK") { _: DialogInterface?, _: Int -> + val newSpeed = if (vBinding.useGlobalCheckbox.isChecked) FeedPreferences.SPEED_USE_GLOBAL + else vBinding.seekBar.currentSpeed + saveFeedPreferences { it: FeedPreferences -> + it.playSpeed = newSpeed + } + } + .setNegativeButton(R.string.cancel_label, null) + .show() + }, verticalAlignment = Alignment.CenterVertically + ) { + Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_playback_speed), "") + Text(stringResource(id = R.string.playback_speed)) + } }, + { Row(modifier = Modifier.padding(horizontal = 16.dp) + .clickable { + isExpanded = false + Logd(TAG, "ic_tag: ${selected.size}") + TagSettingsDialog.newInstance(selected).show(activity.supportFragmentManager, Companion.TAG) + }, verticalAlignment = Alignment.CenterVertically + ) { + Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_tag), "") + Text(stringResource(id = R.string.edit_tags)) + } }, + { Row(modifier = Modifier.padding(horizontal = 16.dp) + .clickable { + isExpanded = false + Logd(TAG, "ic_playlist_play: ${selected.size}") + associatedQueuePrefHandler() + }, verticalAlignment = Alignment.CenterVertically + ) { + Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_playlist_play), "") + Text(stringResource(id = R.string.pref_feed_associated_queue)) + } }, + { Row(modifier = Modifier.padding(horizontal = 16.dp) + .clickable { + isExpanded = false + Logd(TAG, "baseline_import_export_24: ${selected.size}") + val exportType = Export.OPML_SELECTED + val title = String.format(exportType.outputNameTemplate, SimpleDateFormat("yyyy-MM-dd", Locale.US).format(Date())) + val intentPickAction = Intent(Intent.ACTION_CREATE_DOCUMENT) + .addCategory(Intent.CATEGORY_OPENABLE) + .setType(exportType.contentType) + .putExtra(Intent.EXTRA_TITLE, title) + try { + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> + if (result.resultCode != RESULT_OK || result.data == null) return@registerForActivityResult + val uri = result.data!!.data + exportOPML(uri, selected) + }.launch(intentPickAction) + return@clickable + } catch (e: ActivityNotFoundException) { Log.e(Companion.TAG, "No activity found. Should never happen...") } + // if on SDK lower than API 21 or the implicit intent failed, fallback to the legacy export process + exportOPML(null, selected) + }, verticalAlignment = Alignment.CenterVertically + ) { + Icon(imageVector = ImageVector.vectorResource(id = R.drawable.baseline_import_export_24), "") + Text(stringResource(id = R.string.opml_export_label)) + } }, + ) + val scrollState = rememberScrollState() + Column(modifier = modifier.verticalScroll(scrollState), verticalArrangement = Arrangement.Bottom) { + if (isExpanded) options.forEachIndexed { _, button -> + FloatingActionButton(modifier = Modifier.padding(start = 4.dp, bottom = 6.dp).height(40.dp), + containerColor = Color.LightGray, + onClick = {}) { button() } } + FloatingActionButton(containerColor = Color.Green, + onClick = { isExpanded = !isExpanded }) { Icon(Icons.Filled.Edit, "Edit") } } } - private inner class GridAdapter : SubscriptionsAdapter() { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderBrief { - val itemView: View = LayoutInflater.from(activity).inflate(R.layout.subscription_item_brief, parent, false) - return ViewHolderBrief(itemView) - } - @UnstableApi override fun onBindViewHolder(holder: ViewHolderBrief, position: Int) { - val feed: Feed = feedList[position] - holder.bind(feed) - if (inActionMode()) { - holder.selectCheckbox.visibility = View.VISIBLE - holder.selectView.visibility = View.VISIBLE - - holder.selectCheckbox.setChecked(isSelected(position)) - holder.selectCheckbox.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> - setSelected(holder.bindingAdapterPosition, isChecked) - } - holder.coverImage.alpha = 0.6f - holder.count.visibility = View.GONE + @kotlin.OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) + @Composable + fun LazyList() { + var selectedSize by remember { mutableStateOf(0) } + val selected = remember { mutableStateListOf() } + var longPressIndex by remember { mutableIntStateOf(-1) } + var refreshing by remember { mutableStateOf(false)} + val coroutineScope = rememberCoroutineScope() + PullToRefreshBox(modifier = Modifier.fillMaxWidth(), isRefreshing = refreshing, indicator = {}, onRefresh = { +// coroutineScope.launch { + refreshing = true + if (swipeToRefresh) FeedUpdateManager.runOnceOrAsk(requireContext()) + refreshing = false +// } + }) { + if (if (useGrid == null) useGridLayout else useGrid!!) { + val lazyGridState = rememberLazyGridState() + LazyVerticalGrid(state = lazyGridState, + columns = GridCells.Fixed(3), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + contentPadding = PaddingValues(start = 12.dp, top = 16.dp, end = 12.dp, bottom = 16.dp), + content = { + items(feedListFiltered.size) { index -> + val feed by remember { mutableStateOf(feedListFiltered[index]) } + var isSelected by remember { mutableStateOf(false) } + LaunchedEffect(key1 = selectMode, key2 = selectedSize) { + isSelected = selectMode && feed in selected + } + fun toggleSelected() { + isSelected = !isSelected + if (isSelected) selected.add(feed) + else selected.remove(feed) + } + Column(Modifier.background(if (isSelected) MaterialTheme.colorScheme.secondary else MaterialTheme.colorScheme.surface)) { + val textColor = MaterialTheme.colorScheme.onSurface + ConstraintLayout { + val (coverImage, episodeCount, error) = createRefs() + AsyncImage(model = feed.imageUrl, contentDescription = "coverImage", + placeholder = painterResource(R.mipmap.ic_launcher), + modifier = Modifier + .constrainAs(coverImage) { + top.linkTo(parent.top) + bottom.linkTo(parent.bottom) + start.linkTo(parent.start) + }.combinedClickable(onClick = { + Logd(TAG, "clicked: ${feed.title}") + if (selectMode) toggleSelected() + else (activity as MainActivity).loadChildFragment(FeedEpisodesFragment.newInstance(feed.id)) + }, onLongClick = { + selectMode = !selectMode + isSelected = selectMode + if (selectMode) { + selected.add(feed) + longPressIndex = index + } else { + selectedSize = 0 + longPressIndex = -1 + } + Logd(TAG, "long clicked: ${feed.title}") + })) + Text(NumberFormat.getInstance().format(feed.episodes.size.toLong()), + modifier = Modifier.constrainAs(episodeCount) { + end.linkTo(parent.end) + top.linkTo(coverImage.top) + }) + Icon(painter = painterResource(R.drawable.ic_error), + contentDescription = "error", + modifier = Modifier.constrainAs(error) { + end.linkTo(parent.end) + bottom.linkTo(coverImage.bottom) + }) + } + Text(feed.title ?: "No title", + color = textColor, + maxLines = 2, + overflow = TextOverflow.Ellipsis) + } + } + } + ) } else { - holder.selectView.visibility = View.GONE - holder.coverImage.alpha = 1.0f - } - holder.coverImage.setOnClickListener { - if (inActionMode()) holder.selectCheckbox.setChecked(!isSelected(holder.bindingAdapterPosition)) - else { - val fragment: Fragment = FeedEpisodesFragment.newInstance(feed.id) - (activity as MainActivity).loadChildFragment(fragment) - } - } - holder.coverImage.setOnLongClickListener { - longPressedPosition = holder.bindingAdapterPosition - selectedItem = feed - startSelectMode(longPressedPosition) - true - } - holder.itemView.setOnTouchListener { _: View?, e: MotionEvent -> - if (e.isFromSource(InputDevice.SOURCE_MOUSE) && e.buttonState == MotionEvent.BUTTON_SECONDARY) { - if (!inActionMode()) { - longPressedPosition = holder.bindingAdapterPosition - selectedItem = feed + val lazyListState = rememberLazyListState() + LazyColumn(state = lazyListState, + modifier = Modifier.padding(start = 10.dp, end = 10.dp, top = 10.dp, bottom = 10.dp), + verticalArrangement = Arrangement.spacedBy(8.dp)) { + itemsIndexed(feedListFiltered) { index, feed -> + var isSelected by remember { mutableStateOf(false) } + LaunchedEffect(key1 = selectMode, key2 = selectedSize) { + isSelected = selectMode && feed in selected + } + fun toggleSelected() { + isSelected = !isSelected + if (isSelected) selected.add(feed) + else selected.remove(feed) + } + Row(Modifier.background(if (isSelected) MaterialTheme.colorScheme.secondary else MaterialTheme.colorScheme.surface) + .combinedClickable(onClick = { + Logd(TAG, "clicked: ${feed.title}") + if (selectMode) toggleSelected() + else (activity as MainActivity).loadChildFragment(FeedEpisodesFragment.newInstance(feed.id)) + }, onLongClick = { + selectMode = !selectMode + isSelected = selectMode + if (selectMode) { + selected.add(feed) + longPressIndex = index + } else { + selectedSize = 0 + longPressIndex = -1 + } + Logd(TAG, "long clicked: ${feed.title}") + })) { + AsyncImage(model = feed.imageUrl, contentDescription = "imgvCover", + placeholder = painterResource(R.mipmap.ic_launcher), + modifier = Modifier.width(80.dp).height(80.dp) +// .clickable(onClick = { +// Logd(TAG, "icon clicked!") +// }) + ) + val textColor = MaterialTheme.colorScheme.onSurface + Column(Modifier.fillMaxWidth().padding(start = 10.dp)) { + Text(feed.title ?: "No title", color = textColor, maxLines = 1, overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold)) + Text(feed.author ?: "No author", color = textColor, maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium) + Row(Modifier.padding(top = 5.dp)) { + Text(NumberFormat.getInstance().format(feed.episodes.size.toLong()) + " episodes", + color = textColor, style = MaterialTheme.typography.bodyMedium) + Spacer(modifier = Modifier.weight(1f)) + var feedSortInfo by remember { mutableStateOf(feed.sortInfo) } + LaunchedEffect(feedSorted) { + feedSortInfo = feed.sortInfo + } + Text(feedSortInfo, color = textColor, style = MaterialTheme.typography.bodyMedium) + } + } + Icon(painter = painterResource(R.drawable.ic_error), contentDescription = "error") + } } } - false } - holder.itemView.setOnClickListener { - if (inActionMode()) holder.selectCheckbox.setChecked(!isSelected(holder.bindingAdapterPosition)) + if (selectMode) { + Row(modifier = Modifier.align(Alignment.TopEnd).width(150.dp).height(45.dp) + .background(Color.LightGray), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically) { + Icon(painter = painterResource(R.drawable.baseline_arrow_upward_24), + tint = Color.Black, + contentDescription = null, + modifier = Modifier.width(35.dp).height(35.dp).padding(end = 10.dp) + .clickable(onClick = { + selected.clear() + for (i in 0..longPressIndex) { + selected.add(feedListFiltered[i]) + } + selectedSize = selected.size + Logd(TAG, "selectedIds: ${selected.size}") + })) + Icon(painter = painterResource(R.drawable.baseline_arrow_downward_24), + tint = Color.Black, + contentDescription = null, + modifier = Modifier.width(35.dp).height(35.dp).padding(end = 10.dp) + .clickable(onClick = { + selected.clear() + for (i in longPressIndex..) { + try { + runBlocking { + Logd(TAG, "selectedFeeds: ${selectedItems.size}") + if (uri == null) ExportWorker(OpmlWriter(), requireContext()).exportFile(selectedItems) else { -// val fragment: Fragment = FeedEpisodesFragment.newInstance(feed.id) -// mainActivityRef.get()?.loadChildFragment(fragment) + val worker = DocumentFileExportWorker(OpmlWriter(), requireContext(), uri) + worker.exportFile(selectedItems) } } - } - } - - private inner class ViewHolderExpanded(itemView: View) : RecyclerView.ViewHolder(itemView) { - val binding = SubscriptionItemBinding.bind(itemView) - val count: TextView = binding.episodeCount - val coverImage: ImageView = binding.coverImage - val infoCard: LinearLayout = binding.infoCard - val selectView: FrameLayout = binding.selectContainer - val selectCheckbox: CheckBox = binding.selectCheckBox - private val errorIcon: View = binding.errorIcon - - @UnstableApi - fun bind(feed: Feed) { - val drawable: Drawable? = AppCompatResources.getDrawable(selectView.context, R.drawable.ic_checkbox_background) - selectView.background = drawable // Setting this in XML crashes API <= 21 - binding.titleLabel.text = feed.title - binding.producerLabel.text = feed.author - binding.sortInfo.text = feed.sortInfo - coverImage.contentDescription = feed.title - coverImage.setImageDrawable(null) - - count.text = NumberFormat.getInstance().format(feed.episodes.size.toLong()) + " episodes" - count.visibility = View.VISIBLE - - val mainActRef = (activity as MainActivity) - if (feed.imageUrl != null) { - val coverLoader = CoverLoader(mainActRef) - coverLoader.withUri(feed.imageUrl) - errorIcon.visibility = if (feed.lastUpdateFailed) View.VISIBLE else View.GONE - coverLoader.withCoverView(coverImage) - coverLoader.load() - } else coverImage.setImageDrawable(AppCompatResources.getDrawable(requireContext(), R.drawable.ic_launcher_foreground)) - - val density: Float = mainActRef.resources.displayMetrics.density - binding.outerContainer.setCardBackgroundColor(SurfaceColors.getColorForElevation(mainActRef, 1 * density)) - - val textHPadding = 20 - val textVPadding = 5 - binding.titleLabel.setPadding(textHPadding, textVPadding, textHPadding, textVPadding) - binding.producerLabel.setPadding(textHPadding, textVPadding, textHPadding, textVPadding) - count.setPadding(textHPadding, textVPadding, textHPadding, textVPadding) - - val textSize = 14 - binding.titleLabel.textSize = textSize.toFloat() - } - } - - private inner class ViewHolderBrief(itemView: View) : RecyclerView.ViewHolder(itemView) { - val binding = SubscriptionItemBriefBinding.bind(itemView) - private val title = binding.titleLabel - val count: TextView = binding.episodeCount - - val coverImage: ImageView = binding.coverImage - val selectView: FrameLayout = binding.selectContainer - val selectCheckbox: CheckBox = binding.selectCheckBox - - private val errorIcon: View = binding.errorIcon - - @UnstableApi - fun bind(feed: Feed) { - val drawable: Drawable? = AppCompatResources.getDrawable(selectView.context, R.drawable.ic_checkbox_background) - selectView.background = drawable // Setting this in XML crashes API <= 21 - title.text = feed.title - coverImage.contentDescription = feed.title - coverImage.setImageDrawable(null) - - count.text = NumberFormat.getInstance().format(feed.episodes.size.toLong()) - count.visibility = View.VISIBLE - - val mainActRef = (activity as MainActivity) - val coverLoader = CoverLoader(mainActRef) - coverLoader.withUri(feed.imageUrl) - errorIcon.visibility = if (feed.lastUpdateFailed) View.VISIBLE else View.GONE - - coverLoader.withCoverView(coverImage) - coverLoader.load() - - val density: Float = mainActRef.resources.displayMetrics.density - binding.outerContainer.setCardBackgroundColor(SurfaceColors.getColorForElevation(mainActRef, 1 * density)) - - val textHPadding = 20 - val textVPadding = 5 - title.setPadding(textHPadding, textVPadding, textHPadding, textVPadding) - count.setPadding(textHPadding, textVPadding, textHPadding, textVPadding) - - val textSize = 14 - title.textSize = textSize.toFloat() - } - } - - private inner class GridDividerItemDecorator : RecyclerView.ItemDecoration() { - override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { - super.getItemOffsets(outRect, view, parent, state) - val context = parent.context - val insetOffset = convertDpToPixel(context, 1f).toInt() - outRect[insetOffset, insetOffset, insetOffset] = insetOffset - } - private fun convertDpToPixel(context: Context, dp: Float): Float { - return dp * context.resources.displayMetrics.density - } + } catch (e: Exception) { Log.e(TAG, "exportOPML error: ${e.message}") } } class PreferenceSwitchDialog(private var context: Context, private val title: String, private val text: String) { diff --git a/app/src/main/res/layout/downloads_fragment.xml b/app/src/main/res/layout/downloads_fragment.xml index caffc368..3ffecb2b 100644 --- a/app/src/main/res/layout/downloads_fragment.xml +++ b/app/src/main/res/layout/downloads_fragment.xml @@ -32,7 +32,4 @@ android:layout_width="match_parent" android:layout_height="wrap_content"/> - - - diff --git a/app/src/main/res/layout/fragment_subscriptions.xml b/app/src/main/res/layout/fragment_subscriptions.xml index 9adc363a..c16725cd 100644 --- a/app/src/main/res/layout/fragment_subscriptions.xml +++ b/app/src/main/res/layout/fragment_subscriptions.xml @@ -1,11 +1,12 @@ - + android:layout_height="match_parent" + android:orientation="vertical"> - + android:layout_height="wrap_content"/> - - - - - - - - - - - - - - - - - - - - - + android:orientation="horizontal"> - + android:layout_height="match_parent"/> - - - - - - - - - - - - - - - - - + diff --git a/app/src/main/res/layout/subscription_item.xml b/app/src/main/res/layout/subscription_item.xml deleted file mode 100644 index 7c556534..00000000 --- a/app/src/main/res/layout/subscription_item.xml +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/subscription_item_brief.xml b/app/src/main/res/layout/subscription_item_brief.xml deleted file mode 100644 index f6382287..00000000 --- a/app/src/main/res/layout/subscription_item_brief.xml +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/menu/feed_action_speeddial.xml b/app/src/main/res/menu/feed_action_speeddial.xml deleted file mode 100644 index 48fbbf64..00000000 --- a/app/src/main/res/menu/feed_action_speeddial.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/menu/subscriptions.xml b/app/src/main/res/menu/subscriptions.xml index ec770245..3eea1ad7 100644 --- a/app/src/main/res/menu/subscriptions.xml +++ b/app/src/main/res/menu/subscriptions.xml @@ -26,6 +26,9 @@ + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 28f5066b..ca51e93f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -119,6 +119,7 @@ Error An error occurred: Refresh + Toggle grid list Refreshing Reconcile Chapters diff --git a/changelog.md b/changelog.md index e6f32e67..f7f1fadc 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,10 @@ +# 6.8.3 (Preview release) + +* most of Subscriptions view are in Jetpack Compose, feed viewholder and adapters etc are removed +* added toggle grid and list views in the menu of Subscriptions +* migrated reliance on compose.material to compose.material3 +* not yet for prime time + # 6.8.2 (Preview release) * AudioPlayerFragment got overhauled. migrated to Jetpack Compose and PlayUI and PlayerDetailed fragments are Removed diff --git a/fastlane/metadata/android/en-US/changelogs/3020260.txt b/fastlane/metadata/android/en-US/changelogs/3020260.txt new file mode 100644 index 00000000..fc6ebdb2 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/3020260.txt @@ -0,0 +1,6 @@ + Version 6.8.3 (preview release): + +* most of Subscriptions view are in Jetpack Compose, feed viewholder and adapters etc are removed +* added toggle grid and list views in the menu of Subscriptions +* migrated reliance on compose.material to compose.material3 +* not yet for prime time diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index adaf6a7f..3e251253 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,7 +33,8 @@ kotlin = "2.0.20" kotlinxCoroutinesAndroid = "1.8.1" libraryBase = "2.1.0" lifecycleRuntimeKtx = "2.8.6" -material = "1.7.2" +#material = "1.7.2" +material3 = "1.3.0" material3Android = "1.3.0" materialVersion = "1.12.0" media3Common = "1.4.1" @@ -87,8 +88,9 @@ androidx-espresso-intents = { module = "androidx.test.espresso:espresso-intents" androidx-gridlayout = { module = "androidx.gridlayout:gridlayout", version.ref = "gridlayout" } androidx-junit = { module = "androidx.test.ext:junit", version.ref = "junit" } androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } -androidx-material = { module = "androidx.compose.material:material", version.ref = "material" } -androidx-material3-android = { group = "androidx.compose.material3", name = "material3-android", version.ref = "material3Android" } +#androidx-material = { module = "androidx.compose.material:material", version.ref = "material" } +androidx-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" } +androidx-material3-android = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3Exoplayer" } androidx-media3-media3-datasource-okhttp = { module = "androidx.media3:media3-datasource-okhttp", version.ref = "media3Ui" } androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3Ui" }