From 9b829e1cb26b1fb0d6cda383356a2debe40efe6b Mon Sep 17 00:00:00 2001 From: sherazi5110 Date: Sat, 31 Oct 2026 01:03:54 +0000 Subject: [PATCH 1/2] Add custom DNS, Cinema theme, Netflix-style home and fork APK build - Custom DNS-over-HTTPS server option in Settings > General - "Cinema Black" theme and "Cinema Red" accent colour - Taller hero banner, bigger titles, flatter posters on the home screen - GitHub Actions workflow that builds an installable debug APK on the fork Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017bnynhBYpLhsov2cqsFQiC --- .github/debug.keystore | Bin 0 -> 2666 bytes .github/workflows/build_fork.yml | 49 +++++++++++ .../lagradost/cloudstream3/CommonActivity.kt | 2 + .../cloudstream3/network/DohProviders.kt | 9 +++ .../cloudstream3/network/RequestsHelper.kt | 5 ++ .../ui/settings/SettingsGeneralScreen.kt | 76 ++++++++++++++++-- .../main/res/layout/fragment_home_head.xml | 2 +- app/src/main/res/layout/home_result_grid.xml | 6 +- app/src/main/res/layout/home_scroll_view.xml | 10 ++- app/src/main/res/layout/homepage_parent.xml | 6 +- app/src/main/res/values-b+es/array.xml | 2 + app/src/main/res/values-b+pl/array.xml | 2 + app/src/main/res/values-b+tr/array.xml | 2 + app/src/main/res/values-b+vi/array.xml | 2 + app/src/main/res/values/array.xml | 6 ++ app/src/main/res/values/colors.xml | 9 +++ app/src/main/res/values/dimens.xml | 2 +- .../res/values/donottranslate-strings.xml | 1 + app/src/main/res/values/strings.xml | 5 ++ app/src/main/res/values/styles.xml | 23 ++++++ .../cloudstream4/theme/ColorPreference.kt | 2 + .../cloudstream4/DevicePreferenceStore.kt | 3 + .../lagradost/cloudstream4/theme/Palette.kt | 10 +++ .../lagradost/cloudstream4/theme/Scheme.kt | 15 +++- .../com/lagradost/cloudstream4/theme/Theme.kt | 4 + 25 files changed, 237 insertions(+), 16 deletions(-) create mode 100644 .github/debug.keystore create mode 100644 .github/workflows/build_fork.yml diff --git a/.github/debug.keystore b/.github/debug.keystore new file mode 100644 index 0000000000000000000000000000000000000000..f62677530540f08706d08e0a49494d2e0f5b7351 GIT binary patch literal 2666 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 literal 0 HcmV?d00001 diff --git a/.github/workflows/build_fork.yml b/.github/workflows/build_fork.yml new file mode 100644 index 00000000000..9915001c167 --- /dev/null +++ b/.github/workflows/build_fork.yml @@ -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" diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt index 8daf168c1c1..bd1d086a0dc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt @@ -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 } @@ -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 diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/DohProviders.kt b/app/src/main/java/com/lagradost/cloudstream3/network/DohProviders.kt index 4127799e8f4..d406f8c17e6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/network/DohProviders.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/network/DohProviders.kt @@ -20,6 +20,15 @@ fun OkHttpClient.Builder.addGenericDns(url: String, ips: List) = 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", diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt index e4924590458..6e83709ae49 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt @@ -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 diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneralScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneralScreen.kt index 6aa94fa883c..cb58df4348f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneralScreen.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneralScreen.kt @@ -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 @@ -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 @@ -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 @@ -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() @@ -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), diff --git a/app/src/main/res/layout/fragment_home_head.xml b/app/src/main/res/layout/fragment_home_head.xml index b62173edea3..5b0b8212625 100644 --- a/app/src/main/res/layout/fragment_home_head.xml +++ b/app/src/main/res/layout/fragment_home_head.xml @@ -16,7 +16,7 @@ + android:layout_height="580dp"> diff --git a/app/src/main/res/layout/home_scroll_view.xml b/app/src/main/res/layout/home_scroll_view.xml index 3291b9b7b56..ea002358bba 100644 --- a/app/src/main/res/layout/home_scroll_view.xml +++ b/app/src/main/res/layout/home_scroll_view.xml @@ -25,7 +25,7 @@ @@ -54,8 +54,10 @@ android:paddingHorizontal="30dp" android:paddingBottom="10dp" android:textColor="?attr/white" - android:textSize="17sp" + android:textSize="26sp" android:textStyle="bold" + android:shadowColor="#99000000" + android:shadowRadius="8" tools:text="The Perfect Run" /> #5664B7 #D50000 + #E50914 + + + #181818 + #000000 + #222222 + #141414 + #FFFFFF + #B3B3B3 #6200EA #00BFA5 #48E484 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index b0c150000b5..272751dd494 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -2,7 +2,7 @@ 16dp 16dp - 10dp + 6dp 4dp 0dp diff --git a/app/src/main/res/values/donottranslate-strings.xml b/app/src/main/res/values/donottranslate-strings.xml index bf1566cdb98..4ab02581f4d 100644 --- a/app/src/main/res/values/donottranslate-strings.xml +++ b/app/src/main/res/values/donottranslate-strings.xml @@ -48,6 +48,7 @@ random_button_key provider_lang_key dns_key + custom_dns_url_key jsdelivr_proxy_key download_path_key download_parallel_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6c429519967..e16c6597a06 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -347,6 +347,11 @@ Causes problems if set too high on devices with low storage space, such as Android TV. DNS over HTTPS Useful for bypassing ISP blocks + Custom + Custom DNS server + Enter your own DNS-over-HTTPS address + https://dns.example.com/dns-query + Enter a valid https:// DNS-over-HTTPS address GitHub Proxy Could not reach GitHub. Turning on jsDelivr proxy… Bypass blocking of raw github URLs using jsDelivr. May cause updates to be delayed by few days. diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index a5f8492dc9d..222b135e031 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -224,6 +224,19 @@ true + + + +