diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 38a19d85..b4c9a6a1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -54,6 +54,9 @@ tools:ignore="ScopedStorage" /> + + + @@ -101,6 +104,10 @@ + 1) { + document.querySelectorAll('[class^="w-full flex"]').forEach(function (e) { + + + var description = "" + if(e.querySelector('[class^="pb-2 w-100 text-gray-500"]')){ + description = e.querySelector('[class^="pb-2 w-100 text-gray-500"]').textContent.replace(/\s+/g, ' ').trim(); + } + console.log(description) + }) +} \ No newline at end of file diff --git a/app/src/main/assets/extensions/my_extension/fdfdfdf.html b/app/src/main/assets/extensions/my_extension/fdfdfdf.html index 7c2a90fc..e69de29b 100644 --- a/app/src/main/assets/extensions/my_extension/fdfdfdf.html +++ b/app/src/main/assets/extensions/my_extension/fdfdfdf.html @@ -1,93 +0,0 @@ -video-item ::>
- - - - - - -
IPZZ-574 I Didn't Know About My Wife's Twisted Sexual Habits... My Beloved Wife, Who Is Usually Kind, Was A Masochistic Slave Trained By A Sadistic Neighbor. Kana Momonogi
-
- -
- 08/07/2025 | 4260 | 2602 -
- -
-
- 6 -
- -
- Cuckold -
- -
- SM -
-
- Solowork -
- -
-
- -
- -
-
- - - - 2025-07-23 11:22:20.062 26023-26023 Lunatic bums.lunatic.launcher E - - - - - - - - - - - - - - - 2025-07-23 11:22:20.062 26023-26023 Lunatic bums.lunatic.launcher E - - - - - - - - - -
Download Magnet 3.4gbS: 41L: 667 23/07/25Decen
Download Magnet 1.3gbS: 30L: 6 22/07/25
Download Magnet 5.3gbS: 60L: 23 22/07/25
-
-
-
\ No newline at end of file diff --git a/app/src/main/assets/extensions/my_extension/messaging.js b/app/src/main/assets/extensions/my_extension/messaging.js index a8e26c16..ec87b460 100644 --- a/app/src/main/assets/extensions/my_extension/messaging.js +++ b/app/src/main/assets/extensions/my_extension/messaging.js @@ -291,6 +291,180 @@ if (port) { e.remove() ) } + if (document.querySelectorAll('[class^="code-block"]')) { + document.querySelectorAll('[class^="code-block"]').forEach(e => e.remove()) + } + if (document.querySelectorAll('.ad-template')) { + document.querySelectorAll('.ad-template').forEach(function (e) { + e.remove() + }) + } + if (document.querySelectorAll('[class^="popupBanner_w popupOpen"]')) { + document.querySelectorAll('[class^="popupBanner_w popupOpen"]').forEach(function (e) { + e.remove() + }) + } + + if (document.querySelectorAll('iframe')) { + document.querySelectorAll('iframe').forEach(function (e) { + if (e.getAttribute("src") != null && (e.getAttribute("src").search("ads") > -1 || e.getAttribute("src").search("coupang") > -1)) { + e.remove() + } + }) + } + if (location.href.search("x.com") > -1) { + var mainClass = document.querySelector('section').className + + document.querySelector('section').querySelectorAll('div[class="'+ mainClass +'"]').forEach(function (e) { + if (e.hasAttribute("data-testid") && e.querySelector('video')) { + e.querySelectorAll('[poster]').forEach(function (e) { + + }) + console.log(e.innerHTML) + } + }) + + } + + if (document.querySelectorAll('[data-banner^="coupang-"]')) { + document.querySelectorAll('[data-banner^="coupang-"]').forEach(e => e.remove()) + } + + if (document.querySelectorAll('[id^="mobonDivBanner"]')) { + document.querySelectorAll('[id^="mobonDivBanner"]').forEach(e => e.remove()) + } + if (document.querySelectorAll('[id^="pnlTopAd"]')) { + document.querySelectorAll('[id^="pnlTopAd"]').forEach(e => e.remove()) + } + if (document.querySelectorAll('[class^="ad-tag"]')) { + document.querySelectorAll('[class^="ad-tag"]').forEach(e => e.remove()) + } + if (document.querySelectorAll('[class^="science-banner-area"]')) { + document.querySelectorAll('[class^="science-banner-area"]').forEach(e=> e.remove()) + } + if (location.href.search("torrentzota") > -1 && document.querySelectorAll('a')) { + document.querySelectorAll('a').forEach(function (e) { + if (e.getAttribute('href') != null && e.getAttribute('href').startsWith("/adver-")) { + e.remove() + } + } + ) + if(document.querySelectorAll('[class^="py-4 flex flex-row border-b topic-item"]').length > 1) { + var datas = [] + document.querySelectorAll('[class^="py-4 flex flex-row border-b topic-item"]').forEach(function (e) { + var title = "" + var link = "" + e.querySelectorAll("a").forEach(function (e){ + if(e.getAttribute("class") === "item-link"){ + title = e.getAttribute('title') + } + if(e.getAttribute("class") === "item-link"){ + link = e.getAttribute('href') + if (link.length > 0) { + link = location.protocol + '//' + location.hostname + link + } + } + }) + var description = e.querySelector('.flex-none w-16 text-center').textContent + e.querySelector('.badge badge-third w-auto px-1 flex-none mr-2 float-left').textContent + var md = e.querySelector(".flex-none w-14 text-center hidden md:block").textContent + + let todayYear = new Date().getFullYear(); + let now = new Date(); + let hh = String(now.getHours()).padStart(2, '0'); // 시 (2자리) + let mm = String(now.getMinutes()).padStart(2, '0'); // 분 (2자리) + let ss = String(now.getSeconds()).padStart(2, '0'); // 초 (2자리) + + let time = `${hh}:${mm}:${ss}`; + let combinedStr = todayYear + "-" + md + " " + time; + let dateObj = new Date(combinedStr); + + datas.push({ + "title" : title, + "description" : description, + "originPage" : link, + "pubDate" : dateObj.getTime(), + "category" : "TORRENT" + }); + }) + if(datas.length > 0) { + sendMessage( + { + type: "PRIVATES", + privates: datas, + currentPage : location.href + } + ); + } + } + if (document.querySelectorAll('[class^="w-full flex"]').length > 1) { + var datas = [] + document.querySelectorAll('[class^="w-full flex"]').forEach(function (e) { + var thumbnail = "" + e.querySelectorAll("img").forEach(function (e){ + if(e.getAttribute("class") === "border"){ + thumbnail = e.getAttribute('src') + } + }) + + var title = "" + var link = "" + e.querySelectorAll("a").forEach(function (e){ + if(e.getAttribute("class") === "item-link"){ + title = e.getAttribute('title') + } + if(e.getAttribute("class") === "item-link"){ + link = e.getAttribute('href') + if (link.length > 0) { + link = location.protocol + '//' + location.hostname + link + } + } + }) + + + var description = "" + var time = "" + var md = "" + if(e.querySelector('[class^="pb-2 w-100 text-gray-500"]')){ + description = e.querySelector('[class^="pb-2 w-100 text-gray-500"]').textContent.replace(/\s+/g, ' ').trim(); + md = description.split(" ")[1].trim(); + time = description.split(" ")[2].trim(); + description = description.split(" ")[0].trim(); + } + console.log(link) + console.log(thumbnail) + console.log(title) + console.log(description) + console.log(md) + console.log(time) + + + + let todayYear = new Date().getFullYear(); + let combinedStr = todayYear + "-" + md + " " + time; + let dateObj = new Date(combinedStr); + + datas.push({ + "title" : title, + "description" : description, + "originPage" : link, + "thumbnail" : thumbnail, + "pubDate" : dateObj.getTime(), + "category" : "TORRENT" + }); + }) + if(datas.length > 0) { + sendMessage( + { + type: "PRIVATES", + privates: datas, + currentPage : location.href + } + ); + } + + } + + } if (document.querySelectorAll('[class^="col-md-4 mb-4 video-item"]').length > 1) { var datas = [] @@ -440,18 +614,4 @@ function gotoNext() { } catch (e) { } -} -// -// if (document.readyState === "complete" || document.readyState === "interactive") { -// reportCookiesToNative(); -// } else { -// window.addEventListener("DOMContentLoaded", reportCookiesToNative); -// } -// function reportCookiesToNative() { -// // 모든 쿠키를 읽어 네이티브로 전달 -// sendMessage({type: "MSG", cookies: "DOMContentLoaded try to cookies send"}); -// window.cookies.getAll({url: window.location.href}).then((cookies) => { -// // 쿠키 데이터를 네이티브(Android)로 전송 -// sendMessage({type: "Cookies", cookies: cookies}); -// }); -// } \ No newline at end of file +} \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/LauncherActivity.kt b/app/src/main/kotlin/bums/lunatic/launcher/LauncherActivity.kt index 1b9cbc09..4465807f 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/LauncherActivity.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/LauncherActivity.kt @@ -46,6 +46,7 @@ import android.view.WindowInsets import android.view.WindowManager import androidx.activity.OnBackPressedCallback import androidx.annotation.RequiresApi +import androidx.core.content.ContextCompat import androidx.core.net.toUri import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.ViewCompat @@ -62,6 +63,7 @@ import bums.lunatic.launcher.tokiz.Novels import bums.lunatic.launcher.common.CommonActivity import bums.lunatic.launcher.databinding.LauncherActivityBinding import bums.lunatic.launcher.feeds.WidgetHost +import bums.lunatic.launcher.helpers.BluetoothManager import bums.lunatic.launcher.helpers.Constants.Companion.KEY_STATUS_BAR import bums.lunatic.launcher.helpers.Constants.Companion.PREFS_SETTINGS import bums.lunatic.launcher.helpers.Constants.Companion.widgetHostId @@ -72,7 +74,9 @@ import bums.lunatic.launcher.home.RssViewBuilder import bums.lunatic.launcher.model.RssData import bums.lunatic.launcher.model.RssDataType import bums.lunatic.launcher.tokiz.Comics +import bums.lunatic.launcher.tokiz.Twitter import bums.lunatic.launcher.tokiz.Webtoons +import bums.lunatic.launcher.tokiz.Zota import bums.lunatic.launcher.utils.Blog import bums.lunatic.launcher.utils.FeedParseManager import bums.lunatic.launcher.utils.getJ @@ -115,6 +119,7 @@ import java.util.Calendar import java.util.Date import java.util.concurrent.Executors import java.util.concurrent.TimeUnit +import kotlin.jvm.java internal class LauncherActivity : CommonActivity() { @@ -130,41 +135,40 @@ internal class LauncherActivity : CommonActivity() { return sRuntime } - private var mWorkManager: WorkManager? = null + var isOpendFold = false val qDayPeriod = 60L * 8L @JvmStatic var lActivity: LauncherActivity? = null - @JvmStatic var appWidgetManager: AppWidgetManager? = null - @JvmStatic var appWidgetHost: WidgetHost? = null - fun refreshDeviceData() - { - mWorkManager?.cancelAllWorkByTag(SMS_WORK_TAG) - mWorkManager?.enqueueUniquePeriodicWork( - SMS_WORK_TAG, - ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - PeriodicWorkRequestBuilder(PrefLong.longTimePeriod.get(), TimeUnit.MINUTES) - .addTag(SMS_WORK_TAG) - .build()) - mWorkManager?.cancelAllWorkByTag(RecentCallGetter.TAG) - mWorkManager?.enqueueUniquePeriodicWork( - RecentCallGetter.TAG, - ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - PeriodicWorkRequestBuilder(PrefLong.longTimePeriod.get(), TimeUnit.MINUTES) - .addTag(RecentCallGetter.TAG) - .build()) - - mWorkManager?.cancelAllWorkByTag(ContactInfoGetter.TAG) - mWorkManager?.enqueueUniquePeriodicWork( - ContactInfoGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - PeriodicWorkRequestBuilder(12, TimeUnit.HOURS) - .addTag(ContactInfoGetter.TAG) - .build()) - - mWorkManager?.enqueue(OneTimeWorkRequest.from(AppInfoGetter::class.java)) - - } +// fun refreshDeviceData() +// { +// +// mWorkManager?.cancelAllWorkByTag(SMS_WORK_TAG) +// mWorkManager?.enqueueUniquePeriodicWork( +// SMS_WORK_TAG, +// ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, +// PeriodicWorkRequestBuilder(PrefLong.longTimePeriod.get(), TimeUnit.MINUTES) +// .addTag(SMS_WORK_TAG) +// .build()) +// mWorkManager?.cancelAllWorkByTag(RecentCallGetter.TAG) +// mWorkManager?.enqueueUniquePeriodicWork( +// RecentCallGetter.TAG, +// ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, +// PeriodicWorkRequestBuilder(PrefLong.longTimePeriod.get(), TimeUnit.MINUTES) +// .addTag(RecentCallGetter.TAG) +// .build()) +// +// mWorkManager?.cancelAllWorkByTag(ContactInfoGetter.TAG) +// mWorkManager?.enqueueUniquePeriodicWork( +// ContactInfoGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, +// PeriodicWorkRequestBuilder(12, TimeUnit.HOURS) +// .addTag(ContactInfoGetter.TAG) +// .build()) +// +// mWorkManager?.enqueue(OneTimeWorkRequest.from(AppInfoGetter::class.java)) +// +// } // fun runWeatherGetter() { // Executors.newSingleThreadScheduledExecutor().schedule({ @@ -172,92 +176,14 @@ internal class LauncherActivity : CommonActivity() { // }, 200L, TimeUnit.MILLISECONDS) // } - fun getCal() { - Executors.newSingleThreadScheduledExecutor().schedule({ - mWorkManager?.enqueue(OneTimeWorkRequest.from(CalendarGetter::class.java)) - }, 5, TimeUnit.SECONDS) - } - - val defaultDelay = 10L - - fun refreshFeeds() { - mWorkManager?.cancelAllWork() - mWorkManager?.cancelAllWorkByTag(RuliWebGetter.TAG) - mWorkManager?.enqueueUniquePeriodicWork( - RuliWebGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - PeriodicWorkRequestBuilder(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES) - .addTag(RuliWebGetter.TAG) - .build()) - - mWorkManager?.cancelAllWorkByTag(FEDDS_WORK_TAG) - mWorkManager?.enqueueUniquePeriodicWork( - FEDDS_WORK_TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - PeriodicWorkRequestBuilder(PrefLong.shortTimePeriod.get(), TimeUnit.MINUTES) - .addTag(FEDDS_WORK_TAG) - .build()) - mWorkManager?.cancelAllWorkByTag(YT_WORK_TAG) - mWorkManager?.enqueueUniquePeriodicWork( - YT_WORK_TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - PeriodicWorkRequestBuilder(PrefLong.longTimePeriod.get(), TimeUnit.MINUTES) - .addTag(YT_WORK_TAG) - .build()) - mWorkManager?.cancelAllWorkByTag(REDDIT_WORK_TAG) - mWorkManager?.enqueueUniquePeriodicWork( - REDDIT_WORK_TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - PeriodicWorkRequestBuilder(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES) - .addTag(REDDIT_WORK_TAG) - .build()) - mWorkManager?.cancelAllWorkByTag(FM_WORK_TAG) - mWorkManager?.enqueueUniquePeriodicWork( - FM_WORK_TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - PeriodicWorkRequestBuilder(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES) - .addTag(FM_WORK_TAG) - .build()) - mWorkManager?.cancelAllWorkByTag(COMIC2_WORK_TAG) - mWorkManager?.enqueueUniquePeriodicWork( - COMIC2_WORK_TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - PeriodicWorkRequestBuilder(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES) - .addTag(COMIC2_WORK_TAG) - .build()) - mWorkManager?.cancelAllWorkByTag(ClienGetter.TAG) - mWorkManager?.enqueueUniquePeriodicWork( - ClienGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - PeriodicWorkRequestBuilder(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES) - .addTag(ClienGetter.TAG) - .build()) - mWorkManager?.cancelAllWorkByTag(DCGetter.TAG) - mWorkManager?.enqueueUniquePeriodicWork( - DCGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - PeriodicWorkRequestBuilder(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES) - .addTag(DCGetter.TAG) - .build()) - mWorkManager?.cancelAllWorkByTag(TheQooGetter.TAG) - mWorkManager?.enqueueUniquePeriodicWork( - TheQooGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - PeriodicWorkRequestBuilder(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES) - .addTag(TheQooGetter.TAG) - .build()) - mWorkManager?.cancelAllWorkByTag(ArcaGetter.TAG) - mWorkManager?.enqueueUniquePeriodicWork( - ArcaGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - PeriodicWorkRequestBuilder(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES) - .addTag(ArcaGetter.TAG) - .build()) - mWorkManager?.cancelAllWorkByTag(LocationGetter.TAG) - mWorkManager?.enqueueUniquePeriodicWork( - LocationGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - PeriodicWorkRequestBuilder(PrefLong.locationTimePeriod.get(), TimeUnit.MINUTES) - .addTag(LocationGetter.TAG) - .build()) - } +// fun getCal() { +// Executors.newSingleThreadScheduledExecutor().schedule({ +// mWorkManager?.enqueue(OneTimeWorkRequest.from(CalendarGetter::class.java)) +// }, 5, TimeUnit.SECONDS) +// } + - fun workmanager() : WorkManager? { - if (mWorkManager == null && lActivity != null) { - mWorkManager = WorkManager.getInstance(lActivity!!) - } - return mWorkManager - } } @@ -305,15 +231,6 @@ internal class LauncherActivity : CommonActivity() { when(ev.keyCode) { KEYCODE_BUTTON_Y->{ - CoroutineScope(Dispatchers.IO).launch { - String(Base64.getMimeDecoder().decode("aHR0cHM6Ly9qYXZtb3N0LnRvL2xhdGVzdC11cGRhdGVzCg==".toByteArray())).getJ().let { doc -> FeedParseManager.parse(doc){Blog.LOGE(it)} } - } - CoroutineScope(Dispatchers.IO).launch { - String.format(String(Base64.getMimeDecoder().decode("aHR0cHM6Ly9qYXZtb3N0LnRvL3NlYXJjaC9tb3ZpZS8lcw==".toByteArray())),"").getJ().let { doc -> FeedParseManager.parse(doc){Blog.LOGE(it)} } - } - CoroutineScope(Dispatchers.IO).launch { - String.format(String(Base64.getMimeDecoder().decode("aHR0cHM6Ly9rcjcxLnNvZ2lybC5zby8/cz0lcw==".toByteArray())),"").getJ().let { doc -> FeedParseManager.parse(doc){Blog.LOGE(it)} } - } } KEYCODE_BUTTON_X->{ @@ -332,11 +249,7 @@ internal class LauncherActivity : CommonActivity() { } } KEYCODE_BUTTON_B->{ -// RssViewBuilder(lActivity!!) -// .setRssId("https://jav.guru") -// .webViewJavaScriptEnabled(true) -// .showIconClose(true).showIconBack(false).showProgressBar(true).backPressToClose(false).webViewMixedContentMode(1) -// .show("https://jav.guru") + } KEYCODE_DPAD_DOWN->{ @@ -411,7 +324,6 @@ internal class LauncherActivity : CommonActivity() { } fun onClickCenterButton() { - WorkersDb.getRealm().apply { writeBlocking { delete( @@ -584,7 +496,7 @@ internal class LauncherActivity : CommonActivity() { installSplashScreen() getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); lActivity = this - mWorkManager = WorkManager.getInstance(this) + DynamicColors.applyToActivityIfAvailable(this) @@ -594,9 +506,6 @@ internal class LauncherActivity : CommonActivity() { binding = LauncherActivityBinding.inflate(layoutInflater) setContentView(binding.root) - appWidgetManager = AppWidgetManager.getInstance(applicationContext) - appWidgetHost = WidgetHost(applicationContext, widgetHostId) - appWidgetHost?.startListening() HeadsetActionButtonReceiver.register(this) @@ -617,6 +526,9 @@ internal class LauncherActivity : CommonActivity() { handleBackPress() updateLocationService() binding.feeds.isChecked = true + + val intent = Intent(this, BluetoothManager::class.java) + ContextCompat.startForegroundService(this, intent) } fun showContents(id : Int) { @@ -641,6 +553,16 @@ internal class LauncherActivity : CommonActivity() { .replace(R.id.fragment_container, Comics()) .commit() } + R.id.zota ->{ + supportFragmentManager.beginTransaction() + .replace(R.id.fragment_container, Zota()) + .commit() + } + R.id.twitter ->{ + supportFragmentManager.beginTransaction() + .replace(R.id.fragment_container, Twitter()) + .commit() + } else -> {} } } @@ -665,7 +587,6 @@ internal class LauncherActivity : CommonActivity() { override fun onDestroy() { - appWidgetHost?.stopListening() try { sRuntime?.shutdown() sRuntime = null @@ -689,8 +610,6 @@ internal class LauncherActivity : CommonActivity() { @RequiresApi(Build.VERSION_CODES.O_MR1) override fun onResume() { super.onResume() - refreshFeeds() -// blutoothManager?.getPairedDevices() Blog.LOGE("LauncherActivity onResume") } @@ -746,6 +665,9 @@ internal class LauncherActivity : CommonActivity() { currentFragment.doNextPage() } } + is Novels -> { + currentFragment.actionNextEvent(false) + } } } }) diff --git a/app/src/main/kotlin/bums/lunatic/launcher/helpers/BluetoothManager.kt b/app/src/main/kotlin/bums/lunatic/launcher/helpers/BluetoothManager.kt index 5afd84ee..d2d32723 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/helpers/BluetoothManager.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/helpers/BluetoothManager.kt @@ -2,6 +2,10 @@ package bums.lunatic.launcher.helpers import android.Manifest import android.annotation.SuppressLint +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.Service import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothDevice import android.content.BroadcastReceiver @@ -9,8 +13,33 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager +import android.os.Build +import android.os.IBinder import androidx.core.app.ActivityCompat +import androidx.core.app.NotificationCompat +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkManager +import bums.lunatic.launcher.LauncherActivity.Companion.lActivity +import bums.lunatic.launcher.R +import bums.lunatic.launcher.home.GeckoWeb import bums.lunatic.launcher.utils.Blog +import bums.lunatic.launcher.workers.ArcaGetter +import bums.lunatic.launcher.workers.ClienGetter +import bums.lunatic.launcher.workers.DCGetter +import bums.lunatic.launcher.workers.DotaxGetter +import bums.lunatic.launcher.workers.DotaxGetter.Companion.COMIC2_WORK_TAG +import bums.lunatic.launcher.workers.FmKoreaGetter +import bums.lunatic.launcher.workers.FmKoreaGetter.Companion.FM_WORK_TAG +import bums.lunatic.launcher.workers.LocationGetter +import bums.lunatic.launcher.workers.NewsFeedsGetter +import bums.lunatic.launcher.workers.NewsFeedsGetter.Companion.FEDDS_WORK_TAG +import bums.lunatic.launcher.workers.RedditGetter +import bums.lunatic.launcher.workers.RedditGetter.Companion.REDDIT_WORK_TAG +import bums.lunatic.launcher.workers.RuliWebGetter +import bums.lunatic.launcher.workers.TheQooGetter +import bums.lunatic.launcher.workers.YoutubeGetter +import bums.lunatic.launcher.workers.YoutubeGetter.Companion.YT_WORK_TAG import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -22,7 +51,7 @@ import okhttp3.ResponseBody import java.util.concurrent.TimeUnit -class BluetoothManager { +class BluetoothManager : Service() { enum class BLUETOOTH_STATE(val statestr: String) { ENABLED("enabledBlutooth"), @@ -30,50 +59,61 @@ class BluetoothManager { NOT_SUPPORT("notSupport") } - lateinit var context: Context var blueToothAdapter:BluetoothAdapter? = null + private var mWorkManager: WorkManager? = null - constructor(context: Context) { - this.context = context - } - constructor() { - - } - -// init { -// this.context = context -// } - - fun initBluetoothAdapter(){ - if ( blueToothAdapter == null ){ - val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as android.bluetooth.BluetoothManager - blueToothAdapter = bluetoothManager.getAdapter() + val NOTIF_ID = 830721 + override fun onCreate() { + super.onCreate() + Blog.LOGE("onCreate") + mWorkManager = WorkManager.getInstance(this) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + startForeground(NOTIF_ID, createNotification(this)) } + val filter = IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED) + registerReceiver(bluetoothreceiver, filter) + refreshFeeds() +// GeckoWeb(applicationContext).apply { +// loadUrl("aHR0cHM6Ly9pamF2dG9ycmVudC5jb20=") +// } } - fun register(){ - if (context == null) return - context.registerReceiver(bluetoothreceiver, addFilterAction()) + override fun onBind(intent: Intent?): IBinder? { + Blog.LOGE("intent >>> ${intent}") + return null + } + private val CHANNEL_ID = "ble_service_channel" + private val CHANNEL_NAME = "BLE 서비스" + + fun createNotification(context: Context): Notification { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + CHANNEL_ID, + CHANNEL_NAME, + NotificationManager.IMPORTANCE_HIGH // 중요도 낮게 (필요시 변경) + ) + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.createNotificationChannel(channel) + } + return NotificationCompat.Builder(context, CHANNEL_ID) + .setContentTitle("BLE 서비스") + .setContentText("실행중입니다.") + .setPriority(NotificationCompat.PRIORITY_MAX) + .setSmallIcon(R.drawable.ic_b) + .setOngoing(true) // 사용자가 알림을 스와이프로 지울 수 없게 만듦 + .build() } - fun unregister(){ - if (context == null) return - context.unregisterReceiver(bluetoothreceiver) - } //페어링된 디바이스 정보 가져오기 - fun getPairedDevices() { - val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as android.bluetooth.BluetoothManager + val bluetoothManager = applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as android.bluetooth.BluetoothManager blueToothAdapter = bluetoothManager.adapter - if (ActivityCompat.checkSelfPermission(context,Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) return + if (ActivityCompat.checkSelfPermission(applicationContext,Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) return var pairedDevices = blueToothAdapter?.bondedDevices if (pairedDevices?.size ?: 0 > 0) { pairedDevices?.forEach { i -> - //bondState : 12 (페어링 등록된 상태) - //bondState : 10 (페어링 등록 안됨) -// BLog.LOGE("getPairedDevices() / name : ${i.name}") -// BLog.LOGE("getPairedDevices() / bondState : ${i.bondState}") val isConnected = isConnected(i) if(PrefString.carName.get().length > 2 && i.name.equals(PrefString.carName.get()) && isConnected != PrefBoolean.isConnectedCar.get()) { PrefBoolean.isConnectedCar.set(isConnected) @@ -83,6 +123,84 @@ class BluetoothManager { } } + fun refreshFeeds() { + mWorkManager?.cancelAllWork() + mWorkManager?.cancelAllWorkByTag(RuliWebGetter.TAG) + mWorkManager?.enqueueUniquePeriodicWork( + RuliWebGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, + PeriodicWorkRequestBuilder(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES) + .addTag(RuliWebGetter.TAG) + .build()) + + mWorkManager?.cancelAllWorkByTag(FEDDS_WORK_TAG) + mWorkManager?.enqueueUniquePeriodicWork( + FEDDS_WORK_TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, + PeriodicWorkRequestBuilder(PrefLong.shortTimePeriod.get(), TimeUnit.MINUTES) + .addTag(FEDDS_WORK_TAG) + .build()) + mWorkManager?.cancelAllWorkByTag(YT_WORK_TAG) + mWorkManager?.enqueueUniquePeriodicWork( + YT_WORK_TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, + PeriodicWorkRequestBuilder(PrefLong.longTimePeriod.get(), TimeUnit.MINUTES) + .addTag(YT_WORK_TAG) + .build()) + mWorkManager?.cancelAllWorkByTag(REDDIT_WORK_TAG) + mWorkManager?.enqueueUniquePeriodicWork( + REDDIT_WORK_TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, + PeriodicWorkRequestBuilder(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES) + .addTag(REDDIT_WORK_TAG) + .build()) + mWorkManager?.cancelAllWorkByTag(FM_WORK_TAG) + mWorkManager?.enqueueUniquePeriodicWork( + FM_WORK_TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, + PeriodicWorkRequestBuilder(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES) + .addTag(FM_WORK_TAG) + .build()) + mWorkManager?.cancelAllWorkByTag(COMIC2_WORK_TAG) + mWorkManager?.enqueueUniquePeriodicWork( + COMIC2_WORK_TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, + PeriodicWorkRequestBuilder(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES) + .addTag(COMIC2_WORK_TAG) + .build()) + mWorkManager?.cancelAllWorkByTag(ClienGetter.TAG) + mWorkManager?.enqueueUniquePeriodicWork( + ClienGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, + PeriodicWorkRequestBuilder(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES) + .addTag(ClienGetter.TAG) + .build()) + mWorkManager?.cancelAllWorkByTag(DCGetter.TAG) + mWorkManager?.enqueueUniquePeriodicWork( + DCGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, + PeriodicWorkRequestBuilder(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES) + .addTag(DCGetter.TAG) + .build()) + mWorkManager?.cancelAllWorkByTag(TheQooGetter.TAG) + mWorkManager?.enqueueUniquePeriodicWork( + TheQooGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, + PeriodicWorkRequestBuilder(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES) + .addTag(TheQooGetter.TAG) + .build()) + mWorkManager?.cancelAllWorkByTag(ArcaGetter.TAG) + mWorkManager?.enqueueUniquePeriodicWork( + ArcaGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, + PeriodicWorkRequestBuilder(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES) + .addTag(ArcaGetter.TAG) + .build()) + mWorkManager?.cancelAllWorkByTag(LocationGetter.TAG) + mWorkManager?.enqueueUniquePeriodicWork( + LocationGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, + PeriodicWorkRequestBuilder(PrefLong.locationTimePeriod.get(), TimeUnit.MINUTES) + .addTag(LocationGetter.TAG) + .build()) + } + + + fun workmanager() : WorkManager? { + if (mWorkManager == null && lActivity != null) { + mWorkManager = WorkManager.getInstance(lActivity!!) + } + return mWorkManager + } fun sendToI(boolean: Boolean) { if (PrefString.telegramSendTarget.get().length > 5) { @@ -94,20 +212,10 @@ class BluetoothManager { "${PrefString.carName.get()}의 시동이 꺼졌다요." }}" //7068729507 - // OkHttp 클라이언트 객체 생성 - val client = OkHttpClient.Builder() + val response: Response = OkHttpClient.Builder() .connectionPool(ConnectionPool(5, 60, TimeUnit.SECONDS)) - .build() - - // GET 요청 객체 생성 - val builder: Request.Builder = Request.Builder().url(url) - .addHeader("Content-Type", "application/json").get() - - val request: Request = builder.build() - -// BLog.LOGE("sendToI telegram before request ") - // OkHttp 클라이언트로 GET 요청 객체 전송 - val response: Response = client.newCall(request).execute() + .build().newCall(Request.Builder().url(url) + .addHeader("Content-Type", "application/json").get().build()).execute() if (response.isSuccessful()) { // 응답 받아서 처리 val body: ResponseBody? = response.body() diff --git a/app/src/main/kotlin/bums/lunatic/launcher/helpers/PrefHelper.kt b/app/src/main/kotlin/bums/lunatic/launcher/helpers/PrefHelper.kt index ecd98da5..d96c1b5b 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/helpers/PrefHelper.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/helpers/PrefHelper.kt @@ -29,7 +29,7 @@ enum class PrefLong(val def : Long) : PrefKey { maxQueryCount(18L); override fun set(value : Long) { PrefHelper.putLong(this.name, value) } override fun get(def : Long?) : Long { - val value = PrefHelper.getLong(this.name, def ?: this.def) ?: this.def + val value = Math.max(15,PrefHelper.getLong(this.name, def ?: this.def) ?: this.def) // Blog.LOGE("$name : $value") diff --git a/app/src/main/kotlin/bums/lunatic/launcher/home/GeckoWeb.kt b/app/src/main/kotlin/bums/lunatic/launcher/home/GeckoWeb.kt index 462197f8..40db6bff 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/home/GeckoWeb.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/home/GeckoWeb.kt @@ -286,6 +286,7 @@ class GeckoWeb : BWebview { } fun downloadImage(context: Context, url: Uri) { + Blog.LOGE("url.lastPathSegment ${url.lastPathSegment}") val fileName = url.lastPathSegment ?: "${SimpleDateFormat("yyyyMMddHHmmsss")}.jpg" val request = DownloadManager.Request(url) request.setTitle(fileName) @@ -687,9 +688,13 @@ class GeckoWeb : BWebview { var nUrl = url Blog.LOGE("url >>>> ${url}") if (url.endsWith("=")) { - nUrl = String(java.util.Base64.getMimeDecoder().decode(url.toByteArray())) - param?.let { - nUrl = nUrl.plus(param) + try { + nUrl = String(java.util.Base64.getMimeDecoder().decode(url.toByteArray())) + param?.let { + nUrl = nUrl.plus(param) + } + }catch (e: Exception) { + nUrl = url } } else if (url.startsWith("http") == false) { nUrl = lastDomain diff --git a/app/src/main/kotlin/bums/lunatic/launcher/home/RssHome.kt b/app/src/main/kotlin/bums/lunatic/launcher/home/RssHome.kt index b1120d14..bef2568f 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/home/RssHome.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/home/RssHome.kt @@ -50,7 +50,6 @@ import bums.lunatic.launcher.common.letTrue import bums.lunatic.launcher.databinding.LauncherHomeBinding import bums.lunatic.launcher.helpers.Constants.Companion.PREFS_SETTINGS import bums.lunatic.launcher.home.adapters.RssItemAdapter -import bums.lunatic.launcher.home.adapters.RssItemAdapter.Companion.rssList import bums.lunatic.launcher.home.adapters.SwipeToDeleteCallback import bums.lunatic.launcher.model.RssData import bums.lunatic.launcher.model.RssDataType @@ -101,7 +100,7 @@ internal class RssHome : Fragment() { fun rssStateVote() = (lasted?.filter { it.vote == true }?.size ?: -1) == (lasted?.size ?: 0) var lasted: ArrayList = arrayListOf() var infosJob: Job? = null - var rssId = "" +// var rssId = "" lateinit var mRssAdapter: RssItemAdapter var mRssDataResult: RealmResults? = null @@ -336,25 +335,32 @@ internal class RssHome : Fragment() { builder.show() } - + var currentRss : RssData? = null @SuppressLint("SimpleDateFormat") fun openGecko(rssData: RssData? = null) { binding.layoutRssSummary.root.visibility = View.GONE if (rssData?.category()?.equals(RssDataType.PRIVATE) == false && rssData?.originPage?.isNotEmpty() == true) { - rssData?.originPage?.let { + rssData?.let { rss -> + currentRss = rss binding.geckoWeb.privateMode = false - rssId = it - targetList.clear() - var setString = hashSetOf() - setString.addAll(rssList) - setString.removeAll { it.equals(rssId) } - targetList.addAll(setString) - binding.geckoWeb.loadUrl(rssId) + appendReadCount(rss, 1, false) + rss?.originPage?.let { rssId-> + synchronized(lasted) { + if (lasted.isNotEmpty()) { + lasted.removeAll { target -> target.originPage.equals(rssId) } + } + } + binding.geckoWeb.loadUrl(rssId) + } } } else if (rssData?.category()?.equals(RssDataType.PRIVATE) == true){ rssData?.let { binding.geckoWeb.privateMode = true - lasted.removeAll { target -> target.originPage.equals(it.originPage) } + synchronized(lasted) { + if (lasted.isNotEmpty()) { + lasted.removeAll { target -> target.originPage.equals(it.originPage) } + } + } appendReadCount(it, 1, false) Blog.LOGE("removeFirst >>> ${Gson().toJson(it)}") binding.layoutRssSummary.title.tag = it @@ -477,11 +483,13 @@ internal class RssHome : Fragment() { if (binding.geckoWeb.isVisible) { WorkersDb.getRealm().apply { writeBlocking { - val result = query().query("originPage == $0", rssId).find() - if (result.size > 0) { - result.forEach { - if (it.vote) { - it.vote = false + currentRss?.originPage?.let { + val result = query().query("originPage == $0", it).find() + if (result.size > 0) { + result.forEach { + if (it.vote) { + it.vote = false + } } } } @@ -552,45 +560,40 @@ internal class RssHome : Fragment() { } fun vote() { - Blog.LOGE("Arrow Center Click") - WorkersDb.getRealm().apply { - writeBlocking { - val result = query().query( - if (imageView) "thumbnail == $0" else "originPage == $0", - rssId - ).find() - if (result.size > 0) { - result.forEach { it.vote = true } + currentRss?.originPage.let { + Blog.LOGE("Arrow Center Click") + WorkersDb.getRealm().apply { + writeBlocking { + val result = query().query( + if (imageView) "thumbnail == $0" else "originPage == $0", + it + ).find() + if (result.size > 0) { + result.forEach { it.vote = true } + } } } + doNextPage() } - doNextPage() } @SuppressLint("NewApi") fun doNextPage() { - WorkersDb.getRealm().apply { - writeBlocking { - val result = query().query( - if (imageView) "thumbnail == $0" else "originPage == $0", - rssId - ).find() - if (result.size > 0) { - result.forEach { - it.read = it.read + nomoreShowCount + currentRss?.originPage.let { + WorkersDb.getRealm().apply { + writeBlocking { + val result = query().query( + if (imageView) "thumbnail == $0" else "originPage == $0", + it + ).find() + if (result.size > 0) { + result.forEach { it.read = it.read + nomoreShowCount } } } } } - targetList.removeAll { it.equals(rssId) } - if (targetList.size > 0) { - rssId = targetList.removeFirst() - binding.geckoWeb.loadUrl(rssId) - } else { - Toast.makeText(requireContext(), "없어 끄자", Toast.LENGTH_LONG).show() - binding.geckoWeb.visibility = View.GONE - } + openGecko(randomOrNull()) } fun clearJob(job: Job?) { @@ -616,7 +619,7 @@ internal class RssHome : Fragment() { .query( "category != $0 AND category != $1 ", RssDataType.PRIVATE.name, - RssDataType.REDDIT_NSFW.name + RssDataType.TORRENT.name ) .query("vote != $0", true).find() ) @@ -634,12 +637,14 @@ internal class RssHome : Fragment() { flow.collect { changes: ResultsChange -> // when (changes) { // is InitialResults -> { - commandHandler.removeCallbacks(infoUpdate) - WorkersDb.getRealm().apply { - lasted.clear() - lasted.addAll(copyFromRealm(changes.list)) + synchronized(lasted) { + commandHandler.removeCallbacks(infoUpdate) + WorkersDb.getRealm().apply { + lasted.clear() + lasted.addAll(copyFromRealm(changes.list)) + } + commandHandler.post(infoUpdate) } - commandHandler.post(infoUpdate) // } // // is UpdatedResults -> { @@ -714,9 +719,13 @@ internal class RssHome : Fragment() { fun queryInfos( keywords: List, + includeVote : Boolean = false, + includeRead : Boolean = false ) { beforeQuery() - var rQ = getRealm().query().sort("pubDate", Sort.DESCENDING) + var rQ = getRealm().query().sort("read", Sort.ASCENDING) + if (!includeRead) { rQ = rQ.query("read == $0", 0)} + if (!includeVote) { rQ = rQ.query("vote != $0", true)} // 사용 예시 val (queryStr, queryArgs) = buildMultiFieldOrQuery( listOf("title", "description"), diff --git a/app/src/main/kotlin/bums/lunatic/launcher/home/adapters/RssItemAdapter.kt b/app/src/main/kotlin/bums/lunatic/launcher/home/adapters/RssItemAdapter.kt index 84dbb7bc..f1cbf664 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/home/adapters/RssItemAdapter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/home/adapters/RssItemAdapter.kt @@ -61,7 +61,7 @@ internal class RssItemAdapter ( @SuppressLint("SimpleDateFormat") val dateFormat = SimpleDateFormat("a HH:mm / yy - MM - dd") val emptyDate = " - " - var rssList: ArrayList = ArrayList() +// var rssList: ArrayList = ArrayList() val webViewListener = object : WebViewListener() { override fun onCustomMenuClick(menuCode: String?) { super.onCustomMenuClick(menuCode) @@ -188,51 +188,60 @@ internal class RssItemAdapter ( @SuppressLint("SetTextI18n", "ClickableViewAccessibility") override fun onBindViewHolder(holder: RssHolder, position: Int) { - val rssData = rssDataItemLis[position] - if (rssData.pubDate() > 1000L) { - holder.view.date.text = dateFormat.format(Date(rssData.pubDate())) - } else { - holder.view.date.text = emptyDate - } - - - holder.view.title.text = "".plus(if(rssData.vote) " * " else "").plus(rssData.title().plus("[R:${rssData.read}]")) - holder.view.desc.text = rssData.description() - - var param = holder.view.circlePreview.layoutParams - holder.view.circlePreview.layoutParams = ConstraintLayout.LayoutParams(rssData.category().defaultImgSize(), param.height) - holder.view.circlePreview.visibility = rssData.category().getDefaultVisibiliy() - Picasso.get().cancelRequest(holder.view.circlePreview) - - if(rssData.thumbnailUrl()?.length ?: 0 > 6) { - Picasso.get().load(rssData.thumbnailUrl().replace("&","&").toUri()).into(holder.view.circlePreview) - } else if (rssData.category().getResId() > 0 ) { - holder.view.circlePreview.setImageResource(rssData.category().getResId()) - } else { - holder.view.circlePreview.setImageDrawable(null) - } - - holder.itemView.tag = rssData - holder.itemView.setOnClickListener(dateViewClick) - holder.itemView.setOnTouchListener(object : View.OnTouchListener{ - override fun onTouch( - v: View, - event: MotionEvent - ): Boolean { - 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 - } + if (rssDataItemLis.isNotEmpty() && rssDataItemLis.size > position) { + val rssData = rssDataItemLis[position] + if (rssData.pubDate() > 1000L) { + holder.view.date.text = dateFormat.format(Date(rssData.pubDate())) + } else { + holder.view.date.text = emptyDate } - }) + + + holder.view.title.text = "".plus(if (rssData.vote) " * " else "") + .plus(rssData.title().plus("[R:${rssData.read}]")) + holder.view.desc.text = rssData.description() + + var param = holder.view.circlePreview.layoutParams + holder.view.circlePreview.layoutParams = + ConstraintLayout.LayoutParams(rssData.category().defaultImgSize(), param.height) + holder.view.circlePreview.visibility = rssData.category().getDefaultVisibiliy() + Picasso.get().cancelRequest(holder.view.circlePreview) + + if (rssData.thumbnailUrl()?.length ?: 0 > 6) { + Picasso.get().load(rssData.thumbnailUrl().replace("&", "&").toUri()) + .into(holder.view.circlePreview) + } else if (rssData.category().getResId() > 0) { + holder.view.circlePreview.setImageResource(rssData.category().getResId()) + } else { + holder.view.circlePreview.setImageDrawable(null) + } + + holder.itemView.tag = rssData + holder.itemView.setOnClickListener(dateViewClick) + holder.itemView.setOnTouchListener(object : View.OnTouchListener { + override fun onTouch( + v: View, + event: MotionEvent + ): Boolean { + 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 + } + } + }) // v.setOnLongClickListener { // WorkersDb.getRealm().apply { // copyFromRealm(rss) // } // } - holder.itemView.setOnLongClickListener(mLongClickListener) + holder.itemView.setOnLongClickListener(mLongClickListener) + } } var layoutManager : LinearLayoutManager? = null @@ -242,24 +251,29 @@ internal class RssItemAdapter ( this.recyclerView = recyclerView } + @SuppressLint("NotifyDataSetChanged") fun updateData(newList: List) { try { - DiffUtil.calculateDiff(RssItemDiffUtil(rssDataItemLis, newList)).apply { +// DiffUtil.calculateDiff(RssItemDiffUtil(rssDataItemLis, newList)).apply { +// +// }.dispatchUpdatesTo(this).apply { +// val visibleItemCount = (layoutManager?.findLastVisibleItemPosition() ?: 0) - (layoutManager?.findFirstVisibleItemPosition() ?: 0) +// val first = layoutManager?.findLastVisibleItemPosition() ?: 0 +// if (visibleItemCount > 0) { +// this@RssItemAdapter.notifyItemRangeChanged(first, visibleItemCount) +//// recyclerView?.scrollToPosition(0) +// } +// } + synchronized(rssDataItemLis) { + rssDataItemLis.clear() + rssDataItemLis.addAll(newList) + } + notifyDataSetChanged() - }.dispatchUpdatesTo(this).apply { - val visibleItemCount = (layoutManager?.findLastVisibleItemPosition() ?: 0) - (layoutManager?.findFirstVisibleItemPosition() ?: 0) - val first = layoutManager?.findLastVisibleItemPosition() ?: 0 - if (visibleItemCount > 0) { - this@RssItemAdapter.notifyItemRangeChanged(first, visibleItemCount) -// recyclerView?.scrollToPosition(0) - } - } - rssDataItemLis.clear() - rssDataItemLis.addAll(newList) - CoroutineScope(Dispatchers.IO).launch { - rssList.clear() - rssList.addAll(newList.map { it.originPage() }) - } +// CoroutineScope(Dispatchers.IO).launch { +// rssList.clear() +// rssList.addAll(newList.map { it.originPage() }) +// } } catch (e: Exception) { e.printStackTrace() } diff --git a/app/src/main/kotlin/bums/lunatic/launcher/model/RssDataInterface.kt b/app/src/main/kotlin/bums/lunatic/launcher/model/RssDataInterface.kt index e11c681a..9570e16f 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/model/RssDataInterface.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/model/RssDataInterface.kt @@ -7,10 +7,9 @@ import bums.lunatic.launcher.helpers.PrefHelper enum class RssDataType { NO_DATA, PRIVATE, + TORRENT, YOUTUBE, NEWSFEED, -// GURU, -// MOST, TAGS, REDDIT, REDDIT_NSFW, diff --git a/app/src/main/kotlin/bums/lunatic/launcher/tokiz/BaseToki.kt b/app/src/main/kotlin/bums/lunatic/launcher/tokiz/BaseToki.kt index 9fc9626a..46ced71f 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/tokiz/BaseToki.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/tokiz/BaseToki.kt @@ -54,9 +54,11 @@ import bums.lunatic.launcher.tokiz.view.JxEvent import bums.lunatic.launcher.tokiz.view.PagedTextLayout import bums.lunatic.launcher.tokiz.view.PagedTextViewInterface import bums.lunatic.launcher.databinding.BooktokiBinding +import bums.lunatic.launcher.home.toast import bums.lunatic.launcher.tokiz.data.model.FakeSession import bums.lunatic.launcher.tokiz.data.model.FakeSessions import bums.lunatic.launcher.utils.Blog +import bums.lunatic.launcher.workers.WorkersDb import com.google.gson.Gson import io.realm.kotlin.Realm import io.realm.kotlin.UpdatePolicy @@ -130,7 +132,7 @@ abstract class BaseToki : Fragment(), PagedTextViewInterface { abstract var lastNumber : Int abstract val webcontentsName : String abstract val afterDot : String - fun getLastedDoamin(): String { + open fun getLastedDoamin(): String { return String.format("https://%s%d.%s", webcontentsName , lastNumber, afterDot) } val OnTouchListener = object : OnTouchListener { @@ -454,6 +456,12 @@ abstract class BaseToki : Fragment(), PagedTextViewInterface { "SHOWVIEWER" -> { binding.progress.visibility = GONE } + "PRIVATES"->{ + lPortMessage.privates?.let { + requireContext().toast("Received Msg privates form ${lPortMessage.currentPage} data => ${it?.size ?: 0}") + WorkersDb.insertBulkData(it) + } + } else -> { } diff --git a/app/src/main/kotlin/bums/lunatic/launcher/tokiz/Twitter.kt b/app/src/main/kotlin/bums/lunatic/launcher/tokiz/Twitter.kt new file mode 100644 index 00000000..72b67e72 --- /dev/null +++ b/app/src/main/kotlin/bums/lunatic/launcher/tokiz/Twitter.kt @@ -0,0 +1,185 @@ +package bums.lunatic.launcher.tokiz + +import android.content.DialogInterface +import android.content.Intent +import android.content.pm.ActivityInfo +import android.content.res.Configuration +import android.graphics.Bitmap +import android.graphics.Color +import android.net.Uri +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.os.Message +import android.text.InputType +import android.text.SpannableStringBuilder +import android.text.style.RelativeSizeSpan +import android.util.Log +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.View.GONE +import android.view.View.OnTouchListener +import android.view.View.VISIBLE +import android.view.View.inflate +import android.view.ViewGroup +import android.webkit.WebView +import android.webkit.WebViewClient +import android.widget.ArrayAdapter +import android.widget.EditText +import android.widget.TextView +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.net.toUri +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import bums.lunatic.launcher.LauncherActivity.Companion.getRuntime +import bums.lunatic.launcher.R +import bums.lunatic.launcher.tokiz.common.PairArray +import bums.lunatic.launcher.tokiz.common.TouchArea +import bums.lunatic.launcher.tokiz.common.colorz +import bums.lunatic.launcher.tokiz.common.getIndex +import bums.lunatic.launcher.tokiz.common.typesfacez +import bums.lunatic.launcher.tokiz.data.HistoryManager +import bums.lunatic.launcher.tokiz.data.model.ContentsPageInfo +import bums.lunatic.launcher.tokiz.data.model.ContentsCollection +import bums.lunatic.launcher.tokiz.data.model.PageInfosJ +import bums.lunatic.launcher.tokiz.data.model.HistoryItem +import bums.lunatic.launcher.tokiz.data.model.LastInfo +import bums.lunatic.launcher.tokiz.data.model.PortMessage +import bums.lunatic.launcher.tokiz.data.model.ReaderConfig +import bums.lunatic.launcher.tokiz.dialog.DefaultList +import bums.lunatic.launcher.tokiz.view.JxEvent +import bums.lunatic.launcher.tokiz.view.PagedTextLayout +import bums.lunatic.launcher.tokiz.view.PagedTextViewInterface +import bums.lunatic.launcher.databinding.BooktokiBinding +import bums.lunatic.launcher.utils.Blog +import com.google.gson.Gson +import io.realm.kotlin.Realm +import io.realm.kotlin.UpdatePolicy +import io.realm.kotlin.ext.copyFromRealm +import io.realm.kotlin.ext.query +import org.json.JSONException +import org.json.JSONObject +import org.mozilla.gecko.util.ThreadUtils +import org.mozilla.geckoview.GeckoResult +import org.mozilla.geckoview.GeckoSession +import org.mozilla.geckoview.MediaSession +import org.mozilla.geckoview.WebExtension +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.lang.System.currentTimeMillis +import java.text.SimpleDateFormat +import java.util.Date +import kotlin.collections.ArrayList +import kotlin.collections.List +import kotlin.collections.MutableList +import kotlin.collections.arrayListOf +import kotlin.collections.first +import kotlin.collections.isNotEmpty +import kotlin.collections.last +import kotlin.collections.sortBy +import kotlin.random.Random +import kotlin.text.contains +import kotlin.text.endsWith +import kotlin.text.equals +import kotlin.text.replace +import kotlin.text.split +import kotlin.text.startsWith +import kotlin.text.toInt +import kotlin.text.toRegex +import kotlin.text.trim + + +class Twitter : BaseToki(), PagedTextViewInterface { + + override val contentsType = "twitter" + override var lastNumber : Int = 143 + override val webcontentsName : String = "twitter" + override val afterDot = "com" + override fun getLastedDoamin(): String { + return String.format("https://%s.%s", webcontentsName, afterDot) + } + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + super.onCreateView(inflater, container, savedInstanceState) + return binding.root + } + + override fun onStart() { + super.onStart() + } + + override fun onResume() { + super.onResume() + loadLastInfo() + } + + override fun onTouch(touchArea: TouchArea) { + Blog.LOGD(log = "onTouch ${touchArea}") + when (touchArea) { + TouchArea.Center -> { + + } + + TouchArea.Right -> { + actionNextEvent() + } + + TouchArea.Left -> { + actionPrevEvent() + } + + TouchArea.DoubleRight -> { + actionNextEvent(true) + } + + TouchArea.DoubleLeft -> { + actionPrevEvent(true) + } + else -> { + + } + } + + + } + + override fun onLongClick() { + Blog.LOGD(log = "onLongClick") + + } + + override fun onSwipeLeft(count: Int) { + Blog.LOGD(log = "onSwipeLeft ${count}") + actionNextEvent(count > 1) + + } + + override fun onSwipeRight(count: Int) { + Blog.LOGD(log = "onSwipeRight ${count}") + actionPrevEvent(count > 1) + } + + override fun onSwipeUp(touchCount: Int) { + + } + + override fun onSwipeDown(touchCount: Int) { + if (touchCount == 2) { + if (binding.pagedLayer.isVisible) { + binding.pagedLayer.visibility = GONE + } + } + } + + override fun onTimeoverTouch() { + + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/tokiz/Zota.kt b/app/src/main/kotlin/bums/lunatic/launcher/tokiz/Zota.kt new file mode 100644 index 00000000..dd13e92a --- /dev/null +++ b/app/src/main/kotlin/bums/lunatic/launcher/tokiz/Zota.kt @@ -0,0 +1,182 @@ +package bums.lunatic.launcher.tokiz + +import android.content.DialogInterface +import android.content.Intent +import android.content.pm.ActivityInfo +import android.content.res.Configuration +import android.graphics.Bitmap +import android.graphics.Color +import android.net.Uri +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.os.Message +import android.text.InputType +import android.text.SpannableStringBuilder +import android.text.style.RelativeSizeSpan +import android.util.Log +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.View.GONE +import android.view.View.OnTouchListener +import android.view.View.VISIBLE +import android.view.View.inflate +import android.view.ViewGroup +import android.webkit.WebView +import android.webkit.WebViewClient +import android.widget.ArrayAdapter +import android.widget.EditText +import android.widget.TextView +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.net.toUri +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import bums.lunatic.launcher.LauncherActivity.Companion.getRuntime +import bums.lunatic.launcher.R +import bums.lunatic.launcher.tokiz.common.PairArray +import bums.lunatic.launcher.tokiz.common.TouchArea +import bums.lunatic.launcher.tokiz.common.colorz +import bums.lunatic.launcher.tokiz.common.getIndex +import bums.lunatic.launcher.tokiz.common.typesfacez +import bums.lunatic.launcher.tokiz.data.HistoryManager +import bums.lunatic.launcher.tokiz.data.model.ContentsPageInfo +import bums.lunatic.launcher.tokiz.data.model.ContentsCollection +import bums.lunatic.launcher.tokiz.data.model.PageInfosJ +import bums.lunatic.launcher.tokiz.data.model.HistoryItem +import bums.lunatic.launcher.tokiz.data.model.LastInfo +import bums.lunatic.launcher.tokiz.data.model.PortMessage +import bums.lunatic.launcher.tokiz.data.model.ReaderConfig +import bums.lunatic.launcher.tokiz.dialog.DefaultList +import bums.lunatic.launcher.tokiz.view.JxEvent +import bums.lunatic.launcher.tokiz.view.PagedTextLayout +import bums.lunatic.launcher.tokiz.view.PagedTextViewInterface +import bums.lunatic.launcher.databinding.BooktokiBinding +import bums.lunatic.launcher.utils.Blog +import com.google.gson.Gson +import io.realm.kotlin.Realm +import io.realm.kotlin.UpdatePolicy +import io.realm.kotlin.ext.copyFromRealm +import io.realm.kotlin.ext.query +import org.json.JSONException +import org.json.JSONObject +import org.mozilla.gecko.util.ThreadUtils +import org.mozilla.geckoview.GeckoResult +import org.mozilla.geckoview.GeckoSession +import org.mozilla.geckoview.MediaSession +import org.mozilla.geckoview.WebExtension +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.lang.System.currentTimeMillis +import java.text.SimpleDateFormat +import java.util.Date +import kotlin.collections.ArrayList +import kotlin.collections.List +import kotlin.collections.MutableList +import kotlin.collections.arrayListOf +import kotlin.collections.first +import kotlin.collections.isNotEmpty +import kotlin.collections.last +import kotlin.collections.sortBy +import kotlin.random.Random +import kotlin.text.contains +import kotlin.text.endsWith +import kotlin.text.equals +import kotlin.text.replace +import kotlin.text.split +import kotlin.text.startsWith +import kotlin.text.toInt +import kotlin.text.toRegex +import kotlin.text.trim + + +class Zota : BaseToki(), PagedTextViewInterface { + + override val contentsType = "Torrent" + override var lastNumber : Int = 143 + override val webcontentsName : String = "torrentzota" + override val afterDot = "com" + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + super.onCreateView(inflater, container, savedInstanceState) + return binding.root + } + + override fun onStart() { + super.onStart() + } + + override fun onResume() { + super.onResume() + loadLastInfo() + } + + override fun onTouch(touchArea: TouchArea) { + Blog.LOGD(log = "onTouch ${touchArea}") + when (touchArea) { + TouchArea.Center -> { + + } + + TouchArea.Right -> { + actionNextEvent() + } + + TouchArea.Left -> { + actionPrevEvent() + } + + TouchArea.DoubleRight -> { + actionNextEvent(true) + } + + TouchArea.DoubleLeft -> { + actionPrevEvent(true) + } + else -> { + + } + } + + + } + + override fun onLongClick() { + Blog.LOGD(log = "onLongClick") + + } + + override fun onSwipeLeft(count: Int) { + Blog.LOGD(log = "onSwipeLeft ${count}") + actionNextEvent(count > 1) + + } + + override fun onSwipeRight(count: Int) { + Blog.LOGD(log = "onSwipeRight ${count}") + actionPrevEvent(count > 1) + } + + override fun onSwipeUp(touchCount: Int) { + + } + + override fun onSwipeDown(touchCount: Int) { + if (touchCount == 2) { + if (binding.pagedLayer.isVisible) { + binding.pagedLayer.visibility = GONE + } + } + } + + override fun onTimeoverTouch() { + + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/wall/MyWallpaperService.kt b/app/src/main/kotlin/bums/lunatic/launcher/wall/MyWallpaperService.kt index 00265ff3..a457abff 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/wall/MyWallpaperService.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/wall/MyWallpaperService.kt @@ -6,6 +6,8 @@ import android.graphics.Color import android.graphics.Paint import android.graphics.RectF import android.media.MediaCodec +import android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar +import android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar import android.media.MediaExtractor import android.media.MediaFormat import android.renderscript.Allocation @@ -18,6 +20,8 @@ import android.util.DisplayMetrics import android.view.SurfaceHolder import android.view.WindowManager import bums.lunatic.launcher.R +import bums.lunatic.launcher.utils.Blog +import java.nio.ByteBuffer class MyWallpaperService : WallpaperService() { @@ -51,6 +55,8 @@ class MyWallpaperService : WallpaperService() { screenWidth = metrics.widthPixels screenHeight = metrics.heightPixels } + + } override fun onSurfaceCreated(holder: SurfaceHolder) { @@ -98,6 +104,7 @@ class RenderThread( private var renderStartY = 0f private var maxOffset = 0f private var moveXAxis = false + private var keyFrameRate = 60 override fun run() { try { @@ -110,9 +117,16 @@ class RenderThread( for (i in 0 until trackCount) { val format = getTrackFormat(i) val mime = format.getString(MediaFormat.KEY_MIME) ?: "" + + if (format.containsKey(MediaFormat.KEY_FRAME_RATE)) { + keyFrameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE) + } else { + keyFrameRate = 60 + } if (mime.startsWith("video/")) { videoTrackIndex = i selectTrack(i) + videoWidth = format.getInteger(MediaFormat.KEY_WIDTH) videoHeight = format.getInteger(MediaFormat.KEY_HEIGHT) break @@ -144,8 +158,8 @@ class RenderThread( } renderWidth = videoWidth * scale renderHeight = videoHeight * scale - renderStartX = (screenWidth - renderWidth) / 2f - renderStartY = (screenHeight - renderHeight) / 2f + renderStartX = 0f;//(screenWidth - renderWidth) / 2f + renderStartY = 0f;//(screenHeight - renderHeight) / 2f moveXAxis = renderWidth > screenWidth maxOffset = if (moveXAxis) renderWidth - screenWidth else if (renderHeight > screenHeight) renderHeight - screenHeight else 0f @@ -161,9 +175,12 @@ class RenderThread( if (inputBuffer != null) { val sampleSize = extractor?.readSampleData(inputBuffer, 0) ?: -1 if (sampleSize < 0) { - codec?.queueInputBuffer( - inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM) - isEOS = true +// codec?.queueInputBuffer( +// inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM) +// isEOS = true + extractor?.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC) + isEOS = false // false로 재설정해서 계속 받게 + continue } else { val pts = extractor?.sampleTime ?: 0L codec?.queueInputBuffer(inputBufferIndex, 0, sampleSize, pts, 0) @@ -176,13 +193,35 @@ class RenderThread( val outputBufferIndex = codec?.dequeueOutputBuffer(bufferInfo, 10000) ?: -1 if (outputBufferIndex >= 0) { val outputBuffer = codec?.getOutputBuffer(outputBufferIndex) + Blog.LOGE("codec.outputFormat >>> ${codec?.outputFormat}") if (bufferInfo.size > 0 && outputBuffer != null) { - val yuvData = ByteArray(bufferInfo.size) - outputBuffer.get(yuvData) + val format = codec?.outputFormat - val bitmap = convertYUVToBitmap(yuvData, videoWidth, videoHeight) + val colorFormat = format?.getInteger(MediaFormat.KEY_COLOR_FORMAT) ?: /*기본값*/0 + val stride = format?.getInteger(MediaFormat.KEY_STRIDE) ?: videoWidth + val sliceHeight = format?.getInteger(MediaFormat.KEY_SLICE_HEIGHT) ?: videoHeight +// outputBuffer.get(yuvData) + val expectedSize = videoWidth * videoHeight * 3 / 2 + val yuvData = ByteArray(expectedSize) + val rawYUV = ByteArray(bufferInfo.size) + outputBuffer.get(rawYUV) + outputBuffer.position(0) + +// 포맷별 변환 + val nv21Data = when (colorFormat) { + COLOR_FormatYUV420SemiPlanar -> nv12ToNv21(rawYUV, videoWidth, videoHeight) + COLOR_FormatYUV420Planar -> i420ToNv21(rawYUV, videoWidth, videoHeight) + else -> rawYUV // 이미 NV21 등 + } + +// NV21 데이터 → Bitmap + val bitmap = convertYUVToBitmap(nv21Data, videoWidth, videoHeight) +// outputBuffer.get(yuvData, 0, minOf(bufferInfo.size, expectedSize)) +// getYUVDataWithStride(outputBuffer, videoWidth, videoHeight, stride, height) +// val bitmap = convertYUVToBitmap(yuvData, videoWidth, videoHeight) // 6. 왕복 이동 애니메이션 offset 계산 +// Blog.LOGE("maxOffset >>> $maxOffset , offset >>> $offset , direction >> $direction , keyFrameRate >>> $keyFrameRate") if (maxOffset > 0f) { offset += direction * speed if (offset < 0f) { offset = 0f; direction = 1 } @@ -215,7 +254,7 @@ class RenderThread( } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { // 필요하다면 포맷 변경 처리 } - sleep(1000L/45L) // 약 30fps + sleep(1000L / keyFrameRate) // 약 30fps } } catch (e: Exception) { e.printStackTrace() @@ -229,13 +268,17 @@ class RenderThread( } private fun convertYUVToBitmap(yuvByteArray: ByteArray, width: Int, height: Int): Bitmap { - val yuvType = Type.Builder(rs, Element.U8(rs)).setX(yuvByteArray.size) + // YUV420の場合, 바이트 수는 width * height * 3 / 2 여야 함 + val minSize = width * height * 3 / 2 + val realData = if (yuvByteArray.size >= minSize) yuvByteArray.copyOf(minSize) else yuvByteArray + + val yuvType = Type.Builder(rs, Element.U8(rs)).setX(minSize) val inAllocation = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT) val rgbaType = Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height) val outAllocation = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT) - inAllocation.copyFrom(yuvByteArray) + inAllocation.copyFrom(realData) yuvToRgb?.setInput(inAllocation) yuvToRgb?.forEach(outAllocation) @@ -247,4 +290,91 @@ class RenderThread( return bitmap } + + private fun getYUVDataWithStride( + buffer: ByteBuffer, + width: Int, + height: Int, + stride: Int, + sliceHeight: Int + ): ByteArray { + val yuvData = ByteArray(width * height * 3 / 2) + // Y plane 복사 + for (y in 0 until height) { + buffer.position(y * stride) + buffer.get(yuvData, y * width, width) + } + // UV plane... (NV21 등 포맷 따라 별도 구현 필요) + // 이 영역은 포맷에 따라 다름, 기본은 Y만 참고! + return yuvData + } + + // NV12 to NV21 변환 + fun nv12ToNv21(nv12: ByteArray, width: Int, height: Int): ByteArray { + val frameSize = width * height + val nv21 = ByteArray(frameSize * 3 / 2) + + // Y는 그대로 복사 + System.arraycopy(nv12, 0, nv21, 0, frameSize) + + // UV를 VU로 뒤집어서 복사 + var i = 0 + while (i < frameSize / 2 - 1) { + nv21[frameSize + i] = nv12[frameSize + i + 1] + nv21[frameSize + i + 1] = nv12[frameSize + i] + i += 2 + } + return nv21 + } + + // I420(YUV420Planar) to NV21 변환 + fun i420ToNv21(i420: ByteArray, width: Int, height: Int): ByteArray { + val frameSize = width * height + val qFrameSize = frameSize / 4 + val nv21 = ByteArray(frameSize * 3 / 2) + + // Y 복사 + System.arraycopy(i420, 0, nv21, 0, frameSize) + + // VU interleave + val u = frameSize + val v = frameSize + qFrameSize + + for (i in 0 until qFrameSize) { + nv21[frameSize + i * 2] = i420[v + i] // V + nv21[frameSize + i * 2 + 1] = i420[u + i] // U + } + return nv21 + } + fun i420ToNv21WithStride(src: ByteBuffer, width: Int, height: Int, stride: Int, sliceHeight: Int): ByteArray { + val frameSize = width * height + val qFrameSize = frameSize / 4 + val nv21 = ByteArray(frameSize * 3 / 2) + + // 1. Y plane 복사 + for (y in 0 until height) { + src.position(y * stride) + src.get(nv21, y * width, width) + } + + // 2. U/V plane 복사, 각각 stride/2씩 적용 + val uOffsetBuf = sliceHeight * stride + val vOffsetBuf = uOffsetBuf + (sliceHeight / 2) * (stride / 2) + + for (y in 0 until height/2) { + src.position(uOffsetBuf + y * (stride / 2)) + src.get(nv21, frameSize + y * width, width/2) // 임시: U만 + + src.position(vOffsetBuf + y * (stride / 2)) + for (x in 0 until width/2) { + // NV21 순서: V, U + val v = src.get() + val u = nv21[frameSize + y * width + x] // U + nv21[frameSize + y * width + x*2] = v + nv21[frameSize + y * width + x*2 + 1] = u + } + } + + return nv21 + } } \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/DCGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/DCGetter.kt index 15c3c3b7..b9ca070d 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/DCGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/DCGetter.kt @@ -77,12 +77,12 @@ class DCGetter : BaseGetter { Blog.LOGE("realWork() ${this::class.simpleName}") temp.clear() // https://m.dcinside.com/board/singlebungle1472 + //"https://m.dcinside.com/board/programming", + //"https://m.dcinside.com/board/reading", var urls = arrayListOf( "https://m.dcinside.com", "https://m.dcinside.com/board/singlebungle1472", - "https://m.dcinside.com/board/programming", "https://m.dcinside.com/board/cartoon", - "https://m.dcinside.com/board/reading", "https://m.dcinside.com/board/hit", "https://m.dcinside.com/board/dcbest" ) diff --git a/app/src/main/res/drawable/ic_b.png b/app/src/main/res/drawable/ic_b.png new file mode 100644 index 00000000..8ee30611 Binary files /dev/null and b/app/src/main/res/drawable/ic_b.png differ diff --git a/app/src/main/res/layout/launcher_activity.xml b/app/src/main/res/layout/launcher_activity.xml index d1361b29..3ed31dcd 100644 --- a/app/src/main/res/layout/launcher_activity.xml +++ b/app/src/main/res/layout/launcher_activity.xml @@ -59,6 +59,17 @@ android:id="@+id/comics" style="@style/tabItem" android:layout_height="match_parent"/> + +