# Conflicts:
#	app/src/main/kotlin/bums/lunatic/launcher/home/RssHome.kt
#	app/src/main/kotlin/bums/lunatic/launcher/utils/JsoupUtils.kt
...
This commit is contained in:
lunaticbum 2025-07-23 16:40:50 +09:00
commit f56c43d82b
21 changed files with 954 additions and 3224 deletions

View File

@ -5,6 +5,7 @@ plugins {
id ("com.android.application")
id ("kotlin-android")
id ("io.realm.kotlin")
id ("kotlin-kapt")
}
android {
@ -110,6 +111,7 @@ dependencies {
implementation("com.github.delight-im:Android-AdvancedWebView:v3.2.1")
implementation(project(":library"))
implementation(project(":utils"))
implementation( "com.github.bumptech.glide:glide:4.11.0")
// implementation("org.mozilla.geckoview:geckoview:139.0.20250523173407")
// https://mvnrepository.com/artifact/org.mozilla.geckoview/geckoview
implementation("org.mozilla.geckoview:geckoview:139.0.20250523173407")

View File

@ -1,40 +1,65 @@
browser.webRequest.onHeadersReceived.addListener(
function(details) {
let headers = details.responseHeaders || [];
// Cache-Control 헤더가 없거나 no-store, no-cache 등일 때 수정
let found = false;
for (let header of headers) {
if (header.name.toLowerCase() === "cache-control") {
header.value = "public, max-age=31536000"; // 1년 캐시
found = true;
}
}
if (!found) {
headers.push({name: "Cache-Control", value: "public, max-age=31536000"});
}
return {responseHeaders: headers};
},
{urls: ["*://*/*.gif"], types: ["image"]},
["blocking", "responseHeaders"]
);
//const originalLog = console.log;
//console.log = function(...args) {
// // 메시지 가공 또는 네이티브로 전달
// originalLog.apply(console, args);
// browser.runtime.sendNativeMessage("browser", args);
//};
browser.webRequest.onBeforeRequest.addListener(
function(details) {
return { cancel: true };
},
{ urls: ["*://*/*.gif"], types: ["image"] },
["blocking"]
);
//window.addEventListener('error', function(event) {
// if (event.message && event.message.includes('No impl for message: MozAfterPaint')) {
// // 앱으로 오류 신호 전달
// browser.runtime.sendMessage({ type: 'RELOAD_REQUEST' });
//const port = browser.runtime.connectNative("browser");
//// 모든 요청을 가로챔
//browser.webRequest.onCompleted.addListener(async (details) => {
// // 원래 요청 URL
// const url = details.url;
// try {
// // 실제 데이터 fetch (credentials, 쿠키 등 필요시 옵션 맞춤)
// const res = await fetch(url, {credentials: "include"});
// const blob = await res.blob();
// // 데이터 -> ArrayBuffer
// const arrayBuffer = await blob.arrayBuffer();
// // base64로 변환
// const base64 = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));
// // 네이티브 앱에게 전달
// port.postMessage(JSON.stringify({type:"CACAHE",msg:msg , url: url, data: base64}));
//// browser.runtime.sendNativeMessage(
//// "browser", // 등록한 네이티브 앱 id
//// { url: url, data: base64 }
//// );
// } catch(e) {
// // 에러 처리
// console.error(e);
// }
//});
//}, {urls: ["<all_urls>"]});
//
////
////browser.webRequest.onHeadersReceived.addListener(
//// function(details) {
//// let headers = details.responseHeaders || [];
//// // Cache-Control 헤더가 없거나 no-store, no-cache 등일 때 수정
//// let found = false;
//// for (let header of headers) {
//// if (header.name.toLowerCase() === "cache-control") {
//// header.value = "public, max-age=31536000"; // 1년 캐시
//// found = true;
//// }
//// }
//// if (!found) {
//// headers.push({name: "Cache-Control", value: "public, max-age=31536000"});
//// }
//// return {responseHeaders: headers};
//// },
//// {urls: ["*://*/*.gif"], types: ["image"]},
//// ["blocking", "responseHeaders"]
////);
//////const originalLog = console.log;
//////console.log = function(...args) {
////// // 메시지 가공 또는 네이티브로 전달
////// originalLog.apply(console, args);
////// browser.runtime.sendNativeMessage("browser", args);
//////};
////
////browser.webRequest.onBeforeRequest.addListener(
//// function(details) {
//// return { cancel: true };
//// },
//// { urls: ["*://*/*.gif"], types: ["image"] },
//// ["blocking"]
////);
////window.addEventListener('error', function(event) {
//// if (event.message && event.message.includes('No impl for message: MozAfterPaint')) {
//// // 앱으로 오류 신호 전달
//// browser.runtime.sendMessage({ type: 'RELOAD_REQUEST' });
//// }
////});

File diff suppressed because one or more lines are too long

View File

@ -22,7 +22,6 @@
"nativeMessagingFromContent",
"geckoViewAddons",
"webRequest",
"webRequestBlocking",
"*://*/*.gif"
"webRequestBlocking"
]
}

View File

