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();
// }
}
}

View File

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

View File

@ -13,7 +13,9 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.os.IBinder
import androidx.core.app.ActivityCompat
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.YoutubeGetter
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.Dispatchers
import kotlinx.coroutines.launch
@ -49,6 +53,8 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody
import java.io.File
import java.util.UUID
import java.util.concurrent.TimeUnit
@ -56,7 +62,12 @@ class ForeGroundService : Service() {
companion object {
val ACTION_SENDMSG = "ACTION_SEND_TO_LOVE"
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) {
ENABLED("enabledBlutooth"),
DISABLED("disableBlutooth"),
@ -78,20 +89,80 @@ class ForeGroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Blog.LOGE("onStartCommand >>> ${intent}")
if (ACTION_SENDMSG.equals(intent?.action)) {
when(intent?.action) {
ACTION_SENDMSG -> {
intent?.getStringExtra(EXTRA_MSGKEY)?.let {
sendToI(it)
}
}
ACTION_VIDEO_DOWNLOAD -> {
intent?.getStringExtra(EXTRA_TARGET_URL)?.let {
Uri.parse(it)?.let {
addToTargetYtubeUrl(it.toString())
}
}
}
}
startForeGround()
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) {
val channel = NotificationChannel(
CHANNEL_ID,
"BLE 서비스 채널",
"BUM'S 서비스",
NotificationManager.IMPORTANCE_HIGH
)
val manager = getSystemService(NotificationManager::class.java)
@ -110,7 +181,7 @@ class ForeGroundService : Service() {
startForeground(NOTIF_ID, NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("BLE 서비스")
.setContentText("실행중입니다.")
.setContentText(str)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setSmallIcon(R.drawable.ic_b)
.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(2,"돼지 버스 내린다요~!"))
.setOngoing(true) // 사용자가 알림을 스와이프로 지울 수 없게 만듦
.setProgress(max, progress, false)
.build())
}
@ -241,6 +313,8 @@ class ForeGroundService : Service() {
return mWorkManager
}
fun sendToI(msg: String) {
if (PrefString.telegramSendTarget.get().length > 5) {
CoroutineScope(Dispatchers.IO).launch {
@ -256,6 +330,8 @@ class ForeGroundService : Service() {
}
}
}
fun sendToI(boolean: Boolean) {
if (PrefString.telegramSendTarget.get().length > 5) {
CoroutineScope(Dispatchers.IO).launch {
@ -280,6 +356,8 @@ class ForeGroundService : Service() {
} else Blog.LOGE("sendToI telegram Error Occurred")
}
}
}
@SuppressLint("MissingPermission")
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_NEW_TASK
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.os.Handler
import android.os.Looper
@ -41,6 +42,11 @@ import androidx.core.view.isVisible
import androidx.work.Worker
import bums.lunatic.launcher.LauncherActivity.Companion.getRuntime
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.DotaxArticles
import bums.lunatic.launcher.model.getRssData
@ -93,7 +99,6 @@ class GeckoWeb : BWebview {
constructor(context: Context?) : super(context) {
buildWeb()
}
var decoViews = arrayListOf<View>()
override fun setVisibility(visibility: Int) {
super.setVisibility(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.promptDelegate = promptDelegate
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
.ensureBuiltIn(extPath, extId)
.accept( // Register message delegate for background script
@ -420,109 +394,6 @@ class GeckoWeb : BWebview {
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 {
var result = origin
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
fun getFilterF() = String(java.util.Base64.getMimeDecoder().decode("aHR0cHM6Ly9pamF2dG9ycmVudC5jb20=".toByteArray()))
@ -890,8 +691,6 @@ class GeckoWeb : BWebview {
lastedUrl = url
}
checkIfDownloadable(url)
}

View File

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

View File

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

View File

@ -1,6 +1,7 @@
package bums.lunatic.launcher.receiver
import android.Manifest
import android.annotation.SuppressLint
import android.app.Notification
import android.content.BroadcastReceiver
import android.content.ComponentName
@ -30,6 +31,7 @@ import bums.lunatic.launcher.model.LocationLog
import bums.lunatic.launcher.model.NotificationItem
import bums.lunatic.launcher.utils.BitmapConverter
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.WorkersDb
import com.google.android.gms.location.LocationServices
@ -70,6 +72,7 @@ class NLService : NotificationListenerService() {
}
@SuppressLint("MissingPermission")
@RequiresApi(Build.VERSION_CODES.S)
override fun onNotificationPosted(sbn: StatusBarNotification) {
Blog.LOGE("onNotificationPosted ${sbn}")
@ -95,25 +98,51 @@ class NLService : NotificationListenerService() {
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()}")
when (sbn.packageName){
"com.kakao.talk" -> {
"com.kakao.taxi" -> {
var defaultMsg : StringBuffer? = StringBuffer("돼지 택시 ")
if (stringBuffer.contains("택시") && stringBuffer.contains("탑승") && stringBuffer.contains("완료")) {
defaultMsg?.append("탔다요~!")
}else if(stringBuffer.contains("택시") && stringBuffer.contains("자동결제") && stringBuffer.contains("물건")) {
defaultMsg?.append("거의 다 왔다요~!")
}else if(stringBuffer.contains("택시") && stringBuffer.contains("도착") && stringBuffer.contains("선택")) {
defaultMsg?.append("내린다요~!")
} else {
defaultMsg = null
}
"kakaopay.app" -> {
if (stringBuffer.contains("모바일") && stringBuffer.contains("교통카드")) {
defaultMsg?.let {
makeMsgByTransferInfomation(it)
}
}
"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, "돼지가 대중교통에${if (!usePublicTransportation){" 탑승 "} else {" 하차"}} 했다요~!") // 전달할 데이터
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])

View File

@ -28,12 +28,14 @@ import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.ImageButton
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.net.toUri
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import bums.lunatic.launcher.LauncherActivity
import bums.lunatic.launcher.LauncherActivity.Companion.getRuntime
import bums.lunatic.launcher.R
import bums.lunatic.launcher.tokiz.common.PairArray
@ -404,6 +406,21 @@ abstract class BaseToki : Fragment(), PagedTextViewInterface {
} else {
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 {
this.pageUrl = url?.toUri()?.path ?: getLastedDoamin() ?: ""
@ -635,6 +652,15 @@ abstract class BaseToki : Fragment(), PagedTextViewInterface {
}
val nullCursor = PointerIcon.getSystemIcon(context!!, PointerIcon.TYPE_NULL)
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
}
@ -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.content.Context
import android.content.Intent
import android.os.Build
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.PointerIcon
import android.view.View
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.utils.Blog
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 java.util.Base64
@ -23,6 +33,7 @@ enum class JxEvent {
}
typealias JxInteface = (JxEvent)->Unit
open class BWebview : GeckoView {
var decoViews = arrayListOf<View>()
@SuppressLint("ClickableViewAccessibility")
constructor(context: Context?) : super(context) {
this.setOnTouchListener { v, event ->
@ -57,8 +68,53 @@ open class BWebview : GeckoView {
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{
override fun onSwipeUp(

View File

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

View File

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

View File

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