...
This commit is contained in:
parent
83546e5e10
commit
70869d7e1e
@ -9,7 +9,7 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "bums.lunatic.launcher"
|
||||
namespace = "bums.lunatic.launcher"
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
@ -115,6 +115,15 @@ android {
|
||||
doNotStrip("**/libaria2c.zip.so")
|
||||
doNotStrip("**/libffmpeg.zip.so")
|
||||
doNotStrip("**/libpython.zip.so")
|
||||
jniLibs {
|
||||
pickFirsts.add("lib/**/libc++_shared.so")
|
||||
pickFirsts.add("lib/**/libavcodec.so")
|
||||
pickFirsts.add("lib/**/libavfilter.so")
|
||||
pickFirsts.add("lib/**/libavformat.so")
|
||||
pickFirsts.add("lib/**/libavutil.so")
|
||||
pickFirsts.add("lib/**/libswresample.so")
|
||||
pickFirsts.add("lib/**/libswscale.so")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -126,6 +135,7 @@ dependencies {
|
||||
"dir" to "libs",
|
||||
"include" to listOf("*.aar", "*.jar"),
|
||||
)))
|
||||
implementation(project(":gdrive"))
|
||||
val kotlinVersion: String? by extra
|
||||
val realmVersion = "2.0.0"
|
||||
implementation ("androidx.appcompat:appcompat:1.7.1")
|
||||
@ -196,6 +206,28 @@ dependencies {
|
||||
// implementation ("me.everything:providers-core:1.0.1")
|
||||
// implementation ("androidx.window:window:1.0.0")
|
||||
// implementation("io.github.vaneproject:hanguleditor:1.0.0")
|
||||
|
||||
|
||||
// implementation ("androidx.concurrent:concurrent-listenablefuture-ktx:1.1.0")
|
||||
implementation ("com.google.guava:guava:33.2.1-jre")
|
||||
val camerax_version = "1.3.0" // 안정적인 최신 버전 사용
|
||||
|
||||
// CameraX 코어 라이브러리
|
||||
implementation("androidx.camera:camera-core:$camerax_version")
|
||||
implementation("androidx.camera:camera-camera2:$camerax_version")
|
||||
|
||||
// CameraX Lifecycle 라이브러리 (Fragment/Activity 생명주기 연결)
|
||||
implementation("androidx.camera:camera-lifecycle:$camerax_version")
|
||||
|
||||
// CameraX View 라이브러리 (PreviewView 등 UI 구성 요소)
|
||||
implementation("androidx.camera:camera-view:$camerax_version")
|
||||
|
||||
// (선택) CameraX 비디오 라이브러리
|
||||
implementation("androidx.camera:camera-video:$camerax_version")
|
||||
|
||||
// (선택) CameraX 확장 라이브러리 (보케, HDR 등)
|
||||
implementation("androidx.camera:camera-extensions:$camerax_version")
|
||||
|
||||
constraints {
|
||||
// ⚠️ 이 버전을 프로젝트 루트의 build.gradle.kts에 정의된 kotlinVersion 값과 정확히 일치시키세요.
|
||||
val targetKotlinVersion = "2.0.20"
|
||||
|
||||
@ -58,11 +58,9 @@
|
||||
<uses-permission
|
||||
android:name="android.permission.PACKAGE_USAGE_STATS"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_CALL_LOG" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_SMS" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
<application
|
||||
android:name=".LunaticLauncher"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
|
||||
@ -33,7 +33,10 @@ abstract class CommonActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
||||
try {
|
||||
supportActionBar?.hide()
|
||||
actionBar?.hide()
|
||||
} catch (e: Exception) {}
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
val insetsController = WindowInsetsControllerCompat(window, window.decorView)
|
||||
var forWhite = false
|
||||
|
||||
@ -184,7 +184,7 @@ open class GeckoWeb @JvmOverloads constructor(
|
||||
|
||||
override fun setVisibility(visibility: Int) {
|
||||
super.setVisibility(visibility)
|
||||
decoViews.filter { it.id > -1 && it.id != R.id.dl_video }.forEach { it.visibility = visibility }
|
||||
decoViews.filter { it.id > -1 && it.id != R.id.btn_dl_video }.forEach { it.visibility = visibility }
|
||||
}
|
||||
|
||||
open fun loadUrl(url: String, param: String? = null) {
|
||||
@ -212,7 +212,7 @@ open class GeckoWeb @JvmOverloads constructor(
|
||||
fun checkIfDownloadable(url: String) {
|
||||
// UI 초기화
|
||||
post {
|
||||
decoViews.firstOrNull { it.id == R.id.dl_video }?.let {
|
||||
decoViews.firstOrNull { it.id == R.id.btn_dl_video }?.let {
|
||||
it.setOnClickListener {}
|
||||
it.visibility = GONE
|
||||
}
|
||||
@ -242,7 +242,7 @@ open class GeckoWeb @JvmOverloads constructor(
|
||||
val videoInfo = YoutubeDL.getInstance().getInfo(request)
|
||||
if (videoInfo != null && !videoInfo.title.isNullOrEmpty()) {
|
||||
post {
|
||||
decoViews.firstOrNull { it.id == R.id.dl_video }?.let { view ->
|
||||
decoViews.firstOrNull { it.id == R.id.btn_dl_video }?.let { view ->
|
||||
view.setOnClickListener { videoDownload(url) }
|
||||
view.visibility = VISIBLE
|
||||
}
|
||||
@ -320,10 +320,11 @@ open class GeckoWeb @JvmOverloads constructor(
|
||||
|
||||
// 외부 뷰 업데이트
|
||||
decoViews.forEach { view ->
|
||||
if (view == null) return@forEach // 뷰가 null이면 건너뛰어 크래시 방지
|
||||
when(view.id) {
|
||||
R.id.back -> view.setOnClickListener { session.goBack() }
|
||||
R.id.current_address -> (view as? TextView)?.apply { tag = currentTitle; text = url }
|
||||
R.id.reload -> view.setOnClickListener { session.reload() }
|
||||
R.id.btn_back -> view.setOnClickListener { session.goBack() }
|
||||
R.id.tv_address -> (view as? TextView)?.apply { tag = currentTitle; text = url }
|
||||
R.id.btn_reload -> view.setOnClickListener { session.reload() }
|
||||
}
|
||||
}
|
||||
// scrollState = 0
|
||||
|
||||
@ -164,7 +164,7 @@ open class NeoRssActivity : CommonActivity() {
|
||||
}
|
||||
else {
|
||||
|
||||
val currentFragment = fragmentMap.values.find { it.isAdded && (!it.isHidden || it.isVisible) }
|
||||
val currentFragment = supportFragmentManager.fragments.find { it.isAdded && (!it.isHidden || it.isVisible) }
|
||||
if (currentFragment is KeyEventHandler) {
|
||||
if (currentFragment.onKeyEvent(ev)) {
|
||||
return true
|
||||
@ -338,28 +338,16 @@ open class NeoRssActivity : CommonActivity() {
|
||||
}
|
||||
return@setOnTouchListener false
|
||||
}
|
||||
binding.floatingActionMenu.setOnMenuButtonClickListener { v->
|
||||
Blog.LOGE("v >> ${v}")
|
||||
showContents(v.id)
|
||||
}
|
||||
// binding.floatingActionMenu.setOnMenuButtonClickListener { v->
|
||||
// Blog.LOGE("v >> ${v}")
|
||||
// showContents(v.id)
|
||||
// }
|
||||
|
||||
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 {
|
||||
setAction(Intent.ACTION_SEND)
|
||||
(binding.currentAddress.tag as? String)?.let{putExtra(Intent.EXTRA_TITLE, it)}
|
||||
putExtra(Intent.EXTRA_TEXT, binding.currentAddress.text)
|
||||
setType("text/plain")
|
||||
}
|
||||
val shareIntent = Intent.createChooser(sendIntent, "링크 공유하기")
|
||||
startActivity(shareIntent)
|
||||
}
|
||||
}
|
||||
showContents(R.id.btn_info)
|
||||
}
|
||||
|
||||
@ -377,32 +365,31 @@ open class NeoRssActivity : CommonActivity() {
|
||||
private var lastTouchY = 0f
|
||||
|
||||
|
||||
private val fragmentMap = mutableMapOf<Int, Fragment>()
|
||||
// private val fragmentMap = mutableMapOf<Int, Fragment>()
|
||||
|
||||
fun showContents(id : Int) {
|
||||
binding.fragmentLayer.visibility = View.VISIBLE
|
||||
binding.fragmentContainer.visibility = View.VISIBLE
|
||||
binding.controllPanel.visibility = View.VISIBLE
|
||||
binding.floatingActionMenu.visibility = View.VISIBLE
|
||||
if (id == View.NO_ID) {
|
||||
Blog.LOGE("무효한 ID(-1) 호출로 인해 showContents를 취소합니다.")
|
||||
return
|
||||
}
|
||||
|
||||
Blog.LOGE("targetFragment id ${id}")
|
||||
// 1. 모든 관련 레이어 가시성 확보
|
||||
binding.fragmentContainer.isVisible = true
|
||||
|
||||
val transaction = supportFragmentManager.beginTransaction()
|
||||
|
||||
fragmentMap.values.forEach { fragment ->
|
||||
if (fragment.isAdded) {
|
||||
transaction.hide(fragment)
|
||||
val tagKey = "TAG_$id"
|
||||
Blog.LOGE("targetFragment id ${id} tagKey ${tagKey}")
|
||||
// 2. 매니저에 등록된 모든 프래그먼트를 찾아서 숨김 (중복 방지)
|
||||
val allFragments = supportFragmentManager.fragments
|
||||
for (f in allFragments) {
|
||||
if (f.isAdded && f.isVisible) {
|
||||
transaction.hide(f)
|
||||
}
|
||||
}
|
||||
// 2. 요청된 ID에 해당하는 프래그먼트가 이미 생성되었는지 확인
|
||||
var targetFragment = fragmentMap[id]
|
||||
|
||||
if (targetFragment == null) {
|
||||
targetFragment = supportFragmentManager.findFragmentByTag("TAG_$id")
|
||||
|
||||
// 시스템이 복구해 놓은 게 있다면, 맵에 다시 등록(싱크 맞추기)
|
||||
if (targetFragment != null) {
|
||||
fragmentMap[id] = targetFragment
|
||||
}
|
||||
}
|
||||
// 3. 대상 프래그먼트 찾기 (findFragmentByTag 우선)
|
||||
var targetFragment = supportFragmentManager.findFragmentByTag(tagKey)
|
||||
|
||||
if (targetFragment == null) {
|
||||
// 처음 호출되는 메뉴라면 인스턴스 생성 및 추가
|
||||
@ -426,10 +413,11 @@ open class NeoRssActivity : CommonActivity() {
|
||||
}
|
||||
|
||||
targetFragment?.let {
|
||||
fragmentMap[id] = it
|
||||
transaction.add(R.id.fragment_container, it, "TAG_$id")
|
||||
Blog.LOGE("targetFragment id ${id} key : ${tagKey}, instance ${targetFragment}")
|
||||
transaction.add(R.id.fragment_container, it, tagKey)
|
||||
}
|
||||
} else {
|
||||
Blog.LOGE("targetFragment id ${id} key : ${tagKey}, instance ${targetFragment}")
|
||||
// 이미 생성된 프래그먼트가 있다면 다시 보여줌
|
||||
transaction.show(targetFragment)
|
||||
}
|
||||
|
||||
@ -220,11 +220,11 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
||||
return when (event.keyCode) {
|
||||
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||
if (isUp) {
|
||||
if (binding.geckoWeb.scrollState > 0) {
|
||||
if (binding.lunaticBrowser.geckoWeb.scrollState > 0) {
|
||||
// 사용자가 손으로 끝까지 내렸든, 볼륨키로 내렸든 상관없이 이곳에 도달함
|
||||
doNextPage()
|
||||
} else {
|
||||
binding.geckoWeb.pageDown()
|
||||
binding.lunaticBrowser.geckoWeb.pageDown()
|
||||
}
|
||||
}
|
||||
true
|
||||
@ -232,10 +232,10 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
||||
}
|
||||
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||
if (isUp) {
|
||||
if (binding.geckoWeb.scrollState < 0) {
|
||||
binding.geckoWeb.session?.goBack()
|
||||
if (binding.lunaticBrowser.geckoWeb.scrollState < 0) {
|
||||
binding.lunaticBrowser.geckoWeb.session?.goBack()
|
||||
} else {
|
||||
binding.geckoWeb.pageUp()
|
||||
binding.lunaticBrowser.geckoWeb.pageUp()
|
||||
}
|
||||
}
|
||||
true
|
||||
@ -261,7 +261,7 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
||||
}
|
||||
}
|
||||
fun searchKeyword() {
|
||||
binding.geckoWeb.visibility = View.GONE
|
||||
binding.lunaticBrowser.visibility = View.GONE
|
||||
val bottomSheet = SearchBottomSheet()
|
||||
bottomSheet.listener = object : OnSearchListener{
|
||||
override fun onSearch(
|
||||
@ -280,11 +280,11 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
||||
|
||||
|
||||
fun ask() {
|
||||
binding.geckoWeb.visibility = View.GONE
|
||||
binding.lunaticBrowser.visibility = View.GONE
|
||||
val bottomSheet = WebBottomSheet()
|
||||
bottomSheet.listener = object : WebBottomSheet.OnGoToWebListener{
|
||||
override fun enterSearch() {
|
||||
binding.geckoWeb.sendSearchDo()
|
||||
binding.lunaticBrowser.geckoWeb.sendSearchDo()
|
||||
}
|
||||
override fun onGotoWeb(
|
||||
keyword: String,
|
||||
@ -294,56 +294,56 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
||||
when (category) {
|
||||
|
||||
RssDataType.ANYTHING -> {
|
||||
if (binding.geckoWeb.lastedUrl?.contains("search.brave") == true) {
|
||||
binding.geckoWeb.sendSearch(keyword)
|
||||
if (binding.lunaticBrowser.geckoWeb.lastedUrl?.contains("search.brave") == true) {
|
||||
binding.lunaticBrowser.geckoWeb.sendSearch(keyword)
|
||||
} else {
|
||||
binding.geckoWeb.loadUrl("https://search.brave.com/", if (keyword.length ?: 0 > 0) { "search?q=${keyword}" } else { null })
|
||||
binding.lunaticBrowser.geckoWeb.loadUrl("https://search.brave.com/", if (keyword.length ?: 0 > 0) { "search?q=${keyword}" } else { null })
|
||||
}
|
||||
binding.geckoWeb.privateMode = false
|
||||
binding.lunaticBrowser.geckoWeb.privateMode = false
|
||||
}
|
||||
RssDataType.GOOGLE -> {
|
||||
if (binding.geckoWeb.lastedUrl?.contains("www.google") == true) {
|
||||
binding.geckoWeb.sendSearch(keyword)
|
||||
if (binding.lunaticBrowser.geckoWeb.lastedUrl?.contains("www.google") == true) {
|
||||
binding.lunaticBrowser.geckoWeb.sendSearch(keyword)
|
||||
} else {
|
||||
binding.geckoWeb.loadUrl("https://www.google.com/", if (keyword.length ?: 0 > 0) { "search?q=${keyword}" } else { null })
|
||||
binding.lunaticBrowser.geckoWeb.loadUrl("https://www.google.com/", if (keyword.length ?: 0 > 0) { "search?q=${keyword}" } else { null })
|
||||
}
|
||||
binding.geckoWeb.privateMode = false
|
||||
binding.lunaticBrowser.geckoWeb.privateMode = false
|
||||
}
|
||||
RssDataType.NAMU -> {
|
||||
if (binding.geckoWeb.lastedUrl?.contains("namu.wiki") == true) {
|
||||
binding.geckoWeb.sendSearch(keyword)
|
||||
if (binding.lunaticBrowser.geckoWeb.lastedUrl?.contains("namu.wiki") == true) {
|
||||
binding.lunaticBrowser.geckoWeb.sendSearch(keyword)
|
||||
} else {
|
||||
binding.geckoWeb.loadUrl("https://namu.wiki", if (keyword.length ?: 0 > 0) {"/Search?q=${keyword}"} else {null})
|
||||
binding.lunaticBrowser.geckoWeb.loadUrl("https://namu.wiki", if (keyword.length ?: 0 > 0) {"/Search?q=${keyword}"} else {null})
|
||||
}
|
||||
binding.geckoWeb.privateMode = false
|
||||
binding.lunaticBrowser.geckoWeb.privateMode = false
|
||||
}
|
||||
RssDataType.NAVER -> {
|
||||
if (binding.geckoWeb.lastedUrl?.contains("naver") == true) {
|
||||
binding.geckoWeb.sendSearch(keyword)
|
||||
if (binding.lunaticBrowser.geckoWeb.lastedUrl?.contains("naver") == true) {
|
||||
binding.lunaticBrowser.geckoWeb.sendSearch(keyword)
|
||||
} else {
|
||||
binding.geckoWeb.loadUrl("https://search.naver.com/", if (keyword.length ?: 0 > 0) {"search.naver?where=nexearch&query==${keyword}"} else {null})
|
||||
binding.lunaticBrowser.geckoWeb.loadUrl("https://search.naver.com/", if (keyword.length ?: 0 > 0) {"search.naver?where=nexearch&query==${keyword}"} else {null})
|
||||
}
|
||||
binding.geckoWeb.privateMode = false
|
||||
binding.lunaticBrowser.geckoWeb.privateMode = false
|
||||
}
|
||||
RssDataType.NEWSFEED -> {
|
||||
if (binding.geckoWeb.lastedUrl?.contains("news.google") == true) {
|
||||
binding.geckoWeb.sendSearch(keyword)
|
||||
if (binding.lunaticBrowser.geckoWeb.lastedUrl?.contains("news.google") == true) {
|
||||
binding.lunaticBrowser.geckoWeb.sendSearch(keyword)
|
||||
} else {
|
||||
binding.geckoWeb.loadUrl("https://news.google.com/", if (keyword.length ?: 0 > 0) {"search?q=${keyword}&hl=ko&gl=KR&ceid=KR%3Ako"} else {null})
|
||||
binding.lunaticBrowser.geckoWeb.loadUrl("https://news.google.com/", if (keyword.length ?: 0 > 0) {"search?q=${keyword}&hl=ko&gl=KR&ceid=KR%3Ako"} else {null})
|
||||
}
|
||||
binding.geckoWeb.privateMode = false
|
||||
binding.lunaticBrowser.geckoWeb.privateMode = false
|
||||
}
|
||||
RssDataType.NEWS -> {
|
||||
if (binding.geckoWeb.lastedUrl?.contains("bigkinds") == true) {
|
||||
binding.geckoWeb.sendSearch(keyword)
|
||||
if (binding.lunaticBrowser.geckoWeb.lastedUrl?.contains("bigkinds") == true) {
|
||||
binding.lunaticBrowser.geckoWeb.sendSearch(keyword)
|
||||
} else {
|
||||
binding.geckoWeb.loadUrl("https://www.bigkinds.or.kr/", if (keyword.length ?: 0 > 0) {"search?q=${keyword}&hl=ko&gl=KR&ceid=KR%3Ako"} else {null})
|
||||
binding.lunaticBrowser.geckoWeb.loadUrl("https://www.bigkinds.or.kr/", if (keyword.length ?: 0 > 0) {"search?q=${keyword}&hl=ko&gl=KR&ceid=KR%3Ako"} else {null})
|
||||
}
|
||||
binding.geckoWeb.privateMode = false
|
||||
binding.lunaticBrowser.geckoWeb.privateMode = false
|
||||
}
|
||||
RssDataType.PRIVATE -> {
|
||||
binding.geckoWeb.privateMode = privateMode
|
||||
binding.geckoWeb.loadUrl("aHR0cHM6Ly9pamF2dG9ycmVudC5jb20=", if (keyword.length ?: 0 > 0) {"/?searchTerm=${keyword}"} else {null})
|
||||
binding.lunaticBrowser.geckoWeb.privateMode = privateMode
|
||||
binding.lunaticBrowser.geckoWeb.loadUrl("aHR0cHM6Ly9pamF2dG9ycmVudC5jb20=", if (keyword.length ?: 0 > 0) {"/?searchTerm=${keyword}"} else {null})
|
||||
}
|
||||
else -> {
|
||||
|
||||
@ -365,7 +365,7 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
||||
}
|
||||
appendReadCount(it, 1, false)
|
||||
binding.layoutRssSummary.title.setOnLongClickListener {
|
||||
currentRss?.originPage?.let { binding.geckoWeb.loadUrl(it)}
|
||||
currentRss?.originPage?.let { binding.lunaticBrowser.geckoWeb.loadUrl(it)}
|
||||
binding.layoutRssSummary.root.visibility = View.GONE
|
||||
true
|
||||
}
|
||||
@ -437,12 +437,15 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
fun openGecko(rssData: RssData? = null) {
|
||||
binding.layoutRssSummary.root.visibility = View.GONE
|
||||
binding.lunaticBrowser.visibility = View.GONE
|
||||
binding.lunaticBrowser.setupForRssHome()
|
||||
if (rssData?.category()?.equals(RssDataType.PRIVATE) == true && rssData?.getMagnet().isNullOrEmpty()) {
|
||||
openSummary(rssData)
|
||||
} else if (rssData?.originPage?.isNotEmpty() == true) {
|
||||
rssData?.let { rss ->
|
||||
currentRss = rss
|
||||
binding.geckoWeb.privateMode = false
|
||||
binding.lunaticBrowser.visibility = View.VISIBLE
|
||||
binding.lunaticBrowser.geckoWeb.privateMode = false
|
||||
appendReadCount(rss, 1, false)
|
||||
rss?.originPage?.let { rssId->
|
||||
synchronized(lasted) {
|
||||
@ -450,7 +453,7 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
||||
lasted.removeAll { target -> target.originPage.equals(rssId) }
|
||||
}
|
||||
}
|
||||
binding.geckoWeb.loadUrl(rssId)
|
||||
binding.lunaticBrowser.geckoWeb.loadUrl(rssId)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -487,17 +490,12 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
||||
return@setOnTouchListener false
|
||||
}
|
||||
}
|
||||
binding.vote.setOnClickListener {
|
||||
if (binding.geckoWeb.isVisible) {
|
||||
vote()
|
||||
}
|
||||
}
|
||||
|
||||
if (openQuery.length > 0) {
|
||||
binding.geckoWeb.loadUrl("https://www.google.com/search?q=${openQuery}")
|
||||
binding.lunaticBrowser.geckoWeb.loadUrl("https://www.google.com/search?q=${openQuery}")
|
||||
openQuery = ""
|
||||
}
|
||||
binding.geckoWeb?.mOnSave = object : GeckoWeb.OnSave{
|
||||
binding.lunaticBrowser.geckoWeb?.mOnSave = object : GeckoWeb.OnSave{
|
||||
override fun saved() {
|
||||
currentRss?.originPage.let {
|
||||
Blog.LOGE("Arrow Center Click")
|
||||
@ -516,41 +514,7 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.hide.setOnClickListener {
|
||||
if (binding.geckoWeb.isVisible) {
|
||||
WorkersDb.getRealm().apply {
|
||||
writeBlocking {
|
||||
currentRss?.originPage?.let {
|
||||
val result = query<RssData>().query("originPage == $0", it).find()
|
||||
if (result.size > 0) {
|
||||
result.forEach {
|
||||
if (it.vote) {
|
||||
it.vote = false
|
||||
}
|
||||
it.hide = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
doNextPage()
|
||||
}
|
||||
}
|
||||
|
||||
binding.home.setOnClickListener {
|
||||
if (binding.geckoWeb.isVisible || binding.layoutRssSummary.root.isVisible) {
|
||||
binding.geckoWeb.visibility = View.GONE
|
||||
binding.layoutRssSummary.root.visibility = View.GONE
|
||||
} else {
|
||||
queryInfos()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
binding.bookmark.setOnClickListener {
|
||||
binding.layoutRssSummary.root.visibility = View.GONE
|
||||
queryVotes()
|
||||
}
|
||||
|
||||
binding.layoutRssSummary.link.setOnClickListener {
|
||||
(it.tag as? RssData)?.let {
|
||||
@ -573,46 +537,113 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
||||
|
||||
queryInfos()
|
||||
|
||||
binding.geckoWeb.progress = binding.progressBar
|
||||
binding.geckoWeb.jxInteface = { jxEvent ->
|
||||
when (jxEvent) {
|
||||
JxEvent.SCROLL_UP -> binding.geckoWeb.sendScrollDown(false)
|
||||
JxEvent.SCROLL_DOWN -> binding.geckoWeb.sendScrollDown(true)
|
||||
JxEvent.ON_CLICK -> {
|
||||
binding.geckoWeb.visibility = View.GONE
|
||||
}
|
||||
val nullCursor = PointerIcon.getSystemIcon(requireContext(), PointerIcon.TYPE_NULL)
|
||||
binding.root.setPointerIcon(nullCursor)
|
||||
|
||||
JxEvent.SWIPE_LEFT -> {
|
||||
binding.lunaticBrowser.let { browser ->
|
||||
// 1. 상단 바 RssHome 전용 기능 연결
|
||||
browser.binding.btnHome.setOnClickListener {
|
||||
// if (binding.lunaticBrowser.isVisible || binding.layoutRssSummary.root.isVisible) {
|
||||
binding.lunaticBrowser.visibility = View.GONE
|
||||
binding.layoutRssSummary.root.visibility = View.GONE
|
||||
// } else {
|
||||
queryInfos()
|
||||
// }
|
||||
}
|
||||
|
||||
browser.binding.btnBookmark.setOnClickListener {
|
||||
binding.layoutRssSummary.root.visibility = View.GONE
|
||||
binding.lunaticBrowser.visibility = View.GONE
|
||||
queryVotes()
|
||||
}
|
||||
|
||||
browser.binding.btnSearch.setOnClickListener { searchKeyword() }
|
||||
browser.binding.btnSearch.setOnLongClickListener{
|
||||
ask()
|
||||
true
|
||||
}
|
||||
browser.binding.btnHide.setOnClickListener {
|
||||
if (binding.lunaticBrowser.isVisible) {
|
||||
WorkersDb.getRealm().apply {
|
||||
writeBlocking {
|
||||
currentRss?.originPage?.let {
|
||||
val result = query<RssData>().query("originPage == $0", it).find()
|
||||
if (result.size > 0) {
|
||||
result.forEach {
|
||||
if (it.vote) {
|
||||
it.vote = false
|
||||
}
|
||||
it.hide = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
doNextPage()
|
||||
}
|
||||
}
|
||||
|
||||
JxEvent.SWIPE_RIGHT -> {
|
||||
browser.binding.btnVote.setOnClickListener {
|
||||
if (binding.lunaticBrowser.isVisible) {
|
||||
vote()
|
||||
}
|
||||
}
|
||||
|
||||
// 2. GeckoWeb 콜백 및 설정 유지
|
||||
browser.geckoWeb.jxInteface = { jxEvent ->
|
||||
when (jxEvent) {
|
||||
JxEvent.SWIPE_LEFT -> doNextPage()
|
||||
JxEvent.SWIPE_RIGHT -> vote()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
val nullCursor = PointerIcon.getSystemIcon(requireContext(), PointerIcon.TYPE_NULL)
|
||||
binding.root.setPointerIcon(nullCursor)
|
||||
binding.search.setOnClickListener { searchKeyword() }
|
||||
binding.search.setOnLongClickListener{
|
||||
ask()
|
||||
true
|
||||
binding.lunaticBrowser.geckoWeb.restoreSessionState()
|
||||
|
||||
binding.let { b ->
|
||||
// 전체 탭
|
||||
b.tabAll.setOnClickListener {
|
||||
highlightTab(it as TextView)
|
||||
queryInfos() // 기존 전체 쿼리 호출
|
||||
}
|
||||
|
||||
// 북마크(Voted) 탭
|
||||
b.tabVoted.setOnClickListener {
|
||||
highlightTab(it as TextView)
|
||||
queryVotes() // 기존 보트 쿼리 호출
|
||||
}
|
||||
|
||||
// 프라이빗 탭
|
||||
b.tabPrivate.setOnClickListener {
|
||||
highlightTab(it as TextView)
|
||||
queryPrevate() // 기존 프라이빗 쿼리 호출
|
||||
}
|
||||
b.tabAll.performClick()
|
||||
|
||||
b.tabSerarch.setOnClickListener {
|
||||
searchKeyword()
|
||||
}
|
||||
b.tabSerarch.setOnLongClickListener {
|
||||
ask()
|
||||
true
|
||||
}
|
||||
|
||||
}
|
||||
binding.geckoWeb.decoViews.add(binding.hide)
|
||||
binding.geckoWeb.decoViews.add(binding.vote)
|
||||
binding.geckoWeb.decoViews.add(binding.progressBar)
|
||||
(activity as? NeoRssActivity)?.let { activity ->
|
||||
binding.geckoWeb.decoViews.add(activity.findViewById<TextView>(R.id.current_address))
|
||||
binding.geckoWeb.decoViews.add(activity.findViewById<ImageButton>(R.id.back))
|
||||
binding.geckoWeb.decoViews.add(activity.findViewById<ImageButton>(R.id.reload))
|
||||
binding.geckoWeb.decoViews.add(activity.findViewById<ImageButton>(R.id.dl_video))
|
||||
}
|
||||
binding.geckoWeb.restoreSessionState()
|
||||
|
||||
// 탭 강조를 위한 간단한 함수
|
||||
|
||||
return binding.root
|
||||
}
|
||||
private fun highlightTab(selected: TextView) {
|
||||
val tabs = listOf(binding.tabAll, binding.tabVoted, binding.tabPrivate)
|
||||
tabs.forEach {
|
||||
it.setTextColor(resources.getColor(R.color.finestSilver))
|
||||
}
|
||||
selected.setTextColor(resources.getColor(R.color.white))
|
||||
}
|
||||
|
||||
fun vote() {
|
||||
binding.geckoWeb?.saveMd(true)
|
||||
binding.lunaticBrowser.geckoWeb?.saveMd(true)
|
||||
}
|
||||
|
||||
// TokiFragment 또는 GeckoView를 사용하는 프래그먼트 내부
|
||||
@ -620,7 +651,7 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
||||
super.onHiddenChanged(hidden)
|
||||
if (hidden) {
|
||||
// 💡 화면에서 사라질 때: 타이머 중지 및 애니메이션 중지
|
||||
binding.geckoWeb?.onPause()
|
||||
binding.lunaticBrowser.geckoWeb?.onPause()
|
||||
// 일반 WebView라면: webView.onPause() 및 webView.pauseTimers()
|
||||
} else {
|
||||
// 💡 다시 나타날 때: 다시 시작
|
||||
@ -960,26 +991,6 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
||||
}
|
||||
fun randomOrNull() : RssData? = lasted.randomOrNull()
|
||||
|
||||
// fun rett(imageView: ImageView,imageUrl: String){
|
||||
// // OkHttp로 직접 이미지 다운로드 후
|
||||
// val request = Request.Builder().url(imageUrl).build()
|
||||
// val client = OkHttpClient()
|
||||
// client.newCall(request).enqueue(object : Callback {
|
||||
// override fun onFailure(call: Call, e: IOException) {
|
||||
// // 실패 시 기본 이미지 처리 or 로깅
|
||||
//
|
||||
// }
|
||||
//
|
||||
// override fun onResponse(call: Call, response: Response) {
|
||||
// response.body?.byteStream()?.let { inputStream ->
|
||||
// val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||
// mainHandler.post({
|
||||
// imageView.setImageBitmap(bitmap)
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
}
|
||||
var toast: Toast? = null
|
||||
fun Context.toast(string: String) {
|
||||
|
||||
@ -38,6 +38,9 @@ import bums.lunatic.launcher.databinding.FragmentSystemStatusBinding
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import kotlinx.coroutines.*
|
||||
import java.util.Calendar
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
import androidx.core.content.ContextCompat
|
||||
|
||||
class SystemStatusFragment : Fragment() {
|
||||
|
||||
@ -120,7 +123,8 @@ class SystemStatusFragment : Fragment() {
|
||||
}
|
||||
binding.progressBattery.setOnClickListener { binding.textBattery.performClick() }
|
||||
}
|
||||
|
||||
private var isFlashOn = false
|
||||
private lateinit var cameraManager: android.hardware.camera2.CameraManager
|
||||
private fun setupQuickControls() {
|
||||
binding.btnQuickWifi.setOnClickListener {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
@ -177,9 +181,109 @@ class SystemStatusFragment : Fragment() {
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
|
||||
})
|
||||
cameraManager = requireContext().getSystemService(Context.CAMERA_SERVICE) as android.hardware.camera2.CameraManager
|
||||
|
||||
binding.btnQuickFlash.setOnClickListener {
|
||||
toggleFlashlight()
|
||||
}
|
||||
binding.btnQuickMirror.setOnClickListener {
|
||||
if (allPermissionsGranted()) {
|
||||
showMirrorDialog() // 권한이 있으면 거울 실행
|
||||
} else {
|
||||
// 권한이 없으면 사용자에게 요청
|
||||
requestPermissions(arrayOf(CAMERA_PERMISSION), REQUEST_CODE_PERMISSIONS)
|
||||
}
|
||||
}
|
||||
|
||||
updateQuickControlUI()
|
||||
}
|
||||
private val CAMERA_PERMISSION = android.Manifest.permission.CAMERA
|
||||
private val REQUEST_CODE_PERMISSIONS = 1001
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int, permissions: Array<String>, grantResults: IntArray
|
||||
) {
|
||||
if (requestCode == REQUEST_CODE_PERMISSIONS) {
|
||||
if (allPermissionsGranted()) {
|
||||
showMirrorDialog()
|
||||
} else {
|
||||
Toast.makeText(requireContext(), "거울 기능을 사용하려면 카메라 권한이 필요합니다.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var flashTimerJob: Job? = null
|
||||
|
||||
private fun toggleFlashlight() {
|
||||
try {
|
||||
val cameraId = cameraManager.cameraIdList[0]
|
||||
isFlashOn = !isFlashOn
|
||||
cameraManager.setTorchMode(cameraId, isFlashOn)
|
||||
|
||||
// 기존 타이머가 있다면 취소
|
||||
flashTimerJob?.cancel()
|
||||
|
||||
if (isFlashOn) {
|
||||
binding.btnQuickFlash.text = "🔦 ON"
|
||||
binding.btnQuickFlash.setTextColor(Color.YELLOW)
|
||||
|
||||
// 30초 후 자동 종료 타이머 시작
|
||||
flashTimerJob = CoroutineScope(Dispatchers.Main).launch {
|
||||
delay(30000L) // 30초
|
||||
if (isFlashOn) {
|
||||
isFlashOn = false
|
||||
cameraManager.setTorchMode(cameraId, false)
|
||||
binding.btnQuickFlash.text = "🔦 조명"
|
||||
binding.btnQuickFlash.setTextColor(Color.WHITE)
|
||||
Toast.makeText(context, "배터리 보호를 위해 조명을 껐습니다.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.btnQuickFlash.text = "🔦 조명"
|
||||
binding.btnQuickFlash.setTextColor(Color.WHITE)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 권한 확인 함수
|
||||
private fun allPermissionsGranted() = androidx.core.content.ContextCompat.checkSelfPermission(
|
||||
requireContext(), CAMERA_PERMISSION) == android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||
|
||||
private fun showMirrorDialog() {
|
||||
val dialog = android.app.Dialog(requireContext(), android.R.style.Theme_Black_NoTitleBar_Fullscreen)
|
||||
val previewView = androidx.camera.view.PreviewView(requireContext())
|
||||
dialog.setContentView(previewView)
|
||||
|
||||
val cameraProviderFuture = androidx.camera.lifecycle.ProcessCameraProvider.getInstance(requireContext())
|
||||
|
||||
cameraProviderFuture.addListener({
|
||||
val cameraProvider = cameraProviderFuture.get()
|
||||
val preview = androidx.camera.core.Preview.Builder().build()
|
||||
|
||||
// 전면 카메라 선택
|
||||
val cameraSelector = androidx.camera.core.CameraSelector.DEFAULT_FRONT_CAMERA
|
||||
|
||||
try {
|
||||
cameraProvider.unbindAll()
|
||||
// 이 다이얼로그의 LifecycleOwner를 Fragment로 지정
|
||||
cameraProvider.bindToLifecycle(this, cameraSelector, preview)
|
||||
preview.setSurfaceProvider(previewView.surfaceProvider)
|
||||
} catch (e: Exception) {
|
||||
Blog.LOGE("거울 실행 실패: ${e.message}")
|
||||
}
|
||||
}, androidx.core.content.ContextCompat.getMainExecutor(requireContext()))
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
delay(30000L)
|
||||
if (dialog.isShowing) {
|
||||
dialog.dismiss()
|
||||
Toast.makeText(context, "거울을 종료합니다.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
// 화면 터치 시 종료
|
||||
previewView.setOnClickListener { dialog.dismiss() }
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
private fun updateQuickControlUI() {
|
||||
val ringerText = when (audioManager.ringerMode) {
|
||||
|
||||
@ -292,7 +292,7 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
||||
}
|
||||
// Novels의 경우 VISIBLE 처리가 있었으나, GONE 처리 후 필요 시 메뉴를 보여주는 흐름으로 통합
|
||||
if(contentsType == "book") {
|
||||
binding.menuWeb.visibility = VISIBLE
|
||||
binding.lunaticBrowser.visibility = VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -312,7 +312,7 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
||||
|
||||
fun back() {
|
||||
if (contentsType == "youtube") {
|
||||
binding.menuWeb.session?.goBack()
|
||||
binding.lunaticBrowser.geckoWeb.session?.goBack()
|
||||
} else {
|
||||
actionNextEvent(false)
|
||||
}
|
||||
@ -431,7 +431,7 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
||||
super.onHiddenChanged(hidden)
|
||||
if (hidden) {
|
||||
// 💡 화면에서 사라질 때: 타이머 중지 및 애니메이션 중지
|
||||
binding.menuWeb?.onPause()
|
||||
binding.lunaticBrowser.geckoWeb?.onPause()
|
||||
// 일반 WebView라면: webView.onPause() 및 webView.pauseTimers()
|
||||
} else {
|
||||
// 💡 다시 나타날 때: 다시 시작
|
||||
@ -447,7 +447,47 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
||||
): View {
|
||||
Blog.LOGD(log = "onCreate ${this::class.java.name} >> savedInstanceState ${savedInstanceState}")
|
||||
binding = BooktokiBinding.inflate(inflater)
|
||||
binding.menuWeb.let {
|
||||
binding = BooktokiBinding.inflate(inflater)
|
||||
|
||||
// 💡 1. Toki용 레이아웃 설정
|
||||
binding.lunaticBrowser.setupForToki()
|
||||
|
||||
// 💡 2. GeckoWeb 접근 및 설정
|
||||
val geckoWeb = binding.lunaticBrowser.geckoWeb
|
||||
|
||||
|
||||
|
||||
// 💡 3. Toki 전용 버튼 클릭 리스너 연결
|
||||
binding.lunaticBrowser.binding.btnList.setOnClickListener {
|
||||
lastedUrl?.let {
|
||||
Uri.parse(it).path?.let {
|
||||
HistoryManager.getBookInfos(contentsType,processPageUrl(it), {
|
||||
it?.let {
|
||||
it.pages.sortBy { it.pathUrl }
|
||||
Blog.LOGE("bind ing.btnList it >>> $it")
|
||||
showList(it)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.lunaticBrowser.binding.btnSetting.setOnClickListener {
|
||||
activity?.startActivity(Intent(requireContext(), Settings::class.java)) // 필요 시 경로수정
|
||||
}
|
||||
|
||||
binding.lunaticBrowser.binding.btnHistory.setOnClickListener {
|
||||
getHistory()?.let { showHistory(it) }
|
||||
}
|
||||
|
||||
binding.lunaticBrowser.binding.btnHome.setOnClickListener {
|
||||
binding.pagedLayer.visibility = GONE
|
||||
binding.lunaticBrowser.visibility = VISIBLE
|
||||
binding.lunaticBrowser.showBrowserAreaOnly(true)
|
||||
goToHome()
|
||||
}
|
||||
|
||||
geckoWeb.let {
|
||||
it.sessionTag = webcontentsName
|
||||
it.visibility = View.VISIBLE
|
||||
it.lastDomain = getLastedDoamin()
|
||||
@ -462,33 +502,13 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
||||
if (lastedUrl?.contains("youtube.com") == true) {
|
||||
|
||||
} else {
|
||||
// binding.menuWeb.postDelayed({
|
||||
//
|
||||
// Blog.LOGE("onPageStop $success from WebExtension ${mPort!!.name}")
|
||||
// val message: JSONObject = JSONObject()
|
||||
// try {
|
||||
// message.put("type", "getList")
|
||||
// message.put("event", "sadsadds")
|
||||
//// message.put("tab", session.settings.screenId)
|
||||
// } catch (ex: JSONException) {
|
||||
// throw RuntimeException(ex)
|
||||
// }
|
||||
//
|
||||
// mPort!!.postMessage(message)
|
||||
//
|
||||
//
|
||||
// // 타입별 분기 처리 (기존 when 절 대체)
|
||||
// if (contentsType == "comics" || contentsType == "webtoon") {
|
||||
// lastInfo
|
||||
// }
|
||||
// binding.progress.visibility = GONE
|
||||
// }, 10L)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
it.onPageStartCallback = { url ->
|
||||
binding.progress.visibility = VISIBLE
|
||||
// binding.lunaticBrowser.binding.progress.visibility = VISIBLE
|
||||
}
|
||||
it.onSessionStateChangeCallback = {sessionState ->
|
||||
if (contentsType == "comics" || contentsType == "webtoon") {
|
||||
@ -508,15 +528,17 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
||||
}
|
||||
"NotRegistered" -> {
|
||||
binding.pagedLayer.visibility = GONE
|
||||
binding.lunaticBrowser.visibility = VISIBLE
|
||||
}
|
||||
"WebtoonContents"-> {
|
||||
binding.pagedLayer.visibility = GONE
|
||||
binding.lunaticBrowser.visibility = VISIBLE
|
||||
}
|
||||
"MSG" -> {
|
||||
lPortMessage.msg?.let { Toast.makeText(requireContext(),it, Toast.LENGTH_SHORT).show() }
|
||||
}
|
||||
"SHOWVIEWER" -> {
|
||||
binding.progress.visibility = GONE
|
||||
binding.lunaticBrowser.binding.internalProgressBar.visibility = GONE
|
||||
}
|
||||
"PRIVATES"->{
|
||||
lPortMessage.privates?.let {
|
||||
@ -528,7 +550,7 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
||||
|
||||
}
|
||||
}
|
||||
binding.progress.visibility = GONE
|
||||
binding.lunaticBrowser.binding.internalProgressBar.visibility = GONE
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
@ -539,7 +561,7 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
||||
} else {
|
||||
// url이 현재 로드된 주소입니다.
|
||||
Blog.LOGE("GeckoView", "현재 주소: $url")
|
||||
Blog.LOGE("GeckoView", "현재 session: ${binding.menuWeb.session}")
|
||||
Blog.LOGE("GeckoView", "현재 session: ${binding.lunaticBrowser.geckoWeb.session}")
|
||||
url?.let { url ->
|
||||
if (url.split("//").size > 1) {
|
||||
url.replace("//", "/").replace("https:/", "https://").let {
|
||||
@ -570,44 +592,16 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
(activity as? NeoRssActivity)?.let { activity ->
|
||||
it.decoViews.clear()
|
||||
it.decoViews.add(activity.findViewById<TextView>(R.id.current_address))
|
||||
it.decoViews.add(activity.findViewById<ImageButton>(R.id.back))
|
||||
it.decoViews.add(activity.findViewById<ImageButton>(R.id.reload))
|
||||
it.decoViews.add(activity.findViewById<ImageButton>(R.id.dl_video))
|
||||
}
|
||||
it.decoViews.add(binding.lunaticBrowser.findViewById(R.id.tv_address))
|
||||
it.decoViews.add(binding.lunaticBrowser.findViewById(R.id.btn_back))
|
||||
it.decoViews.add(binding.lunaticBrowser.findViewById(R.id.btn_reload))
|
||||
it.decoViews.add(binding.lunaticBrowser.findViewById(R.id.btn_dl_video))
|
||||
it.restoreSessionState()
|
||||
}
|
||||
|
||||
binding.btnList.setOnClickListener { v ->
|
||||
lastedUrl?.let {
|
||||
Uri.parse(it).path?.let {
|
||||
HistoryManager.getBookInfos(contentsType,processPageUrl(it), {
|
||||
it?.let {
|
||||
it.pages.sortBy { it.pathUrl }
|
||||
Blog.LOGE("bind ing.btnList it >>> $it")
|
||||
showList(it)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
binding.btnSetting.setOnClickListener { v ->
|
||||
activity?.startActivity(Intent(requireContext(), Settings::class.java))
|
||||
}
|
||||
binding.btnHistory.setOnClickListener { v ->
|
||||
getHistory()?.let {
|
||||
showHistory(it)
|
||||
}
|
||||
}
|
||||
|
||||
binding.btnHome.setOnClickListener { v ->
|
||||
binding.pagedLayer.visibility = GONE
|
||||
goToHome()
|
||||
}
|
||||
|
||||
val nullCursor = PointerIcon.getSystemIcon(requireContext(), PointerIcon.TYPE_NULL)
|
||||
binding.root.setPointerIcon(nullCursor)
|
||||
@ -967,9 +961,9 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
||||
contentsPageInfo.contents?.let { onLoadedContents(it) }
|
||||
contentsPageInfo.chapterTitle?.let { onFindTitle(it) }
|
||||
if (contentsPageInfo.pathUrl?.startsWith("http") == true) {
|
||||
binding.menuWeb.loadUrl(pathUrl)
|
||||
binding.lunaticBrowser.geckoWeb.loadUrl(pathUrl)
|
||||
} else {
|
||||
binding.menuWeb.loadUrl(getLastedDoamin() + contentsPageInfo.pathUrl!!)
|
||||
binding.lunaticBrowser.geckoWeb.loadUrl(getLastedDoamin() + contentsPageInfo.pathUrl!!)
|
||||
}
|
||||
HistoryManager.save(HistoryItem().putHistory(contentsPageInfo, contentsPageInfo.pathUrl!!))
|
||||
}
|
||||
@ -984,9 +978,10 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
||||
applyCurrentBook(it)
|
||||
} else if(lastInfo != null){
|
||||
binding.pagedLayer.visibility = GONE
|
||||
binding.menuWeb.loadUrl(getLastedDoamin() + lastInfo!!.pageUrl)
|
||||
binding.lunaticBrowser.visibility = VISIBLE
|
||||
binding.lunaticBrowser.geckoWeb.loadUrl(getLastedDoamin() + lastInfo!!.pageUrl)
|
||||
} else {
|
||||
binding.menuWeb.loadUrl(getLastedDoamin())
|
||||
binding.lunaticBrowser.geckoWeb.loadUrl(getLastedDoamin())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1088,7 +1083,7 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
||||
}
|
||||
}
|
||||
if (saveContinuation) {
|
||||
binding.menuWeb.postDelayed({
|
||||
binding.lunaticBrowser.geckoWeb.postDelayed({
|
||||
moveToNext(
|
||||
currentPage?.pathUrl ?: lastedUrl?.toUri()?.path
|
||||
)
|
||||
@ -1108,16 +1103,16 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
||||
contents = (contents.replace("\\n", System.getProperty("line.separator")))
|
||||
view.mPagedTextViewInterface = this@TokiFragment
|
||||
if (lastInfo != null && lastedUrl?.endsWith(lastInfo!!.pageUrl) == true) {
|
||||
binding.progress.visibility = VISIBLE
|
||||
binding.lunaticBrowser.binding.internalProgressBar.visibility = VISIBLE
|
||||
binding.pagedLayer.postDelayed({
|
||||
binding.progress.visibility = GONE
|
||||
binding.lunaticBrowser.binding.internalProgressBar.visibility = GONE
|
||||
}, 1000)
|
||||
}
|
||||
applyReaderConfig()
|
||||
activity?.runOnUiThread {
|
||||
view.text = contents
|
||||
view.visibility = VISIBLE
|
||||
binding.menuWeb.visibility = GONE
|
||||
binding.lunaticBrowser.visibility = GONE
|
||||
}
|
||||
// view.forceUpdateUI()
|
||||
lastedUrl?.let {
|
||||
@ -1139,7 +1134,7 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
||||
HistoryManager.getBooPageInfoContentsSave(contentsType,it, contents)
|
||||
}
|
||||
if (saveContinuation) {
|
||||
binding.menuWeb.postDelayed({moveToNext(currentPage?.pathUrl ?: lastedUrl?.toUri()?.path)}, 10000)
|
||||
binding.lunaticBrowser.geckoWeb.postDelayed({moveToNext(currentPage?.pathUrl ?: lastedUrl?.toUri()?.path)}, 10000)
|
||||
|
||||
}
|
||||
}
|
||||
@ -1155,8 +1150,8 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
||||
var currentChapter: Int = 0
|
||||
|
||||
fun onFindTitle(contents: String) {
|
||||
binding.textviewTitle.text = contents
|
||||
binding.textviewTitle.setOnClickListener {
|
||||
binding.lunaticBrowser.binding.tvTitle.text = contents
|
||||
binding.lunaticBrowser.binding.tvTitle.setOnClickListener {
|
||||
val builder = AlertDialog.Builder(requireContext())
|
||||
builder.setTitle("Title")
|
||||
val input = EditText(requireContext())
|
||||
@ -1196,7 +1191,7 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
||||
}
|
||||
|
||||
fun onStartLoad() {
|
||||
binding.progress.visibility = VISIBLE
|
||||
binding.lunaticBrowser.binding.internalProgressBar.visibility = VISIBLE
|
||||
}
|
||||
|
||||
fun completePageLoad(lastInfo: LastInfo) {
|
||||
@ -1305,6 +1300,6 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
||||
}
|
||||
|
||||
private fun goToHome() {
|
||||
binding.menuWeb.loadUrl(getLastedDoamin())
|
||||
binding.lunaticBrowser.geckoWeb.loadUrl(getLastedDoamin())
|
||||
}
|
||||
}
|
||||
@ -23,6 +23,8 @@ import android.content.SharedPreferences
|
||||
import android.content.res.Resources
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import bums.lunatic.launcher.BuildConfig
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.common.CommonActivity
|
||||
@ -39,6 +41,10 @@ import bums.lunatic.launcher.settings.childs.HomeSettings
|
||||
import bums.lunatic.launcher.settings.childs.Misc
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
import kr.gdrive.bums.lunatic.utils.BackupPayload
|
||||
import kr.gdrive.bums.lunatic.utils.GDriveBackupTask
|
||||
import kr.gdrive.bums.lunatic.utils.GDriveLoginManager
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
@ -46,7 +52,11 @@ import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import io.realm.kotlin.UpdatePolicy
|
||||
import io.realm.kotlin.ext.query
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
@ -55,6 +65,9 @@ internal class SettingsActivity : CommonActivity() {
|
||||
private lateinit var binding: SettingsActivityBinding
|
||||
private val sourceCode = "https://github.com/iamrasel/lunar-launcher"
|
||||
|
||||
// 전역 변수로 매니저 선언 (콜백 처리)
|
||||
private lateinit var loginManager: GDriveLoginManager
|
||||
|
||||
companion object {
|
||||
@JvmStatic var settingsPrefs: SharedPreferences? = null
|
||||
}
|
||||
@ -70,6 +83,49 @@ internal class SettingsActivity : CommonActivity() {
|
||||
|
||||
settingsPrefs = this.getSharedPreferences(PREFS_SETTINGS, 0)
|
||||
|
||||
loginManager = GDriveLoginManager(this) { isSuccess, account, errorMsg ->
|
||||
if (isSuccess && account != null) {
|
||||
updateUiForLoggedIn(account.email ?: "사용자")
|
||||
} else {
|
||||
updateUiForLoggedOut()
|
||||
Blog.LOGE("로그인 에러: $errorMsg")
|
||||
}
|
||||
}
|
||||
|
||||
val currentAccount = loginManager.getSignedInAccount()
|
||||
if (currentAccount != null) {
|
||||
updateUiForLoggedIn(currentAccount.email ?: "사용자")
|
||||
} else {
|
||||
updateUiForLoggedOut()
|
||||
}
|
||||
binding.btnGoogleLogin.setOnClickListener {
|
||||
if (loginManager.getSignedInAccount() == null) {
|
||||
loginManager.signIn()
|
||||
} else {
|
||||
loginManager.signOut { updateUiForLoggedOut() }
|
||||
}
|
||||
}
|
||||
|
||||
// 2) 수동 백업 버튼
|
||||
binding.btnManualBackup.setOnClickListener {
|
||||
val account = loginManager.getSignedInAccount()
|
||||
if (account != null) {
|
||||
performManualBackup(account)
|
||||
} else {
|
||||
Toast.makeText(this, "먼저 구글에 로그인해주세요.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
binding.switchAutoBackup.setOnCheckedChangeListener { _, isChecked ->
|
||||
if (isChecked && loginManager.getSignedInAccount() == null) {
|
||||
Toast.makeText(this, "자동 백업을 켜려면 구글 로그인이 필요합니다.", Toast.LENGTH_SHORT).show()
|
||||
binding.switchAutoBackup.isChecked = false
|
||||
return@setOnCheckedChangeListener
|
||||
}
|
||||
// TODO: isChecked 값을 SharedPreference에 저장
|
||||
}
|
||||
|
||||
/* launch child settings dialogs on button clicks */
|
||||
// binding.timeDate.setOnClickListener {
|
||||
// TopInfos().show(supportFragmentManager, BOTTOM_SHEET_TAG)
|
||||
@ -119,6 +175,46 @@ internal class SettingsActivity : CommonActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateUiForLoggedIn(email: String) {
|
||||
binding.btnGoogleLogin.text = "연결 해제 ($email)"
|
||||
binding.btnManualBackup.isEnabled = true
|
||||
binding.switchAutoBackup.isEnabled = true
|
||||
}
|
||||
|
||||
private fun updateUiForLoggedOut() {
|
||||
binding.btnGoogleLogin.text = "구글 드라이브 연결"
|
||||
binding.btnManualBackup.isEnabled = false
|
||||
binding.switchAutoBackup.isChecked = false
|
||||
binding.switchAutoBackup.isEnabled = false
|
||||
}
|
||||
|
||||
private fun performManualBackup(account: GoogleSignInAccount) {
|
||||
android.widget.Toast.makeText(this, "백업을 시작합니다...", Toast.LENGTH_SHORT).show()
|
||||
binding.btnManualBackup.isEnabled = false
|
||||
|
||||
lifecycleScope.launch {
|
||||
// DB 데이터 추출 (앱, 연락처 등)
|
||||
val appsJson = "..." // WorkersDb에서 가져오기
|
||||
val payload = BackupPayload(
|
||||
manifestJson = """{"version": ${WorkersDb.schemaVersion}}""",
|
||||
folderName = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date()),
|
||||
files = mapOf("apps_info.json" to appsJson)
|
||||
)
|
||||
|
||||
// 백업 실행
|
||||
val backupTask = GDriveBackupTask(this@SettingsActivity, account)
|
||||
val result = backupTask.executeBackup(payload)
|
||||
|
||||
result.onSuccess { msg ->
|
||||
Toast.makeText(this@SettingsActivity, msg, Toast.LENGTH_LONG).show()
|
||||
}.onFailure {
|
||||
Toast.makeText(this@SettingsActivity, "백업 실패", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
binding.btnManualBackup.isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
var cancelCount = Runnable{
|
||||
clickCount = 0
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ import android.view.animation.Interpolator
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import android.widget.ImageView
|
||||
import android.widget.ImageView.ScaleType
|
||||
import android.widget.TextView
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.utils.CommonUtils
|
||||
|
||||
@ -85,7 +86,7 @@ class FloatingActionMenu @JvmOverloads constructor(
|
||||
private var mLabelsStyle = 0
|
||||
private var mCustomTypefaceFromFont: Typeface? = null
|
||||
var isIconAnimated: Boolean = true
|
||||
private var mImageToggle: ImageView? = null
|
||||
private var mToggleView: View? = null
|
||||
private var mMenuButtonShowAnimation: Animation? = null
|
||||
private var mMenuButtonHideAnimation: Animation? = null
|
||||
private var mImageToggleShowAnimation: Animation? = null
|
||||
@ -198,7 +199,7 @@ class FloatingActionMenu @JvmOverloads constructor(
|
||||
attr.getInt(R.styleable.FloatingActionMenu_menu_animationDelayPerItem, 50)
|
||||
mIcon = attr.getDrawable(R.styleable.FloatingActionMenu_menu_icon)
|
||||
if (mIcon == null) {
|
||||
mIcon = getResources().getDrawable(R.drawable.fab_add)
|
||||
// mIcon = getResources().getDrawable(R.drawable.fab_add)
|
||||
}
|
||||
mLabelsSingleLine =
|
||||
attr.getBoolean(R.styleable.FloatingActionMenu_menu_labels_singleLine, false)
|
||||
@ -314,15 +315,21 @@ class FloatingActionMenu @JvmOverloads constructor(
|
||||
mMenuButton!!.mFabSize = mMenuFabSize
|
||||
mMenuButton!!.updateBackground()
|
||||
mMenuButton!!.labelText = mMenuLabelText
|
||||
|
||||
mImageToggle = ImageView(getContext())
|
||||
mImageToggle?.scaleType = ScaleType.FIT_CENTER
|
||||
mImageToggle?.adjustViewBounds = true
|
||||
mImageToggle!!.setImageDrawable(mIcon)
|
||||
|
||||
addView(mMenuButton, super.generateDefaultLayoutParams())
|
||||
addView(mImageToggle)
|
||||
|
||||
if (mIcon != null) {
|
||||
var imge = ImageView(getContext())
|
||||
imge?.scaleType = ScaleType.FIT_CENTER
|
||||
imge?.adjustViewBounds = true
|
||||
imge?.setImageDrawable(mIcon)
|
||||
mToggleView = imge
|
||||
} else {
|
||||
var v = TextView(getContext())
|
||||
v?.text = mMenuLabelText
|
||||
v?.setTextSize(TypedValue.COMPLEX_UNIT_PX, mLabelsTextSize)
|
||||
mToggleView = v
|
||||
}
|
||||
addView(mToggleView)
|
||||
createDefaultIconAnimation()
|
||||
}
|
||||
|
||||
@ -342,14 +349,14 @@ class FloatingActionMenu @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
val collapseAnimator = ObjectAnimator.ofFloat(
|
||||
mImageToggle,
|
||||
mToggleView,
|
||||
"rotation",
|
||||
collapseAngle,
|
||||
CLOSED_PLUS_ROTATION
|
||||
)
|
||||
|
||||
val expandAnimator = ObjectAnimator.ofFloat(
|
||||
mImageToggle,
|
||||
mToggleView,
|
||||
"rotation",
|
||||
CLOSED_PLUS_ROTATION,
|
||||
expandAngle
|
||||
@ -371,12 +378,12 @@ class FloatingActionMenu @JvmOverloads constructor(
|
||||
mMaxButtonWidth = 0
|
||||
var maxLabelWidth = 0
|
||||
|
||||
measureChildWithMargins(mImageToggle, widthMeasureSpec, 0, heightMeasureSpec, 0)
|
||||
measureChildWithMargins(mToggleView, widthMeasureSpec, 0, heightMeasureSpec, 0)
|
||||
|
||||
for (i in 0..<mButtonsCount) {
|
||||
val child = getChildAt(i)
|
||||
|
||||
if (child.getVisibility() == GONE || child === mImageToggle) continue
|
||||
if (child.getVisibility() == GONE || child === mToggleView) continue
|
||||
|
||||
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0)
|
||||
mMaxButtonWidth =
|
||||
@ -387,7 +394,7 @@ class FloatingActionMenu @JvmOverloads constructor(
|
||||
var usedWidth = 0
|
||||
val child = getChildAt(i)
|
||||
|
||||
if (child.getVisibility() == GONE || child === mImageToggle) continue
|
||||
if (child.getVisibility() == GONE || child === mToggleView) continue
|
||||
|
||||
usedWidth += child.getMeasuredWidth()
|
||||
height += child.getMeasuredHeight()
|
||||
@ -439,112 +446,98 @@ class FloatingActionMenu @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
||||
doLayout(changed, l, t, r, b)
|
||||
val buttonsHorizontalCenter = when (mLabelsPosition) {
|
||||
LABELS_POSITION_LEFT, LABELS_POSITION_CENTER -> r - l - mMaxButtonWidth / 2 - paddingRight
|
||||
else -> mMaxButtonWidth / 2 + paddingLeft
|
||||
}
|
||||
|
||||
val openUp = mOpenDirection == OPEN_UP
|
||||
val menuButtonTop = if (openUp) b - t - mMenuButton!!.measuredHeight - paddingBottom else paddingTop
|
||||
val menuButtonLeft = buttonsHorizontalCenter - mMenuButton!!.measuredWidth / 2
|
||||
|
||||
if (mMenuButton!!.left == 0 && mMenuButton!!.top == 0) {
|
||||
mMenuButton!!.layout(menuButtonLeft, menuButtonTop, menuButtonLeft + mMenuButton!!.measuredWidth, menuButtonTop + mMenuButton!!.measuredHeight)
|
||||
}
|
||||
|
||||
// 💡 화면의 너비(r - l)와 높이(b - t)를 함께 넘겨줍니다.
|
||||
layoutChildrenRelative(r - l, b - t)
|
||||
}
|
||||
|
||||
private fun doLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
||||
val buttonsHorizontalCenter = if (mLabelsPosition == LABELS_POSITION_LEFT)
|
||||
r - l - mMaxButtonWidth / 2 - getPaddingRight()
|
||||
else
|
||||
mMaxButtonWidth / 2 + getPaddingLeft()
|
||||
val openUp = mOpenDirection == OPEN_UP
|
||||
private fun moveElements(v: View?, SetX: Float, SetY: Float, oldX: Float, oldY: Float) {
|
||||
val parentView = (parent as ViewGroup).parent as View
|
||||
// 💡 드래그 중에도 부모의 너비와 높이를 계산해 넘겨줍니다.
|
||||
layoutChildrenRelative(parentView.width, parentView.height)
|
||||
}
|
||||
|
||||
val menuButtonTop = if (openUp)
|
||||
b - t - mMenuButton!!.getMeasuredHeight() - getPaddingBottom()
|
||||
else
|
||||
getPaddingTop()
|
||||
val menuButtonLeft = buttonsHorizontalCenter - mMenuButton!!.getMeasuredWidth() / 2
|
||||
// 파라미터에 parentHeight 추가
|
||||
private fun layoutChildrenRelative(parentWidth: Int, parentHeight: Int) {
|
||||
val menuL = mMenuButton!!.x.toInt()
|
||||
val menuT = mMenuButton!!.y.toInt()
|
||||
val menuW = mMenuButton!!.measuredWidth
|
||||
val menuH = mMenuButton!!.measuredHeight
|
||||
val buttonsCenterX = menuL + menuW / 2
|
||||
val buttonsCenterY = menuT + menuH / 2
|
||||
|
||||
mMenuButton!!.layout(
|
||||
menuButtonLeft, menuButtonTop, menuButtonLeft + mMenuButton!!.getMeasuredWidth(),
|
||||
menuButtonTop + mMenuButton!!.getMeasuredHeight()
|
||||
)
|
||||
// 💡 [핵심 복원] 메인 버튼이 화면 하단부에 있으면 위로(openUp = true), 상단부에 있으면 아래로 엽니다.
|
||||
val openUp = buttonsCenterY > (parentHeight / 2)
|
||||
|
||||
val imageLeft = buttonsHorizontalCenter - mImageToggle!!.getMeasuredWidth() / 2
|
||||
val imageTop =
|
||||
menuButtonTop + mMenuButton!!.getMeasuredHeight() / 2 - mImageToggle!!.getMeasuredHeight() / 2
|
||||
// 1. 토글 아이콘 배치
|
||||
val imageLeft = buttonsCenterX - mToggleView!!.measuredWidth / 2
|
||||
val imageTop = menuT + menuH / 2 - mToggleView!!.measuredHeight / 2
|
||||
mToggleView!!.layout(imageLeft, imageTop, imageLeft + mToggleView!!.measuredWidth, imageTop + mToggleView!!.measuredHeight)
|
||||
mToggleView!!.translationX = 0f
|
||||
mToggleView!!.translationY = 0f
|
||||
|
||||
mImageToggle!!.layout(
|
||||
imageLeft, imageTop, imageLeft + mImageToggle!!.getMeasuredWidth(),
|
||||
imageTop + mImageToggle!!.getMeasuredHeight()
|
||||
)
|
||||
val isMenuOnLeft = buttonsCenterX < parentWidth / 2
|
||||
|
||||
var nextY = if (openUp)
|
||||
menuButtonTop + mMenuButton!!.getMeasuredHeight() + mButtonSpacing
|
||||
else
|
||||
menuButtonTop
|
||||
// 💡 시작 높이: 위로 열릴 땐 메인 버튼의 Top, 아래로 열릴 땐 메인 버튼의 Bottom
|
||||
var nextY = if (openUp) menuT else menuT + menuH
|
||||
|
||||
for (i in mButtonsCount - 1 downTo 0) {
|
||||
val child = getChildAt(i)
|
||||
|
||||
if (child === mImageToggle) continue
|
||||
|
||||
if (child === mToggleView) continue
|
||||
val fab = child as FloatingActionButton
|
||||
|
||||
if (fab.getVisibility() == GONE) continue
|
||||
|
||||
val childX = buttonsHorizontalCenter - fab.getMeasuredWidth() / 2
|
||||
val childY = if (openUp) nextY - fab.getMeasuredHeight() - mButtonSpacing else nextY
|
||||
if (fab.visibility == GONE) continue
|
||||
|
||||
if (fab != mMenuButton) {
|
||||
fab.layout(
|
||||
childX, childY, childX + fab.getMeasuredWidth(),
|
||||
childY + fab.getMeasuredHeight()
|
||||
)
|
||||
val childX = buttonsCenterX - fab.measuredWidth / 2
|
||||
|
||||
if (!mIsMenuOpening) {
|
||||
fab.hide(false)
|
||||
// 💡 상/하 전개 방향에 맞게 Y 좌표 계산
|
||||
val childY = if (openUp) nextY - mButtonSpacing - fab.measuredHeight else nextY + mButtonSpacing
|
||||
|
||||
fab.layout(childX, childY, childX + fab.measuredWidth, childY + fab.measuredHeight)
|
||||
fab.translationX = 0f
|
||||
fab.translationY = 0f
|
||||
|
||||
if (!mIsMenuOpening) fab.hide(false)
|
||||
|
||||
// 2. 라벨 배치
|
||||
val label = fab.getTag(R.id.fab_label) as View?
|
||||
if (label != null) {
|
||||
val labelL: Int
|
||||
val labelT: Int
|
||||
|
||||
if (mLabelsPosition == LABELS_POSITION_CENTER) {
|
||||
labelL = childX + (fab.measuredWidth - label.measuredWidth) / 2
|
||||
labelT = childY + (fab.measuredHeight - label.measuredHeight) / 2
|
||||
} else {
|
||||
val actualSide = if (isMenuOnLeft) LABELS_POSITION_RIGHT else LABELS_POSITION_LEFT
|
||||
val offset = (if (mUsingMenuLabel) mMaxButtonWidth / 2 else fab.measuredWidth / 2) + mLabelsMargin
|
||||
|
||||
labelL = if (actualSide == LABELS_POSITION_LEFT) buttonsCenterX - offset - label.measuredWidth else buttonsCenterX + offset
|
||||
labelT = childY - mLabelsVerticalOffset + (fab.measuredHeight - label.measuredHeight) / 2
|
||||
}
|
||||
|
||||
label.layout(labelL, labelT, labelL + label.measuredWidth, labelT + label.measuredHeight)
|
||||
label.translationX = 0f
|
||||
label.translationY = 0f
|
||||
|
||||
if (!mIsMenuOpening) label.visibility = INVISIBLE
|
||||
}
|
||||
|
||||
// 3. 다음 뷰를 위한 Y 좌표 누적 갱신
|
||||
nextY = if (openUp) childY else childY + fab.measuredHeight
|
||||
}
|
||||
|
||||
val label = fab.getTag(R.id.fab_label) as View?
|
||||
if (label != null) {
|
||||
val labelLeft: Int
|
||||
val labelRight: Int
|
||||
val labelTop: Int
|
||||
|
||||
// 💡 [수정] Center 옵션일 경우 X,Y축 모두 버튼의 중앙으로 계산
|
||||
if (mLabelsPosition == LABELS_POSITION_CENTER) {
|
||||
labelLeft = childX + (fab.getMeasuredWidth() - label.getMeasuredWidth()) / 2
|
||||
labelRight = labelLeft + label.getMeasuredWidth()
|
||||
labelTop = childY + (fab.getMeasuredHeight() - label.getMeasuredHeight()) / 2
|
||||
} else {
|
||||
val labelsOffset =
|
||||
(if (mUsingMenuLabel) mMaxButtonWidth / 2 else fab.getMeasuredWidth() / 2) + mLabelsMargin
|
||||
val labelXNearButton = if (mLabelsPosition == LABELS_POSITION_LEFT)
|
||||
buttonsHorizontalCenter - labelsOffset
|
||||
else
|
||||
buttonsHorizontalCenter + labelsOffset
|
||||
|
||||
val labelXAwayFromButton = if (mLabelsPosition == LABELS_POSITION_LEFT)
|
||||
labelXNearButton - label.getMeasuredWidth()
|
||||
else
|
||||
labelXNearButton + label.getMeasuredWidth()
|
||||
|
||||
labelLeft = if (mLabelsPosition == LABELS_POSITION_LEFT)
|
||||
labelXAwayFromButton
|
||||
else
|
||||
labelXNearButton
|
||||
|
||||
labelRight = if (mLabelsPosition == LABELS_POSITION_LEFT)
|
||||
labelXNearButton
|
||||
else
|
||||
labelXAwayFromButton
|
||||
|
||||
labelTop = childY - mLabelsVerticalOffset + (fab.getMeasuredHeight() - label.getMeasuredHeight()) / 2
|
||||
}
|
||||
|
||||
label.layout(labelLeft, labelTop, labelRight, labelTop + label.getMeasuredHeight())
|
||||
|
||||
if (!mIsMenuOpening) {
|
||||
label.setVisibility(INVISIBLE)
|
||||
}
|
||||
}
|
||||
|
||||
nextY = if (openUp)
|
||||
childY - mButtonSpacing
|
||||
else
|
||||
childY + child.getMeasuredHeight() + mButtonSpacing
|
||||
}
|
||||
}
|
||||
|
||||
@ -555,14 +548,14 @@ class FloatingActionMenu @JvmOverloads constructor(
|
||||
override fun onFinishInflate() {
|
||||
super.onFinishInflate()
|
||||
bringChildToFront(mMenuButton)
|
||||
bringChildToFront(mImageToggle)
|
||||
bringChildToFront(mToggleView)
|
||||
mButtonsCount = getChildCount()
|
||||
createLabels()
|
||||
}
|
||||
|
||||
private fun createLabels() {
|
||||
for (i in 0..<mButtonsCount) {
|
||||
if (getChildAt(i) === mImageToggle) continue
|
||||
if (getChildAt(i) === mToggleView) continue
|
||||
|
||||
val fab = getChildAt(i) as FloatingActionButton
|
||||
|
||||
@ -582,47 +575,55 @@ class FloatingActionMenu @JvmOverloads constructor(
|
||||
var dY: Float = 0f
|
||||
var startX: Float = 0f
|
||||
var startY: Float = 0f
|
||||
var lastAction: Int = 0
|
||||
|
||||
override fun onTouch(v: View, event: MotionEvent): Boolean {
|
||||
val oldX = v.getX()
|
||||
val oldY = v.getY()
|
||||
when (event.getActionMasked()) {
|
||||
val oldX = v.x
|
||||
val oldY = v.y
|
||||
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
dX = v.getX() - event.getRawX()
|
||||
dY = v.getY() - event.getRawY()
|
||||
startX = event.getRawX()
|
||||
startY = event.getRawY()
|
||||
lastAction = MotionEvent.ACTION_DOWN
|
||||
dX = v.x - event.rawX
|
||||
dY = v.y - event.rawY
|
||||
startX = event.rawX
|
||||
startY = event.rawY
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
if (!isDragMenuDisabled and (checkViewLimits(
|
||||
v,
|
||||
(event.getRawX() + dX).toInt(),
|
||||
(event.getRawY() + dY).toInt()
|
||||
))
|
||||
) {
|
||||
v.setY(event.getRawY() + dY)
|
||||
v.setX(event.getRawX() + dX)
|
||||
if (!isDragMenuDisabled) {
|
||||
// 1. 드래그하려는 목표 좌표 계산
|
||||
var newX = event.rawX + dX
|
||||
var newY = event.rawY + dY
|
||||
|
||||
moveElements(
|
||||
v,
|
||||
event.getRawX() + dX,
|
||||
event.getRawY() + dY,
|
||||
oldX,
|
||||
oldY
|
||||
)
|
||||
// 2. 화면 전체 너비와 높이 가져오기 (현재 FloatingActionMenu가 match_parent이므로 자체 크기 사용)
|
||||
val menuWidth = this@FloatingActionMenu.width
|
||||
val menuHeight = this@FloatingActionMenu.height
|
||||
|
||||
// 3. 화면을 벗어나지 않도록 좌표 가두기 (패딩 포함)
|
||||
val minX = paddingLeft.toFloat()
|
||||
val maxX = (menuWidth - v.width - paddingRight).toFloat()
|
||||
|
||||
val minY = paddingTop.toFloat()
|
||||
val maxY = (menuHeight - v.height - paddingBottom).toFloat()
|
||||
|
||||
// coerceIn 함수를 사용하여 최소~최대 값 사이로 강제 보정
|
||||
newX = newX.coerceIn(minX, maxX)
|
||||
newY = newY.coerceIn(minY, maxY)
|
||||
|
||||
// 4. 보정된 좌표로 뷰 이동
|
||||
v.x = newX
|
||||
v.y = newY
|
||||
|
||||
// 5. 하위 메뉴들도 바뀐 메인 버튼 위치에 맞춰 다시 그리기
|
||||
moveElements(v, newX, newY, oldX, oldY)
|
||||
}
|
||||
lastAction = MotionEvent.ACTION_MOVE
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_UP -> if (abs((startX - event.getRawX()).toDouble()) < 10 && abs(
|
||||
(startY - event.getRawY()).toDouble()
|
||||
) < 10
|
||||
) {
|
||||
toggle(mIsAnimated)
|
||||
MotionEvent.ACTION_UP -> {
|
||||
// 드래그가 아니라 단순 클릭(오차 10픽셀 이내)이었다면 메뉴 토글
|
||||
if (abs(startX - event.rawX) < 10 && abs(startY - event.rawY) < 10) {
|
||||
toggle(mIsAnimated)
|
||||
}
|
||||
}
|
||||
|
||||
else -> return false
|
||||
}
|
||||
return true
|
||||
@ -650,113 +651,6 @@ class FloatingActionMenu @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun moveElements(v: View?, SetX: Float, SetY: Float, oldX: Float, oldY: Float) {
|
||||
val parent = (this.getParent() as ViewGroup).getParent() as View
|
||||
|
||||
val l = mMenuButton!!.getX().toInt()
|
||||
val t = mMenuButton!!.getY().toInt()
|
||||
val r = l + mMenuButton!!.getMeasuredWidth()
|
||||
val b = t + mMenuButton!!.getMeasuredHeight()
|
||||
|
||||
val mMenuH = mMenuButton!!.getMeasuredHeight()
|
||||
|
||||
val halfHeight = parent.getHeight() / 2
|
||||
|
||||
val buttonsHorizontalCenter = (r - l) / 2
|
||||
val openUp = halfHeight < (t)
|
||||
|
||||
var nextY = if (openUp) t - getPaddingTop() else t + mMenuH
|
||||
var fab: Any?
|
||||
|
||||
for (i in mButtonsCount - 1 downTo 0) {
|
||||
val child = getChildAt(i)
|
||||
if (child.getVisibility() == GONE) continue
|
||||
|
||||
val childX: Int
|
||||
val childY: Int
|
||||
|
||||
if (child === mImageToggle) {
|
||||
val imageLeft = l + buttonsHorizontalCenter - mImageToggle!!.getMeasuredWidth() / 2
|
||||
val imageTop =
|
||||
t + mMenuButton!!.getMeasuredHeight() / 2 - mImageToggle!!.getMeasuredHeight() / 2
|
||||
child.layout(
|
||||
imageLeft,
|
||||
imageTop,
|
||||
l + mImageToggle!!.getMeasuredWidth() * 2,
|
||||
t + mImageToggle!!.getMeasuredHeight() * 2
|
||||
)
|
||||
} else {
|
||||
childX = l + buttonsHorizontalCenter - child.getMeasuredWidth() / 2
|
||||
childY =
|
||||
if (openUp) nextY - child.getMeasuredHeight() - mButtonSpacing else nextY + mButtonSpacing
|
||||
|
||||
fab = child as FloatingActionButton
|
||||
if (fab === mMenuButton) continue
|
||||
|
||||
child.layout(
|
||||
childX, childY, childX + child.getMeasuredWidth(),
|
||||
childY + child.getMeasuredHeight()
|
||||
)
|
||||
|
||||
if (!mIsMenuOpening) {
|
||||
if (child is FloatingActionButton) fab.hide(false)
|
||||
}
|
||||
|
||||
var label: View? = null
|
||||
if (child !== mImageToggle) label = fab.getTag(R.id.fab_label) as View?
|
||||
|
||||
if (label != null) {
|
||||
val labelLeft: Int
|
||||
val labelRight: Int
|
||||
val labelTop: Int
|
||||
|
||||
// 💡 [수정] Center 옵션일 경우 드래그 중에도 중앙으로 정렬
|
||||
if (mLabelsPosition == LABELS_POSITION_CENTER) {
|
||||
labelLeft = childX + (fab.getMeasuredWidth() - label.getMeasuredWidth()) / 2
|
||||
labelRight = labelLeft + label.getMeasuredWidth()
|
||||
labelTop = childY + (fab.getMeasuredHeight() - label.getMeasuredHeight()) / 2
|
||||
} else {
|
||||
val labelsOffset =
|
||||
(if (mUsingMenuLabel) mMaxButtonWidth / 2 else fab.getMeasuredWidth() / 2) + mLabelsMargin
|
||||
val labelXNearButton = if (mLabelsPosition == LABELS_POSITION_LEFT)
|
||||
l + buttonsHorizontalCenter - labelsOffset
|
||||
else
|
||||
l + buttonsHorizontalCenter + labelsOffset
|
||||
|
||||
val labelXAwayFromButton = if (mLabelsPosition == LABELS_POSITION_LEFT)
|
||||
labelXNearButton - label.getMeasuredWidth()
|
||||
else
|
||||
labelXNearButton + label.getMeasuredWidth()
|
||||
|
||||
labelLeft = if (mLabelsPosition == LABELS_POSITION_LEFT)
|
||||
labelXAwayFromButton
|
||||
else
|
||||
labelXNearButton
|
||||
|
||||
labelRight = if (mLabelsPosition == LABELS_POSITION_LEFT)
|
||||
labelXNearButton
|
||||
else
|
||||
labelXAwayFromButton
|
||||
|
||||
labelTop = childY - mLabelsVerticalOffset + (fab.getMeasuredHeight() - label.getMeasuredHeight()) / 2
|
||||
}
|
||||
|
||||
label.layout(
|
||||
labelLeft,
|
||||
labelTop,
|
||||
labelRight,
|
||||
labelTop + label.getMeasuredHeight()
|
||||
)
|
||||
|
||||
if (!mIsMenuOpening) {
|
||||
label.setVisibility(INVISIBLE)
|
||||
}
|
||||
}
|
||||
nextY =
|
||||
if (openUp) childY - mButtonSpacing else childY + fab.getMeasuredHeight() + mButtonSpacing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addLabel(fab: FloatingActionButton) {
|
||||
val text = fab.labelText
|
||||
@ -821,7 +715,8 @@ class FloatingActionMenu @JvmOverloads constructor(
|
||||
label.setTypeface(mCustomTypefaceFromFont)
|
||||
}
|
||||
label.setText(text)
|
||||
label.setOnClickListener(fab.getOnClickListener())
|
||||
label.setOnClickListener {fab.performClick()}
|
||||
|
||||
|
||||
addView(label)
|
||||
fab.setTag(R.id.fab_label, label)
|
||||
@ -859,9 +754,9 @@ class FloatingActionMenu @JvmOverloads constructor(
|
||||
if (!this.isMenuButtonHidden) {
|
||||
mMenuButton!!.hide(animate)
|
||||
if (animate) {
|
||||
mImageToggle!!.startAnimation(mImageToggleHideAnimation)
|
||||
mToggleView!!.startAnimation(mImageToggleHideAnimation)
|
||||
}
|
||||
mImageToggle!!.setVisibility(INVISIBLE)
|
||||
mToggleView!!.setVisibility(INVISIBLE)
|
||||
mIsMenuButtonAnimationRunning = false
|
||||
}
|
||||
}
|
||||
@ -870,9 +765,9 @@ class FloatingActionMenu @JvmOverloads constructor(
|
||||
if (this.isMenuButtonHidden) {
|
||||
mMenuButton!!.show(animate)
|
||||
if (animate) {
|
||||
mImageToggle!!.startAnimation(mImageToggleShowAnimation)
|
||||
mToggleView!!.startAnimation(mImageToggleShowAnimation)
|
||||
}
|
||||
mImageToggle!!.setVisibility(VISIBLE)
|
||||
mToggleView!!.setVisibility(VISIBLE)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1055,8 +950,8 @@ class FloatingActionMenu @JvmOverloads constructor(
|
||||
mToggleListener = listener
|
||||
}
|
||||
|
||||
val menuIconView: ImageView
|
||||
get() = mImageToggle!!
|
||||
// val menuIconView: ImageView
|
||||
// get() = mToggleView!!
|
||||
|
||||
fun setMenuButtonShowAnimation(showAnimation: Animation) {
|
||||
mMenuButtonShowAnimation = showAnimation
|
||||
@ -1239,7 +1134,7 @@ class FloatingActionMenu @JvmOverloads constructor(
|
||||
val viewsToRemove: MutableList<FloatingActionButton> = ArrayList<FloatingActionButton>()
|
||||
for (i in 0..<getChildCount()) {
|
||||
val v = getChildAt(i)
|
||||
if (v !== mMenuButton && v !== mImageToggle && v is FloatingActionButton) {
|
||||
if (v !== mMenuButton && v !== mToggleView && v is FloatingActionButton) {
|
||||
viewsToRemove.add(v)
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,72 @@
|
||||
package bums.lunatic.launcher.view
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.isVisible
|
||||
import bums.lunatic.launcher.databinding.LayoutLunaticBrowserBinding
|
||||
import bums.lunatic.launcher.home.GeckoWeb
|
||||
|
||||
class LunaticBrowserLayout @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
val binding: LayoutLunaticBrowserBinding =
|
||||
LayoutLunaticBrowserBinding.inflate(LayoutInflater.from(context), this)
|
||||
|
||||
val geckoWeb: GeckoWeb get() = binding.internalGeckoWeb
|
||||
|
||||
init {
|
||||
geckoWeb.progress = binding.internalProgressBar
|
||||
|
||||
// 공통 하단바 로직
|
||||
binding.btnBack.setOnClickListener { geckoWeb.session?.goBack() }
|
||||
binding.btnReload.setOnClickListener { geckoWeb.session?.reload() }
|
||||
binding.btnShare.setOnClickListener {
|
||||
val addr = binding.tvAddress.text.toString()
|
||||
if (addr.length > 5) {
|
||||
val intent = Intent(Intent.ACTION_SEND).apply {
|
||||
(binding.tvAddress.tag as? String)?.let { putExtra(Intent.EXTRA_TITLE, it) }
|
||||
putExtra(Intent.EXTRA_TEXT, addr)
|
||||
type = "text/plain"
|
||||
}
|
||||
context.startActivity(Intent.createChooser(intent, "공유"))
|
||||
}
|
||||
}
|
||||
|
||||
// GeckoWeb 내부에서 주소 등 업데이트 시 연동되도록 등록
|
||||
geckoWeb.decoViews.addAll(listOf(
|
||||
binding.tvAddress, binding.btnDlVideo, binding.btnReload, binding.btnBack, binding.tvTitle
|
||||
))
|
||||
}
|
||||
|
||||
fun setupForRssHome() {
|
||||
binding.btnBookmark.isVisible = true
|
||||
binding.btnSearch.isVisible = true
|
||||
binding.btnHide.isVisible = true
|
||||
binding.btnVote.isVisible = true
|
||||
|
||||
binding.btnList.isVisible = false
|
||||
binding.btnHistory.isVisible = false
|
||||
binding.btnSetting.isVisible = false
|
||||
}
|
||||
|
||||
fun setupForToki() {
|
||||
binding.btnBookmark.isVisible = false
|
||||
binding.btnSearch.isVisible = false
|
||||
binding.btnHide.isVisible = false
|
||||
binding.btnVote.isVisible = false
|
||||
|
||||
binding.btnList.isVisible = true
|
||||
binding.btnHistory.isVisible = true
|
||||
binding.btnSetting.isVisible = true
|
||||
}
|
||||
|
||||
// 💡 소설 뷰어 모드로 전환 시, 웹뷰와 하단바를 숨기고 상단 타이틀바만 남기기 위한 헬퍼
|
||||
fun showBrowserAreaOnly(isVisible: Boolean) {
|
||||
binding.internalGeckoWeb.isVisible = isVisible
|
||||
binding.layoutBottomBar.isVisible = isVisible
|
||||
}
|
||||
}
|
||||
@ -67,13 +67,15 @@ object WorkersDb {
|
||||
|
||||
//RecentCall::class, RecentSms::class,
|
||||
val clazz : Set<KClass<out BaseRealmObject>> = setOf(
|
||||
RssData::class, NotificationItem::class, AppInfo::class,SimpleContact::class, CurrentPlayItem::class,
|
||||
TelegramBotUpdate::class, TelegramData::class, TelegramMessage::class, TelegramChat::class, BotCommandEentitie::class, TelegramFrom::class,
|
||||
WeatherForcast::class, Location::class, Current::class, Forecast::class, Condition::class, Forecastday::class, Day::class, Astro::class, Hour::class,
|
||||
RssData::class, AppInfo::class, SimpleContact::class,
|
||||
NotificationItem::class,
|
||||
CurrentPlayItem::class,
|
||||
// TelegramBotUpdate::class, TelegramData::class, TelegramMessage::class, TelegramChat::class, BotCommandEentitie::class, TelegramFrom::class,
|
||||
// WeatherForcast::class, Location::class, Current::class, Forecast::class, Condition::class, Forecastday::class, Day::class, Astro::class, Hour::class,
|
||||
LocationLog::class,
|
||||
LastInfo::class, HistoryItem::class, ReaderConfig::class, ContentsCollection::class, ContentsPageInfo::class,
|
||||
WidgetData::class,AppUsageLog::class,
|
||||
|
||||
AppUsageLog::class,
|
||||
WidgetData::class,
|
||||
)
|
||||
//,UserActionModel::class
|
||||
|
||||
|
||||
BIN
app/src/main/res/font/material_symbols.ttf
Normal file
BIN
app/src/main/res/font/material_symbols.ttf
Normal file
Binary file not shown.
@ -70,7 +70,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/eight"
|
||||
app:layout_constraintTop_toBottomOf="@id/totalTouch"
|
||||
app:layout_constraintTop_toBottomOf="@id/recommend"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
<TextView
|
||||
|
||||
@ -8,112 +8,21 @@
|
||||
android:layout_height="match_parent"
|
||||
>
|
||||
|
||||
<bums.lunatic.launcher.home.GeckoWeb
|
||||
android:id="@+id/menu_web"
|
||||
android:layout_margin="5dp"
|
||||
<bums.lunatic.launcher.view.LunaticBrowserLayout
|
||||
android:id="@+id/lunaticBrowser"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/textview_title" />
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterButton
|
||||
android:id="@+id/btn_home"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/main_top_height"
|
||||
android:background="@android:color/transparent"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/home"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="MissingConstraints" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textview_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/main_top_height"
|
||||
android:text="@string/app_name"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintRight_toLeftOf="@id/btn_list"
|
||||
app:layout_constraintLeft_toRightOf="@id/btn_setting"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterButton
|
||||
android:background="@android:color/transparent"
|
||||
android:id="@+id/btn_list"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/main_top_height"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/bookmark"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toRightOf="@id/textview_title"
|
||||
app:layout_constraintRight_toLeftOf="@+id/btn_history"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
/>
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterButton
|
||||
android:id="@+id/btn_history"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/main_top_height"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/saved"
|
||||
android:background="@android:color/transparent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
/>
|
||||
|
||||
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterButton
|
||||
android:id="@+id/btn_setting"
|
||||
android:layout_width="wrap_content"
|
||||
android:src="@drawable/settings"
|
||||
android:layout_height="@dimen/main_top_height"
|
||||
android:scaleType="fitCenter"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="@android:color/transparent"
|
||||
app:layout_constraintLeft_toRightOf="@+id/btn_home"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintHorizontal_chainStyle="spread"
|
||||
/>
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
|
||||
<bums.lunatic.launcher.home.tokiz.view.PagedTextLayout
|
||||
android:id="@+id/paged_layer"
|
||||
android:layout_margin="1dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
android:elevation="5dp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/textview_title" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="48dp"
|
||||
android:indeterminate="false"
|
||||
android:max="100"
|
||||
android:progressBackgroundTint="#FBE7C6"
|
||||
android:progressDrawable="@drawable/circle_progressbar"
|
||||
android:progressTint="#edbf41"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@ -3,13 +3,13 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
android:background="#E6121212">
|
||||
android:background="?android:attr/windowBackground">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="32dp">
|
||||
android:padding="12dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
@ -25,8 +25,8 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="#1AFFFFFF"
|
||||
android:padding="12dp"
|
||||
android:background="#1fff"
|
||||
android:padding="6dp"
|
||||
android:layout_marginBottom="24dp">
|
||||
|
||||
<TextView
|
||||
@ -42,7 +42,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:weightSum="3">
|
||||
android:weightSum="5">
|
||||
<TextView
|
||||
android:id="@+id/btnQuickWifi"
|
||||
android:layout_width="0dp"
|
||||
@ -76,6 +76,28 @@
|
||||
android:text="🔔 소리"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textStyle="bold" />
|
||||
<TextView
|
||||
android:id="@+id/btnQuickFlash"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:padding="8dp"
|
||||
android:text="🔦 조명"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textStyle="bold" />
|
||||
<TextView
|
||||
android:id="@+id/btnQuickMirror"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:padding="8dp"
|
||||
android:text="🪞 거울"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:background="@android:color/transparent"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/mainFragmentsContainer"
|
||||
|
||||
>
|
||||
<FrameLayout
|
||||
android:id="@+id/widget_container"
|
||||
|
||||
@ -6,109 +6,75 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageButton
|
||||
app:layout_constraintTop_toTopOf="@id/home"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="@id/home"
|
||||
android:id="@+id/search"
|
||||
android:alpha="0.5"
|
||||
android:scaleType="fitCenter"
|
||||
android:adjustViewBounds="true"
|
||||
android:visibility="visible"
|
||||
android:background="@null"
|
||||
android:src="@drawable/search"
|
||||
android:layout_width="30dp"
|
||||
android:tint="@color/finestSilver"
|
||||
android:foregroundTint="@color/finestSilver"
|
||||
tools:ignore="ContentDescription,UseAppTint"
|
||||
android:layout_height="30dp" />
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="45dp"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageButton
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
android:id="@+id/vote"
|
||||
android:scaleType="fitCenter"
|
||||
android:adjustViewBounds="true"
|
||||
android:visibility="visible"
|
||||
android:background="@null"
|
||||
android:src="@drawable/saved"
|
||||
android:layout_width="@dimen/main_top_height"
|
||||
tools:ignore="ContentDescription"
|
||||
android:layout_height="@dimen/main_top_height" />
|
||||
<TextView
|
||||
style="@style/MaterialIconButtonStyle"
|
||||
android:id="@+id/tab_all"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:text="dynamic_feed"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/white"
|
||||
android:background="?attr/selectableItemBackground"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/hide"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintRight_toLeftOf="@id/vote"
|
||||
android:src="@drawable/ic_delete"
|
||||
android:tintMode="multiply"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="@null"
|
||||
android:layout_width="@dimen/main_top_height"
|
||||
android:visibility="visible"
|
||||
android:adjustViewBounds="true"
|
||||
tools:ignore="ContentDescription"
|
||||
android:layout_height="@dimen/main_top_height"
|
||||
/>
|
||||
<TextView
|
||||
style="@style/MaterialIconButtonStyle"
|
||||
android:id="@+id/tab_voted"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:text="grade"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/white"
|
||||
android:background="?attr/selectableItemBackground"/>
|
||||
|
||||
<TextView
|
||||
style="@style/MaterialIconButtonStyle"
|
||||
android:id="@+id/tab_private"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:text="visibility_off"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/white"
|
||||
android:background="?attr/selectableItemBackground"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/home"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
android:src="@drawable/home"
|
||||
android:tintMode="multiply"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="@null"
|
||||
android:layout_width="@dimen/main_top_height"
|
||||
android:adjustViewBounds="true"
|
||||
android:layout_height="@dimen/main_top_height"
|
||||
app:tint="@color/white"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/bookmark"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toRightOf="@id/home"
|
||||
android:src="@drawable/bookmark"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="@null"
|
||||
android:layout_width="@dimen/main_top_height"
|
||||
android:adjustViewBounds="true"
|
||||
tools:ignore="ContentDescription"
|
||||
android:layout_height="@dimen/main_top_height"/>
|
||||
<TextView
|
||||
style="@style/MaterialIconButtonStyle"
|
||||
android:id="@+id/tab_serarch"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:text="search"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/finestSilver"
|
||||
android:background="?attr/selectableItemBackground"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
android:layout_margin="@dimen/default_layout_margin"
|
||||
android:id="@+id/infoList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/layout_tabs"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_margin="@dimen/default_layout_margin"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
android:overScrollMode="never"
|
||||
android:padding="@dimen/default_padding"
|
||||
android:scrollbars="none"
|
||||
android:background="#AB000000"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/progressBar"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
/>
|
||||
android:scrollbars="none"/>
|
||||
|
||||
|
||||
<bums.lunatic.launcher.home.GeckoWeb
|
||||
android:id="@+id/geckoWeb"
|
||||
android:visibility="gone"
|
||||
<bums.lunatic.launcher.view.LunaticBrowserLayout
|
||||
android:id="@+id/lunaticBrowser"
|
||||
android:layout_width="match_parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/progressBar"
|
||||
android:layout_height="0dp"
|
||||
/>
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone" />
|
||||
|
||||
<include layout="@layout/layout_rss_summary"
|
||||
android:id="@+id/layout_rss_summary"
|
||||
@ -117,21 +83,8 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/bookmark"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_height="0dp"/>
|
||||
|
||||
<ProgressBar
|
||||
app:layout_constraintTop_toBottomOf="@id/home"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="4dp"
|
||||
android:max="100"
|
||||
android:progress="0"
|
||||
android:visibility="visible"
|
||||
android:indeterminate="false"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
||||
96
app/src/main/res/layout/layout_lunatic_browser.xml
Normal file
96
app/src/main/res/layout/layout_lunatic_browser.xml
Normal file
@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_top_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:background="#CC000000"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView android:id="@+id/btn_home" android:text="home" style="@style/MaterialIconButtonStyle" />
|
||||
|
||||
<TextView android:id="@+id/btn_bookmark" android:text="bookmarks" style="@style/MaterialIconButtonStyle" />
|
||||
|
||||
<TextView android:id="@+id/btn_setting" android:text="settings" style="@style/MaterialIconButtonStyle" android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
tools:text="Page Title" />
|
||||
|
||||
<TextView android:id="@+id/btn_list"
|
||||
android:text="list"
|
||||
style="@style/MaterialIconButtonStyle" android:visibility="gone" />
|
||||
|
||||
<TextView android:id="@+id/btn_history"
|
||||
android:text="history"
|
||||
style="@style/MaterialIconButtonStyle" android:visibility="gone" />
|
||||
|
||||
<TextView android:id="@+id/btn_search"
|
||||
android:text="search"
|
||||
style="@style/MaterialIconButtonStyle" />
|
||||
|
||||
<TextView android:id="@+id/btn_hide"
|
||||
android:text="block"
|
||||
style="@style/MaterialIconButtonStyle" />
|
||||
|
||||
<TextView android:id="@+id/btn_vote"
|
||||
android:text="favorite"
|
||||
style="@style/MaterialIconButtonStyle" />
|
||||
</LinearLayout>
|
||||
|
||||
<bums.lunatic.launcher.home.GeckoWeb
|
||||
android:id="@+id/internal_gecko_web"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/layout_top_bar"
|
||||
app:layout_constraintBottom_toTopOf="@id/layout_bottom_bar" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/internal_progress_bar"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="3dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/layout_top_bar" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_bottom_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:background="#CC000000"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
|
||||
<TextView android:id="@+id/btn_back" android:text="arrow_back" style="@style/MaterialIconButtonStyle" />
|
||||
<TextView android:id="@+id/btn_reload" android:text="refresh" style="@style/MaterialIconButtonStyle" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_address"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:textColor="#BBFFFFFF"
|
||||
android:textSize="11sp"
|
||||
android:ellipsize="middle"
|
||||
android:singleLine="true" />
|
||||
|
||||
<TextView android:id="@+id/btn_dl_video" android:text="download" style="@style/MaterialIconButtonStyle" android:visibility="gone" />
|
||||
<TextView android:id="@+id/btn_share" android:text="share" style="@style/MaterialIconButtonStyle" />
|
||||
</LinearLayout>
|
||||
</merge>
|
||||
@ -36,11 +36,13 @@
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:id="@+id/current_chapter"
|
||||
android:layout_marginLeft="30dp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
<TextView
|
||||
android:layout_marginRight="30dp"
|
||||
android:id="@+id/current_page"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
||||
@ -3,198 +3,95 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:padding="0dp"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:background="@android:color/transparent"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/mainFragmentsContainer"
|
||||
>
|
||||
<LinearLayout
|
||||
android:id="@+id/fragment_layer"
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragment_container"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
android:visibility="visible"
|
||||
android:layout_marginBottom="15dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" >
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragment_container"
|
||||
android:visibility="visible"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp" />
|
||||
<LinearLayout
|
||||
android:id="@+id/controll_panel"
|
||||
android:visibility="visible"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp">
|
||||
<ImageButton
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/fragment_container"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
android:id="@+id/back"
|
||||
android:src="@drawable/back_vector"
|
||||
tools:ignore="ContentDescription"
|
||||
style="@style/CommonBottom" />
|
||||
<ImageButton
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/fragment_container"
|
||||
app:layout_constraintLeft_toRightOf="@id/back"
|
||||
android:id="@+id/reload"
|
||||
android:src="@drawable/ic_refresh"
|
||||
tools:ignore="ContentDescription"
|
||||
style="@style/CommonBottom"/>
|
||||
<TextView
|
||||
android:text="asdasdsadasd"
|
||||
android:layout_weight="1"
|
||||
android:id="@+id/current_address"
|
||||
app:layout_constraintTop_toTopOf="@id/back"
|
||||
app:layout_constraintRight_toLeftOf="@id/dl_video"
|
||||
app:layout_constraintLeft_toRightOf="@id/reload"
|
||||
android:textColor="@color/white"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/_12sp"
|
||||
android:ellipsize="middle"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/main_top_height"/>
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<ImageButton
|
||||
app:layout_constraintTop_toTopOf="@id/back"
|
||||
app:layout_constraintRight_toLeftOf="@id/share"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:id="@+id/dl_video"
|
||||
android:src="@drawable/dl_vid"
|
||||
tools:ignore="ContentDescription"
|
||||
style="@style/CommonBottom"/>
|
||||
<ImageButton
|
||||
app:layout_constraintTop_toTopOf="@id/back"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_marginRight="60dp"
|
||||
android:id="@+id/share"
|
||||
android:foregroundTint="@color/white"
|
||||
android:src="@drawable/ic_share"
|
||||
tools:ignore="ContentDescription"
|
||||
style="@style/CommonBottom"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
<bums.lunatic.launcher.view.FloatingActionMenu
|
||||
android:id="@+id/floating_action_menu"
|
||||
android:layout_margin="5dp"
|
||||
android:visibility="visible"
|
||||
app:menu_colorNormal="#80FF0000"
|
||||
app:menu_colorNormal="#2550"
|
||||
app:menu_fab_size="mini"
|
||||
app:menu_icon="@drawable/ic_add"
|
||||
app:menu_fab_label="✨"
|
||||
app:menu_icon="@null"
|
||||
app:menu_labels_textSize="28sp"
|
||||
app:menu_labels_position="center"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:menu_shadowColor="@color/finestSilver"
|
||||
app:menu_showShadow="true"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
>
|
||||
<bums.lunatic.launcher.view.FloatingActionButton
|
||||
app:fab_label="feeds"
|
||||
android:id="@+id/feeds"
|
||||
app:fab_showShadow="true"
|
||||
app:fab_size="mini"
|
||||
android:onClick="floatClick"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"/>
|
||||
app:fab_label="📰"
|
||||
style="@style/CommonFabStyle"
|
||||
android:id="@+id/feeds"/>
|
||||
|
||||
<bums.lunatic.launcher.view.FloatingActionButton
|
||||
app:fab_label="booktoki"
|
||||
android:id="@+id/books"
|
||||
app:fab_showShadow="true"
|
||||
app:fab_size="mini"
|
||||
android:onClick="floatClick"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"/>
|
||||
app:fab_label="📚"
|
||||
style="@style/CommonFabStyle"
|
||||
android:id="@+id/books"/>
|
||||
<bums.lunatic.launcher.view.FloatingActionButton
|
||||
app:fab_label="newtoki"
|
||||
android:id="@+id/webtoons"
|
||||
app:fab_showShadow="true"
|
||||
app:fab_size="mini"
|
||||
android:onClick="floatClick"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"/>
|
||||
app:fab_label="🎨"
|
||||
style="@style/CommonFabStyle"
|
||||
android:id="@+id/webtoons"/>
|
||||
<bums.lunatic.launcher.view.FloatingActionButton
|
||||
app:fab_label="manatoki"
|
||||
android:id="@+id/comics"
|
||||
app:fab_showShadow="true"
|
||||
app:fab_size="mini"
|
||||
android:onClick="floatClick"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"/>
|
||||
app:fab_label="🗯️"
|
||||
style="@style/CommonFabStyle"
|
||||
android:id="@+id/comics"/>
|
||||
<bums.lunatic.launcher.view.FloatingActionButton
|
||||
app:fab_label="youtube"
|
||||
android:id="@+id/youtube"
|
||||
app:fab_showShadow="true"
|
||||
app:fab_size="mini"
|
||||
android:onClick="floatClick"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"/>
|
||||
app:fab_label="📺"
|
||||
style="@style/CommonFabStyle"
|
||||
android:id="@+id/youtube"/>
|
||||
<bums.lunatic.launcher.view.FloatingActionButton
|
||||
app:fab_label="perplexity"
|
||||
android:id="@+id/perplexity"
|
||||
app:fab_showShadow="true"
|
||||
app:fab_size="mini"
|
||||
android:onClick="floatClick"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"/>
|
||||
app:fab_label="🤖"
|
||||
style="@style/CommonFabStyle"
|
||||
android:id="@+id/perplexity"/>
|
||||
<bums.lunatic.launcher.view.FloatingActionButton
|
||||
app:fab_label="짤방"
|
||||
android:id="@+id/zzalbang"
|
||||
app:fab_showShadow="true"
|
||||
app:fab_size="mini"
|
||||
android:onClick="floatClick"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"/>
|
||||
app:fab_label="😂"
|
||||
style="@style/CommonFabStyle"
|
||||
android:id="@+id/zzalbang"/>
|
||||
|
||||
<bums.lunatic.launcher.view.FloatingActionButton
|
||||
app:fab_label="X"
|
||||
android:id="@+id/btn_x"
|
||||
app:fab_showShadow="true"
|
||||
app:fab_size="mini"
|
||||
android:onClick="floatClick"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"/>
|
||||
app:fab_label="🐦"
|
||||
style="@style/CommonFabStyle"
|
||||
android:id="@+id/btn_x"/>
|
||||
|
||||
<bums.lunatic.launcher.view.FloatingActionButton
|
||||
app:fab_label="I"
|
||||
android:id="@+id/btn_i"
|
||||
app:fab_showShadow="true"
|
||||
app:fab_size="mini"
|
||||
android:onClick="floatClick"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"/>
|
||||
app:fab_label="🔞"
|
||||
style="@style/CommonFabStyle"
|
||||
android:id="@+id/btn_i"/>
|
||||
|
||||
<bums.lunatic.launcher.view.FloatingActionButton
|
||||
app:fab_label="torrent"
|
||||
app:fab_label="🧲"
|
||||
style="@style/CommonFabStyle"
|
||||
android:id="@+id/btn_torrent"
|
||||
app:fab_showShadow="true"
|
||||
app:fab_size="mini"
|
||||
android:onClick="floatClick"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"/>
|
||||
/>
|
||||
<bums.lunatic.launcher.view.FloatingActionButton
|
||||
app:fab_label="system"
|
||||
app:fab_label="📊"
|
||||
style="@style/CommonFabStyle"
|
||||
android:id="@+id/btn_info"
|
||||
app:fab_showShadow="true"
|
||||
app:fab_size="mini"
|
||||
android:onClick="floatClick"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"/>
|
||||
/>
|
||||
<bums.lunatic.launcher.view.FloatingActionButton
|
||||
app:fab_label="close"
|
||||
android:id="@+id/close"
|
||||
app:fab_showShadow="true"
|
||||
app:fab_size="mini"
|
||||
android:onClick="floatClick"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"/>
|
||||
style="@style/CommonFabStyle"
|
||||
app:fab_label="❌"
|
||||
android:id="@+id/close"/>
|
||||
|
||||
</bums.lunatic.launcher.view.FloatingActionMenu>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@ -29,107 +29,127 @@
|
||||
android:background="@drawable/rounded_bg_top"
|
||||
android:backgroundTint="@color/black"
|
||||
android:paddingHorizontal="@dimen/thirtySix"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/version"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/appBar">
|
||||
|
||||
<com.google.android.material.button.MaterialButtonToggleGroup
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:singleSelection="true"
|
||||
android:layout_gravity="center">
|
||||
android:paddingTop="@dimen/twelve"
|
||||
android:paddingBottom="@dimen/thirtySix">
|
||||
|
||||
<!-- <com.google.android.material.button.MaterialButton-->
|
||||
<!-- android:id="@+id/timeDate"-->
|
||||
<!-- style="@style/Widget.Material3.Button.ElevatedButton"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:text="Display Info"-->
|
||||
<!-- android:textAllCaps="true"-->
|
||||
<!-- android:textStyle="bold" />-->
|
||||
<com.google.android.material.button.MaterialButtonToggleGroup
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:singleSelection="true">
|
||||
|
||||
<!-- <com.google.android.material.button.MaterialButton-->
|
||||
<!-- android:id="@+id/weather"-->
|
||||
<!-- style="@style/Widget.Material3.Button.ElevatedButton"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:text="@string/weather"-->
|
||||
<!-- android:textAllCaps="true"-->
|
||||
<!-- android:textStyle="bold" />-->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/todo"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/home"
|
||||
android:textAllCaps="true"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/apps"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_drawer"
|
||||
android:textAllCaps="true"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/misc"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/misc"
|
||||
android:textAllCaps="true"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/about"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/about"
|
||||
android:textAllCaps="true"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/support"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/support"
|
||||
android:textAllCaps="true"
|
||||
android:textStyle="bold" />
|
||||
</com.google.android.material.button.MaterialButtonToggleGroup>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="#33FFFFFF" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:text="데이터 백업 및 복원"
|
||||
android:textColor="@color/white"
|
||||
android:textStyle="bold"
|
||||
style="@style/TextAppearance.Material3.TitleMedium" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/todo"
|
||||
android:id="@+id/btnGoogleLogin"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/home"
|
||||
android:textAllCaps="true"
|
||||
android:text="구글 드라이브 연결"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/apps"
|
||||
android:id="@+id/btnManualBackup"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_drawer"
|
||||
android:textAllCaps="true"
|
||||
android:enabled="false"
|
||||
android:text="지금 데이터 백업하기"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- <com.google.android.material.button.MaterialButton-->
|
||||
<!-- android:id="@+id/appearances"-->
|
||||
<!-- style="@style/Widget.Material3.Button.ElevatedButton"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:text="@string/appearances"-->
|
||||
<!-- android:textAllCaps="true"-->
|
||||
<!-- android:textStyle="bold" />-->
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/misc"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/switchAutoBackup"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/misc"
|
||||
android:textAllCaps="true"
|
||||
android:textStyle="bold" />
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:enabled="false"
|
||||
android:text="자동 백업 허용 (주 1회)"
|
||||
android:textColor="@color/white"
|
||||
android:textStyle="bold"
|
||||
app:switchPadding="16dp" />
|
||||
|
||||
<!-- <com.google.android.material.button.MaterialButton-->
|
||||
<!-- android:id="@+id/advance"-->
|
||||
<!-- style="@style/Widget.Material3.Button.ElevatedButton"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:text="@string/advance"-->
|
||||
<!-- android:textAllCaps="true"-->
|
||||
<!-- android:textStyle="bold" />-->
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/about"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/about"
|
||||
android:textAllCaps="true"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/support"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/support"
|
||||
android:textAllCaps="true"
|
||||
android:textStyle="bold" />
|
||||
</com.google.android.material.button.MaterialButtonToggleGroup>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/version"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/twelve"
|
||||
android:layout_marginBottom="@dimen/twelve"
|
||||
android:textColor="#88FFFFFF"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@ -5,6 +5,11 @@
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_launcher</item>
|
||||
<item name="windowSplashScreenAnimationDuration">1000</item>
|
||||
<item name="postSplashScreenTheme">@style/Theme.LunarLauncher</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="windowActionBar">false</item>
|
||||
|
||||
<item name="android:windowShowWallpaper">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
<style name="Slider" parent="Widget.Material3.Slider">
|
||||
@ -139,4 +144,37 @@
|
||||
<item name="android:layout_height">@dimen/main_top_height</item>
|
||||
</style>
|
||||
|
||||
<style name="EmojiButtonStyle">
|
||||
<item name="android:layout_width">48dp</item>
|
||||
<item name="android:layout_height">match_parent</item>
|
||||
<item name="android:gravity">center</item>
|
||||
|
||||
<item name="android:includeFontPadding">false</item>
|
||||
|
||||
<item name="android:background">?attr/selectableItemBackgroundBorderless</item>
|
||||
<item name="android:clickable">true</item>
|
||||
<item name="android:focusable">true</item>
|
||||
</style>
|
||||
|
||||
<style name="MaterialIconButtonStyle" parent="Widget.AppCompat.Button.Borderless">
|
||||
<item name="android:layout_width">40dp</item>
|
||||
<item name="android:layout_height">match_parent</item>
|
||||
<item name="android:gravity">center</item>
|
||||
<item name="android:textColor">@color/white</item>
|
||||
<item name="android:fontFamily">@font/material_symbols</item>
|
||||
<item name="android:textSize">40sp</item>
|
||||
<item name="android:padding">1dp</item>
|
||||
<item name="android:background">?attr/selectableItemBackgroundBorderless</item>
|
||||
</style>
|
||||
|
||||
<style name="CommonFabStyle">
|
||||
<item name="fab_showShadow">true</item>
|
||||
<item name="fab_size">mini</item>
|
||||
<item name="menu_labels_textSize">28sp</item>
|
||||
<item name="fab_colorNormal">#2550</item>
|
||||
<item name="fab_shadowColor">@color/finestSilver</item>
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">28dp</item>
|
||||
<item name="android:onClick">floatClick</item>
|
||||
</style>
|
||||
</resources>
|
||||
1
gdrive/.gitignore
vendored
Normal file
1
gdrive/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
||||
53
gdrive/build.gradle.kts
Normal file
53
gdrive/build.gradle.kts
Normal file
@ -0,0 +1,53 @@
|
||||
plugins {
|
||||
id ("com.android.library")
|
||||
id ("kotlin-android")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "kr.bums.lunatic.utils.gdrive"
|
||||
compileSdk = 36
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
|
||||
implementation("androidx.core:core-ktx:1.12.0")
|
||||
implementation("androidx.activity:activity-ktx:1.8.2") // ActivityResultLauncher용
|
||||
|
||||
// 구글 로그인
|
||||
implementation("com.google.android.gms:play-services-auth:20.7.0")
|
||||
|
||||
// 구글 드라이브 API
|
||||
implementation("com.google.api-client:google-api-client-android:1.33.0") {
|
||||
exclude(group = "org.apache.httpcomponents")
|
||||
}
|
||||
implementation("com.google.apis:google-api-services-drive:v3-rev20220815-2.0.0") {
|
||||
exclude(group = "org.apache.httpcomponents")
|
||||
}
|
||||
// implementation("androidx.core:core-ktx:1.17.0")
|
||||
implementation("androidx.appcompat:appcompat:1.7.1")
|
||||
implementation("com.google.android.material:material:1.13.0")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.3.0")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")
|
||||
}
|
||||
21
gdrive/proguard-rules.pro
vendored
Normal file
21
gdrive/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@ -0,0 +1,24 @@
|
||||
package bums.lunatic.utils.gdrive
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("bums.lunatic.utils.gdrive", appContext.packageName)
|
||||
}
|
||||
}
|
||||
1
gdrive/src/main/AndroidManifest.xml
Normal file
1
gdrive/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1 @@
|
||||
<manifest package="kr.bums.lunatic.utils.gdrive"/>
|
||||
@ -0,0 +1,76 @@
|
||||
package kr.gdrive.bums.lunatic.utils
|
||||
|
||||
import android.content.Context
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
|
||||
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential
|
||||
import com.google.api.client.http.ByteArrayContent
|
||||
import com.google.api.client.http.javanet.NetHttpTransport
|
||||
import com.google.api.client.json.gson.GsonFactory
|
||||
import com.google.api.services.drive.Drive
|
||||
import com.google.api.services.drive.DriveScopes
|
||||
import com.google.api.services.drive.model.File
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class GDriveBackupTask(
|
||||
private val context: Context,
|
||||
private val account: GoogleSignInAccount
|
||||
) {
|
||||
// 💡 백업 실행 (IO 스레드에서 돌아야 함)
|
||||
suspend fun executeBackup(payload: BackupPayload): Result<String> = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val credential = GoogleAccountCredential.usingOAuth2(context, listOf(DriveScopes.DRIVE_APPDATA))
|
||||
credential.selectedAccount = account.account
|
||||
|
||||
val driveService = Drive.Builder(
|
||||
NetHttpTransport(), GsonFactory.getDefaultInstance(), credential
|
||||
).setApplicationName("LunaticLauncherBackup").build()
|
||||
|
||||
// 1. 매니페스트 업데이트
|
||||
uploadFile(driveService, "appDataFolder", "manifest.json", payload.manifestJson)
|
||||
|
||||
// 2. 폴더 가져오기 or 생성
|
||||
val folderId = getOrCreateFolder(driveService, payload.folderName)
|
||||
|
||||
// 3. 파일들 업로드
|
||||
payload.files.forEach { (fileName, jsonContent) ->
|
||||
uploadFile(driveService, folderId, fileName, jsonContent)
|
||||
}
|
||||
|
||||
Result.success("백업이 완료되었습니다. (${payload.folderName})")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getOrCreateFolder(driveService: Drive, folderName: String): String {
|
||||
val query = "mimeType='application/vnd.google-apps.folder' and name='$folderName' and 'appDataFolder' in parents and trashed=false"
|
||||
val fileList = driveService.files().list().setSpaces("appDataFolder").setQ(query).execute()
|
||||
|
||||
if (fileList.files.isNotEmpty()) return fileList.files[0].id
|
||||
|
||||
val folderMetadata = File().apply {
|
||||
name = folderName
|
||||
mimeType = "application/vnd.google-apps.folder"
|
||||
parents = listOf("appDataFolder")
|
||||
}
|
||||
return driveService.files().create(folderMetadata).setFields("id").execute().id
|
||||
}
|
||||
|
||||
private fun uploadFile(driveService: Drive, parentId: String, fileName: String, contentStr: String) {
|
||||
val content = ByteArrayContent.fromString("application/json", contentStr)
|
||||
val query = "name='$fileName' and '$parentId' in parents and trashed=false"
|
||||
val fileList = driveService.files().list().setSpaces("appDataFolder").setQ(query).execute()
|
||||
|
||||
if (fileList.files.isNotEmpty()) {
|
||||
driveService.files().update(fileList.files[0].id, null, content).execute()
|
||||
} else {
|
||||
val fileMetadata = File().apply {
|
||||
name = fileName
|
||||
parents = listOf(parentId)
|
||||
}
|
||||
driveService.files().create(fileMetadata, content).execute()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
package kr.gdrive.bums.lunatic.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
|
||||
import com.google.android.gms.common.api.Scope
|
||||
import com.google.api.services.drive.DriveScopes
|
||||
|
||||
class GDriveLoginManager(
|
||||
private val activity: ComponentActivity,
|
||||
private val onLoginResult: (Boolean, GoogleSignInAccount?, String?) -> Unit
|
||||
) {
|
||||
// 필수 권한: 숨겨진 App Data 폴더 접근 권한
|
||||
private val driveScope = Scope(DriveScopes.DRIVE_APPDATA)
|
||||
|
||||
private val signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
|
||||
.requestEmail()
|
||||
.requestScopes(driveScope)
|
||||
.build()
|
||||
|
||||
private val signInClient = GoogleSignIn.getClient(activity, signInOptions)
|
||||
|
||||
// 액티비티 생성 시점에 등록되어야 하는 런처
|
||||
private val signInLauncher: ActivityResultLauncher<Intent> =
|
||||
activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
|
||||
try {
|
||||
val account = task.getResult(Exception::class.java)
|
||||
if (account != null && GoogleSignIn.hasPermissions(account, driveScope)) {
|
||||
onLoginResult(true, account, null)
|
||||
} else {
|
||||
onLoginResult(false, null, "드라이브 접근 권한이 부족합니다.")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
onLoginResult(false, null, "로그인 실패: ${e.message}")
|
||||
}
|
||||
} else {
|
||||
onLoginResult(false, null, "로그인이 취소되었습니다.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 이미 로그인된 유효한 계정이 있는지 확인 (백그라운드에서 유용함)
|
||||
*/
|
||||
fun getSignedInAccount(context: Context = activity): GoogleSignInAccount? {
|
||||
val account = GoogleSignIn.getLastSignedInAccount(context)
|
||||
return if (account != null && GoogleSignIn.hasPermissions(account, driveScope)) {
|
||||
account
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그인 화면 띄우기
|
||||
*/
|
||||
fun signIn() {
|
||||
val account = getSignedInAccount()
|
||||
if (account != null) {
|
||||
onLoginResult(true, account, null)
|
||||
} else {
|
||||
signInLauncher.launch(signInClient.signInIntent)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그아웃 (연결 해제)
|
||||
*/
|
||||
fun signOut(onComplete: () -> Unit) {
|
||||
signInClient.signOut().addOnCompleteListener { onComplete() }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package kr.gdrive.bums.lunatic.utils
|
||||
|
||||
sealed class GDriveState {
|
||||
object Idle : GDriveState()
|
||||
object StartingLogin : GDriveState()
|
||||
object Uploading : GDriveState()
|
||||
class Success(val message: String) : GDriveState()
|
||||
class Error(val message: String, val exception: Exception? = null) : GDriveState()
|
||||
}
|
||||
|
||||
// 메인 앱에서 백업할 데이터를 담아 보낼 데이터 클래스
|
||||
data class BackupPayload(
|
||||
val manifestJson: String, // 최상단에 저장될 버전 정보
|
||||
val folderName: String, // 예: "2026-03-05" (날짜 폴더명)
|
||||
val files: Map<String, String> // 파일명(키)과 JSON 텍스트(값)의 쌍
|
||||
)
|
||||
@ -0,0 +1,17 @@
|
||||
package bums.lunatic.utils.gdrive
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
@ -3,8 +3,8 @@ pluginManagement {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
mavenCentral()
|
||||
// jcenter()
|
||||
maven (url = "https://maven.mozilla.org/maven2/")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,8 +17,10 @@ dependencyResolutionManagement {
|
||||
mavenCentral()
|
||||
maven (url = "https://maven.mozilla.org/maven2/")
|
||||
maven(url = "https://jitpack.io")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "LunarLauncher"
|
||||
include ("app","library","utils")
|
||||
include(":gdrive")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user