This commit is contained in:
lunaticbum 2025-08-22 13:06:45 +09:00
parent 53ba35a3de
commit 4acc01b742
13 changed files with 349 additions and 252 deletions

View File

@ -901,5 +901,5 @@ function autoScrollAndHandleDotax() {
// //
// // 시작 // // 시작
// scrollAndSend(); // scrollAndSend();
// } }
} }

View File

@ -76,16 +76,21 @@ import bums.lunatic.launcher.tokiz.Novels
import bums.lunatic.launcher.tokiz.Perplexity import bums.lunatic.launcher.tokiz.Perplexity
import bums.lunatic.launcher.tokiz.Twitter import bums.lunatic.launcher.tokiz.Twitter
import bums.lunatic.launcher.tokiz.Webtoons import bums.lunatic.launcher.tokiz.Webtoons
import bums.lunatic.launcher.tokiz.YouTube
import bums.lunatic.launcher.tokiz.Zota import bums.lunatic.launcher.tokiz.Zota
import bums.lunatic.launcher.utils.Blog import bums.lunatic.launcher.utils.Blog
import bums.lunatic.launcher.utils.KakaoPublicTransfer
import bums.lunatic.launcher.workers.WorkersDb import bums.lunatic.launcher.workers.WorkersDb
import com.google.android.gms.common.util.DataUtils
import com.google.android.material.color.DynamicColors import com.google.android.material.color.DynamicColors
import com.google.gson.Gson
import com.yausername.ffmpeg.FFmpeg import com.yausername.ffmpeg.FFmpeg
import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLException import com.yausername.youtubedl_android.YoutubeDLException
import io.realm.kotlin.ext.query import io.realm.kotlin.ext.query
import kr.lunaticbum.utils.ui.DisplayUtil import kr.lunaticbum.utils.ui.DisplayUtil
import org.json.JSONObject import org.json.JSONObject
import org.jsoup.helper.DataUtil
import org.mozilla.geckoview.ExperimentDelegate import org.mozilla.geckoview.ExperimentDelegate
import org.mozilla.geckoview.GeckoResult import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoRuntime import org.mozilla.geckoview.GeckoRuntime
@ -465,6 +470,7 @@ open class LauncherActivity : CommonActivity() {
} catch (e: YoutubeDLException) { } catch (e: YoutubeDLException) {
Blog.LOGE("failed to initialize youtubedl-android", e) Blog.LOGE("failed to initialize youtubedl-android", e)
} }
val intent = Intent(this, ForeGroundService::class.java) val intent = Intent(this, ForeGroundService::class.java)
this.startForegroundService(intent) this.startForegroundService(intent)
@ -536,6 +542,11 @@ open class LauncherActivity : CommonActivity() {
.replace(R.id.fragment_container, Comics()) .replace(R.id.fragment_container, Comics())
.commit() .commit()
} }
R.id.youtube ->{
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, YouTube())
.commit()
}
R.id.perplexity ->{ R.id.perplexity ->{
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, Perplexity()) .replace(R.id.fragment_container, Perplexity())
@ -679,6 +690,9 @@ open class LauncherActivity : CommonActivity() {
currentFragment.doNextPage() currentFragment.doNextPage()
} }
} }
is YouTube -> {
currentFragment.back()
}
is Novels -> { is Novels -> {
currentFragment.actionNextEvent(false) currentFragment.actionNextEvent(false)
} }

View File

