...
This commit is contained in:
parent
6e7e9685ab
commit
df69d779d9
File diff suppressed because it is too large
Load Diff
@ -44,6 +44,7 @@ import bums.lunatic.launcher.settings.SettingsActivity
|
|||||||
import bums.lunatic.launcher.home.tokiz.Comics
|
import bums.lunatic.launcher.home.tokiz.Comics
|
||||||
import bums.lunatic.launcher.home.tokiz.Novels
|
import bums.lunatic.launcher.home.tokiz.Novels
|
||||||
import bums.lunatic.launcher.home.tokiz.Perplexity
|
import bums.lunatic.launcher.home.tokiz.Perplexity
|
||||||
|
import bums.lunatic.launcher.home.tokiz.TokiFragment
|
||||||
import bums.lunatic.launcher.home.tokiz.Webtoons
|
import bums.lunatic.launcher.home.tokiz.Webtoons
|
||||||
import bums.lunatic.launcher.home.tokiz.YouTube
|
import bums.lunatic.launcher.home.tokiz.YouTube
|
||||||
import bums.lunatic.launcher.utils.Blog
|
import bums.lunatic.launcher.utils.Blog
|
||||||
@ -479,28 +480,28 @@ open class NeoRssActivity : CommonActivity() {
|
|||||||
}
|
}
|
||||||
R.id.books ->{
|
R.id.books ->{
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager.beginTransaction()
|
||||||
.replace(R.id.fragment_container, Novels())
|
.replace(R.id.fragment_container, TokiFragment.newInstanceNovels())
|
||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.webtoons ->{
|
R.id.webtoons ->{
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager.beginTransaction()
|
||||||
.replace(R.id.fragment_container, Webtoons())
|
.replace(R.id.fragment_container, TokiFragment.newInstanceWebtoons())
|
||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
R.id.comics ->{
|
R.id.comics ->{
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager.beginTransaction()
|
||||||
.replace(R.id.fragment_container, Comics())
|
.replace(R.id.fragment_container, TokiFragment.newInstanceComics())
|
||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
R.id.youtube ->{
|
R.id.youtube ->{
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager.beginTransaction()
|
||||||
.replace(R.id.fragment_container, YouTube())
|
.replace(R.id.fragment_container, TokiFragment.newInstanceYouTube())
|
||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
R.id.perplexity ->{
|
R.id.perplexity ->{
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager.beginTransaction()
|
||||||
.replace(R.id.fragment_container, Perplexity())
|
.replace(R.id.fragment_container, TokiFragment.newInstancePerplexity())
|
||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
R.id.zota ->{
|
R.id.zota ->{
|
||||||
|
|||||||
@ -47,6 +47,7 @@ import bums.lunatic.launcher.R
|
|||||||
import bums.lunatic.launcher.common.letTrue
|
import bums.lunatic.launcher.common.letTrue
|
||||||
import bums.lunatic.launcher.databinding.LauncherHomeBinding
|
import bums.lunatic.launcher.databinding.LauncherHomeBinding
|
||||||
import bums.lunatic.launcher.helpers.Constants.Companion.PREFS_SETTINGS
|
import bums.lunatic.launcher.helpers.Constants.Companion.PREFS_SETTINGS
|
||||||
|
import bums.lunatic.launcher.home.GeckoWeb.JxEvent
|
||||||
import bums.lunatic.launcher.home.NeoRssActivity.Companion.lActivity
|
import bums.lunatic.launcher.home.NeoRssActivity.Companion.lActivity
|
||||||
import bums.lunatic.launcher.home.SearchBottomSheet.OnSearchListener
|
import bums.lunatic.launcher.home.SearchBottomSheet.OnSearchListener
|
||||||
import bums.lunatic.launcher.home.adapters.RssItemAdapter
|
import bums.lunatic.launcher.home.adapters.RssItemAdapter
|
||||||
@ -54,7 +55,6 @@ import bums.lunatic.launcher.home.adapters.SwipeToDeleteCallback
|
|||||||
import bums.lunatic.launcher.model.RssData
|
import bums.lunatic.launcher.model.RssData
|
||||||
import bums.lunatic.launcher.model.RssDataType
|
import bums.lunatic.launcher.model.RssDataType
|
||||||
import bums.lunatic.launcher.model.WeatherForcast
|
import bums.lunatic.launcher.model.WeatherForcast
|
||||||
import bums.lunatic.launcher.home.tokiz.view.JxEvent
|
|
||||||
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 bums.lunatic.launcher.utils.beforeDay
|
import bums.lunatic.launcher.utils.beforeDay
|
||||||
@ -581,7 +581,7 @@ internal class RssHome : Fragment() {
|
|||||||
binding.geckoWeb.decoViews.add(activity.findViewById<ImageButton>(R.id.reload))
|
binding.geckoWeb.decoViews.add(activity.findViewById<ImageButton>(R.id.reload))
|
||||||
binding.geckoWeb.decoViews.add(activity.findViewById<ImageButton>(R.id.dl_video))
|
binding.geckoWeb.decoViews.add(activity.findViewById<ImageButton>(R.id.dl_video))
|
||||||
}
|
}
|
||||||
|
Blog.LOGE("binding.geckoWeb.decoViews >>> ${binding.geckoWeb.decoViews.size}")
|
||||||
|
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|||||||
698
app/src/main/kotlin/bums/lunatic/launcher/home/UniversalToki.kt
Normal file
698
app/src/main/kotlin/bums/lunatic/launcher/home/UniversalToki.kt
Normal file
@ -0,0 +1,698 @@
|
|||||||
|
//package bums.lunatic.launcher.home.tokiz
|
||||||
|
//
|
||||||
|
//import android.content.Context
|
||||||
|
//import android.content.Intent
|
||||||
|
//import android.content.res.Configuration
|
||||||
|
//import android.net.Uri
|
||||||
|
//import android.os.Bundle
|
||||||
|
//import android.os.Handler
|
||||||
|
//import android.os.Looper
|
||||||
|
//import android.os.Message
|
||||||
|
//import android.text.InputType
|
||||||
|
//import android.text.SpannableStringBuilder
|
||||||
|
//import android.text.style.RelativeSizeSpan
|
||||||
|
//import android.view.KeyEvent
|
||||||
|
//import android.view.LayoutInflater
|
||||||
|
//import android.view.PointerIcon
|
||||||
|
//import android.view.View
|
||||||
|
//import android.view.View.GONE
|
||||||
|
//import android.view.View.VISIBLE
|
||||||
|
//import android.view.ViewGroup
|
||||||
|
//import android.widget.ArrayAdapter
|
||||||
|
//import android.widget.EditText
|
||||||
|
//import android.widget.ImageButton
|
||||||
|
//import android.widget.TextView
|
||||||
|
//import android.widget.Toast
|
||||||
|
//import androidx.activity.OnBackPressedCallback
|
||||||
|
//import androidx.appcompat.app.AlertDialog
|
||||||
|
//import androidx.core.net.toUri
|
||||||
|
//import androidx.core.view.isVisible
|
||||||
|
//import androidx.fragment.app.Fragment
|
||||||
|
//import bums.lunatic.launcher.R
|
||||||
|
//import bums.lunatic.launcher.databinding.BooktokiBinding
|
||||||
|
//import bums.lunatic.launcher.home.JxEvent
|
||||||
|
//import bums.lunatic.launcher.home.NeoRssActivity
|
||||||
|
//import bums.lunatic.launcher.home.NeoRssActivity.Companion.getRuntime
|
||||||
|
//import bums.lunatic.launcher.home.tokiz.view.JxEvent
|
||||||
|
//import bums.lunatic.launcher.home.tokiz.view.PagedTextLayout
|
||||||
|
//import bums.lunatic.launcher.home.tokiz.view.PagedTextViewInterface
|
||||||
|
//import bums.lunatic.launcher.utils.Blog
|
||||||
|
//import bums.lunatic.launcher.utils.DefaultList
|
||||||
|
//import bums.lunatic.launcher.workers.WorkersDb
|
||||||
|
//import io.realm.kotlin.Realm
|
||||||
|
//import io.realm.kotlin.UpdatePolicy
|
||||||
|
//import io.realm.kotlin.ext.copyFromRealm
|
||||||
|
//import io.realm.kotlin.ext.query
|
||||||
|
//import org.json.JSONException
|
||||||
|
//import org.json.JSONObject
|
||||||
|
//import org.mozilla.gecko.util.ThreadUtils
|
||||||
|
//import org.mozilla.geckoview.GeckoResult
|
||||||
|
//import org.mozilla.geckoview.GeckoSession
|
||||||
|
//import org.mozilla.geckoview.GeckoSessionSettings
|
||||||
|
//import org.mozilla.geckoview.WebExtension
|
||||||
|
//import java.net.URL
|
||||||
|
//import java.text.SimpleDateFormat
|
||||||
|
//import java.util.Date
|
||||||
|
//import kotlin.random.Random
|
||||||
|
//
|
||||||
|
//class UniversalToki : Fragment(), PagedTextViewInterface {
|
||||||
|
//
|
||||||
|
// // --- Configuration Arguments (각 하위 클래스의 특성을 정의) ---
|
||||||
|
// private var contentsType: String = "book" // 기본값: Novels
|
||||||
|
// private var lastNumber: Int = 468
|
||||||
|
// private var webContentsName: String = "booktoki"
|
||||||
|
// private var afterDot: String = "com"
|
||||||
|
// private var isPrivateMode: Boolean = false
|
||||||
|
// private var includeNumberInDomain: Boolean = true // 도메인에 숫자 포함 여부
|
||||||
|
// private var enableGestures: Boolean = true // 제스처 사용 여부
|
||||||
|
// private var showWebOnSwipeDown: Boolean = false // 스와이프 다운 시 웹뷰 보이기 여부
|
||||||
|
//
|
||||||
|
// // --- State Variables ---
|
||||||
|
// private var lastInfo: LastInfo? = null
|
||||||
|
// private var currentPage: ContentsPageInfo? = null
|
||||||
|
// private var saveContinuation = false
|
||||||
|
// private var lastedUrl: String? = null
|
||||||
|
// private var canGoBack: Boolean = false
|
||||||
|
// private var _binding: BooktokiBinding? = null
|
||||||
|
// private val binding get() = _binding!!
|
||||||
|
//
|
||||||
|
// private var currentTitle: String = ""
|
||||||
|
// private var currentChapter: Int = 0
|
||||||
|
//
|
||||||
|
// // WebExtension
|
||||||
|
// private var mPort: WebExtension.Port? = null
|
||||||
|
// private val mPortNam = "browser"
|
||||||
|
// private val extPath = "resource://android/assets/extensions/my_extension/"
|
||||||
|
// private val extId = "messaging@booktoki468.com"
|
||||||
|
//
|
||||||
|
// // --- Companion Object (Factory Methods for Subclass Replacement) ---
|
||||||
|
// companion object {
|
||||||
|
// private const val ARG_CONTENTS_TYPE = "type"
|
||||||
|
// private const val ARG_LAST_NUMBER = "last_num"
|
||||||
|
// private const val ARG_WEB_NAME = "web_name"
|
||||||
|
// private const val ARG_AFTER_DOT = "after_dot"
|
||||||
|
// private const val ARG_PRIVATE = "private"
|
||||||
|
// private const val ARG_INC_NUM = "inc_num"
|
||||||
|
// private const val ARG_GESTURES = "gestures"
|
||||||
|
// private const val ARG_WEB_ON_SWIPE = "web_swipe"
|
||||||
|
//
|
||||||
|
// // [팩토리 메서드] 기존 클래스들을 대체합니다.
|
||||||
|
// fun newNovels() = create("book", 468, "booktoki", "com", webOnSwipe = true)
|
||||||
|
// fun newWebtoons() = create("webtoon", 468, "newtoki", "com")
|
||||||
|
// fun newComics() = create("comics", 468, "manatoki", "net")
|
||||||
|
// fun newYoutube() = create("youtube", 143, "youtube", "com", isPrivate = true, incNum = false, gestures = false)
|
||||||
|
// fun newMagnet() = create("btsearch", 143, "btsearch", "love", incNum = false)
|
||||||
|
// fun newPerplexity() = create("perplexity", 143, "www.perplexity", "ai", incNum = false)
|
||||||
|
//
|
||||||
|
// private fun create(
|
||||||
|
// type: String, lastNum: Int, name: String, dot: String,
|
||||||
|
// isPrivate: Boolean = false, incNum: Boolean = true,
|
||||||
|
// gestures: Boolean = true, webOnSwipe: Boolean = false
|
||||||
|
// ): UniversalToki {
|
||||||
|
// return UniversalToki().apply {
|
||||||
|
// arguments = Bundle().apply {
|
||||||
|
// putString(ARG_CONTENTS_TYPE, type)
|
||||||
|
// putInt(ARG_LAST_NUMBER, lastNum)
|
||||||
|
// putString(ARG_WEB_NAME, name)
|
||||||
|
// putString(ARG_AFTER_DOT, dot)
|
||||||
|
// putBoolean(ARG_PRIVATE, isPrivate)
|
||||||
|
// putBoolean(ARG_INC_NUM, incNum)
|
||||||
|
// putBoolean(ARG_GESTURES, gestures)
|
||||||
|
// putBoolean(ARG_WEB_ON_SWIPE, webOnSwipe)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
// super.onCreate(savedInstanceState)
|
||||||
|
// arguments?.let {
|
||||||
|
// contentsType = it.getString(ARG_CONTENTS_TYPE, "book")!!
|
||||||
|
// lastNumber = it.getInt(ARG_LAST_NUMBER, 468)
|
||||||
|
// webContentsName = it.getString(ARG_WEB_NAME, "booktoki")!!
|
||||||
|
// afterDot = it.getString(ARG_AFTER_DOT, "com")!!
|
||||||
|
// isPrivateMode = it.getBoolean(ARG_PRIVATE, false)
|
||||||
|
// includeNumberInDomain = it.getBoolean(ARG_INC_NUM, true)
|
||||||
|
// enableGestures = it.getBoolean(ARG_GESTURES, true)
|
||||||
|
// showWebOnSwipeDown = it.getBoolean(ARG_WEB_ON_SWIPE, false)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // --- Back Press Handling (기존 BaseToki의 뒤로가기 로직 통합) ---
|
||||||
|
// override fun onAttach(context: Context) {
|
||||||
|
// super.onAttach(context)
|
||||||
|
// requireActivity().onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||||
|
// override fun handleOnBackPressed() {
|
||||||
|
// handleBackAction()
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun handleBackAction() {
|
||||||
|
// // 1. 뷰어(텍스트 레이어)가 켜져 있으면 끈다.
|
||||||
|
// if (binding.pagedLayer.isVisible) {
|
||||||
|
// binding.pagedLayer.visibility = GONE
|
||||||
|
// // Novels(booktoki)는 스와이프 다운이나 뒤로가기 시 웹뷰를 다시 보여줌
|
||||||
|
// if (showWebOnSwipeDown || !binding.menuWeb.isVisible) {
|
||||||
|
// binding.menuWeb.visibility = VISIBLE
|
||||||
|
// }
|
||||||
|
// onTouch(TouchArea.Center) // 터치 상태 초기화
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 2. 웹 브라우저 뒤로가기가 가능하면 실행
|
||||||
|
// // (GeckoWeb 내부 상태가 canGoBack이면)
|
||||||
|
// if (canGoBack) {
|
||||||
|
// binding.menuWeb.session?.goBack()
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 3. 더 이상 뒤로 갈 곳이 없으면 시스템 기본 동작(액티비티 종료 등)
|
||||||
|
// isEnabled = false // 콜백 비활성화
|
||||||
|
// requireActivity().onBackPressed()
|
||||||
|
// isEnabled = true // 다시 활성화
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
|
// _binding = BooktokiBinding.inflate(inflater, container, false)
|
||||||
|
// return binding.root
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
// super.onViewCreated(view, savedInstanceState)
|
||||||
|
//
|
||||||
|
// setupGeckoWeb()
|
||||||
|
// setupButtons()
|
||||||
|
// setupExternalViews() // 액티비티 상단 UI 연결
|
||||||
|
//
|
||||||
|
// val nullCursor = PointerIcon.getSystemIcon(requireContext(), PointerIcon.TYPE_NULL)
|
||||||
|
// binding.root.pointerIcon = nullCursor
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun setupGeckoWeb() {
|
||||||
|
// binding.menuWeb.apply {
|
||||||
|
// lastDomain = getLastedDoamin()
|
||||||
|
// privateMode = isPrivateMode
|
||||||
|
//
|
||||||
|
// setOnLongClickListener {
|
||||||
|
// if (enableGestures) onTouch(TouchArea.Center)
|
||||||
|
// return@setOnLongClickListener false
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // --- Callbacks (GeckoWeb에서 발생한 이벤트를 여기서 처리) ---
|
||||||
|
// onLocationChangeCallback = { url ->
|
||||||
|
// val cleanUrl = if (url.split("//").size > 1) {
|
||||||
|
// url.replace("//", "/").replace("https:/", "https://")
|
||||||
|
// } else url
|
||||||
|
//
|
||||||
|
// lastedUrl = cleanUrl
|
||||||
|
//
|
||||||
|
// // 외부 뷰 업데이트 및 다운로드 가능 여부 체크
|
||||||
|
// updateAddressBar(cleanUrl)
|
||||||
|
// binding.menuWeb.checkIfDownloadable(cleanUrl)
|
||||||
|
//
|
||||||
|
// // 페이지 로드 완료 처리 (히스토리 저장 등)
|
||||||
|
// completePageLoad(LastInfo().apply {
|
||||||
|
// this.pageUrl = cleanUrl.toUri().path ?: getLastedDoamin()
|
||||||
|
// this.contentsName = webContentsName
|
||||||
|
// this.contentsType = contentsType
|
||||||
|
// this.pageIndex = 0
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// onPageStartCallback = {
|
||||||
|
// binding.progress.visibility = VISIBLE
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// onPageStopCallback = { success ->
|
||||||
|
// if (success) {
|
||||||
|
// // 페이지 로드 완료 시 확장 프로그램에 리스트 요청 메시지 전송
|
||||||
|
// val message = JSONObject().put("type", "getList").put("tab", session?.settings?.screenId)
|
||||||
|
// mPort?.postMessage(message)
|
||||||
|
// }
|
||||||
|
// binding.progress.visibility = GONE
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// onSessionStateChangeCallback = { state -> onStateChange(state) }
|
||||||
|
// onPortMessageCallback = { msg -> handlePortMessage(msg) }
|
||||||
|
//
|
||||||
|
// // 제스처 연결
|
||||||
|
// jxInteface = { jxEvent ->
|
||||||
|
// if (enableGestures) {
|
||||||
|
// when (jxEvent) {
|
||||||
|
// JxEvent.SCROLL_UP -> sendScrollDown(true)
|
||||||
|
// JxEvent.SCROLL_DOWN -> sendScrollDown(false)
|
||||||
|
// JxEvent.SWIPE_LEFT -> if (contentsType == "comics") sendViewerTouch("left") else actionNextEvent()
|
||||||
|
// JxEvent.SWIPE_RIGHT -> if (contentsType == "comics") sendViewerTouch("right") else actionPrevEvent()
|
||||||
|
// else -> {}
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun setupButtons() {
|
||||||
|
// // [리스트 보기 버튼]
|
||||||
|
// binding.btnList.setOnClickListener {
|
||||||
|
// val url = lastedUrl ?: getLastedDoamin()
|
||||||
|
// val path = Uri.parse(url).path ?: ""
|
||||||
|
// val processedPath = processPageUrl(path)
|
||||||
|
//
|
||||||
|
// if (processedPath.isNotEmpty()) {
|
||||||
|
// HistoryManager.getBookInfos(contentsType, processedPath) { info ->
|
||||||
|
// if (info != null && info.pages.isNotEmpty()) {
|
||||||
|
// info.pages.sortBy { it.pathUrl }
|
||||||
|
// activity?.runOnUiThread { showList(info) }
|
||||||
|
// } else {
|
||||||
|
// // DB에 없으면 현재 페이지 정보를 기반으로 재시도하거나 토스트
|
||||||
|
// showToast("목록을 불러올 수 없습니다.")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// showToast("유효하지 않은 주소입니다.")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// binding.btnSetting.setOnClickListener { startActivity(Intent(requireContext(), Settings::class.java)) }
|
||||||
|
// binding.btnHistory.setOnClickListener { getHistory()?.let { showHistory(it) } }
|
||||||
|
// binding.btnHome.setOnClickListener {
|
||||||
|
// binding.pagedLayer.visibility = GONE
|
||||||
|
// goToHome()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun setupExternalViews() {
|
||||||
|
// (activity as? NeoRssActivity)?.let { act ->
|
||||||
|
// // 뒤로가기 버튼
|
||||||
|
// act.findViewById<ImageButton>(R.id.back)?.setOnClickListener { handleBackAction() }
|
||||||
|
// // 새로고침
|
||||||
|
// act.findViewById<ImageButton>(R.id.reload)?.setOnClickListener { binding.menuWeb.session?.reload() }
|
||||||
|
//
|
||||||
|
// // GeckoWeb decoViews에 등록 (주소창 텍스트 업데이트용)
|
||||||
|
// binding.menuWeb.decoViews.clear()
|
||||||
|
// binding.menuWeb.decoViews.add(act.findViewById(R.id.current_address))
|
||||||
|
// binding.menuWeb.decoViews.add(act.findViewById(R.id.dl_video))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun updateAddressBar(url: String) {
|
||||||
|
// (activity as? NeoRssActivity)?.findViewById<TextView>(R.id.current_address)?.let {
|
||||||
|
// it.tag = currentTitle
|
||||||
|
// it.text = url
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // --- Helper Functions ---
|
||||||
|
//
|
||||||
|
// fun openRealm(): Realm = HistoryManager.openRealm
|
||||||
|
//
|
||||||
|
// fun getLastedDoamin(): String {
|
||||||
|
// return if (includeNumberInDomain) String.format("https://%s%d.%s", webContentsName, lastNumber, afterDot)
|
||||||
|
// else String.format("https://%s.%s", webContentsName, afterDot)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // --- Browser State & History Logic ---
|
||||||
|
//
|
||||||
|
// fun onStateChange(sessionState: GeckoSession.SessionState) {
|
||||||
|
// // GeckoWeb의 canGoBack 상태 동기화
|
||||||
|
// canGoBack = binding.menuWeb.canGoBack ?: false
|
||||||
|
//
|
||||||
|
// if (sessionState.last().uri.length > 10) {
|
||||||
|
// lastedUrl?.let { url ->
|
||||||
|
// val path = Uri.parse(url).path ?: return@let
|
||||||
|
// HistoryManager.getBookPageInfo(contentsType, path) { info ->
|
||||||
|
// info?.let {
|
||||||
|
// currentPage = it
|
||||||
|
// currentChapter = it.chapterNum ?: 0
|
||||||
|
// HistoryManager.save(HistoryItem().putHistory(it, lastedUrl!!))
|
||||||
|
//
|
||||||
|
// updateLastInfoRealm(it)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if (saveContinuation) {
|
||||||
|
// binding.menuWeb.postDelayed({ moveToNext(currentPage?.pathUrl ?: lastedUrl?.toUri()?.path) }, 10000)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun updateLastInfoRealm(info: ContentsPageInfo) {
|
||||||
|
// openRealm().writeBlocking {
|
||||||
|
// val query = this.query<LastInfo>("contentsType == $0", contentsType).find()
|
||||||
|
// if (query.isNotEmpty()) {
|
||||||
|
// query.last()?.let { last ->
|
||||||
|
// last.title = currentTitle
|
||||||
|
// last.chapter = currentChapter
|
||||||
|
// last.contentsType = info.contentsType
|
||||||
|
// this@UniversalToki.lastInfo = copyFromRealm(last)
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// LastInfo().let { last ->
|
||||||
|
// last.title = currentTitle
|
||||||
|
// last.chapter = currentChapter
|
||||||
|
// last._id = contentsType
|
||||||
|
// last.contentsType = info.contentsType
|
||||||
|
// copyToRealm(last, UpdatePolicy.ALL)
|
||||||
|
// this@UniversalToki.lastInfo = last
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun processPageUrl(pageUrl: String): String {
|
||||||
|
// try {
|
||||||
|
// if (pageUrl.isEmpty()) return ""
|
||||||
|
// var workingUrl = pageUrl
|
||||||
|
// if (workingUrl.startsWith("http")) {
|
||||||
|
// try { workingUrl = URL(workingUrl).path } catch (e: Exception) {}
|
||||||
|
// }
|
||||||
|
// // URL 경로 처리 (숫자만 있는 세그먼트 유지 등 기존 로직)
|
||||||
|
// val paths = workingUrl.split("/").filter { it.isNotEmpty() }.toMutableList()
|
||||||
|
// if (paths.isNotEmpty()) {
|
||||||
|
// val last = paths.last()
|
||||||
|
// // 기존 로직: 마지막이 숫자가 아니면 제거 (목록 페이지 등에서 필요)
|
||||||
|
// if (!last.all { it.isDigit() }) {
|
||||||
|
// // paths.removeAt(paths.size - 1) // 필요 시 주석 해제하여 사용
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return "/" + paths.joinToString("/")
|
||||||
|
// } catch (e: Exception) {
|
||||||
|
// e.printStackTrace()
|
||||||
|
// return pageUrl
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // --- Book Info & List Handling (오버로딩 포함) ---
|
||||||
|
//
|
||||||
|
// // [중요] PageInfosJ를 처리하는 오버로딩 (기존 로직 누락 방지)
|
||||||
|
// fun onBookInfos(infosj: PageInfosJ) {
|
||||||
|
// val realm = openRealm()
|
||||||
|
// realm.writeBlocking {
|
||||||
|
// try {
|
||||||
|
// infosj.bookPageUrl = processPageUrl(infosj.bookPageUrl ?: "")
|
||||||
|
// infosj.pages.forEach { it.pathUrl = processPageUrl(it.pathUrl ?: "") }
|
||||||
|
//
|
||||||
|
// // DB에 정보 업데이트/저장 로직
|
||||||
|
// // (기존 BaseToki.kt의 복잡한 로직을 간소화하여 호출)
|
||||||
|
// HistoryManager.getBookInfos(contentsType, infosj.bookPageUrl!!) { savedInfos ->
|
||||||
|
// if (savedInfos != null) {
|
||||||
|
// // 기존 정보가 있으면 병합
|
||||||
|
// val managedInfos = copyToRealm(savedInfos, UpdatePolicy.ALL)
|
||||||
|
// for (item in infosj.pages) {
|
||||||
|
// if (!managedInfos.hasItem(item.getRealm())) {
|
||||||
|
// managedInfos.pages.add(item.getRealm())
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// // 없으면 새로 생성
|
||||||
|
// val newInfos = infosj.getR()
|
||||||
|
// if (newInfos != null) {
|
||||||
|
// val managedInfos = copyToRealm(newInfos, UpdatePolicy.ALL)
|
||||||
|
// infosj.pages.forEach { managedInfos.pages.add(it.getRealm()) }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } catch(e: Exception) { e.printStackTrace() }
|
||||||
|
// }
|
||||||
|
// // 저장 후 리스트 표시
|
||||||
|
// onBookInfos(infosj.getR())
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun onBookInfos(aInfos: ContentsCollection) {
|
||||||
|
// activity?.runOnUiThread { showList(aInfos) }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun showList(infos: ContentsCollection) {
|
||||||
|
// if (infos.pages.isEmpty()) return
|
||||||
|
//
|
||||||
|
// val items = ArrayList(infos.pages).apply { sortBy { it.chapterID } }
|
||||||
|
//
|
||||||
|
// DefaultList.showDefaultList(
|
||||||
|
// requireContext(),
|
||||||
|
// "현재: $currentTitle ($currentChapter 화)",
|
||||||
|
// items,
|
||||||
|
// currentChapter,
|
||||||
|
// { pos -> items[pos].chapterTitle ?: "제목 없음" },
|
||||||
|
// { pos -> moveTo(items[pos]) },
|
||||||
|
// { state ->
|
||||||
|
// if (state < 0) {
|
||||||
|
// saveContinuation = true
|
||||||
|
// moveToNext(currentPage?.pathUrl)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun moveTo(item: ContentsPageInfo?) {
|
||||||
|
// item?.pathUrl?.let { contentsLoad(it) }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun contentsLoad(pathUrl: String) {
|
||||||
|
// val fullUrl = if (pathUrl.startsWith("http")) pathUrl else getLastedDoamin() + pathUrl
|
||||||
|
// binding.menuWeb.loadUrl(fullUrl)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // --- History Handling ---
|
||||||
|
//
|
||||||
|
// private fun getHistory(): List<HistoryItem>? {
|
||||||
|
// return try {
|
||||||
|
// openRealm().query<HistoryItem>().query("contentsType == $0", contentsType).find().copyFromRealm().reversed()
|
||||||
|
// } catch (e: Exception) { e.printStackTrace(); null }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun showHistory(infos: List<HistoryItem>) {
|
||||||
|
// val builder = AlertDialog.Builder(requireContext())
|
||||||
|
// builder.setTitle("$currentTitle : $currentChapter")
|
||||||
|
//
|
||||||
|
// val adapter = ArrayAdapter<String>(requireContext(), android.R.layout.select_dialog_singlechoice)
|
||||||
|
// infos.forEach { adapter.add(it.title) }
|
||||||
|
//
|
||||||
|
// builder.setAdapter(adapter) { dialog, which ->
|
||||||
|
// val item = infos[which]
|
||||||
|
// AlertDialog.Builder(requireContext())
|
||||||
|
// .setTitle("${item.title}로 이동?")
|
||||||
|
// .setPositiveButton("이동") { _, _ -> contentsLoad(item.pageUrl); dialog.dismiss() }
|
||||||
|
// .setNeutralButton("삭제") { _, _ ->
|
||||||
|
// openRealm().writeBlocking {
|
||||||
|
// this.query<HistoryItem>().query("title == '${item.title}'").find().lastOrNull()?.let { delete(it) }
|
||||||
|
// }
|
||||||
|
// dialog.dismiss()
|
||||||
|
// }
|
||||||
|
// .setNegativeButton("취소", null)
|
||||||
|
// .show()
|
||||||
|
// }
|
||||||
|
// builder.setNegativeButton("닫기", null)
|
||||||
|
// builder.show()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // --- Port Message Handling ---
|
||||||
|
// private fun handlePortMessage(msg: bums.lunatic.launcher.home.tokiz.PortMessage) {
|
||||||
|
// when(msg.type) {
|
||||||
|
// "getListResult" -> msg.bookInfos?.let { onBookInfos(it.sort()) }
|
||||||
|
// "BookContents" -> {
|
||||||
|
// msg.book?.chapterTitle?.let { onFindTitle(it) }
|
||||||
|
// msg.book?.bookContents?.let { onLoadedContents(it) }
|
||||||
|
// }
|
||||||
|
// "NotRegistered", "WebtoonContents" -> binding.pagedLayer.visibility = GONE
|
||||||
|
// "MSG" -> showToast(msg.msg ?: "")
|
||||||
|
// "PRIVATES" -> msg.privates?.let { WorkersDb.insertBulkData(it) }
|
||||||
|
// "SHOWVIEWER" -> binding.progress.visibility = GONE
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // --- Viewer Logic (PagedTextView) ---
|
||||||
|
//
|
||||||
|
// fun onFindTitle(contents: String) {
|
||||||
|
// binding.textviewTitle.text = contents
|
||||||
|
// binding.textviewTitle.setOnClickListener {
|
||||||
|
// val input = EditText(requireContext()).apply {
|
||||||
|
// setText(lastedUrl ?: "")
|
||||||
|
// inputType = InputType.TYPE_CLASS_TEXT
|
||||||
|
// }
|
||||||
|
// AlertDialog.Builder(requireContext())
|
||||||
|
// .setTitle("URL 이동")
|
||||||
|
// .setView(input)
|
||||||
|
// .setPositiveButton("이동") { _, _ -> contentsLoad(input.text.toString().trim()) }
|
||||||
|
// .setNegativeButton("취소", null)
|
||||||
|
// .show()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// val testRegex = """[^0-9]""".toRegex()
|
||||||
|
// if (contents.contains("-")) {
|
||||||
|
// currentTitle = contents.split("-")[0]
|
||||||
|
// currentChapter = testRegex.replace(contents.split("-")[1], "").toIntOrNull() ?: 0
|
||||||
|
// } else {
|
||||||
|
// currentTitle = contents
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun onLoadedContents(aContents: String) {
|
||||||
|
// binding.pagedLayer.post {
|
||||||
|
// if (aContents.length > 10) {
|
||||||
|
// // 텍스트 정제
|
||||||
|
// val contents = aContents.replace("\\\"", "\"").replace("\\n", System.getProperty("line.separator"))
|
||||||
|
//
|
||||||
|
// binding.pagedLayer.mPagedTextViewInterface = this
|
||||||
|
// applyReaderConfig()
|
||||||
|
//
|
||||||
|
// activity?.runOnUiThread {
|
||||||
|
// binding.pagedLayer.text = contents
|
||||||
|
// binding.pagedLayer.visibility = VISIBLE
|
||||||
|
// binding.menuWeb.visibility = GONE
|
||||||
|
// }
|
||||||
|
// binding.pagedLayer.forceUpdateUI()
|
||||||
|
//
|
||||||
|
// // 현재 페이지 정보 저장
|
||||||
|
// lastedUrl?.let { url ->
|
||||||
|
// val path = Uri.parse(url).path ?: return@let
|
||||||
|
// HistoryManager.getBookPageInfo(contentsType, path) { info ->
|
||||||
|
// info?.let {
|
||||||
|
// currentPage = it
|
||||||
|
// currentChapter = it.chapterNum ?: 0
|
||||||
|
// binding.pagedLayer.currentPage = currentChapter
|
||||||
|
// HistoryManager.save(HistoryItem().putHistory(it, url))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// // 콘텐츠 저장 (오프라인 등 용도)
|
||||||
|
// HistoryManager.getBooPageInfoContentsSave(contentsType, path, contents)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (saveContinuation) {
|
||||||
|
// binding.menuWeb.postDelayed({ moveToNext(currentPage?.pathUrl ?: lastedUrl?.toUri()?.path) }, 10000)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun applyReaderConfig() {
|
||||||
|
// val realm = HistoryManager.openRealm
|
||||||
|
// realm.query<ReaderConfig>().find().firstOrNull()?.let { config ->
|
||||||
|
// val it = realm.copyFromRealm(config)
|
||||||
|
// activity?.runOnUiThread {
|
||||||
|
// // 폰트, 스타일, 크기 등 설정 적용
|
||||||
|
// val typeface = typesfacez[getIndex(typesfacez as PairArray<Any>, it.font ?: "")]
|
||||||
|
// binding.pagedLayer.setTypeface(resources.getFont(typeface.second))
|
||||||
|
// val color = colorz[it.style ?: 0]
|
||||||
|
// binding.pagedLayer.setColorStyle(color.second)
|
||||||
|
// binding.pagedLayer.setTextSize(it.textSize?.toFloat() ?: 14f)
|
||||||
|
// // ... 기타 패딩 등 설정
|
||||||
|
// binding.pagedLayer.invalidate()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // --- Gesture & Navigation ---
|
||||||
|
//
|
||||||
|
// fun sendScrollDown(isUp: Boolean) {
|
||||||
|
// val message = JSONObject().put("type", "scrollDown").put("isUpDown", if (isUp) 1 else -1)
|
||||||
|
// mPort?.postMessage(message)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun sendViewerTouch(area: String) {
|
||||||
|
// val message = JSONObject().put("type", "ViewerTouch").put("area", area)
|
||||||
|
// mPort?.postMessage(message)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun actionNextEvent(fast: Boolean = false) {
|
||||||
|
// if (binding.pagedLayer.isVisible) {
|
||||||
|
// binding.pagedLayer.doNext(fast)
|
||||||
|
// updateLastInfo(binding.pagedLayer)
|
||||||
|
// } else {
|
||||||
|
// moveToNext(currentPage?.pathUrl)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun actionPrevEvent(fast: Boolean = false) {
|
||||||
|
// if (binding.pagedLayer.isVisible) {
|
||||||
|
// binding.pagedLayer.doPrev(fast)
|
||||||
|
// updateLastInfo(binding.pagedLayer)
|
||||||
|
// } else {
|
||||||
|
// moveToPrev(currentPage?.pathUrl)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun moveToNext(path: String?) {
|
||||||
|
// path?.let { HistoryManager.getNextPage(contentsType, it) { info -> moveTo(info) } }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun moveToPrev(path: String?) {
|
||||||
|
// path?.let { HistoryManager.getPrevPage(contentsType, it) { info -> moveTo(info) } }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun updateLastInfo(pagedTextLayout: PagedTextLayout) {
|
||||||
|
// // 현재 페이지 인덱스 저장 (책갈피 기능)
|
||||||
|
// (currentPage?.pathUrl ?: lastedUrl?.toUri()?.path)?.let {
|
||||||
|
// HistoryManager.setBookPageInfo(contentsType, it, pagedTextLayout.current())
|
||||||
|
// }
|
||||||
|
// // ... Realm 업데이트 로직
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun onTouch(touchArea: TouchArea) {
|
||||||
|
// if(!enableGestures) return
|
||||||
|
// when(touchArea) {
|
||||||
|
// TouchArea.Right -> actionNextEvent()
|
||||||
|
// TouchArea.Left -> actionPrevEvent()
|
||||||
|
// TouchArea.DoubleRight -> actionNextEvent(true)
|
||||||
|
// TouchArea.DoubleLeft -> actionPrevEvent(true)
|
||||||
|
// else -> {}
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// override fun onSwipeLeft(count: Int) { if(enableGestures) actionNextEvent(count > 1) }
|
||||||
|
// override fun onSwipeRight(count: Int) { if(enableGestures) actionPrevEvent(count > 1) }
|
||||||
|
// override fun onSwipeDown(touchCount: Int) {
|
||||||
|
// if (touchCount == 2) {
|
||||||
|
// binding.pagedLayer.visibility = GONE
|
||||||
|
// if (showWebOnSwipeDown) binding.menuWeb.visibility = VISIBLE
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun onLongClick() {}
|
||||||
|
// override fun onSwipeUp(touchCount: Int) {}
|
||||||
|
// override fun onTimeoverTouch() {}
|
||||||
|
//
|
||||||
|
// private fun goToHome() {
|
||||||
|
// binding.menuWeb.loadUrl(getLastedDoamin())
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun showToast(msg: String) {
|
||||||
|
// activity?.runOnUiThread {
|
||||||
|
// Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun onDestroyView() {
|
||||||
|
// super.onDestroyView()
|
||||||
|
// _binding = null
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // --- Helper for Config ---
|
||||||
|
// fun loadLastInfo() {
|
||||||
|
// var targetUrl = getLastedDoamin()
|
||||||
|
// try {
|
||||||
|
// applyReaderConfig()
|
||||||
|
// openRealm().query<LastInfo>("contentsType == $0", contentsType).find().lastOrNull()?.copyFromRealm()?.let { lastInfo ->
|
||||||
|
// HistoryManager.getBookPageInfo(contentsType, lastInfo.pageUrl) { info ->
|
||||||
|
// info?.let {
|
||||||
|
// currentPage = it
|
||||||
|
// if ((currentPage?.pathUrl?.length ?: 0) > 1) {
|
||||||
|
// HistoryManager.save(HistoryItem().putHistory(currentPage, currentPage?.pathUrl ?: getLastedDoamin()))
|
||||||
|
// currentPage?.pathUrl?.let { targetPath ->
|
||||||
|
// targetUrl = if (targetPath.startsWith("http", true)) targetPath else getLastedDoamin() + targetPath
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } catch (e: Exception) { e.printStackTrace() }
|
||||||
|
// finally {
|
||||||
|
// if (lastedUrl?.contains(targetUrl) != true) {
|
||||||
|
// contentsLoad(targetUrl)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun onResume() {
|
||||||
|
// super.onResume()
|
||||||
|
// loadLastInfo()
|
||||||
|
// }
|
||||||
|
//}
|
||||||
File diff suppressed because it is too large
Load Diff
1251
app/src/main/kotlin/bums/lunatic/launcher/home/tokiz/TokiFragment.kt
Normal file
1251
app/src/main/kotlin/bums/lunatic/launcher/home/tokiz/TokiFragment.kt
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,336 +1,336 @@
|
|||||||
package bums.lunatic.launcher.home.tokiz.view
|
//package bums.lunatic.launcher.home.tokiz.view
|
||||||
|
//
|
||||||
import android.annotation.SuppressLint
|
//import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
//import android.content.Context
|
||||||
import android.content.Intent
|
//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.net.toUri
|
//import androidx.core.net.toUri
|
||||||
import androidx.core.view.isVisible
|
//import androidx.core.view.isVisible
|
||||||
import bums.lunatic.launcher.R
|
//import bums.lunatic.launcher.R
|
||||||
import bums.lunatic.launcher.helpers.ForeGroundService
|
//import bums.lunatic.launcher.helpers.ForeGroundService
|
||||||
import bums.lunatic.launcher.helpers.ForeGroundService.Companion.ACTION_VIDEO_DOWNLOAD
|
//import bums.lunatic.launcher.helpers.ForeGroundService.Companion.ACTION_VIDEO_DOWNLOAD
|
||||||
import bums.lunatic.launcher.helpers.ForeGroundService.Companion.EXTRA_TARGET_URL
|
//import bums.lunatic.launcher.helpers.ForeGroundService.Companion.EXTRA_TARGET_URL
|
||||||
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 com.yausername.youtubedl_android.YoutubeDL
|
||||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
//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
|
||||||
import org.mozilla.gecko.util.ThreadUtils.runOnUiThread
|
//import org.mozilla.gecko.util.ThreadUtils.runOnUiThread
|
||||||
import org.mozilla.geckoview.GeckoView
|
//import org.mozilla.geckoview.GeckoView
|
||||||
import java.io.File
|
//import java.io.File
|
||||||
import java.util.Base64
|
//import java.util.Base64
|
||||||
import kotlin.collections.iterator
|
//import kotlin.collections.iterator
|
||||||
|
//
|
||||||
enum class JxEvent {
|
//enum class JxEvent {
|
||||||
SCROLL_UP,
|
// SCROLL_UP,
|
||||||
SCROLL_DOWN,
|
// SCROLL_DOWN,
|
||||||
SWIPE_LEFT,
|
// SWIPE_LEFT,
|
||||||
SWIPE_RIGHT,
|
// SWIPE_RIGHT,
|
||||||
ON_CLICK,
|
// ON_CLICK,
|
||||||
}
|
//}
|
||||||
typealias JxInteface = (JxEvent)->Unit
|
//typealias JxInteface = (JxEvent)->Unit
|
||||||
open class BWebview : GeckoView {
|
//open class BWebview : GeckoView {
|
||||||
var decoViews = arrayListOf<View>()
|
// 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 ->
|
||||||
if (event.device.name?.contains(
|
// if (event.device.name?.contains(
|
||||||
"JX-12",
|
// "JX-12",
|
||||||
true
|
// true
|
||||||
) == true || event.device.name?.equals("J06", true) == true
|
// ) == true || event.device.name?.equals("J06", true) == true
|
||||||
) {
|
// ) {
|
||||||
return@setOnTouchListener mSimpleFingerGestures.onTouch(v, event)
|
// return@setOnTouchListener mSimpleFingerGestures.onTouch(v, event)
|
||||||
} else {
|
// } else {
|
||||||
return@setOnTouchListener super.onTouchEvent(event)
|
// return@setOnTouchListener super.onTouchEvent(event)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
val nullCursor = PointerIcon.getSystemIcon(context!!, PointerIcon.TYPE_NULL)
|
// val nullCursor = PointerIcon.getSystemIcon(context!!, PointerIcon.TYPE_NULL)
|
||||||
this.setPointerIcon(nullCursor)
|
// this.setPointerIcon(nullCursor)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
// @SuppressLint("ClickableViewAccessibility")
|
||||||
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
|
// constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
|
||||||
this.setOnTouchListener { v,event ->
|
// this.setOnTouchListener { v,event ->
|
||||||
if (event.device.name?.contains("JX-12",true) == true|| event.device.name?.equals("J06",true) == true) {
|
// if (event.device.name?.contains("JX-12",true) == true|| event.device.name?.equals("J06",true) == true) {
|
||||||
return@setOnTouchListener mSimpleFingerGestures.onTouch(v,event)
|
// return@setOnTouchListener mSimpleFingerGestures.onTouch(v,event)
|
||||||
} else {
|
// } else {
|
||||||
return@setOnTouchListener super.onTouchEvent(event)
|
// return@setOnTouchListener super.onTouchEvent(event)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
val nullCursor = PointerIcon.getSystemIcon(context!!, PointerIcon.TYPE_NULL)
|
// val nullCursor = PointerIcon.getSystemIcon(context!!, PointerIcon.TYPE_NULL)
|
||||||
this.setPointerIcon(nullCursor)
|
// this.setPointerIcon(nullCursor)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
fun videoDlownLoad(videoUrl : String) {
|
// fun videoDlownLoad(videoUrl : String) {
|
||||||
val actionIntent = Intent(context, ForeGroundService::class.java).apply {
|
// val actionIntent = Intent(context, ForeGroundService::class.java).apply {
|
||||||
action = ACTION_VIDEO_DOWNLOAD
|
// action = ACTION_VIDEO_DOWNLOAD
|
||||||
putExtra(EXTRA_TARGET_URL, videoUrl) // 전달할 데이터
|
// putExtra(EXTRA_TARGET_URL, videoUrl) // 전달할 데이터
|
||||||
}
|
// }
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
context.startForegroundService(actionIntent)
|
// context.startForegroundService(actionIntent)
|
||||||
} else {
|
// } else {
|
||||||
context.startService(actionIntent)
|
// context.startService(actionIntent)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
class GKCookie {
|
// class GKCookie {
|
||||||
var COOKIES : String? = null
|
// var COOKIES : String? = null
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
var mGKCookie : GKCookie? = null
|
// var mGKCookie : GKCookie? = null
|
||||||
|
//
|
||||||
fun checkIfDownloadable(url: String) {
|
// fun checkIfDownloadable(url: String) {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
// CoroutineScope(Dispatchers.Main).launch {
|
||||||
runOnUiThread {
|
// runOnUiThread {
|
||||||
decoViews.filter { it.id == R.id.dl_video }.firstOrNull()?.let {
|
// decoViews.filter { it.id == R.id.dl_video }.firstOrNull()?.let {
|
||||||
it.setOnClickListener {}
|
// it.setOnClickListener {}
|
||||||
it.visibility = GONE
|
// it.visibility = GONE
|
||||||
}}}
|
// }}}
|
||||||
Blog.LOGE("checkIfDownloadable ${url}")
|
// Blog.LOGE("checkIfDownloadable ${url}")
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
// CoroutineScope(Dispatchers.IO).launch {
|
||||||
try {
|
// try {
|
||||||
var request = YoutubeDLRequest(url)
|
// var request = YoutubeDLRequest(url)
|
||||||
(mGKCookie?.COOKIES)?.let{
|
// (mGKCookie?.COOKIES)?.let{
|
||||||
Blog.LOGE(it)
|
// Blog.LOGE(it)
|
||||||
val cookies = it.split(";")
|
// val cookies = it.split(";")
|
||||||
.map { it.trim() }
|
// .map { it.trim() }
|
||||||
.mapNotNull {
|
// .mapNotNull {
|
||||||
val parts = it.split("=", limit = 2)
|
// val parts = it.split("=", limit = 2)
|
||||||
if (parts.size == 2) parts[0] to parts[1] else null
|
// if (parts.size == 2) parts[0] to parts[1] else null
|
||||||
}
|
// }
|
||||||
.toMap()
|
// .toMap()
|
||||||
val expires = (System.currentTimeMillis() / 1000) + 3600 * 24 * 7 // 일주일 후 만료 예시
|
// val expires = (System.currentTimeMillis() / 1000) + 3600 * 24 * 7 // 일주일 후 만료 예시
|
||||||
|
//
|
||||||
val cookieFileContent = buildString {
|
// val cookieFileContent = buildString {
|
||||||
appendLine("# Netscape HTTP Cookie File")
|
// appendLine("# Netscape HTTP Cookie File")
|
||||||
for ((name, value) in cookies) {
|
// for ((name, value) in cookies) {
|
||||||
appendLine(".${url.toUri().host}\tTRUE\t/\tTRUE\t$expires\t$name\t$value")
|
// appendLine(".${url.toUri().host}\tTRUE\t/\tTRUE\t$expires\t$name\t$value")
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
val cookieFile = File(context.filesDir, "cookies.txt")
|
// val cookieFile = File(context.filesDir, "cookies.txt")
|
||||||
cookieFile.writeText(cookieFileContent)
|
// cookieFile.writeText(cookieFileContent)
|
||||||
request.addOption("--cookies", cookieFile.absolutePath)
|
// request.addOption("--cookies", cookieFile.absolutePath)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
val videoInfo = YoutubeDL.getInstance().getInfo(request)
|
// val videoInfo = YoutubeDL.getInstance().getInfo(request)
|
||||||
// videoInfo 가 null 아니고, 필요한 키(예: title, url 등)가 있으면 다운로드 가능
|
// // videoInfo 가 null 아니고, 필요한 키(예: title, url 등)가 있으면 다운로드 가능
|
||||||
Blog.LOGE("checkIfDownloadable ${url}\n videoInfo : ${videoInfo}")
|
// Blog.LOGE("checkIfDownloadable ${url}\n videoInfo : ${videoInfo}")
|
||||||
var canVideoDown = videoInfo != null && !videoInfo.title.isNullOrEmpty()
|
// var canVideoDown = videoInfo != null && !videoInfo.title.isNullOrEmpty()
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
// CoroutineScope(Dispatchers.Main).launch {
|
||||||
runOnUiThread {
|
// runOnUiThread {
|
||||||
decoViews.filter { it.id == R.id.dl_video }.firstOrNull()?.let {
|
// decoViews.filter { it.id == R.id.dl_video }.firstOrNull()?.let {
|
||||||
it.setOnClickListener {
|
// it.setOnClickListener {
|
||||||
videoDlownLoad(url)
|
// videoDlownLoad(url)
|
||||||
}
|
// }
|
||||||
it.visibility = if (canVideoDown){
|
// it.visibility = if (canVideoDown){
|
||||||
VISIBLE
|
// VISIBLE
|
||||||
} else{
|
// } else{
|
||||||
GONE
|
// GONE
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
} catch (e: Exception) {
|
// } catch (e: Exception) {
|
||||||
e.printStackTrace()
|
// e.printStackTrace()
|
||||||
Blog.LOGE("checkIfDownloadable ${url} ${e}")
|
// Blog.LOGE("checkIfDownloadable ${url} ${e}")
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
// CoroutineScope(Dispatchers.Main).launch {
|
||||||
runOnUiThread {
|
// runOnUiThread {
|
||||||
decoViews.filter { it.id == R.id.dl_video }.firstOrNull()?.let {
|
// decoViews.filter { it.id == R.id.dl_video }.firstOrNull()?.let {
|
||||||
it.setOnClickListener {}
|
// it.setOnClickListener {}
|
||||||
it.visibility = GONE
|
// it.visibility = GONE
|
||||||
}}}
|
// }}}
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
val mSimpleFingerGestures = SimpleFingerGestures(omfgl = object : SimpleFingerGestures.OnFingerGestureListener{
|
// val mSimpleFingerGestures = SimpleFingerGestures(omfgl = object : SimpleFingerGestures.OnFingerGestureListener{
|
||||||
|
//
|
||||||
override fun onSwipeUp(
|
// override fun onSwipeUp(
|
||||||
targetView: View,
|
// targetView: View,
|
||||||
fingers: Int,
|
// fingers: Int,
|
||||||
gestureDuration: Long,
|
// gestureDuration: Long,
|
||||||
gestureDistance: Double
|
// gestureDistance: Double
|
||||||
): Boolean {
|
// ): Boolean {
|
||||||
Blog.LOGE("")
|
// Blog.LOGE("")
|
||||||
jxInteface?.invoke(JxEvent.SCROLL_UP )
|
// jxInteface?.invoke(JxEvent.SCROLL_UP )
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
override fun onSwipeDown(
|
// override fun onSwipeDown(
|
||||||
targetView: View,
|
// targetView: View,
|
||||||
fingers: Int,
|
// fingers: Int,
|
||||||
gestureDuration: Long,
|
// gestureDuration: Long,
|
||||||
gestureDistance: Double
|
// gestureDistance: Double
|
||||||
): Boolean {
|
// ): Boolean {
|
||||||
Blog.LOGE("")
|
// Blog.LOGE("")
|
||||||
jxInteface?.invoke(JxEvent.SCROLL_DOWN)
|
// jxInteface?.invoke(JxEvent.SCROLL_DOWN)
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
override fun onSwipeLeft(
|
// override fun onSwipeLeft(
|
||||||
targetView: View,
|
// targetView: View,
|
||||||
fingers: Int,
|
// fingers: Int,
|
||||||
gestureDuration: Long,
|
// gestureDuration: Long,
|
||||||
gestureDistance: Double
|
// gestureDistance: Double
|
||||||
): Boolean {
|
// ): Boolean {
|
||||||
Blog.LOGE("")
|
// Blog.LOGE("")
|
||||||
jxInteface?.invoke(JxEvent.SWIPE_LEFT)
|
// jxInteface?.invoke(JxEvent.SWIPE_LEFT)
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
override fun onSwipeRight(
|
// override fun onSwipeRight(
|
||||||
targetView: View,
|
// targetView: View,
|
||||||
fingers: Int,
|
// fingers: Int,
|
||||||
gestureDuration: Long,
|
// gestureDuration: Long,
|
||||||
gestureDistance: Double
|
// gestureDistance: Double
|
||||||
): Boolean {
|
// ): Boolean {
|
||||||
Blog.LOGE("")
|
// Blog.LOGE("")
|
||||||
jxInteface?.invoke(JxEvent.SWIPE_RIGHT)
|
// jxInteface?.invoke(JxEvent.SWIPE_RIGHT)
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
override fun onPinch(
|
// override fun onPinch(
|
||||||
targetView: View,
|
// targetView: View,
|
||||||
fingers: Int,
|
// fingers: Int,
|
||||||
gestureDuration: Long,
|
// gestureDuration: Long,
|
||||||
gestureDistance: Double
|
// gestureDistance: Double
|
||||||
): Boolean {
|
// ): Boolean {
|
||||||
Blog.LOGE("onPinch")
|
// Blog.LOGE("onPinch")
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
override fun onUnpinch(
|
// override fun onUnpinch(
|
||||||
targetView: View,
|
// targetView: View,
|
||||||
fingers: Int,
|
// fingers: Int,
|
||||||
gestureDuration: Long,
|
// gestureDuration: Long,
|
||||||
gestureDistance: Double
|
// gestureDistance: Double
|
||||||
): Boolean {
|
// ): Boolean {
|
||||||
Blog.LOGE("")
|
// Blog.LOGE("")
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
override fun onDoubleTap(
|
// override fun onDoubleTap(
|
||||||
targetView: View,
|
// targetView: View,
|
||||||
fingers: Int
|
// fingers: Int
|
||||||
): Boolean {
|
// ): Boolean {
|
||||||
Blog.LOGE("")
|
// Blog.LOGE("")
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
override fun onLongPress(
|
// override fun onLongPress(
|
||||||
targetView: View,
|
// targetView: View,
|
||||||
fingers: Int
|
// fingers: Int
|
||||||
): Boolean {
|
// ): Boolean {
|
||||||
Blog.LOGE("onLongPress")
|
// Blog.LOGE("onLongPress")
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
override fun onClick(
|
// override fun onClick(
|
||||||
targetView: View,
|
// targetView: View,
|
||||||
fingers: Int
|
// fingers: Int
|
||||||
): Boolean {
|
// ): Boolean {
|
||||||
Blog.LOGE("onClick")
|
// Blog.LOGE("onClick")
|
||||||
jxInteface?.invoke(JxEvent.ON_CLICK)
|
// jxInteface?.invoke(JxEvent.ON_CLICK)
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
})
|
// })
|
||||||
companion object {
|
// companion object {
|
||||||
var currentRetryCount = 0
|
// var currentRetryCount = 0
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
var jxInteface : JxInteface? = null
|
// var jxInteface : JxInteface? = null
|
||||||
|
//
|
||||||
var lastDomain : String = ""
|
// var lastDomain : String = ""
|
||||||
|
//
|
||||||
open fun loadUrl(url: String, param : String? = null) {
|
// open fun loadUrl(url: String, param : String? = null) {
|
||||||
var nUrl = url
|
// var nUrl = url
|
||||||
Blog.LOGE("url >>>> ${url}")
|
// Blog.LOGE("url >>>> ${url}")
|
||||||
if (url.endsWith("=")) {
|
// if (url.endsWith("=")) {
|
||||||
nUrl = String(Base64.getMimeDecoder().decode(url.toByteArray()))
|
// nUrl = String(Base64.getMimeDecoder().decode(url.toByteArray()))
|
||||||
param?.let {
|
// param?.let {
|
||||||
nUrl = nUrl.plus(param)
|
// nUrl = nUrl.plus(param)
|
||||||
}
|
// }
|
||||||
} else if (url.startsWith("http") == false) {
|
// } else if (url.startsWith("http") == false) {
|
||||||
nUrl = lastDomain
|
// nUrl = lastDomain
|
||||||
}
|
// }
|
||||||
if (this.isVisible == false) {
|
// if (this.isVisible == false) {
|
||||||
this.visibility = VISIBLE
|
// this.visibility = VISIBLE
|
||||||
}
|
// }
|
||||||
Blog.LOGE("nUrl >>>> ${nUrl}")
|
// Blog.LOGE("nUrl >>>> ${nUrl}")
|
||||||
|
//
|
||||||
|
//
|
||||||
nUrl?.let { url ->
|
// nUrl?.let { url ->
|
||||||
if (url.split("//").size > 1) {
|
// if (url.split("//").size > 1) {
|
||||||
url.replace("//","/").replace("https:/","https://").let {
|
// url.replace("//","/").replace("https:/","https://").let {
|
||||||
Blog.LOGE("url >> ${url} , it >>> ${it}")
|
// Blog.LOGE("url >> ${url} , it >>> ${it}")
|
||||||
this.session?.loadUri(it)
|
// this.session?.loadUri(it)
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
this.session?.loadUri(url)
|
// this.session?.loadUri(url)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
currentRetryCount = 0;
|
// currentRetryCount = 0;
|
||||||
}
|
// }
|
||||||
private var lastX = 0f
|
// private var lastX = 0f
|
||||||
private var lastY = 0f
|
// private var lastY = 0f
|
||||||
|
//
|
||||||
|
//
|
||||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
// override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||||
Blog.LOGE("event.device.name >>> ${event.device.name}")
|
// Blog.LOGE("event.device.name >>> ${event.device.name}")
|
||||||
if (event.device.name?.contains("JX-12",true) == true || event.device.name?.equals("J06",true) == true) {
|
// if (event.device.name?.contains("JX-12",true) == true || event.device.name?.equals("J06",true) == true) {
|
||||||
Blog.LOGE("BWebview onTouchEvent $event")
|
// Blog.LOGE("BWebview onTouchEvent $event")
|
||||||
when (event.action) {
|
// when (event.action) {
|
||||||
MotionEvent.ACTION_DOWN -> {
|
// MotionEvent.ACTION_DOWN -> {
|
||||||
lastX = event.x
|
// lastX = event.x
|
||||||
lastY = event.y
|
// lastY = event.y
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
MotionEvent.ACTION_MOVE -> {
|
// MotionEvent.ACTION_MOVE -> {
|
||||||
val deltaX = event.x - lastX
|
// val deltaX = event.x - lastX
|
||||||
val deltaY = event.y - lastY
|
// val deltaY = event.y - lastY
|
||||||
// 상하 이동이 더 크면(즉, 거의 수직 이동이면)만 처리
|
// // 상하 이동이 더 크면(즉, 거의 수직 이동이면)만 처리
|
||||||
if (Math.abs(deltaY) > Math.abs(deltaX)) {
|
// if (Math.abs(deltaY) > Math.abs(deltaX)) {
|
||||||
// 원하는 감도 적용
|
// // 원하는 감도 적용
|
||||||
val scrollFactor = 0.1f
|
// val scrollFactor = 0.1f
|
||||||
// scrollBy(0, (-deltaY * scrollFactor).toInt())
|
//// scrollBy(0, (-deltaY * scrollFactor).toInt())
|
||||||
|
//
|
||||||
jxInteface?.invoke(if ((-deltaY * scrollFactor).toInt() > 0){
|
// jxInteface?.invoke(if ((-deltaY * scrollFactor).toInt() > 0){
|
||||||
JxEvent.SCROLL_DOWN
|
// JxEvent.SCROLL_DOWN
|
||||||
} else {
|
// } else {
|
||||||
JxEvent.SCROLL_UP
|
// JxEvent.SCROLL_UP
|
||||||
})
|
// })
|
||||||
lastY = event.y
|
// lastY = event.y
|
||||||
lastX = event.x
|
// lastX = event.x
|
||||||
Blog.LOGE("return true for scroll")
|
// Blog.LOGE("return true for scroll")
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
// 좌우 이동은 무시
|
// // 좌우 이동은 무시
|
||||||
lastY = event.y
|
// lastY = event.y
|
||||||
lastX = event.x
|
// lastX = event.x
|
||||||
Blog.LOGE("return false")
|
// Blog.LOGE("return false")
|
||||||
return false
|
// return false
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
else -> {
|
// else -> {
|
||||||
Blog.LOGE("call super")
|
// Blog.LOGE("call super")
|
||||||
return super.onTouchEvent(event)
|
// return super.onTouchEvent(event)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
return super.onTouchEvent(event)
|
// return super.onTouchEvent(event)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
@ -8,7 +8,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
>
|
>
|
||||||
|
|
||||||
<bums.lunatic.launcher.home.tokiz.view.BWebview
|
<bums.lunatic.launcher.home.GeckoWeb
|
||||||
android:id="@+id/menu_web"
|
android:id="@+id/menu_web"
|
||||||
android:layout_margin="5dp"
|
android:layout_margin="5dp"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user