Skip to content
Open
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
Binary file added .github/debug.keystore
Binary file not shown.
49 changes: 49 additions & 0 deletions .github/workflows/build_fork.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Build APK (fork)

# Builds an installable debug APK for this fork.
# Runs on every push to a branch other than master, or manually from the Actions tab.
on:
push:
branches-ignore: [ master ]
paths-ignore:
- '*.md'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: build-fork-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Set up JDK 17
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 17

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5

# Use the same debug key on every build so new APKs install over old ones
- name: Install debug keystore
run: |
mkdir -p ~/.android
cp .github/debug.keystore ~/.android/debug.keystore

- name: Build APK
run: |
chmod +x gradlew
./gradlew assemblePrereleaseDebug

- name: Upload APK
uses: actions/upload-artifact@v7
with:
name: cloudstream-apk
path: "app/build/outputs/apk/prerelease/debug/*.apk"
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ object CommonActivity {
"Dracula" -> R.style.DraculaMode
"Lavender" -> R.style.LavenderMode
"SilentBlue" -> R.style.SilentBlueMode
"Cinema" -> R.style.CinemaMode

else -> R.style.AppTheme
}
Expand All @@ -377,6 +378,7 @@ object CommonActivity {
"Green" -> R.style.OverlayPrimaryColorGreen
"GreenApple" -> R.style.OverlayPrimaryColorGreenApple
"Red" -> R.style.OverlayPrimaryColorRed
"CinemaRed" -> R.style.OverlayPrimaryColorCinemaRed
"Banana" -> R.style.OverlayPrimaryColorBanana
"Party" -> R.style.OverlayPrimaryColorParty
"Pink" -> R.style.OverlayPrimaryColorPink
Expand Down
9 changes: 5 additions & 4 deletions app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLandscape
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.settings.Globals.updateTv
import com.lagradost.cloudstream3.ui.settings.SettingsGeneral
Expand Down Expand Up @@ -557,14 +556,16 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
}*/

binding?.apply {
navRailView.isVisible = isNavVisible && isLandscape()
navView.isVisible = isNavVisible && !isLandscape()
// Side rail on TV, bottom bar on phones and tablets
val useRail = isLayout(TV or EMULATOR)
navRailView.isVisible = isNavVisible && useRail
navView.isVisible = isNavVisible && !useRail
navHostFragment.apply {
val marginPx = resources.getDimensionPixelSize(R.dimen.nav_rail_view_width)
layoutParams =
(navHostFragment.layoutParams as ViewGroup.MarginLayoutParams).apply {
marginStart =
if (isNavVisible && isLandscape() && isLayout(TV or EMULATOR)) marginPx else 0
if (isNavVisible && useRail) marginPx else 0
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ fun OkHttpClient.Builder.addGenericDns(url: String, ips: List<String>) = dns(
.build()
)

/** User supplied DNS-over-HTTPS server, resolved using the system DNS */
fun OkHttpClient.Builder.addCustomDns(url: String) = dns(
DnsOverHttps
.Builder()
.client(build())
.url(url.trim().toHttpUrl())
.build()
)

fun OkHttpClient.Builder.addGoogleDns() = (
addGenericDns(
"https://dns.google/dns-query",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ fun buildDefaultClient(context: Context, ignoreSSL: Boolean = false): OkHttpClie
6 -> addQuad9Dns()
7 -> addDnsSbDns()
8 -> addCanadianShieldDns()
9 -> settingsManager.getString(
context.getString(R.string.custom_dns_url_key), null
)?.takeIf { it.isNotBlank() }?.let { url ->
safe { addCustomDns(url) }
}
}
}
// Needs to be build as otherwise the other builders will change this object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,8 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(
homeMasterRecycler.setRecycledViewPool(ParentItemAdapter.sharedPool)
homeMasterRecycler.adapter = homeMasterAdapter

homeApiFab.isVisible = isLayout(PHONE)
// The extension switch lives in the home top bar instead
homeApiFab.isVisible = false

homePreviewReloadProvider.setOnClickListener {
homeViewModel.loadAndCancel(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui.home
import android.content.Context
import android.os.Bundle
import android.os.Parcelable
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
Expand All @@ -28,6 +29,8 @@ import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.databinding.FragmentHomeHeadBinding
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.getDisplayName
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.selectHomepage
import com.lagradost.cloudstream3.databinding.FragmentHomeHeadTvBinding
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.debugException
Expand Down Expand Up @@ -58,6 +61,7 @@ import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectStringRes
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbarMargin
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbarView
import com.lagradost.cloudstream3.utils.UIHelper.toPx
import com.lagradost.cloudstream3.utils.UIHelper.populateChips
import androidx.core.graphics.toColorInt
import com.lagradost.cloudstream3.ui.setRecycledViewPool
Expand Down Expand Up @@ -529,6 +533,29 @@ class HomeParentItemAdapterPreview(

fixPaddingStatusbarMargin(topPadding)

(binding as? FragmentHomeHeadBinding)?.apply {
// The hero cards start below the top bar, so they need the same status bar offset
fixPaddingStatusbarMargin(previewViewpager)
fixPaddingStatusbarMargin(homePreviewShimmer)

// Tablets align the hero buttons with the left aligned title
if (root.resources.configuration.smallestScreenWidthDp >= 600) {
homePreviewTitleHolder.gravity = Gravity.START or Gravity.CENTER_VERTICAL
homePreviewTitleHolder.setPaddingRelative(
root.resources.getDimensionPixelSize(R.dimen.home_hero_margin) + 24.toPx,
0, 0, 0
)
}

val changeApiListener = View.OnClickListener { view ->
view.context.selectHomepage(viewModel.apiName.value) { api ->
viewModel.loadAndCancel(api, forceReload = true, fromUI = true)
}
}
homeHeadChangeApi.setOnClickListener(changeApiListener)
alternateHomeHeadChangeApi.setOnClickListener(changeApiListener)
}

for ((chip, watch) in toggleList) {
chip.isChecked = false
chip.setOnCheckedChangeListener { _, isChecked ->
Expand Down Expand Up @@ -797,6 +824,13 @@ class HomeParentItemAdapterPreview(
binding.homePreviewReloadProvider.isGone = (name == noneApi.name)
}
}*/
(binding as? FragmentHomeHeadBinding)?.apply {
observe(viewModel.apiName) { name ->
val displayName = homeHeadChangeApi.context.getDisplayName(name) ?: name
homeHeadChangeApi.text = displayName
alternateHomeHeadChangeApi.text = displayName
}
}
observe(viewModel.resumeWatching) {
updateResume(it)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@ import android.content.Intent
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.integerArrayResource
import androidx.compose.ui.res.painterResource
Expand All @@ -34,6 +39,9 @@ import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTagToLanguageName
import com.lagradost.cloudstream3.utils.SubtitleHelper.getNameNextToFlagEmoji
import com.lagradost.cloudstream4.AppSettings
import com.lagradost.cloudstream4.compose.ActionDialog
import com.lagradost.cloudstream4.compose.BlackButton
import com.lagradost.cloudstream4.compose.BlackTextField
import com.lagradost.cloudstream4.compose.WhiteButton
import com.lagradost.cloudstream4.compose.PHONE
import com.lagradost.cloudstream4.compose.isLayout
import com.lagradost.cloudstream4.rememberAppSettings
Expand All @@ -44,6 +52,10 @@ import com.mihon.presentation.settings.SearchableSettings
import com.mihon.presentation.settings.collectAsState
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentMap
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull

/** Value of the custom entry in R.array.dns_pref_values */
private const val CUSTOM_DNS = 9

object SettingsGeneralScreen : SearchableSettings {
@Composable
Expand Down Expand Up @@ -108,6 +120,54 @@ object SettingsGeneralScreen : SearchableSettings {
)
}

val customDnsUrl by settings.general.customDnsUrl.collectAsState()
var isCustomDnsShown by remember { mutableStateOf(false) }

fun reloadClients() {
(CloudStreamApp.context)?.let { ctx ->
app.initClient(ctx, ignoreSSL = false)
@OptIn(UnsafeSSL::class)
insecureApp.initClient(ctx, ignoreSSL = true)
}
}

if (isCustomDnsShown) {
var input by remember { mutableStateOf(customDnsUrl) }
AlertDialog(
containerColor = MaterialTheme.colorScheme.background,
onDismissRequest = { isCustomDnsShown = false },
title = { Text(text = stringResource(R.string.custom_dns_pref)) },
text = {
BlackTextField(
modifier = Modifier.fillMaxWidth(),
value = input,
placeHolder = stringResource(R.string.custom_dns_hint),
singleLine = true,
onValueChange = { input = it.trim() }
)
},
confirmButton = {
WhiteButton(text = stringResource(R.string.ok), onClick = {
val url = input.toHttpUrlOrNull()
if (url == null || !url.isHttps) {
showToast(R.string.custom_dns_invalid)
return@WhiteButton
}
isCustomDnsShown = false
// Must set the value before initClient is called
settings.general.customDnsUrl.set(input)
settings.general.dns.set(CUSTOM_DNS)
reloadClients()
})
},
dismissButton = {
BlackButton(text = stringResource(R.string.cancel), onClick = {
isCustomDnsShown = false
})
}
)
}

val default = AllLanguagesName to stringResource(R.string.all_languages_preference)
val languages = APIHolder.apis.withLock {
APIHolder.apis.map { api -> api.lang }.distinct()
Expand Down Expand Up @@ -223,15 +283,21 @@ object SettingsGeneralScreen : SearchableSettings {
stringArrayResource(R.array.dns_pref)
).toMap(),
onValueChanged = { newValue ->
settings.general.dns.set(newValue) // Must set the value before initClient is called
(CloudStreamApp.context)?.let { ctx ->
app.initClient(ctx, ignoreSSL = false)
@OptIn(UnsafeSSL::class)
insecureApp.initClient(ctx, ignoreSSL = true)
if (newValue == CUSTOM_DNS && customDnsUrl.isBlank()) {
isCustomDnsShown = true
return@ListPreference false
}
settings.general.dns.set(newValue) // Must set the value before initClient is called
reloadClients()
return@ListPreference true
},
),
Preference.PreferenceItem.TextPreference(
title = stringResource(R.string.custom_dns_pref),
subtitle = customDnsUrl.ifBlank { stringResource(R.string.custom_dns_pref_summary) },
icon = painterResource(R.drawable.dns_24px),
onClick = { isCustomDnsShown = true }
),
Preference.PreferenceItem.SwitchPreference(
title = stringResource(R.string.jsdelivr_proxy),
subtitle = stringResource(R.string.jsdelivr_proxy_summary),
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/res/drawable/home_pill_background.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Translucent gray pill used for the hero "Info" and extension buttons -->
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="#66808080" />
<corners android:radius="24dp" />
</shape>
Loading
Loading