Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ android {
applicationId = "com.github.livingwithhippos.unchained"
minSdk = 27
targetSdk = 37
versionCode = 60
versionName = "1.7.1"
versionCode = 61
versionName = "1.8.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down
1 change: 1 addition & 0 deletions app/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_LOCAL_NETWORK" />

<!-- Android 11+ hides other apps from PackageManager unless declared here. We resolve and
launch whatever app the user has set as their default video player, so instead of listing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import com.github.livingwithhippos.unchained.utilities.Event
import com.github.livingwithhippos.unchained.utilities.postEvent
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.milliseconds

/**
* A [ViewModel] subclass. It offers LiveData to be observed during the authentication process and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ class DeleteDialogFragment : DialogFragment() {
.setMessage(R.string.confirm_item_removal_description)
.setTitle(title)
.setPositiveButton(R.string.delete) { _, _ ->

val bundle = Bundle().apply {
putBoolean("deleteConfirmation", true)
}
val bundle = Bundle().apply { putBoolean("deleteConfirmation", true) }
setFragmentResult("deleteActionKey", bundle)
}
.setNegativeButton(R.string.close) { dialog, _ -> dialog.cancel() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,22 +66,20 @@ import com.github.livingwithhippos.unchained.utilities.extension.downloadFileInS
import com.github.livingwithhippos.unchained.utilities.extension.isTv
import com.github.livingwithhippos.unchained.utilities.extension.openExternalWebPage
import com.github.livingwithhippos.unchained.utilities.extension.parcelable
import com.github.livingwithhippos.unchained.utilities.extension.isTv
import com.github.livingwithhippos.unchained.utilities.extension.showToast
import com.github.livingwithhippos.unchained.utilities.extension.toHex
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.elevation.SurfaceColors
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import java.lang.RuntimeException
import java.security.MessageDigest
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber
import kotlin.time.Duration.Companion.milliseconds

/** A [AppCompatActivity] subclass. Shared between all the fragments except for the preferences. */
@AndroidEntryPoint
Expand All @@ -101,6 +99,7 @@ class MainActivity : AppCompatActivity() {
TelemetryManager.onStart(this)

val bottomColor = SurfaceColors.SURFACE_2.getColor(this)
@Suppress("DEPRECATION")
window.navigationBarColor = bottomColor
// Set color of system navigationBar same as BottomNavigationView
// window.statusBarColor = color // Set color of system statusBar same as ActionBar
Expand Down Expand Up @@ -189,6 +188,14 @@ class MainActivity : AppCompatActivity() {
}
}

private val requestLocalNetworkPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean
->
if (!isGranted) {
applicationContext.showToast(R.string.needs_local_network_permission)
}
}

private val requestNotificationPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean
->
Expand Down Expand Up @@ -650,6 +657,13 @@ class MainActivity : AppCompatActivity() {
}
}

