..
This commit is contained in:
parent
578f3f96ad
commit
dc6b95108f
@ -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);
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</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
|
||||
|
||||
//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<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")
|
||||
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<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) {
|
||||
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()
|
||||
|
||||
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.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
|
||||
|
||||
|
||||
@ -9,11 +9,34 @@
|
||||
android:orientation="vertical"
|
||||
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
|
||||
android:id="@+id/fragment_container"
|
||||
android:visibility="visible"
|
||||
android:visibility="gone"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
||||
@ -132,5 +132,6 @@
|
||||
android:progress="0"
|
||||
android:visibility="visible"
|
||||
android:indeterminate="false"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
||||
Loading…
x
Reference in New Issue
Block a user