...
This commit is contained in:
parent
44ac92ca17
commit
b34750099e
@ -385,10 +385,20 @@ const domainRules = [
|
||||
{ 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("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("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() {
|
||||
// 공통 광고 제거
|
||||
if (document.querySelector(".top_google_ad_space")) document.querySelector(".top_google_ad_space").remove();
|
||||
@ -752,6 +762,10 @@ function handleDcinside() {
|
||||
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"]'
|
||||
).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"]');
|
||||
}
|
||||
|
||||
|
||||
@ -45,6 +45,7 @@ import android.view.KeyEvent.KEYCODE_BUTTON_Y
|
||||
import android.view.KeyEvent.KEYCODE_DPAD_DOWN
|
||||
import android.view.KeyEvent.KEYCODE_DPAD_UP
|
||||
import android.view.MotionEvent
|
||||
import android.view.PointerIcon
|
||||
import android.view.View
|
||||
import android.view.WindowInsets
|
||||
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.PrefHelper.putString
|
||||
import bums.lunatic.launcher.helpers.PrefLong
|
||||
import bums.lunatic.launcher.home.GeckoWeb
|
||||
import bums.lunatic.launcher.home.RssHome
|
||||
import bums.lunatic.launcher.home.RssViewBuilder
|
||||
import bums.lunatic.launcher.model.RssData
|
||||
@ -562,7 +564,8 @@ open class LauncherActivity : CommonActivity() {
|
||||
if (intent?.action == Intent.ACTION_WEB_SEARCH) {
|
||||
openWithIntent(intent)
|
||||
}
|
||||
|
||||
val nullCursor = PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL)
|
||||
binding.root.setPointerIcon(nullCursor)
|
||||
binding.share.setOnClickListener {
|
||||
if (binding.currentAddress.text.length > 5) {
|
||||
val sendIntent: Intent = Intent().apply {
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
package bums.lunatic.launcher.home
|
||||
|
||||
import CustomVideoNodeRenderer
|
||||
import android.app.Dialog
|
||||
import android.app.DownloadManager
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
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.net.Uri
|
||||
import android.os.Environment
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.AttributeSet
|
||||
import android.util.Base64
|
||||
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_UP
|
||||
import android.view.LayoutInflater
|
||||
import android.view.PointerIcon
|
||||
import android.view.View
|
||||
import android.widget.CheckBox
|
||||
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.view.BWebview
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import bums.lunatic.launcher.utils.CommonUtils
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
import com.google.gson.Gson
|
||||
import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter
|
||||
@ -46,6 +53,9 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kr.lunaticbum.utils.service.ServiceUtil.getSystemService
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import org.jsoup.Jsoup
|
||||
@ -54,13 +64,13 @@ import org.mozilla.gecko.util.ThreadUtils
|
||||
import org.mozilla.geckoview.ExperimentDelegate
|
||||
import org.mozilla.geckoview.GeckoResult
|
||||
import org.mozilla.geckoview.GeckoSession
|
||||
import org.mozilla.geckoview.GeckoSession.PermissionDelegate
|
||||
import org.mozilla.geckoview.MediaSession
|
||||
import org.mozilla.geckoview.WebExtension
|
||||
import org.mozilla.geckoview.WebExtension.MessageDelegate
|
||||
import org.mozilla.geckoview.WebExtension.PortDelegate
|
||||
import org.mozilla.geckoview.WebExtensionController.AddonManagerDelegate
|
||||
import org.mozilla.geckoview.WebRequestError
|
||||
import org.mozilla.geckoview.WebResponse
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
@ -94,6 +104,7 @@ class GeckoWeb : BWebview {
|
||||
val session: GeckoSession = GeckoSession()
|
||||
session.open(it)
|
||||
this.setSession(session)
|
||||
|
||||
session.contentDelegate = contentDelegate
|
||||
session.progressDelegate = progressDelegate
|
||||
session.navigationDelegate = navigationDelegate
|
||||
@ -437,7 +448,7 @@ class GeckoWeb : BWebview {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var dialog : Dialog? = null
|
||||
fun getFilterF() = String(java.util.Base64.getMimeDecoder().decode("aHR0cHM6Ly9pamF2dG9ycmVudC5jb20=".toByteArray()))
|
||||
var currentTitle = ""
|
||||
val contentDelegate = object : GeckoSession.ContentDelegate {
|
||||
@ -463,6 +474,62 @@ class GeckoWeb : BWebview {
|
||||
|
||||
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(
|
||||
session: GeckoSession,
|
||||
@ -470,12 +537,37 @@ class GeckoWeb : BWebview {
|
||||
screenY: Int,
|
||||
element: GeckoSession.ContentDelegate.ContextElement
|
||||
) {
|
||||
if (element.type == GeckoSession.ContentDelegate.ContextElement.TYPE_IMAGE) {
|
||||
Uri.parse(element.srcUri)?.let {
|
||||
showImageDownloadDialog(context,it)
|
||||
}
|
||||
}
|
||||
|
||||
if (element.baseUri?.contains("youtube") == true) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -557,8 +649,18 @@ class GeckoWeb : BWebview {
|
||||
Blog.LOGE("GeckoView", "onNewSession: $session from WebExtension")
|
||||
|
||||
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)
|
||||
} else if(uri.contains("googlevideo.com")) {
|
||||
CommonUtils.downloadFileWithOkHttp(context, Uri.parse(lastedUrl),uri)
|
||||
} else {
|
||||
val builder: AlertDialog.Builder = AlertDialog.Builder(context)
|
||||
builder.setTitle("Move To\n${uri}")
|
||||
@ -607,7 +709,8 @@ class GeckoWeb : BWebview {
|
||||
// url이 현재 로드된 주소입니다.
|
||||
Blog.LOGE("GeckoView", "현재 주소: $url")
|
||||
Blog.LOGE("GeckoView", "현재 session: $session")
|
||||
|
||||
val nullCursor = PointerIcon.getSystemIcon(context, PointerIcon.TYPE_NULL)
|
||||
this@GeckoWeb.setPointerIcon(nullCursor)
|
||||
url?.let { url ->
|
||||
if (url?.contains(getFilterF()) == true && privateMode) {
|
||||
this@GeckoWeb.visibility = View.INVISIBLE
|
||||
|
||||
@ -259,7 +259,7 @@ internal class RssHome : Fragment() {
|
||||
}
|
||||
|
||||
RssDataType.REDDIT -> {
|
||||
openReddit(rss.originPage())
|
||||
openGecko(rss)
|
||||
}
|
||||
|
||||
RssDataType.DOTAX -> {
|
||||
@ -267,7 +267,7 @@ internal class RssHome : Fragment() {
|
||||
}
|
||||
|
||||
RssDataType.YOUTUBE -> {
|
||||
openYouTube(rss.originPage())
|
||||
openGecko(rss)
|
||||
}
|
||||
|
||||
RssDataType.CLIEN -> {
|
||||
@ -415,7 +415,8 @@ internal class RssHome : Fragment() {
|
||||
}
|
||||
} else if (rssData?.category()?.equals(RssDataType.PRIVATE) == true){
|
||||
rssData?.let {
|
||||
binding.geckoWeb.privateMode = true
|
||||
currentRss = it
|
||||
|
||||
synchronized(lasted) {
|
||||
if (lasted.isNotEmpty()) {
|
||||
lasted.removeAll { target -> target.originPage.equals(it.originPage) }
|
||||
@ -423,6 +424,11 @@ internal class RssHome : Fragment() {
|
||||
}
|
||||
appendReadCount(it, 1, false)
|
||||
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.root.visibility = View.VISIBLE
|
||||
binding.layoutRssSummary.scrollView.scrollTo(0,0)
|
||||
@ -607,12 +613,12 @@ internal class RssHome : Fragment() {
|
||||
}
|
||||
}
|
||||
val nullCursor = PointerIcon.getSystemIcon(requireContext(), PointerIcon.TYPE_NULL)
|
||||
binding.root.setPointerIcon(nullCursor)
|
||||
binding.search.setOnClickListener { searchKeyword() }
|
||||
binding.search.setOnLongClickListener{
|
||||
ask()
|
||||
true
|
||||
}
|
||||
binding.root.setPointerIcon(nullCursor)
|
||||
binding.geckoWeb.decoViews.add(binding.hide)
|
||||
binding.geckoWeb.decoViews.add(binding.vote)
|
||||
binding.geckoWeb.decoViews.add(binding.progressBar)
|
||||
|
||||
@ -1,9 +1,17 @@
|
||||
package bums.lunatic.launcher.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
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 {
|
||||
fun dpToPx(context: Context, dp: Float): Int {
|
||||
@ -18,4 +26,145 @@ object CommonUtils {
|
||||
fun hasLollipop(): Boolean {
|
||||
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()
|
||||
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user