From 68c16339ea023b0c160b8fbacbca61e2e7141e02 Mon Sep 17 00:00:00 2001 From: lunaticbum Date: Tue, 24 Mar 2026 15:53:29 +0900 Subject: [PATCH] .... --- .../extensions/my_extension/messaging.js | 50 ++++++ .../launcher/helpers/ForeGroundService.kt | 148 ++++++++++++++--- .../launcher/home/CompletedFilesFragment.kt | 4 +- .../bums/lunatic/launcher/home/GeckoWeb.kt | 150 ++++++++++++++---- .../lunatic/launcher/home/NeoRssActivity.kt | 1 + .../launcher/home/tokiz/HistoryManager.kt | 1 + .../res/layout/layout_lunatic_browser.xml | 2 +- app/src/main/res/layout/rss_activity.xml | 2 +- 8 files changed, 298 insertions(+), 60 deletions(-) diff --git a/app/src/main/assets/extensions/my_extension/messaging.js b/app/src/main/assets/extensions/my_extension/messaging.js index 3d781fec..36887ccf 100644 --- a/app/src/main/assets/extensions/my_extension/messaging.js +++ b/app/src/main/assets/extensions/my_extension/messaging.js @@ -81,6 +81,19 @@ port.onMessage.addListener(response => { } } + } break; + case "PAUSE_YT": // 5초 뒤로 + { + if (document.location.href.search("youtube") > -1) { + let btn = + document.querySelector('button[aria-label="동영상 일시중지"]') ; + + if (btn) { + btn.click(); + return; + } + } + } break; case "SEEK_PREV": // 5초 뒤로 { @@ -594,6 +607,36 @@ var mainContentsEl = null; let lastState = null; // 이전 상태 저장 (-1, 0, 1) let throttleTimer = null; +function sendCookiesToNative() { + try { + const cookies = document.cookie; + // 쿠키가 존재할 때만 전송 + if (cookies && cookies.length > 0) { + const netscapeCookies = '# Netscape HTTP Cookie File\\n' + + document.cookie.split('; ') + .map(c => { + const eqIdx = c.indexOf('='); + const name = c.substring(0, eqIdx).trim(); + const value = decodeURIComponent(c.substring(eqIdx + 1)); + return `.youtube.com\\tTRUE\\t/\\tFALSE\\t0\\t${name}\\t${value}`; + }) + .filter(c => c.includes('youtube') || c.includes('google')) + .join('\\n'); + + + + sendMessage({ + type: "COOKIES_REPORT", + value: netscapeCookies, + url: location.href + }); + console.log("Cookies sent to native."); + } + } catch (e) { + // 예외 무시 + } +} + document.addEventListener('DOMContentLoaded', function () { const currentUrl = location.href; @@ -650,6 +693,13 @@ document.addEventListener('DOMContentLoaded', function () { throttleTimer = null; }, 150); // 0.15초 간격으로 체크 (사용성에 따라 조절 가능) }; + + if (document.readyState === 'complete') { + sendCookiesToNative(); + } else { + // 2. 아직 로딩 중이라면 window.onload(모든 리소스 로드 완료) 시점에 실행 + window.addEventListener('load', sendCookiesToNative); + } }) const keywords = ["youtube", "mojeek"]; diff --git a/app/src/main/kotlin/bums/lunatic/launcher/helpers/ForeGroundService.kt b/app/src/main/kotlin/bums/lunatic/launcher/helpers/ForeGroundService.kt index 9751de95..dc1aa9d3 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/helpers/ForeGroundService.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/helpers/ForeGroundService.kt @@ -6,6 +6,7 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.app.Service +import android.app.WallpaperManager import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothDevice import android.content.BroadcastReceiver @@ -47,14 +48,66 @@ import java.io.File import java.util.UUID import java.util.concurrent.TimeUnit import android.bluetooth.BluetoothClass +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.RectF import android.media.AudioManager import android.os.SystemClock import android.os.VibrationEffect import android.os.Vibrator import android.view.KeyEvent import androidx.annotation.RequiresPermission +import androidx.core.content.ContentProviderCompat.requireContext +import androidx.work.workDataOf +import bums.lunatic.launcher.home.GeckoWeb.Companion.currentCookieString +import bums.lunatic.launcher.home.GeckoWeb.Companion.currentCookieUrlString import kotlinx.coroutines.delay +class WallpaperAutoChangeWorker(private val context: Context, params: WorkerParameters) : CoroutineWorker(context, params) { + override suspend fun doWork(): Result { + val folderPath = inputData.getString("FOLDER_PATH") ?: return Result.failure() + val folder = File(folderPath) + + // 1. 이미지 파일 목록 가져오기 + val images = folder.listFiles { file -> + file.isFile && (file.extension.equals("jpg", true) || file.extension.equals("png", true) || file.extension.equals("jpeg", true)) + } + + if (images.isNullOrEmpty()) return Result.failure() + + // 2. 랜덤 이미지 선택 및 비트맵 로드 + val randomImage = images.random() + val options = BitmapFactory.Options().apply { inJustDecodeBounds = false } + val originalBitmap = BitmapFactory.decodeFile(randomImage.absolutePath, options) ?: return Result.failure() + + return try { + val wm = WallpaperManager.getInstance(context) + val targetWidth = wm.desiredMinimumWidth + val targetHeight = wm.desiredMinimumHeight + + // 3. 비율 유지하며 Center Crop 처리 + val finalBitmap = Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_8888) + val canvas = Canvas(finalBitmap) + val scale = Math.max(targetWidth.toFloat() / originalBitmap.width, targetHeight.toFloat() / originalBitmap.height) + val scaledWidth = scale * originalBitmap.width + val scaledHeight = scale * originalBitmap.height + val left = (targetWidth - scaledWidth) / 2f + val top = (targetHeight - scaledHeight) / 2f + + canvas.drawBitmap(originalBitmap, null, RectF(left, top, left + scaledWidth, top + scaledHeight), Paint(Paint.FILTER_BITMAP_FLAG)) + + // 4. 적용 + wm.setBitmap(finalBitmap, null, true, WallpaperManager.FLAG_SYSTEM) + Result.success() + } catch (e: Exception) { + e.printStackTrace() + Result.retry() + } + } +} + class AggregatedSystemWorker(private val context: Context, params: WorkerParameters) : CoroutineWorker(context, params) { override suspend fun doWork(): Result { // 통합 시스템 작업 실행 @@ -95,7 +148,7 @@ class ForeGroundService : Service() { val ACTION_SIT_DOWN = "ACTION_SIT_DOWN" val ACTION_COPY_COMPLETE = "ACTION_COPY_COMPLETE" - val targetUrls = arrayListOf() + val targetUrls = arrayListOf>() } enum class BLUETOOTH_STATE(val statestr: String) { @@ -106,7 +159,7 @@ class ForeGroundService : Service() { var blueToothAdapter:BluetoothAdapter? = null private var mWorkManager: WorkManager? = null - + private val serviceScope = CoroutineScope(Dispatchers.Default) val NOTIF_ID = 830721 override fun onCreate() { super.onCreate() @@ -114,9 +167,31 @@ class ForeGroundService : Service() { val filter = IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED) registerReceiver(bluetoothreceiver, filter) refreshFeeds() + startWallpaperTimer() startForeGround(vibrator = true) } + private fun startWallpaperTimer() { + serviceScope.launch { + while (true) { + // 실행하고자 하는 폴더 경로 (본인 경로로 수정) + val myFolderPath = File(File(getExternalFilesDir(null), "completed_torrents"),"이미지").absolutePath + + val workRequest = OneTimeWorkRequestBuilder() + .setInputData(workDataOf("FOLDER_PATH" to myFolderPath)) + .build() + + workmanager()?.enqueueUniqueWork( + "SingleWallpaperChange", + ExistingWorkPolicy.REPLACE, + workRequest + ) + + delay(TimeUnit.MINUTES.toMillis(10)) // 10분 대기 + } + } + } + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { Blog.LOGE("intent?.action >> ${intent?.action}") when(intent?.action) { @@ -128,7 +203,7 @@ class ForeGroundService : Service() { ACTION_VIDEO_DOWNLOAD -> { intent?.getStringExtra(EXTRA_TARGET_URL)?.let { Uri.parse(it)?.let { - addToTargetYtubeUrl(it.toString()) + addToTargetYtubeUrl(it.toString(), intent.getBooleanExtra("forMusic", false)) } } } @@ -159,51 +234,76 @@ class ForeGroundService : Service() { return START_STICKY } - fun addToTargetYtubeUrl(url : String) { - targetUrls.add(url) - if((targetUrls?.size ?: 0) > 0) { - downloadVideo(targetUrls?.firstOrNull()) + fun addToTargetYtubeUrl(url : String? = null, forMusic: Boolean = false) { + url?.let { url -> + if (url.length > 0) { + targetUrls.add(url to forMusic) + } } + + targetUrls.removeFirstOrNull()?.let { + downloadVideo(it.first,it.second) + } + + + } var currentProcessId : String? = null set(value) { field = value if (value == null) { - startForeGround(max= 0, progress = 0, vibrator = false) +// startForeGround(max= 0, progress = 0, vibrator = false) } } - fun downloadVideo(url: String?) { + fun downloadVideo(url: String?, forMusic: Boolean = false) { url?.let { CoroutineScope(Dispatchers.IO).launch { - - try { -// val youtubeDLDir = File( -// Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), -// "youtubedl-android" -// ) val youtubeDLDir = File(getExternalFilesDir(null), "completed_torrents") - val command = YoutubeDLRequest(url) - command.addOption("-o", youtubeDLDir.getAbsolutePath() + "/%(title)s.%(ext)s"); + val command = YoutubeDLRequest(url).apply { + addOption("-q") // 로그 최소화 + addOption("--no-warnings") // 경고 숨김 + + val cookieFile = File(this@ForeGroundService.cacheDir, "cookies.txt") + addOption("--cookies", "${cookieFile.absolutePath}") + + // 출력 경로 + addOption("-o", "${youtubeDLDir.absolutePath}/%(title)s.%(ext)s") + + if (forMusic) { + // 음악 전용 옵션 + addOption("-x") // 오디오 추출 + addOption("--audio-format", "mp3") + addOption("--extractor-args", "youtube:player_client=web_music,android") + addOption("-f", "bestaudio[ext=m4a]/bestaudio/best") + } else { + // 일반 영상용 + addOption("-f", "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best") + } + + // 안정성 향상 옵션 (모두에 적용) + addOption("--no-mtime") // 파일 수정시간 안 건드림 + addOption("--restrict-filenames") // 특수문자 제한 + } + currentProcessId = UUID.randomUUID().toString() YoutubeDL.getInstance() .execute(command, currentProcessId) { progress, est, str -> - startForeGround(100, progress.toInt(),str, false) + startForeGround(100, progress.toInt(), str, false) if (progress >= 100) { - targetUrls.remove(url) currentProcessId = null - if((targetUrls?.size ?: 0) > 0) { - downloadVideo(targetUrls?.firstOrNull()) + if ((targetUrls?.size ?: 0) > 0) { + addToTargetYtubeUrl() } } } } catch (e: Exception) { - e.printStackTrace() + Blog.LOGE("Download Error", e) currentProcessId = null - if((targetUrls?.size ?: 0) > 0) { - downloadVideo(targetUrls?.firstOrNull()) + if ((targetUrls?.size ?: 0) > 0) { + addToTargetYtubeUrl() } } } diff --git a/app/src/main/kotlin/bums/lunatic/launcher/home/CompletedFilesFragment.kt b/app/src/main/kotlin/bums/lunatic/launcher/home/CompletedFilesFragment.kt index d143046f..79b2e7e1 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/home/CompletedFilesFragment.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/home/CompletedFilesFragment.kt @@ -737,11 +737,11 @@ class CompletedFilesFragment : Fragment() { private fun organizeRootFiles() { CoroutineScope(Dispatchers.IO).launch { // 1. 루트 폴더의 파일 목록 가져오기 - val filesInRoot = rootDir.listFiles()?.filter { it.isFile } ?: emptyList() + val filesInRoot = if (selectedFiles.isEmpty()) rootDir.listFiles()?.filter { it.isFile } else selectedFiles var movedCount = 0 // 2. 널브러진 파일들을 확장자별 폴더로 이동 - filesInRoot.forEach { file -> + filesInRoot?.forEach { file -> val ext = file.extension.lowercase() val folderName = when { extImages.contains(ext) -> "이미지" diff --git a/app/src/main/kotlin/bums/lunatic/launcher/home/GeckoWeb.kt b/app/src/main/kotlin/bums/lunatic/launcher/home/GeckoWeb.kt index 78f0d65e..67f0014e 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/home/GeckoWeb.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/home/GeckoWeb.kt @@ -83,6 +83,7 @@ open class GeckoWeb @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : GeckoView(context, attrs) { + // --- 1. Properties & Initialization --- // 1. 세션 상태를 저장할 SharedPreferences 키 @@ -193,6 +194,8 @@ open class GeckoWeb @JvmOverloads constructor( companion object { var currentRetryCount = 0 + var currentCookieString = "" + var currentCookieUrlString = "" } @@ -201,7 +204,7 @@ open class GeckoWeb @JvmOverloads constructor( override fun setVisibility(visibility: Int) { super.setVisibility(visibility) - decoViews.filter { it.id > -1 && it.id != R.id.btn_dl_video }.forEach { it.visibility = visibility } + decoViews.filter { it.id > -1 }.forEach { it.visibility = visibility } } open fun loadUrl(url: String, param: String? = null) { @@ -226,58 +229,110 @@ open class GeckoWeb @JvmOverloads constructor( currentRetryCount = 0 } - fun checkIfDownloadable(url: String) { - // UI 초기화 - post { - decoViews.firstOrNull { it.id == R.id.btn_dl_video }?.let { - it.setOnClickListener {} - it.visibility = GONE + var lastCheckUrlS = hashSetOf() + + fun cleanYoutubeUrl(url: String): String { + return try { + val uri = Uri.parse(url) + val videoId = uri.getQueryParameter("v") + + if (videoId != null) { + // v=만 남기고 나머지 파라미터 제거 + "https://www.youtube.com/watch?v=$videoId" + } else { + // Shorts나 다른 형식 + url } + } catch (e: Exception) { + url // 실패시 원본 반환 + } + } + + fun checkIfDownloadable(firstUrl: String, forMusic: Boolean = false) { + if (firstUrl.startsWith("about")) { + return + } + val cleanUrl = cleanYoutubeUrl(firstUrl) // ← 추가! + + decoViews.firstOrNull { it.id == R.id.btn_dl_video }?.let { + it.setOnClickListener {} + it.visibility = GONE } CoroutineScope(Dispatchers.IO).launch { try { - val request = YoutubeDLRequest(url) -// mGKCookie?.COOKIES?.let { cookieStr -> -// val cookieFile = File(context.filesDir, "cookies.txt") -// val cookies = cookieStr.split(";").mapNotNull { -// val p = it.trim().split("=", limit = 2) -// if (p.size == 2) p[0] to p[1] else null -// }.toMap() -// val expires = (System.currentTimeMillis() / 1000) + 3600 * 24 * 7 -// -// val content = buildString { -// appendLine("# Netscape HTTP Cookie File") -// cookies.forEach { (k, v) -> -// appendLine(".${url.toUri().host}\tTRUE\t/\tTRUE\t$expires\t$k\t$v") -// } -// } -// cookieFile.writeText(content) -// request.addOption("--cookies", cookieFile.absolutePath) -// } + val request = YoutubeDLRequest(cleanUrl).apply { + // 1. yt-dlp 업데이트 강제 (가장 중요!) + addOption("-q") // 로그 최소화 + addOption("--no-warnings") // 경고 숨김 + + val cookieFile = File(context.cacheDir, "cookies.txt") + addOption("--cookies", "${cookieFile.absolutePath}") + + if (forMusic) { + addOption("-x") // 오디오 추출 + addOption("--audio-format", "mp3") + // YouTube Music 전용 extractor arg 추가 + addOption("--extractor-args", "youtube:player_client=web_music,android") + // 음악 스트림 우선 선택 + addOption("-f", "bestaudio[ext=m4a]/bestaudio/best") + } else { + // 일반 영상용 기본 포맷 + addOption("-f", "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best") + } + + + // 디버깅용 (테스트 후 제거) +// addOption("--verbose") + } val videoInfo = YoutubeDL.getInstance().getInfo(request) if (videoInfo != null && !videoInfo.title.isNullOrEmpty()) { - post { + CoroutineScope(Dispatchers.Main).launch { decoViews.firstOrNull { it.id == R.id.btn_dl_video }?.let { view -> - view.setOnClickListener { videoDownload(url) } + view.setOnClickListener { videoDownload(cleanUrl, forMusic) } view.visibility = VISIBLE } } } } catch (e: Exception) { - // Log.e("GeckoWeb", "Download Check Error", e) + Blog.LOGE("Download Check Error", e) + +// val msg = e.message ?: "" + +// if (msg.contains("parse video information") && !lastCheckUrlS.contains(cleanUrl)) { +// CoroutineScope(Dispatchers.Main).launch { +// CoroutineScope(Dispatchers.Main).launch { +// decoViews.firstOrNull { it.id == R.id.btn_dl_video }?.let { view -> +// view.setOnClickListener { +// AlertDialog.Builder(context) +// .setTitle("음악 전용으로 재시도?") +// .setMessage("parse 에러 발생. forMusic=true로 재시도합니다.") +// .setPositiveButton("오키") { _, _ -> +// checkIfDownloadable(cleanUrl, true) +// } +// .setNegativeButton("취소", null) +// .show() +// lastCheckUrlS.add(cleanUrl) +// } +// } +// } +// +// } +// } } } } - fun videoDownload(videoUrl: String) { + fun videoDownload(videoUrl: String,forMusic : Boolean = false) { val intent = Intent(context, ForeGroundService::class.java).apply { action = ACTION_VIDEO_DOWNLOAD putExtra(EXTRA_TARGET_URL, videoUrl) + putExtra("forMusic",forMusic) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) context.startForegroundService(intent) else context.startService(intent) + pauseYT() } // --- 3. Delegates (선언 위치를 위로 올려 초기화 보장) --- @@ -322,6 +377,8 @@ open class GeckoWeb @JvmOverloads constructor( // [Navigation Delegate] private val navigationDelegate = object : GeckoSession.NavigationDelegate { + + override fun onNewSession(session: GeckoSession, uri: String): GeckoResult? { Uri.parse(uri)?.let { if (it.host?.let { h -> lastedUrl?.contains(h, true) } == true || @@ -366,8 +423,8 @@ open class GeckoWeb @JvmOverloads constructor( Blog.LOGE("Gecko 에러 발생!! URI: $uri, Code: ${error.code}, Category: ${error.category}") if (uri?.contains("booktoki") == true) { when(lastArrow) { - 1->{sendJsonMsg("CLICK_NEXT_CHAPTER")} - -1->{sendJsonMsg("CLICK_PREV_CHAPTER")} + 1->{sendJsonMsg("CLICK_NEXT_CHAPTER")} + -1->{sendJsonMsg("CLICK_PREV_CHAPTER")} else -> {} } @@ -378,13 +435,14 @@ open class GeckoWeb @JvmOverloads constructor( } override fun onCanGoBack(session: GeckoSession, canGoBack: Boolean) { + Blog.LOGE("onCanGoBack $canGoBack ${session}") this@GeckoWeb.canGoBack = canGoBack } } var lastArrow = 0 fun goBack() { if (true == canGoBack) - session?.goBack() + session?.goBack() } // [Progress Delegate] @@ -411,17 +469,21 @@ open class GeckoWeb @JvmOverloads constructor( saveCurrentSessionState() } onPageStartCallback?.invoke(url) +// Blog.LOGE("onPageStart $url ${session}") + checkIfDownloadable(url) } override fun onPageStop(session: GeckoSession, success: Boolean) { onPageStopCallback?.invoke(success) if (success) { saveCurrentSessionState() } + Blog.LOGE("onPageStop $success ${session}") } override fun onSessionStateChange(session: GeckoSession, sessionState: GeckoSession.SessionState) { lastSessionState = sessionState onSessionStateChangeCallback?.invoke(sessionState) + Blog.LOGE("onSessionStateChange $sessionState ${session}") } } private var lastSessionState: GeckoSession.SessionState? = null @@ -557,9 +619,28 @@ open class GeckoWeb @JvmOverloads constructor( return false // 실제 구현 시에는 이전 MTRANS_Y 값과 비교 로직 추가 } + private suspend fun saveYoutubeCookiesToFile(): String? { + val cookiesStr = currentCookieString + return try { + val cookieFile = File(context.cacheDir, "cookies.txt") + cookieFile.writeText(cookiesStr) + cookieFile.absolutePath + } catch (e: Exception) { + Blog.LOGE("Cookie file save failed", e) + null + } + } private fun handlePortMessage(msg: PortMessage) { when (msg.type) { + "COOKIES_REPORT"-> { + Blog.LOGE("${msg.value} -> ${msg.url}") + currentCookieString = msg.value ?: "" + currentCookieUrlString = msg.url ?: "" + CoroutineScope(Dispatchers.IO).launch { + saveYoutubeCookiesToFile() + } + } "SCROLL_STATE" -> { Blog.LOGE("${msg.type} : ${msg.value}") scrollState = msg.value?.toInt() ?: 0 @@ -790,4 +871,9 @@ open class GeckoWeb @JvmOverloads constructor( fun play_pause() { sendJsonMsg("PLAY_PAUSE") } + + fun pauseYT() { + sendJsonMsg("PAUSE_YT") + } + } \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/home/NeoRssActivity.kt b/app/src/main/kotlin/bums/lunatic/launcher/home/NeoRssActivity.kt index 2a26fcd9..43877f09 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/home/NeoRssActivity.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/home/NeoRssActivity.kt @@ -426,6 +426,7 @@ open class NeoRssActivity : CommonActivity() { FFmpeg.getInstance().init(this) CoroutineScope(Dispatchers.IO).launch { try { + Blog.LOGE("YoutubeDL.getInstance().updateYoutubeDL()") YoutubeDL.getInstance().updateYoutubeDL(this@NeoRssActivity) } catch (e: YoutubeDLException) { Blog.LOGE("failed to initialize youtubedl-android", e) diff --git a/app/src/main/kotlin/bums/lunatic/launcher/home/tokiz/HistoryManager.kt b/app/src/main/kotlin/bums/lunatic/launcher/home/tokiz/HistoryManager.kt index 7245b560..23bb0a07 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/home/tokiz/HistoryManager.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/home/tokiz/HistoryManager.kt @@ -104,6 +104,7 @@ class PortMessage { var imgSrc: String? = null var base64Data: String? = null var value : String? = null + var url : String? = null } class BookContents { var chapterTitle : String? = null diff --git a/app/src/main/res/layout/layout_lunatic_browser.xml b/app/src/main/res/layout/layout_lunatic_browser.xml index 2da79f71..743f9eb5 100644 --- a/app/src/main/res/layout/layout_lunatic_browser.xml +++ b/app/src/main/res/layout/layout_lunatic_browser.xml @@ -90,7 +90,7 @@ android:ellipsize="middle" android:singleLine="true" /> - + \ No newline at end of file diff --git a/app/src/main/res/layout/rss_activity.xml b/app/src/main/res/layout/rss_activity.xml index 4e47fd70..2c1175de 100644 --- a/app/src/main/res/layout/rss_activity.xml +++ b/app/src/main/res/layout/rss_activity.xml @@ -6,7 +6,7 @@ android:padding="0dp" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" - android:background="@android:color/transparent" + android:background="#66000000" android:orientation="vertical" >