@ -13,7 +13,9 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment
import android.os.IBinder import android.os.IBinder
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
@ -41,6 +43,8 @@ import bums.lunatic.launcher.workers.RuliWebGetter
import bums.lunatic.launcher.workers.TheQooGetter import bums.lunatic.launcher.workers.TheQooGetter
import bums.lunatic.launcher.workers.YoutubeGetter import bums.lunatic.launcher.workers.YoutubeGetter
import bums.lunatic.launcher.workers.YoutubeGetter.Companion.YT_WORK_TAG import bums.lunatic.launcher.workers.YoutubeGetter.Companion.YT_WORK_TAG
import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLRequest
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -49,6 +53,8 @@ import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import okhttp3.ResponseBody import okhttp3.ResponseBody
import java.io.File
import java.util.UUID
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -56,7 +62,12 @@ class ForeGroundService : Service() {
companion object { companion object {
val ACTION_SENDMSG = "ACTION_SEND_TO_LOVE" val ACTION_SENDMSG = "ACTION_SEND_TO_LOVE"
val EXTRA_MSGKEY = "SEND_MSG" val EXTRA_MSGKEY = "SEND_MSG"
val ACTION_VIDEO_DOWNLOAD = "ACTION_YTURL_DOWNLOAD"
val EXTRA_TARGET_URL = "ACTION_SEND_TO_LOVE"
val targetUrls = arrayListOf<String>()
} }
enum class BLUETOOTH_STATE(val statestr: String) { enum class BLUETOOTH_STATE(val statestr: String) {
ENABLED("enabledBlutooth"), ENABLED("enabledBlutooth"),
DISABLED("disableBlutooth"), DISABLED("disableBlutooth"),
@ -78,20 +89,80 @@ class ForeGroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Blog.LOGE("onStartCommand >>> ${intent}") Blog.LOGE("onStartCommand >>> ${intent}")
if (ACTION_SENDMSG.equals(intent?.action)) { when(intent?.action) {
intent?.getStringExtra(EXTRA_MSGKEY)?.let { ACTION_SENDMSG -> {
sendToI(it) intent?.getStringExtra(EXTRA_MSGKEY)?.let {
sendToI(it)
}
}
ACTION_VIDEO_DOWNLOAD -> {
intent?.getStringExtra(EXTRA_TARGET_URL)?.let {
Uri.parse(it)?.let {
addToTargetYtubeUrl(it.toString())
}
}
} }
} }
startForeGround() startForeGround()
return START_STICKY return START_STICKY
} }
fun startForeGround() { fun addToTargetYtubeUrl(url : String) {
targetUrls.add(url)
if((targetUrls?.size ?: 0) > 0) {
downloadVideo(targetUrls?.firstOrNull())
}
}
var currentProcessId : String? = null
set(value) {
field = value
if (value == null) {
startForeGround(0,0)
}
}
fun downloadVideo(url: String?) {
url?.let {
CoroutineScope(Dispatchers.IO).launch {
try {
val youtubeDLDir = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
"youtubedl-android"
)
val command = YoutubeDLRequest(url)
command.addOption("-o", youtubeDLDir.getAbsolutePath() + "/%(title)s.%(ext)s");
currentProcessId = UUID.randomUUID().toString()
YoutubeDL.getInstance()
.execute(command, currentProcessId) { progress, est, str ->
startForeGround(100, progress.toInt(),str)
if (progress >= 100) {
targetUrls.remove(url)
currentProcessId = null
if((targetUrls?.size ?: 0) > 0) {
downloadVideo(targetUrls?.firstOrNull())
}
}
}
} catch (e: Exception) {
e.printStackTrace()
currentProcessId = null
if((targetUrls?.size ?: 0) > 0) {
downloadVideo(targetUrls?.firstOrNull())
}
}
}
}
}
fun startForeGround(max : Int = 0 , progress : Int = 0, str : String = "실행중입니다.") {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel( val channel = NotificationChannel(
CHANNEL_ID, CHANNEL_ID,
"BLE 서비스 채널", "BUM'S 서비스",
NotificationManager.IMPORTANCE_HIGH NotificationManager.IMPORTANCE_HIGH
) )
val manager = getSystemService(NotificationManager::class.java) val manager = getSystemService(NotificationManager::class.java)
@ -110,7 +181,7 @@ class ForeGroundService : Service() {
startForeground(NOTIF_ID, NotificationCompat.Builder(this, CHANNEL_ID) startForeground(NOTIF_ID, NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("BLE 서비스") .setContentTitle("BLE 서비스")
.setContentText("실행중입니다.") .setContentText(str)
.setPriority(NotificationCompat.PRIORITY_MAX) .setPriority(NotificationCompat.PRIORITY_MAX)
.setSmallIcon(R.drawable.ic_b) .setSmallIcon(R.drawable.ic_b)
.setContentIntent(pendingIntent) .setContentIntent(pendingIntent)
@ -118,6 +189,7 @@ class ForeGroundService : Service() {
.addAction(android.R.drawable.ic_btn_speak_now,"버스 탐", makeSendMsgAction(1,"돼지 버스 탔다요~!")) .addAction(android.R.drawable.ic_btn_speak_now,"버스 탐", makeSendMsgAction(1,"돼지 버스 탔다요~!"))
.addAction(android.R.drawable.ic_btn_speak_now,"버스 내림", makeSendMsgAction(2,"돼지 버스 내린다요~!")) .addAction(android.R.drawable.ic_btn_speak_now,"버스 내림", makeSendMsgAction(2,"돼지 버스 내린다요~!"))
.setOngoing(true) // 사용자가 알림을 스와이프로 지울 수 없게 만듦 .setOngoing(true) // 사용자가 알림을 스와이프로 지울 수 없게 만듦
.setProgress(max, progress, false)
.build()) .build())
} }
@ -241,6 +313,8 @@ class ForeGroundService : Service() {
return mWorkManager return mWorkManager
} }
fun sendToI(msg: String) { fun sendToI(msg: String) {
if (PrefString.telegramSendTarget.get().length > 5) { if (PrefString.telegramSendTarget.get().length > 5) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
@ -256,6 +330,8 @@ class ForeGroundService : Service() {
} }
} }
} }
fun sendToI(boolean: Boolean) { fun sendToI(boolean: Boolean) {
if (PrefString.telegramSendTarget.get().length > 5) { if (PrefString.telegramSendTarget.get().length > 5) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
@ -280,6 +356,8 @@ class ForeGroundService : Service() {
} else Blog.LOGE("sendToI telegram Error Occurred") } else Blog.LOGE("sendToI telegram Error Occurred")
} }
} }
} }
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
fun isConnected(device: BluetoothDevice): Boolean { fun isConnected(device: BluetoothDevice): Boolean {

View File

@ -11,6 +11,7 @@ import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Environment import android.os.Environment
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
@ -41,6 +42,11 @@ import androidx.core.view.isVisible
import androidx.work.Worker import androidx.work.Worker
import bums.lunatic.launcher.LauncherActivity.Companion.getRuntime import bums.lunatic.launcher.LauncherActivity.Companion.getRuntime
import bums.lunatic.launcher.R import bums.lunatic.launcher.R
import bums.lunatic.launcher.helpers.ForeGroundService
import bums.lunatic.launcher.helpers.ForeGroundService.Companion.ACTION_SENDMSG
import bums.lunatic.launcher.helpers.ForeGroundService.Companion.ACTION_VIDEO_DOWNLOAD
import bums.lunatic.launcher.helpers.ForeGroundService.Companion.EXTRA_MSGKEY
import bums.lunatic.launcher.helpers.ForeGroundService.Companion.EXTRA_TARGET_URL
import bums.lunatic.launcher.model.Dotax import bums.lunatic.launcher.model.Dotax
import bums.lunatic.launcher.model.DotaxArticles import bums.lunatic.launcher.model.DotaxArticles
import bums.lunatic.launcher.model.getRssData import bums.lunatic.launcher.model.getRssData
@ -93,7 +99,6 @@ class GeckoWeb : BWebview {
constructor(context: Context?) : super(context) { constructor(context: Context?) : super(context) {
buildWeb() buildWeb()
} }
var decoViews = arrayListOf<View>()
override fun setVisibility(visibility: Int) { override fun setVisibility(visibility: Int) {
super.setVisibility(visibility) super.setVisibility(visibility)
decoViews.filter { it != null && it.id > -1 && it.id != R.id.dl_video }.forEach { it.visibility = visibility } decoViews.filter { it != null && it.id > -1 && it.id != R.id.dl_video }.forEach { it.visibility = visibility }
@ -124,38 +129,7 @@ class GeckoWeb : BWebview {
session.mediaDelegate = mediaDelegate session.mediaDelegate = mediaDelegate
session.promptDelegate = promptDelegate session.promptDelegate = promptDelegate
session.mediaSessionDelegate = mediaSessionDelegate session.mediaSessionDelegate = mediaSessionDelegate
// session.permissionDelegate = (object : PermissionDelegate {
// override fun onContentPermissionRequest(
// session: GeckoSession,
// perm: PermissionDelegate.ContentPermission
// ): GeckoResult<Int?>? {
//
// return super.onContentPermissionRequest(session, perm)
// }
//
// override fun onAndroidPermissionsRequest(
// session: GeckoSession,
// permissions: Array<out String?>?,
// callback: PermissionDelegate.Callback
// ) {
// super.onAndroidPermissionsRequest(session, permissions, callback)
// }
//
// override fun onMediaPermissionRequest(
// session: GeckoSession,
// uri: String,
// video: Array<out PermissionDelegate.MediaSource?>?,
// audio: Array<out PermissionDelegate.MediaSource?>?,
// callback: PermissionDelegate.MediaCallback
// ) {
//
// // 첫 번째 비디오·오디오 소스를 허용
//
// callback.grant(video?.firstOrNull(), audio?.firstOrNull())
// }
//
//
// });
it.webExtensionController it.webExtensionController
.ensureBuiltIn(extPath, extId) .ensureBuiltIn(extPath, extId)
.accept( // Register message delegate for background script .accept( // Register message delegate for background script
@ -420,109 +394,6 @@ class GeckoWeb : BWebview {
mOnSave?.saved() mOnSave?.saved()
} }
fun downloadImage(context: Context, url: Uri, isGif : Boolean = false) {
Blog.LOGE("url.lastPathSegment ${url.lastPathSegment}")
val fileName = url.host + "_${SimpleDateFormat("yyyyMMddHHmmsss").format(Date())}.${if(isGif){"gif"}else{"jpg"}}"
val request = DownloadManager.Request(url)
request.setTitle(fileName)
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE)
request.setDescription("이미지 다운로드 중...")
request.setAllowedOverMetered(true) // 선택 사항 - 데이터 요금제 네트워크에서도 허용
request.setVisibleInDownloadsUi(true)
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
// 네트워크타입, 알림설정 등 옵션 추가 가능
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
val dm = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
Toast.makeText(context, "다운로드 시작: $fileName", Toast.LENGTH_SHORT).show()
val downloadId = dm.enqueue(request)
monitorDownloadStatus(dm, downloadId, context)
}
fun monitorDownloadStatus(dm: DownloadManager, downloadId: Long, context: Context) {
val handler = CoroutineExceptionHandler { _, exception ->
// 에러 처리 로직 (선택)
exception.printStackTrace()
}
// 백그라운드에서 5초 간격으로 상태 체크
CoroutineScope(Dispatchers.IO + handler).launch {
while (true) {
val query = DownloadManager.Query().setFilterById(downloadId)
val cursor = dm.query(query)
var downloadFinished = false
if (cursor != null && cursor.moveToFirst()) {
val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
when (status) {
DownloadManager.STATUS_SUCCESSFUL -> {
// 다운로드 성공 처리
withContext(Dispatchers.Main) {
// UI 갱신 등 메인 스레드 작업 (필요 시)
}
downloadFinished = true
}
DownloadManager.STATUS_FAILED -> {
val reason = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON))
// 여기에 reason값에 따라 적절한 처리 또는 로깅 수행
Log.e("DownloadManager", "Download failed with reason code: $reason")
// 다운로드 실패 처리
withContext(Dispatchers.Main) {
// UI 갱신 등 메인 스레드 작업 (필요 시)
}
downloadFinished = true
}
else -> {
Blog.LOGE("DownloadManager.STATUS >> ${status}")
}
// 진행 중, 대기 중 등 기타 상태는 계속 확인
}
}
cursor?.close()
if (downloadFinished) break
delay(5000L) // 5초 대기 후 다시 반복
}
}
}
fun checkIfDownloadable(url: String) {
CoroutineScope(Dispatchers.Main).launch {
runOnUiThread {
decoViews.filter { it.id == R.id.dl_video }.firstOrNull()?.let {
it.setOnClickListener {}
it.visibility = View.GONE
}}}
Blog.LOGE("checkIfDownloadable ${url}")
CoroutineScope(Dispatchers.IO).launch {
try {
val videoInfo = YoutubeDL.getInstance().getInfo(url)
// videoInfo 가 null 아니고, 필요한 키(예: title, url 등)가 있으면 다운로드 가능
Blog.LOGE("checkIfDownloadable ${url}\n videoInfo : ${videoInfo}")
var canVideoDown = videoInfo != null && !videoInfo.title.isNullOrEmpty()
CoroutineScope(Dispatchers.Main).launch {
runOnUiThread {
decoViews.filter { it.id == R.id.dl_video }.firstOrNull()?.let {
it.setOnClickListener {
videoDlownLoad(url)
}
it.visibility = if (canVideoDown){View.VISIBLE} else{View.GONE}
}
}
}
} catch (e: Exception) {
Blog.LOGE("checkIfDownloadable ${url} ${e}")
CoroutineScope(Dispatchers.Main).launch {
runOnUiThread {
decoViews.filter { it.id == R.id.dl_video }.firstOrNull()?.let {
it.setOnClickListener {}
it.visibility = View.GONE
}}}
}
}
}
fun replaceDcUrl(origin: String): String { fun replaceDcUrl(origin: String): String {
var result = origin var result = origin
for (i in 0..19) { for (i in 0..19) {
@ -562,76 +433,6 @@ class GeckoWeb : BWebview {
lateinit var progressDialog: AlertDialog
fun showProgressDialog() {
val dialogView = layoutInflater.inflate(R.layout.progress_dialog, null)
val progressBar = dialogView.findViewById<ProgressBar>(R.id.progressBar)
val textProgress = dialogView.findViewById<TextView>(R.id.textProgress)
val btn = dialogView.findViewById<android.widget.Button>(R.id.dl_cancel)
progressDialog = AlertDialog.Builder(context)
.setTitle("다운로드 중...")
.setView(dialogView)
.setCancelable(false)
.create()
progressDialog.show()
// UI 업데이트 함수 예 (나중에 실행)
fun updateProgress(progress: Int, est : Long, str : String) {
runOnUiThread {
progressBar.progress = progress
textProgress.text = "$progress%"
}
}
}
fun dismissProgressDialog() {
progressDialog.dismiss()
}
suspend fun downloadVideo(processId : String,url: String, updateProgress: (Float, Long, String) -> Unit) = withContext(Dispatchers.IO) {
val youtubeDLDir = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
"youtubedl-android"
)
val command = YoutubeDLRequest(url)
command.addOption("-o", youtubeDLDir.getAbsolutePath() + "/%(title)s.%(ext)s");
var process = YoutubeDL.getInstance().execute(command,processId) { progress, est , str ->
updateProgress(progress, est, str)
}
return@withContext process
}
fun videoDlownLoad(videoUrl : String) {
CoroutineScope(Dispatchers.Main).launch {
try {
showProgressDialog()
var res: YoutubeDLResponse? = null
val processId = UUID.randomUUID().toString()
res = downloadVideo(processId, videoUrl) { progress , time , str->
runOnUiThread {
val pb =
progressDialog.findViewById<ProgressBar>(R.id.progressBar)
val tv =
progressDialog.findViewById<TextView>(R.id.textProgress)
pb?.progress = progress.toInt()
val btn = progressDialog.findViewById<android.widget.Button>(R.id.dl_cancel)
tv?.text = "$progress%\n$str"
btn?.setOnClickListener {
progressDialog?.dismiss()
YoutubeDL.getInstance().destroyProcessById(processId);
}
}
}
dismissProgressDialog()
Toast.makeText(context, "다운로드 완료", Toast.LENGTH_SHORT)
.show()
} catch (e: Exception) {
e.printStackTrace()
progressDialog?.dismiss()
Toast.makeText(context, "오류: ${e.message}", Toast.LENGTH_LONG)
.show()
}
}
}
var dialog : Dialog? = null var dialog : Dialog? = null
fun getFilterF() = String(java.util.Base64.getMimeDecoder().decode("aHR0cHM6Ly9pamF2dG9ycmVudC5jb20=".toByteArray())) fun getFilterF() = String(java.util.Base64.getMimeDecoder().decode("aHR0cHM6Ly9pamF2dG9ycmVudC5jb20=".toByteArray()))
@ -890,8 +691,6 @@ class GeckoWeb : BWebview {
lastedUrl = url lastedUrl = url
} }
checkIfDownloadable(url) checkIfDownloadable(url)
} }

View File

@ -281,6 +281,7 @@ internal class RssHome : Fragment() {
} }
} }
fun searchKeyword() { fun searchKeyword() {
binding.geckoWeb.visibility = View.GONE
// val builder: AlertDialog.Builder = AlertDialog.Builder(requireContext()) // val builder: AlertDialog.Builder = AlertDialog.Builder(requireContext())
// builder.setTitle("Keyword") // builder.setTitle("Keyword")
// val viewInflated: View = LayoutInflater.from(requireContext()) // val viewInflated: View = LayoutInflater.from(requireContext())
@ -327,6 +328,7 @@ internal class RssHome : Fragment() {
fun ask() { fun ask() {
binding.geckoWeb.visibility = View.GONE
val bottomSheet = WebBottomSheet() val bottomSheet = WebBottomSheet()
bottomSheet.listener = object : WebBottomSheet.OnGoToWebListener{ bottomSheet.listener = object : WebBottomSheet.OnGoToWebListener{
override fun enterSearch() { override fun enterSearch() {

View File

@ -54,7 +54,7 @@ class SearchBottomSheet : BottomSheetDialogFragment() {
val categoryContainer = view.findViewById<LinearLayout>(R.id.categoryContainer) val categoryContainer = view.findViewById<LinearLayout>(R.id.categoryContainer)
addVote = view.findViewById<CheckBox>(R.id.add_vote) as CheckBox addVote = view.findViewById<CheckBox>(R.id.add_vote) as CheckBox
addRead = view.findViewById<CheckBox>(R.id.add_read) as CheckBox addRead = view.findViewById<CheckBox>(R.id.add_read) as CheckBox
addRead.setOnCheckedChangeListener {v,b->triggerSearchWithDebounce(inputKeyword.text.toString())} addVote.setOnCheckedChangeListener {v,b->triggerSearchWithDebounce(inputKeyword.text.toString())}
addRead.setOnCheckedChangeListener {v,b->triggerSearchWithDebounce(inputKeyword.text.toString())} addRead.setOnCheckedChangeListener {v,b->triggerSearchWithDebounce(inputKeyword.text.toString())}
// 카테고리 목록 // 카테고리 목록
val categories = RssDataType.getAll() val categories = RssDataType.getAll()
@ -66,6 +66,7 @@ class SearchBottomSheet : BottomSheetDialogFragment() {
text = category.name text = category.name
tag = category tag = category
isAllCaps = false isAllCaps = false
this.isSelected = true
setBackgroundResource(android.R.drawable.btn_default) setBackgroundResource(android.R.drawable.btn_default)
setTextColor(Color.WHITE) setTextColor(Color.WHITE)
setBackgroundResource(R.color.tabs_black) setBackgroundResource(R.color.tabs_black)

View File

@ -1,6 +1,7 @@
package bums.lunatic.launcher.receiver package bums.lunatic.launcher.receiver
import android.Manifest import android.Manifest
import android.annotation.SuppressLint
import android.app.Notification import android.app.Notification
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.ComponentName import android.content.ComponentName
@ -30,6 +31,7 @@ import bums.lunatic.launcher.model.LocationLog
import bums.lunatic.launcher.model.NotificationItem import bums.lunatic.launcher.model.NotificationItem
import bums.lunatic.launcher.utils.BitmapConverter import bums.lunatic.launcher.utils.BitmapConverter
import bums.lunatic.launcher.utils.Blog import bums.lunatic.launcher.utils.Blog
import bums.lunatic.launcher.utils.KakaoPublicTransfer
import bums.lunatic.launcher.workers.LocationUpdateService.Companion.inRangeLocation import bums.lunatic.launcher.workers.LocationUpdateService.Companion.inRangeLocation
import bums.lunatic.launcher.workers.WorkersDb import bums.lunatic.launcher.workers.WorkersDb
import com.google.android.gms.location.LocationServices import com.google.android.gms.location.LocationServices
@ -70,6 +72,7 @@ class NLService : NotificationListenerService() {
} }
@SuppressLint("MissingPermission")
@RequiresApi(Build.VERSION_CODES.S) @RequiresApi(Build.VERSION_CODES.S)
override fun onNotificationPosted(sbn: StatusBarNotification) { override fun onNotificationPosted(sbn: StatusBarNotification) {
Blog.LOGE("onNotificationPosted ${sbn}") Blog.LOGE("onNotificationPosted ${sbn}")
@ -95,27 +98,53 @@ class NLService : NotificationListenerService() {
Blog.LOGE("title >> ${title} text >> ${text} bigText >> ${bigText} extraInfo >> ${extraInfo} subText >> ${subText} conversationTitle >> ${conversationTitle} summaryText >> ${summaryText} verificationText >> ${verificationText}") Blog.LOGE("title >> ${title} text >> ${text} bigText >> ${bigText} extraInfo >> ${extraInfo} subText >> ${subText} conversationTitle >> ${conversationTitle} summaryText >> ${summaryText} verificationText >> ${verificationText}")
mHourlyLogWriter?.writeLog("${sbn.packageName}\n${stringBuffer.toString()}") mHourlyLogWriter?.writeLog("${sbn.packageName}\n${stringBuffer.toString()}")
when (sbn.packageName){ when (sbn.packageName){
"com.kakao.talk" -> { "com.kakao.taxi" -> {
var defaultMsg : StringBuffer? = StringBuffer("돼지 택시 ")
} if (stringBuffer.contains("택시") && stringBuffer.contains("탑승") && stringBuffer.contains("완료")) {
"kakaopay.app" -> { defaultMsg?.append("탔다요~!")
if (stringBuffer.contains("모바일") && stringBuffer.contains("교통카드")) { }else if(stringBuffer.contains("택시") && stringBuffer.contains("자동결제") && stringBuffer.contains("물건")) {
var usePublicTransportation = PrefBoolean.usePublicTransportation.get(false) defaultMsg?.append("거의 다 왔다요~!")
PrefBoolean.usePublicTransportation.set(!usePublicTransportation) }else if(stringBuffer.contains("택시") && stringBuffer.contains("도착") && stringBuffer.contains("선택")) {
val actionIntent = Intent(this, ForeGroundService::class.java).apply { defaultMsg?.append("내린다요~!")
action = ACTION_SENDMSG } else {
putExtra(EXTRA_MSGKEY, "돼지가 대중교통에${if (!usePublicTransportation){" 탑승 "} else {" 하차"}} 했다요~!") // 전달할 데이터 defaultMsg = null
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { defaultMsg?.let {
startForegroundService(actionIntent) makeMsgByTransferInfomation(it)
} else {
startService(actionIntent)
}
} }
} }
"com.kakao.talk" -> {
if (stringBuffer.contains("카카오페이") && stringBuffer.contains("모바일") && stringBuffer.contains("교통카드") && stringBuffer.contains("사용 내역")) {
var usePublicTransportation = PrefBoolean.usePublicTransportation.get(false)
PrefBoolean.usePublicTransportation.set(!usePublicTransportation)
var defaultMsg = StringBuffer("돼지가 대중교통에${if (!usePublicTransportation){" 탑승 "} else {" 하차"}} 했다요~!")
KakaoPublicTransfer(stringBuffer.toString()).let {
defaultMsg.append("\n${it.transportType}(${it.transportName})")
defaultMsg.append("\n${it.dateTime}")
}
makeMsgByTransferInfomation(defaultMsg)
}
}
"kakaopay.app" -> {
}
} }
} }
@RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
fun makeMsgByTransferInfomation(stringBuffer : StringBuffer) {
val actionIntent = Intent(this, ForeGroundService::class.java).apply {
action = ACTION_SENDMSG
putExtra(EXTRA_MSGKEY, stringBuffer.toString()) // 전달할 데이터
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(actionIntent)
} else {
startService(actionIntent)
}
pushLocation(this)
}
@RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION]) @RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
fun pushLocation(context: Context) { fun pushLocation(context: Context) {
try { try {

View File

@ -28,12 +28,14 @@ import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.EditText import android.widget.EditText
import android.widget.ImageButton
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import bums.lunatic.launcher.LauncherActivity
import bums.lunatic.launcher.LauncherActivity.Companion.getRuntime import bums.lunatic.launcher.LauncherActivity.Companion.getRuntime
import bums.lunatic.launcher.R import bums.lunatic.launcher.R
import bums.lunatic.launcher.tokiz.common.PairArray import bums.lunatic.launcher.tokiz.common.PairArray
@ -404,6 +406,21 @@ abstract class BaseToki : Fragment(), PagedTextViewInterface {
} else { } else {
lastedUrl = url lastedUrl = url
} }
binding.menuWeb.checkIfDownloadable(url)
binding.menuWeb.decoViews.filter { it != null && it.id > -1 }.forEach {
if (it != null && it.id > -1) {
if (it.id == R.id.back) {
it.setOnClickListener { session.goBack() }
} else if (it.id == R.id.current_address) {
(it as? TextView)?.let {
it.tag = currentTitle
it.text = url
}
}else if (it.id == R.id.reload) {
it.setOnClickListener { session.reload() }
}
}
}
} }
completePageLoad(LastInfo().apply { completePageLoad(LastInfo().apply {
this.pageUrl = url?.toUri()?.path ?: getLastedDoamin() ?: "" this.pageUrl = url?.toUri()?.path ?: getLastedDoamin() ?: ""
@ -635,6 +652,15 @@ abstract class BaseToki : Fragment(), PagedTextViewInterface {
} }
val nullCursor = PointerIcon.getSystemIcon(context!!, PointerIcon.TYPE_NULL) val nullCursor = PointerIcon.getSystemIcon(context!!, PointerIcon.TYPE_NULL)
binding.root.setPointerIcon(nullCursor) binding.root.setPointerIcon(nullCursor)
binding.menuWeb
(activity as? LauncherActivity)?.let { activity ->
binding.menuWeb.decoViews.add(activity.findViewById<TextView>(R.id.current_address))
binding.menuWeb.decoViews.add(activity.findViewById<ImageButton>(R.id.back))
binding.menuWeb.decoViews.add(activity.findViewById<ImageButton>(R.id.reload))
binding.menuWeb.decoViews.add(activity.findViewById<ImageButton>(R.id.dl_video))
}
return binding.root return binding.root
} }
@ -1500,6 +1526,8 @@ abstract class BaseToki : Fragment(), PagedTextViewInterface {
} }
open fun back() {
// binding.menuWeb.session?.goBack()
}
} }

View File

@ -0,0 +1,60 @@
package bums.lunatic.launcher.tokiz
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import bums.lunatic.launcher.tokiz.common.TouchArea
import bums.lunatic.launcher.tokiz.view.PagedTextViewInterface
class YouTube : BaseToki(){
override val contentsType = "youtube"
override var lastNumber : Int = 143
override val webcontentsName : String = "youtube"
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 back() {
binding.menuWeb.session?.goBack()
}
override fun onTouch(touchArea: TouchArea) {
}
override fun onTimeoverTouch() {
}
override fun onSwipeLeft(touchCount: Int) {
}
override fun onSwipeRight(touchCount: Int) {
}
override fun onSwipeDown(touchCount: Int) {
}
override fun onSwipeUp(touchCount: Int) {
}
override fun onLongClick() {
}
}

View File

@ -2,15 +2,25 @@ package bums.lunatic.launcher.tokiz.view
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent
import android.os.Build import android.os.Build
import android.util.AttributeSet import android.util.AttributeSet
import android.view.MotionEvent import android.view.MotionEvent
import android.view.PointerIcon import android.view.PointerIcon
import android.view.View import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import bums.lunatic.launcher.R
import bums.lunatic.launcher.helpers.ForeGroundService
import bums.lunatic.launcher.helpers.ForeGroundService.Companion.ACTION_VIDEO_DOWNLOAD
import bums.lunatic.launcher.helpers.ForeGroundService.Companion.EXTRA_TARGET_URL
import bums.lunatic.launcher.tokiz.common.TouchArea 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 com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.mozilla.gecko.util.ThreadUtils.runOnUiThread
import org.mozilla.geckoview.GeckoView import org.mozilla.geckoview.GeckoView
import java.util.Base64 import java.util.Base64
@ -23,6 +33,7 @@ enum class JxEvent {
} }
typealias JxInteface = (JxEvent)->Unit typealias JxInteface = (JxEvent)->Unit
open class BWebview : GeckoView { open class BWebview : GeckoView {
var decoViews = arrayListOf<View>()
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
constructor(context: Context?) : super(context) { constructor(context: Context?) : super(context) {
this.setOnTouchListener { v, event -> this.setOnTouchListener { v, event ->
@ -57,8 +68,53 @@ open class BWebview : GeckoView {
this.setPointerIcon(nullCursor) this.setPointerIcon(nullCursor)
} }
} }
fun videoDlownLoad(videoUrl : String) {
val actionIntent = Intent(context, ForeGroundService::class.java).apply {
action = ACTION_VIDEO_DOWNLOAD
putExtra(EXTRA_TARGET_URL, videoUrl) // 전달할 데이터
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(actionIntent)
} else {
context.startService(actionIntent)
}
}
fun checkIfDownloadable(url: String) {
CoroutineScope(Dispatchers.Main).launch {
runOnUiThread {
decoViews.filter { it.id == R.id.dl_video }.firstOrNull()?.let {
it.setOnClickListener {}
it.visibility = View.GONE
}}}
Blog.LOGE("checkIfDownloadable ${url}")
CoroutineScope(Dispatchers.IO).launch {
try {
val videoInfo = YoutubeDL.getInstance().getInfo(url)
// videoInfo 가 null 아니고, 필요한 키(예: title, url 등)가 있으면 다운로드 가능
Blog.LOGE("checkIfDownloadable ${url}\n videoInfo : ${videoInfo}")
var canVideoDown = videoInfo != null && !videoInfo.title.isNullOrEmpty()
CoroutineScope(Dispatchers.Main).launch {
runOnUiThread {
decoViews.filter { it.id == R.id.dl_video }.firstOrNull()?.let {
it.setOnClickListener {
videoDlownLoad(url)
}
it.visibility = if (canVideoDown){View.VISIBLE} else{View.GONE}
}
}
}
} catch (e: Exception) {
Blog.LOGE("checkIfDownloadable ${url} ${e}")
CoroutineScope(Dispatchers.Main).launch {
runOnUiThread {
decoViews.filter { it.id == R.id.dl_video }.firstOrNull()?.let {
it.setOnClickListener {}
it.visibility = View.GONE
}}}
}
}
}
val mSimpleFingerGestures = SimpleFingerGestures(omfgl = object : SimpleFingerGestures.OnFingerGestureListener{ val mSimpleFingerGestures = SimpleFingerGestures(omfgl = object : SimpleFingerGestures.OnFingerGestureListener{
override fun onSwipeUp( override fun onSwipeUp(

View File

@ -8,6 +8,27 @@ import android.provider.ContactsContract.PhoneLookup
import java.util.Calendar import java.util.Calendar
import java.util.Date import java.util.Date
data class KakaoPayTransitHistory(
val dateTime: String,
val transportType: String,
val transportName: String,
val balance: Int
)
fun KakaoPublicTransfer(raw : String) : KakaoPayTransitHistory {
val dateTimeRegex = Regex("""사용일시\s*:\s*([^\n]+)""")
val transportTypeRegex = Regex("""이용수단\s*:\s*([^\n]+)""")
val transportNameRegex = Regex("""이용수단명\s*:\s*([^\n]+)""")
val balanceRegex = Regex("""잔액\s*:\s*([\d,]+)""")
val dateTime = dateTimeRegex.find(raw)?.groups?.get(1)?.value?.trim() ?: ""
val transportType = transportTypeRegex.find(raw)?.groups?.get(1)?.value?.trim() ?: ""
val transportName = transportNameRegex.find(raw)?.groups?.get(1)?.value?.trim() ?: ""
val balance = balanceRegex.find(raw)?.groups?.get(1)?.value?.replace(",", "")?.toInt() ?: 0
return KakaoPayTransitHistory(dateTime, transportType, transportName, balance)
}
fun afterDay(date: Long): Long { fun afterDay(date: Long): Long {
val cal: Calendar = Calendar.getInstance() val cal: Calendar = Calendar.getInstance()
cal.setTime(Date(date)) cal.setTime(Date(date))

View File

@ -33,9 +33,9 @@
android:src="@drawable/back_vector" android:src="@drawable/back_vector"
android:tint="@color/white" android:tint="@color/white"
android:foregroundTint="@color/white" android:foregroundTint="@color/white"
android:layout_width="40dp" android:layout_width="@dimen/main_top_height"
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
android:layout_height="40dp" /> android:layout_height="@dimen/main_top_height" />
<ImageButton <ImageButton
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/fragment_container" app:layout_constraintTop_toBottomOf="@id/fragment_container"
@ -48,9 +48,9 @@
android:src="@drawable/ic_refresh" android:src="@drawable/ic_refresh"
android:tint="@color/white" android:tint="@color/white"
android:foregroundTint="@color/white" android:foregroundTint="@color/white"
android:layout_width="40dp" android:layout_width="@dimen/main_top_height"
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
android:layout_height="40dp" /> android:layout_height="@dimen/main_top_height" />
<TextView <TextView
android:text="asdasdsadasd" android:text="asdasdsadasd"
android:id="@+id/current_address" android:id="@+id/current_address"
@ -63,7 +63,8 @@
android:ellipsize="middle" android:ellipsize="middle"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="40dp"/> android:layout_height="@dimen/main_top_height"/>
"/>
<ImageButton <ImageButton
app:layout_constraintTop_toTopOf="@id/back" app:layout_constraintTop_toTopOf="@id/back"
app:layout_constraintRight_toLeftOf="@id/share" app:layout_constraintRight_toLeftOf="@id/share"
@ -76,9 +77,9 @@
android:tint="@color/white" android:tint="@color/white"
android:foregroundTint="@color/white" android:foregroundTint="@color/white"
android:src="@drawable/dl_vid" android:src="@drawable/dl_vid"
android:layout_width="40dp" android:layout_width="@dimen/main_top_height"
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
android:layout_height="40dp" /> android:layout_height="@dimen/main_top_height" />
<ImageButton <ImageButton
app:layout_constraintTop_toTopOf="@id/back" app:layout_constraintTop_toTopOf="@id/back"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
@ -92,9 +93,9 @@
android:tint="@color/white" android:tint="@color/white"
android:foregroundTint="@color/white" android:foregroundTint="@color/white"
android:src="@drawable/ic_share" android:src="@drawable/ic_share"
android:layout_width="40dp" android:layout_width="@dimen/main_top_height"
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
android:layout_height="40dp" /> android:layout_height="@dimen/main_top_height" />
<bums.lunatic.launcher.view.FloatingActionMenu <bums.lunatic.launcher.view.FloatingActionMenu
android:id="@+id/floating_action_menu" android:id="@+id/floating_action_menu"
android:layout_margin="5dp" android:layout_margin="5dp"
@ -138,6 +139,14 @@
android:onClick="floatClick" android:onClick="floatClick"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="20dp"/> android:layout_height="20dp"/>
<bums.lunatic.launcher.view.FloatingActionButton
app:fab_label="youtube"
android:id="@+id/youtube"
app:fab_showShadow="true"
app:fab_size="mini"
android:onClick="floatClick"
android:layout_width="wrap_content"
android:layout_height="20dp"/>
<bums.lunatic.launcher.view.FloatingActionButton <bums.lunatic.launcher.view.FloatingActionButton
app:fab_label="perplexity" app:fab_label="perplexity"
android:id="@+id/perplexity" android:id="@+id/perplexity"

View File

@ -33,9 +33,9 @@
android:visibility="visible" android:visibility="visible"
android:background="@null" android:background="@null"
android:src="@drawable/saved" android:src="@drawable/saved"
android:layout_width="40dp" android:layout_width="@dimen/main_top_height"
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
android:layout_height="40dp" /> android:layout_height="@dimen/main_top_height" />
<ImageButton <ImageButton
android:id="@+id/hide" android:id="@+id/hide"
@ -46,11 +46,11 @@
android:layout_marginLeft="12dp" android:layout_marginLeft="12dp"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:background="@null" android:background="@null"
android:layout_width="40dp" android:layout_width="@dimen/main_top_height"
android:visibility="visible" android:visibility="visible"
android:adjustViewBounds="true" android:adjustViewBounds="true"
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
android:layout_height="40dp" android:layout_height="@dimen/main_top_height"
/> />
@ -62,9 +62,9 @@
android:tintMode="multiply" android:tintMode="multiply"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:background="@null" android:background="@null"
android:layout_width="40dp" android:layout_width="@dimen/main_top_height"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:layout_height="40dp" android:layout_height="@dimen/main_top_height"
app:tint="@color/white" app:tint="@color/white"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
@ -75,10 +75,10 @@
android:src="@drawable/bookmark" android:src="@drawable/bookmark"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:background="@null" android:background="@null"
android:layout_width="40dp" android:layout_width="@dimen/main_top_height"
android:adjustViewBounds="true" android:adjustViewBounds="true"
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
android:layout_height="40dp"/> android:layout_height="@dimen/main_top_height"/>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView