diff --git a/app/build.gradle b/app/build.gradle index 0937444b..574e12f0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -26,8 +26,8 @@ android { vectorDrawables.useSupportLibrary false vectorDrawables.generatedDensities = [] - versionCode 3020319 - versionName "6.15.9" + versionCode 3020320 + versionName "6.15.10" applicationId "ac.mdiq.podcini.R" def commit = "" diff --git a/app/src/main/kotlin/ac/mdiq/podcini/net/feed/FeedBuilder.kt b/app/src/main/kotlin/ac/mdiq/podcini/net/feed/FeedBuilder.kt index 4ee9185f..979d0be5 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/net/feed/FeedBuilder.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/net/feed/FeedBuilder.kt @@ -116,9 +116,7 @@ class FeedBuilder(val context: Context, val showError: (String?, String)->Unit) } } onDismissRequest() - }) { - Text("Confirm") - } + }) { Text("Confirm") } } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/RealmDB.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/RealmDB.kt index 23c35965..42a4b933 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/RealmDB.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/RealmDB.kt @@ -214,7 +214,7 @@ object RealmDB { result } } - + fun runOnIOScope(block: suspend () -> Unit) : Job { return ioScope.launch { if (Dispatchers.IO == coroutineContext[ContinuationInterceptor]) block() diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/SwipeActions.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/SwipeActions.kt index 0b3a1582..61577183 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/SwipeActions.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/SwipeActions.kt @@ -18,13 +18,11 @@ import ac.mdiq.podcini.ui.compose.* import ac.mdiq.podcini.ui.fragment.* import ac.mdiq.podcini.util.EventFlow import ac.mdiq.podcini.util.FlowEvent -import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.MiscFormatter.fullDateTimeString import android.content.Context import android.content.DialogInterface import android.content.SharedPreferences import android.util.TypedValue -import android.view.ViewGroup import androidx.annotation.AttrRes import androidx.annotation.DrawableRes import androidx.compose.foundation.BorderStroke @@ -39,7 +37,6 @@ 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.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource @@ -66,34 +63,29 @@ interface SwipeAction { @DrawableRes fun getActionColor(): Int + @Composable + fun ActionOptions() {} + fun performAction(item: Episode, fragment: Fragment) } class SwipeActions(private val fragment: Fragment, private val tag: String) : DefaultLifecycleObserver { - var actions: Actions = getPrefs(tag, "") + var actions by mutableStateOf(getPrefs(tag, "")) override fun onStart(owner: LifecycleOwner) { actions = getPrefs(tag, "") } - fun showDialog() { - Logd("SwipeActions", "showDialog()") - val composeView = ComposeView(fragment.requireContext()).apply { - setContent { - val showDialog = remember { mutableStateOf(true) } - CustomTheme(fragment.requireContext()) { - SwipeActionsDialog(this@SwipeActions.tag, onDismissRequest = { - showDialog.value = false - (fragment.view as? ViewGroup)?.removeView(this@apply) - }) { - actions = getPrefs(this@SwipeActions.tag, "") - // TODO: remove the need of event - EventFlow.postEvent(FlowEvent.SwipeActionsChangedEvent()) - } - } - } - } - (fragment.view as? ViewGroup)?.addView(composeView) + @Composable + fun ActionOptionsDialog() { + actions.left[0].ActionOptions() + actions.right[0].ActionOptions() + } + + fun dialogCallback() { + actions = getPrefs(this@SwipeActions.tag, "") + // TODO: remove the need of event + EventFlow.postEvent(FlowEvent.SwipeActionsChangedEvent()) } class Actions(prefs: String?) { @@ -148,6 +140,10 @@ class SwipeActions(private val fragment: Fragment, private val tag: String) : De } class ComboSwipeAction : SwipeAction { + var showDialog by mutableStateOf(false) + var onEpisode by mutableStateOf(null) + var onFragment by mutableStateOf(null) + var useAction by mutableStateOf(null) override fun getId(): String { return ActionTypes.COMBO.name } @@ -161,40 +157,36 @@ class SwipeActions(private val fragment: Fragment, private val tag: String) : De return context.getString(R.string.combo_action) } override fun performAction(item: Episode, fragment: Fragment) { - val composeView = ComposeView(fragment.requireContext()).apply { - setContent { - var showDialog by remember { mutableStateOf(true) } - CustomTheme(fragment.requireContext()) { - if (showDialog) Dialog(onDismissRequest = { - showDialog = false - (fragment.view as? ViewGroup)?.removeView(this@apply) - }) { - val context = LocalContext.current - Surface(shape = RoundedCornerShape(16.dp), border = BorderStroke(1.dp, MaterialTheme.colorScheme.tertiary)) { - Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { - for (action in swipeActions) { - if (action.getId() == ActionTypes.NO_ACTION.name || action.getId() == ActionTypes.COMBO.name) continue - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(4.dp).clickable { - action.performAction(item, fragment) - showDialog = false - (fragment.view as? ViewGroup)?.removeView(this@apply) - }) { - val colorAccent = remember { - val typedValue = TypedValue() - context.theme.resolveAttribute(action.getActionColor(), typedValue, true) - Color(typedValue.data) - } - Icon(imageVector = ImageVector.vectorResource(id = action.getActionIcon()), tint = colorAccent, contentDescription = action.getTitle(context)) - Text(action.getTitle(context), Modifier.padding(start = 4.dp)) - } - } + onEpisode = item + onFragment = fragment + showDialog = true + } + @Composable + override fun ActionOptions() { + useAction?.ActionOptions() + if (showDialog && onEpisode!= null && onFragment != null) Dialog(onDismissRequest = { showDialog = false }) { + val context = LocalContext.current + Surface(shape = RoundedCornerShape(16.dp), border = BorderStroke(1.dp, MaterialTheme.colorScheme.tertiary)) { + Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { + for (action in swipeActions) { + if (action.getId() == ActionTypes.NO_ACTION.name || action.getId() == ActionTypes.COMBO.name) continue + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(4.dp).clickable { + useAction = action + action.performAction(onEpisode!!, onFragment!!) + showDialog = false + }) { + val colorAccent = remember { + val typedValue = TypedValue() + context.theme.resolveAttribute(action.getActionColor(), typedValue, true) + Color(typedValue.data) } + Icon(imageVector = ImageVector.vectorResource(id = action.getActionIcon()), tint = colorAccent, contentDescription = action.getTitle(context)) + Text(action.getTitle(context), Modifier.padding(start = 4.dp)) } } } } } - (fragment.view as? ViewGroup)?.addView(composeView) } } @@ -226,6 +218,8 @@ class SwipeActions(private val fragment: Fragment, private val tag: String) : De } class SetRatingSwipeAction : SwipeAction { + var showChooseRatingDialog by mutableStateOf(false) + var onEpisode by mutableStateOf(null) override fun getId(): String { return ActionTypes.RATING.name } @@ -239,22 +233,20 @@ class SwipeActions(private val fragment: Fragment, private val tag: String) : De return context.getString(R.string.set_rating_label) } override fun performAction(item: Episode, fragment: Fragment) { - var showChooseRatingDialog by mutableStateOf(true) - val composeView = ComposeView(fragment.requireContext()).apply { - setContent { - CustomTheme(fragment.requireContext()) { - if (showChooseRatingDialog) ChooseRatingDialog(listOf(item)) { - showChooseRatingDialog = false - (fragment.view as? ViewGroup)?.removeView(this@apply) - } - } - } - } - (fragment.view as? ViewGroup)?.addView(composeView) + onEpisode = item + showChooseRatingDialog = true + } + @Composable + override fun ActionOptions() { + if (showChooseRatingDialog && onEpisode!= null) ChooseRatingDialog(listOf(onEpisode!!)) { showChooseRatingDialog = false } } } class AddCommentSwipeAction : SwipeAction { + var showEditComment by mutableStateOf(false) + var onEpisode by mutableStateOf(null) + var localTime by mutableLongStateOf(System.currentTimeMillis()) + var editCommentText by mutableStateOf(TextFieldValue("")) override fun getId(): String { return ActionTypes.COMMENT.name } @@ -268,28 +260,25 @@ class SwipeActions(private val fragment: Fragment, private val tag: String) : De return context.getString(R.string.add_opinion_label) } override fun performAction(item: Episode, fragment: Fragment) { - var showEditComment by mutableStateOf(true) - val composeView = ComposeView(fragment.requireContext()).apply { - setContent { - CustomTheme(fragment.requireContext()) { - if (showEditComment) { - val localTime = remember { System.currentTimeMillis() } - val initCommentText = remember { (if (item.comment.isBlank()) "" else item.comment + "\n") + fullDateTimeString(localTime) + ":\n" } - var commentTextState by remember { mutableStateOf(TextFieldValue(initCommentText)) } - LargeTextEditingDialog(textState = commentTextState, onTextChange = { commentTextState = it }, - onDismissRequest = { - showEditComment = false - (fragment.view as? ViewGroup)?.removeView(this@apply) - }, - onSave = { runOnIOScope { upsert(item) { - it.comment = commentTextState.text - it.commentTime = localTime - } } }) +// onEpisode = realm.query(Episode::class).query("id == ${item.id}").first().find() + onEpisode = item + localTime = System.currentTimeMillis() + editCommentText = TextFieldValue((if (onEpisode?.comment.isNullOrBlank()) "" else onEpisode!!.comment + "\n") + fullDateTimeString(localTime) + ":\n") + showEditComment = true + } + @Composable + override fun ActionOptions() { + if (showEditComment && onEpisode != null) { + LargeTextEditingDialog(textState = editCommentText, onTextChange = { editCommentText = it }, onDismissRequest = { showEditComment = false }, + onSave = { text -> + runOnIOScope { upsert(onEpisode!!) { + it.comment = text + it.commentTime = localTime } - } - } + onEpisode = null + } + }) } - (fragment.view as? ViewGroup)?.addView(composeView) } } @@ -371,12 +360,13 @@ class SwipeActions(private val fragment: Fragment, private val tag: String) : De if (almostEnded) item = upsertBlk(item) { it.media?.playbackCompletionDate = Date() } } if (item.playState < PlayState.SKIPPED.code) item = runBlocking { setPlayStateSync(PlayState.SKIPPED.code, item, resetMediaPosition = false, removeFromQueue = false) } -// removeFromQueue(item) runOnIOScope { removeFromQueueSync(curQueue, item) } } } class PutToQueueSwipeAction : SwipeAction { + var showPutToQueueDialog by mutableStateOf(false) + var onEpisode by mutableStateOf(null) override fun getId(): String { return ActionTypes.PUT_TO_QUEUE.name } @@ -390,18 +380,12 @@ class SwipeActions(private val fragment: Fragment, private val tag: String) : De return context.getString(R.string.put_in_queue_label) } override fun performAction(item: Episode, fragment: Fragment) { - var showPutToQueueDialog by mutableStateOf(true) - val composeView = ComposeView(fragment.requireContext()).apply { - setContent { - CustomTheme(fragment.requireContext()) { - if (showPutToQueueDialog ) PutToQueueDialog(listOf(item)) { - showPutToQueueDialog = false - (fragment.view as? ViewGroup)?.removeView(this@apply) - } - } - } - } - (fragment.view as? ViewGroup)?.addView(composeView) + onEpisode = item + showPutToQueueDialog = true + } + @Composable + override fun ActionOptions() { + if (showPutToQueueDialog && onEpisode != null) PutToQueueDialog(listOf(onEpisode!!)) { showPutToQueueDialog = false } } } @@ -426,6 +410,8 @@ class SwipeActions(private val fragment: Fragment, private val tag: String) : De } class SetPlaybackStateSwipeAction : SwipeAction { + var showPlayStateDialog by mutableStateOf(false) + var onEpisode by mutableStateOf(null) override fun getId(): String { return ActionTypes.SET_PLAY_STATE.name } @@ -439,18 +425,12 @@ class SwipeActions(private val fragment: Fragment, private val tag: String) : De return context.getString(R.string.set_play_state_label) } override fun performAction(item: Episode, fragment: Fragment) { - var showPlayStateDialog by mutableStateOf(true) - val composeView = ComposeView(fragment.requireContext()).apply { - setContent { - CustomTheme(fragment.requireContext()) { - if (showPlayStateDialog) PlayStateDialog(listOf(item)) { - showPlayStateDialog = false - (fragment.view as? ViewGroup)?.removeView(this@apply) - } - } - } - } - (fragment.view as? ViewGroup)?.addView(composeView) + onEpisode = item + showPlayStateDialog = true + } + @Composable + override fun ActionOptions() { + if (showPlayStateDialog && onEpisode != null) PlayStateDialog(listOf(onEpisode!!)) { showPlayStateDialog = false } } // private fun delayedExecution(item: Episode, fragment: Fragment, duration: Float) = runBlocking { // delay(ceil((duration * 1.05f).toDouble()).toLong()) @@ -464,6 +444,8 @@ class SwipeActions(private val fragment: Fragment, private val tag: String) : De } class ShelveSwipeAction : SwipeAction { + var showShelveDialog by mutableStateOf(false) + var onEpisode by mutableStateOf(null) override fun getId(): String { return ActionTypes.SHELVE.name } @@ -477,22 +459,18 @@ class SwipeActions(private val fragment: Fragment, private val tag: String) : De return context.getString(R.string.shelve_label) } override fun performAction(item: Episode, fragment: Fragment) { - var showShelveDialog by mutableStateOf(true) - val composeView = ComposeView(fragment.requireContext()).apply { - setContent { - CustomTheme(fragment.requireContext()) { - if (showShelveDialog) ShelveDialog(listOf(item)) { - showShelveDialog = false - (fragment.view as? ViewGroup)?.removeView(this@apply) - } - } - } - } - (fragment.view as? ViewGroup)?.addView(composeView) + onEpisode = item + showShelveDialog = true + } + @Composable + override fun ActionOptions() { + if (showShelveDialog && onEpisode!= null) ShelveDialog(listOf(onEpisode!!)) { showShelveDialog = false } } } class EraseSwipeAction : SwipeAction { + var showEraseDialog by mutableStateOf(false) + var onEpisode by mutableStateOf(null) override fun getId(): String { return ActionTypes.ERASE.name } @@ -506,18 +484,12 @@ class SwipeActions(private val fragment: Fragment, private val tag: String) : De return context.getString(R.string.erase_episodes_label) } override fun performAction(item: Episode, fragment: Fragment) { - var showEraseDialog by mutableStateOf(true) - val composeView = ComposeView(fragment.requireContext()).apply { - setContent { - CustomTheme(fragment.requireContext()) { - if (showEraseDialog) EraseEpisodesDialog(listOf(item), item.feed) { - showEraseDialog = false - (fragment.view as? ViewGroup)?.removeView(this@apply) - } - } - } - } - (fragment.view as? ViewGroup)?.addView(composeView) + onEpisode = item + showEraseDialog = true + } + @Composable + override fun ActionOptions() { + if (showEraseDialog && onEpisode!= null) EraseEpisodesDialog(listOf(onEpisode!!), onEpisode!!.feed) { showEraseDialog = false } } } @@ -614,21 +586,15 @@ class SwipeActions(private val fragment: Fragment, private val tag: String) : De Dialog(onDismissRequest = { onDismissRequest() }) { var forFragment = "" when (tag) { -// AllEpisodesFragment.TAG -> { -// forFragment = stringResource(R.string.episodes_label) -// keys = keys.filter { a: SwipeAction -> !a.getId().equals(ActionTypes.REMOVE_FROM_HISTORY.name) } -// } EpisodesFragment.TAG -> { forFragment = stringResource(R.string.episodes_label) } OnlineEpisodesFragment.TAG -> { forFragment = stringResource(R.string.online_episodes_label) } -// DownloadsFragment.TAG -> { -// forFragment = stringResource(R.string.downloads_label) -// keys = keys.filter { a: SwipeAction -> -// (!a.getId().equals(ActionTypes.REMOVE_FROM_HISTORY.name) && !a.getId().equals(ActionTypes.START_DOWNLOAD.name)) } -// } + SearchFragment.TAG -> { + forFragment = stringResource(R.string.search_label) + } FeedEpisodesFragment.TAG -> { forFragment = stringResource(R.string.subscription) keys = keys.filter { a: SwipeAction -> !a.getId().equals(ActionTypes.REMOVE_FROM_HISTORY.name) } @@ -639,10 +605,6 @@ class SwipeActions(private val fragment: Fragment, private val tag: String) : De (!a.getId().equals(ActionTypes.ADD_TO_QUEUE.name) && !a.getId().equals(ActionTypes.REMOVE_FROM_HISTORY.name)) }.toList() // keys = keys.filter { a: SwipeAction -> (!a.getId().equals(ActionTypes.REMOVE_FROM_HISTORY.name)) } } -// HistoryFragment.TAG -> { -// forFragment = stringResource(R.string.playback_history_label) -// keys = keys.toList() -// } else -> {} } if (tag != QueuesFragment.TAG) keys = keys.filter { a: SwipeAction -> !a.getId().equals(ActionTypes.REMOVE_FROM_QUEUE.name) } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/BugReportActivity.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/BugReportActivity.kt index 1f009854..fb7ad196 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/BugReportActivity.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/BugReportActivity.kt @@ -11,6 +11,8 @@ import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.error.CrashReportWriter import android.content.ClipData import android.content.ClipboardManager +import android.content.Intent +import android.net.Uri import android.os.Build import android.os.Bundle import androidx.activity.compose.setContent @@ -19,6 +21,8 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.MoreVert @@ -65,7 +69,8 @@ class BugReportActivity : AppCompatActivity() { fun MainView() { val textColor = MaterialTheme.colorScheme.onSurface Scaffold(topBar = { MyTopAppBar() }) { innerPadding -> - Column(modifier = Modifier.padding(innerPadding).fillMaxSize().padding(horizontal = 10.dp)) { + val scrollState = rememberScrollState() + Column(modifier = Modifier.padding(innerPadding).fillMaxSize().padding(horizontal = 10.dp).verticalScroll(scrollState)) { if (showToast) CustomToast(message = toastMassege, onDismiss = { showToast = false }) ComfirmDialog(0, stringResource(R.string.confirm_export_log_dialog_message), showConfirmExport) { exportLog() @@ -82,6 +87,7 @@ class BugReportActivity : AppCompatActivity() { toastMassege = getString(R.string.copied_to_clipboard) showToast = true }) { Text(stringResource(R.string.copy_to_clipboard)) } + Button(modifier = Modifier.fillMaxWidth(), onClick = { sendEmail() }) { Text(stringResource(R.string.email_developer)) } Text(crashDetailsTextView, color = textColor) } } @@ -127,6 +133,19 @@ class BugReportActivity : AppCompatActivity() { } } + private fun sendEmail() { + val emailIntent = Intent(Intent.ACTION_SENDTO).apply { + data = Uri.parse("mailto:") + putExtra(Intent.EXTRA_EMAIL, arrayOf("xilin.vw@gmail.com")) + putExtra(Intent.EXTRA_SUBJECT, "Podcini issue") + putExtra(Intent.EXTRA_TEXT, crashDetailsTextView) + } + if (emailIntent.resolveActivity(packageManager) != null) startActivity(emailIntent) + else { + toastMassege = getString(R.string.need_email_client) + showToast = true + } + } companion object { private val TAG: String = BugReportActivity::class.simpleName ?: "Anonymous" } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/PreferenceActivity.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/PreferenceActivity.kt index 4965c76b..5c80754f 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/PreferenceActivity.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/PreferenceActivity.kt @@ -347,10 +347,31 @@ class PreferenceActivity : AppCompatActivity() { } IconTitleSummaryActionRow(R.drawable.ic_questionmark, R.string.online_help, R.string.online_help_sum) { openInBrowser(this@PreferenceActivity, "https://github.com/XilinJia/Podcini/") } IconTitleSummaryActionRow(R.drawable.ic_info, R.string.privacy_policy, R.string.privacy_policy) { openInBrowser(this@PreferenceActivity, "https://github.com/XilinJia/Podcini/blob/main/PrivacyPolicy.md") } - IconTitleSummaryActionRow(R.drawable.ic_info, R.string.licenses, R.string.licenses_summary) { - navController.navigate(Screens.preferences_license.tag) -// supportFragmentManager.beginTransaction().replace(R.id.settingsContainer, LicensesFragment()).addToBackStack(getString(R.string.translators)).commit() + IconTitleSummaryActionRow(R.drawable.ic_info, R.string.licenses, R.string.licenses_summary) { navController.navigate(Screens.preferences_license.tag) } + IconTitleSummaryActionRow(R.drawable.baseline_mail_outline_24, R.string.email_developer, R.string.email_sum) { + val emailIntent = Intent(Intent.ACTION_SENDTO).apply { + data = Uri.parse("mailto:") + putExtra(Intent.EXTRA_EMAIL, arrayOf("xilin.vw@gmail.com")) + putExtra(Intent.EXTRA_SUBJECT, "Regarding Podcini") + } + if (emailIntent.resolveActivity(packageManager) != null) startActivity(emailIntent) + else { + toastMassege = getString(R.string.need_email_client) + showToast = true + } } +// TextButton(modifier = Modifier.fillMaxWidth(), onClick = { +// val emailIntent = Intent(Intent.ACTION_SENDTO).apply { +// data = Uri.parse("mailto:") +// putExtra(Intent.EXTRA_EMAIL, arrayOf("xilin.vw@gmail.com")) +// putExtra(Intent.EXTRA_SUBJECT, "Regarding Podcini") +// } +// if (emailIntent.resolveActivity(packageManager) != null) startActivity(emailIntent) +// else { +// toastMassege = getString(R.string.need_email_client) +// showToast = true +// } +// }) { Text(stringResource(R.string.email_developer)) } } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/VideoplayerActivity.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/VideoplayerActivity.kt index 1ebcec42..dd6c3383 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/VideoplayerActivity.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/VideoplayerActivity.kt @@ -25,7 +25,6 @@ import ac.mdiq.podcini.storage.model.EpisodeMedia import ac.mdiq.podcini.storage.model.Playable import ac.mdiq.podcini.ui.activity.starter.MainActivityStarter import ac.mdiq.podcini.ui.compose.* -import ac.mdiq.podcini.ui.dialog.ShareDialog import ac.mdiq.podcini.ui.dialog.SleepTimerDialog import ac.mdiq.podcini.ui.utils.ShownotesCleaner import ac.mdiq.podcini.ui.view.ShownotesWebView @@ -91,6 +90,8 @@ class VideoplayerActivity : CastEnabledActivity() { val showErrorDialog = mutableStateOf(false) var errorMessage by mutableStateOf("") + var showShareDialog by mutableStateOf(false) + override fun onCreate(savedInstanceState: Bundle?) { setTheme(getNoTitleTheme(this)) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -125,6 +126,12 @@ class VideoplayerActivity : CastEnabledActivity() { if (showAudioControlDialog) PlaybackControlsDialog(onDismiss = { showAudioControlDialog = false }) if (showSpeedDialog) PlaybackSpeedFullDialog(settingCode = booleanArrayOf(true, true, true), indexDefault = 0, maxSpeed = 3f, onDismiss = { showSpeedDialog = false }) MediaPlayerErrorDialog(this, errorMessage, showErrorDialog) + if (showShareDialog) { + val feedItem = (curMedia as? EpisodeMedia)?.episodeOrFetch() + if (feedItem != null) ShareDialog(feedItem, this@VideoplayerActivity) { showShareDialog = false } + else showShareDialog = false + } + LaunchedEffect(curMediaId) { cleanedNotes = null } Scaffold(topBar = { MyTopAppBar() }) { innerPadding -> @@ -371,12 +378,7 @@ class VideoplayerActivity : CastEnabledActivity() { val feedItem = (curMedia as? EpisodeMedia)?.episodeOrFetch() if (feedItem != null) startActivity(MainActivity.getIntentToOpenFeed(this@VideoplayerActivity, feedItem.feedId!!)) }) { Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_feed), contentDescription = "open podcast") } - IconButton(onClick = { - val feedItem = (curMedia as? EpisodeMedia)?.episodeOrFetch() - if (feedItem != null) { - val shareDialog = ShareDialog.newInstance(feedItem) - shareDialog.show(supportFragmentManager, "ShareEpisodeDialog") - } + IconButton(onClick = { showShareDialog = true }) { Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_share), contentDescription = "share") } } CastIconButton() @@ -403,11 +405,7 @@ class VideoplayerActivity : CastEnabledActivity() { expanded = false }) DropdownMenuItem(text = { Text(stringResource(R.string.share_label)) }, onClick = { - val feedItem = (curMedia as? EpisodeMedia)?.episodeOrFetch() - if (feedItem != null) { - val shareDialog = ShareDialog.newInstance(feedItem) - shareDialog.show(supportFragmentManager, "ShareEpisodeDialog") - } + showShareDialog = true expanded = false }) DropdownMenuItem(text = { Text(stringResource(R.string.playback_speed)) }, onClick = { 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 242053cc..da6b77ea 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 @@ -2,8 +2,13 @@ package ac.mdiq.podcini.ui.compose import ac.mdiq.podcini.R import ac.mdiq.podcini.preferences.UserPreferences.appPrefs +import ac.mdiq.podcini.storage.model.Episode import ac.mdiq.podcini.ui.activity.MainActivity +import ac.mdiq.podcini.util.ShareUtils.shareFeedItemFile +import ac.mdiq.podcini.util.ShareUtils.shareFeedItemLinkWithDownloadLink +import ac.mdiq.podcini.util.ShareUtils.shareMediaDownloadLink import android.app.Activity +import android.content.Context import android.text.Spannable import android.text.SpannableString import android.text.style.ForegroundColorSpan @@ -19,6 +24,7 @@ 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.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.TextStyle @@ -36,6 +42,11 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.compose.LocalLifecycleOwner import com.google.android.material.bottomsheet.BottomSheetBehavior import kotlinx.coroutines.delay +import java.time.Instant +import java.time.LocalDate +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import java.util.* @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -309,3 +320,148 @@ fun SearchBarRow(hintTextRes: Int, defaultText: String = "", performSearch: (Str modifier = Modifier.width(40.dp).height(40.dp).padding(start = 5.dp).clickable(onClick = { performSearch(queryText) })) } } + +@Composable +fun ShareDialog(item: Episode, act: Activity, onDismissRequest: ()->Unit) { + val PREF_SHARE_EPISODE_START_AT = "prefShareEpisodeStartAt" + val PREF_SHARE_EPISODE_TYPE = "prefShareEpisodeType" + + val prefs = remember { act.getSharedPreferences("ShareDialog", Context.MODE_PRIVATE) } + val hasMedia = remember { item.media != null } + val downloaded = remember { hasMedia && item.media!!.downloaded } + val hasDownloadUrl = remember { hasMedia && item.media!!.downloadUrl != null } + + var type = remember { prefs.getInt(PREF_SHARE_EPISODE_TYPE, 1) } + if ((type == 2 && !hasDownloadUrl) || (type == 3 && !downloaded)) type = 1 + + var position by remember { mutableIntStateOf(type) } + var isChecked by remember { mutableStateOf(false) } + var ctx = LocalContext.current + + AlertDialog(onDismissRequest = { onDismissRequest() }, + title = { Text(stringResource(R.string.share_label), style = CustomTextStyles.titleCustom) }, + text = { + Column { + Row(verticalAlignment = Alignment.CenterVertically) { + RadioButton(selected = position == 1, onClick = { position = 1 }) + Text(stringResource(R.string.share_dialog_for_social)) + } + if (hasDownloadUrl) Row(verticalAlignment = Alignment.CenterVertically) { + RadioButton(selected = position == 2, onClick = { position = 2 }) + Text(stringResource(R.string.share_dialog_media_address)) + } + if (downloaded) Row(verticalAlignment = Alignment.CenterVertically) { + RadioButton(selected = position == 3, onClick = { position = 3 }) + Text(stringResource(R.string.share_dialog_media_file_label)) + } + HorizontalDivider(modifier = Modifier.fillMaxWidth().height(5.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + Checkbox(checked = isChecked, onCheckedChange = { isChecked = it }) + Text(stringResource(R.string.share_playback_position_dialog_label)) + } + } + }, + confirmButton = { + TextButton(onClick = { + when(position) { + 1 -> shareFeedItemLinkWithDownloadLink(ctx, item, isChecked) + 2 -> shareMediaDownloadLink(ctx, item.media!!) + 3 -> shareFeedItemFile(ctx, item.media!!) + } + prefs.edit().putBoolean(PREF_SHARE_EPISODE_START_AT, isChecked).putInt(PREF_SHARE_EPISODE_TYPE, position).apply() + onDismissRequest() + }) { Text(text = "OK") } + }, + dismissButton = { TextButton(onClick = { onDismissRequest() }) { Text(text = "Cancel") } } + ) +} + +@Composable +fun DatesFilterDialogCompose(inclPlayed: Boolean = false, from: Long? = null, to: Long? = null, oldestDate: Long, onDismissRequest: ()->Unit, callback: (Long, Long, Boolean) -> Unit) { + @Composable + fun MonthYearInput(default: String, onMonthYearChange: (String) -> Unit) { + fun formatMonthYear(input: String): String { + val sanitized = input.replace(Regex("[^0-9/]"), "") + return when { + sanitized.length > 7 -> sanitized.substring(0, 7) + else -> sanitized + } + } + fun isValidMonthYear(input: String): Boolean { + val regex = Regex("^(0[1-9]|1[0-2])/\\d{4}$") + return regex.matches(input) + } + var monthYear by remember { mutableStateOf(TextFieldValue(default)) } + var isValid by remember { mutableStateOf(isValidMonthYear(monthYear.text)) } + Column(modifier = Modifier.padding(16.dp)) { + TextField(value = monthYear, label = { Text(stringResource(R.string.statistics_month_year)) }, + onValueChange = { input -> + monthYear = input + val formattedInput = formatMonthYear(monthYear.text) + isValid = isValidMonthYear(formattedInput) + if (isValid) onMonthYearChange(formattedInput) + }, + isError = !isValidMonthYear(monthYear.text), + modifier = Modifier.fillMaxWidth() + ) + if (!isValid) Text(text = "Invalid format. Please use MM/YYYY.", color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodyMedium) + } + } + fun convertMonthYearToUnixTime(monthYear: String, start: Boolean = true): Long? { + val regex = Regex("^(0[1-9]|1[0-2])/\\d{4}$") + if (!regex.matches(monthYear)) return null + val (month, year) = monthYear.split("/").map { it.toInt() } + val localDate = if (start) LocalDate.of(year, month, 1) else LocalDate.of(year, month, 1).plusMonths(1).minusDays(1) + return localDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli() + } + fun convertUnixTimeToMonthYear(unixTime: Long): String { + return Instant.ofEpochMilli(unixTime).atZone(ZoneId.systemDefault()).toLocalDate().format(DateTimeFormatter.ofPattern("MM/yyyy")) + } + var includeMarkedAsPlayed by remember { mutableStateOf(inclPlayed) } + var timeFilterFrom by remember { mutableLongStateOf(from ?: oldestDate) } + var timeFilterTo by remember { mutableLongStateOf(to ?: Date().time) } + var useAllTime by remember { mutableStateOf(false) } + AlertDialog(onDismissRequest = { onDismissRequest() }, + title = { Text(stringResource(R.string.share_label), style = CustomTextStyles.titleCustom) }, + text = { + Column { + Row(verticalAlignment = Alignment.CenterVertically) { + Checkbox(checked = includeMarkedAsPlayed, onCheckedChange = { + includeMarkedAsPlayed = it +// if (includeMarkedAsPlayed) { +// timeFilterFrom = 0 +// timeFilterTo = Long.MAX_VALUE +// } + }) + Text(stringResource(R.string.statistics_include_marked)) + } + if (!useAllTime) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text(stringResource(R.string.statistics_start_month)) + MonthYearInput(convertUnixTimeToMonthYear(oldestDate)) { timeFilterFrom = convertMonthYearToUnixTime(it) ?: oldestDate } + } + Row(verticalAlignment = Alignment.CenterVertically) { + Text(stringResource(R.string.statistics_end_month)) + MonthYearInput(convertUnixTimeToMonthYear(System.currentTimeMillis())) { timeFilterTo = convertMonthYearToUnixTime(it, false) ?: Date().time } + } + } + Row(verticalAlignment = Alignment.CenterVertically) { + Checkbox(checked = useAllTime, onCheckedChange = { useAllTime = it }) + Text(stringResource(R.string.statistics_filter_all_time)) + } + Text(stringResource(R.string.statistics_speed_not_counted)) + } + }, + confirmButton = { + TextButton(onClick = { + if (useAllTime) { + timeFilterFrom = oldestDate + timeFilterTo = Date().time + } + callback(timeFilterFrom, timeFilterTo, includeMarkedAsPlayed) + onDismissRequest() + }) { Text(text = "OK") } + }, + dismissButton = { TextButton(onClick = { onDismissRequest() }) { Text(text = "Cancel") } } + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/dialog/DatesFilterDialog.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/dialog/DatesFilterDialog.kt deleted file mode 100644 index 8363bfe1..00000000 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/dialog/DatesFilterDialog.kt +++ /dev/null @@ -1,132 +0,0 @@ -package ac.mdiq.podcini.ui.dialog - - -import ac.mdiq.podcini.R -import ac.mdiq.podcini.databinding.StatisticsFilterDialogBinding -import android.content.Context -import android.content.DialogInterface -import android.text.format.DateFormat -import android.view.LayoutInflater -import android.view.View -import android.widget.ArrayAdapter -import android.widget.CompoundButton -import androidx.core.util.Pair -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import java.text.SimpleDateFormat -import java.util.* -import kotlin.math.max - -abstract class DatesFilterDialog(private val context: Context, oldestDate: Long) { - -// protected var prefs: SharedPreferences? = null - protected var includeMarkedAsPlayed: Boolean = false - protected var timeFilterFrom: Long = 0L - protected var timeFilterTo: Long = Date().time - protected var showMarkPlayed = true - - private val filterDatesFrom: Pair, Array> - private val filterDatesTo: Pair, Array> - - init { - initParams() - filterDatesFrom = makeMonthlyList(oldestDate, false) - filterDatesTo = makeMonthlyList(oldestDate, true) - } - -// set prefs, includeMarkedAsPlayed, timeFilterFrom, timeFilterTo - abstract fun initParams() - abstract fun callback(timeFilterFrom: Long, timeFilterTo: Long, includeMarkedAsPlayed: Boolean = true) - - fun show() { - val binding = StatisticsFilterDialogBinding.inflate(LayoutInflater.from(context)) - val builder = MaterialAlertDialogBuilder(context) - builder.setView(binding.root) - builder.setTitle(R.string.filter) - binding.includeMarkedCheckbox.setOnCheckedChangeListener { _: CompoundButton?, checked: Boolean -> - binding.timeToSpinner.isEnabled = !checked - binding.timeFromSpinner.isEnabled = !checked - binding.pastYearButton.isEnabled = !checked - binding.allTimeButton.isEnabled = !checked - binding.dateSelectionContainer.alpha = if (checked) 0.5f else 1f - } - if (showMarkPlayed) binding.includeMarkedCheckbox.isChecked = includeMarkedAsPlayed - else { - binding.includeMarkedCheckbox.visibility = View.GONE - binding.noticeMessage.visibility = View.GONE - } - - val adapterFrom = ArrayAdapter(context, android.R.layout.simple_spinner_item, filterDatesFrom.first) - adapterFrom.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) - binding.timeFromSpinner.adapter = adapterFrom - for (i in filterDatesFrom.second.indices) { - if (filterDatesFrom.second[i] >= timeFilterFrom) { - binding.timeFromSpinner.setSelection(i) - break - } - } - - val adapterTo = ArrayAdapter(context, android.R.layout.simple_spinner_item, filterDatesTo.first) - adapterTo.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) - binding.timeToSpinner.adapter = adapterTo - for (i in filterDatesTo.second.indices) { - if (filterDatesTo.second[i] >= timeFilterTo) { - binding.timeToSpinner.setSelection(i) - break - } - } - - binding.allTimeButton.setOnClickListener { - binding.timeFromSpinner.setSelection(0) - binding.timeToSpinner.setSelection(filterDatesTo.first.size - 1) - } - binding.pastYearButton.setOnClickListener { - binding.timeFromSpinner.setSelection(max(0.0, (filterDatesFrom.first.size - 12).toDouble()).toInt()) - binding.timeToSpinner.setSelection(filterDatesTo.first.size - 2) - } - - builder.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> - includeMarkedAsPlayed = binding.includeMarkedCheckbox.isChecked - if (includeMarkedAsPlayed) { -// TODO: what can be done with this? - // We do not know the date at which something was marked as played, so filtering does not make sense - timeFilterFrom = 0 - timeFilterTo = Long.MAX_VALUE - } else { - timeFilterFrom = filterDatesFrom.second[binding.timeFromSpinner.selectedItemPosition] - timeFilterTo = filterDatesTo.second[binding.timeToSpinner.selectedItemPosition] - } - callback(timeFilterFrom, timeFilterTo, includeMarkedAsPlayed) - } - builder.show() - } - - private fun makeMonthlyList(oldestDate: Long, inclusive: Boolean): Pair, Array> { - val date = Calendar.getInstance() - date.timeInMillis = oldestDate - date[Calendar.HOUR_OF_DAY] = 0 - date[Calendar.MINUTE] = 0 - date[Calendar.SECOND] = 0 - date[Calendar.MILLISECOND] = 0 - date[Calendar.DAY_OF_MONTH] = 1 - val names = ArrayList() - val timestamps = ArrayList() - val skeleton = DateFormat.getBestDateTimePattern(Locale.getDefault(), "MMM yyyy") - val dateFormat = SimpleDateFormat(skeleton, Locale.getDefault()) - while (date.timeInMillis < System.currentTimeMillis()) { - names.add(dateFormat.format(Date(date.timeInMillis))) - if (!inclusive) timestamps.add(date.timeInMillis) - - if (date[Calendar.MONTH] == Calendar.DECEMBER) { - date[Calendar.MONTH] = Calendar.JANUARY - date[Calendar.YEAR] = date[Calendar.YEAR] + 1 - } else date[Calendar.MONTH] = date[Calendar.MONTH] + 1 - - if (inclusive) timestamps.add(date.timeInMillis) - } - if (inclusive) { - names.add(context.getString(R.string.statistics_today)) - timestamps.add(Long.MAX_VALUE) - } - return Pair(names.toTypedArray(), timestamps.toTypedArray()) - } -} diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/dialog/ShareDialog.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/dialog/ShareDialog.kt deleted file mode 100644 index 873eeed5..00000000 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/dialog/ShareDialog.kt +++ /dev/null @@ -1,100 +0,0 @@ -package ac.mdiq.podcini.ui.dialog - -import android.content.Context -import android.content.SharedPreferences -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.RadioGroup -import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import ac.mdiq.podcini.util.ShareUtils.shareFeedItemFile -import ac.mdiq.podcini.util.ShareUtils.shareFeedItemLinkWithDownloadLink -import ac.mdiq.podcini.util.ShareUtils.shareMediaDownloadLink -import ac.mdiq.podcini.databinding.ShareEpisodeDialogBinding -import ac.mdiq.podcini.storage.model.Episode -import ac.mdiq.podcini.ui.fragment.SubscriptionsFragment.Companion.TAG -import ac.mdiq.podcini.util.Logd - -class ShareDialog : BottomSheetDialogFragment() { - private lateinit var ctx: Context - private lateinit var prefs: SharedPreferences - private var _binding: ShareEpisodeDialogBinding? = null - private val binding get() = _binding!! - - private var item: Episode? = null - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - ctx = requireContext() - prefs = requireActivity().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) - _binding = ShareEpisodeDialogBinding.inflate(inflater) - binding.shareDialogRadioGroup.setOnCheckedChangeListener { _: RadioGroup?, checkedId: Int -> - binding.sharePositionCheckbox.isEnabled = checkedId == binding.shareSocialRadio.id - } - setupOptions() - binding.shareButton.setOnClickListener { - val includePlaybackPosition = binding.sharePositionCheckbox.isChecked - val position: Int - when { - binding.shareSocialRadio.isChecked -> { - shareFeedItemLinkWithDownloadLink(ctx, item!!, includePlaybackPosition) - position = 1 - } - binding.shareMediaReceiverRadio.isChecked -> { - shareMediaDownloadLink(ctx, item!!.media!!) - position = 2 - } - binding.shareMediaFileRadio.isChecked -> { - shareFeedItemFile(ctx, item!!.media!!) - position = 3 - } - else -> throw IllegalStateException("Unknown share method") - } - prefs.edit().putBoolean(PREF_SHARE_EPISODE_START_AT, includePlaybackPosition).putInt(PREF_SHARE_EPISODE_TYPE, position).apply() - dismiss() - } - return binding.root - } - - private fun setupOptions() { - val hasMedia = item!!.media != null - val downloaded = hasMedia && item!!.media!!.downloaded - binding.shareMediaFileRadio.visibility = if (downloaded) View.VISIBLE else View.GONE - - val hasDownloadUrl = hasMedia && item!!.media!!.downloadUrl != null - if (!hasDownloadUrl) binding.shareMediaReceiverRadio.visibility = View.GONE - - var type = prefs.getInt(PREF_SHARE_EPISODE_TYPE, 1) - if ((type == 2 && !hasDownloadUrl) || (type == 3 && !downloaded)) type = 1 - - binding.shareSocialRadio.isChecked = type == 1 - binding.shareMediaReceiverRadio.isChecked = type == 2 - binding.shareMediaFileRadio.isChecked = type == 3 - - val switchIsChecked = prefs.getBoolean(PREF_SHARE_EPISODE_START_AT, false) - binding.sharePositionCheckbox.isChecked = switchIsChecked - } - - override fun onDestroyView() { - Logd(TAG, "onDestroyView") - _binding = null - super.onDestroyView() - } - - fun setItem(item_: Episode) { - item = item_ - } - - companion object { -// private const val ARGUMENT_FEED_ITEM = "feedItem" - private const val PREF_NAME = "ShareDialog" - private const val PREF_SHARE_EPISODE_START_AT = "prefShareEpisodeStartAt" - private const val PREF_SHARE_EPISODE_TYPE = "prefShareEpisodeType" - - fun newInstance(item: Episode): ShareDialog { - val dialog = ShareDialog() - dialog.setItem(item) - return dialog - } - } -} 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 5102184f..e4d0e77f 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 @@ -43,6 +43,7 @@ import ac.mdiq.podcini.ui.compose.CustomTextStyles import ac.mdiq.podcini.ui.compose.CustomTheme import ac.mdiq.podcini.ui.compose.MediaPlayerErrorDialog import ac.mdiq.podcini.ui.compose.PlaybackSpeedFullDialog +import ac.mdiq.podcini.ui.compose.ShareDialog import ac.mdiq.podcini.ui.compose.SkipDialog import ac.mdiq.podcini.ui.compose.SkipDirection import ac.mdiq.podcini.ui.dialog.* @@ -425,6 +426,8 @@ class AudioPlayerFragment : Fragment() { val notAudioOnly = (curMedia as? EpisodeMedia)?.episode?.feed?.preferences?.videoModePolicy != VideoMode.AUDIO_ONLY var showVolumeDialog by remember { mutableStateOf(false) } if (showVolumeDialog) VolumeAdaptionDialog { showVolumeDialog = false } + var showShareDialog by remember { mutableStateOf(false) } + if (showShareDialog && currentItem != null) ShareDialog(currentItem!!, requireActivity()) {showShareDialog = false } Row(modifier = Modifier.fillMaxWidth().padding(10.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_arrow_down), tint = textColor, contentDescription = "Collapse", modifier = Modifier.clickable { (activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED) @@ -458,10 +461,7 @@ class AudioPlayerFragment : Fragment() { } }) Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_share), tint = textColor, contentDescription = "Share", modifier = Modifier.clickable { - if (currentItem != null) { - val shareDialog: ShareDialog = ShareDialog.newInstance(currentItem!!) - shareDialog.show((requireActivity().supportFragmentManager), "ShareEpisodeDialog") - } + if (currentItem != null) showShareDialog = true }) Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_volume_adaption), tint = textColor, contentDescription = "Volume adaptation", modifier = Modifier.clickable { if (currentItem != null) showVolumeDialog = true 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 0d2cad58..ee084c3b 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 @@ -20,7 +20,6 @@ import ac.mdiq.podcini.storage.utils.ImageResourceUtils import ac.mdiq.podcini.ui.actions.* import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.compose.* -import ac.mdiq.podcini.ui.dialog.ShareDialog import ac.mdiq.podcini.ui.utils.ShownotesCleaner import ac.mdiq.podcini.ui.utils.ThemeUtils import ac.mdiq.podcini.ui.view.ShownotesWebView @@ -98,6 +97,8 @@ class EpisodeInfoFragment : Fragment() { private var inQueue by mutableStateOf(if (episode != null) curQueue.contains(episode!!) else false) var isPlayed by mutableIntStateOf(episode?.playState ?: PlayState.UNSPECIFIED.code) + var showShareDialog by mutableStateOf(false) + private var webviewData by mutableStateOf("") private var showHomeScreen by mutableStateOf(false) private var actionButton1 by mutableStateOf(null) @@ -139,6 +140,8 @@ class EpisodeInfoFragment : Fragment() { var showPlayStateDialog by remember { mutableStateOf(false) } if (showPlayStateDialog) PlayStateDialog(listOf(episode!!)) { showPlayStateDialog = false } + if (showShareDialog && episode != null) ShareDialog(episode!!, requireActivity()) { showShareDialog = false } + Scaffold(topBar = { MyTopAppBar() }) { innerPadding -> Column(modifier = Modifier.padding(innerPadding).fillMaxSize()) { Row(modifier = Modifier.padding(start = 16.dp, end = 16.dp), verticalAlignment = Alignment.CenterVertically) { @@ -254,8 +257,7 @@ class EpisodeInfoFragment : Fragment() { expanded = false }) if (episode != null) DropdownMenuItem(text = { Text(stringResource(R.string.share_label)) }, onClick = { - val shareDialog: ShareDialog = ShareDialog.newInstance(episode!!) - shareDialog.show((requireActivity().supportFragmentManager), "ShareEpisodeDialog") + showShareDialog = true expanded = false }) } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/EpisodesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/EpisodesFragment.kt index 2b9f0311..58f62b21 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/EpisodesFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/EpisodesFragment.kt @@ -19,10 +19,10 @@ import ac.mdiq.podcini.ui.actions.DeleteActionButton import ac.mdiq.podcini.ui.actions.EpisodeActionButton import ac.mdiq.podcini.ui.actions.SwipeAction import ac.mdiq.podcini.ui.actions.SwipeActions +import ac.mdiq.podcini.ui.actions.SwipeActions.Companion.SwipeActionsDialog import ac.mdiq.podcini.ui.actions.SwipeActions.NoActionSwipeAction import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.compose.* -import ac.mdiq.podcini.ui.dialog.DatesFilterDialog import ac.mdiq.podcini.util.EventFlow import ac.mdiq.podcini.util.FlowEvent import ac.mdiq.podcini.util.Logd @@ -39,7 +39,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material3.* import androidx.compose.runtime.* @@ -68,6 +67,7 @@ class EpisodesFragment : Fragment() { protected var infoBarText = mutableStateOf("") private var leftActionState = mutableStateOf(NoActionSwipeAction()) private var rightActionState = mutableStateOf(NoActionSwipeAction()) + private var showSwipeActionsDialog by mutableStateOf(false) lateinit var swipeActions: SwipeActions @@ -76,6 +76,7 @@ class EpisodesFragment : Fragment() { var showFilterDialog by mutableStateOf(false) var showSortDialog by mutableStateOf(false) var sortOrder by mutableStateOf(EpisodeSortOrder.DATE_NEW_OLD) + private var showDatesFilter by mutableStateOf(false) var actionButtonToPass by mutableStateOf<((Episode) -> EpisodeActionButton)?>(null) @@ -115,22 +116,18 @@ class EpisodesFragment : Fragment() { val composeView = ComposeView(requireContext()).apply { setContent { CustomTheme(requireContext()) { - if (showFilterDialog) EpisodesFilterDialog(filter = getFilter(), filtersDisabled = filtersDisabled(), - onDismissRequest = { showFilterDialog = false }) { onFilterChanged(it) } - if (showSortDialog) EpisodeSortDialog(initOrder = sortOrder, onDismissRequest = { showSortDialog = false }) { order, _ -> onSort(order) } OpenDialog() - Scaffold(topBar = { MyTopAppBar() }) { innerPadding -> Column(modifier = Modifier.padding(innerPadding).fillMaxSize()) { - InforBar(infoBarText, leftAction = leftActionState, rightAction = rightActionState, actionConfig = { swipeActions.showDialog() }) + InforBar(infoBarText, leftAction = leftActionState, rightAction = rightActionState, actionConfig = { showSwipeActionsDialog = true }) EpisodeLazyColumn( activity as MainActivity, vms = vms, leftSwipeCB = { - if (leftActionState.value is NoActionSwipeAction) swipeActions.showDialog() + if (leftActionState.value is NoActionSwipeAction) showSwipeActionsDialog = true else leftActionState.value.performAction(it, this@EpisodesFragment) }, rightSwipeCB = { - if (rightActionState.value is NoActionSwipeAction) swipeActions.showDialog() + if (rightActionState.value is NoActionSwipeAction) showSwipeActionsDialog = true else rightActionState.value.performAction(it, this@EpisodesFragment) }, actionButton_ = actionButtonToPass @@ -260,7 +257,15 @@ class EpisodesFragment : Fragment() { @Composable fun OpenDialog() { + if (showSwipeActionsDialog) SwipeActionsDialog(TAG, onDismissRequest = { showSwipeActionsDialog = false }) { swipeActions.dialogCallback() } + if (showFilterDialog) EpisodesFilterDialog(filter = getFilter(), filtersDisabled = filtersDisabled(), + onDismissRequest = { showFilterDialog = false }) { onFilterChanged(it) } + if (showSortDialog) EpisodeSortDialog(initOrder = sortOrder, onDismissRequest = { showSortDialog = false }) { order, _ -> onSort(order) } + swipeActions.ActionOptionsDialog() ComfirmDialog(titleRes = R.string.clear_history_label, message = stringResource(R.string.clear_playback_history_msg), showDialog = showClearHistoryDialog) { clearHistory() } + if (showDatesFilter) DatesFilterDialogCompose(inclPlayed = false, oldestDate = 0L, onDismissRequest = { showDatesFilter = false} ) {timeFilterFrom, timeFilterTo, _ -> + EventFlow.postEvent(FlowEvent.HistoryEvent(sortOrder, timeFilterFrom, timeFilterTo)) + } } /** * Loads the playback history from the database. A FeedItem is in the playback history if playback of the correpsonding episode @@ -317,42 +322,30 @@ class EpisodesFragment : Fragment() { navigationIcon = if (displayUpArrow) { { IconButton(onClick = { parentFragmentManager.popBackStack() }) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") } } } else { - { IconButton(onClick = { (activity as? MainActivity)?.openDrawer() }) { Icon(Icons.Filled.Menu, contentDescription = "Open Drawer") } } + { IconButton(onClick = { (activity as? MainActivity)?.openDrawer() }) { Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_feed), contentDescription = "Open Drawer") } } }, actions = { IconButton(onClick = { (activity as MainActivity).loadChildFragment(SearchFragment.newInstance()) - }) { Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_search), contentDescription = "web") } + }) { Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_search), contentDescription = "search") } IconButton(onClick = { showSortDialog = true - }) { Icon(imageVector = ImageVector.vectorResource(R.drawable.arrows_sort), contentDescription = "web") } - if (episodes.isNotEmpty() && spinnerTexts[curIndex] == QuickAccess.All.name) IconButton(onClick = { - if (spinnerTexts[curIndex] == QuickAccess.History.name) { - val dialog = object: DatesFilterDialog(requireContext(), 0L) { - override fun initParams() { - val calendar = Calendar.getInstance() - calendar.add(Calendar.YEAR, -1) // subtract 1 year - timeFilterFrom = calendar.timeInMillis - showMarkPlayed = false - } - override fun callback(timeFilterFrom: Long, timeFilterTo: Long, includeMarkedAsPlayed: Boolean) { - EventFlow.postEvent(FlowEvent.HistoryEvent(sortOrder, timeFilterFrom, timeFilterTo)) - } - } - dialog.show() - } else showFilterDialog = true - }) { Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_filter), contentDescription = "web") } + }) { Icon(imageVector = ImageVector.vectorResource(R.drawable.arrows_sort), contentDescription = "sort") } + if (vms.isNotEmpty() && spinnerTexts[curIndex] == QuickAccess.All.name) IconButton(onClick = { showFilterDialog = true + }) { Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_filter), contentDescription = "filter") } + if (vms.isNotEmpty() && spinnerTexts[curIndex] == QuickAccess.History.name) IconButton(onClick = { showDatesFilter = true + }) { Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_filter), contentDescription = "filter") } IconButton(onClick = { expanded = true }) { Icon(Icons.Default.MoreVert, contentDescription = "Menu") } DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { - if (episodes.isNotEmpty() && spinnerTexts[curIndex] == QuickAccess.History.name) + if (vms.isNotEmpty() && spinnerTexts[curIndex] == QuickAccess.History.name) DropdownMenuItem(text = { Text(stringResource(R.string.clear_history_label)) }, onClick = { showClearHistoryDialog.value = true expanded = false }) - if (episodes.isNotEmpty() && spinnerTexts[curIndex] == QuickAccess.Downloaded.name) + if (vms.isNotEmpty() && spinnerTexts[curIndex] == QuickAccess.Downloaded.name) DropdownMenuItem(text = { Text(stringResource(R.string.reconcile_label)) }, onClick = { reconcile() expanded = false }) - if (episodes.isNotEmpty() && spinnerTexts[curIndex] == QuickAccess.New.name) + if (vms.isNotEmpty() && spinnerTexts[curIndex] == QuickAccess.New.name) DropdownMenuItem(text = { Text(stringResource(R.string.clear_new_label)) }, onClick = { clearNew() expanded = false diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedEpisodesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedEpisodesFragment.kt index b9072595..1791efa9 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedEpisodesFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedEpisodesFragment.kt @@ -14,6 +14,7 @@ import ac.mdiq.podcini.storage.model.EpisodeSortOrder.Companion.fromCode import ac.mdiq.podcini.storage.model.EpisodeSortOrder.Companion.getPermutor import ac.mdiq.podcini.ui.actions.SwipeAction import ac.mdiq.podcini.ui.actions.SwipeActions +import ac.mdiq.podcini.ui.actions.SwipeActions.Companion.SwipeActionsDialog import ac.mdiq.podcini.ui.actions.SwipeActions.NoActionSwipeAction import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.compose.* @@ -72,6 +73,7 @@ class FeedEpisodesFragment : Fragment() { private var infoBarText = mutableStateOf("") private var leftActionState = mutableStateOf(NoActionSwipeAction()) private var rightActionState = mutableStateOf(NoActionSwipeAction()) + private var showSwipeActionsDialog by mutableStateOf(false) private var infoTextFiltered = "" private var infoTextUpdate = "" @@ -177,22 +179,24 @@ class FeedEpisodesFragment : Fragment() { } } } + if (showSwipeActionsDialog) SwipeActionsDialog(TAG, onDismissRequest = { showSwipeActionsDialog = false }) { swipeActions.dialogCallback() } + swipeActions.ActionOptionsDialog() Scaffold(topBar = { MyTopAppBar() }) { innerPadding -> Column(modifier = Modifier.padding(innerPadding).fillMaxSize()) { FeedEpisodesHeader(activity = (activity as MainActivity), filterButColor = filterButtonColor.value, filterClickCB = { if (enableFilter && feed != null) showFilterDialog = true }, filterLongClickCB = { filterLongClick() }) - InforBar(infoBarText, leftAction = leftActionState, rightAction = rightActionState, actionConfig = { swipeActions.showDialog() }) + InforBar(infoBarText, leftAction = leftActionState, rightAction = rightActionState, actionConfig = { showSwipeActionsDialog = true }) EpisodeLazyColumn( activity as MainActivity, vms = vms, feed = feed, layoutMode = layoutMode, refreshCB = { FeedUpdateManager.runOnceOrAsk(requireContext(), feed) }, leftSwipeCB = { - if (leftActionState.value is NoActionSwipeAction) swipeActions.showDialog() + if (leftActionState.value is NoActionSwipeAction) showSwipeActionsDialog = true else leftActionState.value.performAction(it, this@FeedEpisodesFragment) }, rightSwipeCB = { Logd(TAG, "rightActionState: ${rightActionState.value.getId()}") - if (rightActionState.value is NoActionSwipeAction) swipeActions.showDialog() + if (rightActionState.value is NoActionSwipeAction) showSwipeActionsDialog = true else rightActionState.value.performAction(it, this@FeedEpisodesFragment) }, ) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/OnlineEpisodesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/OnlineEpisodesFragment.kt index 4e91d4ec..3dbdc34a 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/OnlineEpisodesFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/OnlineEpisodesFragment.kt @@ -4,6 +4,7 @@ import ac.mdiq.podcini.R import ac.mdiq.podcini.storage.model.Episode import ac.mdiq.podcini.ui.actions.SwipeAction import ac.mdiq.podcini.ui.actions.SwipeActions +import ac.mdiq.podcini.ui.actions.SwipeActions.Companion.SwipeActionsDialog import ac.mdiq.podcini.ui.actions.SwipeActions.NoActionSwipeAction import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.compose.CustomTheme @@ -29,8 +30,10 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.res.stringResource @@ -42,6 +45,7 @@ class OnlineEpisodesFragment: Fragment() { private var infoBarText = mutableStateOf("") private var leftActionState = mutableStateOf(NoActionSwipeAction()) private var rightActionState = mutableStateOf(NoActionSwipeAction()) + private var showSwipeActionsDialog by mutableStateOf(false) lateinit var swipeActions: SwipeActions val episodes = mutableListOf() @@ -59,17 +63,18 @@ class OnlineEpisodesFragment: Fragment() { val composeView = ComposeView(requireContext()).apply { setContent { CustomTheme(requireContext()) { - OpenDialog() + if (showSwipeActionsDialog) SwipeActionsDialog(TAG, onDismissRequest = { showSwipeActionsDialog = false }) { swipeActions.dialogCallback() } + swipeActions.ActionOptionsDialog() Scaffold(topBar = { MyTopAppBar() }) { innerPadding -> Column(modifier = Modifier.padding(innerPadding).fillMaxSize()) { - InforBar(infoBarText, leftAction = leftActionState, rightAction = rightActionState, actionConfig = { swipeActions.showDialog() }) + InforBar(infoBarText, leftAction = leftActionState, rightAction = rightActionState, actionConfig = { showSwipeActionsDialog = true }) EpisodeLazyColumn(activity as MainActivity, vms = vms, leftSwipeCB = { - if (leftActionState.value is NoActionSwipeAction) swipeActions.showDialog() + if (leftActionState.value is NoActionSwipeAction) showSwipeActionsDialog = true else leftActionState.value.performAction(it, this@OnlineEpisodesFragment) }, rightSwipeCB = { - if (rightActionState.value is NoActionSwipeAction) swipeActions.showDialog() + if (rightActionState.value is NoActionSwipeAction) showSwipeActionsDialog = true else rightActionState.value.performAction(it, this@OnlineEpisodesFragment) }, ) @@ -94,9 +99,6 @@ class OnlineEpisodesFragment: Fragment() { ) } - @Composable - fun OpenDialog() {} - override fun onStart() { super.onStart() stopMonitor(vms) 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 d8ccde6c..0dcdaa09 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 @@ -24,6 +24,7 @@ import ac.mdiq.podcini.storage.model.EpisodeSortOrder.Companion.getPermutor import ac.mdiq.podcini.storage.utils.DurationConverter import ac.mdiq.podcini.ui.actions.SwipeAction import ac.mdiq.podcini.ui.actions.SwipeActions +import ac.mdiq.podcini.ui.actions.SwipeActions.Companion.SwipeActionsDialog import ac.mdiq.podcini.ui.actions.SwipeActions.NoActionSwipeAction import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.compose.* @@ -97,6 +98,7 @@ class QueuesFragment : Fragment() { private var rightActionState = mutableStateOf(NoActionSwipeAction()) private var leftActionStateBin = mutableStateOf(NoActionSwipeAction()) private var rightActionStateBin = mutableStateOf(NoActionSwipeAction()) + private var showSwipeActionsDialog by mutableStateOf(false) private var isQueueLocked by mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefQueueLocked.name, true)) @@ -148,21 +150,24 @@ class QueuesFragment : Fragment() { val composeView = ComposeView(requireContext()).apply { setContent { CustomTheme(requireContext()) { + if (showSwipeActionsDialog) SwipeActionsDialog(if (showBin) "$TAG.Bin" else TAG, onDismissRequest = { showSwipeActionsDialog = false }) { swipeActions.dialogCallback() } ComfirmDialog(titleRes = R.string.clear_queue_label, message = stringResource(R.string.clear_queue_confirmation_msg), showDialog = showClearQueueDialog) { clearQueue() } if (shouldShowLockWarningDiwload) ShowLockWarning { shouldShowLockWarningDiwload = false } RenameQueueDialog(showDialog = showRenameQueueDialog.value, onDismiss = { showRenameQueueDialog.value = false }) AddQueueDialog(showDialog = showAddQueueDialog.value, onDismiss = { showAddQueueDialog.value = false }) + swipeActions.ActionOptionsDialog() + swipeActionsBin.ActionOptionsDialog() Scaffold(topBar = { MyTopAppBar() }) { innerPadding -> if (showBin) { Column(modifier = Modifier.padding(innerPadding).fillMaxSize()) { - InforBar(infoBarText, leftAction = leftActionStateBin, rightAction = rightActionStateBin, actionConfig = { swipeActionsBin.showDialog() }) + InforBar(infoBarText, leftAction = leftActionStateBin, rightAction = rightActionStateBin, actionConfig = { showSwipeActionsDialog = true }) val leftCB = { episode: Episode -> - if (leftActionStateBin.value is NoActionSwipeAction) swipeActionsBin.showDialog() + if (leftActionStateBin.value is NoActionSwipeAction) showSwipeActionsDialog = true else leftActionStateBin.value.performAction(episode, this@QueuesFragment) } val rightCB = { episode: Episode -> - if (rightActionStateBin.value is NoActionSwipeAction) swipeActionsBin.showDialog() + if (rightActionStateBin.value is NoActionSwipeAction) showSwipeActionsDialog = true else rightActionStateBin.value.performAction(episode, this@QueuesFragment) } EpisodeLazyColumn(activity as MainActivity, vms = vms, leftSwipeCB = { leftCB(it) }, rightSwipeCB = { rightCB(it) }) @@ -177,13 +182,13 @@ class QueuesFragment : Fragment() { reorderQueue(sortOrder, true) } - InforBar(infoBarText, leftAction = leftActionState, rightAction = rightActionState, actionConfig = { swipeActions.showDialog() }) + InforBar(infoBarText, leftAction = leftActionState, rightAction = rightActionState, actionConfig = { showSwipeActionsDialog = true }) val leftCB = { episode: Episode -> - if (leftActionState.value is NoActionSwipeAction) swipeActions.showDialog() + if (leftActionState.value is NoActionSwipeAction) showSwipeActionsDialog = true else leftActionState.value.performAction(episode, this@QueuesFragment) } val rightCB = { episode: Episode -> - if (rightActionState.value is NoActionSwipeAction) swipeActions.showDialog() + if (rightActionState.value is NoActionSwipeAction) showSwipeActionsDialog = true else rightActionState.value.performAction(episode, this@QueuesFragment) } EpisodeLazyColumn(activity as MainActivity, vms = vms, diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SearchFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SearchFragment.kt index 50638f41..5c8f3f28 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SearchFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SearchFragment.kt @@ -10,6 +10,7 @@ import ac.mdiq.podcini.storage.model.Feed import ac.mdiq.podcini.storage.model.Rating import ac.mdiq.podcini.ui.actions.SwipeAction import ac.mdiq.podcini.ui.actions.SwipeActions +import ac.mdiq.podcini.ui.actions.SwipeActions.Companion.SwipeActionsDialog import ac.mdiq.podcini.ui.actions.SwipeActions.NoActionSwipeAction import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.compose.* @@ -72,6 +73,7 @@ class SearchFragment : Fragment() { private var leftActionState = mutableStateOf(NoActionSwipeAction()) private var rightActionState = mutableStateOf(NoActionSwipeAction()) + private var showSwipeActionsDialog by mutableStateOf(false) private lateinit var swipeActions: SwipeActions override fun onCreate(savedInstanceState: Bundle?) { @@ -92,6 +94,8 @@ class SearchFragment : Fragment() { val composeView = ComposeView(requireContext()).apply { setContent { CustomTheme(requireContext()) { + if (showSwipeActionsDialog) SwipeActionsDialog(TAG, onDismissRequest = { showSwipeActionsDialog = false }) { swipeActions.dialogCallback() } + swipeActions.ActionOptionsDialog() Scaffold(topBar = { MyTopAppBar() }) { innerPadding -> Column(modifier = Modifier.padding(innerPadding).fillMaxSize()) { if (searchInFeed) FilterChip(onClick = { }, label = { Text(feedName) }, selected = searchInFeed, @@ -104,15 +108,14 @@ class SearchFragment : Fragment() { ) CriteriaList() FeedsRow() - InforBar(infoBarText, leftAction = leftActionState, rightAction = rightActionState, actionConfig = { swipeActions.showDialog() }) - EpisodeLazyColumn( - activity as MainActivity, vms = vms, + InforBar(infoBarText, leftAction = leftActionState, rightAction = rightActionState, actionConfig = { showSwipeActionsDialog = true }) + EpisodeLazyColumn(activity as MainActivity, vms = vms, leftSwipeCB = { - if (leftActionState.value is NoActionSwipeAction) swipeActions.showDialog() + if (leftActionState.value is NoActionSwipeAction) showSwipeActionsDialog = true else leftActionState.value.performAction(it, this@SearchFragment) }, rightSwipeCB = { - if (rightActionState.value is NoActionSwipeAction) swipeActions.showDialog() + if (rightActionState.value is NoActionSwipeAction) showSwipeActionsDialog = true else rightActionState.value.performAction(it, this@SearchFragment) }, ) @@ -404,7 +407,8 @@ class SearchFragment : Fragment() { } companion object { - private val TAG: String = SearchFragment::class.simpleName ?: "Anonymous" + val TAG: String = SearchFragment::class.simpleName ?: "Anonymous" + private const val ARG_QUERY = "query" private const val ARG_FEED = "feed" private const val ARG_FEED_NAME = "feedName" diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/StatisticsFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/StatisticsFragment.kt index cca8fa11..773a7f82 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/StatisticsFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/StatisticsFragment.kt @@ -11,7 +11,7 @@ import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.activity.starter.MainActivityStarter import ac.mdiq.podcini.ui.compose.ComfirmDialog import ac.mdiq.podcini.ui.compose.CustomTheme -import ac.mdiq.podcini.ui.dialog.DatesFilterDialog +import ac.mdiq.podcini.ui.compose.DatesFilterDialogCompose import ac.mdiq.podcini.util.Logd import android.content.Context import android.content.SharedPreferences @@ -29,7 +29,6 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material3.* import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset @@ -66,13 +65,14 @@ class StatisticsFragment : Fragment() { private var includeMarkedAsPlayed by mutableStateOf(false) private var statisticsState by mutableIntStateOf(0) private val selectedTabIndex = mutableIntStateOf(0) + private var showFilter by mutableStateOf(false) + lateinit var statsResult: StatisticsResult private val showResetDialog = mutableStateOf(false) override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { super.onCreateView(inflater, container, savedInstanceState) -// setHasOptionsMenu(true) val composeView = ComposeView(requireContext()).apply { setContent { @@ -91,7 +91,12 @@ class StatisticsFragment : Fragment() { } } } - + if (showFilter) DatesFilterDialogCompose(inclPlayed = prefs.getBoolean(PREF_INCLUDE_MARKED_PLAYED, false), from = prefs.getLong(PREF_FILTER_FROM, 0), to = prefs.getLong(PREF_FILTER_TO, Long.MAX_VALUE), + oldestDate = statsResult.oldestDate, onDismissRequest = {showFilter = false} ) { timeFilterFrom, timeFilterTo, includeMarkedAsPlayed_ -> + prefs.edit()?.putBoolean(PREF_INCLUDE_MARKED_PLAYED, includeMarkedAsPlayed_)?.putLong(PREF_FILTER_FROM, timeFilterFrom)?.putLong(PREF_FILTER_TO, timeFilterTo)?.apply() + includeMarkedAsPlayed = includeMarkedAsPlayed_ + statisticsState++ + } val tabTitles = listOf(R.string.subscriptions_label, R.string.months_statistics_label, R.string.downloads_label) Scaffold(topBar = { MyTopAppBar() }) { innerPadding -> Column(modifier = Modifier.padding(innerPadding).fillMaxSize()) { @@ -123,23 +128,24 @@ class StatisticsFragment : Fragment() { navigationIcon = { IconButton(onClick = { (activity as? MainActivity)?.openDrawer() }) { Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_chart_box), contentDescription = "Open Drawer") } }, actions = { if (selectedTabIndex.value == 0) IconButton(onClick = { - val dialog = object: DatesFilterDialog(requireContext(), statsResult.oldestDate) { - override fun initParams() { - includeMarkedAsPlayed = prefs.getBoolean(PREF_INCLUDE_MARKED_PLAYED, false) - timeFilterFrom = prefs.getLong(PREF_FILTER_FROM, 0) - timeFilterTo = prefs.getLong(PREF_FILTER_TO, Long.MAX_VALUE) - } - override fun callback(timeFilterFrom: Long, timeFilterTo: Long, includeMarkedAsPlayed_: Boolean) { - prefs.edit() - ?.putBoolean(PREF_INCLUDE_MARKED_PLAYED, includeMarkedAsPlayed_) - ?.putLong(PREF_FILTER_FROM, timeFilterFrom) - ?.putLong(PREF_FILTER_TO, timeFilterTo) - ?.apply() - includeMarkedAsPlayed = includeMarkedAsPlayed_ - statisticsState++ - } - } - dialog.show() + showFilter = true +// val dialog = object: DatesFilterDialog(requireContext(), statsResult.oldestDate) { +// override fun initParams() { +// includeMarkedAsPlayed = prefs.getBoolean(PREF_INCLUDE_MARKED_PLAYED, false) +// timeFilterFrom = prefs.getLong(PREF_FILTER_FROM, 0) +// timeFilterTo = prefs.getLong(PREF_FILTER_TO, Long.MAX_VALUE) +// } +// override fun callback(timeFilterFrom: Long, timeFilterTo: Long, includeMarkedAsPlayed_: Boolean) { +// prefs.edit() +// ?.putBoolean(PREF_INCLUDE_MARKED_PLAYED, includeMarkedAsPlayed_) +// ?.putLong(PREF_FILTER_FROM, timeFilterFrom) +// ?.putLong(PREF_FILTER_TO, timeFilterTo) +// ?.apply() +// includeMarkedAsPlayed = includeMarkedAsPlayed_ +// statisticsState++ +// } +// } +// dialog.show() }) { Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_filter), contentDescription = "filter") } IconButton(onClick = { expanded = true }) { Icon(Icons.Default.MoreVert, contentDescription = "Menu") } DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { diff --git a/app/src/main/res/drawable/baseline_mail_outline_24.xml b/app/src/main/res/drawable/baseline_mail_outline_24.xml new file mode 100644 index 00000000..7c88f375 --- /dev/null +++ b/app/src/main/res/drawable/baseline_mail_outline_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/share_episode_dialog.xml b/app/src/main/res/layout/share_episode_dialog.xml deleted file mode 100644 index 6849f74c..00000000 --- a/app/src/main/res/layout/share_episode_dialog.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - -