..
This commit is contained in:
parent
578f3f96ad
commit
dc6b95108f
@ -153,16 +153,90 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < 100 ; i++) {
|
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를 가져오는 함수
|
// 재귀적으로 모든 사진 ID를 가져오는 함수
|
||||||
async function fetchAllPhotos(offset) {
|
async function fetchAllPhotos(offset) {
|
||||||
@ -220,6 +294,91 @@
|
|||||||
};
|
};
|
||||||
}, 1500); // transition 시간과 동일하게 설정
|
}, 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);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package bums.lunatic.launcher
|
package bums.lunatic.launcher
|
||||||
|
|
||||||
//import kr.lunaticbum.utils.ui.DisplayUtil
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
import android.app.SearchManager
|
import android.app.SearchManager
|
||||||
|
import android.appwidget.AppWidgetHost
|
||||||
|
import android.appwidget.AppWidgetHostView
|
||||||
|
import android.appwidget.AppWidgetManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
@ -42,6 +28,7 @@ import android.view.MotionEvent
|
|||||||
import android.view.PointerIcon
|
import android.view.PointerIcon
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import android.widget.FrameLayout
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.net.toUri
|
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.LauncherActivity.Companion.lActivity
|
||||||
import bums.lunatic.launcher.common.CommonActivity
|
import bums.lunatic.launcher.common.CommonActivity
|
||||||
import bums.lunatic.launcher.databinding.LauncherActivityBinding
|
import bums.lunatic.launcher.databinding.LauncherActivityBinding
|
||||||
|
import bums.lunatic.launcher.feeds.WidgetHost
|
||||||
import bums.lunatic.launcher.helpers.ForeGroundService
|
import bums.lunatic.launcher.helpers.ForeGroundService
|
||||||
import bums.lunatic.launcher.helpers.HeadsetActionButtonReceiver
|
import bums.lunatic.launcher.helpers.HeadsetActionButtonReceiver
|
||||||
import bums.lunatic.launcher.home.GeckoWeb
|
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.home.adapters.BookmarkPagerFragment
|
||||||
import bums.lunatic.launcher.model.RssData
|
import bums.lunatic.launcher.model.RssData
|
||||||
import bums.lunatic.launcher.model.RssDataType
|
import bums.lunatic.launcher.model.RssDataType
|
||||||
|
import bums.lunatic.launcher.model.WidgetData
|
||||||
import bums.lunatic.launcher.receiver.NLService
|
import bums.lunatic.launcher.receiver.NLService
|
||||||
import bums.lunatic.launcher.settings.SettingsActivity
|
import bums.lunatic.launcher.settings.SettingsActivity
|
||||||
import bums.lunatic.launcher.tokiz.Comics
|
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.ffmpeg.FFmpeg
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
import com.yausername.youtubedl_android.YoutubeDLException
|
import com.yausername.youtubedl_android.YoutubeDLException
|
||||||
|
import io.realm.kotlin.UpdatePolicy
|
||||||
import io.realm.kotlin.ext.query
|
import io.realm.kotlin.ext.query
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -396,7 +386,40 @@ open class LauncherActivity : CommonActivity() {
|
|||||||
.commit()
|
.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<WidgetData>("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")
|
@SuppressLint("NewApi", "MissingPermission", "ClickableViewAccessibility")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@ -434,7 +457,7 @@ open class LauncherActivity : CommonActivity() {
|
|||||||
updateLocationService()
|
updateLocationService()
|
||||||
|
|
||||||
|
|
||||||
showContents(binding.feeds.id)
|
// showContents(binding.feeds.id)
|
||||||
binding.floatingActionMenu.setOnTouchListener { v: View, e: MotionEvent ->
|
binding.floatingActionMenu.setOnTouchListener { v: View, e: MotionEvent ->
|
||||||
if (binding.floatingActionMenu.isOpened) {
|
if (binding.floatingActionMenu.isOpened) {
|
||||||
binding.floatingActionMenu.close(true)
|
binding.floatingActionMenu.close(true)
|
||||||
@ -464,9 +487,268 @@ open class LauncherActivity : CommonActivity() {
|
|||||||
startActivity(shareIntent)
|
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<WidgetData>().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) {
|
fun showContents(id : Int) {
|
||||||
|
binding.fragmentContainer.visibility = View.VISIBLE
|
||||||
when(id) {
|
when(id) {
|
||||||
R.id.feeds -> {
|
R.id.feeds -> {
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager.beginTransaction()
|
||||||
@ -533,23 +815,26 @@ open class LauncherActivity : CommonActivity() {
|
|||||||
return super.onTouchEvent(event)
|
return super.onTouchEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
appWidgetHost?.startListening() // [필수] 여기서 리스닝 시작
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
appWidgetHost?.stopListening() // [필수] 여기서 리스닝 중지 (onDestroy 대신 여기 추천)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
sRuntime?.shutdown()
|
sRuntime?.shutdown()
|
||||||
sRuntime = null
|
sRuntime = null
|
||||||
} catch (e: Exception) {e.printStackTrace()
|
} catch (e: Exception) { e.printStackTrace() }
|
||||||
}
|
|
||||||
|
|
||||||
|
// appWidgetHost?.stopListening() // 이 줄은 제거하고 onStop으로 이동
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super.onStart()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O_MR1)
|
@RequiresApi(Build.VERSION_CODES.O_MR1)
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|||||||
10
app/src/main/kotlin/bums/lunatic/launcher/apps/test.java
Normal file
10
app/src/main/kotlin/bums/lunatic/launcher/apps/test.java
Normal file
@ -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;
|
||||||
@ -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
|
||||||
|
}
|
||||||
@ -25,6 +25,7 @@ import bums.lunatic.launcher.model.TelegramData
|
|||||||
import bums.lunatic.launcher.model.TelegramFrom
|
import bums.lunatic.launcher.model.TelegramFrom
|
||||||
import bums.lunatic.launcher.model.TelegramMessage
|
import bums.lunatic.launcher.model.TelegramMessage
|
||||||
import bums.lunatic.launcher.model.WeatherForcast
|
import bums.lunatic.launcher.model.WeatherForcast
|
||||||
|
import bums.lunatic.launcher.model.WidgetData
|
||||||
import bums.lunatic.launcher.tokiz.ContentsCollection
|
import bums.lunatic.launcher.tokiz.ContentsCollection
|
||||||
import bums.lunatic.launcher.tokiz.ContentsPageInfo
|
import bums.lunatic.launcher.tokiz.ContentsPageInfo
|
||||||
import bums.lunatic.launcher.tokiz.HistoryItem
|
import bums.lunatic.launcher.tokiz.HistoryItem
|
||||||
@ -62,7 +63,7 @@ object WorkersDb {
|
|||||||
TelegramBotUpdate::class, TelegramData::class, TelegramMessage::class, TelegramChat::class, BotCommandEentitie::class, TelegramFrom::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,
|
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
|
||||||
)
|
)
|
||||||
//,UserActionModel::class
|
//,UserActionModel::class
|
||||||
|
|
||||||
|
|||||||
@ -9,11 +9,34 @@
|
|||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:id="@+id/mainFragmentsContainer"
|
android:id="@+id/mainFragmentsContainer"
|
||||||
>
|
>
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/widget_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipChildren="false"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/delete_zone"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="삭제하려면 여기에 놓으세요"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:background="#99FF0000"
|
||||||
|
android:padding="20dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:layout_marginTop="50dp"/>
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/fragment_container"
|
android:id="@+id/fragment_container"
|
||||||
android:visibility="visible"
|
android:visibility="gone"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
|||||||
@ -132,5 +132,6 @@
|
|||||||
android:progress="0"
|
android:progress="0"
|
||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
android:indeterminate="false"/>
|
android:indeterminate="false"/>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</layout>
|
</layout>
|
||||||
Loading…
x
Reference in New Issue
Block a user