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

View File

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

View File

@ -1,4 +1,4 @@
const list = ["?page=2","?page=3","?page=4","?page=5","?page=6"];
const port = browser.runtime.connectNative("browser"); const port = browser.runtime.connectNative("browser");
port.onMessage.addListener(response => { port.onMessage.addListener(response => {
var type= response["type"]; 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) },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.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.OneTimeWorkRequest 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.Constants.Companion.widgetHostId
import bums.lunatic.launcher.helpers.HeadsetActionButtonReceiver import bums.lunatic.launcher.helpers.HeadsetActionButtonReceiver
import bums.lunatic.launcher.helpers.PrefLong 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.home.RssViewBuilder
import bums.lunatic.launcher.model.RssData import bums.lunatic.launcher.model.RssData
import bums.lunatic.launcher.model.RssDataType import bums.lunatic.launcher.model.RssDataType
@ -612,7 +613,7 @@ internal class LauncherActivity : CommonActivity() {
when(id) { when(id) {
R.id.feeds -> { R.id.feeds -> {
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, LauncherHome()) .replace(R.id.fragment_container, RssHome())
.commit() .commit()
} }
R.id.books ->{ R.id.books ->{
@ -728,8 +729,12 @@ internal class LauncherActivity : CommonActivity() {
override fun handleOnBackPressed() { override fun handleOnBackPressed() {
val currentFragment = supportFragmentManager.findFragmentById(R.id.fragment_container) val currentFragment = supportFragmentManager.findFragmentById(R.id.fragment_container)
when(currentFragment) { when(currentFragment) {
is LauncherHome ->{ is RssHome ->{
currentFragment.doNextPage() 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 androidx.annotation.Nullable;
//// ////
////import java.util.HashMap; ////import java.util.HashMap;
//// ////
public class ExtHahMap extends HashMap<String,Object> { //
@Nullable //import androidx.annotation.Nullable;
@Override //
public Object put(String key, Object value) { //import java.util.HashMap;
if (value instanceof String) { //
if (((String) value).length() > 0) { //////package bums.lunatic.launcher.home;
//////
} else { //////import androidx.annotation.Nullable;
//////
} //////import java.util.HashMap;
}else { //////
return super.put(key, value); //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 package bums.lunatic.launcher.home
import android.app.DownloadManager
import android.content.Context import android.content.Context
import android.content.Intent 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.net.Uri
import android.os.Environment
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.os.Message import android.os.Message
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Base64
import android.util.Log import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
import android.view.KeyEvent.ACTION_UP 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_BUTTON_Y
import android.view.KeyEvent.KEYCODE_DPAD_DOWN import android.view.KeyEvent.KEYCODE_DPAD_DOWN
import android.view.KeyEvent.KEYCODE_DPAD_UP import android.view.KeyEvent.KEYCODE_DPAD_UP
import android.view.View
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import bums.lunatic.launcher.LauncherActivity.Companion.getRuntime 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.BWebview
import bums.lunatic.launcher.tokiz.view.JxEvent import bums.lunatic.launcher.tokiz.view.JxEvent
import bums.lunatic.launcher.utils.Blog import bums.lunatic.launcher.utils.Blog
import bums.lunatic.launcher.utils.afterDay
import bums.lunatic.launcher.workers.WorkersDb
import com.google.gson.Gson import com.google.gson.Gson
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
import org.jsoup.Jsoup
import org.mozilla.gecko.util.ThreadUtils import org.mozilla.gecko.util.ThreadUtils
import org.mozilla.geckoview.ExperimentDelegate import org.mozilla.geckoview.ExperimentDelegate
import org.mozilla.geckoview.GeckoResult 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.WebExtension.PortDelegate
import org.mozilla.geckoview.WebExtensionController.AddonManagerDelegate import org.mozilla.geckoview.WebExtensionController.AddonManagerDelegate
import org.mozilla.geckoview.WebRequestError import org.mozilla.geckoview.WebRequestError
import java.io.File
import java.text.SimpleDateFormat
class GeckoWeb : BWebview { class GeckoWeb : BWebview {
constructor(context: Context?) : super(context) { constructor(context: Context?) : super(context) {
buildWeb() 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) { constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
buildWeb() buildWeb()
@ -54,6 +73,7 @@ class GeckoWeb : BWebview {
val mPortNam = "browser" val mPortNam = "browser"
val extPath = "resource://android/assets/extensions/my_extension/" val extPath = "resource://android/assets/extensions/my_extension/"
val extId = "messaging@booktoki468.com" val extId = "messaging@booktoki468.com"
private fun buildWeb() { private fun buildWeb() {
getRuntime()?.let { getRuntime()?.let {
val session: GeckoSession = GeckoSession() val session: GeckoSession = GeckoSession()
@ -87,7 +107,7 @@ class GeckoWeb : BWebview {
var lastedUrl: String? = null var lastedUrl: String? = null
var canGoBack: Boolean? = null var canGoBack: Boolean? = null
var mPort: WebExtension.Port? = null var mPort: WebExtension.Port? = null
var mCaache : WebExtension.Port? = null
object WebExtensionInfo { object WebExtensionInfo {
val mPortNam = "browser" val mPortNam = "browser"
val extPath = "resource://android/assets/extensions/my_extension/" val extPath = "resource://android/assets/extensions/my_extension/"
@ -238,6 +258,32 @@ class GeckoWeb : BWebview {
return super.onRecordMalformedConfigurationEvent(feature, part) 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 { val contentDelegate = object : GeckoSession.ContentDelegate {
override fun onTitleChange( override fun onTitleChange(
session: GeckoSession, session: GeckoSession,
@ -260,6 +306,21 @@ class GeckoWeb : BWebview {
super.onFirstContentfulPaint(session) 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 { val progressDelegate = object : GeckoSession.ProgressDelegate {
override fun onSecurityChange( override fun onSecurityChange(
@ -284,15 +345,20 @@ class GeckoWeb : BWebview {
} }
override fun onPageStart(session: GeckoSession, url: String) { override fun onPageStart(session: GeckoSession, url: String) {
super.onPageStart(session, url) super.onPageStart(session, url)
if (url?.contains("reddit.app.link") == true) { if (url.contains(getFilterF()) && url.contains("jpg") == false) {
session.stop() this@GeckoWeb.visibility = View.INVISIBLE
Uri.parse(url)?.let { uri ->
context.startActivity(Intent().apply {
action = Intent.ACTION_VIEW
data = uri
})
}
} }
// 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) { override fun onPageStop(session: GeckoSession, success: Boolean) {
@ -305,6 +371,7 @@ class GeckoWeb : BWebview {
} }
} }
} }
} }
@ -328,7 +395,18 @@ class GeckoWeb : BWebview {
): GeckoResult<GeckoSession>? { ): GeckoResult<GeckoSession>? {
Blog.LOGE("GeckoView", "onNewSession: $session from WebExtension") 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) return super.onNewSession(session, uri)
} }
@ -343,6 +421,9 @@ class GeckoWeb : BWebview {
Blog.LOGE("GeckoView", "현재 session: $session") Blog.LOGE("GeckoView", "현재 session: $session")
url?.let { url -> url?.let { url ->
if (url?.contains(getFilterF()) == true && url.contains("jpg") == false) {
this@GeckoWeb.visibility = View.INVISIBLE
}
if (url.split("//").size > 1) { if (url.split("//").size > 1) {
url.replace("//", "/").replace("https:/", "https://").let { url.replace("//", "/").replace("https:/", "https://").let {
Blog.LOGE("url >> ${url} , it >>> ${it}") Blog.LOGE("url >> ${url} , it >>> ${it}")
@ -371,23 +452,129 @@ class GeckoWeb : BWebview {
message: Any, port: WebExtension.Port message: Any, port: WebExtension.Port
) { ) {
Blog.LOGE("PortDelegate", "Received message from extension: $message") 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) { override fun onDisconnect(port: WebExtension.Port) {
// This port is not usable anymore. // This port is not usable anymore.
if (port === mPort) { if (port === mCaache) {
mPort = null 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 = val messageDelegate: MessageDelegate =
object : MessageDelegate { object : MessageDelegate {
override fun onConnect(port: WebExtension.Port) { override fun onConnect(port: WebExtension.Port) {
mPort = port Blog.LOGE("onConnect port >>> ${port.name}")
mPort!!.setDelegate(portDelegate) if (port != null) {
mPort = port
mPort!!.setDelegate(portDelegate)
}
} }
override fun onMessage( override fun onMessage(
@ -401,11 +588,8 @@ class GeckoWeb : BWebview {
) )
return super.onMessage(nativeApp, message, sender) return super.onMessage(nativeApp, message, sender)
} }
} }
fun onStart() { fun onStart() {
} }

View File

@ -19,8 +19,9 @@
package bums.lunatic.launcher.home package bums.lunatic.launcher.home
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
@ -29,9 +30,11 @@ import android.view.LayoutInflater
import android.view.PointerIcon import android.view.PointerIcon
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.Toast import android.widget.Toast
import androidx.annotation.NonNull 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.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.DividerItemDecoration 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.RssData
import bums.lunatic.launcher.model.RssDataType import bums.lunatic.launcher.model.RssDataType
import bums.lunatic.launcher.model.WeatherForcast import bums.lunatic.launcher.model.WeatherForcast
import bums.lunatic.launcher.openClient
import bums.lunatic.launcher.openReddit import bums.lunatic.launcher.openReddit
import bums.lunatic.launcher.openYouTube import bums.lunatic.launcher.openYouTube
import bums.lunatic.launcher.tokiz.view.JxEvent 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.beforeDay
import bums.lunatic.launcher.utils.beforeOneDay import bums.lunatic.launcher.utils.beforeOneDay
import bums.lunatic.launcher.workers.WorkersDb import bums.lunatic.launcher.workers.WorkersDb
import com.bumptech.glide.Glide
import com.google.android.material.imageview.ShapeableImageView import com.google.android.material.imageview.ShapeableImageView
import io.realm.kotlin.UpdatePolicy import io.realm.kotlin.UpdatePolicy
import io.realm.kotlin.ext.query import io.realm.kotlin.ext.query
import io.realm.kotlin.notifications.InitialResults
import io.realm.kotlin.notifications.ResultsChange import io.realm.kotlin.notifications.ResultsChange
import io.realm.kotlin.notifications.UpdatedResults
import io.realm.kotlin.query.RealmQuery import io.realm.kotlin.query.RealmQuery
import io.realm.kotlin.query.RealmResults import io.realm.kotlin.query.RealmResults
import io.realm.kotlin.query.Sort import io.realm.kotlin.query.Sort
@ -70,7 +75,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
internal class LauncherHome : Fragment() { internal class RssHome : Fragment() {
lateinit var binding: LauncherHomeBinding lateinit var binding: LauncherHomeBinding
private lateinit var fragManager: FragmentManager private lateinit var fragManager: FragmentManager
@ -78,18 +83,17 @@ internal class LauncherHome : Fragment() {
private var shouldResume = true private var shouldResume = true
companion object { companion object {
var home: LauncherHome? = null var home: RssHome? = null
var lastedFinishedPageUrl: String = "" var lastedFinishedPageUrl: String = ""
} }
val UPDATE_DELAY = 5L
val commandHandler = Handler(Looper.getMainLooper()) val commandHandler = Handler(Looper.getMainLooper())
val infoUpdate = Runnable { chooseAdpater() } val infoUpdate = Runnable { chooseAdpater() }
var result: RealmResults<WeatherForcast>? = null var result: RealmResults<WeatherForcast>? = null
val nomoreShowCount = 5 val nomoreShowCount = 5
fun rssStateVote() = (lasted?.filter { it.vote == true }?.size ?: -1) == (lasted?.size ?: 0) 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 infosJob: Job? = null
var rssId = "" var rssId = ""
lateinit var mRssAdapter: RssItemAdapter lateinit var mRssAdapter: RssItemAdapter
@ -105,7 +109,9 @@ internal class LauncherHome : Fragment() {
gestureDistance: Double gestureDistance: Double
): Boolean { ): Boolean {
Blog.LOGE("") Blog.LOGE("")
if (imageView){
openGecko("")
}
return true return true
} }
@ -115,6 +121,9 @@ internal class LauncherHome : Fragment() {
gestureDuration: Long, gestureDuration: Long,
gestureDistance: Double gestureDistance: Double
): Boolean { ): Boolean {
if (imageView){
openGecko("")
}
Blog.LOGE("") Blog.LOGE("")
return true return true
} }
@ -202,19 +211,27 @@ internal class LauncherHome : Fragment() {
} }
} }
when(rss.category()) { when(rss.category()) {
//RssDataType.GURU,RssDataType.MOST, RssDataType.REDDIT_NSFW,RssDataType.PRIVATE -> {
RssDataType.REDDIT_NSFW -> {
v.findViewById<ShapeableImageView>(R.id.circle_preview)?.let { v.findViewById<ShapeableImageView>(R.id.circle_preview)?.let {
if (it.visibility == View.GONE) { if (RssDataType.PRIVATE.equals(rss.category()) && imageView) {
it.visibility = View.VISIBLE openGecko("")
it.postDelayed({
it.visibility = View.GONE
}, 2000L)
} else { } else {
if (RssDataType.REDDIT_NSFW.equals(rss.category())) { if (it.visibility == View.GONE) {
openReddit(rss.originPage()) it.visibility = View.VISIBLE
it.postDelayed({
it.visibility = View.GONE
}, 2000L)
} else { } 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) { fun openGecko(originPage: String) {
rssId = originPage if (!imageView) {
targetList.clear() rssId = originPage
targetList.clear()
var setString = hashSetOf<String>() var setString = hashSetOf<String>()
setString.addAll(rssList) setString.addAll(rssList)
setString.removeAll { it.equals(rssId) } setString.removeAll { it.equals(rssId) }
targetList.addAll(setString) targetList.addAll(setString)
binding.geckoWeb.loadUrl(rssId) 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") @SuppressLint("ClickableViewAccessibility")
@ -272,6 +312,54 @@ internal class LauncherHome : Fragment() {
return@setOnTouchListener false 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() queryInfos()
binding.geckoWeb.progress = binding.progressBar binding.geckoWeb.progress = binding.progressBar
binding.geckoWeb.jxInteface = { jxEvent -> binding.geckoWeb.jxInteface = { jxEvent ->
@ -289,20 +377,19 @@ internal class LauncherHome : Fragment() {
} }
} }
} }
val nullCursor = PointerIcon.getSystemIcon(requireContext(), PointerIcon.TYPE_NULL)
val nullCursor = PointerIcon.getSystemIcon(context!!, PointerIcon.TYPE_NULL)
binding.root.setPointerIcon(nullCursor) 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 return binding.root
} }
fun vote(){ fun vote(){
Blog.LOGE("Arrow Center Click") Blog.LOGE("Arrow Center Click")
WorkersDb.getRealm().apply { WorkersDb.getRealm().apply {
writeBlocking { 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) { if (result.size > 0) {
result.forEach { it.vote = true } result.forEach { it.vote = true }
} }
@ -316,10 +403,10 @@ internal class LauncherHome : Fragment() {
fun doNextPage() { fun doNextPage() {
WorkersDb.getRealm().apply { WorkersDb.getRealm().apply {
writeBlocking { 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) { if (result.size > 0) {
result.forEach { result.forEach {
it.read = it.read + nomoreShowCount it.read = it.read + nomoreShowCount
} }
} }
} }
@ -357,30 +444,48 @@ internal class LauncherHome : Fragment() {
mRssDataResult?.asFlow()?.let { flow -> mRssDataResult?.asFlow()?.let { flow ->
infosJob = CoroutineScope(Dispatchers.IO).launch { infosJob = CoroutineScope(Dispatchers.IO).launch {
flow.collect { changes: ResultsChange<RssData> -> flow.collect { changes: ResultsChange<RssData> ->
commandHandler.removeCallbacks(infoUpdate) when(changes) {
WorkersDb.getRealm().apply { is InitialResults -> {
lasted = copyFromRealm(changes.list) 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() infosJob?.start()
} }
} }
fun queryPrevate() {
imageView = true
beforeQuery()
updateQuery(WorkersDb.getPrivate())
}
fun queryVotes() { fun queryVotes() {
imageView = false
beforeQuery() beforeQuery()
updateQuery(WorkersDb.getVotedRss()) updateQuery(WorkersDb.getVotedRss())
} }
var imageView = false
fun queryInfos( fun queryInfos(
//RssDataType.GURU, RssDataType.MOST, filter: Collection<RssDataType>? = arrayListOf(RssDataType.REDDIT_NSFW,RssDataType.PRIVATE), noLimit: Boolean = false
filter: Collection<RssDataType>? = arrayListOf(RssDataType.REDDIT_NSFW), noLimit: Boolean = false
) { ) {
imageView = false
beforeQuery() beforeQuery()
var rQ = WorkersDb.getRealm().query<RssData>().query("read < $0", nomoreShowCount).distinct("originPage", "title") var rQ = WorkersDb.getRealm().query<RssData>().query("read < $0", nomoreShowCount).distinct("originPage", "title")
if (!noLimit) rQ.query("pubDate > $0", beforeOneDay()) 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) updateQuery(rQ)
} }
@ -401,16 +506,16 @@ internal class LauncherHome : Fragment() {
Blog.LOGE("onViewCreated()") Blog.LOGE("onViewCreated()")
fragManager.addOnBackStackChangedListener { // fragManager.addOnBackStackChangedListener {
Blog.LOGE("addOnBackStackChangedListener()") // Blog.LOGE("addOnBackStackChangedListener()")
shouldResume = if (fragManager.backStackEntryCount == 0) { // shouldResume = if (fragManager.backStackEntryCount == 0) {
binding.root.visibility = View.VISIBLE // binding.root.visibility = View.VISIBLE
true // true
} else { // } else {
binding.root.visibility = View.GONE // binding.root.visibility = View.GONE
false // false
} // }
} // }
enableSwipeToDeleteAndUndo() enableSwipeToDeleteAndUndo()
} }
@ -475,8 +580,6 @@ internal class LauncherHome : Fragment() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
val nullCursor = PointerIcon.getSystemIcon(context!!, PointerIcon.TYPE_NULL)
binding.root.setPointerIcon(nullCursor)
if (shouldResume) { 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, v: View,
event: MotionEvent event: MotionEvent
): Boolean { ): Boolean {
Blog.LOGE("event.device.name >>> ${event.device.name}") if (event.device != null && event.device.name != null && (event.device.name?.contains("JX-12",true) == true|| event.device.name?.equals("J06",true) == true)) {
if (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) return true//mSimpleFingerGestures.onTouch(v,event)
} else { } else {
return false return false

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
package bums.lunatic.launcher.tokiz.data.model package bums.lunatic.launcher.tokiz.data.model
import bums.lunatic.launcher.model.RssData
import io.realm.kotlin.ext.realmListOf import io.realm.kotlin.ext.realmListOf
import io.realm.kotlin.types.RealmList import io.realm.kotlin.types.RealmList
import io.realm.kotlin.types.RealmObject import io.realm.kotlin.types.RealmObject
@ -10,6 +11,8 @@ class PortMessage {
var bookInfos : PageInfosJ? = null var bookInfos : PageInfosJ? = null
var book : BookContents? = null var book : BookContents? = null
var msg : String? = null var msg : String? = null
var contents : String? = null
var privates : ArrayList<RssData>? = null
} }
class BookContents { class BookContents {
var chapterTitle : String? = null 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.Blog
import bums.lunatic.launcher.utils.SimpleFingerGestures import bums.lunatic.launcher.utils.SimpleFingerGestures
import org.mozilla.geckoview.GeckoView import org.mozilla.geckoview.GeckoView
import java.util.Base64
enum class JxEvent { enum class JxEvent {
SCROLL_UP, SCROLL_UP,
@ -160,14 +161,19 @@ open class BWebview : GeckoView {
var lastDomain : String = "" var lastDomain : String = ""
fun loadUrl(url: 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) { if (this.isVisible == false) {
this.visibility = View.VISIBLE this.visibility = View.VISIBLE
} }
Blog.LOGE("url >>>> ${url}") Blog.LOGE("nUrl >>>> ${nUrl}")
var nUrl = url
if (url.startsWith("http") == false) {
nUrl = lastDomain
}
nUrl?.let { url -> nUrl?.let { url ->
if (url.split("//").size > 1) { if (url.split("//").size > 1) {
url.replace("//","/").replace("https:/","https://").let { url.replace("//","/").replace("https:/","https://").let {

View File

@ -8,6 +8,14 @@ import android.provider.ContactsContract.PhoneLookup
import java.util.Calendar import java.util.Calendar
import java.util.Date 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 { fun before30Min(date: Date): Long {
val cal: Calendar = Calendar.getInstance() val cal: Calendar = Calendar.getInstance()
cal.setTime(date) cal.setTime(date)

View File

@ -1,6 +1,6 @@
package bums.lunatic.launcher.utils 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.RssData
import bums.lunatic.launcher.model.RssDataType import bums.lunatic.launcher.model.RssDataType
import bums.lunatic.launcher.model.dateFormat 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.NotificationItem
import bums.lunatic.launcher.model.RssData import bums.lunatic.launcher.model.RssData
import bums.lunatic.launcher.model.RssDataInterface import bums.lunatic.launcher.model.RssDataInterface
import bums.lunatic.launcher.model.RssDataType
import bums.lunatic.launcher.model.TelegramBotUpdate import bums.lunatic.launcher.model.TelegramBotUpdate
import bums.lunatic.launcher.model.TelegramChat import bums.lunatic.launcher.model.TelegramChat
import bums.lunatic.launcher.model.TelegramData 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.JamoUtils
import bums.lunatic.launcher.utils.beforeDay import bums.lunatic.launcher.utils.beforeDay
import bums.lunatic.launcher.utils.beforeOneDay import bums.lunatic.launcher.utils.beforeOneDay
import com.google.gson.Gson
import io.realm.kotlin.Realm import io.realm.kotlin.Realm
import io.realm.kotlin.RealmConfiguration import io.realm.kotlin.RealmConfiguration
import io.realm.kotlin.UpdatePolicy import io.realm.kotlin.UpdatePolicy
@ -86,9 +88,21 @@ object WorkersDb {
getRealm().apply { getRealm().apply {
this.writeBlocking { this.writeBlocking {
try { try {
if (query<RssData>("originPage == $0", rssData.originPage).find().isEmpty()) { if (rssData.category().equals(RssDataType.PRIVATE)) {
this.copyToRealm(rssData, UpdatePolicy.ERROR) 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) { } 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 getVotedRss() = getRealm().query<RssData>().query("vote == $0", true).distinct("originPage", "title")
fun getDeleteQuery( ) : RealmQuery<RssData>{ fun getDeleteQuery( ) : RealmQuery<RssData>{

View File

@ -1,8 +1,8 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:height="24dp" android:width="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24"
android:tint="?attr/colorControlNormal"> android:tint="@color/white">
<path <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"/> 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> </vector>

View File

@ -1,45 +1,146 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <layout xmlns:tools="http://schemas.android.com/tools">
xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@android:color/transparent" 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"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:overScrollMode="never"
android:padding="@dimen/default_padding" <ImageButton
android:scrollbars="none" app:layout_constraintTop_toTopOf="parent"
android:visibility="visible" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" android:id="@+id/test"
app:layout_constraintTop_toTopOf="parent" android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="parent" 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 <bums.lunatic.launcher.home.GeckoWeb
android:id="@+id/geckoWeb" android:id="@+id/geckoWeb"
android:visibility="gone" android:visibility="gone"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" app:layout_constraintBottom_toBottomOf="parent"
/> app:layout_constraintLeft_toLeftOf="parent"
<ProgressBar app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/geckoWeb" app:layout_constraintTop_toBottomOf="@id/bookmark"
app:layout_constraintLeft_toLeftOf="@id/geckoWeb" android:layout_height="0dp"
app:layout_constraintRight_toRightOf="@id/geckoWeb" />
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal" <include layout="@layout/layout_rss_summary"
android:layout_width="0dp" android:id="@+id/layout_rss_summary"
android:layout_height="4dp" android:layout_width="match_parent"
android:max="100" android:visibility="gone"
android:progress="0" app:layout_constraintBottom_toBottomOf="parent"
android:visibility="visible" app:layout_constraintLeft_toLeftOf="parent"
android:indeterminate="false"/> app:layout_constraintRight_toRightOf="parent"
</androidx.constraintlayout.widget.ConstraintLayout> 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>