@ -1,4 +1,4 @@
const list = ["?page=2","?page=3","?page=4","?page=5","?page=6"];
const port = browser.runtime.connectNative("browser");
port.onMessage.addListener(response => {
var type= response["type"];
@ -285,5 +285,126 @@ if (port) {
)
}
if (document.querySelectorAll('[class^="col-md-4 mb-4 video-item"]').length > 1) {
var datas = []
document.querySelectorAll('[class^="col-md-4 mb-4 video-item"]').forEach(function (e) {
var date = 0
try {
const dateString = e.querySelector('[class^="mb-2"]').querySelector("a").textContent.trim();
const [day, month, year] = dateString.split("/").map(Number);
date = new Date(year, month - 1, day).getTime();
}catch (e) {
}
var actor = ""
try {
actor = e.querySelector('[class^="mb-1"]').getAttribute("alt").trim()
}catch (e) {
}
var desc = ""
try {
e.querySelectorAll('[class^="badge badge-"]').forEach(function (e) {
try {
if (Number(e.textContent) > 0) {
}else {
if (desc.length > 0) {
desc += ","
}
desc += e.textContent.trim()
}
}catch (e) {}
}
)
}catch (e) {
}
var thumb = ""
try {
thumb = e.querySelector("td").querySelector("a").getAttribute('data-link')
}catch (e) {
}
var magnet = ""
try {
e.querySelectorAll("td").forEach(function (e) {
e.querySelectorAll("a").forEach(function (e) {
if(e.getAttribute("href").startsWith("magnet")) {
magnet = e.getAttribute("href").replaceAll("&amp;", "&");
}
})
})
}catch (e) {
}
var title = "";
try {
title = e.querySelector(".name").querySelector("a").querySelector("span").textContent.trim();
}catch (e) {
}
var originPage = ""
try {
originPage = location.protocol + "//" + location.hostname + e.querySelector(".name").querySelector("a").getAttribute("href");
}catch (e) {
}
var screenshots = ""
try {
e.querySelectorAll("a").forEach(function (e) {
if(e.getAttribute("href").search("screenshots") > -1) {
screenshots = e.getAttribute("href");
}
})
}catch (e) {
}
datas.push({
"title" : title,
"description" : desc,
"originPage" : originPage,
"magnet" : magnet,
"thumbnail" : thumb,
"pubDate" : date,
"screenshots" : screenshots,
"chosung" : "",
"category" : "PRIVATE"
});
})
sendMessage(
{
type: "PRIVATES",
privates: datas
}
);
gotoNext()
}
},1500)
}
function gotoNext() {
if (location.href.search("page") < 0) {
targetUrl = location.protocol + "//" + location.hostname + "/" + list[0]
}else {
var targetUrl = ""
for (i = 0; i < list.length - 1; i++) {
try {
if (location.href.search(list[i]) > -1) {
targetUrl = location.protocol + "//" + location.hostname + list[i + 1]
}
}catch (e) {
console.error(e)
}
}
}
if (targetUrl.length > 5) {
setTimeout(function () {location.href = targetUrl},3000)
}
}

View File

@ -50,6 +50,7 @@ import androidx.core.net.toUri
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.OneTimeWorkRequest
@ -66,7 +67,7 @@ import bums.lunatic.launcher.helpers.Constants.Companion.PREFS_SETTINGS
import bums.lunatic.launcher.helpers.Constants.Companion.widgetHostId
import bums.lunatic.launcher.helpers.HeadsetActionButtonReceiver
import bums.lunatic.launcher.helpers.PrefLong
import bums.lunatic.launcher.home.LauncherHome
import bums.lunatic.launcher.home.RssHome
import bums.lunatic.launcher.home.RssViewBuilder
import bums.lunatic.launcher.model.RssData
import bums.lunatic.launcher.model.RssDataType
@ -612,7 +613,7 @@ internal class LauncherActivity : CommonActivity() {
when(id) {
R.id.feeds -> {
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, LauncherHome())
.replace(R.id.fragment_container, RssHome())
.commit()
}
R.id.books ->{
@ -728,8 +729,12 @@ internal class LauncherActivity : CommonActivity() {
override fun handleOnBackPressed() {
val currentFragment = supportFragmentManager.findFragmentById(R.id.fragment_container)
when(currentFragment) {
is LauncherHome ->{
currentFragment.doNextPage()
is RssHome ->{
if (currentFragment.binding.layoutRssSummary.root.isVisible) {
currentFragment.openGecko("")
} else {
currentFragment.doNextPage()
}
}
}
}

View File

@ -1,31 +1,31 @@
//import androidx.annotation.Nullable;
//
//import java.util.HashMap;
//
import androidx.annotation.Nullable;
import java.util.HashMap;
////package bums.lunatic.launcher.home;
////
////import androidx.annotation.Nullable;
////
////import java.util.HashMap;
////
public class ExtHahMap extends HashMap<String,Object> {
@Nullable
@Override
public Object put(String key, Object value) {
if (value instanceof String) {
if (((String) value).length() > 0) {
} else {
}
}else {
return super.put(key, value);
}
}
}
//
//import androidx.annotation.Nullable;
//
//import java.util.HashMap;
//
//////package bums.lunatic.launcher.home;
//////
//////import androidx.annotation.Nullable;
//////
//////import java.util.HashMap;
//////
//public class ExtHahMap extends HashMap<String,Object> {
// @Nullable
// @Override
// public Object put(String key, Object value) {
// if (value instanceof String) {
// if (((String) value).length() > 0) {
//
// } else {
//
// }
// }else {
// return super.put(key, value);
// }
// }
//}
//

View File

@ -1,12 +1,17 @@
package bums.lunatic.launcher.home
import android.app.DownloadManager
import android.content.Context
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.net.Uri
import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.util.AttributeSet
import android.util.Base64
import android.util.Log
import android.view.KeyEvent
import android.view.KeyEvent.ACTION_UP
@ -18,14 +23,23 @@ import android.view.KeyEvent.KEYCODE_BUTTON_X
import android.view.KeyEvent.KEYCODE_BUTTON_Y
import android.view.KeyEvent.KEYCODE_DPAD_DOWN
import android.view.KeyEvent.KEYCODE_DPAD_UP
import android.view.View
import android.widget.ProgressBar
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import bums.lunatic.launcher.LauncherActivity.Companion.getRuntime
import bums.lunatic.launcher.tokiz.data.HistoryManager
import bums.lunatic.launcher.tokiz.data.model.History
import bums.lunatic.launcher.tokiz.data.model.PortMessage
import bums.lunatic.launcher.tokiz.view.BWebview
import bums.lunatic.launcher.tokiz.view.JxEvent
import bums.lunatic.launcher.utils.Blog
import bums.lunatic.launcher.utils.afterDay
import bums.lunatic.launcher.workers.WorkersDb
import com.google.gson.Gson
import org.json.JSONException
import org.json.JSONObject
import org.jsoup.Jsoup
import org.mozilla.gecko.util.ThreadUtils
import org.mozilla.geckoview.ExperimentDelegate
import org.mozilla.geckoview.GeckoResult
@ -39,13 +53,18 @@ import org.mozilla.geckoview.WebExtension.MessageDelegate
import org.mozilla.geckoview.WebExtension.PortDelegate
import org.mozilla.geckoview.WebExtensionController.AddonManagerDelegate
import org.mozilla.geckoview.WebRequestError
import java.io.File
import java.text.SimpleDateFormat
class GeckoWeb : BWebview {
constructor(context: Context?) : super(context) {
buildWeb()
}
var decoViews = arrayListOf<View>()
override fun setVisibility(visibility: Int) {
super.setVisibility(visibility)
decoViews.forEach { it.visibility = visibility }
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
buildWeb()
@ -54,6 +73,7 @@ class GeckoWeb : BWebview {
val mPortNam = "browser"
val extPath = "resource://android/assets/extensions/my_extension/"
val extId = "messaging@booktoki468.com"
private fun buildWeb() {
getRuntime()?.let {
val session: GeckoSession = GeckoSession()
@ -87,7 +107,7 @@ class GeckoWeb : BWebview {
var lastedUrl: String? = null
var canGoBack: Boolean? = null
var mPort: WebExtension.Port? = null
var mCaache : WebExtension.Port? = null
object WebExtensionInfo {
val mPortNam = "browser"
val extPath = "resource://android/assets/extensions/my_extension/"
@ -238,6 +258,32 @@ class GeckoWeb : BWebview {
return super.onRecordMalformedConfigurationEvent(feature, part)
}
}
fun showImageDownloadDialog(context: Context, imageUrl: Uri) {
AlertDialog.Builder(context)
.setTitle("이미지 다운로드")
.setMessage("이미지를 저장하시겠습니까?")
.setPositiveButton("저장") { _, _ ->
downloadImage(context, imageUrl)
}
.setNegativeButton("취소", null)
.show()
}
fun downloadImage(context: Context, url: Uri) {
val fileName = url.lastPathSegment ?: "${SimpleDateFormat("yyyyMMddHHmmsss")}.jpg"
val request = DownloadManager.Request(url)
request.setTitle(fileName)
request.setDescription("이미지 다운로드 중...")
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
// 네트워크타입, 알림설정 등 옵션 추가 가능
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
val dm = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
dm.enqueue(request)
Toast.makeText(context, "다운로드 시작: $fileName", Toast.LENGTH_SHORT).show()
}
fun getFilterF() = String(java.util.Base64.getMimeDecoder().decode("aHR0cHM6Ly9pamF2dG9ycmVudC5jb20=".toByteArray()))
val contentDelegate = object : GeckoSession.ContentDelegate {
override fun onTitleChange(
session: GeckoSession,
@ -260,6 +306,21 @@ class GeckoWeb : BWebview {
super.onFirstContentfulPaint(session)
}
override fun onContextMenu(
session: GeckoSession,
screenX: Int,
screenY: Int,
element: GeckoSession.ContentDelegate.ContextElement
) {
if (element.type == GeckoSession.ContentDelegate.ContextElement.TYPE_IMAGE) {
Uri.parse(element.srcUri)?.let {
showImageDownloadDialog(context,it)
}
}
super.onContextMenu(session, screenX, screenY, element)
}
}
val progressDelegate = object : GeckoSession.ProgressDelegate {
override fun onSecurityChange(
@ -284,15 +345,20 @@ class GeckoWeb : BWebview {
}
override fun onPageStart(session: GeckoSession, url: String) {
super.onPageStart(session, url)
if (url?.contains("reddit.app.link") == true) {
session.stop()
Uri.parse(url)?.let { uri ->
context.startActivity(Intent().apply {
action = Intent.ACTION_VIEW
data = uri
})
}
if (url.contains(getFilterF()) && url.contains("jpg") == false) {
this@GeckoWeb.visibility = View.INVISIBLE
}
// if (url?.contains("reddit.app.link") == true) {
// session.stop()
// Uri.parse(url)?.let { uri ->
// context.startActivity(Intent().apply {
// action = Intent.ACTION_VIEW
// flags = Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS.or(FLAG_ACTIVITY_CLEAR_TOP).or(
// FLAG_ACTIVITY_NEW_TASK)
// data = uri
// })
// }
// }
}
override fun onPageStop(session: GeckoSession, success: Boolean) {
@ -305,6 +371,7 @@ class GeckoWeb : BWebview {
}
}
}
}
@ -328,7 +395,18 @@ class GeckoWeb : BWebview {
): GeckoResult<GeckoSession>? {
Blog.LOGE("GeckoView", "onNewSession: $session from WebExtension")
Uri.parse(uri)?.let {
if(it.host?.let { it1 -> lastedUrl?.contains(it1, true) } == true) {
loadUrl(uri)
} else {
context.startActivity(Intent().apply {
action = Intent.ACTION_VIEW
flags = Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS.or(FLAG_ACTIVITY_CLEAR_TOP).or(
FLAG_ACTIVITY_NEW_TASK)
data = it
})
}
}
return super.onNewSession(session, uri)
}
@ -343,6 +421,9 @@ class GeckoWeb : BWebview {
Blog.LOGE("GeckoView", "현재 session: $session")
url?.let { url ->
if (url?.contains(getFilterF()) == true && url.contains("jpg") == false) {
this@GeckoWeb.visibility = View.INVISIBLE
}
if (url.split("//").size > 1) {
url.replace("//", "/").replace("https:/", "https://").let {
Blog.LOGE("url >> ${url} , it >>> ${it}")
@ -371,23 +452,129 @@ class GeckoWeb : BWebview {
message: Any, port: WebExtension.Port
) {
Blog.LOGE("PortDelegate", "Received message from extension: $message")
if (message is String && message.contains("type")) {
try {
var lPortMessage =
Gson().fromJson<PortMessage>(message, PortMessage::class.java)
when(lPortMessage.type) {
"getListResult" -> {
}
"BookContents"->{
}
"NotRegistered" -> {
}
"WebtoonContents"-> {
}
"MSG" -> {
}
"SHOWVIEWER" -> {
}
"PRIVATES"->{
lPortMessage.privates?.forEach {
Blog.LOGE("Item screenshots >>> ${it.screenshots}")
it.pubDate = afterDay(it.pubDate)
WorkersDb.insertData(it)
}
}
"PagerContents" -> {
if (lPortMessage.contents?.isNotEmpty() == true) {
}
}
else -> {
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
override fun onDisconnect(port: WebExtension.Port) {
// This port is not usable anymore.
mPort = null
}
}
val cacheDelegate: PortDelegate =
object : PortDelegate {
override fun onPortMessage(
message: Any, port: WebExtension.Port
) {
Blog.LOGE("cacheDelegate", "cacheDelegate message : $message")
}
override fun onDisconnect(port: WebExtension.Port) {
// This port is not usable anymore.
if (port === mPort) {
mPort = null
if (port === mCaache) {
mCaache = null
}
}
}
fun onReceiveFromExtension(message: JSONObject) {
val url = message.getString("url")
val dataBase64 = message.getString("data")
val host = Uri.parse(url).host!!
val cacheDir = File(context.filesDir, "webcache/$host")
if (!cacheDir.exists()) cacheDir.mkdirs()
// 파일명 생성 (중복처리 필요)
val fileName = filenameFromUrl(url)
val resourceFile = File(cacheDir, fileName)
val rawData = Base64.decode(dataBase64, Base64.DEFAULT)
resourceFile.writeBytes(rawData)
}
fun filenameFromUrl(url: String): String {
// URL을 파싱
val uri = Uri.parse(url)
// 경로 마지막 세그먼트 (예시: /images/logo.png → logo.png)
var filename = uri.lastPathSegment ?: "index.html"
// 쿼리 파라미터 등 URL이 붙은 경우 처리 (예: index.html?version=2)
if (filename.contains("?")) {
filename = filename.substringBefore("?")
}
if (filename.isEmpty() || filename.endsWith("/")) {
filename = "index.html"
}
// 파일명에 사용할 수 없는 문자 제거(윈도우, 리눅스, 맥 등 호환)
filename = filename.replace(Regex("[\\\\/:*?\"<>|]"), "_")
// 너무 긴 파일명은 자르기
val maxLength = 100
if (filename.length > maxLength) {
val ext = filename.substringAfterLast('.', "")
filename = filename.take(maxLength - ext.length - 1) +
if (ext.isNotBlank()) ".$ext" else ""
}
return filename
}
val messageDelegate: MessageDelegate =
object : MessageDelegate {
override fun onConnect(port: WebExtension.Port) {
mPort = port
mPort!!.setDelegate(portDelegate)
Blog.LOGE("onConnect port >>> ${port.name}")
if (port != null) {
mPort = port
mPort!!.setDelegate(portDelegate)
}
}
override fun onMessage(
@ -401,11 +588,8 @@ class GeckoWeb : BWebview {
)
return super.onMessage(nativeApp, message, sender)
}
}
fun onStart() {
}

View File

@ -19,8 +19,9 @@
package bums.lunatic.launcher.home
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
@ -29,9 +30,11 @@ import android.view.LayoutInflater
import android.view.PointerIcon
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.Toast
import androidx.annotation.NonNull
import androidx.annotation.RequiresApi
import androidx.core.view.isVisible
import androidx.databinding.BindingAdapter
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.DividerItemDecoration
@ -48,7 +51,6 @@ import bums.lunatic.launcher.home.adapters.SwipeToDeleteCallback
import bums.lunatic.launcher.model.RssData
import bums.lunatic.launcher.model.RssDataType
import bums.lunatic.launcher.model.WeatherForcast
import bums.lunatic.launcher.openClient
import bums.lunatic.launcher.openReddit
import bums.lunatic.launcher.openYouTube
import bums.lunatic.launcher.tokiz.view.JxEvent
@ -57,10 +59,13 @@ import bums.lunatic.launcher.utils.SimpleFingerGestures
import bums.lunatic.launcher.utils.beforeDay
import bums.lunatic.launcher.utils.beforeOneDay
import bums.lunatic.launcher.workers.WorkersDb
import com.bumptech.glide.Glide
import com.google.android.material.imageview.ShapeableImageView
import io.realm.kotlin.UpdatePolicy
import io.realm.kotlin.ext.query
import io.realm.kotlin.notifications.InitialResults
import io.realm.kotlin.notifications.ResultsChange
import io.realm.kotlin.notifications.UpdatedResults
import io.realm.kotlin.query.RealmQuery
import io.realm.kotlin.query.RealmResults
import io.realm.kotlin.query.Sort
@ -70,7 +75,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
internal class LauncherHome : Fragment() {
internal class RssHome : Fragment() {
lateinit var binding: LauncherHomeBinding
private lateinit var fragManager: FragmentManager
@ -78,18 +83,17 @@ internal class LauncherHome : Fragment() {
private var shouldResume = true
companion object {
var home: LauncherHome? = null
var home: RssHome? = null
var lastedFinishedPageUrl: String = ""
}
val UPDATE_DELAY = 5L
val commandHandler = Handler(Looper.getMainLooper())
val infoUpdate = Runnable { chooseAdpater() }
var result: RealmResults<WeatherForcast>? = null
val nomoreShowCount = 5
fun rssStateVote() = (lasted?.filter { it.vote == true }?.size ?: -1) == (lasted?.size ?: 0)
var lasted: List<RssData>? = null
var lasted: ArrayList<RssData> = arrayListOf()
var infosJob: Job? = null
var rssId = ""
lateinit var mRssAdapter: RssItemAdapter
@ -105,7 +109,9 @@ internal class LauncherHome : Fragment() {
gestureDistance: Double
): Boolean {
Blog.LOGE("")
if (imageView){
openGecko("")
}
return true
}
@ -115,6 +121,9 @@ internal class LauncherHome : Fragment() {
gestureDuration: Long,
gestureDistance: Double
): Boolean {
if (imageView){
openGecko("")
}
Blog.LOGE("")
return true
}
@ -202,19 +211,27 @@ internal class LauncherHome : Fragment() {
}
}
when(rss.category()) {
//RssDataType.GURU,RssDataType.MOST,
RssDataType.REDDIT_NSFW -> {
RssDataType.REDDIT_NSFW,RssDataType.PRIVATE -> {
v.findViewById<ShapeableImageView>(R.id.circle_preview)?.let {
if (it.visibility == View.GONE) {
it.visibility = View.VISIBLE
it.postDelayed({
it.visibility = View.GONE
}, 2000L)
if (RssDataType.PRIVATE.equals(rss.category()) && imageView) {
openGecko("")
} else {
if (RssDataType.REDDIT_NSFW.equals(rss.category())) {
openReddit(rss.originPage())
if (it.visibility == View.GONE) {
it.visibility = View.VISIBLE
it.postDelayed({
it.visibility = View.GONE
}, 2000L)
} else {
openGecko(rss.originPage())
if (RssDataType.REDDIT_NSFW.equals(rss.category())) {
openReddit(rss.originPage())
} else if (RssDataType.PRIVATE.equals(rss.category())) {
startActivity(Intent().apply {
action = Intent.ACTION_VIEW
data = Uri.parse(rss.originPage)
})
} else {
openGecko(rss.originPage())
}
}
}
}
@ -238,15 +255,38 @@ internal class LauncherHome : Fragment() {
}
fun openGecko(originPage: String) {
rssId = originPage
targetList.clear()
if (!imageView) {
rssId = originPage
targetList.clear()
var setString = hashSetOf<String>()
setString.addAll(rssList)
setString.removeAll { it.equals(rssId) }
var setString = hashSetOf<String>()
setString.addAll(rssList)
setString.removeAll { it.equals(rssId) }
targetList.addAll(setString)
binding.geckoWeb.loadUrl(rssId)
targetList.addAll(setString)
binding.geckoWeb.loadUrl(rssId)
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
lasted?.removeFirst()?.let {
binding.layoutRssSummary.root.visibility = View.VISIBLE
binding.layoutRssSummary.title.text = it.title()
binding.layoutRssSummary.desc.text = it.description()
binding.layoutRssSummary.link.text = it.originPage()
loadImage(binding.layoutRssSummary.cover,it.thumbnailUrl())
loadImage(binding.layoutRssSummary.screen,it.getScreen())
}
}
// targetList.clear()
// lasted?.forEach {
// it.thumbnail?.let {
// targetList.add(it)
// }
// }
// rssId = targetList.removeAt(0)
// binding.geckoWeb.loadUrl(rssId)
}
}
@SuppressLint("ClickableViewAccessibility")
@ -272,6 +312,54 @@ internal class LauncherHome : Fragment() {
return@setOnTouchListener false
}
}
binding.vote.setOnClickListener {
if (binding.geckoWeb.isVisible) {
vote()
}
}
binding.test.setOnClickListener {
if(binding.geckoWeb.isVisible) {
binding.geckoWeb.visibility = View.GONE
}
binding.geckoWeb.visibility = View.GONE
binding.geckoWeb.loadUrl("aHR0cHM6Ly9pamF2dG9ycmVudC5jb20=")
}
binding.hide.setOnClickListener {
if (binding.geckoWeb.isVisible) {
WorkersDb.getRealm().apply {
writeBlocking {
val result = query<RssData>().query("originPage == $0", rssId).find()
if (result.size > 0) {
result.forEach {
if(it.vote) {
it.vote = false
}
}
}
}
}
doNextPage()
}
}
binding.home.setOnClickListener {
if (binding.geckoWeb.isVisible) {
binding.geckoWeb.visibility = View.GONE
}
binding.layoutRssSummary.root.visibility = View.GONE
queryInfos()
}
binding.bookmark.setOnClickListener {
binding.layoutRssSummary.root.visibility = View.GONE
queryVotes()
}
binding.prv.setOnClickListener {
queryPrevate()
}
queryInfos()
binding.geckoWeb.progress = binding.progressBar
binding.geckoWeb.jxInteface = { jxEvent ->
@ -289,20 +377,19 @@ internal class LauncherHome : Fragment() {
}
}
}
val nullCursor = PointerIcon.getSystemIcon(context!!, PointerIcon.TYPE_NULL)
val nullCursor = PointerIcon.getSystemIcon(requireContext(), PointerIcon.TYPE_NULL)
binding.root.setPointerIcon(nullCursor)
binding.geckoWeb.decoViews.add(binding.hide)
binding.geckoWeb.decoViews.add(binding.vote)
binding.geckoWeb.decoViews.add(binding.progressBar)
return binding.root
}
fun vote(){
Blog.LOGE("Arrow Center Click")
WorkersDb.getRealm().apply {
writeBlocking {
val result = query<RssData>().query("originPage == $0", rssId).find()
val result = query<RssData>().query(if(imageView)"thumbnail == $0" else "originPage == $0", rssId).find()
if (result.size > 0) {
result.forEach { it.vote = true }
}
@ -316,10 +403,10 @@ internal class LauncherHome : Fragment() {
fun doNextPage() {
WorkersDb.getRealm().apply {
writeBlocking {
val result = query<RssData>().query("originPage == $0", rssId).find()
val result = query<RssData>().query(if(imageView)"thumbnail == $0" else "originPage == $0", rssId).find()
if (result.size > 0) {
result.forEach {
it.read = it.read + nomoreShowCount
it.read = it.read + nomoreShowCount
}
}
}
@ -357,30 +444,48 @@ internal class LauncherHome : Fragment() {
mRssDataResult?.asFlow()?.let { flow ->
infosJob = CoroutineScope(Dispatchers.IO).launch {
flow.collect { changes: ResultsChange<RssData> ->
commandHandler.removeCallbacks(infoUpdate)
WorkersDb.getRealm().apply {
lasted = copyFromRealm(changes.list)
when(changes) {
is InitialResults -> {
commandHandler.removeCallbacks(infoUpdate)
WorkersDb.getRealm().apply {
lasted.clear()
lasted.addAll(copyFromRealm(changes.list))
}
commandHandler.post(infoUpdate)
}
is UpdatedResults -> {
CoroutineScope(Dispatchers.Main).launch {
changes.changeRanges.forEach {
mRssAdapter.notifyItemRangeChanged(it.startIndex, it.length)
}
}
}
}
commandHandler.postDelayed(infoUpdate, UPDATE_DELAY)
}
}
infosJob?.start()
}
}
fun queryPrevate() {
imageView = true
beforeQuery()
updateQuery(WorkersDb.getPrivate())
}
fun queryVotes() {
imageView = false
beforeQuery()
updateQuery(WorkersDb.getVotedRss())
}
var imageView = false
fun queryInfos(
//RssDataType.GURU, RssDataType.MOST,
filter: Collection<RssDataType>? = arrayListOf(RssDataType.REDDIT_NSFW), noLimit: Boolean = false
filter: Collection<RssDataType>? = arrayListOf(RssDataType.REDDIT_NSFW,RssDataType.PRIVATE), noLimit: Boolean = false
) {
imageView = false
beforeQuery()
var rQ = WorkersDb.getRealm().query<RssData>().query("read < $0", nomoreShowCount).distinct("originPage", "title")
if (!noLimit) rQ.query("pubDate > $0", beforeOneDay())
// ((filter?.size ?: 0) > 0).letTrue {filter!!.forEach {rQ = rQ.query("category != $0", it.name)}}
((filter?.size ?: 0) > 0).letTrue {filter!!.forEach {rQ = rQ.query("category != $0", it.name)}}
updateQuery(rQ)
}
@ -401,16 +506,16 @@ internal class LauncherHome : Fragment() {
Blog.LOGE("onViewCreated()")
fragManager.addOnBackStackChangedListener {
Blog.LOGE("addOnBackStackChangedListener()")
shouldResume = if (fragManager.backStackEntryCount == 0) {
binding.root.visibility = View.VISIBLE
true
} else {
binding.root.visibility = View.GONE
false
}
}
// fragManager.addOnBackStackChangedListener {
// Blog.LOGE("addOnBackStackChangedListener()")
// shouldResume = if (fragManager.backStackEntryCount == 0) {
// binding.root.visibility = View.VISIBLE
// true
// } else {
// binding.root.visibility = View.GONE
// false
// }
// }
enableSwipeToDeleteAndUndo()
}
@ -475,8 +580,6 @@ internal class LauncherHome : Fragment() {
override fun onResume() {
super.onResume()
val nullCursor = PointerIcon.getSystemIcon(context!!, PointerIcon.TYPE_NULL)
binding.root.setPointerIcon(nullCursor)
if (shouldResume) {
}
@ -488,7 +591,21 @@ internal class LauncherHome : Fragment() {
}
@BindingAdapter("imageUrl")
fun loadImage(imageView: ImageView, url: String?) {
url?.let {
if (it.length > 4) {
Blog.LOGE("loadImage >>> $it")
Glide.with(imageView.context)
.load(url)
.fitCenter()
.into(imageView)
imageView.visibility = View.VISIBLE
} else {
imageView.visibility = View.GONE
}
} ?: {
imageView.visibility = View.GONE
}
}

View File

@ -219,8 +219,8 @@ internal class RssItemAdapter (
v: View,
event: MotionEvent
): Boolean {
Blog.LOGE("event.device.name >>> ${event.device.name}")
if (event.device.name?.contains("JX-12",true) == true|| event.device.name?.equals("J06",true) == true) {
if (event.device != null && event.device.name != null && (event.device.name?.contains("JX-12",true) == true|| event.device.name?.equals("J06",true) == true)) {
Blog.LOGE("event.device.name >>> ${event.device.name}")
return true//mSimpleFingerGestures.onTouch(v,event)
} else {
return false

View File

@ -1,5 +1,6 @@
package bums.lunatic.launcher.model
import bums.lunatic.launcher.utils.Blog
import bums.lunatic.launcher.utils.JamoUtils
import bums.lunatic.launcher.utils.afterDay
import bums.lunatic.launcher.utils.beforeDayBy
@ -218,6 +219,12 @@ class RssData : RealmObject, RssDataInterface {
var thumbnail : String? = null
var pubDate : Long = 0L
var category : String? = null
var magnet : String? = null
var screenshots : String? = null
fun getScreen() : String {
Blog.LOGE("getScreen $screenshots")
return screenshots ?: ""
}
var chosung : String? = null
var vote : Boolean = false
@ -232,15 +239,18 @@ class RssData : RealmObject, RssDataInterface {
}
else -> title ?: ""
}.apply {
Blog.LOGE("title $this")
chosung = JamoUtils.split(title).joinToString("")
}
}
override fun thumbnailUrl(): String {
Blog.LOGE("thumbnail $thumbnail")
return thumbnail ?: ""
}
override fun originPage(): String {
Blog.LOGE("originPage $originPage")
return originPage ?: ""
}

View File

@ -6,6 +6,7 @@ import bums.lunatic.launcher.helpers.PrefHelper
enum class RssDataType {
NO_DATA,
PRIVATE,
YOUTUBE,
NEWSFEED,
// GURU,
@ -35,14 +36,14 @@ enum class RssDataType {
fun defaultImgSize() = when (this) {
YOUTUBE -> 200
REDDIT_NSFW -> 360
REDDIT_NSFW,PRIVATE -> 360
//,GURU,MOST
else -> { 120 }
}
fun getDefaultVisibiliy() = when (this) {
//,GURU,MOST
REDDIT_NSFW,NEWSFEED -> View.GONE
REDDIT_NSFW,PRIVATE -> View.GONE
else -> { View.VISIBLE }
}

View File

@ -1174,7 +1174,6 @@ abstract class BaseToki : Fragment(), PagedTextViewInterface {
}
}
}
}
fun onLoadedContents(aContents: String) {

View File

@ -1,5 +1,6 @@
package bums.lunatic.launcher.tokiz.data.model
import bums.lunatic.launcher.model.RssData
import io.realm.kotlin.ext.realmListOf
import io.realm.kotlin.types.RealmList
import io.realm.kotlin.types.RealmObject
@ -10,6 +11,8 @@ class PortMessage {
var bookInfos : PageInfosJ? = null
var book : BookContents? = null
var msg : String? = null
var contents : String? = null
var privates : ArrayList<RssData>? = null
}
class BookContents {
var chapterTitle : String? = null

View File

@ -12,6 +12,7 @@ import bums.lunatic.launcher.tokiz.common.TouchArea
import bums.lunatic.launcher.utils.Blog
import bums.lunatic.launcher.utils.SimpleFingerGestures
import org.mozilla.geckoview.GeckoView
import java.util.Base64
enum class JxEvent {
SCROLL_UP,
@ -160,14 +161,19 @@ open class BWebview : GeckoView {
var lastDomain : String = ""
fun loadUrl(url: String) {
var nUrl = url
Blog.LOGE("url >>>> ${url}")
if (url.endsWith("=")) {
nUrl = String(Base64.getMimeDecoder().decode(url.toByteArray()))
} else if (url.startsWith("http") == false) {
nUrl = lastDomain
}
if (this.isVisible == false) {
this.visibility = View.VISIBLE
}
Blog.LOGE("url >>>> ${url}")
var nUrl = url
if (url.startsWith("http") == false) {
nUrl = lastDomain
}
Blog.LOGE("nUrl >>>> ${nUrl}")
nUrl?.let { url ->
if (url.split("//").size > 1) {
url.replace("//","/").replace("https:/","https://").let {

View File

@ -8,6 +8,14 @@ import android.provider.ContactsContract.PhoneLookup
import java.util.Calendar
import java.util.Date
fun afterDay(date: Long): Long {
val cal: Calendar = Calendar.getInstance()
cal.setTime(Date(date))
cal.add(Calendar.HOUR_OF_DAY, 23)
cal.add(Calendar.MINUTE, 53)
return cal.timeInMillis
}
fun before30Min(date: Date): Long {
val cal: Calendar = Calendar.getInstance()
cal.setTime(date)

View File

@ -1,6 +1,6 @@
package bums.lunatic.launcher.utils
import bums.lunatic.launcher.home.LauncherHome.Companion.lastedFinishedPageUrl
import bums.lunatic.launcher.home.RssHome.Companion.lastedFinishedPageUrl
import bums.lunatic.launcher.model.RssData
import bums.lunatic.launcher.model.RssDataType
import bums.lunatic.launcher.model.dateFormat

View File

@ -23,6 +23,7 @@ import bums.lunatic.launcher.model.LocationLog
import bums.lunatic.launcher.model.NotificationItem
import bums.lunatic.launcher.model.RssData
import bums.lunatic.launcher.model.RssDataInterface
import bums.lunatic.launcher.model.RssDataType
import bums.lunatic.launcher.model.TelegramBotUpdate
import bums.lunatic.launcher.model.TelegramChat
import bums.lunatic.launcher.model.TelegramData
@ -33,6 +34,7 @@ import bums.lunatic.launcher.utils.Blog
import bums.lunatic.launcher.utils.JamoUtils
import bums.lunatic.launcher.utils.beforeDay
import bums.lunatic.launcher.utils.beforeOneDay
import com.google.gson.Gson
import io.realm.kotlin.Realm
import io.realm.kotlin.RealmConfiguration
import io.realm.kotlin.UpdatePolicy
@ -86,9 +88,21 @@ object WorkersDb {
getRealm().apply {
this.writeBlocking {
try {
if (query<RssData>("originPage == $0", rssData.originPage).find().isEmpty()) {
this.copyToRealm(rssData, UpdatePolicy.ERROR)
if (rssData.category().equals(RssDataType.PRIVATE)) {
this.copyToRealm(rssData, UpdatePolicy.ALL)
Blog.LOGE("rssData >> $rssData ${rssData.getScreen()}")
} else {
if (query<RssData>("originPage == $0", rssData.originPage).find()
.isEmpty()
) {
this.copyToRealm(rssData, UpdatePolicy.ERROR)
}
}
try {
query<RssData>("originPage == $0",rssData.originPage).find()?.first()?.let {
Blog.LOGE("SAVED CHECK ${Gson().toJson(this.copyFromRealm(it))}")
}
}catch (e: Exception) {e.printStackTrace()}
} catch (e : Exception) {
}
@ -212,6 +226,9 @@ object WorkersDb {
}
}
fun getPrivate() = getRealm().query<RssData>().query("category == $0 ",
RssDataType.PRIVATE.name).distinct("originPage", "title").sort("pubDate", Sort.DESCENDING)
fun getVotedRss() = getRealm().query<RssData>().query("vote == $0", true).distinct("originPage", "title")
fun getDeleteQuery( ) : RealmQuery<RssData>{

View File

@ -1,8 +1,8 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
android:tint="@color/white">
<path
android:fillColor="?android:attr/textColorPrimary"
android:fillColor="@color/white"
android:pathData="M7.7,20.5Q6.775,20.5 6.163,19.887Q5.55,19.275 5.55,18.35V5.9H4.55V4.55H8.95V3.65H15.1V4.55H19.5V5.9H18.5V18.35Q18.5,19.275 17.888,19.887Q17.275,20.5 16.35,20.5ZM17.15,5.9H6.9V18.35Q6.9,18.7 7.125,18.925Q7.35,19.15 7.7,19.15H16.35Q16.65,19.15 16.9,18.9Q17.15,18.65 17.15,18.35ZM9.525,17.125H10.875V7.925H9.525ZM13.175,17.125H14.525V7.925H13.175ZM6.9,5.9V18.35Q6.9,18.7 6.9,18.925Q6.9,19.15 6.9,19.15Q6.9,19.15 6.9,18.925Q6.9,18.7 6.9,18.35Z"/>
</vector>

View File

@ -1,45 +1,146 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:background="@android:color/transparent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_margin="@dimen/default_layout_margin"
android:id="@+id/infoList"
<layout xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@android:color/transparent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
android:padding="@dimen/default_padding"
android:scrollbars="none"
android:visibility="visible"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
/>
android:layout_height="match_parent">
<ImageButton
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:id="@+id/test"
android:scaleType="fitCenter"
android:adjustViewBounds="true"
android:visibility="visible"
android:background="@null"
android:src="@drawable/ic_search"
android:layout_width="40dp"
android:tint="@color/white"
android:foregroundTint="@color/white"
tools:ignore="ContentDescription,UseAppTint"
android:layout_height="40dp" />
<ImageButton
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:id="@+id/vote"
android:scaleType="fitCenter"
android:adjustViewBounds="true"
android:visibility="visible"
android:background="@null"
android:src="@drawable/saved"
android:layout_width="40dp"
tools:ignore="ContentDescription"
android:layout_height="40dp" />
<ImageButton
android:id="@+id/hide"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toLeftOf="@id/vote"
android:src="@drawable/ic_delete"
android:tintMode="multiply"
android:layout_marginLeft="12dp"
android:scaleType="fitCenter"
android:background="@null"
android:layout_width="40dp"
android:visibility="visible"
android:adjustViewBounds="true"
tools:ignore="ContentDescription"
android:layout_height="40dp"
/>
<ImageButton
android:id="@+id/home"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:src="@drawable/home"
android:tintMode="multiply"
android:scaleType="fitCenter"
android:background="@null"
android:layout_width="40dp"
android:adjustViewBounds="true"
android:layout_height="40dp"
app:tint="@color/white"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/bookmark"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toRightOf="@id/home"
android:src="@drawable/bookmark"
android:scaleType="fitCenter"
android:background="@null"
android:layout_width="40dp"
android:adjustViewBounds="true"
tools:ignore="ContentDescription"
android:layout_height="40dp"/>
<ImageButton
android:layout_marginLeft="10dp"
android:id="@+id/prv"
app:layout_constraintBottom_toBottomOf="@id/bookmark"
app:layout_constraintLeft_toRightOf="@id/bookmark"
android:src="@drawable/bookmark"
android:scaleType="fitCenter"
android:background="@null"
android:layout_width="10dp"
android:alpha="0.2"
android:tint="@color/finestSilver"
android:adjustViewBounds="true"
tools:ignore="ContentDescription"
android:layout_height="10dp"/>
<androidx.recyclerview.widget.RecyclerView
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_margin="@dimen/default_layout_margin"
android:id="@+id/infoList"
android:layout_width="match_parent"
android:overScrollMode="never"
android:padding="@dimen/default_padding"
android:scrollbars="none"
android:visibility="visible"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/bookmark"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
/>
<bums.lunatic.launcher.home.GeckoWeb
android:id="@+id/geckoWeb"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<ProgressBar
app:layout_constraintTop_toTopOf="@id/geckoWeb"
app:layout_constraintLeft_toLeftOf="@id/geckoWeb"
app:layout_constraintRight_toRightOf="@id/geckoWeb"
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="4dp"
android:max="100"
android:progress="0"
android:visibility="visible"
android:indeterminate="false"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<bums.lunatic.launcher.home.GeckoWeb
android:id="@+id/geckoWeb"
android:visibility="gone"
android:layout_width="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/bookmark"
android:layout_height="0dp"
/>
<include layout="@layout/layout_rss_summary"
android:id="@+id/layout_rss_summary"
android:layout_width="match_parent"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/bookmark"
android:layout_height="0dp"/>
<ProgressBar
app:layout_constraintTop_toTopOf="@id/geckoWeb"
app:layout_constraintLeft_toLeftOf="@id/geckoWeb"
app:layout_constraintRight_toRightOf="@id/geckoWeb"
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="4dp"
android:max="100"
android:progress="0"
android:visibility="visible"
android:indeterminate="false"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="bums.lunatic.launcher.model.RssData"/>
<variable
name="rss"
type="bums.lunatic.launcher.model.RssData" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="UselessParent">
<LinearLayout
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/title"
android:singleLine="false"
android:lines="0"
android:background="#000"
android:textSize="@dimen/_20sp"
android:textColor="@color/white"
android:layout_width="match_parent"
android:layout_height="150dp"/>
<ImageView
android:alpha="0.05"
android:id="@+id/cover"
android:adjustViewBounds="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/desc"
android:singleLine="false"
android:lines="0"
android:background="#000"
android:textSize="@dimen/_20sp"
android:textColor="@color/white"
android:layout_width="match_parent"
android:layout_height="150dp"/>
<ImageView
android:id="@+id/screen"
android:alpha="0.05"
android:adjustViewBounds="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/link"
android:singleLine="false"
android:lines="0"
android:background="#000"
android:textSize="@dimen/_20sp"
android:textColor="@color/white"
android:layout_width="match_parent"
android:layout_height="150dp"/>
</LinearLayout>
</ScrollView>
</FrameLayout>
</layout>