...
This commit is contained in:
parent
dc19ff5339
commit
8c3bc96417
@ -65,6 +65,7 @@ import androidx.work.NetworkType
|
|||||||
import androidx.work.workDataOf
|
import androidx.work.workDataOf
|
||||||
import bums.lunatic.launcher.home.GeckoWeb.Companion.currentCookieString
|
import bums.lunatic.launcher.home.GeckoWeb.Companion.currentCookieString
|
||||||
import bums.lunatic.launcher.home.GeckoWeb.Companion.currentCookieUrlString
|
import bums.lunatic.launcher.home.GeckoWeb.Companion.currentCookieUrlString
|
||||||
|
import bums.lunatic.launcher.model.QuoteItem
|
||||||
import bums.lunatic.launcher.model.WallContentGroup
|
import bums.lunatic.launcher.model.WallContentGroup
|
||||||
import bums.lunatic.launcher.workers.WorkersDb
|
import bums.lunatic.launcher.workers.WorkersDb
|
||||||
import com.google.common.reflect.TypeToken
|
import com.google.common.reflect.TypeToken
|
||||||
@ -77,6 +78,87 @@ import okhttp3.RequestBody
|
|||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
|
||||||
|
data class ZenQuoteResponse(val q: String, val a: String)
|
||||||
|
data class KorAdviceResponse(val author: String, val message: String)
|
||||||
|
|
||||||
|
class QuoteFetchWorker(private val context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
|
||||||
|
|
||||||
|
override suspend fun doWork(): Result {
|
||||||
|
return try {
|
||||||
|
fetchAndSaveQuotes()
|
||||||
|
Result.success()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Blog.LOGE("QuoteFetchWorker Failed", e)
|
||||||
|
Result.retry() // 실패 시 재시도
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchAndSaveQuotes() {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
Blog.LOGE("fetchAndSaveQuotes")
|
||||||
|
val client = OkHttpClient.Builder()
|
||||||
|
.connectTimeout(15, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(15, TimeUnit.SECONDS)
|
||||||
|
.build()
|
||||||
|
val gson = Gson()
|
||||||
|
val realm = WorkersDb.getRealm()
|
||||||
|
|
||||||
|
// 5회 반복 호출
|
||||||
|
for (i in 1..5) {
|
||||||
|
try {
|
||||||
|
// 1. ZenQuotes (영어 명언) 호출 - 배열 형태로 응답옴
|
||||||
|
val zenRequest = Request.Builder().url("https://zenquotes.io/api/random").build()
|
||||||
|
val zenResponse = client.newCall(zenRequest).execute()
|
||||||
|
|
||||||
|
if (zenResponse.isSuccessful) {
|
||||||
|
zenResponse.body?.string()?.let { json ->
|
||||||
|
val listType = object : TypeToken<List<ZenQuoteResponse>>() {}.type
|
||||||
|
val zenList: List<ZenQuoteResponse> = gson.fromJson(json, listType)
|
||||||
|
if (zenList.isNotEmpty()) {
|
||||||
|
val quote = zenList[0]
|
||||||
|
realm.writeBlocking {
|
||||||
|
copyToRealm(QuoteItem().apply {
|
||||||
|
textEn = quote.q
|
||||||
|
authorEn = quote.a
|
||||||
|
sourceApi = "ZENQUOTES"
|
||||||
|
isTranslated = false
|
||||||
|
timestamp = System.currentTimeMillis()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Korean Advice (한국어 명언) 호출 - 객체 형태로 응답옴
|
||||||
|
val korRequest = Request.Builder().url("https://korean-advice-open-api.vercel.app/api/advice").build()
|
||||||
|
val korResponse = client.newCall(korRequest).execute()
|
||||||
|
|
||||||
|
if (korResponse.isSuccessful) {
|
||||||
|
korResponse.body?.string()?.let { json ->
|
||||||
|
val korQuote = gson.fromJson(json, KorAdviceResponse::class.java)
|
||||||
|
realm.writeBlocking {
|
||||||
|
copyToRealm(QuoteItem().apply {
|
||||||
|
textKo = korQuote.message
|
||||||
|
authorKo = korQuote.author
|
||||||
|
sourceApi = "KOR_ADVICE"
|
||||||
|
isTranslated = false
|
||||||
|
timestamp = System.currentTimeMillis()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 무료 API Rate Limit(호출 제한) 방어를 위해 1.5초 대기
|
||||||
|
delay(1500)
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Blog.LOGE("Quote Fetch Iteration $i Failed", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class WallpaperAutoChangeWorker(private val context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
|
class WallpaperAutoChangeWorker(private val context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
val folderPath = inputData.getString("FOLDER_PATH") ?: return Result.failure()
|
val folderPath = inputData.getString("FOLDER_PATH") ?: return Result.failure()
|
||||||
@ -652,6 +734,22 @@ class ForeGroundService : Service() {
|
|||||||
wallpaperRequest
|
wallpaperRequest
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val quoteRequest = PeriodicWorkRequestBuilder<QuoteFetchWorker>(
|
||||||
|
1, TimeUnit.HOURS // 1시간 간격 설정
|
||||||
|
)
|
||||||
|
.setConstraints(
|
||||||
|
Constraints.Builder()
|
||||||
|
.setRequiredNetworkType(NetworkType.CONNECTED) // 네트워크가 연결된 상태에서만 실행
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
workManager.enqueueUniquePeriodicWork(
|
||||||
|
"QuoteFetchWork",
|
||||||
|
ExistingPeriodicWorkPolicy.KEEP,
|
||||||
|
quoteRequest
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
19
app/src/main/kotlin/bums/lunatic/launcher/model/QuoteItem.kt
Normal file
19
app/src/main/kotlin/bums/lunatic/launcher/model/QuoteItem.kt
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package bums.lunatic.launcher.model
|
||||||
|
|
||||||
|
import io.realm.kotlin.types.RealmObject
|
||||||
|
import io.realm.kotlin.types.annotations.PrimaryKey
|
||||||
|
import org.mongodb.kbson.ObjectId
|
||||||
|
|
||||||
|
class QuoteItem : RealmObject {
|
||||||
|
@PrimaryKey
|
||||||
|
var _id: ObjectId = ObjectId()
|
||||||
|
|
||||||
|
var textEn: String = "" // 영문 텍스트
|
||||||
|
var textKo: String = "" // 한글 텍스트
|
||||||
|
var authorEn: String = "" // 영문 작가명
|
||||||
|
var authorKo: String = "" // 한글 작가명
|
||||||
|
|
||||||
|
var sourceApi: String = "" // "ZENQUOTES" 또는 "KOR_ADVICE"
|
||||||
|
var isTranslated: Boolean = false // AI를 통해 양방향 번역이 완료되었는지 여부
|
||||||
|
var timestamp: Long = System.currentTimeMillis()
|
||||||
|
}
|
||||||
@ -35,12 +35,14 @@ import bums.lunatic.launcher.helpers.Constants.Companion.PREFS_SETTINGS
|
|||||||
import bums.lunatic.launcher.helpers.PrefBoolean
|
import bums.lunatic.launcher.helpers.PrefBoolean
|
||||||
import bums.lunatic.launcher.helpers.PrefHelper
|
import bums.lunatic.launcher.helpers.PrefHelper
|
||||||
import bums.lunatic.launcher.model.AppInfo
|
import bums.lunatic.launcher.model.AppInfo
|
||||||
|
import bums.lunatic.launcher.model.QuoteItem
|
||||||
import bums.lunatic.launcher.model.SimpleContact
|
import bums.lunatic.launcher.model.SimpleContact
|
||||||
import bums.lunatic.launcher.settings.childs.Apps
|
import bums.lunatic.launcher.settings.childs.Apps
|
||||||
import bums.lunatic.launcher.settings.childs.HomeSettings
|
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 bums.lunatic.launcher.workers.WorkersDb.getRealm
|
||||||
import kr.gdrive.bums.lunatic.utils.BackupPayload
|
import kr.gdrive.bums.lunatic.utils.BackupPayload
|
||||||
import kr.gdrive.bums.lunatic.utils.GDriveBackupTask
|
import kr.gdrive.bums.lunatic.utils.GDriveBackupTask
|
||||||
import kr.gdrive.bums.lunatic.utils.GDriveLoginManager
|
import kr.gdrive.bums.lunatic.utils.GDriveLoginManager
|
||||||
@ -175,6 +177,14 @@ internal class SettingsActivity : CommonActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getUntranslatedQuotesAsJson(): String {
|
||||||
|
val realm = getRealm()
|
||||||
|
val untranslatedList = realm.query<QuoteItem>("isTranslated == false").find()
|
||||||
|
|
||||||
|
// 리스트를 통째로 Gson을 사용해 JSON String으로 변환 (Logcat이나 파일로 출력)
|
||||||
|
return Gson().toJson(untranslatedList)
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateUiForLoggedIn(email: String) {
|
private fun updateUiForLoggedIn(email: String) {
|
||||||
binding.btnGoogleLogin.text = "연결 해제 ($email)"
|
binding.btnGoogleLogin.text = "연결 해제 ($email)"
|
||||||
binding.btnManualBackup.isEnabled = true
|
binding.btnManualBackup.isEnabled = true
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import android.os.Handler
|
|||||||
import android.os.HandlerThread
|
import android.os.HandlerThread
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import android.service.wallpaper.WallpaperService
|
import android.service.wallpaper.WallpaperService
|
||||||
|
import android.text.SpannableStringBuilder
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.SurfaceHolder
|
import android.view.SurfaceHolder
|
||||||
@ -35,10 +36,12 @@ import android.widget.LinearLayout
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.graphics.alpha
|
import androidx.core.graphics.alpha
|
||||||
import bums.lunatic.launcher.R
|
import bums.lunatic.launcher.R
|
||||||
|
import bums.lunatic.launcher.model.QuoteItem
|
||||||
import bums.lunatic.launcher.model.Translation
|
import bums.lunatic.launcher.model.Translation
|
||||||
import bums.lunatic.launcher.model.WallContentGroup
|
import bums.lunatic.launcher.model.WallContentGroup
|
||||||
import bums.lunatic.launcher.utils.Blog
|
import bums.lunatic.launcher.utils.Blog
|
||||||
import bums.lunatic.launcher.workers.LocationUpdateService
|
import bums.lunatic.launcher.workers.LocationUpdateService
|
||||||
|
import bums.lunatic.launcher.workers.WorkersDb
|
||||||
import com.google.common.reflect.TypeToken
|
import com.google.common.reflect.TypeToken
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -60,7 +63,7 @@ class MyWallpaperService : WallpaperService() {
|
|||||||
data class BatteryInfo(
|
data class BatteryInfo(
|
||||||
val percent: Int,
|
val percent: Int,
|
||||||
val isCharging: Boolean,
|
val isCharging: Boolean,
|
||||||
val speedText: String,
|
// val speedText: String,
|
||||||
val detailsText: String // "9.0V 1.5A (13.5W)" 같은 상세 정보
|
val detailsText: String // "9.0V 1.5A (13.5W)" 같은 상세 정보
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -76,16 +79,16 @@ class MyWallpaperService : WallpaperService() {
|
|||||||
val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
|
val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
|
||||||
status == BatteryManager.BATTERY_STATUS_FULL
|
status == BatteryManager.BATTERY_STATUS_FULL
|
||||||
|
|
||||||
var speedText = ""
|
// var speedText = ""
|
||||||
var detailsText = ""
|
var detailsText = ""
|
||||||
|
|
||||||
if (isCharging) {
|
if (isCharging) {
|
||||||
speedText = when (plugged) {
|
// speedText = when (plugged) {
|
||||||
BatteryManager.BATTERY_PLUGGED_AC -> "고속"
|
// BatteryManager.BATTERY_PLUGGED_AC -> "고속"
|
||||||
BatteryManager.BATTERY_PLUGGED_USB -> "저속"
|
// BatteryManager.BATTERY_PLUGGED_USB -> "저속"
|
||||||
BatteryManager.BATTERY_PLUGGED_WIRELESS -> "무선"
|
// BatteryManager.BATTERY_PLUGGED_WIRELESS -> "무선"
|
||||||
else -> ""
|
// else -> ""
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 1. 전압(Voltage) 가져오기: 기본 단위는 밀리볼트(mV)
|
// 1. 전압(Voltage) 가져오기: 기본 단위는 밀리볼트(mV)
|
||||||
val voltageMv = intent?.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0) ?: 0
|
val voltageMv = intent?.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0) ?: 0
|
||||||
@ -108,7 +111,7 @@ class MyWallpaperService : WallpaperService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return BatteryInfo(batteryPct, isCharging, speedText, detailsText)
|
return BatteryInfo(batteryPct, isCharging, detailsText)
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class NativeRenderEngine : Engine() {
|
inner class NativeRenderEngine : Engine() {
|
||||||
@ -405,40 +408,139 @@ class MyWallpaperService : WallpaperService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun drawBitmapFromText(weatherLines: List<String>, isCharging: Boolean = false): Bitmap? {
|
private fun getKoreanWeatherCondition(condition: String): String {
|
||||||
// 1. 데이터 준비
|
val lower = condition.trim().lowercase()
|
||||||
val dateFull = SimpleDateFormat("yyyy.MM.dd (E)", Locale.KOREAN).format(Date())
|
return when {
|
||||||
val ramInfo = getRamUsage()
|
lower == "sunny" || lower == "clear" -> "맑음"
|
||||||
val tempInfo = getDeviceTemperature()
|
lower == "partly cloudy" -> "구름 조금"
|
||||||
val batteryInfo = getBatteryStatus().let { info ->
|
lower == "cloudy" -> "구름 많음"
|
||||||
if (info.isCharging && info.detailsText.isNotEmpty()) {
|
lower == "overcast" -> "흐림"
|
||||||
"${info.percent}% ⚡${info.speedText} [${info.detailsText}]"
|
|
||||||
} else if (info.isCharging) {
|
|
||||||
"${info.percent}% ⚡${info.speedText}"
|
|
||||||
} else {
|
|
||||||
"${info.percent}%"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
lower.contains("mist") || lower.contains("fog") -> "안개"
|
||||||
|
lower.contains("blizzard") || lower.contains("blowing snow") -> "눈보라"
|
||||||
|
|
||||||
|
// 뇌우(번개) 계열
|
||||||
|
lower.contains("thunder") && lower.contains("snow") -> "천둥/눈"
|
||||||
|
lower.contains("thunder") && lower.contains("rain") -> "천둥/비"
|
||||||
|
lower.contains("thunder") -> "뇌우"
|
||||||
|
|
||||||
|
// 얼음 섞인 비/눈 계열
|
||||||
|
lower.contains("sleet") -> "진눈깨비"
|
||||||
|
lower.contains("ice pellets") -> "싸락눈"
|
||||||
|
|
||||||
|
// 비 계열 (우선순위: 폭우 -> 소나기 -> 강한 비 -> 이슬비 -> 비)
|
||||||
|
lower.contains("torrential") -> "폭우"
|
||||||
|
lower.contains("heavy") && lower.contains("shower") -> "강한 소나기"
|
||||||
|
lower.contains("shower") -> "소나기"
|
||||||
|
lower.contains("heavy rain") -> "강한 비"
|
||||||
|
lower.contains("drizzle") -> "이슬비"
|
||||||
|
lower.contains("rain") -> "비"
|
||||||
|
|
||||||
|
// 눈 계열 (우선순위: 폭설 -> 눈)
|
||||||
|
lower.contains("heavy snow") -> "폭설"
|
||||||
|
lower.contains("snow") -> "눈"
|
||||||
|
|
||||||
|
// 매핑되지 않은 기본값 (혹시 모를 예외 대비)
|
||||||
|
else -> condition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val fontz = arrayListOf<Int>(
|
||||||
|
R.font.cafe24ohsquareair,
|
||||||
|
R.font.cafe24oneprettynight,
|
||||||
|
R.font.cafe24ssukssukregular,
|
||||||
|
R.font.dovemayo,
|
||||||
|
R.font.ebs_r,
|
||||||
|
R.font.gabia_solmee,
|
||||||
|
R.font.godomaum,
|
||||||
|
R.font.jsarirang_hon,
|
||||||
|
R.font.jsarirang_ppuri,
|
||||||
|
R.font.jsdongkang_regular,
|
||||||
|
R.font.kcc_kimhoon,
|
||||||
|
R.font.kcc_sonkeechung,
|
||||||
|
R.font.kccahnjunggeun,
|
||||||
|
R.font.kotra_bold,
|
||||||
|
R.font.kotra_songeulssi,
|
||||||
|
R.font.kyobo_handwriting_2019,
|
||||||
|
R.font.kyobo_handwriting_2021sjy,
|
||||||
|
R.font.kyobohandwriting2024psw,
|
||||||
|
R.font.mapoagape,
|
||||||
|
R.font.mapobackpacking,
|
||||||
|
R.font.mapodacapo,
|
||||||
|
R.font.mapodpp,
|
||||||
|
R.font.mapodpp_2,
|
||||||
|
R.font.mapoflowerisland,
|
||||||
|
R.font.mapogoldenpier,
|
||||||
|
R.font.mapomaponaru,
|
||||||
|
R.font.mapopeacefull,
|
||||||
|
R.font.material_symbols,
|
||||||
|
R.font.nnsgc_brhp,
|
||||||
|
R.font.nnsgc_gd_an_gd,
|
||||||
|
R.font.nnsgc_md,
|
||||||
|
R.font.nnsgc_wsjidyp,
|
||||||
|
R.font.nnsgc_yjc,
|
||||||
|
R.font.on_jsuhl,
|
||||||
|
R.font.on_jsuhr,
|
||||||
|
R.font.on_sbsjl,
|
||||||
|
R.font.on_sbsjr,
|
||||||
|
R.font.on_treeususimgul,
|
||||||
|
R.font.on_treeususimgul_r,
|
||||||
|
R.font.on_wibsr,
|
||||||
|
R.font.on_wisbl,
|
||||||
|
R.font.on_ychyuhl,
|
||||||
|
R.font.on_ychyuhr,
|
||||||
|
R.font.ssshinb7,
|
||||||
|
R.font.taebaek_milkyway,
|
||||||
|
R.font.taefont_tsthlml,
|
||||||
|
R.font.tvn_jguiyg_light,
|
||||||
|
R.font.tvn_jguiyg_medium,
|
||||||
|
R.font.wandohoper,
|
||||||
|
R.font.ylee_mortal_heart_immortal_memory,
|
||||||
|
)
|
||||||
|
|
||||||
|
var monoBold = resources.getFont(fontz.random())
|
||||||
|
var quote = resources.getFont(fontz.random())
|
||||||
|
var local = resources.getFont(fontz.random())
|
||||||
|
var numbers = resources.getFont(fontz.random())
|
||||||
|
|
||||||
|
private fun drawBitmapFromText(weatherLines: List<String>, isCharging: Boolean = false): Bitmap? {
|
||||||
|
// 💡 1. 원의 크기와 내부 여백 계산
|
||||||
|
val displayMetrics = resources.displayMetrics
|
||||||
|
var sizeRate = 1.1f
|
||||||
|
val circleSize = ((displayMetrics.widthPixels * 0.48f).toInt() * sizeRate).toInt()
|
||||||
|
val padding = 5
|
||||||
|
val maxContentWidth = circleSize - (padding * 2)
|
||||||
|
|
||||||
|
if (circleSize <= 0) return null
|
||||||
|
|
||||||
|
// 💡 2. 텍스트 크기 3단계 변수 정의 (가독성과 감성을 모두 잡은 안정적인 비율)
|
||||||
|
val iconRate = 1.3f
|
||||||
|
val sizeLarge = 24f // 메인 날씨 상태
|
||||||
|
val sizeMedium = 15f // 날짜, 주소, 온도, 명언 본문
|
||||||
|
val sizeSmall = 13f // 시스템 정보, 명언 번역, 작가
|
||||||
|
|
||||||
|
// 3. 데이터 준비
|
||||||
|
val dateFull = SimpleDateFormat("yyyy.MM.dd(E)", Locale.ENGLISH).format(Date())
|
||||||
val weatherCondition = if (weatherLines.isNotEmpty()) weatherLines[0] else ""
|
val weatherCondition = if (weatherLines.isNotEmpty()) weatherLines[0] else ""
|
||||||
val weatherTemp = if (weatherLines.size > 1) weatherLines[1] else ""
|
val weatherTemp = if (weatherLines.size > 1) weatherLines[1] else ""
|
||||||
val weatherHum = if (weatherLines.size > 2) weatherLines[2] else ""
|
val weatherHum = if (weatherLines.size > 2) weatherLines[2] else ""
|
||||||
val address = if (weatherLines.size > 3) weatherLines[3] else ""
|
val address = if (weatherLines.size > 3) weatherLines[3] else ""
|
||||||
|
|
||||||
val wrapContent = ViewGroup.LayoutParams.WRAP_CONTENT
|
val wrapContent = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
val monoBold = Typeface.create(Typeface.MONOSPACE, Typeface.BOLD)
|
|
||||||
|
|
||||||
// 2. 뷰 생성 헬퍼
|
|
||||||
fun createTextView(textToSet: String, textSizeDp: Float, font: Typeface, alphaValue: Float = 1.0f): TextView {
|
|
||||||
|
// 4. 뷰 생성 헬퍼
|
||||||
|
fun createTextView(textToSet: CharSequence, textSizeDp: Float, font: Typeface, alphaValue: Float = 1.0f): TextView {
|
||||||
return TextView(this@MyWallpaperService).apply {
|
return TextView(this@MyWallpaperService).apply {
|
||||||
layoutParams = LinearLayout.LayoutParams(wrapContent, wrapContent)
|
layoutParams = LinearLayout.LayoutParams(wrapContent, wrapContent)
|
||||||
|
maxWidth = maxContentWidth
|
||||||
text = textToSet
|
text = textToSet
|
||||||
textSize = textSizeDp
|
textSize = textSizeDp
|
||||||
typeface = font
|
typeface = font
|
||||||
setTextColor(Color.WHITE)
|
setTextColor(Color.WHITE)
|
||||||
alpha = alphaValue
|
alpha = alphaValue
|
||||||
gravity = Gravity.CENTER
|
gravity = Gravity.CENTER
|
||||||
setShadowLayer(8f, 2f, 2f, Color.parseColor("#99000000"))
|
setShadowLayer(6f, 3f, 3f, Color.parseColor("#AA000000"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,51 +549,108 @@ class MyWallpaperService : WallpaperService() {
|
|||||||
layoutParams = LinearLayout.LayoutParams(wrapContent, wrapContent)
|
layoutParams = LinearLayout.LayoutParams(wrapContent, wrapContent)
|
||||||
orientation = LinearLayout.HORIZONTAL
|
orientation = LinearLayout.HORIZONTAL
|
||||||
gravity = Gravity.CENTER
|
gravity = Gravity.CENTER
|
||||||
|
setPadding(1, 1, 1, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val sysAlpha = 0.9f
|
||||||
// 3. 각 줄(Row)을 생성하여 리스트에 담기
|
// 5. 각 줄(Row) 생성
|
||||||
val rowsToArrange = mutableListOf<View>()
|
val rowsToArrange = mutableListOf<View>()
|
||||||
|
|
||||||
// ① 날씨 (아이콘 + 상태)
|
// ① 날씨 (아이콘 + 상태)
|
||||||
if (weatherCondition.isNotEmpty()) {
|
if (weatherCondition.isNotEmpty()) {
|
||||||
val weatherRow = createHorizontalLayout()
|
val weatherRow = createHorizontalLayout()
|
||||||
weatherRow.addView(createTextView(getWeatherIconString(weatherCondition), 50f, materialIconFont))
|
// 아이콘은 Large 텍스트보다 살짝 크게 배율 고정
|
||||||
weatherRow.addView(createTextView(weatherCondition, 28f, monoBold).apply { setPadding(20, 0, 0, 0) })
|
weatherRow.addView(createTextView(getWeatherIconString(weatherCondition), sizeLarge * iconRate, materialIconFont))
|
||||||
|
weatherRow.addView(createTextView(" " + getKoreanWeatherCondition(weatherCondition), sizeLarge, monoBold))
|
||||||
rowsToArrange.add(weatherRow)
|
rowsToArrange.add(weatherRow)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ② 온도 & 습도
|
// ② 온도 & 습도
|
||||||
if (weatherTemp.isNotEmpty()) {
|
if (weatherTemp.isNotEmpty()) {
|
||||||
val tempRow = createHorizontalLayout()
|
val tempRow = createHorizontalLayout()
|
||||||
|
|
||||||
val tempStr = weatherTemp.trim()
|
val tempStr = weatherTemp.trim()
|
||||||
val humStr = weatherHum.trim()
|
val humStr = weatherHum.trim()
|
||||||
|
|
||||||
tempRow.addView(createTextView("thermostat", 20f, materialIconFont, 0.9f))
|
tempRow.addView(createTextView("thermostat", sizeMedium * iconRate, materialIconFont, sysAlpha))
|
||||||
tempRow.addView(createTextView(tempStr, 20f, monoBold, 0.9f).apply { setPadding(8, 0, 30, 0) })
|
tempRow.addView(createTextView(tempStr, sizeMedium, numbers,sysAlpha))
|
||||||
|
|
||||||
if (humStr.isNotEmpty()) {
|
if (humStr.isNotEmpty()) {
|
||||||
tempRow.addView(createTextView("water_drop", 20f, materialIconFont, 0.9f))
|
tempRow.addView(createTextView("water_drop", sizeMedium * iconRate, materialIconFont, sysAlpha))
|
||||||
tempRow.addView(createTextView(humStr, 20f, monoBold, 0.9f).apply { setPadding(8, 0, 0, 0) })
|
tempRow.addView(createTextView(humStr, sizeMedium, numbers, sysAlpha))
|
||||||
}
|
}
|
||||||
rowsToArrange.add(tempRow)
|
rowsToArrange.add(tempRow)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ③ 주소
|
// ③ 주소
|
||||||
if (address.isNotEmpty()) {
|
if (address.isNotEmpty()) {
|
||||||
rowsToArrange.add(createTextView(address, 16f, monoBold, 0.95f))
|
rowsToArrange.add(createTextView(address, sizeMedium, local, sysAlpha))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ④ 날짜
|
// ④ 날짜
|
||||||
rowsToArrange.add(createTextView(dateFull, 14f, monoBold, 0.8f))
|
rowsToArrange.add(createTextView(dateFull, sizeMedium, local))
|
||||||
|
|
||||||
// ⑤ 시스템 정보 (한 줄로 묶음)
|
// ⑤ 시스템 정보
|
||||||
rowsToArrange.add(createTextView("$ramInfo | $tempInfo | Battery $batteryInfo", 12f, monoBold, 0.6f))
|
val sysInfoRow = createHorizontalLayout()
|
||||||
|
|
||||||
// 4. 가로 길이 측정 후 다이아몬드 형태로 재정렬
|
|
||||||
val unspecifiedSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
val ramValue = getRamUsage().replace("RAM: ", "")
|
||||||
rowsToArrange.forEach { it.measure(unspecifiedSpec, unspecifiedSpec) }
|
sysInfoRow.addView(createTextView("memory", sizeSmall * iconRate, materialIconFont, sysAlpha))
|
||||||
|
sysInfoRow.addView(createTextView(ramValue, sizeSmall, numbers, sysAlpha).apply { setPadding(0, 0, 15, 0) })
|
||||||
|
|
||||||
|
val tempValue = getDeviceTemperature().replace("Temp: ", "")
|
||||||
|
sysInfoRow.addView(createTextView("device_thermostat", sizeSmall * iconRate, materialIconFont, sysAlpha))
|
||||||
|
sysInfoRow.addView(createTextView(tempValue, sizeSmall, numbers, sysAlpha).apply { setPadding(0, 0, 15, 0) })
|
||||||
|
|
||||||
|
val batteryInfo = getBatteryStatus()
|
||||||
|
val batteryIcon = if (batteryInfo.isCharging) "battery_charging_full" else "battery_full"
|
||||||
|
val batteryText = "${batteryInfo.percent}%"
|
||||||
|
|
||||||
|
sysInfoRow.addView(createTextView(batteryIcon, sizeSmall * iconRate, materialIconFont, sysAlpha))
|
||||||
|
sysInfoRow.addView(createTextView(batteryText, sizeSmall, numbers, sysAlpha))
|
||||||
|
rowsToArrange.add(sysInfoRow)
|
||||||
|
|
||||||
|
// ⑥ 명언
|
||||||
|
randomQuote?.let { randomQuote ->
|
||||||
|
val builder = SpannableStringBuilder()
|
||||||
|
|
||||||
|
fun appendSpanned(text: String, sizeDp: Float, font: Typeface, alpha: Float) {
|
||||||
|
val start = builder.length
|
||||||
|
builder.append(text)
|
||||||
|
val end = builder.length
|
||||||
|
|
||||||
|
builder.setSpan(android.text.style.AbsoluteSizeSpan(sizeDp.toInt(), true), start, end, android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
val alphaInt = (alpha * 255).toInt()
|
||||||
|
val color = Color.argb(alphaInt, 255, 255, 255)
|
||||||
|
builder.setSpan(android.text.style.ForegroundColorSpan(color), start, end, android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
builder.setSpan(object : android.text.style.MetricAffectingSpan() {
|
||||||
|
override fun updateDrawState(tp: android.text.TextPaint) { tp.typeface = font }
|
||||||
|
override fun updateMeasureState(tp: android.text.TextPaint) { tp.typeface = font }
|
||||||
|
}, start, end, android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (randomQuote.isTranslated) {
|
||||||
|
appendSpanned("\"${randomQuote.textEn}\"", sizeMedium, quote, 1.0f)
|
||||||
|
builder.append("\n")
|
||||||
|
appendSpanned(randomQuote.textKo, sizeSmall, monoBold, sysAlpha)
|
||||||
|
val author = if (randomQuote.authorKo.isNotEmpty()) randomQuote.authorKo else randomQuote.authorEn
|
||||||
|
appendSpanned(" - $author -", sizeSmall, monoBold, sysAlpha)
|
||||||
|
} else {
|
||||||
|
val mainText = if (randomQuote.textKo.isNotEmpty()) randomQuote.textKo else randomQuote.textEn
|
||||||
|
val author = if (randomQuote.authorKo.isNotEmpty()) randomQuote.authorKo else randomQuote.authorEn
|
||||||
|
appendSpanned("\"$mainText\"", sizeMedium, quote, 1.0f)
|
||||||
|
appendSpanned(" - $author -", sizeSmall, monoBold, sysAlpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
val quoteView = createTextView(builder, sizeMedium, quote, 1.0f).apply {
|
||||||
|
// setLineSpacing(0f, 1.2f)
|
||||||
|
}
|
||||||
|
rowsToArrange.add(quoteView)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 💡 6. 정렬 및 다이아몬드 배열
|
||||||
|
val widthSpec = View.MeasureSpec.makeMeasureSpec(maxContentWidth, View.MeasureSpec.AT_MOST)
|
||||||
|
val heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
||||||
|
rowsToArrange.forEach { it.measure(widthSpec, heightSpec) }
|
||||||
|
|
||||||
val sortedRows = rowsToArrange.sortedBy { it.measuredWidth }
|
val sortedRows = rowsToArrange.sortedBy { it.measuredWidth }
|
||||||
val arrangedRows = arrayOfNulls<View>(sortedRows.size)
|
val arrangedRows = arrayOfNulls<View>(sortedRows.size)
|
||||||
@ -503,49 +662,32 @@ class MyWallpaperService : WallpaperService() {
|
|||||||
else arrangedRows[bottomIndex--] = sortedRows[i]
|
else arrangedRows[bottomIndex--] = sortedRows[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 최종 부모 레이아웃 조립 (원형/타원형 배경)
|
// 7. 35% 고정 크기 원형 틀 생성
|
||||||
val rootLayout = LinearLayout(this@MyWallpaperService).apply {
|
val rootLayout = LinearLayout(this@MyWallpaperService).apply {
|
||||||
layoutParams = ViewGroup.LayoutParams(wrapContent, wrapContent)
|
layoutParams = ViewGroup.LayoutParams(circleSize, circleSize)
|
||||||
orientation = LinearLayout.VERTICAL
|
orientation = LinearLayout.VERTICAL
|
||||||
gravity = Gravity.CENTER
|
gravity = Gravity.CENTER
|
||||||
// 💡 원형 안에 글씨가 넉넉히 들어가도록 패딩을 충분히 줍니다.
|
|
||||||
setPadding(20, 20, 20, 20)
|
|
||||||
background = WavyOvalDrawable(
|
background = WavyOvalDrawable(
|
||||||
bgColor = Color.parseColor("#33000000"),
|
bgColor = Color.parseColor("#40000000"),
|
||||||
strokeColor = Color.parseColor("#22000000"), // 물결이 잘 보이도록 불투명도를 살짝 올림
|
strokeColor = Color.parseColor("#09000000"),
|
||||||
strokeThick = 30f
|
strokeThick = 40f
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
arrangedRows.forEach { rowView ->
|
arrangedRows.forEach { rowView ->
|
||||||
if (rowView != null) {
|
if (rowView != null) {
|
||||||
val lp = LinearLayout.LayoutParams(wrapContent, wrapContent).apply {
|
val lp = LinearLayout.LayoutParams(wrapContent, wrapContent).apply {
|
||||||
setMargins(0, 10, 0, 10)
|
setMargins(0, 2, 0, 2)
|
||||||
}
|
}
|
||||||
rootLayout.addView(rowView, lp)
|
rootLayout.addView(rowView, lp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. 💡 완벽한 정원형(Perfect Circle)을 위한 2단계 측정(Measure) 로직
|
// 8. 강제 측정, 렌더링 및 캡처
|
||||||
val displayMetrics = resources.displayMetrics
|
|
||||||
val maxWidthSpec = View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels, View.MeasureSpec.AT_MOST)
|
|
||||||
|
|
||||||
// 1차 측정: 내용물들이 자연스럽게 늘어났을 때의 크기(너비, 높이)를 구합니다.
|
|
||||||
rootLayout.measure(maxWidthSpec, unspecifiedSpec)
|
|
||||||
|
|
||||||
// 너비와 높이 중 '더 큰 값'을 찾아 원의 지름(Size)으로 삼습니다.
|
|
||||||
val circleSize = Math.max(rootLayout.measuredWidth, rootLayout.measuredHeight)
|
|
||||||
|
|
||||||
if (circleSize <= 0) return null
|
|
||||||
|
|
||||||
// 2차 측정: 너비와 높이를 원의 지름 크기로 '정확히(EXACTLY)' 덮어씌웁니다.
|
|
||||||
val exactSpec = View.MeasureSpec.makeMeasureSpec(circleSize, View.MeasureSpec.EXACTLY)
|
val exactSpec = View.MeasureSpec.makeMeasureSpec(circleSize, View.MeasureSpec.EXACTLY)
|
||||||
rootLayout.measure(exactSpec, exactSpec)
|
rootLayout.measure(exactSpec, exactSpec)
|
||||||
|
|
||||||
// 레이아웃 배치
|
|
||||||
rootLayout.layout(0, 0, circleSize, circleSize)
|
rootLayout.layout(0, 0, circleSize, circleSize)
|
||||||
|
|
||||||
// 7. 비트맵 캡처
|
|
||||||
val bitmap = Bitmap.createBitmap(circleSize, circleSize, Bitmap.Config.ARGB_8888)
|
val bitmap = Bitmap.createBitmap(circleSize, circleSize, Bitmap.Config.ARGB_8888)
|
||||||
val canvas = Canvas(bitmap)
|
val canvas = Canvas(bitmap)
|
||||||
rootLayout.draw(canvas)
|
rootLayout.draw(canvas)
|
||||||
@ -598,12 +740,20 @@ class MyWallpaperService : WallpaperService() {
|
|||||||
}
|
}
|
||||||
val mediaDir = File(File(this@MyWallpaperService.getExternalFilesDir(null), "completed_torrents"), "Images")
|
val mediaDir = File(File(this@MyWallpaperService.getExternalFilesDir(null), "completed_torrents"), "Images")
|
||||||
val supportedExtensions = listOf("mp4", "mkv", "avi", "mov","webm", "jpg", "jpeg", "png", "bmp", "webp", "gif")
|
val supportedExtensions = listOf("mp4", "mkv", "avi", "mov","webm", "jpg", "jpeg", "png", "bmp", "webp", "gif")
|
||||||
|
var randomQuote : QuoteItem? = null
|
||||||
private val nextMediaCallback = object : NativeRenderer.NextMediaCallback {
|
private val nextMediaCallback = object : NativeRenderer.NextMediaCallback {
|
||||||
override fun onNextMediaRequested() {
|
override fun onNextMediaRequested() {
|
||||||
loadFiles()
|
loadFiles()
|
||||||
val nextFile = mediaFiles.random()
|
val nextFile = mediaFiles.random()
|
||||||
Log.d(TAG, "Callback: Preloading next random media: ${nextFile.absolutePath}")
|
Log.d(TAG, "Callback: Preloading next random media: ${nextFile.absolutePath}")
|
||||||
|
// ⑥ 명언 (DB 랜덤 추출)
|
||||||
|
val realm = WorkersDb.getRealm()
|
||||||
|
val quotes = realm.query<QuoteItem>(QuoteItem::class).find().shuffled()
|
||||||
|
randomQuote = if (quotes.isNotEmpty()) quotes.random() else null
|
||||||
|
monoBold = resources.getFont(fontz.random())
|
||||||
|
quote = resources.getFont(fontz.random())
|
||||||
|
local = resources.getFont(fontz.random())
|
||||||
|
numbers = resources.getFont(fontz.random())
|
||||||
|
|
||||||
getFdFromPath(nextFile.absolutePath)?.let { fd ->
|
getFdFromPath(nextFile.absolutePath)?.let { fd ->
|
||||||
nativeRenderer?.startNextPreload(fd)
|
nativeRenderer?.startNextPreload(fd)
|
||||||
@ -682,7 +832,7 @@ class WavyOvalDrawable(
|
|||||||
private val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
private val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
color = strokeColor
|
color = strokeColor
|
||||||
style = Paint.Style.STROKE
|
style = Paint.Style.STROKE
|
||||||
strokeWidth = strokeThick
|
strokeWidth = 100f
|
||||||
strokeCap = Paint.Cap.ROUND
|
strokeCap = Paint.Cap.ROUND
|
||||||
strokeJoin = Paint.Join.ROUND
|
strokeJoin = Paint.Join.ROUND
|
||||||
|
|
||||||
@ -706,14 +856,14 @@ class WavyOvalDrawable(
|
|||||||
if (radius > 0) {
|
if (radius > 0) {
|
||||||
// 바깥쪽 색상: 입력받은 bgColor에서 투명도(Alpha)만 0으로 날려버린 색
|
// 바깥쪽 색상: 입력받은 bgColor에서 투명도(Alpha)만 0으로 날려버린 색
|
||||||
// 이렇게 해야 검은색->흰색으로 깨지는 현상 없이 자연스럽게 투명해집니다.
|
// 이렇게 해야 검은색->흰색으로 깨지는 현상 없이 자연스럽게 투명해집니다.
|
||||||
val transparentColor = bgColor and 0x11FFFFFF
|
val transparentColor = bgColor and 0x10FFFFFF
|
||||||
|
|
||||||
bgPaint.shader = RadialGradient(
|
bgPaint.shader = RadialGradient(
|
||||||
centerX,
|
centerX,
|
||||||
centerY,
|
centerY,
|
||||||
radius,
|
radius,
|
||||||
intArrayOf(bgColor, transparentColor), // 중앙 -> 바깥 색상 배열
|
intArrayOf(bgColor, transparentColor), // 중앙 -> 바깥 색상 배열
|
||||||
floatArrayOf(0.4f, 1.0f), // 중심에서 40% 지점부터 투명해지기 시작
|
floatArrayOf(0.4f, 1.1f), // 중심에서 40% 지점부터 투명해지기 시작
|
||||||
Shader.TileMode.CLAMP
|
Shader.TileMode.CLAMP
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -722,15 +872,17 @@ class WavyOvalDrawable(
|
|||||||
override fun draw(canvas: Canvas) {
|
override fun draw(canvas: Canvas) {
|
||||||
val rectF = RectF(bounds)
|
val rectF = RectF(bounds)
|
||||||
|
|
||||||
// 1. 방사형 그라데이션 배경 채우기
|
|
||||||
canvas.drawOval(rectF, bgPaint)
|
|
||||||
|
|
||||||
// 2. 선 잘림 방지용 여백 계산
|
// 2. 선 잘림 방지용 여백 계산
|
||||||
val inset = amplitude + 3f
|
val inset = 8f
|
||||||
rectF.inset(inset, inset)
|
rectF.inset(inset, inset)
|
||||||
|
|
||||||
// 3. 물결 테두리 선 그리기
|
// 3. 물결 테두리 선 그리기
|
||||||
canvas.drawOval(rectF, strokePaint)
|
canvas.drawOval(rectF, strokePaint)
|
||||||
|
|
||||||
|
// 1. 방사형 그라데이션 배경 채우기
|
||||||
|
canvas.drawOval(rectF, bgPaint)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setAlpha(alpha: Int) {
|
override fun setAlpha(alpha: Int) {
|
||||||
|
|||||||
@ -49,6 +49,7 @@ class LocationUpdateService : Service(), LocationListener {
|
|||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
geocoder.getFromLocation(lat, long, 1) { addresses ->
|
geocoder.getFromLocation(lat, long, 1) { addresses ->
|
||||||
addresses.first()?.let {
|
addresses.first()?.let {
|
||||||
|
addresses.get(0)
|
||||||
WorkersDb.getRealm()?.apply {
|
WorkersDb.getRealm()?.apply {
|
||||||
LocationLog().let { loc ->
|
LocationLog().let { loc ->
|
||||||
loc.fillData(it)
|
loc.fillData(it)
|
||||||
@ -123,7 +124,9 @@ class LocationUpdateService : Service(), LocationListener {
|
|||||||
if (body != null) {
|
if (body != null) {
|
||||||
var result = body.string()
|
var result = body.string()
|
||||||
var w = Gson().fromJson<CurrentWeather>(result,CurrentWeather::class.java)
|
var w = Gson().fromJson<CurrentWeather>(result,CurrentWeather::class.java)
|
||||||
w.addr = addresses.first().getAddressLine(0).replace("대한민국", "")
|
var address =addresses.first()
|
||||||
|
|
||||||
|
w.addr = address.getAddressLine(0)
|
||||||
lastWeather.clear()
|
lastWeather.clear()
|
||||||
lastWeather.addAll(w.getSummaryInfo())
|
lastWeather.addAll(w.getSummaryInfo())
|
||||||
Blog.LOGE("Location >>> ${result}\n${lastWeather}")
|
Blog.LOGE("Location >>> ${result}\n${lastWeather}")
|
||||||
@ -265,7 +268,7 @@ open class Location {
|
|||||||
open class CurrentWeather {
|
open class CurrentWeather {
|
||||||
var addr: String? = null
|
var addr: String? = null
|
||||||
var current: Current? = null
|
var current: Current? = null
|
||||||
|
var location : bums.lunatic.launcher.workers.Location? = null
|
||||||
// 아이콘 URL을 배열의 4번째(index 3) 요소로 추가 전달합니다.
|
// 아이콘 URL을 배열의 4번째(index 3) 요소로 추가 전달합니다.
|
||||||
fun getSummaryInfo(): List<String> {
|
fun getSummaryInfo(): List<String> {
|
||||||
val iconUrl = current?.condition?.icon?.let { "https:$it" } ?: ""
|
val iconUrl = current?.condition?.icon?.let { "https:$it" } ?: ""
|
||||||
@ -273,7 +276,7 @@ open class CurrentWeather {
|
|||||||
"${current?.condition?.text}", // 0: 날씨 상태
|
"${current?.condition?.text}", // 0: 날씨 상태
|
||||||
"${current?.temp_c}", // 1: 온도 및 습도
|
"${current?.temp_c}", // 1: 온도 및 습도
|
||||||
"${current?.humidity}",
|
"${current?.humidity}",
|
||||||
"$addr", // 2: 주소
|
"${addr}", // 2: 주소
|
||||||
iconUrl // 3: 아이콘 URL
|
iconUrl // 3: 아이콘 URL
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -117,35 +117,35 @@ class TorrentService : Service() {
|
|||||||
var speedText = ""
|
var speedText = ""
|
||||||
var detailsText = ""
|
var detailsText = ""
|
||||||
|
|
||||||
if (isCharging) {
|
// if (isCharging) {
|
||||||
speedText = when (plugged) {
|
// speedText = when (plugged) {
|
||||||
BatteryManager.BATTERY_PLUGGED_AC -> "고속"
|
// BatteryManager.BATTERY_PLUGGED_AC -> "고속"
|
||||||
BatteryManager.BATTERY_PLUGGED_USB -> "저속"
|
// BatteryManager.BATTERY_PLUGGED_USB -> "저속"
|
||||||
BatteryManager.BATTERY_PLUGGED_WIRELESS -> "무선"
|
// BatteryManager.BATTERY_PLUGGED_WIRELESS -> "무선"
|
||||||
else -> ""
|
// else -> ""
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// 1. 전압(Voltage) 가져오기: 기본 단위는 밀리볼트(mV)
|
// // 1. 전압(Voltage) 가져오기: 기본 단위는 밀리볼트(mV)
|
||||||
val voltageMv = intent?.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0) ?: 0
|
// val voltageMv = intent?.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0) ?: 0
|
||||||
val voltageV = voltageMv / 1000.0f
|
// val voltageV = voltageMv / 1000.0f
|
||||||
|
//
|
||||||
// 2. 전류(Current) 가져오기: BatteryManager를 통해 실시간 값 조회
|
// // 2. 전류(Current) 가져오기: BatteryManager를 통해 실시간 값 조회
|
||||||
val batteryManager = getSystemService(BATTERY_SERVICE) as BatteryManager
|
// val batteryManager = getSystemService(BATTERY_SERVICE) as BatteryManager
|
||||||
// 안드로이드 표준 단위는 마이크로암페어(µA) 입니다.
|
// // 안드로이드 표준 단위는 마이크로암페어(µA) 입니다.
|
||||||
val currentUa = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_NOW)
|
// val currentUa = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_NOW)
|
||||||
|
//
|
||||||
// 음수(방전)로 나올 수 있으므로 절대값 처리 후 암페어(A)로 변환
|
// // 음수(방전)로 나올 수 있으므로 절대값 처리 후 암페어(A)로 변환
|
||||||
val currentA = Math.abs(currentUa) / 1000000.0f
|
// val currentA = Math.abs(currentUa) / 1000000.0f
|
||||||
|
//
|
||||||
// 3. 전력(Watt) 계산: W = V * A
|
// // 3. 전력(Watt) 계산: W = V * A
|
||||||
val wattage = voltageV * currentA
|
// val wattage = voltageV * currentA
|
||||||
|
//
|
||||||
// 값이 정상적으로 읽혔을 때만 텍스트 생성 (가끔 0으로 떨어지는 기기 방어)
|
// // 값이 정상적으로 읽혔을 때만 텍스트 생성 (가끔 0으로 떨어지는 기기 방어)
|
||||||
if (voltageV > 0f && currentA > 0f) {
|
// if (voltageV > 0f && currentA > 0f) {
|
||||||
detailsText = String.format("%.1fV %.1fA (%.1fW)", voltageV, currentA, wattage)
|
// detailsText = String.format("%.1fV %.1fA (%.1fW)", voltageV, currentA, wattage)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
Blog.LOGE("detailsText >>> $detailsText")
|
// Blog.LOGE("detailsText >>> $detailsText")
|
||||||
|
|
||||||
updateSessionState()
|
updateSessionState()
|
||||||
}
|
}
|
||||||
@ -159,12 +159,10 @@ class TorrentService : Service() {
|
|||||||
networkCallback = object : ConnectivityManager.NetworkCallback() {
|
networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||||
override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) {
|
override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) {
|
||||||
val isWifi = caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|
val isWifi = caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|
||||||
if (isWifiConnected != isWifi) {
|
|
||||||
isWifiConnected = isWifi
|
isWifiConnected = isWifi
|
||||||
updateSessionState()
|
updateSessionState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
connectivityManager.registerNetworkCallback(request, networkCallback!!)
|
connectivityManager.registerNetworkCallback(request, networkCallback!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,7 @@ import bums.lunatic.launcher.home.tokiz.LastInfo
|
|||||||
import bums.lunatic.launcher.home.tokiz.ReaderConfig
|
import bums.lunatic.launcher.home.tokiz.ReaderConfig
|
||||||
import bums.lunatic.launcher.model.ExpressionItem
|
import bums.lunatic.launcher.model.ExpressionItem
|
||||||
import bums.lunatic.launcher.model.ExpressionWord
|
import bums.lunatic.launcher.model.ExpressionWord
|
||||||
|
import bums.lunatic.launcher.model.QuoteItem
|
||||||
import bums.lunatic.launcher.model.WallContentGroup
|
import bums.lunatic.launcher.model.WallContentGroup
|
||||||
import bums.lunatic.launcher.utils.Blog
|
import bums.lunatic.launcher.utils.Blog
|
||||||
import bums.lunatic.launcher.utils.JamoUtils
|
import bums.lunatic.launcher.utils.JamoUtils
|
||||||
@ -79,7 +80,7 @@ object WorkersDb {
|
|||||||
LastInfo::class, HistoryItem::class, ReaderConfig::class, ContentsCollection::class, ContentsPageInfo::class,
|
LastInfo::class, HistoryItem::class, ReaderConfig::class, ContentsCollection::class, ContentsPageInfo::class,
|
||||||
AppUsageLog::class,
|
AppUsageLog::class,
|
||||||
WidgetData::class,
|
WidgetData::class,
|
||||||
ExpressionItem::class,ExpressionWord::class
|
ExpressionItem::class,ExpressionWord::class,QuoteItem::class
|
||||||
)
|
)
|
||||||
//,UserActionModel::class
|
//,UserActionModel::class
|
||||||
|
|
||||||
|
|||||||
BIN
app/src/main/res/font/cafe24ohsquareair.ttf
Normal file
BIN
app/src/main/res/font/cafe24ohsquareair.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
app/src/main/res/font/cafe24ssukssukregular.ttf
Normal file
BIN
app/src/main/res/font/cafe24ssukssukregular.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/kyobo_handwriting_2019.ttf
Normal file
BIN
app/src/main/res/font/kyobo_handwriting_2019.ttf
Normal file
Binary file not shown.
BIN
app/src/main/res/font/kyobohandwriting2024psw.ttf
Normal file
BIN
app/src/main/res/font/kyobohandwriting2024psw.ttf
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user