...
This commit is contained in:
parent
83546e5e10
commit
70869d7e1e
@ -9,7 +9,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "bums.lunatic.launcher"
|
namespace = "bums.lunatic.launcher"
|
||||||
compileSdk = 35
|
compileSdk = 35
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
@ -115,6 +115,15 @@ android {
|
|||||||
doNotStrip("**/libaria2c.zip.so")
|
doNotStrip("**/libaria2c.zip.so")
|
||||||
doNotStrip("**/libffmpeg.zip.so")
|
doNotStrip("**/libffmpeg.zip.so")
|
||||||
doNotStrip("**/libpython.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",
|
"dir" to "libs",
|
||||||
"include" to listOf("*.aar", "*.jar"),
|
"include" to listOf("*.aar", "*.jar"),
|
||||||
)))
|
)))
|
||||||
|
implementation(project(":gdrive"))
|
||||||
val kotlinVersion: String? by extra
|
val kotlinVersion: String? by extra
|
||||||
val realmVersion = "2.0.0"
|
val realmVersion = "2.0.0"
|
||||||
implementation ("androidx.appcompat:appcompat:1.7.1")
|
implementation ("androidx.appcompat:appcompat:1.7.1")
|
||||||
@ -196,6 +206,28 @@ dependencies {
|
|||||||
// implementation ("me.everything:providers-core:1.0.1")
|
// implementation ("me.everything:providers-core:1.0.1")
|
||||||
// implementation ("androidx.window:window:1.0.0")
|
// implementation ("androidx.window:window:1.0.0")
|
||||||
// implementation("io.github.vaneproject:hanguleditor: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 {
|
constraints {
|
||||||
// ⚠️ 이 버전을 프로젝트 루트의 build.gradle.kts에 정의된 kotlinVersion 값과 정확히 일치시키세요.
|
// ⚠️ 이 버전을 프로젝트 루트의 build.gradle.kts에 정의된 kotlinVersion 값과 정확히 일치시키세요.
|
||||||
val targetKotlinVersion = "2.0.20"
|
val targetKotlinVersion = "2.0.20"
|
||||||
|
|||||||
@ -58,11 +58,9 @@
|
|||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.PACKAGE_USAGE_STATS"
|
android:name="android.permission.PACKAGE_USAGE_STATS"
|
||||||
tools:ignore="ProtectedPermissions" />
|
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.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
|
<application
|
||||||
android:name=".LunaticLauncher"
|
android:name=".LunaticLauncher"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@drawable/ic_launcher"
|
||||||
|
|||||||
@ -33,7 +33,10 @@ abstract class CommonActivity : AppCompatActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
try {
|
||||||
|
supportActionBar?.hide()
|
||||||
|
actionBar?.hide()
|
||||||
|
} catch (e: Exception) {}
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
val insetsController = WindowInsetsControllerCompat(window, window.decorView)
|
val insetsController = WindowInsetsControllerCompat(window, window.decorView)
|
||||||
var forWhite = false
|
var forWhite = false
|
||||||
|
|||||||
@ -184,7 +184,7 @@ open class GeckoWeb @JvmOverloads constructor(
|
|||||||
|
|
||||||
override fun setVisibility(visibility: Int) {
|
override fun setVisibility(visibility: Int) {
|
||||||
super.setVisibility(visibility)
|
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) {
|
open fun loadUrl(url: String, param: String? = null) {
|
||||||
@ -212,7 +212,7 @@ open class GeckoWeb @JvmOverloads constructor(
|
|||||||
fun checkIfDownloadable(url: String) {
|
fun checkIfDownloadable(url: String) {
|
||||||
// UI 초기화
|
// UI 초기화
|
||||||
post {
|
post {
|
||||||
decoViews.firstOrNull { it.id == R.id.dl_video }?.let {
|
decoViews.firstOrNull { it.id == R.id.btn_dl_video }?.let {
|
||||||
it.setOnClickListener {}
|
it.setOnClickListener {}
|
||||||
it.visibility = GONE
|
it.visibility = GONE
|
||||||
}
|
}
|
||||||
@ -242,7 +242,7 @@ open class GeckoWeb @JvmOverloads constructor(
|
|||||||
val videoInfo = YoutubeDL.getInstance().getInfo(request)
|
val videoInfo = YoutubeDL.getInstance().getInfo(request)
|
||||||
if (videoInfo != null && !videoInfo.title.isNullOrEmpty()) {
|
if (videoInfo != null && !videoInfo.title.isNullOrEmpty()) {
|
||||||
post {
|
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.setOnClickListener { videoDownload(url) }
|
||||||
view.visibility = VISIBLE
|
view.visibility = VISIBLE
|
||||||
}
|
}
|
||||||
@ -320,10 +320,11 @@ open class GeckoWeb @JvmOverloads constructor(
|
|||||||
|
|
||||||
// 외부 뷰 업데이트
|
// 외부 뷰 업데이트
|
||||||
decoViews.forEach { view ->
|
decoViews.forEach { view ->
|
||||||
|
if (view == null) return@forEach // 뷰가 null이면 건너뛰어 크래시 방지
|
||||||
when(view.id) {
|
when(view.id) {
|
||||||
R.id.back -> view.setOnClickListener { session.goBack() }
|
R.id.btn_back -> view.setOnClickListener { session.goBack() }
|
||||||
R.id.current_address -> (view as? TextView)?.apply { tag = currentTitle; text = url }
|
R.id.tv_address -> (view as? TextView)?.apply { tag = currentTitle; text = url }
|
||||||
R.id.reload -> view.setOnClickListener { session.reload() }
|
R.id.btn_reload -> view.setOnClickListener { session.reload() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// scrollState = 0
|
// scrollState = 0
|
||||||
|
|||||||
@ -164,7 +164,7 @@ open class NeoRssActivity : CommonActivity() {
|
|||||||
}
|
}
|
||||||
else {
|
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 is KeyEventHandler) {
|
||||||
if (currentFragment.onKeyEvent(ev)) {
|
if (currentFragment.onKeyEvent(ev)) {
|
||||||
return true
|
return true
|
||||||
@ -338,28 +338,16 @@ open class NeoRssActivity : CommonActivity() {
|
|||||||
}
|
}
|
||||||
return@setOnTouchListener false
|
return@setOnTouchListener false
|
||||||
}
|
}
|
||||||
binding.floatingActionMenu.setOnMenuButtonClickListener { v->
|
// binding.floatingActionMenu.setOnMenuButtonClickListener { v->
|
||||||
Blog.LOGE("v >> ${v}")
|
// Blog.LOGE("v >> ${v}")
|
||||||
showContents(v.id)
|
// showContents(v.id)
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (intent?.action == Intent.ACTION_WEB_SEARCH) {
|
if (intent?.action == Intent.ACTION_WEB_SEARCH) {
|
||||||
openWithIntent(intent)
|
openWithIntent(intent)
|
||||||
}
|
}
|
||||||
val nullCursor = PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL)
|
val nullCursor = PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL)
|
||||||
binding.root.setPointerIcon(nullCursor)
|
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)
|
showContents(R.id.btn_info)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,32 +365,31 @@ open class NeoRssActivity : CommonActivity() {
|
|||||||
private var lastTouchY = 0f
|
private var lastTouchY = 0f
|
||||||
|
|
||||||
|
|
||||||
private val fragmentMap = mutableMapOf<Int, Fragment>()
|
// private val fragmentMap = mutableMapOf<Int, Fragment>()
|
||||||
|
|
||||||
fun showContents(id : Int) {
|
fun showContents(id : Int) {
|
||||||
binding.fragmentLayer.visibility = View.VISIBLE
|
if (id == View.NO_ID) {
|
||||||
binding.fragmentContainer.visibility = View.VISIBLE
|
Blog.LOGE("무효한 ID(-1) 호출로 인해 showContents를 취소합니다.")
|
||||||
binding.controllPanel.visibility = View.VISIBLE
|
return
|
||||||
binding.floatingActionMenu.visibility = View.VISIBLE
|
}
|
||||||
|
|
||||||
|
Blog.LOGE("targetFragment id ${id}")
|
||||||
|
// 1. 모든 관련 레이어 가시성 확보
|
||||||
|
binding.fragmentContainer.isVisible = true
|
||||||
|
|
||||||
val transaction = supportFragmentManager.beginTransaction()
|
val transaction = supportFragmentManager.beginTransaction()
|
||||||
|
val tagKey = "TAG_$id"
|
||||||
fragmentMap.values.forEach { fragment ->
|
Blog.LOGE("targetFragment id ${id} tagKey ${tagKey}")
|
||||||
if (fragment.isAdded) {
|
// 2. 매니저에 등록된 모든 프래그먼트를 찾아서 숨김 (중복 방지)
|
||||||
transaction.hide(fragment)
|
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) {
|
// 3. 대상 프래그먼트 찾기 (findFragmentByTag 우선)
|
||||||
targetFragment = supportFragmentManager.findFragmentByTag("TAG_$id")
|
var targetFragment = supportFragmentManager.findFragmentByTag(tagKey)
|
||||||
|
|
||||||
// 시스템이 복구해 놓은 게 있다면, 맵에 다시 등록(싱크 맞추기)
|
|
||||||
if (targetFragment != null) {
|
|
||||||
fragmentMap[id] = targetFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetFragment == null) {
|
if (targetFragment == null) {
|
||||||
// 처음 호출되는 메뉴라면 인스턴스 생성 및 추가
|
// 처음 호출되는 메뉴라면 인스턴스 생성 및 추가
|
||||||
@ -426,10 +413,11 @@ open class NeoRssActivity : CommonActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
targetFragment?.let {
|
targetFragment?.let {
|
||||||
fragmentMap[id] = it
|
Blog.LOGE("targetFragment id ${id} key : ${tagKey}, instance ${targetFragment}")
|
||||||
transaction.add(R.id.fragment_container, it, "TAG_$id")
|
transaction.add(R.id.fragment_container, it, tagKey)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
Blog.LOGE("targetFragment id ${id} key : ${tagKey}, instance ${targetFragment}")
|
||||||
// 이미 생성된 프래그먼트가 있다면 다시 보여줌
|
// 이미 생성된 프래그먼트가 있다면 다시 보여줌
|
||||||
transaction.show(targetFragment)
|
transaction.show(targetFragment)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -220,11 +220,11 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
|||||||
return when (event.keyCode) {
|
return when (event.keyCode) {
|
||||||
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||||
if (isUp) {
|
if (isUp) {
|
||||||
if (binding.geckoWeb.scrollState > 0) {
|
if (binding.lunaticBrowser.geckoWeb.scrollState > 0) {
|
||||||
// 사용자가 손으로 끝까지 내렸든, 볼륨키로 내렸든 상관없이 이곳에 도달함
|
// 사용자가 손으로 끝까지 내렸든, 볼륨키로 내렸든 상관없이 이곳에 도달함
|
||||||
doNextPage()
|
doNextPage()
|
||||||
} else {
|
} else {
|
||||||
binding.geckoWeb.pageDown()
|
binding.lunaticBrowser.geckoWeb.pageDown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
@ -232,10 +232,10 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
|||||||
}
|
}
|
||||||
KeyEvent.KEYCODE_VOLUME_UP -> {
|
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||||
if (isUp) {
|
if (isUp) {
|
||||||
if (binding.geckoWeb.scrollState < 0) {
|
if (binding.lunaticBrowser.geckoWeb.scrollState < 0) {
|
||||||
binding.geckoWeb.session?.goBack()
|
binding.lunaticBrowser.geckoWeb.session?.goBack()
|
||||||
} else {
|
} else {
|
||||||
binding.geckoWeb.pageUp()
|
binding.lunaticBrowser.geckoWeb.pageUp()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
@ -261,7 +261,7 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun searchKeyword() {
|
fun searchKeyword() {
|
||||||
binding.geckoWeb.visibility = View.GONE
|
binding.lunaticBrowser.visibility = View.GONE
|
||||||
val bottomSheet = SearchBottomSheet()
|
val bottomSheet = SearchBottomSheet()
|
||||||
bottomSheet.listener = object : OnSearchListener{
|
bottomSheet.listener = object : OnSearchListener{
|
||||||
override fun onSearch(
|
override fun onSearch(
|
||||||
@ -280,11 +280,11 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
|||||||
|
|
||||||
|
|
||||||
fun ask() {
|
fun ask() {
|
||||||
binding.geckoWeb.visibility = View.GONE
|
binding.lunaticBrowser.visibility = View.GONE
|
||||||
val bottomSheet = WebBottomSheet()
|
val bottomSheet = WebBottomSheet()
|
||||||
bottomSheet.listener = object : WebBottomSheet.OnGoToWebListener{
|
bottomSheet.listener = object : WebBottomSheet.OnGoToWebListener{
|
||||||
override fun enterSearch() {
|
override fun enterSearch() {
|
||||||
binding.geckoWeb.sendSearchDo()
|
binding.lunaticBrowser.geckoWeb.sendSearchDo()
|
||||||
}
|
}
|
||||||
override fun onGotoWeb(
|
override fun onGotoWeb(
|
||||||
keyword: String,
|
keyword: String,
|
||||||
@ -294,56 +294,56 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
|||||||
when (category) {
|
when (category) {
|
||||||
|
|
||||||
RssDataType.ANYTHING -> {
|
RssDataType.ANYTHING -> {
|
||||||
if (binding.geckoWeb.lastedUrl?.contains("search.brave") == true) {
|
if (binding.lunaticBrowser.geckoWeb.lastedUrl?.contains("search.brave") == true) {
|
||||||
binding.geckoWeb.sendSearch(keyword)
|
binding.lunaticBrowser.geckoWeb.sendSearch(keyword)
|
||||||
} else {
|
} 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 -> {
|
RssDataType.GOOGLE -> {
|
||||||
if (binding.geckoWeb.lastedUrl?.contains("www.google") == true) {
|
if (binding.lunaticBrowser.geckoWeb.lastedUrl?.contains("www.google") == true) {
|
||||||
binding.geckoWeb.sendSearch(keyword)
|
binding.lunaticBrowser.geckoWeb.sendSearch(keyword)
|
||||||
} else {
|
} 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 -> {
|
RssDataType.NAMU -> {
|
||||||
if (binding.geckoWeb.lastedUrl?.contains("namu.wiki") == true) {
|
if (binding.lunaticBrowser.geckoWeb.lastedUrl?.contains("namu.wiki") == true) {
|
||||||
binding.geckoWeb.sendSearch(keyword)
|
binding.lunaticBrowser.geckoWeb.sendSearch(keyword)
|
||||||
} else {
|
} 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 -> {
|
RssDataType.NAVER -> {
|
||||||
if (binding.geckoWeb.lastedUrl?.contains("naver") == true) {
|
if (binding.lunaticBrowser.geckoWeb.lastedUrl?.contains("naver") == true) {
|
||||||
binding.geckoWeb.sendSearch(keyword)
|
binding.lunaticBrowser.geckoWeb.sendSearch(keyword)
|
||||||
} else {
|
} 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 -> {
|
RssDataType.NEWSFEED -> {
|
||||||
if (binding.geckoWeb.lastedUrl?.contains("news.google") == true) {
|
if (binding.lunaticBrowser.geckoWeb.lastedUrl?.contains("news.google") == true) {
|
||||||
binding.geckoWeb.sendSearch(keyword)
|
binding.lunaticBrowser.geckoWeb.sendSearch(keyword)
|
||||||
} else {
|
} 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 -> {
|
RssDataType.NEWS -> {
|
||||||
if (binding.geckoWeb.lastedUrl?.contains("bigkinds") == true) {
|
if (binding.lunaticBrowser.geckoWeb.lastedUrl?.contains("bigkinds") == true) {
|
||||||
binding.geckoWeb.sendSearch(keyword)
|
binding.lunaticBrowser.geckoWeb.sendSearch(keyword)
|
||||||
} else {
|
} 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 -> {
|
RssDataType.PRIVATE -> {
|
||||||
binding.geckoWeb.privateMode = privateMode
|
binding.lunaticBrowser.geckoWeb.privateMode = privateMode
|
||||||
binding.geckoWeb.loadUrl("aHR0cHM6Ly9pamF2dG9ycmVudC5jb20=", if (keyword.length ?: 0 > 0) {"/?searchTerm=${keyword}"} else {null})
|
binding.lunaticBrowser.geckoWeb.loadUrl("aHR0cHM6Ly9pamF2dG9ycmVudC5jb20=", if (keyword.length ?: 0 > 0) {"/?searchTerm=${keyword}"} else {null})
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
|
|
||||||
@ -365,7 +365,7 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
|||||||
}
|
}
|
||||||
appendReadCount(it, 1, false)
|
appendReadCount(it, 1, false)
|
||||||
binding.layoutRssSummary.title.setOnLongClickListener {
|
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
|
binding.layoutRssSummary.root.visibility = View.GONE
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@ -437,12 +437,15 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
|||||||
@SuppressLint("SimpleDateFormat")
|
@SuppressLint("SimpleDateFormat")
|
||||||
fun openGecko(rssData: RssData? = null) {
|
fun openGecko(rssData: RssData? = null) {
|
||||||
binding.layoutRssSummary.root.visibility = View.GONE
|
binding.layoutRssSummary.root.visibility = View.GONE
|
||||||
|
binding.lunaticBrowser.visibility = View.GONE
|
||||||
|
binding.lunaticBrowser.setupForRssHome()
|
||||||
if (rssData?.category()?.equals(RssDataType.PRIVATE) == true && rssData?.getMagnet().isNullOrEmpty()) {
|
if (rssData?.category()?.equals(RssDataType.PRIVATE) == true && rssData?.getMagnet().isNullOrEmpty()) {
|
||||||
openSummary(rssData)
|
openSummary(rssData)
|
||||||
} else if (rssData?.originPage?.isNotEmpty() == true) {
|
} else if (rssData?.originPage?.isNotEmpty() == true) {
|
||||||
rssData?.let { rss ->
|
rssData?.let { rss ->
|
||||||
currentRss = rss
|
currentRss = rss
|
||||||
binding.geckoWeb.privateMode = false
|
binding.lunaticBrowser.visibility = View.VISIBLE
|
||||||
|
binding.lunaticBrowser.geckoWeb.privateMode = false
|
||||||
appendReadCount(rss, 1, false)
|
appendReadCount(rss, 1, false)
|
||||||
rss?.originPage?.let { rssId->
|
rss?.originPage?.let { rssId->
|
||||||
synchronized(lasted) {
|
synchronized(lasted) {
|
||||||
@ -450,7 +453,7 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
|||||||
lasted.removeAll { target -> target.originPage.equals(rssId) }
|
lasted.removeAll { target -> target.originPage.equals(rssId) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.geckoWeb.loadUrl(rssId)
|
binding.lunaticBrowser.geckoWeb.loadUrl(rssId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -487,17 +490,12 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
|||||||
return@setOnTouchListener false
|
return@setOnTouchListener false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.vote.setOnClickListener {
|
|
||||||
if (binding.geckoWeb.isVisible) {
|
|
||||||
vote()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (openQuery.length > 0) {
|
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 = ""
|
openQuery = ""
|
||||||
}
|
}
|
||||||
binding.geckoWeb?.mOnSave = object : GeckoWeb.OnSave{
|
binding.lunaticBrowser.geckoWeb?.mOnSave = object : GeckoWeb.OnSave{
|
||||||
override fun saved() {
|
override fun saved() {
|
||||||
currentRss?.originPage.let {
|
currentRss?.originPage.let {
|
||||||
Blog.LOGE("Arrow Center Click")
|
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 {
|
binding.layoutRssSummary.link.setOnClickListener {
|
||||||
(it.tag as? RssData)?.let {
|
(it.tag as? RssData)?.let {
|
||||||
@ -573,46 +537,113 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
|||||||
|
|
||||||
queryInfos()
|
queryInfos()
|
||||||
|
|
||||||
binding.geckoWeb.progress = binding.progressBar
|
val nullCursor = PointerIcon.getSystemIcon(requireContext(), PointerIcon.TYPE_NULL)
|
||||||
binding.geckoWeb.jxInteface = { jxEvent ->
|
binding.root.setPointerIcon(nullCursor)
|
||||||
when (jxEvent) {
|
|
||||||
JxEvent.SCROLL_UP -> binding.geckoWeb.sendScrollDown(false)
|
|
||||||
JxEvent.SCROLL_DOWN -> binding.geckoWeb.sendScrollDown(true)
|
|
||||||
JxEvent.ON_CLICK -> {
|
|
||||||
binding.geckoWeb.visibility = View.GONE
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
doNextPage()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
JxEvent.SWIPE_RIGHT -> {
|
browser.binding.btnVote.setOnClickListener {
|
||||||
|
if (binding.lunaticBrowser.isVisible) {
|
||||||
vote()
|
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.lunaticBrowser.geckoWeb.restoreSessionState()
|
||||||
binding.root.setPointerIcon(nullCursor)
|
|
||||||
binding.search.setOnClickListener { searchKeyword() }
|
binding.let { b ->
|
||||||
binding.search.setOnLongClickListener{
|
// 전체 탭
|
||||||
ask()
|
b.tabAll.setOnClickListener {
|
||||||
true
|
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
|
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() {
|
fun vote() {
|
||||||
binding.geckoWeb?.saveMd(true)
|
binding.lunaticBrowser.geckoWeb?.saveMd(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TokiFragment 또는 GeckoView를 사용하는 프래그먼트 내부
|
// TokiFragment 또는 GeckoView를 사용하는 프래그먼트 내부
|
||||||
@ -620,7 +651,7 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
|||||||
super.onHiddenChanged(hidden)
|
super.onHiddenChanged(hidden)
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
// 💡 화면에서 사라질 때: 타이머 중지 및 애니메이션 중지
|
// 💡 화면에서 사라질 때: 타이머 중지 및 애니메이션 중지
|
||||||
binding.geckoWeb?.onPause()
|
binding.lunaticBrowser.geckoWeb?.onPause()
|
||||||
// 일반 WebView라면: webView.onPause() 및 webView.pauseTimers()
|
// 일반 WebView라면: webView.onPause() 및 webView.pauseTimers()
|
||||||
} else {
|
} else {
|
||||||
// 💡 다시 나타날 때: 다시 시작
|
// 💡 다시 나타날 때: 다시 시작
|
||||||
@ -960,26 +991,6 @@ internal class RssHome : Fragment() , KeyEventHandler {
|
|||||||
}
|
}
|
||||||
fun randomOrNull() : RssData? = lasted.randomOrNull()
|
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
|
var toast: Toast? = null
|
||||||
fun Context.toast(string: String) {
|
fun Context.toast(string: String) {
|
||||||
|
|||||||
@ -38,6 +38,9 @@ import bums.lunatic.launcher.databinding.FragmentSystemStatusBinding
|
|||||||
import bums.lunatic.launcher.utils.Blog
|
import bums.lunatic.launcher.utils.Blog
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
|
||||||
class SystemStatusFragment : Fragment() {
|
class SystemStatusFragment : Fragment() {
|
||||||
|
|
||||||
@ -120,7 +123,8 @@ class SystemStatusFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
binding.progressBattery.setOnClickListener { binding.textBattery.performClick() }
|
binding.progressBattery.setOnClickListener { binding.textBattery.performClick() }
|
||||||
}
|
}
|
||||||
|
private var isFlashOn = false
|
||||||
|
private lateinit var cameraManager: android.hardware.camera2.CameraManager
|
||||||
private fun setupQuickControls() {
|
private fun setupQuickControls() {
|
||||||
binding.btnQuickWifi.setOnClickListener {
|
binding.btnQuickWifi.setOnClickListener {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
@ -177,9 +181,109 @@ class SystemStatusFragment : Fragment() {
|
|||||||
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
|
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
|
||||||
override fun onStopTrackingTouch(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()
|
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() {
|
private fun updateQuickControlUI() {
|
||||||
val ringerText = when (audioManager.ringerMode) {
|
val ringerText = when (audioManager.ringerMode) {
|
||||||
|
|||||||
@ -292,7 +292,7 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
|||||||
}
|
}
|
||||||
// Novels의 경우 VISIBLE 처리가 있었으나, GONE 처리 후 필요 시 메뉴를 보여주는 흐름으로 통합
|
// Novels의 경우 VISIBLE 처리가 있었으나, GONE 처리 후 필요 시 메뉴를 보여주는 흐름으로 통합
|
||||||
if(contentsType == "book") {
|
if(contentsType == "book") {
|
||||||
binding.menuWeb.visibility = VISIBLE
|
binding.lunaticBrowser.visibility = VISIBLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -312,7 +312,7 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
|||||||
|
|
||||||
fun back() {
|
fun back() {
|
||||||
if (contentsType == "youtube") {
|
if (contentsType == "youtube") {
|
||||||
binding.menuWeb.session?.goBack()
|
binding.lunaticBrowser.geckoWeb.session?.goBack()
|
||||||
} else {
|
} else {
|
||||||
actionNextEvent(false)
|
actionNextEvent(false)
|
||||||
}
|
}
|
||||||
@ -431,7 +431,7 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
|||||||
super.onHiddenChanged(hidden)
|
super.onHiddenChanged(hidden)
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
// 💡 화면에서 사라질 때: 타이머 중지 및 애니메이션 중지
|
// 💡 화면에서 사라질 때: 타이머 중지 및 애니메이션 중지
|
||||||
binding.menuWeb?.onPause()
|
binding.lunaticBrowser.geckoWeb?.onPause()
|
||||||
// 일반 WebView라면: webView.onPause() 및 webView.pauseTimers()
|
// 일반 WebView라면: webView.onPause() 및 webView.pauseTimers()
|
||||||
} else {
|
} else {
|
||||||
// 💡 다시 나타날 때: 다시 시작
|
// 💡 다시 나타날 때: 다시 시작
|
||||||
@ -447,7 +447,47 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
|||||||
): View {
|
): View {
|
||||||
Blog.LOGD(log = "onCreate ${this::class.java.name} >> savedInstanceState ${savedInstanceState}")
|
Blog.LOGD(log = "onCreate ${this::class.java.name} >> savedInstanceState ${savedInstanceState}")
|
||||||
binding = BooktokiBinding.inflate(inflater)
|
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.sessionTag = webcontentsName
|
||||||
it.visibility = View.VISIBLE
|
it.visibility = View.VISIBLE
|
||||||
it.lastDomain = getLastedDoamin()
|
it.lastDomain = getLastedDoamin()
|
||||||
@ -462,33 +502,13 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
|||||||
if (lastedUrl?.contains("youtube.com") == true) {
|
if (lastedUrl?.contains("youtube.com") == true) {
|
||||||
|
|
||||||
} else {
|
} 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 ->
|
it.onPageStartCallback = { url ->
|
||||||
binding.progress.visibility = VISIBLE
|
// binding.lunaticBrowser.binding.progress.visibility = VISIBLE
|
||||||
}
|
}
|
||||||
it.onSessionStateChangeCallback = {sessionState ->
|
it.onSessionStateChangeCallback = {sessionState ->
|
||||||
if (contentsType == "comics" || contentsType == "webtoon") {
|
if (contentsType == "comics" || contentsType == "webtoon") {
|
||||||
@ -508,15 +528,17 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
|||||||
}
|
}
|
||||||
"NotRegistered" -> {
|
"NotRegistered" -> {
|
||||||
binding.pagedLayer.visibility = GONE
|
binding.pagedLayer.visibility = GONE
|
||||||
|
binding.lunaticBrowser.visibility = VISIBLE
|
||||||
}
|
}
|
||||||
"WebtoonContents"-> {
|
"WebtoonContents"-> {
|
||||||
binding.pagedLayer.visibility = GONE
|
binding.pagedLayer.visibility = GONE
|
||||||
|
binding.lunaticBrowser.visibility = VISIBLE
|
||||||
}
|
}
|
||||||
"MSG" -> {
|
"MSG" -> {
|
||||||
lPortMessage.msg?.let { Toast.makeText(requireContext(),it, Toast.LENGTH_SHORT).show() }
|
lPortMessage.msg?.let { Toast.makeText(requireContext(),it, Toast.LENGTH_SHORT).show() }
|
||||||
}
|
}
|
||||||
"SHOWVIEWER" -> {
|
"SHOWVIEWER" -> {
|
||||||
binding.progress.visibility = GONE
|
binding.lunaticBrowser.binding.internalProgressBar.visibility = GONE
|
||||||
}
|
}
|
||||||
"PRIVATES"->{
|
"PRIVATES"->{
|
||||||
lPortMessage.privates?.let {
|
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) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
@ -539,7 +561,7 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
|||||||
} else {
|
} else {
|
||||||
// url이 현재 로드된 주소입니다.
|
// url이 현재 로드된 주소입니다.
|
||||||
Blog.LOGE("GeckoView", "현재 주소: $url")
|
Blog.LOGE("GeckoView", "현재 주소: $url")
|
||||||
Blog.LOGE("GeckoView", "현재 session: ${binding.menuWeb.session}")
|
Blog.LOGE("GeckoView", "현재 session: ${binding.lunaticBrowser.geckoWeb.session}")
|
||||||
url?.let { url ->
|
url?.let { url ->
|
||||||
if (url.split("//").size > 1) {
|
if (url.split("//").size > 1) {
|
||||||
url.replace("//", "/").replace("https:/", "https://").let {
|
url.replace("//", "/").replace("https:/", "https://").let {
|
||||||
@ -570,44 +592,16 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
|||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(activity as? NeoRssActivity)?.let { activity ->
|
|
||||||
it.decoViews.clear()
|
it.decoViews.clear()
|
||||||
it.decoViews.add(activity.findViewById<TextView>(R.id.current_address))
|
it.decoViews.add(binding.lunaticBrowser.findViewById(R.id.tv_address))
|
||||||
it.decoViews.add(activity.findViewById<ImageButton>(R.id.back))
|
it.decoViews.add(binding.lunaticBrowser.findViewById(R.id.btn_back))
|
||||||
it.decoViews.add(activity.findViewById<ImageButton>(R.id.reload))
|
it.decoViews.add(binding.lunaticBrowser.findViewById(R.id.btn_reload))
|
||||||
it.decoViews.add(activity.findViewById<ImageButton>(R.id.dl_video))
|
it.decoViews.add(binding.lunaticBrowser.findViewById(R.id.btn_dl_video))
|
||||||
}
|
|
||||||
it.restoreSessionState()
|
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)
|
val nullCursor = PointerIcon.getSystemIcon(requireContext(), PointerIcon.TYPE_NULL)
|
||||||
binding.root.setPointerIcon(nullCursor)
|
binding.root.setPointerIcon(nullCursor)
|
||||||
@ -967,9 +961,9 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
|||||||
contentsPageInfo.contents?.let { onLoadedContents(it) }
|
contentsPageInfo.contents?.let { onLoadedContents(it) }
|
||||||
contentsPageInfo.chapterTitle?.let { onFindTitle(it) }
|
contentsPageInfo.chapterTitle?.let { onFindTitle(it) }
|
||||||
if (contentsPageInfo.pathUrl?.startsWith("http") == true) {
|
if (contentsPageInfo.pathUrl?.startsWith("http") == true) {
|
||||||
binding.menuWeb.loadUrl(pathUrl)
|
binding.lunaticBrowser.geckoWeb.loadUrl(pathUrl)
|
||||||
} else {
|
} else {
|
||||||
binding.menuWeb.loadUrl(getLastedDoamin() + contentsPageInfo.pathUrl!!)
|
binding.lunaticBrowser.geckoWeb.loadUrl(getLastedDoamin() + contentsPageInfo.pathUrl!!)
|
||||||
}
|
}
|
||||||
HistoryManager.save(HistoryItem().putHistory(contentsPageInfo, contentsPageInfo.pathUrl!!))
|
HistoryManager.save(HistoryItem().putHistory(contentsPageInfo, contentsPageInfo.pathUrl!!))
|
||||||
}
|
}
|
||||||
@ -984,9 +978,10 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
|||||||
applyCurrentBook(it)
|
applyCurrentBook(it)
|
||||||
} else if(lastInfo != null){
|
} else if(lastInfo != null){
|
||||||
binding.pagedLayer.visibility = GONE
|
binding.pagedLayer.visibility = GONE
|
||||||
binding.menuWeb.loadUrl(getLastedDoamin() + lastInfo!!.pageUrl)
|
binding.lunaticBrowser.visibility = VISIBLE
|
||||||
|
binding.lunaticBrowser.geckoWeb.loadUrl(getLastedDoamin() + lastInfo!!.pageUrl)
|
||||||
} else {
|
} else {
|
||||||
binding.menuWeb.loadUrl(getLastedDoamin())
|
binding.lunaticBrowser.geckoWeb.loadUrl(getLastedDoamin())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1088,7 +1083,7 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (saveContinuation) {
|
if (saveContinuation) {
|
||||||
binding.menuWeb.postDelayed({
|
binding.lunaticBrowser.geckoWeb.postDelayed({
|
||||||
moveToNext(
|
moveToNext(
|
||||||
currentPage?.pathUrl ?: lastedUrl?.toUri()?.path
|
currentPage?.pathUrl ?: lastedUrl?.toUri()?.path
|
||||||
)
|
)
|
||||||
@ -1108,16 +1103,16 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
|||||||
contents = (contents.replace("\\n", System.getProperty("line.separator")))
|
contents = (contents.replace("\\n", System.getProperty("line.separator")))
|
||||||
view.mPagedTextViewInterface = this@TokiFragment
|
view.mPagedTextViewInterface = this@TokiFragment
|
||||||
if (lastInfo != null && lastedUrl?.endsWith(lastInfo!!.pageUrl) == true) {
|
if (lastInfo != null && lastedUrl?.endsWith(lastInfo!!.pageUrl) == true) {
|
||||||
binding.progress.visibility = VISIBLE
|
binding.lunaticBrowser.binding.internalProgressBar.visibility = VISIBLE
|
||||||
binding.pagedLayer.postDelayed({
|
binding.pagedLayer.postDelayed({
|
||||||
binding.progress.visibility = GONE
|
binding.lunaticBrowser.binding.internalProgressBar.visibility = GONE
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
applyReaderConfig()
|
applyReaderConfig()
|
||||||
activity?.runOnUiThread {
|
activity?.runOnUiThread {
|
||||||
view.text = contents
|
view.text = contents
|
||||||
view.visibility = VISIBLE
|
view.visibility = VISIBLE
|
||||||
binding.menuWeb.visibility = GONE
|
binding.lunaticBrowser.visibility = GONE
|
||||||
}
|
}
|
||||||
// view.forceUpdateUI()
|
// view.forceUpdateUI()
|
||||||
lastedUrl?.let {
|
lastedUrl?.let {
|
||||||
@ -1139,7 +1134,7 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
|||||||
HistoryManager.getBooPageInfoContentsSave(contentsType,it, contents)
|
HistoryManager.getBooPageInfoContentsSave(contentsType,it, contents)
|
||||||
}
|
}
|
||||||
if (saveContinuation) {
|
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
|
var currentChapter: Int = 0
|
||||||
|
|
||||||
fun onFindTitle(contents: String) {
|
fun onFindTitle(contents: String) {
|
||||||
binding.textviewTitle.text = contents
|
binding.lunaticBrowser.binding.tvTitle.text = contents
|
||||||
binding.textviewTitle.setOnClickListener {
|
binding.lunaticBrowser.binding.tvTitle.setOnClickListener {
|
||||||
val builder = AlertDialog.Builder(requireContext())
|
val builder = AlertDialog.Builder(requireContext())
|
||||||
builder.setTitle("Title")
|
builder.setTitle("Title")
|
||||||
val input = EditText(requireContext())
|
val input = EditText(requireContext())
|
||||||
@ -1196,7 +1191,7 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onStartLoad() {
|
fun onStartLoad() {
|
||||||
binding.progress.visibility = VISIBLE
|
binding.lunaticBrowser.binding.internalProgressBar.visibility = VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
fun completePageLoad(lastInfo: LastInfo) {
|
fun completePageLoad(lastInfo: LastInfo) {
|
||||||
@ -1305,6 +1300,6 @@ class TokiFragment : Fragment(), PagedTextViewInterface,KeyEventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun goToHome() {
|
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.content.res.Resources
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import bums.lunatic.launcher.BuildConfig
|
import bums.lunatic.launcher.BuildConfig
|
||||||
import bums.lunatic.launcher.R
|
import bums.lunatic.launcher.R
|
||||||
import bums.lunatic.launcher.common.CommonActivity
|
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.settings.childs.Misc
|
||||||
import bums.lunatic.launcher.utils.Blog
|
import bums.lunatic.launcher.utils.Blog
|
||||||
import bums.lunatic.launcher.workers.WorkersDb
|
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.bottomsheet.BottomSheetDialog
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
@ -46,7 +52,11 @@ import com.google.gson.Gson
|
|||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import io.realm.kotlin.UpdatePolicy
|
import io.realm.kotlin.UpdatePolicy
|
||||||
import io.realm.kotlin.ext.query
|
import io.realm.kotlin.ext.query
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
|
||||||
@ -55,6 +65,9 @@ internal class SettingsActivity : CommonActivity() {
|
|||||||
private lateinit var binding: SettingsActivityBinding
|
private lateinit var binding: SettingsActivityBinding
|
||||||
private val sourceCode = "https://github.com/iamrasel/lunar-launcher"
|
private val sourceCode = "https://github.com/iamrasel/lunar-launcher"
|
||||||
|
|
||||||
|
// 전역 변수로 매니저 선언 (콜백 처리)
|
||||||
|
private lateinit var loginManager: GDriveLoginManager
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic var settingsPrefs: SharedPreferences? = null
|
@JvmStatic var settingsPrefs: SharedPreferences? = null
|
||||||
}
|
}
|
||||||
@ -70,6 +83,49 @@ internal class SettingsActivity : CommonActivity() {
|
|||||||
|
|
||||||
settingsPrefs = this.getSharedPreferences(PREFS_SETTINGS, 0)
|
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 */
|
/* launch child settings dialogs on button clicks */
|
||||||
// binding.timeDate.setOnClickListener {
|
// binding.timeDate.setOnClickListener {
|
||||||
// TopInfos().show(supportFragmentManager, BOTTOM_SHEET_TAG)
|
// 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{
|
var cancelCount = Runnable{
|
||||||
clickCount = 0
|
clickCount = 0
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import android.view.animation.Interpolator
|
|||||||
import android.view.animation.OvershootInterpolator
|
import android.view.animation.OvershootInterpolator
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.ImageView.ScaleType
|
import android.widget.ImageView.ScaleType
|
||||||
|
import android.widget.TextView
|
||||||
import bums.lunatic.launcher.R
|
import bums.lunatic.launcher.R
|
||||||
import bums.lunatic.launcher.utils.CommonUtils
|
import bums.lunatic.launcher.utils.CommonUtils
|
||||||
|
|
||||||
@ -85,7 +86,7 @@ class FloatingActionMenu @JvmOverloads constructor(
|
|||||||
private var mLabelsStyle = 0
|
private var mLabelsStyle = 0
|
||||||
private var mCustomTypefaceFromFont: Typeface? = null
|
private var mCustomTypefaceFromFont: Typeface? = null
|
||||||
var isIconAnimated: Boolean = true
|
var isIconAnimated: Boolean = true
|
||||||
private var mImageToggle: ImageView? = null
|
private var mToggleView: View? = null
|
||||||
private var mMenuButtonShowAnimation: Animation? = null
|
private var mMenuButtonShowAnimation: Animation? = null
|
||||||
private var mMenuButtonHideAnimation: Animation? = null
|
private var mMenuButtonHideAnimation: Animation? = null
|
||||||
private var mImageToggleShowAnimation: Animation? = null
|
private var mImageToggleShowAnimation: Animation? = null
|
||||||
@ -198,7 +199,7 @@ class FloatingActionMenu @JvmOverloads constructor(
|
|||||||
attr.getInt(R.styleable.FloatingActionMenu_menu_animationDelayPerItem, 50)
|
attr.getInt(R.styleable.FloatingActionMenu_menu_animationDelayPerItem, 50)
|
||||||
mIcon = attr.getDrawable(R.styleable.FloatingActionMenu_menu_icon)
|
mIcon = attr.getDrawable(R.styleable.FloatingActionMenu_menu_icon)
|
||||||
if (mIcon == null) {
|
if (mIcon == null) {
|
||||||
mIcon = getResources().getDrawable(R.drawable.fab_add)
|
// mIcon = getResources().getDrawable(R.drawable.fab_add)
|
||||||
}
|
}
|
||||||
mLabelsSingleLine =
|
mLabelsSingleLine =
|
||||||
attr.getBoolean(R.styleable.FloatingActionMenu_menu_labels_singleLine, false)
|
attr.getBoolean(R.styleable.FloatingActionMenu_menu_labels_singleLine, false)
|
||||||
@ -314,15 +315,21 @@ class FloatingActionMenu @JvmOverloads constructor(
|
|||||||
mMenuButton!!.mFabSize = mMenuFabSize
|
mMenuButton!!.mFabSize = mMenuFabSize
|
||||||
mMenuButton!!.updateBackground()
|
mMenuButton!!.updateBackground()
|
||||||
mMenuButton!!.labelText = mMenuLabelText
|
mMenuButton!!.labelText = mMenuLabelText
|
||||||
|
|
||||||
mImageToggle = ImageView(getContext())
|
|
||||||
mImageToggle?.scaleType = ScaleType.FIT_CENTER
|
|
||||||
mImageToggle?.adjustViewBounds = true
|
|
||||||
mImageToggle!!.setImageDrawable(mIcon)
|
|
||||||
|
|
||||||
addView(mMenuButton, super.generateDefaultLayoutParams())
|
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()
|
createDefaultIconAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,14 +349,14 @@ class FloatingActionMenu @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val collapseAnimator = ObjectAnimator.ofFloat(
|
val collapseAnimator = ObjectAnimator.ofFloat(
|
||||||
mImageToggle,
|
mToggleView,
|
||||||
"rotation",
|
"rotation",
|
||||||
collapseAngle,
|
collapseAngle,
|
||||||
CLOSED_PLUS_ROTATION
|
CLOSED_PLUS_ROTATION
|
||||||
)
|
)
|
||||||
|
|
||||||
val expandAnimator = ObjectAnimator.ofFloat(
|
val expandAnimator = ObjectAnimator.ofFloat(
|
||||||
mImageToggle,
|
mToggleView,
|
||||||
"rotation",
|
"rotation",
|
||||||
CLOSED_PLUS_ROTATION,
|
CLOSED_PLUS_ROTATION,
|
||||||
expandAngle
|
expandAngle
|
||||||
@ -371,12 +378,12 @@ class FloatingActionMenu @JvmOverloads constructor(
|
|||||||
mMaxButtonWidth = 0
|
mMaxButtonWidth = 0
|
||||||
var maxLabelWidth = 0
|
var maxLabelWidth = 0
|
||||||
|
|
||||||
measureChildWithMargins(mImageToggle, widthMeasureSpec, 0, heightMeasureSpec, 0)
|
measureChildWithMargins(mToggleView, widthMeasureSpec, 0, heightMeasureSpec, 0)
|
||||||
|
|
||||||
for (i in 0..<mButtonsCount) {
|
for (i in 0..<mButtonsCount) {
|
||||||
val child = getChildAt(i)
|
val child = getChildAt(i)
|
||||||
|
|
||||||
if (child.getVisibility() == GONE || child === mImageToggle) continue
|
if (child.getVisibility() == GONE || child === mToggleView) continue
|
||||||
|
|
||||||
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0)
|
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0)
|
||||||
mMaxButtonWidth =
|
mMaxButtonWidth =
|
||||||
@ -387,7 +394,7 @@ class FloatingActionMenu @JvmOverloads constructor(
|
|||||||
var usedWidth = 0
|
var usedWidth = 0
|
||||||
val child = getChildAt(i)
|
val child = getChildAt(i)
|
||||||
|
|
||||||
if (child.getVisibility() == GONE || child === mImageToggle) continue
|
if (child.getVisibility() == GONE || child === mToggleView) continue
|
||||||
|
|
||||||
usedWidth += child.getMeasuredWidth()
|
usedWidth += child.getMeasuredWidth()
|
||||||
height += child.getMeasuredHeight()
|
height += child.getMeasuredHeight()
|
||||||
@ -439,112 +446,98 @@ class FloatingActionMenu @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
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) {
|
private fun moveElements(v: View?, SetX: Float, SetY: Float, oldX: Float, oldY: Float) {
|
||||||
val buttonsHorizontalCenter = if (mLabelsPosition == LABELS_POSITION_LEFT)
|
val parentView = (parent as ViewGroup).parent as View
|
||||||
r - l - mMaxButtonWidth / 2 - getPaddingRight()
|
// 💡 드래그 중에도 부모의 너비와 높이를 계산해 넘겨줍니다.
|
||||||
else
|
layoutChildrenRelative(parentView.width, parentView.height)
|
||||||
mMaxButtonWidth / 2 + getPaddingLeft()
|
}
|
||||||
val openUp = mOpenDirection == OPEN_UP
|
|
||||||
|
|
||||||
val menuButtonTop = if (openUp)
|
// 파라미터에 parentHeight 추가
|
||||||
b - t - mMenuButton!!.getMeasuredHeight() - getPaddingBottom()
|
private fun layoutChildrenRelative(parentWidth: Int, parentHeight: Int) {
|
||||||
else
|
val menuL = mMenuButton!!.x.toInt()
|
||||||
getPaddingTop()
|
val menuT = mMenuButton!!.y.toInt()
|
||||||
val menuButtonLeft = buttonsHorizontalCenter - mMenuButton!!.getMeasuredWidth() / 2
|
val menuW = mMenuButton!!.measuredWidth
|
||||||
|
val menuH = mMenuButton!!.measuredHeight
|
||||||
|
val buttonsCenterX = menuL + menuW / 2
|
||||||
|
val buttonsCenterY = menuT + menuH / 2
|
||||||
|
|
||||||
mMenuButton!!.layout(
|
// 💡 [핵심 복원] 메인 버튼이 화면 하단부에 있으면 위로(openUp = true), 상단부에 있으면 아래로 엽니다.
|
||||||
menuButtonLeft, menuButtonTop, menuButtonLeft + mMenuButton!!.getMeasuredWidth(),
|
val openUp = buttonsCenterY > (parentHeight / 2)
|
||||||
menuButtonTop + mMenuButton!!.getMeasuredHeight()
|
|
||||||
)
|
|
||||||
|
|
||||||
val imageLeft = buttonsHorizontalCenter - mImageToggle!!.getMeasuredWidth() / 2
|
// 1. 토글 아이콘 배치
|
||||||
val imageTop =
|
val imageLeft = buttonsCenterX - mToggleView!!.measuredWidth / 2
|
||||||
menuButtonTop + mMenuButton!!.getMeasuredHeight() / 2 - mImageToggle!!.getMeasuredHeight() / 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(
|
val isMenuOnLeft = buttonsCenterX < parentWidth / 2
|
||||||
imageLeft, imageTop, imageLeft + mImageToggle!!.getMeasuredWidth(),
|
|
||||||
imageTop + mImageToggle!!.getMeasuredHeight()
|
|
||||||
)
|
|
||||||
|
|
||||||
var nextY = if (openUp)
|
// 💡 시작 높이: 위로 열릴 땐 메인 버튼의 Top, 아래로 열릴 땐 메인 버튼의 Bottom
|
||||||
menuButtonTop + mMenuButton!!.getMeasuredHeight() + mButtonSpacing
|
var nextY = if (openUp) menuT else menuT + menuH
|
||||||
else
|
|
||||||
menuButtonTop
|
|
||||||
|
|
||||||
for (i in mButtonsCount - 1 downTo 0) {
|
for (i in mButtonsCount - 1 downTo 0) {
|
||||||
val child = getChildAt(i)
|
val child = getChildAt(i)
|
||||||
|
if (child === mToggleView) continue
|
||||||
if (child === mImageToggle) continue
|
|
||||||
|
|
||||||
val fab = child as FloatingActionButton
|
val fab = child as FloatingActionButton
|
||||||
|
if (fab.visibility == GONE) continue
|
||||||
if (fab.getVisibility() == GONE) continue
|
|
||||||
|
|
||||||
val childX = buttonsHorizontalCenter - fab.getMeasuredWidth() / 2
|
|
||||||
val childY = if (openUp) nextY - fab.getMeasuredHeight() - mButtonSpacing else nextY
|
|
||||||
|
|
||||||
if (fab != mMenuButton) {
|
if (fab != mMenuButton) {
|
||||||
fab.layout(
|
val childX = buttonsCenterX - fab.measuredWidth / 2
|
||||||
childX, childY, childX + fab.getMeasuredWidth(),
|
|
||||||
childY + fab.getMeasuredHeight()
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!mIsMenuOpening) {
|
// 💡 상/하 전개 방향에 맞게 Y 좌표 계산
|
||||||
fab.hide(false)
|
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() {
|
override fun onFinishInflate() {
|
||||||
super.onFinishInflate()
|
super.onFinishInflate()
|
||||||
bringChildToFront(mMenuButton)
|
bringChildToFront(mMenuButton)
|
||||||
bringChildToFront(mImageToggle)
|
bringChildToFront(mToggleView)
|
||||||
mButtonsCount = getChildCount()
|
mButtonsCount = getChildCount()
|
||||||
createLabels()
|
createLabels()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createLabels() {
|
private fun createLabels() {
|
||||||
for (i in 0..<mButtonsCount) {
|
for (i in 0..<mButtonsCount) {
|
||||||
if (getChildAt(i) === mImageToggle) continue
|
if (getChildAt(i) === mToggleView) continue
|
||||||
|
|
||||||
val fab = getChildAt(i) as FloatingActionButton
|
val fab = getChildAt(i) as FloatingActionButton
|
||||||
|
|
||||||
@ -582,47 +575,55 @@ class FloatingActionMenu @JvmOverloads constructor(
|
|||||||
var dY: Float = 0f
|
var dY: Float = 0f
|
||||||
var startX: Float = 0f
|
var startX: Float = 0f
|
||||||
var startY: Float = 0f
|
var startY: Float = 0f
|
||||||
var lastAction: Int = 0
|
|
||||||
override fun onTouch(v: View, event: MotionEvent): Boolean {
|
override fun onTouch(v: View, event: MotionEvent): Boolean {
|
||||||
val oldX = v.getX()
|
val oldX = v.x
|
||||||
val oldY = v.getY()
|
val oldY = v.y
|
||||||
when (event.getActionMasked()) {
|
|
||||||
|
when (event.actionMasked) {
|
||||||
MotionEvent.ACTION_DOWN -> {
|
MotionEvent.ACTION_DOWN -> {
|
||||||
dX = v.getX() - event.getRawX()
|
dX = v.x - event.rawX
|
||||||
dY = v.getY() - event.getRawY()
|
dY = v.y - event.rawY
|
||||||
startX = event.getRawX()
|
startX = event.rawX
|
||||||
startY = event.getRawY()
|
startY = event.rawY
|
||||||
lastAction = MotionEvent.ACTION_DOWN
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_MOVE -> {
|
MotionEvent.ACTION_MOVE -> {
|
||||||
if (!isDragMenuDisabled and (checkViewLimits(
|
if (!isDragMenuDisabled) {
|
||||||
v,
|
// 1. 드래그하려는 목표 좌표 계산
|
||||||
(event.getRawX() + dX).toInt(),
|
var newX = event.rawX + dX
|
||||||
(event.getRawY() + dY).toInt()
|
var newY = event.rawY + dY
|
||||||
))
|
|
||||||
) {
|
|
||||||
v.setY(event.getRawY() + dY)
|
|
||||||
v.setX(event.getRawX() + dX)
|
|
||||||
|
|
||||||
moveElements(
|
// 2. 화면 전체 너비와 높이 가져오기 (현재 FloatingActionMenu가 match_parent이므로 자체 크기 사용)
|
||||||
v,
|
val menuWidth = this@FloatingActionMenu.width
|
||||||
event.getRawX() + dX,
|
val menuHeight = this@FloatingActionMenu.height
|
||||||
event.getRawY() + dY,
|
|
||||||
oldX,
|
// 3. 화면을 벗어나지 않도록 좌표 가두기 (패딩 포함)
|
||||||
oldY
|
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(
|
MotionEvent.ACTION_UP -> {
|
||||||
(startY - event.getRawY()).toDouble()
|
// 드래그가 아니라 단순 클릭(오차 10픽셀 이내)이었다면 메뉴 토글
|
||||||
) < 10
|
if (abs(startX - event.rawX) < 10 && abs(startY - event.rawY) < 10) {
|
||||||
) {
|
toggle(mIsAnimated)
|
||||||
toggle(mIsAnimated)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> return false
|
else -> return false
|
||||||
}
|
}
|
||||||
return true
|
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) {
|
private fun addLabel(fab: FloatingActionButton) {
|
||||||
val text = fab.labelText
|
val text = fab.labelText
|
||||||
@ -821,7 +715,8 @@ class FloatingActionMenu @JvmOverloads constructor(
|
|||||||
label.setTypeface(mCustomTypefaceFromFont)
|
label.setTypeface(mCustomTypefaceFromFont)
|
||||||
}
|
}
|
||||||
label.setText(text)
|
label.setText(text)
|
||||||
label.setOnClickListener(fab.getOnClickListener())
|
label.setOnClickListener {fab.performClick()}
|
||||||
|
|
||||||
|
|
||||||
addView(label)
|
addView(label)
|
||||||
fab.setTag(R.id.fab_label, label)
|
fab.setTag(R.id.fab_label, label)
|
||||||
@ -859,9 +754,9 @@ class FloatingActionMenu @JvmOverloads constructor(
|
|||||||
if (!this.isMenuButtonHidden) {
|
if (!this.isMenuButtonHidden) {
|
||||||
mMenuButton!!.hide(animate)
|
mMenuButton!!.hide(animate)
|
||||||
if (animate) {
|
if (animate) {
|
||||||
mImageToggle!!.startAnimation(mImageToggleHideAnimation)
|
mToggleView!!.startAnimation(mImageToggleHideAnimation)
|
||||||
}
|
}
|
||||||
mImageToggle!!.setVisibility(INVISIBLE)
|
mToggleView!!.setVisibility(INVISIBLE)
|
||||||
mIsMenuButtonAnimationRunning = false
|
mIsMenuButtonAnimationRunning = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -870,9 +765,9 @@ class FloatingActionMenu @JvmOverloads constructor(
|
|||||||
if (this.isMenuButtonHidden) {
|
if (this.isMenuButtonHidden) {
|
||||||
mMenuButton!!.show(animate)
|
mMenuButton!!.show(animate)
|
||||||
if (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
|
mToggleListener = listener
|
||||||
}
|
}
|
||||||
|
|
||||||
val menuIconView: ImageView
|
// val menuIconView: ImageView
|
||||||
get() = mImageToggle!!
|
// get() = mToggleView!!
|
||||||
|
|
||||||
fun setMenuButtonShowAnimation(showAnimation: Animation) {
|
fun setMenuButtonShowAnimation(showAnimation: Animation) {
|
||||||
mMenuButtonShowAnimation = showAnimation
|
mMenuButtonShowAnimation = showAnimation
|
||||||
@ -1239,7 +1134,7 @@ class FloatingActionMenu @JvmOverloads constructor(
|
|||||||
val viewsToRemove: MutableList<FloatingActionButton> = ArrayList<FloatingActionButton>()
|
val viewsToRemove: MutableList<FloatingActionButton> = ArrayList<FloatingActionButton>()
|
||||||
for (i in 0..<getChildCount()) {
|
for (i in 0..<getChildCount()) {
|
||||||
val v = getChildAt(i)
|
val v = getChildAt(i)
|
||||||
if (v !== mMenuButton && v !== mImageToggle && v is FloatingActionButton) {
|
if (v !== mMenuButton && v !== mToggleView && v is FloatingActionButton) {
|
||||||
viewsToRemove.add(v)
|
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,
|
//RecentCall::class, RecentSms::class,
|
||||||
val clazz : Set<KClass<out BaseRealmObject>> = setOf(
|
val clazz : Set<KClass<out BaseRealmObject>> = setOf(
|
||||||
RssData::class, NotificationItem::class, AppInfo::class,SimpleContact::class, CurrentPlayItem::class,
|
RssData::class, AppInfo::class, SimpleContact::class,
|
||||||
TelegramBotUpdate::class, TelegramData::class, TelegramMessage::class, TelegramChat::class, BotCommandEentitie::class, TelegramFrom::class,
|
NotificationItem::class,
|
||||||
WeatherForcast::class, Location::class, Current::class, Forecast::class, Condition::class, Forecastday::class, Day::class, Astro::class, Hour::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,
|
LocationLog::class,
|
||||||
LastInfo::class, HistoryItem::class, ReaderConfig::class, ContentsCollection::class, ContentsPageInfo::class,
|
LastInfo::class, HistoryItem::class, ReaderConfig::class, ContentsCollection::class, ContentsPageInfo::class,
|
||||||
WidgetData::class,AppUsageLog::class,
|
AppUsageLog::class,
|
||||||
|
WidgetData::class,
|
||||||
)
|
)
|
||||||
//,UserActionModel::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_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/eight"
|
android:layout_marginTop="@dimen/eight"
|
||||||
app:layout_constraintTop_toBottomOf="@id/totalTouch"
|
app:layout_constraintTop_toBottomOf="@id/recommend"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"/>
|
app:layout_constraintStart_toStartOf="parent"/>
|
||||||
<TextView
|
<TextView
|
||||||
|
|||||||
@ -8,112 +8,21 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
>
|
>
|
||||||
|
|
||||||
<bums.lunatic.launcher.home.GeckoWeb
|
<bums.lunatic.launcher.view.LunaticBrowserLayout
|
||||||
android:id="@+id/menu_web"
|
android:id="@+id/lunaticBrowser"
|
||||||
android:layout_margin="5dp"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="match_parent" />
|
||||||
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"
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
<bums.lunatic.launcher.home.tokiz.view.PagedTextLayout
|
<bums.lunatic.launcher.home.tokiz.view.PagedTextLayout
|
||||||
android:id="@+id/paged_layer"
|
android:id="@+id/paged_layer"
|
||||||
android:layout_margin="1dp"
|
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:elevation="5dp"
|
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="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" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|||||||
@ -3,13 +3,13 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:fillViewport="true"
|
android:fillViewport="true"
|
||||||
android:background="#E6121212">
|
android:background="?android:attr/windowBackground">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="32dp">
|
android:padding="12dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -25,8 +25,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:background="#1AFFFFFF"
|
android:background="#1fff"
|
||||||
android:padding="12dp"
|
android:padding="6dp"
|
||||||
android:layout_marginBottom="24dp">
|
android:layout_marginBottom="24dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@ -42,7 +42,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:layout_marginBottom="12dp"
|
android:layout_marginBottom="12dp"
|
||||||
android:weightSum="3">
|
android:weightSum="5">
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/btnQuickWifi"
|
android:id="@+id/btnQuickWifi"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@ -76,6 +76,28 @@
|
|||||||
android:text="🔔 소리"
|
android:text="🔔 소리"
|
||||||
android:textColor="#FFFFFF"
|
android:textColor="#FFFFFF"
|
||||||
android:textStyle="bold" />
|
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>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:id="@+id/mainFragmentsContainer"
|
|
||||||
>
|
>
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/widget_container"
|
android:id="@+id/widget_container"
|
||||||
|
|||||||
@ -6,109 +6,75 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<ImageButton
|
<LinearLayout
|
||||||
app:layout_constraintTop_toTopOf="@id/home"
|
android:id="@+id/layout_tabs"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
android:layout_width="match_parent"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
android:layout_height="45dp"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/home"
|
android:orientation="horizontal"
|
||||||
android:id="@+id/search"
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
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" />
|
|
||||||
|
|
||||||
<ImageButton
|
<TextView
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
style="@style/MaterialIconButtonStyle"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
android:id="@+id/tab_all"
|
||||||
android:id="@+id/vote"
|
android:layout_width="0dp"
|
||||||
android:scaleType="fitCenter"
|
android:layout_height="match_parent"
|
||||||
android:adjustViewBounds="true"
|
android:layout_weight="1"
|
||||||
android:visibility="visible"
|
android:text="dynamic_feed"
|
||||||
android:background="@null"
|
android:gravity="center"
|
||||||
android:src="@drawable/saved"
|
android:textColor="@color/white"
|
||||||
android:layout_width="@dimen/main_top_height"
|
android:background="?attr/selectableItemBackground"/>
|
||||||
tools:ignore="ContentDescription"
|
|
||||||
android:layout_height="@dimen/main_top_height" />
|
|
||||||
|
|
||||||
<ImageButton
|
<TextView
|
||||||
android:id="@+id/hide"
|
style="@style/MaterialIconButtonStyle"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
android:id="@+id/tab_voted"
|
||||||
app:layout_constraintRight_toLeftOf="@id/vote"
|
android:layout_width="0dp"
|
||||||
android:src="@drawable/ic_delete"
|
android:layout_height="match_parent"
|
||||||
android:tintMode="multiply"
|
android:layout_weight="1"
|
||||||
android:layout_marginLeft="12dp"
|
android:text="grade"
|
||||||
android:scaleType="fitCenter"
|
android:gravity="center"
|
||||||
android:background="@null"
|
android:textColor="@color/white"
|
||||||
android:layout_width="@dimen/main_top_height"
|
android:background="?attr/selectableItemBackground"/>
|
||||||
android:visibility="visible"
|
|
||||||
android:adjustViewBounds="true"
|
|
||||||
tools:ignore="ContentDescription"
|
|
||||||
android:layout_height="@dimen/main_top_height"
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
<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
|
<TextView
|
||||||
android:id="@+id/home"
|
style="@style/MaterialIconButtonStyle"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
android:id="@+id/tab_serarch"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
android:layout_width="0dp"
|
||||||
android:src="@drawable/home"
|
android:layout_height="match_parent"
|
||||||
android:tintMode="multiply"
|
android:layout_weight="1"
|
||||||
android:scaleType="fitCenter"
|
android:text="search"
|
||||||
android:background="@null"
|
android:gravity="center"
|
||||||
android:layout_width="@dimen/main_top_height"
|
android:textColor="@color/finestSilver"
|
||||||
android:adjustViewBounds="true"
|
android:background="?attr/selectableItemBackground"/>
|
||||||
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"/>
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
|
||||||
android:layout_margin="@dimen/default_layout_margin"
|
|
||||||
android:id="@+id/infoList"
|
android:id="@+id/infoList"
|
||||||
android:layout_width="match_parent"
|
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:overScrollMode="never"
|
||||||
android:padding="@dimen/default_padding"
|
android:scrollbars="none"/>
|
||||||
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"
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
<bums.lunatic.launcher.view.LunaticBrowserLayout
|
||||||
<bums.lunatic.launcher.home.GeckoWeb
|
android:id="@+id/lunaticBrowser"
|
||||||
android:id="@+id/geckoWeb"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
android:layout_height="match_parent"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
android:visibility="gone" />
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/progressBar"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<include layout="@layout/layout_rss_summary"
|
<include layout="@layout/layout_rss_summary"
|
||||||
android:id="@+id/layout_rss_summary"
|
android:id="@+id/layout_rss_summary"
|
||||||
@ -117,21 +83,8 @@
|
|||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/bookmark"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
android:layout_height="0dp"/>
|
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>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</layout>
|
</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>
|
</LinearLayout>
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/current_chapter"
|
android:id="@+id/current_chapter"
|
||||||
|
android:layout_marginLeft="30dp"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
<TextView
|
<TextView
|
||||||
|
android:layout_marginRight="30dp"
|
||||||
android:id="@+id/current_page"
|
android:id="@+id/current_page"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
|||||||
@ -3,198 +3,95 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:padding="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:id="@+id/mainFragmentsContainer"
|
|
||||||
>
|
>
|
||||||
<LinearLayout
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/fragment_layer"
|
android:id="@+id/fragment_container"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:clipChildren="false"
|
android:clipChildren="false"
|
||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
|
android:layout_marginBottom="15dp"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="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"/>
|
|
||||||
|
|
||||||
<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
|
<bums.lunatic.launcher.view.FloatingActionMenu
|
||||||
android:id="@+id/floating_action_menu"
|
android:id="@+id/floating_action_menu"
|
||||||
android:layout_margin="5dp"
|
|
||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
app:menu_colorNormal="#80FF0000"
|
app:menu_colorNormal="#2550"
|
||||||
app:menu_fab_size="mini"
|
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:menu_labels_position="center"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:menu_shadowColor="@color/finestSilver"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:menu_showShadow="true"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
>
|
>
|
||||||
<bums.lunatic.launcher.view.FloatingActionButton
|
<bums.lunatic.launcher.view.FloatingActionButton
|
||||||
app:fab_label="feeds"
|
app:fab_label="📰"
|
||||||
android:id="@+id/feeds"
|
style="@style/CommonFabStyle"
|
||||||
app:fab_showShadow="true"
|
android:id="@+id/feeds"/>
|
||||||
app:fab_size="mini"
|
|
||||||
android:onClick="floatClick"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="20dp"/>
|
|
||||||
|
|
||||||
<bums.lunatic.launcher.view.FloatingActionButton
|
<bums.lunatic.launcher.view.FloatingActionButton
|
||||||
app:fab_label="booktoki"
|
app:fab_label="📚"
|
||||||
android:id="@+id/books"
|
style="@style/CommonFabStyle"
|
||||||
app:fab_showShadow="true"
|
android:id="@+id/books"/>
|
||||||
app:fab_size="mini"
|
|
||||||
android:onClick="floatClick"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="20dp"/>
|
|
||||||
<bums.lunatic.launcher.view.FloatingActionButton
|
<bums.lunatic.launcher.view.FloatingActionButton
|
||||||
app:fab_label="newtoki"
|
app:fab_label="🎨"
|
||||||
android:id="@+id/webtoons"
|
style="@style/CommonFabStyle"
|
||||||
app:fab_showShadow="true"
|
android:id="@+id/webtoons"/>
|
||||||
app:fab_size="mini"
|
|
||||||
android:onClick="floatClick"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="20dp"/>
|
|
||||||
<bums.lunatic.launcher.view.FloatingActionButton
|
<bums.lunatic.launcher.view.FloatingActionButton
|
||||||
app:fab_label="manatoki"
|
app:fab_label="🗯️"
|
||||||
android:id="@+id/comics"
|
style="@style/CommonFabStyle"
|
||||||
app:fab_showShadow="true"
|
android:id="@+id/comics"/>
|
||||||
app:fab_size="mini"
|
|
||||||
android:onClick="floatClick"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="20dp"/>
|
|
||||||
<bums.lunatic.launcher.view.FloatingActionButton
|
<bums.lunatic.launcher.view.FloatingActionButton
|
||||||
app:fab_label="youtube"
|
app:fab_label="📺"
|
||||||
android:id="@+id/youtube"
|
style="@style/CommonFabStyle"
|
||||||
app:fab_showShadow="true"
|
android:id="@+id/youtube"/>
|
||||||
app:fab_size="mini"
|
|
||||||
android:onClick="floatClick"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="20dp"/>
|
|
||||||
<bums.lunatic.launcher.view.FloatingActionButton
|
<bums.lunatic.launcher.view.FloatingActionButton
|
||||||
app:fab_label="perplexity"
|
app:fab_label="🤖"
|
||||||
android:id="@+id/perplexity"
|
style="@style/CommonFabStyle"
|
||||||
app:fab_showShadow="true"
|
android:id="@+id/perplexity"/>
|
||||||
app:fab_size="mini"
|
|
||||||
android:onClick="floatClick"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="20dp"/>
|
|
||||||
<bums.lunatic.launcher.view.FloatingActionButton
|
<bums.lunatic.launcher.view.FloatingActionButton
|
||||||
app:fab_label="짤방"
|
app:fab_label="😂"
|
||||||
android:id="@+id/zzalbang"
|
style="@style/CommonFabStyle"
|
||||||
app:fab_showShadow="true"
|
android:id="@+id/zzalbang"/>
|
||||||
app:fab_size="mini"
|
|
||||||
android:onClick="floatClick"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="20dp"/>
|
|
||||||
|
|
||||||
<bums.lunatic.launcher.view.FloatingActionButton
|
<bums.lunatic.launcher.view.FloatingActionButton
|
||||||
app:fab_label="X"
|
app:fab_label="🐦"
|
||||||
android:id="@+id/btn_x"
|
style="@style/CommonFabStyle"
|
||||||
app:fab_showShadow="true"
|
android:id="@+id/btn_x"/>
|
||||||
app:fab_size="mini"
|
|
||||||
android:onClick="floatClick"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="20dp"/>
|
|
||||||
|
|
||||||
<bums.lunatic.launcher.view.FloatingActionButton
|
<bums.lunatic.launcher.view.FloatingActionButton
|
||||||
app:fab_label="I"
|
app:fab_label="🔞"
|
||||||
android:id="@+id/btn_i"
|
style="@style/CommonFabStyle"
|
||||||
app:fab_showShadow="true"
|
android:id="@+id/btn_i"/>
|
||||||
app:fab_size="mini"
|
|
||||||
android:onClick="floatClick"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="20dp"/>
|
|
||||||
|
|
||||||
<bums.lunatic.launcher.view.FloatingActionButton
|
<bums.lunatic.launcher.view.FloatingActionButton
|
||||||
app:fab_label="torrent"
|
app:fab_label="🧲"
|
||||||
|
style="@style/CommonFabStyle"
|
||||||
android:id="@+id/btn_torrent"
|
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
|
<bums.lunatic.launcher.view.FloatingActionButton
|
||||||
app:fab_label="system"
|
app:fab_label="📊"
|
||||||
|
style="@style/CommonFabStyle"
|
||||||
android:id="@+id/btn_info"
|
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
|
<bums.lunatic.launcher.view.FloatingActionButton
|
||||||
app:fab_label="close"
|
style="@style/CommonFabStyle"
|
||||||
android:id="@+id/close"
|
app:fab_label="❌"
|
||||||
app:fab_showShadow="true"
|
android:id="@+id/close"/>
|
||||||
app:fab_size="mini"
|
|
||||||
android:onClick="floatClick"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="20dp"/>
|
|
||||||
|
|
||||||
</bums.lunatic.launcher.view.FloatingActionMenu>
|
</bums.lunatic.launcher.view.FloatingActionMenu>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|||||||
@ -29,107 +29,127 @@
|
|||||||
android:background="@drawable/rounded_bg_top"
|
android:background="@drawable/rounded_bg_top"
|
||||||
android:backgroundTint="@color/black"
|
android:backgroundTint="@color/black"
|
||||||
android:paddingHorizontal="@dimen/thirtySix"
|
android:paddingHorizontal="@dimen/thirtySix"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toTopOf="@id/version"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/appBar">
|
app:layout_constraintTop_toBottomOf="@id/appBar">
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButtonToggleGroup
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
app:singleSelection="true"
|
android:paddingTop="@dimen/twelve"
|
||||||
android:layout_gravity="center">
|
android:paddingBottom="@dimen/thirtySix">
|
||||||
|
|
||||||
<!-- <com.google.android.material.button.MaterialButton-->
|
<com.google.android.material.button.MaterialButtonToggleGroup
|
||||||
<!-- android:id="@+id/timeDate"-->
|
android:layout_width="match_parent"
|
||||||
<!-- style="@style/Widget.Material3.Button.ElevatedButton"-->
|
android:layout_height="wrap_content"
|
||||||
<!-- android:layout_width="match_parent"-->
|
android:orientation="vertical"
|
||||||
<!-- android:layout_height="wrap_content"-->
|
app:singleSelection="true">
|
||||||
<!-- android:text="Display Info"-->
|
|
||||||
<!-- android:textAllCaps="true"-->
|
|
||||||
<!-- android:textStyle="bold" />-->
|
|
||||||
|
|
||||||
<!-- <com.google.android.material.button.MaterialButton-->
|
<com.google.android.material.button.MaterialButton
|
||||||
<!-- android:id="@+id/weather"-->
|
android:id="@+id/todo"
|
||||||
<!-- style="@style/Widget.Material3.Button.ElevatedButton"-->
|
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||||
<!-- android:layout_width="match_parent"-->
|
android:layout_width="match_parent"
|
||||||
<!-- android:layout_height="wrap_content"-->
|
android:layout_height="wrap_content"
|
||||||
<!-- android:text="@string/weather"-->
|
android:text="@string/home"
|
||||||
<!-- android:textAllCaps="true"-->
|
android:textAllCaps="true"
|
||||||
<!-- android:textStyle="bold" />-->
|
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
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/todo"
|
android:id="@+id/btnGoogleLogin"
|
||||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/home"
|
android:text="구글 드라이브 연결"
|
||||||
android:textAllCaps="true"
|
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/apps"
|
android:id="@+id/btnManualBackup"
|
||||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/app_drawer"
|
android:enabled="false"
|
||||||
android:textAllCaps="true"
|
android:text="지금 데이터 백업하기"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<!-- <com.google.android.material.button.MaterialButton-->
|
<androidx.appcompat.widget.SwitchCompat
|
||||||
<!-- android:id="@+id/appearances"-->
|
android:id="@+id/switchAutoBackup"
|
||||||
<!-- 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"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/misc"
|
android:layout_marginTop="8dp"
|
||||||
android:textAllCaps="true"
|
android:layout_marginBottom="8dp"
|
||||||
android:textStyle="bold" />
|
android:enabled="false"
|
||||||
|
android:text="자동 백업 허용 (주 1회)"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:switchPadding="16dp" />
|
||||||
|
|
||||||
<!-- <com.google.android.material.button.MaterialButton-->
|
</LinearLayout>
|
||||||
<!-- 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>
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/version"
|
android:id="@+id/version"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/twelve"
|
||||||
android:layout_marginBottom="@dimen/twelve"
|
android:layout_marginBottom="@dimen/twelve"
|
||||||
|
android:textColor="#88FFFFFF"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@ -5,6 +5,11 @@
|
|||||||
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_launcher</item>
|
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_launcher</item>
|
||||||
<item name="windowSplashScreenAnimationDuration">1000</item>
|
<item name="windowSplashScreenAnimationDuration">1000</item>
|
||||||
<item name="postSplashScreenTheme">@style/Theme.LunarLauncher</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>
|
||||||
|
|
||||||
<style name="Slider" parent="Widget.Material3.Slider">
|
<style name="Slider" parent="Widget.Material3.Slider">
|
||||||
@ -139,4 +144,37 @@
|
|||||||
<item name="android:layout_height">@dimen/main_top_height</item>
|
<item name="android:layout_height">@dimen/main_top_height</item>
|
||||||
</style>
|
</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>
|
</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()
|
gradlePluginPortal()
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
// jcenter()
|
|
||||||
maven (url = "https://maven.mozilla.org/maven2/")
|
maven (url = "https://maven.mozilla.org/maven2/")
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,8 +17,10 @@ dependencyResolutionManagement {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven (url = "https://maven.mozilla.org/maven2/")
|
maven (url = "https://maven.mozilla.org/maven2/")
|
||||||
maven(url = "https://jitpack.io")
|
maven(url = "https://jitpack.io")
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = "LunarLauncher"
|
rootProject.name = "LunarLauncher"
|
||||||
include ("app","library","utils")
|
include ("app","library","utils")
|
||||||
|
include(":gdrive")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user