This commit is contained in:
lunaticbum 2025-08-18 18:24:19 +09:00
parent 44ac92ca17
commit b34750099e
5 changed files with 290 additions and 15 deletions

View File

@ -385,10 +385,20 @@ const domainRules = [
{ test: url => url.includes("torrentzota"), handler: handleToreentZota}, { test: url => url.includes("torrentzota"), handler: handleToreentZota},
{ test: url => url.includes("acrofan.com") && document.querySelectorAll('[id^="wide"]').length > 0, handler: handleAcrofan }, { test: url => url.includes("acrofan.com") && document.querySelectorAll('[id^="wide"]').length > 0, handler: handleAcrofan },
{ test: url => url.includes("yna.co.kr") && document.querySelectorAll('[class^="wrapper"]').length > 0, handler: handleYna }, { test: url => url.includes("yna.co.kr") && document.querySelectorAll('[class^="wrapper"]').length > 0, handler: handleYna },
{ test: url => url.includes("yt1d.com/"), handler: ytDown },
{ test: url => url.includes("clien") && document.querySelectorAll('[class^="content_view"]').length > 0, handler: handleClien }, { test: url => url.includes("clien") && document.querySelectorAll('[class^="content_view"]').length > 0, handler: handleClien },
{ test: url => url.includes("toki") && (document.querySelectorAll('[id^="id_mbv"]').length > 0 || document.querySelectorAll('[class^="basic-banner"]').length > 0), handler: handleToki }, { test: url => url.includes("toki") && (document.querySelectorAll('[id^="id_mbv"]').length > 0 || document.querySelectorAll('[class^="basic-banner"]').length > 0), handler: handleToki },
]; ];
function ytDown() {
navigator.clipboard.readText().then(function(text) {
document.querySelector("#txt-url").value = text;
}).catch(function(err) {
alert('클립보드 읽기에 실패했습니다: ' + err);
});
}
function handleCommon() { function handleCommon() {
// 공통 광고 제거 // 공통 광고 제거
if (document.querySelector(".top_google_ad_space")) document.querySelector(".top_google_ad_space").remove(); if (document.querySelector(".top_google_ad_space")) document.querySelector(".top_google_ad_space").remove();
@ -752,6 +762,10 @@ function handleDcinside() {
document.querySelectorAll( document.querySelectorAll(
'[id^="view_btn_area"], [class^="trend-rank"], [class^="view-btm-con"], [class^="md-tit-box"], [class^="gall-detail-lst"], [class^="outside-search-box"], [class^="footer ftlong"], [class^="adv-group"], li[style^="cursor:default;"], [id^="div_adnmore_area"]' '[id^="view_btn_area"], [class^="trend-rank"], [class^="view-btm-con"], [class^="md-tit-box"], [class^="gall-detail-lst"], [class^="outside-search-box"], [class^="footer ftlong"], [class^="adv-group"], li[style^="cursor:default;"], [id^="div_adnmore_area"]'
).forEach(e => e.remove()); ).forEach(e => e.remove());
document.querySelectorAll('div[class^="imgwrap"]').forEach(function (e) {
try {e.style.backgroundColor = 'red';} catch (e) {}
})
mainContentsEl = document.querySelector('div[class="container"]'); mainContentsEl = document.querySelector('div[class="container"]');
} }

View File

@ -45,6 +45,7 @@ import android.view.KeyEvent.KEYCODE_BUTTON_Y
import android.view.KeyEvent.KEYCODE_DPAD_DOWN import android.view.KeyEvent.KEYCODE_DPAD_DOWN
import android.view.KeyEvent.KEYCODE_DPAD_UP import android.view.KeyEvent.KEYCODE_DPAD_UP
import android.view.MotionEvent import android.view.MotionEvent
import android.view.PointerIcon
import android.view.View import android.view.View
import android.view.WindowInsets import android.view.WindowInsets
import android.view.WindowManager import android.view.WindowManager
@ -77,6 +78,7 @@ import bums.lunatic.launcher.helpers.Constants.Companion.widgetHostId
import bums.lunatic.launcher.helpers.HeadsetActionButtonReceiver import bums.lunatic.launcher.helpers.HeadsetActionButtonReceiver
import bums.lunatic.launcher.helpers.PrefHelper.putString import bums.lunatic.launcher.helpers.PrefHelper.putString
import bums.lunatic.launcher.helpers.PrefLong import bums.lunatic.launcher.helpers.PrefLong
import bums.lunatic.launcher.home.GeckoWeb
import bums.lunatic.launcher.home.RssHome import bums.lunatic.launcher.home.RssHome
import bums.lunatic.launcher.home.RssViewBuilder import bums.lunatic.launcher.home.RssViewBuilder
import bums.lunatic.launcher.model.RssData import bums.lunatic.launcher.model.RssData
@ -562,7 +564,8 @@ open class LauncherActivity : CommonActivity() {
if (intent?.action == Intent.ACTION_WEB_SEARCH) { if (intent?.action == Intent.ACTION_WEB_SEARCH) {
openWithIntent(intent) openWithIntent(intent)
} }
val nullCursor = PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL)
binding.root.setPointerIcon(nullCursor)
binding.share.setOnClickListener { binding.share.setOnClickListener {
if (binding.currentAddress.text.length > 5) { if (binding.currentAddress.text.length > 5) {
val sendIntent: Intent = Intent().apply { val sendIntent: Intent = Intent().apply {

View File

@ -1,7 +1,10 @@
package bums.lunatic.launcher.home package bums.lunatic.launcher.home
import CustomVideoNodeRenderer import CustomVideoNodeRenderer
import android.app.Dialog
import android.app.DownloadManager import android.app.DownloadManager
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
@ -9,6 +12,8 @@ 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.Environment import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log
@ -23,6 +28,7 @@ import android.view.KeyEvent.KEYCODE_BUTTON_Y
import android.view.KeyEvent.KEYCODE_DPAD_DOWN import android.view.KeyEvent.KEYCODE_DPAD_DOWN
import android.view.KeyEvent.KEYCODE_DPAD_UP import android.view.KeyEvent.KEYCODE_DPAD_UP
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.PointerIcon
import android.view.View import android.view.View
import android.widget.CheckBox import android.widget.CheckBox
import android.widget.EditText import android.widget.EditText
@ -37,6 +43,7 @@ import bums.lunatic.launcher.R
import bums.lunatic.launcher.tokiz.data.model.PortMessage import bums.lunatic.launcher.tokiz.data.model.PortMessage
import bums.lunatic.launcher.tokiz.view.BWebview import bums.lunatic.launcher.tokiz.view.BWebview
import bums.lunatic.launcher.utils.Blog import bums.lunatic.launcher.utils.Blog
import bums.lunatic.launcher.utils.CommonUtils
import bums.lunatic.launcher.workers.WorkersDb import bums.lunatic.launcher.workers.WorkersDb
import com.google.gson.Gson import com.google.gson.Gson
import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter
@ -46,6 +53,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kr.lunaticbum.utils.service.ServiceUtil.getSystemService
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
import org.jsoup.Jsoup import org.jsoup.Jsoup
@ -54,13 +64,13 @@ import org.mozilla.gecko.util.ThreadUtils
import org.mozilla.geckoview.ExperimentDelegate import org.mozilla.geckoview.ExperimentDelegate
import org.mozilla.geckoview.GeckoResult import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.GeckoSession.PermissionDelegate
import org.mozilla.geckoview.MediaSession import org.mozilla.geckoview.MediaSession
import org.mozilla.geckoview.WebExtension import org.mozilla.geckoview.WebExtension
import org.mozilla.geckoview.WebExtension.MessageDelegate import org.mozilla.geckoview.WebExtension.MessageDelegate
import org.mozilla.geckoview.WebExtension.PortDelegate import org.mozilla.geckoview.WebExtension.PortDelegate
import org.mozilla.geckoview.WebExtensionController.AddonManagerDelegate import org.mozilla.geckoview.WebExtensionController.AddonManagerDelegate
import org.mozilla.geckoview.WebRequestError import org.mozilla.geckoview.WebRequestError
import org.mozilla.geckoview.WebResponse
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.InputStream import java.io.InputStream
@ -94,6 +104,7 @@ class GeckoWeb : BWebview {
val session: GeckoSession = GeckoSession() val session: GeckoSession = GeckoSession()
session.open(it) session.open(it)
this.setSession(session) this.setSession(session)
session.contentDelegate = contentDelegate session.contentDelegate = contentDelegate
session.progressDelegate = progressDelegate session.progressDelegate = progressDelegate
session.navigationDelegate = navigationDelegate session.navigationDelegate = navigationDelegate
@ -437,7 +448,7 @@ class GeckoWeb : BWebview {
} }
} }
} }
var dialog : Dialog? = null
fun getFilterF() = String(java.util.Base64.getMimeDecoder().decode("aHR0cHM6Ly9pamF2dG9ycmVudC5jb20=".toByteArray())) fun getFilterF() = String(java.util.Base64.getMimeDecoder().decode("aHR0cHM6Ly9pamF2dG9ycmVudC5jb20=".toByteArray()))
var currentTitle = "" var currentTitle = ""
val contentDelegate = object : GeckoSession.ContentDelegate { val contentDelegate = object : GeckoSession.ContentDelegate {
@ -463,6 +474,62 @@ class GeckoWeb : BWebview {
super.onFirstContentfulPaint(session) super.onFirstContentfulPaint(session)
} }
fun replaceDcUrl(origin: String): String {
var result = origin
for (i in 0..19) {
result = result.replace(String.format("dcimg%d.", i), "dcimg2.")
}
return result
}
fun copyToClipboard(text: String?) {
if (text == null) return
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Media URL", text)
clipboard.setPrimaryClip(clip)
Toast.makeText(context, "주소가 복사되었습니다.", Toast.LENGTH_SHORT).show()
}
override fun onExternalResponse(session: GeckoSession, response: WebResponse) {
Blog.LOGE("response >>> ${response.uri} ")
if (response.uri.contains(".apk")) return
val url = response.uri
val filename = "${url.substringAfterLast("/")}_${System.currentTimeMillis()}.mp4" // 파일명 추출, 없으면 URL에서 가져옴
// 저장 경로 결정
val savePath = File(context.getExternalFilesDir(null), filename)
// OkHttp로 파일 다운로드
val client = OkHttpClient()
val request = Request.Builder()
.url(url)
.addHeader("User-Agent", "Mozilla/5.0")
// 필요시 Referer, 쿠키 등 헤더 추가
.build()
Thread { // 네트워크로 인한 별도 스레드 필요(코루틴도 가능)
val responseOk = client.newCall(request).execute()
if (responseOk.isSuccessful) {
responseOk.body()?.byteStream()?.use { input ->
FileOutputStream(savePath).use { output ->
input.copyTo(output)
}
}
// 다운로드 완료 알림
Handler(Looper.getMainLooper()).post {
Toast.makeText(context, "파일 저장 완료: ${savePath.name}", Toast.LENGTH_SHORT).show()
}
} else {
Handler(Looper.getMainLooper()).post {
Toast.makeText(context, "다운로드 실패: ${responseOk.message()}", Toast.LENGTH_SHORT).show()
}
}
dialog?.dismiss()
}.start()
super.onExternalResponse(session, response)
}
override fun onContextMenu( override fun onContextMenu(
session: GeckoSession, session: GeckoSession,
@ -470,12 +537,37 @@ class GeckoWeb : BWebview {
screenY: Int, screenY: Int,
element: GeckoSession.ContentDelegate.ContextElement element: GeckoSession.ContentDelegate.ContextElement
) { ) {
if (element.type == GeckoSession.ContentDelegate.ContextElement.TYPE_IMAGE) {
Uri.parse(element.srcUri)?.let { if (element.baseUri?.contains("youtube") == true) {
showImageDownloadDialog(context,it) copyToClipboard(lastedUrl)
loadUrl("https://ko.savefrom.net/227lt/#url=${lastedUrl}")
// copyToClipboard(lastedUrl)
// Dialog(context)?.let { dialog ->
// val popupWebView = GeckoWeb(context).apply {
// loadUrl(lastedUrl!!.replace("https://","https://ss"))
// this.dialog = dialog
// }
// dialog.setCanceledOnTouchOutside(true)
// dialog.setContentView(popupWebView)
// dialog.show()
// }
} else {
Blog.LOGE("onContextMenu:: x = ${x}, y = ${y} , element = ${Gson().toJson(element)}")
if (element.type == GeckoSession.ContentDelegate.ContextElement.TYPE_IMAGE) {
element.srcUri?.let {
(if (it.contains("dcimg")){ replaceDcUrl(it) } else { element.srcUri })?.let {
CommonUtils.downloadFileWithOkHttp(context, Uri.parse(element.baseUri), it)
}
}
}else if (element.type == GeckoSession.ContentDelegate.ContextElement.TYPE_VIDEO) {
element.srcUri?.let {
(if (it.contains("dcimg")){ replaceDcUrl(it) } else { element.srcUri })?.let {
CommonUtils.downloadFileWithOkHttp(context, Uri.parse(element.baseUri), it)
}
}
} }
} }
super.onContextMenu(session, screenX, screenY, element) super.onContextMenu(session, screenX, screenY, element)
} }
} }
@ -557,8 +649,18 @@ class GeckoWeb : BWebview {
Blog.LOGE("GeckoView", "onNewSession: $session from WebExtension") Blog.LOGE("GeckoView", "onNewSession: $session from WebExtension")
Uri.parse(uri)?.let { Uri.parse(uri)?.let {
if(it.host?.let { it1 -> lastedUrl?.contains(it1, true) } == true) { if(it.host?.let { it1 ->
lastedUrl?.contains(
it1,
true
)
} == true ||
((it.host?.contains("x.com") ?: false) == true) ||
((it.host?.contains("www.instagram.com") ?: false) == true)
) {
loadUrl(uri) loadUrl(uri)
} else if(uri.contains("googlevideo.com")) {
CommonUtils.downloadFileWithOkHttp(context, Uri.parse(lastedUrl),uri)
} else { } else {
val builder: AlertDialog.Builder = AlertDialog.Builder(context) val builder: AlertDialog.Builder = AlertDialog.Builder(context)
builder.setTitle("Move To\n${uri}") builder.setTitle("Move To\n${uri}")
@ -607,7 +709,8 @@ class GeckoWeb : BWebview {
// url이 현재 로드된 주소입니다. // url이 현재 로드된 주소입니다.
Blog.LOGE("GeckoView", "현재 주소: $url") Blog.LOGE("GeckoView", "현재 주소: $url")
Blog.LOGE("GeckoView", "현재 session: $session") Blog.LOGE("GeckoView", "현재 session: $session")
val nullCursor = PointerIcon.getSystemIcon(context, PointerIcon.TYPE_NULL)
this@GeckoWeb.setPointerIcon(nullCursor)
url?.let { url -> url?.let { url ->
if (url?.contains(getFilterF()) == true && privateMode) { if (url?.contains(getFilterF()) == true && privateMode) {
this@GeckoWeb.visibility = View.INVISIBLE this@GeckoWeb.visibility = View.INVISIBLE

View File

@ -259,7 +259,7 @@ internal class RssHome : Fragment() {
} }
RssDataType.REDDIT -> { RssDataType.REDDIT -> {
openReddit(rss.originPage()) openGecko(rss)
} }
RssDataType.DOTAX -> { RssDataType.DOTAX -> {
@ -267,7 +267,7 @@ internal class RssHome : Fragment() {
} }
RssDataType.YOUTUBE -> { RssDataType.YOUTUBE -> {
openYouTube(rss.originPage()) openGecko(rss)
} }
RssDataType.CLIEN -> { RssDataType.CLIEN -> {
@ -415,7 +415,8 @@ internal class RssHome : Fragment() {
} }
} else if (rssData?.category()?.equals(RssDataType.PRIVATE) == true){ } else if (rssData?.category()?.equals(RssDataType.PRIVATE) == true){
rssData?.let { rssData?.let {
binding.geckoWeb.privateMode = true currentRss = it
synchronized(lasted) { synchronized(lasted) {
if (lasted.isNotEmpty()) { if (lasted.isNotEmpty()) {
lasted.removeAll { target -> target.originPage.equals(it.originPage) } lasted.removeAll { target -> target.originPage.equals(it.originPage) }
@ -423,6 +424,11 @@ internal class RssHome : Fragment() {
} }
appendReadCount(it, 1, false) appendReadCount(it, 1, false)
Blog.LOGE("removeFirst >>> ${Gson().toJson(it)}") Blog.LOGE("removeFirst >>> ${Gson().toJson(it)}")
binding.layoutRssSummary.title.setOnLongClickListener {
currentRss?.originPage?.let { binding.geckoWeb.loadUrl(it)}
binding.layoutRssSummary.root.visibility = View.GONE
true
}
binding.layoutRssSummary.title.tag = it binding.layoutRssSummary.title.tag = it
binding.layoutRssSummary.root.visibility = View.VISIBLE binding.layoutRssSummary.root.visibility = View.VISIBLE
binding.layoutRssSummary.scrollView.scrollTo(0,0) binding.layoutRssSummary.scrollView.scrollTo(0,0)
@ -607,12 +613,12 @@ internal class RssHome : Fragment() {
} }
} }
val nullCursor = PointerIcon.getSystemIcon(requireContext(), PointerIcon.TYPE_NULL) val nullCursor = PointerIcon.getSystemIcon(requireContext(), PointerIcon.TYPE_NULL)
binding.root.setPointerIcon(nullCursor)
binding.search.setOnClickListener { searchKeyword() } binding.search.setOnClickListener { searchKeyword() }
binding.search.setOnLongClickListener{ binding.search.setOnLongClickListener{
ask() ask()
true true
} }
binding.root.setPointerIcon(nullCursor)
binding.geckoWeb.decoViews.add(binding.hide) binding.geckoWeb.decoViews.add(binding.hide)
binding.geckoWeb.decoViews.add(binding.vote) binding.geckoWeb.decoViews.add(binding.vote)
binding.geckoWeb.decoViews.add(binding.progressBar) binding.geckoWeb.decoViews.add(binding.progressBar)

View File

@ -1,9 +1,17 @@
package bums.lunatic.launcher.utils package bums.lunatic.launcher.utils
import android.content.Context import android.content.Context
import android.net.Uri
import android.os.Build import android.os.Build
import java.io.FileOutputStream
import android.webkit.MimeTypeMap
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.File
object CommonUtils { object CommonUtils {
fun dpToPx(context: Context, dp: Float): Int { fun dpToPx(context: Context, dp: Float): Int {
@ -18,4 +26,145 @@ object CommonUtils {
fun hasLollipop(): Boolean { fun hasLollipop(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
} }
fun getFileTypeBySignature(file: File): String? {
val buffer = ByteArray(12) // 동영상 포맷 확인용으로 충분히 크게 버퍼 확장
file.inputStream().use { it.read(buffer, 0, buffer.size) }
// GIF
if (buffer.sliceArray(0..5).contentEquals("GIF87a".toByteArray()) ||
buffer.sliceArray(0..5).contentEquals("GIF89a".toByteArray())) {
return "gif"
}
// JPEG
if (buffer[0] == 0xFF.toByte() && buffer[1] == 0xD8.toByte() && buffer[2] == 0xFF.toByte()) {
return "jpg"
}
// PNG
if (buffer.sliceArray(0..7).contentEquals(byteArrayOf(0x89.toByte(),'P'.toByte(),'N'.toByte(),'G'.toByte(),0x0D,0x0A,0x1A,0x0A))) {
return "png"
}
// MP4 (ftyp... 브랜드 체크, 보통 4~8바이트 확인)
// "ftyp"는 4바이트 offset 4 ~ 7에 위치
if (buffer.size >= 12 && buffer.sliceArray(4..7).contentEquals("ftyp".toByteArray())) {
return "mp4"
}
// AVI (큰 헤더 "RIFF....AVI ")
if (buffer.size >= 12 &&
buffer.sliceArray(0..3).contentEquals("RIFF".toByteArray()) &&
buffer.sliceArray(8..11).contentEquals("AVI ".toByteArray())) {
return "avi"
}
// MKV (Matroska) - EBML 헤더: 0x1A 0x45 0xDF 0xA3
if (buffer.size >= 4 &&
buffer[0] == 0x1A.toByte() && buffer[1] == 0x45.toByte() &&
buffer[2] == 0xDF.toByte() && buffer[3] == 0xA3.toByte()) {
return "mkv"
}
return null
}
fun downloadFileWithOkHttp(context: Context,refferer : Uri, fileUrl: String) {
android.app.AlertDialog.Builder(context)
.setTitle("파일 다운로드")
.setMessage("해당 파일을 다운로드하시겠습니까?")
.setPositiveButton("확인") { _, _ ->
CoroutineScope(Dispatchers.IO).launch {
val client = OkHttpClient()
val request = Request.Builder().url(fileUrl).addHeader("Referer", refferer.toString())
.addHeader("User-Agent", "Mozilla/5.0").build()
val dir = File("/storage/emulated/0/bums_ob/BUM'S PACED /scraped/md")
if (!dir.exists()) {
dir.mkdirs()
} else {
dir.listFiles().forEach { Blog.LOGE("child -> ${it.absolutePath}") }
}
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
withContext(Dispatchers.Main) {
android.widget.Toast.makeText(
context,
"다운로드 실패!\n${fileUrl}\n${response.message()}",
android.widget.Toast.LENGTH_SHORT
).show()
}
} else {
// Content-Type에서 확장자 추출
val contentType = response.header("Content-Type")
Blog.LOGE("downloadFileWithOkHttp contentType $contentType")
var extension = contentType?.let {
MimeTypeMap.getSingleton().getExtensionFromMimeType(it)
}
// 확장자 없으면 URL에서 추출하거나 기본값 "dat" 사용
if (extension.isNullOrBlank()) {
extension = MimeTypeMap.getFileExtensionFromUrl(fileUrl)
if (extension.isNullOrBlank()) {
extension = "dat"
}
}
Blog.LOGE("downloadFileWithOkHttp extension $extension")
// if (extension.equals("bin")){
//// extension = "jpg"
// }
val fileName =
"${
refferer.host?.replace(
",",
"_"
)
}_${System.currentTimeMillis()}.$extension"
val file = File(dir, fileName)
response.body()?.byteStream()?.use { input ->
FileOutputStream(file).use { output ->
input.copyTo(output)
}
}
val resultFile = if (extension in listOf("bin", "dat")) {
val realExt = getFileTypeBySignature(file)
when {
realExt.isNullOrBlank() -> file
realExt == extension -> file
else -> {
val newFile = File(dir, "${
refferer.host?.replace(
",",
"_"
)
}_${System.currentTimeMillis()}.$extension")
if (file.renameTo(newFile)) newFile else file
}
}
} else {
file
}
Blog.LOGE("downloadFileWithOkHttp File saved: ${resultFile.absolutePath}")
withContext(Dispatchers.Main) {
android.widget.Toast.makeText(
context,
"다운로드 완료!\n${resultFile.absolutePath}",
android.widget.Toast.LENGTH_SHORT
).show()
}
}
}
}
}
.setNegativeButton("취소", null)
.show()
}
} }