Skip to content

Commit

Permalink
6.15.10 commit
Browse files Browse the repository at this point in the history
  • Loading branch information
XilinJia committed Dec 15, 2024
1 parent 2e6d22c commit 6031d0f
Show file tree
Hide file tree
Showing 24 changed files with 445 additions and 638 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
Expand Down
4 changes: 1 addition & 3 deletions app/src/main/kotlin/ac/mdiq/podcini/net/feed/FeedBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,7 @@ class FeedBuilder(val context: Context, val showError: (String?, String)->Unit)
}
}
onDismissRequest()
}) {
Text("Confirm")
}
}) { Text("Confirm") }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ object RealmDB {
result
}
}

fun runOnIOScope(block: suspend () -> Unit) : Job {
return ioScope.launch {
if (Dispatchers.IO == coroutineContext[ContinuationInterceptor]) block()
Expand Down
250 changes: 106 additions & 144 deletions app/src/main/kotlin/ac/mdiq/podcini/ui/actions/SwipeActions.kt

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -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)
}
}
Expand Down Expand Up @@ -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("[email protected]"))
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"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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("[email protected]"))
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("[email protected]"))
// 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)) }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 ->
Expand Down Expand Up @@ -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()
Expand All @@ -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 = {
Expand Down
156 changes: 156 additions & 0 deletions app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Composables.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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") } }
)
}
Loading

0 comments on commit 6031d0f

Please sign in to comment.