MainActivityMessage.RequireLocalNetworkPermissions -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN) {
requestLocalNetworkPermissionLauncher.launch(
Manifest.permission.ACCESS_LOCAL_NETWORK
)
}
}
null -> {}
}
}
Expand Down Expand Up @@ -989,68 +1003,67 @@ class MainActivity : AppCompatActivity() {
Snackbar.make(binding.root, fileName, Snackbar.LENGTH_INDEFINITE)
.setAnchorView(binding.bottomNavView)

tvDownloadProgressJob =
lifecycleScope.launch {
try {
snackbar.show()
while (true) {
var status: Int? = null
var progress = 0
manager.query(DownloadManager.Query().setFilterById(downloadId)).use {
cursor ->
if (cursor.moveToFirst()) {
status =
cursor.getInt(
cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)
)
val downloadedBytes =
cursor.getLong(
cursor.getColumnIndexOrThrow(
DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR
)
)
val totalBytes =
cursor.getLong(
cursor.getColumnIndexOrThrow(
DownloadManager.COLUMN_TOTAL_SIZE_BYTES
)
tvDownloadProgressJob = lifecycleScope.launch {
try {
snackbar.show()
while (true) {
var status: Int? = null
var progress = 0
manager.query(DownloadManager.Query().setFilterById(downloadId)).use { cursor ->
if (cursor.moveToFirst()) {
status =
cursor.getInt(
cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)
)
val downloadedBytes =
cursor.getLong(
cursor.getColumnIndexOrThrow(
DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR
)
if (totalBytes > 0)
progress = (downloadedBytes * 100 / totalBytes).toInt()
}
}
when (status) {
// the download disappeared from the download manager, e.g. it was
// removed by the user: stop silently
null -> break
DownloadManager.STATUS_SUCCESSFUL -> {
applicationContext.showToast(R.string.download_complete)
break
}
DownloadManager.STATUS_FAILED -> {
applicationContext.showToast(
getString(R.string.download_failed_format, fileName)
)
break
}
else -> {
snackbar.setText(
"$fileName\n${getString(R.string.download_in_progress_format, progress)}"
val totalBytes =
cursor.getLong(
cursor.getColumnIndexOrThrow(
DownloadManager.COLUMN_TOTAL_SIZE_BYTES
)
)
}
if (totalBytes > 0)
progress = (downloadedBytes * 100 / totalBytes).toInt()
}
}
when (status) {
// the download disappeared from the download manager, e.g. it was
// removed by the user: stop silently
null -> break
DownloadManager.STATUS_SUCCESSFUL -> {
applicationContext.showToast(R.string.download_complete)
break
}
DownloadManager.STATUS_FAILED -> {
applicationContext.showToast(
getString(R.string.download_failed_format, fileName)
)
break
}
else -> {
snackbar.setText(
"$fileName\n${getString(R.string.download_in_progress_format, progress)}"
)
}
delay(1000)
}
} finally {
snackbar.dismiss()
delay(1000.milliseconds)
}
} finally {
snackbar.dismiss()
}
}
}


/** On TV, a back press while focus is inside a long/endless list moves it to the bottom nav
* instead of navigating, since there is no other way to reach the nav bar from deep in a
* list (see issue #376). */
/**
* On TV, a back press while focus is inside a long/endless list moves it to the bottom nav
* instead of navigating, since there is no other way to reach the nav bar from deep in a list
* (see issue #376).
*/
private fun setupTvBackFocusHandling() {
if (!isTv()) return

Expand All @@ -1067,9 +1080,11 @@ class MainActivity : AppCompatActivity() {
}
}

/** On TV, the toolbar's overflow button is first in layout order and silently grabs default
/**
* On TV, the toolbar's overflow button is first in layout order and silently grabs default
* focus on every new destination. Rescue focus onto the bottom nav when that happens, without
* touching focus a fragment or back navigation already placed deliberately. */
* touching focus a fragment or back navigation already placed deliberately.
*/
private fun setupTvInitialFocusHandling() {
if (!isTv()) return

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,16 @@ import android.content.SharedPreferences
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import com.github.livingwithhippos.unchained.R
import androidx.core.content.ContextCompat
import com.github.livingwithhippos.unchained.R
import com.github.livingwithhippos.unchained.settings.view.SettingsFragment
import com.github.livingwithhippos.unchained.settings.view.SettingsFragment.Companion.KEY_NEW_THEME
import com.github.livingwithhippos.unchained.settings.view.SettingsFragment.Companion.THEME_AUTO
import com.github.livingwithhippos.unchained.settings.view.SettingsFragment.Companion.THEME_DAY
import com.github.livingwithhippos.unchained.settings.view.SettingsFragment.Companion.THEME_NIGHT
import com.github.livingwithhippos.unchained.settings.view.ThemeItem
import com.github.livingwithhippos.unchained.utilities.PreferenceKeys
import com.github.livingwithhippos.unchained.utilities.extension.applyThemeAwareSystemBarIconColors
import com.github.livingwithhippos.unchained.utilities.extension.getThemeItem
import com.github.livingwithhippos.unchained.utilities.extension.getThemeList
import com.google.android.material.color.DynamicColors
import com.google.android.material.color.DynamicColorsOptions
import java.util.WeakHashMap
Expand Down Expand Up @@ -46,7 +44,9 @@ class ThemingCallback(val preferences: SharedPreferences) : Application.Activity
if (!DynamicColors.isDynamicColorAvailable()) return
val options =
if (theme.key == CUSTOM_THEME_KEY) {
DynamicColorsOptions.Builder().setContentBasedSource(customSeedColor(activity)).build()
DynamicColorsOptions.Builder()
.setContentBasedSource(customSeedColor(activity))
.build()
} else {
DynamicColorsOptions.Builder().build()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ abstract class UnchainedFragment : Fragment() {
if (current != null && current.getAction(action.actionId) != null) {
try {
nav.navigate(action)
Timber.w(
"Navigation valid from destination ${current.id} for actionId=${action.actionId}"
)
return true
} catch (e: IllegalArgumentException) {
Timber.w(e, "Safe navigate failed for actionId=${action.actionId}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import kotlin.math.sin
import kotlin.math.sqrt

/**
* An HSV color wheel: hue is the angle around the wheel, saturation is the distance from the
* center (fully saturated at the edge, white at the center). Brightness (HSV value) isn't
* represented on the wheel itself, since a 2D wheel can only encode two of the three HSV
* components; pair this with a separate brightness control that sets [value].
* An HSV color wheel: hue is the angle around the wheel, saturation is the distance from the center
* (fully saturated at the edge, white at the center). Brightness (HSV value) isn't represented on
* the wheel itself, since a 2D wheel can only encode two of the three HSV components; pair this
* with a separate brightness control that sets [value].
*/
class ColorWheelView
@JvmOverloads
Expand Down Expand Up @@ -76,7 +76,8 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0

if (radius <= 0f) return

val hues = IntArray(361) { degree -> Color.HSVToColor(floatArrayOf(degree.toFloat(), 1f, 1f)) }
val hues =
IntArray(361) { degree -> Color.HSVToColor(floatArrayOf(degree.toFloat(), 1f, 1f)) }
val sweep = SweepGradient(centerX, centerY, hues, null)
val radial =
RadialGradient(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.github.livingwithhippos.unchained.data.model.torznab

import com.github.livingwithhippos.unchained.utilities.directChild
import com.github.livingwithhippos.unchained.utilities.directChildren
import kotlin.collections.ifEmpty
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.jsoup.Jsoup
Expand Down
Loading