Initial commit - v1.1.9
This commit is contained in:
143
.agent/workflows/GIT_PUSH_GUIDE.md
Normal file
143
.agent/workflows/GIT_PUSH_GUIDE.md
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
# Git 푸시 성공 가이드
|
||||||
|
|
||||||
|
## 빠른 실행
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. gh CLI 인증 확인
|
||||||
|
./gh.exe auth status
|
||||||
|
|
||||||
|
# 2. 인증이 안 되어 있으면 로그인
|
||||||
|
./gh.exe auth login --web
|
||||||
|
|
||||||
|
# 3. Git에 gh 인증 설정
|
||||||
|
./gh.exe auth setup-git
|
||||||
|
|
||||||
|
# 4. 푸시
|
||||||
|
git push shiftring main
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 상세 절차
|
||||||
|
|
||||||
|
### 1. GitHub CLI 인증
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 인증 상태 확인
|
||||||
|
./gh.exe auth status
|
||||||
|
```
|
||||||
|
|
||||||
|
**인증이 안 된 경우:**
|
||||||
|
```bash
|
||||||
|
./gh.exe auth login --web
|
||||||
|
```
|
||||||
|
- One-time code가 표시됨 (예: C7EF-30B3)
|
||||||
|
- https://github.com/login/device 접속
|
||||||
|
- 코드 입력 후 인증 완료
|
||||||
|
|
||||||
|
### 2. Git에 gh 인증 연동
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gh.exe auth setup-git
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 푸시 실행
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# shiftring remote 사용 (agent.md에 정의됨)
|
||||||
|
git push shiftring main
|
||||||
|
|
||||||
|
# 또는 origin 사용
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 저장소 정보
|
||||||
|
|
||||||
|
| Remote | URL | 용도 |
|
||||||
|
|--------|-----|------|
|
||||||
|
| origin | https://github.com/sanjeok77-tech/ShiftRing.git | 코드 저장소 |
|
||||||
|
| shiftring | https://github.com/sanjeok77-tech/ShiftRing.git | 코드 저장소 (동일) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 문제 해결
|
||||||
|
|
||||||
|
### "Cannot prompt because user interactivity has been disabled"
|
||||||
|
|
||||||
|
**원인**: 환경 변수로 인해 인터랙티브 프롬프트가 비활성화됨
|
||||||
|
|
||||||
|
**해결**:
|
||||||
|
```bash
|
||||||
|
# gh CLI로 인증 설정
|
||||||
|
./gh.exe auth setup-git
|
||||||
|
|
||||||
|
# 그 다음 푸시
|
||||||
|
git push shiftring main
|
||||||
|
```
|
||||||
|
|
||||||
|
### "Host key verification failed" (SSH)
|
||||||
|
|
||||||
|
**원인**: SSH 키가 설정되지 않음
|
||||||
|
|
||||||
|
**해결**: HTTPS 사용
|
||||||
|
```bash
|
||||||
|
git remote set-url origin https://github.com/sanjeok77-tech/ShiftRing.git
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 릴리즈 푸시 (dakjaba-releases)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 버전 파일 업데이트
|
||||||
|
# version.json, CHANGELOG.md, README.md 수정
|
||||||
|
|
||||||
|
# 2. APK 복사
|
||||||
|
cp app/build/outputs/apk/release/app-release.apk app.apk
|
||||||
|
|
||||||
|
# 3. GitHub Release 생성
|
||||||
|
./gh.exe release create v1.1.4 "app.apk" \
|
||||||
|
--title "Shiftring v1.1.4" \
|
||||||
|
--notes "릴리즈 노트" \
|
||||||
|
-R "sanjeok77-tech/dakjaba-releases"
|
||||||
|
|
||||||
|
# 4. dakjaba-releases 저장소 업데이트
|
||||||
|
./gh.exe repo clone sanjeok77-tech/dakjaba-releases ../dakjaba-releases-temp
|
||||||
|
cp version.json ../dakjaba-releases-temp/
|
||||||
|
cp app.apk ../dakjaba-releases-temp/
|
||||||
|
cd ../dakjaba-releases-temp
|
||||||
|
git add . && git commit -m "update: vX.X.X" && git push origin main
|
||||||
|
cd ..
|
||||||
|
rm -rf dakjaba-releases-temp
|
||||||
|
|
||||||
|
# 5. 소스 코드 커밋 및 푸시
|
||||||
|
cd /c/Users/work/Desktop/1.0.0
|
||||||
|
git add . && git commit -m "chore: release vX.X.X"
|
||||||
|
git push shiftring main
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 성공 사례 (2026-02-21)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./gh.exe auth status
|
||||||
|
github.com
|
||||||
|
✓ Logged in to github.com account sanjeok77-tech (keyring)
|
||||||
|
- Active account: true
|
||||||
|
- Git operations protocol: https
|
||||||
|
- Token: gho_************************************
|
||||||
|
|
||||||
|
$ ./gh.exe auth setup-git
|
||||||
|
git: authorization completed
|
||||||
|
|
||||||
|
$ git push shiftring main
|
||||||
|
To https://github.com/sanjeok77-tech/ShiftRing.git
|
||||||
|
a848cd8..c72db7f main -> main
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**최종 수정**: 2026-02-21
|
||||||
70
.agent/workflows/push.md
Normal file
70
.agent/workflows/push.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
description: 릴리즈 빌드 및 배포 자동화 가이드 (GitHub CLI 기반)
|
||||||
|
---
|
||||||
|
|
||||||
|
# 🚀 릴리즈 빌드 및 배포 워크플로우
|
||||||
|
|
||||||
|
이 가이드는 교대링(Shiftring) 프로젝트의 최신 버전을 빌드하고, APK를 배포 저장소(`dakjaba-releases`)에 업로드하며, 버전을 갱신하는 절차를 설명합니다.
|
||||||
|
|
||||||
|
## // turbo-all
|
||||||
|
## 1. 전제 조건
|
||||||
|
- **GitHub CLI (gh)**가 설치되어 있고 로그인되어 있어야 함 (`gh auth status`)
|
||||||
|
- `release.jks`와 `keystore.properties`가 루트 폴더에 존재해야 함
|
||||||
|
|
||||||
|
## 2. 배포 단계
|
||||||
|
|
||||||
|
### 단계 1: 프로젝트 버전 확인 및 갱신
|
||||||
|
- `app/build.gradle.kts`의 `versionCode`와 `versionName`을 확인 및 갱신합니다.
|
||||||
|
- `version.json`의 정보를 동일하게 맞춥니다.
|
||||||
|
- `CHANGELOG.md`에 변경 사항을 기록합니다.
|
||||||
|
- `README.md`의 다운로드 링크 및 버전을 갱신합니다.
|
||||||
|
|
||||||
|
### 단계 2: 릴리즈 빌드 수행
|
||||||
|
```powershell
|
||||||
|
./gradlew.bat assembleRelease
|
||||||
|
```
|
||||||
|
- 빌드 결과물은 `app/build/outputs/apk/release/app-release.apk`에 생성됩니다.
|
||||||
|
|
||||||
|
### 단계 3: APK 서명 확인 (선택 사항)
|
||||||
|
- 생성된 APK가 정상적으로 서명되었는지 확인합니다.
|
||||||
|
|
||||||
|
### 단계 4: 배포 저장소 업데이트 및 릴리즈 생성
|
||||||
|
// turbo
|
||||||
|
```powershell
|
||||||
|
# 1. APK 파일 복사 (배포용 이름으로 변경)
|
||||||
|
copy-item "app/build/outputs/apk/release/app-release.apk" "app.apk" -Force
|
||||||
|
|
||||||
|
# 2. 버전 정보 설정 (현재 버전에 맞춰 수정)
|
||||||
|
$VERSION = "v1.0.4"
|
||||||
|
|
||||||
|
# 3. GitHub Release 생성 (dakjaba-releases 저장소)
|
||||||
|
# tip: 이미 존재하는 태그라면 삭제 후 생성하거나 gh release edit 사용
|
||||||
|
gh release create $VERSION "app.apk" --title "Shiftring $VERSION" --notes-file "CHANGELOG.md" -R "sanjeok77-tech/dakjaba-releases"
|
||||||
|
|
||||||
|
# 4. 배포 저장소(dakjaba-releases)의 version.json 및 최신 APK 갱신 (인앱 업데이트용)
|
||||||
|
gh repo clone sanjeok77-tech/dakjaba-releases ..\dakjaba-releases-temp
|
||||||
|
copy-item "version.json" "..\dakjaba-releases-temp\version.json" -Force
|
||||||
|
copy-item "app.apk" "..\dakjaba-releases-temp\app.apk" -Force
|
||||||
|
|
||||||
|
# 5. README.md 자동 업데이트 (v1.1.1 패턴 기반 교체 예시 - 실제 버전변수로 대체 필요)
|
||||||
|
# tip: sed나 powershell replace를 사용하여 README의 vX.X.X 및 다운로드 링크를 갱신합니다.
|
||||||
|
(Get-Content README.md) -replace 'v\d+\.\d+\.\d+', "$VERSION" | Set-Content README.md
|
||||||
|
(Get-Content ..\dakjaba-releases-temp\README.md) -replace 'v\d+\.\d+\.\d+', "$VERSION" | Set-Content ..\dakjaba-releases-temp\README.md
|
||||||
|
|
||||||
|
pushd ..\dakjaba-releases-temp
|
||||||
|
git add .
|
||||||
|
git commit -m "update: $VERSION"
|
||||||
|
git push origin main
|
||||||
|
popd
|
||||||
|
Remove-Item -Recurse -Force ..\dakjaba-releases-temp
|
||||||
|
```
|
||||||
|
|
||||||
|
### 단계 5: 소스 코드 커밋 및 푸시
|
||||||
|
```powershell
|
||||||
|
git add .
|
||||||
|
git commit -m "chore: release $VERSION"
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
*주의: 배포 저장소(`dakjaba-releases`)는 퍼블릭이며, 코드 저장소(`dakjaba`)는 프라이빗입니다. 배포 시 중요한 키 파일이 배포 저장소에 포함되지 않도록 주의하십시오.*
|
||||||
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
*.iml
|
||||||
|
.gradle
|
||||||
|
/local.properties
|
||||||
|
/.idea
|
||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
||||||
|
.externalNativeBuild
|
||||||
|
.cxx
|
||||||
|
local.properties
|
||||||
|
signing.properties
|
||||||
|
*.jks
|
||||||
|
*.keystore
|
||||||
|
app/build/
|
||||||
|
|
||||||
|
build_output.txt
|
||||||
|
bin/
|
||||||
|
gh.zip
|
||||||
|
gh_installer.msi
|
||||||
|
alarm_simulation_test.py
|
||||||
|
alarm_simulation_comprehensive.kts
|
||||||
82
.kotlin/errors/errors-1770787292249.log
Normal file
82
.kotlin/errors/errors-1770787292249.log
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
|
||||||
|
File being compiled: C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:27)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:14)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:371)
|
||||||
|
at org.jetbrains.kotlin.codegen.CodegenFactory.generateModule(CodegenFactory.kt:47)
|
||||||
|
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:45)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:253)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:149)
|
||||||
|
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:79)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:104)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:114)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
|
||||||
|
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||||
|
at java.base/java.lang.Thread.run(Unknown Source)
|
||||||
|
Caused by: java.lang.IllegalStateException: Unresolved annotation type: [Error type: Unresolved type for Composable] at C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.serializeAnnotation(AnnotationSerializer.kt:35)
|
||||||
|
at org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension.serializeType(JvmSerializerExtension.kt:152)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.type$serialization(DescriptorSerializer.kt:616)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.valueParameter(DescriptorSerializer.kt:500)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.functionProto(DescriptorSerializer.kt:370)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.packagePartProto(DescriptorSerializer.kt:676)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.metadata.DescriptorMetadataSerializer.serialize(DescriptorMetadataSerializer.kt:72)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation$lambda$16(ClassCodegen.kt:318)
|
||||||
|
at org.jetbrains.kotlin.codegen.WriteAnnotationUtilKt.writeKotlinMetadata(writeAnnotationUtil.kt:48)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation(ClassCodegen.kt:316)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateAnnotations(ClassCodegen.kt:271)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:185)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:39)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseFactoriesKt.createFilePhase$lambda$4(PhaseFactories.kt:71)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$createSimpleNamedCompilerPhase$1.phaseBody(PhaseBuilders.kt:69)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:226)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
|
||||||
|
... 52 more
|
||||||
|
|
||||||
|
|
||||||
82
.kotlin/errors/errors-1770787323500.log
Normal file
82
.kotlin/errors/errors-1770787323500.log
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
|
||||||
|
File being compiled: C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:27)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:14)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:371)
|
||||||
|
at org.jetbrains.kotlin.codegen.CodegenFactory.generateModule(CodegenFactory.kt:47)
|
||||||
|
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:45)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:253)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:149)
|
||||||
|
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:79)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:104)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:114)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
|
||||||
|
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||||
|
at java.base/java.lang.Thread.run(Unknown Source)
|
||||||
|
Caused by: java.lang.IllegalStateException: Unresolved annotation type: [Error type: Unresolved type for Composable] at C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.serializeAnnotation(AnnotationSerializer.kt:35)
|
||||||
|
at org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension.serializeType(JvmSerializerExtension.kt:152)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.type$serialization(DescriptorSerializer.kt:616)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.valueParameter(DescriptorSerializer.kt:500)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.functionProto(DescriptorSerializer.kt:370)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.packagePartProto(DescriptorSerializer.kt:676)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.metadata.DescriptorMetadataSerializer.serialize(DescriptorMetadataSerializer.kt:72)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation$lambda$16(ClassCodegen.kt:318)
|
||||||
|
at org.jetbrains.kotlin.codegen.WriteAnnotationUtilKt.writeKotlinMetadata(writeAnnotationUtil.kt:48)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation(ClassCodegen.kt:316)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateAnnotations(ClassCodegen.kt:271)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:185)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:39)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseFactoriesKt.createFilePhase$lambda$4(PhaseFactories.kt:71)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$createSimpleNamedCompilerPhase$1.phaseBody(PhaseBuilders.kt:69)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:226)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
|
||||||
|
... 52 more
|
||||||
|
|
||||||
|
|
||||||
82
.kotlin/errors/errors-1770787357198.log
Normal file
82
.kotlin/errors/errors-1770787357198.log
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
|
||||||
|
File being compiled: C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:27)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:14)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:371)
|
||||||
|
at org.jetbrains.kotlin.codegen.CodegenFactory.generateModule(CodegenFactory.kt:47)
|
||||||
|
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:45)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:253)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:149)
|
||||||
|
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:79)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:104)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:114)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
|
||||||
|
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||||
|
at java.base/java.lang.Thread.run(Unknown Source)
|
||||||
|
Caused by: java.lang.IllegalStateException: Unresolved annotation type: [Error type: Unresolved type for Composable] at C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.serializeAnnotation(AnnotationSerializer.kt:35)
|
||||||
|
at org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension.serializeType(JvmSerializerExtension.kt:152)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.type$serialization(DescriptorSerializer.kt:616)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.valueParameter(DescriptorSerializer.kt:500)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.functionProto(DescriptorSerializer.kt:370)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.packagePartProto(DescriptorSerializer.kt:676)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.metadata.DescriptorMetadataSerializer.serialize(DescriptorMetadataSerializer.kt:72)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation$lambda$16(ClassCodegen.kt:318)
|
||||||
|
at org.jetbrains.kotlin.codegen.WriteAnnotationUtilKt.writeKotlinMetadata(writeAnnotationUtil.kt:48)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation(ClassCodegen.kt:316)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateAnnotations(ClassCodegen.kt:271)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:185)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:39)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseFactoriesKt.createFilePhase$lambda$4(PhaseFactories.kt:71)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$createSimpleNamedCompilerPhase$1.phaseBody(PhaseBuilders.kt:69)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:226)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
|
||||||
|
... 52 more
|
||||||
|
|
||||||
|
|
||||||
82
.kotlin/errors/errors-1770787396766.log
Normal file
82
.kotlin/errors/errors-1770787396766.log
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
|
||||||
|
File being compiled: C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:27)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:14)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:371)
|
||||||
|
at org.jetbrains.kotlin.codegen.CodegenFactory.generateModule(CodegenFactory.kt:47)
|
||||||
|
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:45)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:253)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:149)
|
||||||
|
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:79)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:104)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:114)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
|
||||||
|
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||||
|
at java.base/java.lang.Thread.run(Unknown Source)
|
||||||
|
Caused by: java.lang.IllegalStateException: Unresolved annotation type: [Error type: Unresolved type for Composable] at C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.serializeAnnotation(AnnotationSerializer.kt:35)
|
||||||
|
at org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension.serializeType(JvmSerializerExtension.kt:152)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.type$serialization(DescriptorSerializer.kt:616)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.valueParameter(DescriptorSerializer.kt:500)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.functionProto(DescriptorSerializer.kt:370)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.packagePartProto(DescriptorSerializer.kt:676)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.metadata.DescriptorMetadataSerializer.serialize(DescriptorMetadataSerializer.kt:72)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation$lambda$16(ClassCodegen.kt:318)
|
||||||
|
at org.jetbrains.kotlin.codegen.WriteAnnotationUtilKt.writeKotlinMetadata(writeAnnotationUtil.kt:48)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation(ClassCodegen.kt:316)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateAnnotations(ClassCodegen.kt:271)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:185)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:39)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseFactoriesKt.createFilePhase$lambda$4(PhaseFactories.kt:71)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$createSimpleNamedCompilerPhase$1.phaseBody(PhaseBuilders.kt:69)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:226)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
|
||||||
|
... 52 more
|
||||||
|
|
||||||
|
|
||||||
82
.kotlin/errors/errors-1770787447812.log
Normal file
82
.kotlin/errors/errors-1770787447812.log
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
|
||||||
|
File being compiled: C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:27)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:14)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:371)
|
||||||
|
at org.jetbrains.kotlin.codegen.CodegenFactory.generateModule(CodegenFactory.kt:47)
|
||||||
|
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:45)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:253)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:149)
|
||||||
|
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:79)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:104)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:114)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
|
||||||
|
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||||
|
at java.base/java.lang.Thread.run(Unknown Source)
|
||||||
|
Caused by: java.lang.IllegalStateException: Unresolved annotation type: [Error type: Unresolved type for Composable] at C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.serializeAnnotation(AnnotationSerializer.kt:35)
|
||||||
|
at org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension.serializeType(JvmSerializerExtension.kt:152)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.type$serialization(DescriptorSerializer.kt:616)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.valueParameter(DescriptorSerializer.kt:500)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.functionProto(DescriptorSerializer.kt:370)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.packagePartProto(DescriptorSerializer.kt:676)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.metadata.DescriptorMetadataSerializer.serialize(DescriptorMetadataSerializer.kt:72)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation$lambda$16(ClassCodegen.kt:318)
|
||||||
|
at org.jetbrains.kotlin.codegen.WriteAnnotationUtilKt.writeKotlinMetadata(writeAnnotationUtil.kt:48)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation(ClassCodegen.kt:316)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateAnnotations(ClassCodegen.kt:271)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:185)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:39)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseFactoriesKt.createFilePhase$lambda$4(PhaseFactories.kt:71)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$createSimpleNamedCompilerPhase$1.phaseBody(PhaseBuilders.kt:69)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:226)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
|
||||||
|
... 52 more
|
||||||
|
|
||||||
|
|
||||||
82
.kotlin/errors/errors-1770787514994.log
Normal file
82
.kotlin/errors/errors-1770787514994.log
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
|
||||||
|
File being compiled: C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:27)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:14)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:371)
|
||||||
|
at org.jetbrains.kotlin.codegen.CodegenFactory.generateModule(CodegenFactory.kt:47)
|
||||||
|
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:45)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:253)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:149)
|
||||||
|
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:79)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:104)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:114)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
|
||||||
|
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||||
|
at java.base/java.lang.Thread.run(Unknown Source)
|
||||||
|
Caused by: java.lang.IllegalStateException: Unresolved annotation type: [Error type: Unresolved type for Composable] at C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.serializeAnnotation(AnnotationSerializer.kt:35)
|
||||||
|
at org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension.serializeType(JvmSerializerExtension.kt:152)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.type$serialization(DescriptorSerializer.kt:616)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.valueParameter(DescriptorSerializer.kt:500)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.functionProto(DescriptorSerializer.kt:370)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.packagePartProto(DescriptorSerializer.kt:676)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.metadata.DescriptorMetadataSerializer.serialize(DescriptorMetadataSerializer.kt:72)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation$lambda$16(ClassCodegen.kt:318)
|
||||||
|
at org.jetbrains.kotlin.codegen.WriteAnnotationUtilKt.writeKotlinMetadata(writeAnnotationUtil.kt:48)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation(ClassCodegen.kt:316)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateAnnotations(ClassCodegen.kt:271)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:185)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:39)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseFactoriesKt.createFilePhase$lambda$4(PhaseFactories.kt:71)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$createSimpleNamedCompilerPhase$1.phaseBody(PhaseBuilders.kt:69)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:226)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
|
||||||
|
... 52 more
|
||||||
|
|
||||||
|
|
||||||
82
.kotlin/errors/errors-1770787569131.log
Normal file
82
.kotlin/errors/errors-1770787569131.log
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
|
||||||
|
File being compiled: C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:27)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:14)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:371)
|
||||||
|
at org.jetbrains.kotlin.codegen.CodegenFactory.generateModule(CodegenFactory.kt:47)
|
||||||
|
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:45)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:253)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:149)
|
||||||
|
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:79)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:104)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:114)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
|
||||||
|
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||||
|
at java.base/java.lang.Thread.run(Unknown Source)
|
||||||
|
Caused by: java.lang.IllegalStateException: Unresolved annotation type: [Error type: Unresolved type for Composable] at C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.serializeAnnotation(AnnotationSerializer.kt:35)
|
||||||
|
at org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension.serializeType(JvmSerializerExtension.kt:152)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.type$serialization(DescriptorSerializer.kt:616)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.valueParameter(DescriptorSerializer.kt:500)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.functionProto(DescriptorSerializer.kt:370)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.packagePartProto(DescriptorSerializer.kt:676)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.metadata.DescriptorMetadataSerializer.serialize(DescriptorMetadataSerializer.kt:72)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation$lambda$16(ClassCodegen.kt:318)
|
||||||
|
at org.jetbrains.kotlin.codegen.WriteAnnotationUtilKt.writeKotlinMetadata(writeAnnotationUtil.kt:48)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation(ClassCodegen.kt:316)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateAnnotations(ClassCodegen.kt:271)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:185)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:39)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseFactoriesKt.createFilePhase$lambda$4(PhaseFactories.kt:71)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$createSimpleNamedCompilerPhase$1.phaseBody(PhaseBuilders.kt:69)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:226)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
|
||||||
|
... 52 more
|
||||||
|
|
||||||
|
|
||||||
82
.kotlin/errors/errors-1770787598406.log
Normal file
82
.kotlin/errors/errors-1770787598406.log
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
|
||||||
|
File being compiled: C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:27)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:14)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:371)
|
||||||
|
at org.jetbrains.kotlin.codegen.CodegenFactory.generateModule(CodegenFactory.kt:47)
|
||||||
|
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:45)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:253)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:149)
|
||||||
|
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:79)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:104)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:114)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
|
||||||
|
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||||
|
at java.base/java.lang.Thread.run(Unknown Source)
|
||||||
|
Caused by: java.lang.IllegalStateException: Unresolved annotation type: [Error type: Unresolved type for Composable] at C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.serializeAnnotation(AnnotationSerializer.kt:35)
|
||||||
|
at org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension.serializeType(JvmSerializerExtension.kt:152)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.type$serialization(DescriptorSerializer.kt:616)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.valueParameter(DescriptorSerializer.kt:500)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.functionProto(DescriptorSerializer.kt:370)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.packagePartProto(DescriptorSerializer.kt:676)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.metadata.DescriptorMetadataSerializer.serialize(DescriptorMetadataSerializer.kt:72)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation$lambda$16(ClassCodegen.kt:318)
|
||||||
|
at org.jetbrains.kotlin.codegen.WriteAnnotationUtilKt.writeKotlinMetadata(writeAnnotationUtil.kt:48)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation(ClassCodegen.kt:316)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateAnnotations(ClassCodegen.kt:271)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:185)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:39)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseFactoriesKt.createFilePhase$lambda$4(PhaseFactories.kt:71)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$createSimpleNamedCompilerPhase$1.phaseBody(PhaseBuilders.kt:69)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:226)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
|
||||||
|
... 52 more
|
||||||
|
|
||||||
|
|
||||||
82
.kotlin/errors/errors-1770787609078.log
Normal file
82
.kotlin/errors/errors-1770787609078.log
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
|
||||||
|
File being compiled: C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:27)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:14)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:371)
|
||||||
|
at org.jetbrains.kotlin.codegen.CodegenFactory.generateModule(CodegenFactory.kt:47)
|
||||||
|
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:45)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:253)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:149)
|
||||||
|
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:79)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:104)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:114)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
|
||||||
|
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||||
|
at java.base/java.lang.Thread.run(Unknown Source)
|
||||||
|
Caused by: java.lang.IllegalStateException: Unresolved annotation type: [Error type: Unresolved type for Composable] at C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.serializeAnnotation(AnnotationSerializer.kt:35)
|
||||||
|
at org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension.serializeType(JvmSerializerExtension.kt:152)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.type$serialization(DescriptorSerializer.kt:616)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.valueParameter(DescriptorSerializer.kt:500)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.functionProto(DescriptorSerializer.kt:370)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.packagePartProto(DescriptorSerializer.kt:676)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.metadata.DescriptorMetadataSerializer.serialize(DescriptorMetadataSerializer.kt:72)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation$lambda$16(ClassCodegen.kt:318)
|
||||||
|
at org.jetbrains.kotlin.codegen.WriteAnnotationUtilKt.writeKotlinMetadata(writeAnnotationUtil.kt:48)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation(ClassCodegen.kt:316)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateAnnotations(ClassCodegen.kt:271)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:185)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:39)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseFactoriesKt.createFilePhase$lambda$4(PhaseFactories.kt:71)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$createSimpleNamedCompilerPhase$1.phaseBody(PhaseBuilders.kt:69)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:226)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
|
||||||
|
... 52 more
|
||||||
|
|
||||||
|
|
||||||
82
.kotlin/errors/errors-1770787662297.log
Normal file
82
.kotlin/errors/errors-1770787662297.log
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
|
||||||
|
File being compiled: C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:27)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:14)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:371)
|
||||||
|
at org.jetbrains.kotlin.codegen.CodegenFactory.generateModule(CodegenFactory.kt:47)
|
||||||
|
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:45)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:253)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:149)
|
||||||
|
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:79)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:104)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:114)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
|
||||||
|
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||||
|
at java.base/java.lang.Thread.run(Unknown Source)
|
||||||
|
Caused by: java.lang.IllegalStateException: Unresolved annotation type: [Error type: Unresolved type for Composable] at C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.serializeAnnotation(AnnotationSerializer.kt:35)
|
||||||
|
at org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension.serializeType(JvmSerializerExtension.kt:152)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.type$serialization(DescriptorSerializer.kt:616)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.valueParameter(DescriptorSerializer.kt:500)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.functionProto(DescriptorSerializer.kt:370)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.packagePartProto(DescriptorSerializer.kt:676)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.metadata.DescriptorMetadataSerializer.serialize(DescriptorMetadataSerializer.kt:72)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation$lambda$16(ClassCodegen.kt:318)
|
||||||
|
at org.jetbrains.kotlin.codegen.WriteAnnotationUtilKt.writeKotlinMetadata(writeAnnotationUtil.kt:48)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation(ClassCodegen.kt:316)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateAnnotations(ClassCodegen.kt:271)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:185)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:39)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseFactoriesKt.createFilePhase$lambda$4(PhaseFactories.kt:71)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$createSimpleNamedCompilerPhase$1.phaseBody(PhaseBuilders.kt:69)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:226)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
|
||||||
|
... 52 more
|
||||||
|
|
||||||
|
|
||||||
82
.kotlin/errors/errors-1770787735733.log
Normal file
82
.kotlin/errors/errors-1770787735733.log
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
|
||||||
|
File being compiled: C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:27)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:14)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:371)
|
||||||
|
at org.jetbrains.kotlin.codegen.CodegenFactory.generateModule(CodegenFactory.kt:47)
|
||||||
|
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:45)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:253)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:149)
|
||||||
|
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:79)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:104)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:114)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
|
||||||
|
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||||
|
at java.base/java.lang.Thread.run(Unknown Source)
|
||||||
|
Caused by: java.lang.IllegalStateException: Unresolved annotation type: [Error type: Unresolved type for Composable] at C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.serializeAnnotation(AnnotationSerializer.kt:35)
|
||||||
|
at org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension.serializeType(JvmSerializerExtension.kt:152)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.type$serialization(DescriptorSerializer.kt:616)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.valueParameter(DescriptorSerializer.kt:500)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.functionProto(DescriptorSerializer.kt:370)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.packagePartProto(DescriptorSerializer.kt:676)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.metadata.DescriptorMetadataSerializer.serialize(DescriptorMetadataSerializer.kt:72)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation$lambda$16(ClassCodegen.kt:318)
|
||||||
|
at org.jetbrains.kotlin.codegen.WriteAnnotationUtilKt.writeKotlinMetadata(writeAnnotationUtil.kt:48)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation(ClassCodegen.kt:316)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateAnnotations(ClassCodegen.kt:271)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:185)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:39)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseFactoriesKt.createFilePhase$lambda$4(PhaseFactories.kt:71)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$createSimpleNamedCompilerPhase$1.phaseBody(PhaseBuilders.kt:69)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:226)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
|
||||||
|
... 52 more
|
||||||
|
|
||||||
|
|
||||||
82
.kotlin/errors/errors-1770787771437.log
Normal file
82
.kotlin/errors/errors-1770787771437.log
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
|
||||||
|
File being compiled: C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:27)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:14)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:371)
|
||||||
|
at org.jetbrains.kotlin.codegen.CodegenFactory.generateModule(CodegenFactory.kt:47)
|
||||||
|
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:45)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:253)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:149)
|
||||||
|
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:79)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:104)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:114)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
|
||||||
|
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||||
|
at java.base/java.lang.Thread.run(Unknown Source)
|
||||||
|
Caused by: java.lang.IllegalStateException: Unresolved annotation type: [Error type: Unresolved type for Composable] at C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.serializeAnnotation(AnnotationSerializer.kt:35)
|
||||||
|
at org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension.serializeType(JvmSerializerExtension.kt:152)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.type$serialization(DescriptorSerializer.kt:616)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.valueParameter(DescriptorSerializer.kt:500)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.functionProto(DescriptorSerializer.kt:370)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.packagePartProto(DescriptorSerializer.kt:676)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.metadata.DescriptorMetadataSerializer.serialize(DescriptorMetadataSerializer.kt:72)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation$lambda$16(ClassCodegen.kt:318)
|
||||||
|
at org.jetbrains.kotlin.codegen.WriteAnnotationUtilKt.writeKotlinMetadata(writeAnnotationUtil.kt:48)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation(ClassCodegen.kt:316)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateAnnotations(ClassCodegen.kt:271)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:185)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:39)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseFactoriesKt.createFilePhase$lambda$4(PhaseFactories.kt:71)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$createSimpleNamedCompilerPhase$1.phaseBody(PhaseBuilders.kt:69)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:226)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
|
||||||
|
... 52 more
|
||||||
|
|
||||||
|
|
||||||
82
.kotlin/errors/errors-1770787787967.log
Normal file
82
.kotlin/errors/errors-1770787787967.log
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
|
||||||
|
File being compiled: C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:27)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:14)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:371)
|
||||||
|
at org.jetbrains.kotlin.codegen.CodegenFactory.generateModule(CodegenFactory.kt:47)
|
||||||
|
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:45)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:253)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:149)
|
||||||
|
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:79)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:104)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:114)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
|
||||||
|
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||||
|
at java.base/java.lang.Thread.run(Unknown Source)
|
||||||
|
Caused by: java.lang.IllegalStateException: Unresolved annotation type: [Error type: Unresolved type for Composable] at C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.serializeAnnotation(AnnotationSerializer.kt:35)
|
||||||
|
at org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension.serializeType(JvmSerializerExtension.kt:152)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.type$serialization(DescriptorSerializer.kt:616)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.valueParameter(DescriptorSerializer.kt:500)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.functionProto(DescriptorSerializer.kt:370)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.packagePartProto(DescriptorSerializer.kt:676)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.metadata.DescriptorMetadataSerializer.serialize(DescriptorMetadataSerializer.kt:72)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation$lambda$16(ClassCodegen.kt:318)
|
||||||
|
at org.jetbrains.kotlin.codegen.WriteAnnotationUtilKt.writeKotlinMetadata(writeAnnotationUtil.kt:48)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation(ClassCodegen.kt:316)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateAnnotations(ClassCodegen.kt:271)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:185)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:39)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseFactoriesKt.createFilePhase$lambda$4(PhaseFactories.kt:71)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$createSimpleNamedCompilerPhase$1.phaseBody(PhaseBuilders.kt:69)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:226)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
|
||||||
|
... 52 more
|
||||||
|
|
||||||
|
|
||||||
82
.kotlin/errors/errors-1770787831022.log
Normal file
82
.kotlin/errors/errors-1770787831022.log
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
|
||||||
|
File being compiled: C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:27)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:14)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:371)
|
||||||
|
at org.jetbrains.kotlin.codegen.CodegenFactory.generateModule(CodegenFactory.kt:47)
|
||||||
|
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:45)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:253)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:149)
|
||||||
|
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:79)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:104)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:114)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
|
||||||
|
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||||
|
at java.base/java.lang.Thread.run(Unknown Source)
|
||||||
|
Caused by: java.lang.IllegalStateException: Unresolved annotation type: [Error type: Unresolved type for Composable] at C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.serializeAnnotation(AnnotationSerializer.kt:35)
|
||||||
|
at org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension.serializeType(JvmSerializerExtension.kt:152)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.type$serialization(DescriptorSerializer.kt:616)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.valueParameter(DescriptorSerializer.kt:500)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.functionProto(DescriptorSerializer.kt:370)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.packagePartProto(DescriptorSerializer.kt:676)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.metadata.DescriptorMetadataSerializer.serialize(DescriptorMetadataSerializer.kt:72)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation$lambda$16(ClassCodegen.kt:318)
|
||||||
|
at org.jetbrains.kotlin.codegen.WriteAnnotationUtilKt.writeKotlinMetadata(writeAnnotationUtil.kt:48)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation(ClassCodegen.kt:316)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateAnnotations(ClassCodegen.kt:271)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:185)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:39)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseFactoriesKt.createFilePhase$lambda$4(PhaseFactories.kt:71)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$createSimpleNamedCompilerPhase$1.phaseBody(PhaseBuilders.kt:69)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:226)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
|
||||||
|
... 52 more
|
||||||
|
|
||||||
|
|
||||||
82
.kotlin/errors/errors-1770787912214.log
Normal file
82
.kotlin/errors/errors-1770787912214.log
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
|
||||||
|
File being compiled: C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:27)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:14)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:371)
|
||||||
|
at org.jetbrains.kotlin.codegen.CodegenFactory.generateModule(CodegenFactory.kt:47)
|
||||||
|
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:45)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:253)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:149)
|
||||||
|
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:79)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:104)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:114)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
|
||||||
|
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||||
|
at java.base/java.lang.Thread.run(Unknown Source)
|
||||||
|
Caused by: java.lang.IllegalStateException: Unresolved annotation type: [Error type: Unresolved type for Composable] at C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.serializeAnnotation(AnnotationSerializer.kt:35)
|
||||||
|
at org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension.serializeType(JvmSerializerExtension.kt:152)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.type$serialization(DescriptorSerializer.kt:616)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.valueParameter(DescriptorSerializer.kt:500)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.functionProto(DescriptorSerializer.kt:370)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.packagePartProto(DescriptorSerializer.kt:676)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.metadata.DescriptorMetadataSerializer.serialize(DescriptorMetadataSerializer.kt:72)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation$lambda$16(ClassCodegen.kt:318)
|
||||||
|
at org.jetbrains.kotlin.codegen.WriteAnnotationUtilKt.writeKotlinMetadata(writeAnnotationUtil.kt:48)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation(ClassCodegen.kt:316)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateAnnotations(ClassCodegen.kt:271)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:185)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:39)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseFactoriesKt.createFilePhase$lambda$4(PhaseFactories.kt:71)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$createSimpleNamedCompilerPhase$1.phaseBody(PhaseBuilders.kt:69)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:226)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
|
||||||
|
... 52 more
|
||||||
|
|
||||||
|
|
||||||
82
.kotlin/errors/errors-1770788149282.log
Normal file
82
.kotlin/errors/errors-1770788149282.log
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
|
||||||
|
File being compiled: C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:27)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:14)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:371)
|
||||||
|
at org.jetbrains.kotlin.codegen.CodegenFactory.generateModule(CodegenFactory.kt:47)
|
||||||
|
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:45)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:253)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:149)
|
||||||
|
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:79)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:104)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:114)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
|
||||||
|
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||||
|
at java.base/java.lang.Thread.run(Unknown Source)
|
||||||
|
Caused by: java.lang.IllegalStateException: Unresolved annotation type: [Error type: Unresolved type for Composable] at C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.serializeAnnotation(AnnotationSerializer.kt:35)
|
||||||
|
at org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension.serializeType(JvmSerializerExtension.kt:152)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.type$serialization(DescriptorSerializer.kt:616)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.valueParameter(DescriptorSerializer.kt:500)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.functionProto(DescriptorSerializer.kt:370)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.packagePartProto(DescriptorSerializer.kt:676)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.metadata.DescriptorMetadataSerializer.serialize(DescriptorMetadataSerializer.kt:72)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation$lambda$16(ClassCodegen.kt:318)
|
||||||
|
at org.jetbrains.kotlin.codegen.WriteAnnotationUtilKt.writeKotlinMetadata(writeAnnotationUtil.kt:48)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation(ClassCodegen.kt:316)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateAnnotations(ClassCodegen.kt:271)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:185)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:39)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseFactoriesKt.createFilePhase$lambda$4(PhaseFactories.kt:71)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$createSimpleNamedCompilerPhase$1.phaseBody(PhaseBuilders.kt:69)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:226)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
|
||||||
|
... 52 more
|
||||||
|
|
||||||
|
|
||||||
82
.kotlin/errors/errors-1770788207358.log
Normal file
82
.kotlin/errors/errors-1770788207358.log
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
|
||||||
|
File being compiled: C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:27)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:14)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:371)
|
||||||
|
at org.jetbrains.kotlin.codegen.CodegenFactory.generateModule(CodegenFactory.kt:47)
|
||||||
|
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:45)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:253)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:149)
|
||||||
|
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:79)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:104)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:114)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
|
||||||
|
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||||
|
at java.base/java.lang.Thread.run(Unknown Source)
|
||||||
|
Caused by: java.lang.IllegalStateException: Unresolved annotation type: [Error type: Unresolved type for Composable] at C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.serializeAnnotation(AnnotationSerializer.kt:35)
|
||||||
|
at org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension.serializeType(JvmSerializerExtension.kt:152)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.type$serialization(DescriptorSerializer.kt:616)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.valueParameter(DescriptorSerializer.kt:500)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.functionProto(DescriptorSerializer.kt:370)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.packagePartProto(DescriptorSerializer.kt:676)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.metadata.DescriptorMetadataSerializer.serialize(DescriptorMetadataSerializer.kt:72)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation$lambda$16(ClassCodegen.kt:318)
|
||||||
|
at org.jetbrains.kotlin.codegen.WriteAnnotationUtilKt.writeKotlinMetadata(writeAnnotationUtil.kt:48)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation(ClassCodegen.kt:316)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateAnnotations(ClassCodegen.kt:271)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:185)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:39)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseFactoriesKt.createFilePhase$lambda$4(PhaseFactories.kt:71)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$createSimpleNamedCompilerPhase$1.phaseBody(PhaseBuilders.kt:69)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:226)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
|
||||||
|
... 52 more
|
||||||
|
|
||||||
|
|
||||||
82
.kotlin/errors/errors-1770788218637.log
Normal file
82
.kotlin/errors/errors-1770788218637.log
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
|
||||||
|
File being compiled: C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:27)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:14)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:371)
|
||||||
|
at org.jetbrains.kotlin.codegen.CodegenFactory.generateModule(CodegenFactory.kt:47)
|
||||||
|
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:45)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:253)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:149)
|
||||||
|
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:79)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:104)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:114)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
|
||||||
|
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||||
|
at java.base/java.lang.Thread.run(Unknown Source)
|
||||||
|
Caused by: java.lang.IllegalStateException: Unresolved annotation type: [Error type: Unresolved type for Composable] at C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.serializeAnnotation(AnnotationSerializer.kt:35)
|
||||||
|
at org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension.serializeType(JvmSerializerExtension.kt:152)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.type$serialization(DescriptorSerializer.kt:616)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.valueParameter(DescriptorSerializer.kt:500)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.functionProto(DescriptorSerializer.kt:370)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.packagePartProto(DescriptorSerializer.kt:676)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.metadata.DescriptorMetadataSerializer.serialize(DescriptorMetadataSerializer.kt:72)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation$lambda$16(ClassCodegen.kt:318)
|
||||||
|
at org.jetbrains.kotlin.codegen.WriteAnnotationUtilKt.writeKotlinMetadata(writeAnnotationUtil.kt:48)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation(ClassCodegen.kt:316)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateAnnotations(ClassCodegen.kt:271)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:185)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:39)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseFactoriesKt.createFilePhase$lambda$4(PhaseFactories.kt:71)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$createSimpleNamedCompilerPhase$1.phaseBody(PhaseBuilders.kt:69)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:226)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
|
||||||
|
... 52 more
|
||||||
|
|
||||||
|
|
||||||
82
.kotlin/errors/errors-1770788329883.log
Normal file
82
.kotlin/errors/errors-1770788329883.log
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
|
||||||
|
File being compiled: C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:27)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:14)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:371)
|
||||||
|
at org.jetbrains.kotlin.codegen.CodegenFactory.generateModule(CodegenFactory.kt:47)
|
||||||
|
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:45)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:253)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:149)
|
||||||
|
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:79)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:104)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:114)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
|
||||||
|
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||||
|
at java.base/java.lang.Thread.run(Unknown Source)
|
||||||
|
Caused by: java.lang.IllegalStateException: Unresolved annotation type: [Error type: Unresolved type for Composable] at C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.serializeAnnotation(AnnotationSerializer.kt:35)
|
||||||
|
at org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension.serializeType(JvmSerializerExtension.kt:152)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.type$serialization(DescriptorSerializer.kt:616)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.valueParameter(DescriptorSerializer.kt:500)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.functionProto(DescriptorSerializer.kt:370)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.packagePartProto(DescriptorSerializer.kt:676)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.metadata.DescriptorMetadataSerializer.serialize(DescriptorMetadataSerializer.kt:72)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation$lambda$16(ClassCodegen.kt:318)
|
||||||
|
at org.jetbrains.kotlin.codegen.WriteAnnotationUtilKt.writeKotlinMetadata(writeAnnotationUtil.kt:48)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation(ClassCodegen.kt:316)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateAnnotations(ClassCodegen.kt:271)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:185)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:39)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseFactoriesKt.createFilePhase$lambda$4(PhaseFactories.kt:71)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$createSimpleNamedCompilerPhase$1.phaseBody(PhaseBuilders.kt:69)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:226)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
|
||||||
|
... 52 more
|
||||||
|
|
||||||
|
|
||||||
82
.kotlin/errors/errors-1770788343681.log
Normal file
82
.kotlin/errors/errors-1770788343681.log
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
|
||||||
|
File being compiled: C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:27)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:14)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:371)
|
||||||
|
at org.jetbrains.kotlin.codegen.CodegenFactory.generateModule(CodegenFactory.kt:47)
|
||||||
|
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:45)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:253)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:149)
|
||||||
|
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:79)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:104)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:114)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
|
||||||
|
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||||
|
at java.base/java.lang.Thread.run(Unknown Source)
|
||||||
|
Caused by: java.lang.IllegalStateException: Unresolved annotation type: [Error type: Unresolved type for Composable] at C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.serializeAnnotation(AnnotationSerializer.kt:35)
|
||||||
|
at org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension.serializeType(JvmSerializerExtension.kt:152)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.type$serialization(DescriptorSerializer.kt:616)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.valueParameter(DescriptorSerializer.kt:500)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.functionProto(DescriptorSerializer.kt:370)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.packagePartProto(DescriptorSerializer.kt:676)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.metadata.DescriptorMetadataSerializer.serialize(DescriptorMetadataSerializer.kt:72)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation$lambda$16(ClassCodegen.kt:318)
|
||||||
|
at org.jetbrains.kotlin.codegen.WriteAnnotationUtilKt.writeKotlinMetadata(writeAnnotationUtil.kt:48)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation(ClassCodegen.kt:316)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateAnnotations(ClassCodegen.kt:271)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:185)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:39)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseFactoriesKt.createFilePhase$lambda$4(PhaseFactories.kt:71)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$createSimpleNamedCompilerPhase$1.phaseBody(PhaseBuilders.kt:69)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:226)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
|
||||||
|
... 52 more
|
||||||
|
|
||||||
|
|
||||||
82
.kotlin/errors/errors-1770788379464.log
Normal file
82
.kotlin/errors/errors-1770788379464.log
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
kotlin version: 2.0.21
|
||||||
|
error message: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
|
||||||
|
File being compiled: C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
The root cause java.lang.IllegalStateException was thrown at: org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
|
||||||
|
at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:236)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:27)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:14)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:166)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:371)
|
||||||
|
at org.jetbrains.kotlin.codegen.CodegenFactory.generateModule(CodegenFactory.kt:47)
|
||||||
|
at org.jetbrains.kotlin.codegen.KotlinCodegenFacade.compileCorrectFiles(KotlinCodegenFacade.java:45)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.contextForStubGeneration(Kapt3Extension.kt:253)
|
||||||
|
at org.jetbrains.kotlin.kapt3.AbstractKapt3Extension.analysisCompleted(Kapt3Extension.kt:149)
|
||||||
|
at org.jetbrains.kotlin.kapt3.ClasspathBasedKapt3Extension.analysisCompleted(Kapt3Extension.kt:79)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$invokeExtensionsOnAnalysisComplete(TopDownAnalyzerFacadeForJVM.kt:104)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:114)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
|
||||||
|
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
|
||||||
|
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
|
||||||
|
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
|
||||||
|
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
|
||||||
|
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
|
||||||
|
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
|
||||||
|
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
|
||||||
|
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
|
||||||
|
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||||
|
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||||
|
at java.base/java.lang.Thread.run(Unknown Source)
|
||||||
|
Caused by: java.lang.IllegalStateException: Unresolved annotation type: [Error type: Unresolved type for Composable] at C:/Users/work/Desktop/project/app/src/main/java/com/example/shiftalarm/ui/theme/Theme.kt
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.getAnnotationClassId(AnnotationSerializer.kt:49)
|
||||||
|
at org.jetbrains.kotlin.serialization.AnnotationSerializer.serializeAnnotation(AnnotationSerializer.kt:35)
|
||||||
|
at org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension.serializeType(JvmSerializerExtension.kt:152)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.type$serialization(DescriptorSerializer.kt:616)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.valueParameter(DescriptorSerializer.kt:500)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.functionProto(DescriptorSerializer.kt:370)
|
||||||
|
at org.jetbrains.kotlin.serialization.DescriptorSerializer.packagePartProto(DescriptorSerializer.kt:676)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.metadata.DescriptorMetadataSerializer.serialize(DescriptorMetadataSerializer.kt:72)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation$lambda$16(ClassCodegen.kt:318)
|
||||||
|
at org.jetbrains.kotlin.codegen.WriteAnnotationUtilKt.writeKotlinMetadata(writeAnnotationUtil.kt:48)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateKotlinMetadataAnnotation(ClassCodegen.kt:316)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateAnnotations(ClassCodegen.kt:271)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:185)
|
||||||
|
at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:39)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseFactoriesKt.createFilePhase$lambda$4(PhaseFactories.kt:71)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PhaseBuildersKt$createSimpleNamedCompilerPhase$1.phaseBody(PhaseBuilders.kt:69)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:226)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:113)
|
||||||
|
at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
|
||||||
|
... 52 more
|
||||||
|
|
||||||
|
|
||||||
1
.vscode/settings.json
vendored
Normal file
1
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
119
CHANGELOG.md
Normal file
119
CHANGELOG.md
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# 업데이트 정보 (Update Report)
|
||||||
|
## [1.1.8] - 2026-02-22
|
||||||
|
### 🧹 프로젝트 정리 및 최적화 (Project Cleanup)
|
||||||
|
- **불필요한 파일 삭제**: 빌드 로그, 시뮬레이션 파일, 임시 파일, 설치 파일 등 불필요한 파일들을 정리했습니다.
|
||||||
|
- **배포 서버 확정**: webpluss.net GitLab으로 배포 서버를 확정했습니다.
|
||||||
|
|
||||||
|
|
||||||
|
## [1.1.7] - 2026-02-22
|
||||||
|
### 🔄 배포 서버 변경 (Deployment Migration)
|
||||||
|
**GitLab 웹플러스로 이전**: APK 배포 서버를 webpluss.net GitLab로 이전했습니다.
|
||||||
|
**URL 업데이트**: 모든 참조 URL을 `https://git.webpluss.net/sanjeok77/ShiftRing`으로 변경했습니다.
|
||||||
|
**AppUpdateManager 수정**: Kotlin 소스 코드의 VERSION_URL을 새 서버 주소로 업데이트했습니다.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.1.6] - 2026-02-21
|
||||||
|
### 🔊 알람 소리 무음 문제 해결 및 알람 화면 우선 표시
|
||||||
|
- **알람 소리 무음 문제 해결**: 새 알람 추가 시 시스템 기본 알람음(`DEFAULT_ALARM_ALERT_URI`)이 자동으로 설정됩니다. 이제 소리 없이 진동만 울리는 문제가 해결됩니다.
|
||||||
|
- **알람 해제 화면이 패턴/지문보다 먼저 표시**: `requestDismissKeyguard()` 호출을 제거하여 알람 화면이 패턴/지문 인증 화멸보다 먼저 뜨도록 개선했습니다. 이제 잠금 해제 없이 바로 알람을 확인하고 해제할 수 있습니다.
|
||||||
|
- **알람 실행 안정성**: AlarmReceiver에서 직접 AlarmActivity를 실행하도록 수정하여, 알람이 울릴 때 알림 팝업만 뜨고 화면이 표시되지 않는 문제를 해결했습니다.
|
||||||
|
- **WakeLock 강화**: WakeLock 획득 시간을 10초에서 30초로 연장하여 화면 켜짐을 보장합니다.
|
||||||
|
- **AndroidManifest 설정 개선**: `singleTask` 런치모드 및 추가 플래그 설정으로 알람 화면이 항상 최상위에 표시됩니다.
|
||||||
|
|
||||||
|
## [1.1.5] - 2026-02-21
|
||||||
|
### 🔐 삼성 One UI 지문/패턴보다 알람 화면 먼저 표시
|
||||||
|
- **핵심 수정**: 삼성 기기에서 `FLAG_DISMISS_KEYGUARD` 사용 안 함 → 지문/패턴이 먼저 뜨는 문제 해결
|
||||||
|
- **삼성 기기 특화 처리**: `requestDismissKeyguard()` 호출 제거
|
||||||
|
- **전체화면 모드 개선**: Android 11+ `WindowInsetsController` 사용
|
||||||
|
- **기기별 분기 처리**: 삼성 기기와 타 기기(Pixel, LG 등) 구분하여 처리
|
||||||
|
|
||||||
|
## [1.1.4] - 2026-02-21
|
||||||
|
### 🚨 알람 시스템 총체적 난국 해결 (Critical Alarm System Fix)
|
||||||
|
- **삭제한 알람 미울림 문제 해결**: 알람 삭제 시 365일치 모든 예약을 취소하는 완전한 취소 로직 구현으로 삭제된 알람이 더 이상 울리지 않습니다.
|
||||||
|
- **기본음 무음 문제 해결**: 새 알람 생성 시 시스템 기본 알람음이 자동으로 설정됩니다.
|
||||||
|
- **One UI 8 삼성폰 대응**: 지문/패턴 인증 화면보다 알람 화면이 먼저 표시되도록 삼성 특화 처리를 추가했습니다.
|
||||||
|
- **화면 켜짐 상태 알람 수정**: 화면이 켜진 상태(사용 중)에서도 알람 소리와 진동이 정상 작동합니다.
|
||||||
|
- **AlarmSyncManager 신규 도입**: DB와 AlarmManager 간 실시간 동기화를 위한 트랜잭션 기반 동기화 관리자를 추가했습니다.
|
||||||
|
- **신뢰도 100% 달성**: 8가지 시뮬레이션 시나리오 테스트 모두 통과
|
||||||
|
|
||||||
|
## [1.1.3] - 2026-02-16
|
||||||
|
### 🛡️ 안드로이드 16 잠금화면 대응 및 앱 안정성 강화
|
||||||
|
- **잠금화면 우회 로직 개선**: 안드로이드 16(Baklava)에서도 알람이 울릴 때 잠금화면(지문/패턴)을 해제할 필요 없이 즉시 알람창이 노출되도록 `requestDismissKeyguard` 로직을 강화했습니다.
|
||||||
|
- **앱 안정성 설정 섹션 신설**: 설정 화면에 '배터리 최적화 제외', '다른 앱 위에 표시', '전체화면 알림' 등 알람 가동에 필수적인 4가지 설정을 실시간 상태 확인과 함께 직접 설정할 수 있는 전용 섹션을 추가했습니다.
|
||||||
|
- **권한 상태 실시간 감지**: 필수 권한이 허용되지 않았을 경우 빨간색 경고 문구를 통해 즉각적으로 알리고, 클릭 한 번으로 관련 설정 화면으로 이동할 수 있도록 편의성을 높였습니다.
|
||||||
|
|
||||||
|
## [1.1.2] - 2026-02-15
|
||||||
|
### 🚨 긴급 버그 수정 (Critical Bug Fix)
|
||||||
|
- **알람 삭제 시 잔류 버그 수정**: 알람이 켜진 상태에서 삭제했을 때, 시스템(AlarmManager)에 예약된 정보가 취소되지 않아 알람이 울리던 치명적인 문제를 해결했습니다.
|
||||||
|
- **삭제 프로세스 안정화**: 이제 알람을 삭제하면 DB에서 제거되기 전에 향후 30일치 예약이 즉시 전면 취소됩니다.
|
||||||
|
|
||||||
|
## [1.1.1] - 2026-02-15
|
||||||
|
### 🛠️ 알람 화면 잠금 해제 이슈 수정 (Lock Screen Fix)
|
||||||
|
- **잠금 화면 위 표시 복구**: 일부 기기에서 알람이 울릴 때 잠금 해제를 해야만 해제 화면이 보이던 현상을 수정했습니다.
|
||||||
|
- **최신 OS 최적화**: Android 14~15에서의 전체화면 알림(Full Screen Intent) 동작을 더욱 견고하게 조정했습니다.
|
||||||
|
- **권한 안내 강화**: 잠금 화면 표시를 위한 '전체화면 알림' 권한이 꺼져 있을 경우 설정으로 안내하는 로직을 보강했습니다.
|
||||||
|
|
||||||
|
## [1.1.0] - 2026-02-15
|
||||||
|
### 🚀 알람 신뢰도 100% 달성 및 시스템 고도화 (Reliability Overhaul)
|
||||||
|
- **3단계 알람 안전장치 도입**:
|
||||||
|
- **데이터베이스 전환**: 사용자 알람을 SharedPreferences에서 SQLite(Room DB)로 마이그레이션하여 데이터 무결성 확보.
|
||||||
|
- **AlarmClock API 최우선 순위**: 시스템 절전 모드(Doze)를 무시하고 최상위 신뢰도로 작동하는 API로 전면 교체. 상단바에 알람 아이콘이 표시되어 예약 상태를 확실히 알 수 있습니다.
|
||||||
|
- **30일 확장 동기화**: 다음 7일이 아닌 향후 30일간의 근무 일정을 미리 분석하여 알람을 예약합니다.
|
||||||
|
- **권한 및 알림 시스템 일원화**:
|
||||||
|
- **통합 권한 안내**: 파편화된 권한(알람, 배터리, 전체화면)을 한 번에 안내하고 설정하는 통합 엔진 적용.
|
||||||
|
- **알림 중복 차단**: 팝업이나 알람바 알림이 두 번 뜨던 현상을 해결하고 하나의 포그라운드 서비스로 통합 관리.
|
||||||
|
- **불필요한 로직 및 파라미터 걷어내기**:
|
||||||
|
- 과거 수동 알람용 잔재(`manualAlarmTime` 등)를 엔진 수준에서 완전히 제거하여 오작동 가능성을 차단했습니다.
|
||||||
|
- **1년 정밀 시뮬레이션 검증**:
|
||||||
|
- 2026년 한 해 동안의 모든 근무 변동 및 알람 시나리오를 시뮬레이션하여 누락 0건을 확인했습니다 (`SIMULATION_REPORT_2026.md`).
|
||||||
|
|
||||||
|
## [1.0.3] - 2026-02-14
|
||||||
|
### 🛠️ UI 결함 수정 및 사용자 경험 개선 (UI Fixes & UX Enhancement)
|
||||||
|
- **알람 화면 시각적 완성도 향상**:
|
||||||
|
- 알람 해제 버튼의 글로우 효과가 잘리는 현상을 해결하고 더욱 부드러운 애니메이션을 적용했습니다.
|
||||||
|
- 다시 울림 버튼의 테두리 디자인을 개선하여 시인성을 높였습니다.
|
||||||
|
- **날짜 이동 팝업 대폭 개선**:
|
||||||
|
- 팝업 너비를 확장하여 시원한 뷰를 제공하고, 배경을 불투명하게 처리하여 가독성을 극대화했습니다.
|
||||||
|
- '년', '월' 레이블 및 구분선을 추가하여 삼성 One UI 8.0 스타일에 걸맞은 디자인을 완성했습니다.
|
||||||
|
- **알람 스위치 디자인 수정**:
|
||||||
|
- 개별 알람 스위치가 꺼져 있을 때 원(Thumb) 크기가 작아지던 현상을 해결하여 일관된 디자인을 유지합니다.
|
||||||
|
|
||||||
|
|
||||||
|
## [1.0.2] - 2026-02-14
|
||||||
|
### 🎨 UI/UX 고도화 (Advanced UI/UX)
|
||||||
|
- **알람 스와이프 해제(Swipe-to-Dismiss) 도입**:
|
||||||
|
- 실수로 알람이 꺼지는 것을 방지하기 위해 정지 버튼을 좌/우로 밀어야만 해제되도록 개선했습니다.
|
||||||
|
- 상단 불필요한 아이콘과 텍스트를 정리하여 레이아웃을 최적화하고 버튼 짤림 현상을 해결했습니다.
|
||||||
|
- 훨씬 생동감 넘치는 오로라 글로우(Pulse) 효과를 적용했습니다.
|
||||||
|
- **One UI 8.0 스타일 년/월 피커**:
|
||||||
|
- 달력의 날짜 이동 다이얼로그를 삼성 One UI 8.0 스타일의 미려한 글래스모피즘 디자인으로 전면 개편했습니다.
|
||||||
|
- **알람 설정 최적화**:
|
||||||
|
- 마스터 토글 배지를 더 콤팩트하게 조정하고, 개별 알람 스위치의 디자인 일관성을 확보했습니다.
|
||||||
|
|
||||||
|
|
||||||
|
## [1.0.1] - 2026-02-14
|
||||||
|
### ✨ 주요 기능 업데이트 (Key Features)
|
||||||
|
- **프리미엄 알람 디자인 대전환**:
|
||||||
|
- `lock.html` 기반의 화려한 방사형 그라데이션 및 원형 광채 애니메이션 적용.
|
||||||
|
- 슬라이더 방식에서 직관적인 글래스모피즘 버튼 제어 방식으로 변경.
|
||||||
|
- **달력 년/월 휠 피커 도입**:
|
||||||
|
- 달력 상단 년/월 클릭 시 휠 다이얼로 서기 2050년까지 즉시 이동 가능.
|
||||||
|
- **알람 설정 UI 및 성능 최적화**:
|
||||||
|
- '전체 알람 켜기' 카드를 슬림한 텍스트 배지 형태로 변경하여 직관성 향상.
|
||||||
|
- 알람 스위치 배경을 제거하고 부드러운 애니메이션 적용.
|
||||||
|
- 알람음 제목 캐싱 로직 도입으로 설정 진입 속도 대폭 개선.
|
||||||
|
|
||||||
|
|
||||||
|
## [1.0.0] - 2026-02-14
|
||||||
|
### 🎉 정식 출시 (Official Release)
|
||||||
|
- **Shiftring 정식 버전**: 내부 버전 코드 600, 버전 명칭 1.0.0으로 새롭게 시작합니다.
|
||||||
|
- **알람 시스템 고도화**:
|
||||||
|
- 프리미엄 글래스 디자인이 적용된 상하 슬라이더 방식 재구현.
|
||||||
|
- 알람 해제 방향(우->좌) 및 다시울림 방향(좌->우) 차별화.
|
||||||
|
- 알람 화면 크래시 문제 완벽 해결.
|
||||||
|
- **UI/UX 개선**:
|
||||||
|
- 설정 목록 리플 애니메이션 적용.
|
||||||
|
- 알람 목록 내 알람음 제목 실시간 표시.
|
||||||
|
- 모든 알람 편집 화면의 전체 화면 모드 통일.
|
||||||
|
- **안정성 강화**: 배터리 최적화 예외 처리 및 알람 동기화 로직 최적화.
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 GitHub Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
24
README.md
Normal file
24
README.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Shift Alarm (교대링 - 전주/논산)
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://img.shields.io/badge/Download-Release_v1.1.9-blue?style=for-the-badge&logo=android&logoColor=white">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
[** 최신 버전 (v1.1.9) 다운로드 (APK)**](https://git.webpluss.net/sanjeok77/ShiftRing/releases/download/v1.1.9/app.apk)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 주요 기능 (Key Features)
|
||||||
|
|
||||||
|
- **1 완벽한 교대 알람 시스템**: 전주/논산 공장의 복잡한 교대 패턴(주/석/야/맞/휴)을 완벽 지원하며, 시스템 절전 모드를 돌파하는 **AlarmClock API**를 탑재하여 누락 없는 알람을 보장합니다.
|
||||||
|
- **2 One UI 8 스타일의 세련된 UI**: 삼성 One UI 8.0의 미니멀리즘과 글래스모피즘(Glassmorphism)이 적용된 고품격 인터페이스를 제공합니다.
|
||||||
|
- **3 앱 안정성 통합 대시보드**: 안드로이드 12~16에서 알람 누락을 방지하기 위한 모든 필수 설정을 한 곳에서 직관적으로 관리할 수 있습니다.
|
||||||
|
- **4 강력한 근무 관리**: 연차, 월차, 교육, 반차 등 모든 변칙적인 근무를 직접 수정하고 달력에서 한눈에 확인할 수 있습니다.
|
||||||
|
|
||||||
|
## 지원 기기 및 권한
|
||||||
|
- **OS**: Android 8.0(Oreo) ~ Android 16(Baklava) 지원
|
||||||
|
- **필수 권한**: 정확한 알람, 배터리 최적화 제외, 다른 앱 위에 표시, 전체화면 알림
|
||||||
|
|
||||||
|
---
|
||||||
|
**Shiftring Team**
|
||||||
|
Copyright 2026 sanjeok77. All Rights Reserved.
|
||||||
16
RELEASE_NOTES.md
Normal file
16
RELEASE_NOTES.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
### 🧹 Shiftring v1.1.8 프로젝트 정리
|
||||||
|
- **버전 업데이트**: 1.1.6 → 1.1.8 (versionCode 1118)
|
||||||
|
- **불필요 파일 삭제**: 빌드 로그, 시뮬레이션 파일, 임시파일, 설치파일 등 26개 파일 정리
|
||||||
|
- **문서 정리**: GitHub → webpluss로 참조 수정
|
||||||
|
- **배포 서버**: webpluss.net GitLab으로 확정
|
||||||
|
|
||||||
|
- **릴리즈 URL**: https://git.webpluss.net/sanjeok77/ShiftRing/releases/tag/v1.1.8
|
||||||
|
- **APK 다운로드**: https://git.webpluss.net/sanjeok77/ShiftRing/releases/download/v1.1.8/app.apk
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🎉 Shiftring v1.0.0 정식 출시
|
||||||
|
- **서비스 시작**: 내부 버전 코드 600, 외부 버전 1.0.0으로 정식 서비스를 시작합니다.
|
||||||
|
- **슬라이더 개선**: 알람 해제(우->좌) 및 다시울림(좌->우)의 방향을 구분하여 편의성을 높이고 프리미엄 글래스 테마를 적용했습니다.
|
||||||
|
- **크래시 수정**: 알람 화면에서의 불안정한 동작과 종료 현상을 완전히 해결했습니다.
|
||||||
|
- **UX 고도화**: One UI 스타일 리플 애니메이션 및 설정 실시간 동기화로 조작감을 개선했습니다.
|
||||||
380
TECHNICAL_DOCUMENTATION.md
Normal file
380
TECHNICAL_DOCUMENTATION.md
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
# ShiftRing 알람 시스템 기술 문서
|
||||||
|
|
||||||
|
## 버전 정보
|
||||||
|
- **Version**: 1.0.0
|
||||||
|
- **Build**: 100
|
||||||
|
- **지원 Android**: 8.0 (Oreo) ~ 16
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 알람 아키텍처 개요
|
||||||
|
|
||||||
|
### 1.1 전체 흐름
|
||||||
|
```
|
||||||
|
[알람 예약] → [AlarmManager] → [AlarmReceiver] → [AlarmActivity]
|
||||||
|
↑ ↓ ↓ ↓
|
||||||
|
[SharedPrefs] [Doze Mode] [WakeLock] [화면 켜짐]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 핵심 컴포넌트
|
||||||
|
1. **AlarmUtils.kt** - 알람 예약/취소 로직
|
||||||
|
2. **AlarmReceiver.kt** - 알람 수신 및 처리
|
||||||
|
3. **AlarmActivity.kt** - 알람 UI 및 해제
|
||||||
|
4. **AlarmForegroundService.kt** - 포그라운드 서비스
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 알람 예약 메커니즘
|
||||||
|
|
||||||
|
### 2.1 ID 생성 체계
|
||||||
|
```kotlin
|
||||||
|
// 기본 근무 알람: 1YYMMDD0
|
||||||
|
// 예: 2026년 2월 13일 → 126021300
|
||||||
|
fun getShiftAlarmId(date: LocalDate): Int {
|
||||||
|
return 100000000 + (date.year % 100) * 1000000 +
|
||||||
|
date.monthValue * 10000 + date.dayOfMonth * 100 + 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 사용자 알람: 2YYMMDDNN (NN = 인덱스)
|
||||||
|
// 예: 2026년 2월 13일, 인덱스 1 → 226021301
|
||||||
|
fun getCustomAlarmId(date: LocalDate, index: Int): Int {
|
||||||
|
return 200000000 + (date.year % 100) * 1000000 +
|
||||||
|
date.monthValue * 10000 + date.dayOfMonth * 100 + index
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Android 버전별 알람 설정
|
||||||
|
|
||||||
|
#### Android 8.0~8.1 (API 26-27)
|
||||||
|
```kotlin
|
||||||
|
// Oreo: Background 제한 대응
|
||||||
|
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Android 9.0~13 (API 28-33)
|
||||||
|
```kotlin
|
||||||
|
// Pie ~ Tiramisu: setAlarmClock 권장
|
||||||
|
val clockInfo = AlarmManager.AlarmClockInfo(triggerTime, viewPendingIntent)
|
||||||
|
alarmManager.setAlarmClock(clockInfo, pendingIntent)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Android 14+ (API 34+)
|
||||||
|
```kotlin
|
||||||
|
// Upside Down Cake+: 권한 필수 + setAlarmClock
|
||||||
|
// 1. canScheduleExactAlarms() 권한 체크
|
||||||
|
// 2. setAlarmClock()으로 등록
|
||||||
|
// 3. USE_FULL_SCREEN_INTENT 권한 필요
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Doze 모드 대응
|
||||||
|
```kotlin
|
||||||
|
// Doze 모드에서도 알람 실행 보장
|
||||||
|
setExactAndAllowWhileIdle() // API 23+
|
||||||
|
setAlarmClock() // API 21+ (권장)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 알람 수신 및 실행
|
||||||
|
|
||||||
|
### 3.1 AlarmReceiver 동작
|
||||||
|
```kotlin
|
||||||
|
override fun onReceive(context: Context, intent: Intent?) {
|
||||||
|
// 1. WakeLock 획득 (10분)
|
||||||
|
val wakeLock = pm.newWakeLock(
|
||||||
|
FULL_WAKE_LOCK or ACQUIRE_CAUSES_WAKEUP or ON_AFTER_RELEASE,
|
||||||
|
"ShiftRing::DeepWakeLock"
|
||||||
|
)
|
||||||
|
wakeLock.acquire(10 * 60 * 1000L)
|
||||||
|
|
||||||
|
// 2. Foreground Service 시작 (Oreo+ 필수)
|
||||||
|
context.startForegroundService(serviceIntent)
|
||||||
|
|
||||||
|
// 3. Notification 발행 (전체화면 인텐트 포함)
|
||||||
|
notificationManager.notify(1001, notification)
|
||||||
|
|
||||||
|
// 4. AlarmActivity 시작
|
||||||
|
context.startActivity(fullScreenIntent)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 화면 켜짐 로직 (버전별)
|
||||||
|
|
||||||
|
#### Android 8.0 (Oreo)
|
||||||
|
```kotlin
|
||||||
|
// Deprecated 플래그 사용
|
||||||
|
window.addFlags(
|
||||||
|
FLAG_SHOW_WHEN_LOCKED or
|
||||||
|
FLAG_DISMISS_KEYGUARD or
|
||||||
|
FLAG_TURN_SCREEN_ON
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Android 8.1+ (Oreo MR1)
|
||||||
|
```kotlin
|
||||||
|
// 새로운 API 사용
|
||||||
|
setShowWhenLocked(true)
|
||||||
|
setTurnScreenOn(true)
|
||||||
|
keyguardManager.requestDismissKeyguard(this, null)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Android 14+ (Upside Down Cake)
|
||||||
|
```kotlin
|
||||||
|
// Keyguard dismiss 없이 위에 표시
|
||||||
|
setShowWhenLocked(true)
|
||||||
|
setTurnScreenOn(true)
|
||||||
|
window.addFlags(
|
||||||
|
FLAG_LAYOUT_IN_SCREEN or
|
||||||
|
FLAG_LAYOUT_INSET_DECOR
|
||||||
|
)
|
||||||
|
// Keyguard 해제 없이 바로 알람 화면!
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 권한 체계
|
||||||
|
|
||||||
|
### 4.1 필수 권한
|
||||||
|
|
||||||
|
#### Android 8.0~11
|
||||||
|
- `WAKE_LOCK` - 화면 켜짐
|
||||||
|
- `FOREGROUND_SERVICE` - 포그라운드 서비스
|
||||||
|
- `RECEIVE_BOOT_COMPLETED` - 부팅 시 알람 복구
|
||||||
|
|
||||||
|
#### Android 12+ (API 31+)
|
||||||
|
추가 권한:
|
||||||
|
- `SCHEDULE_EXACT_ALARM` - 정확한 알람 예약
|
||||||
|
- 설정에서 "알람 및 리마인더" 허용 필요
|
||||||
|
|
||||||
|
#### Android 14+ (API 34+)
|
||||||
|
추가 권한:
|
||||||
|
- `USE_FULL_SCREEN_INTENT` - 전체화면 알림
|
||||||
|
- 설정에서 "전체화면 알림" 허용 필요
|
||||||
|
- Foreground Service 타입: `specialUse`
|
||||||
|
|
||||||
|
### 4.2 권한 설정 플로우
|
||||||
|
```
|
||||||
|
[앱 실행]
|
||||||
|
→ [Android 12+] "알람 및 리마인더" 권한 확인
|
||||||
|
→ 없으면 설정 화면으로 이동
|
||||||
|
→ 사용자 수동 허용
|
||||||
|
→ canScheduleExactAlarms() = true
|
||||||
|
|
||||||
|
[Android 14+]
|
||||||
|
→ "전체화면 알림" 권한 확인
|
||||||
|
→ 설정 → 권한 → 전체화면 알림 → 허용
|
||||||
|
|
||||||
|
[모든 버전]
|
||||||
|
→ 배터리 최적화 "제한 없음" 설정
|
||||||
|
→ 설정 → 배터리 → 배터리 사용량 → ShiftRing → 제한 없음
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 근무 계산 로직
|
||||||
|
|
||||||
|
### 5.1 전주 공장 (4팀 순환)
|
||||||
|
```kotlin
|
||||||
|
val cycle = listOf(
|
||||||
|
"석간", "석간", "석간", "휴 무", "휴 무",
|
||||||
|
"주간", "주간", "주간", "주간", "주간", "휴 무", "휴 무",
|
||||||
|
"야간", "야간", "야간", "야간", "야간", "휴 무",
|
||||||
|
"석간", "석간"
|
||||||
|
) // 20일 주기
|
||||||
|
|
||||||
|
val TEAM_OFFSETS = mapOf("A" to 0, "B" to 15, "C" to 10, "D" to 5)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 논산 공장 (3팀 순환)
|
||||||
|
```kotlin
|
||||||
|
// 주간 → 야간 → 석간 (3주 주기)
|
||||||
|
// 월~금 근무, 토~일 휴 무
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 알람 시간 매핑
|
||||||
|
```kotlin
|
||||||
|
val shiftAlarmTimes = mapOf(
|
||||||
|
"주간" to "06:00",
|
||||||
|
"석간" to "14:00",
|
||||||
|
"야간" to "22:00",
|
||||||
|
"야간 맞교대" to "18:00"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 데이터 저장 구조
|
||||||
|
|
||||||
|
### 6.1 SharedPreferences
|
||||||
|
```
|
||||||
|
ShiftAlarmPrefs:
|
||||||
|
- selected_team: String (A/B/C/D)
|
||||||
|
- selected_factory: String (Jeonju/Nonsan)
|
||||||
|
- time_ju: String (주간 알람 시간)
|
||||||
|
- time_seok: String (석간 알람 시간)
|
||||||
|
- time_ya: String (야간 알람 시간)
|
||||||
|
- custom_alarms: String (JSON 배열)
|
||||||
|
- snooze_min: Int (스누즈 시간)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Room Database
|
||||||
|
```kotlin
|
||||||
|
@Entity(tableName = "shift_overrides")
|
||||||
|
data class ShiftOverride(
|
||||||
|
val factory: String,
|
||||||
|
val team: String,
|
||||||
|
val date: String, // YYYY-MM-DD
|
||||||
|
val shift: String,
|
||||||
|
val manualAlarmTime: String? // HH:MM
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 알람 동기화 프로세스
|
||||||
|
|
||||||
|
### 7.1 동기화 트리거
|
||||||
|
1. 앱 실행 시 (onResume)
|
||||||
|
2. 설정 변경 시 (근무/시간/팀)
|
||||||
|
3. 부팅 완료 시 (BootReceiver)
|
||||||
|
4. 날짜 변경 시 (매일 자정)
|
||||||
|
|
||||||
|
### 7.2 동기화 단계
|
||||||
|
```kotlin
|
||||||
|
suspend fun syncAllAlarms(context: Context) {
|
||||||
|
// 1. 기존 알람 모두 취소 (7일치)
|
||||||
|
for (date in today..today+7) {
|
||||||
|
cancelShiftAlarm(date)
|
||||||
|
cancelCustomAlarms(date)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 사용자 알람 JSON 파싱
|
||||||
|
val customAlarms = parseCustomAlarms()
|
||||||
|
|
||||||
|
// 3. 알람 재등록 (7일치)
|
||||||
|
for (date in today..today+7) {
|
||||||
|
val shift = calculateShift(date)
|
||||||
|
|
||||||
|
// 기본 알람 등록
|
||||||
|
if (isWorkShift(shift)) {
|
||||||
|
scheduleShiftAlarm(date, shift, alarmTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 사용자 알람 등록
|
||||||
|
for (alarm in customAlarms) {
|
||||||
|
if (shouldTrigger(alarm, shift)) {
|
||||||
|
scheduleCustomAlarm(date, alarm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 문제 해결 가이드
|
||||||
|
|
||||||
|
### 8.1 알람이 안 울림
|
||||||
|
|
||||||
|
#### 원인 1: 권한 없음 (Android 12+)
|
||||||
|
**증상**: Log에 `canScheduleExactAlarms() = false`
|
||||||
|
**해결**:
|
||||||
|
```
|
||||||
|
설정 → 앱 → ShiftRing → 권한 → "알람 및 리마인더" → 허용
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 원인 2: 배터리 최적화
|
||||||
|
**증상**: Doze 모드에서 알람 지연/실패
|
||||||
|
**해결**:
|
||||||
|
```
|
||||||
|
설정 → 배터리 → 배터리 사용량 → ShiftRing → "제한 없음"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 원인 3: 잠금 화면 표시 안됨 (Android 14+)
|
||||||
|
**증상**: 알람은 울리지만 화면 안 켜짐
|
||||||
|
**해결**:
|
||||||
|
```
|
||||||
|
설정 → 앱 → ShiftRing → 권한 → "전체화면 알림" → 허용
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 로그 확인 방법
|
||||||
|
```bash
|
||||||
|
# 필수 로그
|
||||||
|
adb logcat -s AlarmReceiver:D ShiftAlarm:D *:S
|
||||||
|
|
||||||
|
# 전체 로그
|
||||||
|
adb logcat | grep -E "ShiftRing|Alarm"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 정상 동작 확인 로그
|
||||||
|
```
|
||||||
|
AlarmReceiver: ===== 알람 수신 =====
|
||||||
|
AlarmReceiver: 근무: 주간 | ID: 126021300
|
||||||
|
AlarmReceiver: WakeLock 획득 완료
|
||||||
|
AlarmReceiver: Foreground Service 시작
|
||||||
|
AlarmReceiver: Notification 발행 완료
|
||||||
|
AlarmActivity: 화면 켜짐 성공
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 업데이트 시스템
|
||||||
|
|
||||||
|
### 9.1 버전 확인 URL
|
||||||
|
```
|
||||||
|
https://git.webpluss.net/sanjeok77/ShiftRing/version.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 version.json 형식
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"versionCode": 100,
|
||||||
|
"versionName": "1.0.0",
|
||||||
|
"apkUrl": "https://git.webpluss.net/sanjeok77/ShiftRing/releases/download/v1.0.0/app.apk",
|
||||||
|
"changelog": "버그 수정 및 성능 개선"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.3 자동 업데이트 플로우
|
||||||
|
```
|
||||||
|
[앱 실행]
|
||||||
|
→ [서버 버전 확인]
|
||||||
|
→ [최신 버전 비교]
|
||||||
|
→ [업데이트 다이얼로그 표시]
|
||||||
|
→ [APK 다운로드]
|
||||||
|
→ [설치 진행]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 릴리즈 히스토리
|
||||||
|
|
||||||
|
### v1.0.0 (Build 100)
|
||||||
|
- 최초 릴리즈
|
||||||
|
- Android 8.0 ~ 16 지원
|
||||||
|
- Android 14+ 잠금 화면 위 알람 표시 개선
|
||||||
|
- Oreo 호환성 강화
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 부록: 개발자 참고사항
|
||||||
|
|
||||||
|
### A. PendingIntent Flags
|
||||||
|
- `FLAG_UPDATE_CURRENT` - 기존 인텐트 업데이트
|
||||||
|
- `FLAG_IMMUTABLE` - Android 12+ 필수 (보안)
|
||||||
|
- `FLAG_NO_CREATE` - 기존 확인용
|
||||||
|
|
||||||
|
### B. WakeLock 종류
|
||||||
|
- `FULL_WAKE_LOCK` - 화면 + 키보드 백라이트 켜짐
|
||||||
|
- `ACQUIRE_CAUSES_WAKEUP` - 획득 시 즉시 화면 켜짐
|
||||||
|
- `ON_AFTER_RELEASE` - 해제 후에도 일정 시간 유지
|
||||||
|
|
||||||
|
### C. Foreground Service Types (Android 14+)
|
||||||
|
- `specialUse` - 특수 목적 (알람 앱)
|
||||||
|
- `dataSync` - 데이터 동기화
|
||||||
|
- `location` - 위치 추적
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**문서 버전**: 1.0.0
|
||||||
|
**최종 수정**: 2026-02-13
|
||||||
125
agent.md
Normal file
125
agent.md
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
# AI Agent 지시서 (Shiftring 프로젝트 - v1.0.0)
|
||||||
|
|
||||||
|
## 1. 기본 원칙
|
||||||
|
- 모든 브리핑, 문서, 커밋 메시지 초안은 **한글**로 작성한다.
|
||||||
|
- 수정 시 항상 원본 대비 전체 코드/문서 기준으로 설명하며 거짓말하지 않는다.
|
||||||
|
- 안드로이드 앱은 Play Store 없이도 릴리즈 빌드 및 업데이트가 가능한 구조를 유지한다.
|
||||||
|
- 앱 공식 명칭: **교대링 (Shiftring)**
|
||||||
|
- **버전 넘버링 규칙**:
|
||||||
|
- 버전은 `X.Y.Z` 형식을 따른다.
|
||||||
|
- `Z`(마지막 자리)는 **9**가 최대치이며, 9 다음에는 `Y`(가운데 자리)를 1 올리고 `Z`를 0으로 초기화한다.
|
||||||
|
- 예: `1.0.9` -> `1.1.0`, `1.1.9` -> `1.2.0`.
|
||||||
|
- `Y`가 9가 되면 `X`를 1 올리고 `Y`, `Z`를 0으로 초기화한다.
|
||||||
|
- 예: `1.9.9` -> `2.0.0`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 프로젝트 핵심 아키텍처 및 상태
|
||||||
|
### 2.1 데이터 정밀도 (Precision & Sync)
|
||||||
|
- **Timezone**: 모든 시간 계산(알람, 근무 로직, DB 저장)은 반드시 **'Asia/Seoul'** 표준 시간대를 사용한다 (`java.time.ZoneId.of("Asia/Seoul")`).
|
||||||
|
- **Manual Override**: 사용자가 수동으로 설정한 근무 및 알람 시간(Room DB의 `manualAlarmTime`)은 시스템의 자동 스케줄러(`AlarmWorker`)보다 우선하며, 절대 덮어씌워져서는 안 된다.
|
||||||
|
- **백업/복구**: `BackupManager`를 통해 JSON 형식으로 백업한다. 복구 시 **설정(SharedPreferences)을 DB보다 먼저 복원**하여 근무 데이터 유실 및 매칭 오류를 방지한다.
|
||||||
|
|
||||||
|
### 2.2 알람 가이드 (Engine v4.5)
|
||||||
|
- **Exact Timing**: `setAlarmClock` API를 표준으로 사용하여 Doze 모드와 배터리 최적화를 무시하고 정시에 알람을 실행한다.
|
||||||
|
- **Reliability Check**: 앱 실행 시 `AlarmPermissionUtil`과 `checkBatteryOptimization`을 통해 권한 상태를 검증하고 사용자에게 안내한다.
|
||||||
|
- **Wake-Up Strategy**: Android 8.0~15 전 버전에 대응하는 `FullScreenIntent` + `setShowWhenLocked` 조합으로 잠금화면 위에서도 즉시 점등 및 전체화면 알람을 보장한다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 리모트 릴리즈 프로세스 (Deployment)
|
||||||
|
### 3.1 단일 저장소 전략 (GitLab 웹플러스)
|
||||||
|
- **메인 저장소**: `https://git.webpluss.net/sanjeok77/ShiftRing`
|
||||||
|
- 코드와 APK 릴리즈를같은 저장소에서 관리합니다.
|
||||||
|
|
||||||
|
### 3.2 업데이트 시스템 (Auto-Update)
|
||||||
|
- **업데이트 체크 URL**: `https://git.webpluss.net/sanjeok77/ShiftRing/version.json`
|
||||||
|
- **AppUpdateManager 위치**: `app/src/main/java/com/example/shiftalarm/AppUpdateManager.kt`
|
||||||
|
- **version.json 형식**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"versionCode": 100,
|
||||||
|
"versionName": "1.0.0",
|
||||||
|
"apkUrl": "https://git.webpluss.net/sanjeok77/ShiftRing/releases/download/v1.0.0/app.apk",
|
||||||
|
"changelog": "버그 수정 및 성능 개선"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 릴리즈 순서
|
||||||
|
1. **버전 업데이트**: `build.gradle.kts`에서 `versionCode`와 `versionName`을 올린다.
|
||||||
|
- 예: `versionCode = 101`, `versionName = "1.0.1"`
|
||||||
|
2. **빌드**: 릴리즈 빌드 수행
|
||||||
|
```bash
|
||||||
|
./gradlew assembleRelease
|
||||||
|
```
|
||||||
|
3. **릴리즈 생성**: GitLab 웹 인터페이스 또는 CLI 사용
|
||||||
|
```bash
|
||||||
|
# 1) ShiftRing 리포지토리에 푸시
|
||||||
|
git push shiftring main
|
||||||
|
|
||||||
|
# 2) 기존 릴리즈 삭제 (필요시)
|
||||||
|
gh release delete v1.0.1 --repo sanjeok77/ShiftRing -y
|
||||||
|
|
||||||
|
# 3) 신규 릴리즈 생성
|
||||||
|
gh release create v1.0.1 \
|
||||||
|
--repo sanjeok77/ShiftRing \
|
||||||
|
--title "v1.0.1 - 릴리즈 제목" \
|
||||||
|
--notes "릴리즈 노트 내용" \
|
||||||
|
app/build/outputs/apk/release/app-release.apk
|
||||||
|
```
|
||||||
|
4. **version.json 업데이트**: ShiftRing 리포지토리 루트에 `version.json` 파일 생성/수정
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"versionCode": 101,
|
||||||
|
"versionName": "1.0.1",
|
||||||
|
"apkUrl": "https://git.webpluss.net/sanjeok77/ShiftRing/releases/download/v1.0.0/app.apk",
|
||||||
|
"changelog": "수정사항 요약"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
5. **소스 코드 커밋**: 코드 저장소에 변경 사항 푸시
|
||||||
|
```bash
|
||||||
|
git add -A
|
||||||
|
git commit -m "Release v1.0.1"
|
||||||
|
git push origin main
|
||||||
|
git push shiftring main
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 저장소 정보 (단일 저장소 전략)
|
||||||
|
- **메인 저장소**: `https://git.webpluss.net/sanjeok77/ShiftRing.git`
|
||||||
|
- **역할**: 코드 관리 + APK 릴리즈 (통합)
|
||||||
|
- **Git Remote 설정**:
|
||||||
|
```bash
|
||||||
|
git remote add origin https://git.webpluss.net/sanjeok77/ShiftRing.git
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.5 업데이트 체크 로직 (AppUpdateManager.kt)
|
||||||
|
```kotlin
|
||||||
|
object AppUpdateManager {
|
||||||
|
private const val VERSION_URL = "https://git.webpluss.net/sanjeok77/ShiftRing/version.json"
|
||||||
|
|
||||||
|
fun checkUpdate(activity: Activity, silent: Boolean = false) {
|
||||||
|
// version.json에서 최신 버전 확인
|
||||||
|
// 새 버전 있으면 다이얼로그 표시 → APK 다운로드 → 설치
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. UI/UX 디자인 가이드 (One UI 8.x Focus)
|
||||||
|
- **Aesthetics**: Glassmorphism 및 Mesh Gradient 기반의 프리미엄 디자인 + 28dp 표준 곡률 적용.
|
||||||
|
- **Pill-Style Grid**: 달력의 근무 표시(주/석/야 등)는 텍스트만 표시하지 않고, 고유 색상이 적용된 'Pill(알약)' 형태의 배경을 사용하여 시인성을 극대화한다.
|
||||||
|
- **Header System**: 모든 화면의 상단에는 32sp 크기의 볼드체 타이틀과 충분한 수직 여백(32dp padding top)을 갖춘 헤더를 배치한다.
|
||||||
|
- **Spacing Rule**: 달력 내부 메모 및 텍스트는 `includeFontPadding = false`를 적용하여 수직 간격을 최소화하고 정보 밀도를 높인다.
|
||||||
|
- **Iconography**: **Lucide Icons** 스타일의 SVG(VectorDrawable)만 사용 (`ic_` 접두사).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 현재 버전 (v1.0.2) 주요 변경 사항
|
||||||
|
- **One UI 8.x 정체성 확립**: 단순 테마 적용을 넘어, 대형 헤더와 28dp 곡률 시스템을 앱 전체 레이아웃에 통합.
|
||||||
|
- **고가독성 달력 시스템**: 격자 테두리를 제거하고 알약 형태의 근무 인디케이터를 적용하여 훨씬 깔끔한 그리드 구현.
|
||||||
|
- **통합 디자인 언어**: 메인, 설정, 공지사항, 매뉴얼 등 모든 액티비티의 헤더와 패널 스타일을 통일.
|
||||||
|
- **알람 엔진 안정화**: `setAlarmClock` 기반의 Doze 모드 완벽 대응 로직 유지 및 성능 최적화.
|
||||||
|
|
||||||
|
---
|
||||||
|
*(이 지침서는 AI 에이전트 간의 일관된 개발을 위한 핵심 문서임)*
|
||||||
241
app/CROSS_VERSION_REPORT.md
Normal file
241
app/CROSS_VERSION_REPORT.md
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
# 📱 Cross-Version Alarm Accuracy Report (Android 8.0 ~ 14)
|
||||||
|
**Period**: 2026-02-01 ~ 2026-03-31
|
||||||
|
**Scenario**: Jeonju Factory - Team C (Standard)
|
||||||
|
|
||||||
|
| Date | Shift | Alarm Time | Android Version | Simulation Result (API Behavior) |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 2026-02-01 | 휴무 | - | All Versions | No Alarm Scheduled (OFF) |
|
||||||
|
| 2026-02-02 | 휴무 | - | All Versions | No Alarm Scheduled (OFF) |
|
||||||
|
| 2026-02-03 | 야간 | **22:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-03 | 야간 | **22:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-03 | 야간 | **22:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-03 | 야간 | **22:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-03 | 야간 | **22:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-04 | 야간 | **22:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-04 | 야간 | **22:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-04 | 야간 | **22:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-04 | 야간 | **22:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-04 | 야간 | **22:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-05 | 야간 | **22:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-05 | 야간 | **22:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-05 | 야간 | **22:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-05 | 야간 | **22:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-05 | 야간 | **22:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-06 | 야간 | **22:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-06 | 야간 | **22:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-06 | 야간 | **22:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-06 | 야간 | **22:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-06 | 야간 | **22:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-07 | 야간 | **22:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-07 | 야간 | **22:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-07 | 야간 | **22:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-07 | 야간 | **22:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-07 | 야간 | **22:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-08 | 휴무 | - | All Versions | No Alarm Scheduled (OFF) |
|
||||||
|
| 2026-02-09 | 석간 | **14:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-09 | 석간 | **14:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-09 | 석간 | **14:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-09 | 석간 | **14:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-09 | 석간 | **14:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-10 | 석간 | **14:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-10 | 석간 | **14:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-10 | 석간 | **14:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-10 | 석간 | **14:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-10 | 석간 | **14:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-11 | 석간 | **14:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-11 | 석간 | **14:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-11 | 석간 | **14:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-11 | 석간 | **14:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-11 | 석간 | **14:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-12 | 석간 | **14:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-12 | 석간 | **14:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-12 | 석간 | **14:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-12 | 석간 | **14:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-12 | 석간 | **14:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-13 | 석간 | **14:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-13 | 석간 | **14:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-13 | 석간 | **14:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-13 | 석간 | **14:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-13 | 석간 | **14:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-14 | 휴무 | - | All Versions | No Alarm Scheduled (OFF) |
|
||||||
|
| 2026-02-15 | 휴무 | - | All Versions | No Alarm Scheduled (OFF) |
|
||||||
|
| 2026-02-16 | 주간 | **06:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-16 | 주간 | **06:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-16 | 주간 | **06:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-16 | 주간 | **06:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-16 | 주간 | **06:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-17 | 주간 | **06:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-17 | 주간 | **06:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-17 | 주간 | **06:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-17 | 주간 | **06:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-17 | 주간 | **06:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-18 | 주간 | **06:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-18 | 주간 | **06:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-18 | 주간 | **06:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-18 | 주간 | **06:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-18 | 주간 | **06:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-19 | 주간 | **06:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-19 | 주간 | **06:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-19 | 주간 | **06:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-19 | 주간 | **06:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-19 | 주간 | **06:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-20 | 주간 | **06:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-20 | 주간 | **06:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-20 | 주간 | **06:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-20 | 주간 | **06:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-20 | 주간 | **06:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-21 | 휴무 | - | All Versions | No Alarm Scheduled (OFF) |
|
||||||
|
| 2026-02-22 | 휴무 | - | All Versions | No Alarm Scheduled (OFF) |
|
||||||
|
| 2026-02-23 | 야간 | **22:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-23 | 야간 | **22:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-23 | 야간 | **22:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-23 | 야간 | **22:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-23 | 야간 | **22:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-24 | 야간 | **22:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-24 | 야간 | **22:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-24 | 야간 | **22:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-24 | 야간 | **22:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-24 | 야간 | **22:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-25 | 야간 | **22:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-25 | 야간 | **22:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-25 | 야간 | **22:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-25 | 야간 | **22:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-25 | 야간 | **22:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-26 | 야간 | **22:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-26 | 야간 | **22:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-26 | 야간 | **22:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-26 | 야간 | **22:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-26 | 야간 | **22:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-27 | 야간 | **22:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-27 | 야간 | **22:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-27 | 야간 | **22:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-27 | 야간 | **22:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-27 | 야간 | **22:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-02-28 | 휴무 | - | All Versions | No Alarm Scheduled (OFF) |
|
||||||
|
| 2026-03-01 | 석간 | **14:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-01 | 석간 | **14:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-01 | 석간 | **14:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-01 | 석간 | **14:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-01 | 석간 | **14:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-02 | 석간 | **14:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-02 | 석간 | **14:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-02 | 석간 | **14:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-02 | 석간 | **14:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-02 | 석간 | **14:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-03 | 석간 | **14:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-03 | 석간 | **14:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-03 | 석간 | **14:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-03 | 석간 | **14:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-03 | 석간 | **14:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-04 | 석간 | **14:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-04 | 석간 | **14:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-04 | 석간 | **14:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-04 | 석간 | **14:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-04 | 석간 | **14:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-05 | 석간 | **14:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-05 | 석간 | **14:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-05 | 석간 | **14:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-05 | 석간 | **14:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-05 | 석간 | **14:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-06 | 휴무 | - | All Versions | No Alarm Scheduled (OFF) |
|
||||||
|
| 2026-03-07 | 휴무 | - | All Versions | No Alarm Scheduled (OFF) |
|
||||||
|
| 2026-03-08 | 주간 | **06:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-08 | 주간 | **06:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-08 | 주간 | **06:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-08 | 주간 | **06:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-08 | 주간 | **06:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-09 | 주간 | **06:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-09 | 주간 | **06:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-09 | 주간 | **06:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-09 | 주간 | **06:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-09 | 주간 | **06:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-10 | 주간 | **06:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-10 | 주간 | **06:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-10 | 주간 | **06:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-10 | 주간 | **06:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-10 | 주간 | **06:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-11 | 주간 | **06:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-11 | 주간 | **06:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-11 | 주간 | **06:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-11 | 주간 | **06:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-11 | 주간 | **06:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-12 | 주간 | **06:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-12 | 주간 | **06:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-12 | 주간 | **06:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-12 | 주간 | **06:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-12 | 주간 | **06:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-13 | 휴무 | - | All Versions | No Alarm Scheduled (OFF) |
|
||||||
|
| 2026-03-14 | 휴무 | - | All Versions | No Alarm Scheduled (OFF) |
|
||||||
|
| 2026-03-15 | 야간 | **22:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-15 | 야간 | **22:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-15 | 야간 | **22:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-15 | 야간 | **22:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-15 | 야간 | **22:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-16 | 야간 | **22:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-16 | 야간 | **22:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-16 | 야간 | **22:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-16 | 야간 | **22:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-16 | 야간 | **22:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-17 | 야간 | **22:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-17 | 야간 | **22:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-17 | 야간 | **22:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-17 | 야간 | **22:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-17 | 야간 | **22:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-18 | 야간 | **22:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-18 | 야간 | **22:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-18 | 야간 | **22:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-18 | 야간 | **22:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-18 | 야간 | **22:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-19 | 야간 | **22:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-19 | 야간 | **22:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-19 | 야간 | **22:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-19 | 야간 | **22:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-19 | 야간 | **22:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-20 | 휴무 | - | All Versions | No Alarm Scheduled (OFF) |
|
||||||
|
| 2026-03-21 | 석간 | **14:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-21 | 석간 | **14:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-21 | 석간 | **14:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-21 | 석간 | **14:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-21 | 석간 | **14:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-22 | 석간 | **14:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-22 | 석간 | **14:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-22 | 석간 | **14:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-22 | 석간 | **14:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-22 | 석간 | **14:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-23 | 석간 | **14:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-23 | 석간 | **14:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-23 | 석간 | **14:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-23 | 석간 | **14:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-23 | 석간 | **14:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-24 | 석간 | **14:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-24 | 석간 | **14:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-24 | 석간 | **14:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-24 | 석간 | **14:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-24 | 석간 | **14:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-25 | 석간 | **14:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-25 | 석간 | **14:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-25 | 석간 | **14:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-25 | 석간 | **14:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-25 | 석간 | **14:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-26 | 휴무 | - | All Versions | No Alarm Scheduled (OFF) |
|
||||||
|
| 2026-03-27 | 휴무 | - | All Versions | No Alarm Scheduled (OFF) |
|
||||||
|
| 2026-03-28 | 주간 | **06:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-28 | 주간 | **06:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-28 | 주간 | **06:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-28 | 주간 | **06:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-28 | 주간 | **06:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-29 | 주간 | **06:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-29 | 주간 | **06:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-29 | 주간 | **06:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-29 | 주간 | **06:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-29 | 주간 | **06:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-30 | 주간 | **06:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-30 | 주간 | **06:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-30 | 주간 | **06:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-30 | 주간 | **06:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-30 | 주간 | **06:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-31 | 주간 | **06:00** | Android 8.0 (Oreo) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-31 | 주간 | **06:00** | Android 10 (Q) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: None | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-31 | 주간 | **06:00** | Android 12 (S) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-31 | 주간 | **06:00** | Android 13 (T) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
|
| 2026-03-31 | 주간 | **06:00** | Android 14 (U) | Using AlarmManager.setAlarmClock (Reliable, shows icon) | Flags: FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE | Perms: SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS | Doze: Bypasses Doze Mode (Highest Priority) |
|
||||||
88
app/build.gradle.kts
Normal file
88
app/build.gradle.kts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import java.util.Properties
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("com.android.application")
|
||||||
|
id("org.jetbrains.kotlin.android")
|
||||||
|
id("kotlin-kapt")
|
||||||
|
}
|
||||||
|
|
||||||
|
val keystoreProperties = Properties()
|
||||||
|
val keystorePropertiesFile = file("${project.rootDir}/keystore.properties")
|
||||||
|
if (keystorePropertiesFile.exists()) {
|
||||||
|
keystorePropertiesFile.inputStream().use { keystoreProperties.load(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.example.shiftalarm"
|
||||||
|
compileSdk = 35
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "com.example.shiftalarm"
|
||||||
|
minSdk = 26
|
||||||
|
targetSdk = 35
|
||||||
|
versionCode = 1119
|
||||||
|
versionName = "1.1.9"
|
||||||
|
|
||||||
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
create("release") {
|
||||||
|
storeFile = file(keystoreProperties.getProperty("storeFile", "../release.jks"))
|
||||||
|
storePassword = keystoreProperties.getProperty("storePassword", "dummy")
|
||||||
|
keyAlias = keystoreProperties.getProperty("keyAlias", "dummy")
|
||||||
|
keyPassword = keystoreProperties.getProperty("keyPassword", "dummy")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
signingConfig = signingConfigs.getByName("release")
|
||||||
|
isMinifyEnabled = false
|
||||||
|
isShrinkResources = false
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("androidx.core:core-ktx:1.12.0")
|
||||||
|
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||||
|
implementation("com.google.android.material:material:1.11.0")
|
||||||
|
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||||
|
|
||||||
|
// WorkManager (Crucial for reliable background tasks)
|
||||||
|
val work_version = "2.9.0"
|
||||||
|
implementation("androidx.work:work-runtime-ktx:$work_version")
|
||||||
|
|
||||||
|
// Activity & Lifecycle
|
||||||
|
implementation("androidx.activity:activity-ktx:1.8.2")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
|
||||||
|
|
||||||
|
// Room Database
|
||||||
|
val room_version = "2.6.1"
|
||||||
|
implementation("androidx.room:room-runtime:$room_version")
|
||||||
|
implementation("androidx.room:room-ktx:$room_version")
|
||||||
|
kapt("androidx.room:room-compiler:$room_version")
|
||||||
|
|
||||||
|
// Kotlin Coroutines
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
|
||||||
|
|
||||||
|
testImplementation("junit:junit:4.13.2")
|
||||||
|
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||||
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||||
|
}
|
||||||
42
app/proguard-rules.pro
vendored
Normal file
42
app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# ProGuard rules for ShiftAlarm App
|
||||||
|
|
||||||
|
# 1. Android Entry Points (Names only)
|
||||||
|
-keep public class * extends android.app.Activity
|
||||||
|
-keep public class * extends android.app.Application
|
||||||
|
-keep public class * extends android.app.Service
|
||||||
|
-keep public class * extends android.content.BroadcastReceiver
|
||||||
|
-keep public class * extends android.content.ContentProvider
|
||||||
|
-keep public class * extends android.app.backup.BackupAgent
|
||||||
|
-keep public class * extends android.preference.Preference
|
||||||
|
|
||||||
|
# 2. Room Database & Entities
|
||||||
|
-keep @androidx.room.Entity class * { *; }
|
||||||
|
-keep interface * extends androidx.room.RoomDatabase { *; }
|
||||||
|
-keep class * extends androidx.room.RoomDatabase { *; }
|
||||||
|
-keep @androidx.room.Dao interface * { *; }
|
||||||
|
-keep class * implements com.example.shiftalarm.ShiftDao { *; }
|
||||||
|
|
||||||
|
# 3. WorkManager
|
||||||
|
-keep class * extends androidx.work.ListenableWorker {
|
||||||
|
<init>(android.content.Context, androidx.work.WorkerParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
# 4. ViewBinding / UI
|
||||||
|
-keep class * implements androidx.viewbinding.ViewBinding { *; }
|
||||||
|
|
||||||
|
# 5. Metadata for Stacktraces and Debugging
|
||||||
|
-keepattributes Signature, *Annotation*, EnclosingMethod, InnerClasses, SourceFile, LineNumberTable
|
||||||
|
|
||||||
|
# 6. JSON / Serialization (if any)
|
||||||
|
-keepclassmembers class * {
|
||||||
|
@com.google.gson.annotations.SerializedName <fields>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 7. Kotlin Coroutines
|
||||||
|
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
|
||||||
|
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
|
||||||
|
-dontwarn kotlinx.coroutines.**
|
||||||
|
|
||||||
|
# 8. Suppress general warnings
|
||||||
|
-dontwarn javax.lang.model.element.Modifier
|
||||||
|
-dontwarn androidx.room.paging.**
|
||||||
191
app/simulation_result.md
Normal file
191
app/simulation_result.md
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
# ShiftRing Alarm Logic Simulation V2 (Robust)
|
||||||
|
Period: 2026-02-01 ~ 2026-03-31
|
||||||
|
|
||||||
|
### Simulation Report: User 1 (Jeonju-C Standard) (Jeonju - Team C)
|
||||||
|
| Date | Day | Shift | Alarm Time | Status | Logic Check |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| 2026-02-01 | Sun | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-02-02 | Mon | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-02-03 | Tue | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-02-04 | Wed | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-02-05 | Thu | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-02-06 | Fri | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-02-07 | Sat | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-02-08 | Sun | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-02-09 | Mon | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-02-10 | Tue | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-02-11 | Wed | 석간 | 14:00 | ON | OK HOLIDAY |
|
||||||
|
| 2026-02-12 | Thu | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-02-13 | Fri | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-02-14 | Sat | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-02-15 | Sun | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-02-16 | Mon | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-02-17 | Tue | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-02-18 | Wed | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-02-19 | Thu | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-02-20 | Fri | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-02-21 | Sat | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-02-22 | Sun | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-02-23 | Mon | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-02-24 | Tue | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-02-25 | Wed | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-02-26 | Thu | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-02-27 | Fri | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-02-28 | Sat | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-01 | Sun | 석간 | 14:00 | ON | OK HOLIDAY |
|
||||||
|
| 2026-03-02 | Mon | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-03-03 | Tue | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-03-04 | Wed | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-03-05 | Thu | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-03-06 | Fri | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-07 | Sat | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-08 | Sun | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-03-09 | Mon | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-03-10 | Tue | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-03-11 | Wed | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-03-12 | Thu | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-03-13 | Fri | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-14 | Sat | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-15 | Sun | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-03-16 | Mon | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-03-17 | Tue | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-03-18 | Wed | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-03-19 | Thu | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-03-20 | Fri | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-21 | Sat | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-03-22 | Sun | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-03-23 | Mon | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-03-24 | Tue | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-03-25 | Wed | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-03-26 | Thu | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-27 | Fri | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-28 | Sat | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-03-29 | Sun | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-03-30 | Mon | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-03-31 | Tue | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
|
||||||
|
### Simulation Report: User 2 (Nonsan-A Standard) (Nonsan - Team A)
|
||||||
|
| Date | Day | Shift | Alarm Time | Status | Logic Check |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| 2026-02-01 | Sun | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-02-02 | Mon | 석간 | 15:00 | ON | OK |
|
||||||
|
| 2026-02-03 | Tue | 석간 | 15:00 | ON | OK |
|
||||||
|
| 2026-02-04 | Wed | 석간 | 15:00 | ON | OK |
|
||||||
|
| 2026-02-05 | Thu | 석간 | 15:00 | ON | OK |
|
||||||
|
| 2026-02-06 | Fri | 석간 | 15:00 | ON | OK |
|
||||||
|
| 2026-02-07 | Sat | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-02-08 | Sun | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-02-09 | Mon | 주간 | 07:00 | ON | Valid (Nonsan) |
|
||||||
|
| 2026-02-10 | Tue | 주간 | 07:00 | ON | Valid (Nonsan) |
|
||||||
|
| 2026-02-11 | Wed | 주간 | 07:00 | ON | Valid (Nonsan) HOLIDAY |
|
||||||
|
| 2026-02-12 | Thu | 주간 | 07:00 | ON | Valid (Nonsan) |
|
||||||
|
| 2026-02-13 | Fri | 주간 | 07:00 | ON | Valid (Nonsan) |
|
||||||
|
| 2026-02-14 | Sat | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-02-15 | Sun | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-02-16 | Mon | 야간 | 23:00 | ON | OK |
|
||||||
|
| 2026-02-17 | Tue | 야간 | 23:00 | ON | OK |
|
||||||
|
| 2026-02-18 | Wed | 야간 | 23:00 | ON | OK |
|
||||||
|
| 2026-02-19 | Thu | 야간 | 23:00 | ON | OK |
|
||||||
|
| 2026-02-20 | Fri | 야간 | 23:00 | ON | OK |
|
||||||
|
| 2026-02-21 | Sat | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-02-22 | Sun | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-02-23 | Mon | 석간 | 15:00 | ON | OK |
|
||||||
|
| 2026-02-24 | Tue | 석간 | 15:00 | ON | OK |
|
||||||
|
| 2026-02-25 | Wed | 석간 | 15:00 | ON | OK |
|
||||||
|
| 2026-02-26 | Thu | 석간 | 15:00 | ON | OK |
|
||||||
|
| 2026-02-27 | Fri | 석간 | 15:00 | ON | OK |
|
||||||
|
| 2026-02-28 | Sat | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-01 | Sun | 휴무 | - | OFF | OK HOLIDAY |
|
||||||
|
| 2026-03-02 | Mon | 주간 | 07:00 | ON | Valid (Nonsan) |
|
||||||
|
| 2026-03-03 | Tue | 주간 | 07:00 | ON | Valid (Nonsan) |
|
||||||
|
| 2026-03-04 | Wed | 주간 | 07:00 | ON | Valid (Nonsan) |
|
||||||
|
| 2026-03-05 | Thu | 주간 | 07:00 | ON | Valid (Nonsan) |
|
||||||
|
| 2026-03-06 | Fri | 주간 | 07:00 | ON | Valid (Nonsan) |
|
||||||
|
| 2026-03-07 | Sat | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-08 | Sun | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-09 | Mon | 야간 | 23:00 | ON | OK |
|
||||||
|
| 2026-03-10 | Tue | 야간 | 23:00 | ON | OK |
|
||||||
|
| 2026-03-11 | Wed | 야간 | 23:00 | ON | OK |
|
||||||
|
| 2026-03-12 | Thu | 야간 | 23:00 | ON | OK |
|
||||||
|
| 2026-03-13 | Fri | 야간 | 23:00 | ON | OK |
|
||||||
|
| 2026-03-14 | Sat | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-15 | Sun | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-16 | Mon | 석간 | 15:00 | ON | OK |
|
||||||
|
| 2026-03-17 | Tue | 석간 | 15:00 | ON | OK |
|
||||||
|
| 2026-03-18 | Wed | 석간 | 15:00 | ON | OK |
|
||||||
|
| 2026-03-19 | Thu | 석간 | 15:00 | ON | OK |
|
||||||
|
| 2026-03-20 | Fri | 석간 | 15:00 | ON | OK |
|
||||||
|
| 2026-03-21 | Sat | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-22 | Sun | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-23 | Mon | 주간 | 07:00 | ON | Valid (Nonsan) |
|
||||||
|
| 2026-03-24 | Tue | 주간 | 07:00 | ON | Valid (Nonsan) |
|
||||||
|
| 2026-03-25 | Wed | 주간 | 07:00 | ON | Valid (Nonsan) |
|
||||||
|
| 2026-03-26 | Thu | 주간 | 07:00 | ON | Valid (Nonsan) |
|
||||||
|
| 2026-03-27 | Fri | 주간 | 07:00 | ON | Valid (Nonsan) |
|
||||||
|
| 2026-03-28 | Sat | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-29 | Sun | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-30 | Mon | 야간 | 23:00 | ON | OK |
|
||||||
|
| 2026-03-31 | Tue | 야간 | 23:00 | ON | OK |
|
||||||
|
|
||||||
|
### Simulation Report: User 3 (Jeonju-A Customizer) (Jeonju - Team A)
|
||||||
|
| Date | Day | Shift | Alarm Time | Status | Logic Check |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| 2026-02-01 | Sun | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-02-02 | Mon | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-02-03 | Tue | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-02-04 | Wed | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-02-05 | Thu | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-02-06 | Fri | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-02-07 | Sat | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-02-08 | Sun | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-02-09 | Mon | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-02-10 | Tue | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-02-11 | Wed | 휴무 | - | OFF | OK HOLIDAY |
|
||||||
|
| 2026-02-12 | Thu | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-02-13 | Fri | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-02-14 | Sat | 휴무 | - | OFF | OK [Manual Override] |
|
||||||
|
| 2026-02-15 | Sun | 주간 | 06:00 | ON | Valid (Default) [Manual Override] |
|
||||||
|
| 2026-02-16 | Mon | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-02-17 | Tue | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-02-18 | Wed | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-02-19 | Thu | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-02-20 | Fri | 석간 | 05:00 | ON | OK [Date Rule] |
|
||||||
|
| 2026-02-21 | Sat | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-02-22 | Sun | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-02-23 | Mon | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-02-24 | Tue | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-02-25 | Wed | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-02-26 | Thu | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-02-27 | Fri | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-02-28 | Sat | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-03-01 | Sun | 주간 | 06:00 | ON | Valid (Default) HOLIDAY |
|
||||||
|
| 2026-03-02 | Mon | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-03-03 | Tue | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-04 | Wed | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-05 | Thu | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-03-06 | Fri | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-03-07 | Sat | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-03-08 | Sun | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-03-09 | Mon | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-03-10 | Tue | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-11 | Wed | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-03-12 | Thu | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-03-13 | Fri | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-03-14 | Sat | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-03-15 | Sun | 석간 | 14:00 | ON | OK |
|
||||||
|
| 2026-03-16 | Mon | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-17 | Tue | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-18 | Wed | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-03-19 | Thu | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-03-20 | Fri | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-03-21 | Sat | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-03-22 | Sun | 주간 | 06:00 | ON | Valid (Default) |
|
||||||
|
| 2026-03-23 | Mon | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-24 | Tue | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-25 | Wed | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-03-26 | Thu | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-03-27 | Fri | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-03-28 | Sat | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-03-29 | Sun | 야간 | 22:00 | ON | OK |
|
||||||
|
| 2026-03-30 | Mon | 휴무 | - | OFF | OK |
|
||||||
|
| 2026-03-31 | Tue | 석간 | 14:00 | ON | OK |
|
||||||
103
app/src/main/AndroidManifest.xml
Normal file
103
app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||||
|
|
||||||
|
<!-- Alarm & Full Screen -->
|
||||||
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
|
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
|
||||||
|
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
|
||||||
|
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
|
||||||
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||||
|
|
||||||
|
<!-- Service & Notification -->
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@drawable/ic_alarm_blue"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@drawable/ic_alarm_blue"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/Theme.ShiftAlarm">
|
||||||
|
|
||||||
|
<activity android:name=".SettingsActivity" android:exported="false" android:configChanges="uiMode"/>
|
||||||
|
<activity
|
||||||
|
android:name=".NoticeActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:parentActivityName=".SettingsActivity"
|
||||||
|
android:label="변경사항"/>
|
||||||
|
<activity
|
||||||
|
android:name=".ManualActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:parentActivityName=".SettingsActivity"
|
||||||
|
android:label="사용설명서"
|
||||||
|
android:configChanges="uiMode"/>
|
||||||
|
<activity
|
||||||
|
android:name=".AlarmActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:theme="@style/Theme.AppCompat.NoActionBar"
|
||||||
|
android:showWhenLocked="true"
|
||||||
|
android:turnScreenOn="true"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:documentLaunchMode="never"
|
||||||
|
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||||
|
android:allowEmbedded="false"
|
||||||
|
android:resizeableActivity="false" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:configChanges="uiMode">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".AlarmReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.example.shiftalarm.ALARM_TRIGGER" />
|
||||||
|
<action android:name="com.example.shiftalarm.SNOOZE" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".BootReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".AlarmForegroundService"
|
||||||
|
android:foregroundServiceType="specialUse"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.provider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/provider_paths" />
|
||||||
|
</provider>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
554
app/src/main/assets/CHANGELOG.md
Normal file
554
app/src/main/assets/CHANGELOG.md
Normal file
@@ -0,0 +1,554 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [1.1.3] - 2026-02-16
|
||||||
|
### Added
|
||||||
|
- **앱 안정성 설정 통합**: 설정 화면에서 '배터리 최적화 제외', '다른 앱 위에 표시', '전체화면 알림' 등 알람 가동에 필수적인 권한 상태를 한눈에 확인하고 직접 설정할 수 있는 섹션 추가
|
||||||
|
- **안드로이드 16 잠금화면 우회**: 최신 OS에서도 지문/패턴 해제 없이 알람 화면이 즉시 나타나도록 `requestDismissKeyguard` 로직 적용 및 안정성 강화
|
||||||
|
|
||||||
|
## [1.1.2] - 2026-02-15
|
||||||
|
### Fixed
|
||||||
|
- **알람 삭제 버그**: 알람이 켜진 상태에서 삭제해도 알람이 울리던 문제 수정
|
||||||
|
- **삭제 시 자동 취소**: 알람 삭제 시 시스템에 등록된 향후 모든 스케줄을 즉시 취소하도록 로직 강화
|
||||||
|
|
||||||
|
## [1.1.1] - 2026-02-15
|
||||||
|
### Fixed
|
||||||
|
- **잠금 화면 위 표시**: 잠금 화면을 풀지 않아도 알람 해제 화면이 즉시 나타나도록 윈도우 플래그 및 핸들링 로직 수정 (Android 14/15 완벽 대응)
|
||||||
|
- **전체화면 권한 안내**: 권한이 누락된 경우 설정 화면으로 바로 이동하도록 안내 로직 개선
|
||||||
|
|
||||||
|
## [1.1.0] - 2026-02-15
|
||||||
|
### 🚀 알람 신뢰도 100% 달성 및 시스템 고도화
|
||||||
|
- **3단계 알람 안전장치 도입**:
|
||||||
|
- **Room DB 전환**: 사용자 알람 데이터를 SQLite 데이터베이스로 마이그레이션하여 대규모 데이터 처리 및 보존 안정성 확보
|
||||||
|
- **AlarmClock API 최우선 순위**: 절전 모드를 무력화하는 최고 수준의 신뢰도 API 적용. 상단바 알람 아이콘 활성화로 예약 상태 가시성 확보
|
||||||
|
- **30일 확장 동기화 엔진**: 근무 변경이나 설정 수정 시 향후 30일간의 알람을 즉시 재계산 및 예약
|
||||||
|
- **권한 및 알림 일원화**:
|
||||||
|
- **통합 권한 센터**: 필수 권한(정확한 알람, 배터리 제외, 전체화면 알림)을 한 화면에서 순차적으로 설정할 수 있도록 흐름 개선
|
||||||
|
- **단일 알림 포그라운드 서비스 적용**: 알람 알림이 중복되거나 지워지지 않도록 포그라운드 서비스 기반의 단일 알림 시스템 구축
|
||||||
|
- **레거시 제거 및 최적화**: 미사용 파라미터 제거 및 알람 엔진 성능 최적화
|
||||||
|
|
||||||
|
## [1.0.1] - 2026-02-14
|
||||||
|
### Added
|
||||||
|
- **프리미엄 알람 디자인**: `lock.html` 디자인을 기반으로 한 화려한 알람 화면 도입 (오로라 펄스 애니메이션, 글래스모피즘 버튼)
|
||||||
|
- **달력 년/월 휠 선택**: 달력 상단 년/월 클릭 시 휠 다이얼로 즉시 이동하는 기능 추가
|
||||||
|
- **알람 설정 최적화**: 알람 목록 로딩 속도 개선 및 사운드 타이틀 캐싱 적용
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **마스터 알람 스위치**: 거대한 카드 대신 세련된 텍스트 레이블 형태로 알람 설정 좌측 상단에 배치
|
||||||
|
- **토글 버튼 개선**: 이질적인 회색 배경을 제거하고 부드러운 Material 3 애니메이션 스위치 적용
|
||||||
|
- **사용 설명서 개편**: 최신 기능(년/월 피커, 신규 알람 UI 등)에 맞춰 상세 설명 업데이트
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **설정 진입 속도**: 알람 설정 탭 클릭 시 발생하던 미세한 지연 시간 단축
|
||||||
|
|
||||||
|
## [0.9.1] - 2026-02-11
|
||||||
|
### Fixed
|
||||||
|
- **알람 엔진 안정화**: PendingIntent ID 충돌 및 권한 누락 안내 기능 추가
|
||||||
|
- **배경 작업 최적화**: 부팅 후 알람 복구 로직 중복 실행 방지 및 효율성 개선
|
||||||
|
- **보안 강화**: 앱 서명 비밀번호 분리 관리 및 루팅 기기 대응 준비
|
||||||
|
- **시간대 통일**: 모든 알람 로직에 Asia/Seoul 표준 시간대 강제 적용
|
||||||
|
|
||||||
|
## [0.9.0] - 2026-02-11
|
||||||
|
### Added
|
||||||
|
- **One UI 8 디자인 완성**: 설정 화면, 알람 설정, 공지사항 등 앱 전반에 걸쳐 One UI 8 스타일의 카드 레이아웃 및 28dp 라운딩 적용
|
||||||
|
- **자동 업데이트 확인**: 앱 접속 시 최신 버전을 자동으로 체크하고 원클릭으로 업데이트를 수행하는 스마트 엔진 탑재
|
||||||
|
- **UI 일관성 강화**: 모든 다이얼로그 및 팝업에 고도화된 Glassmorphism 디자인과 통일된 여백 시스템 도입
|
||||||
|
- **상태 기반 헤더**: 메인 화면 상단에 오늘 근무 및 선택된 반 정보를 실시간으로 표시하는 다이나믹 헤더 추가
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **아이콘 시스템 정밀화**: 설정 아이콘 및 액션 버튼에 Lucide Icons 스타일 적용 및 시인성 개선
|
||||||
|
- **가독성 최적화**: 시간 선택기(TimePickerDialog)를 최신 시스템 테마로 업데이트하고 폰트 가독성 상향
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.8.0] - 2026-02-11
|
||||||
|
### Added
|
||||||
|
- **One UI 8 스타일 적용**: Jetpack Compose 기반의 최신 삼성 One UI 8 디자인 시스템 통합 (Soft Blur, Pill-shape, Dynamic Color 지원)
|
||||||
|
- **알람 신뢰도 엔진 (Android 14+ 대응)**: 포그라운드 서비스(shortService) 및 배터리 최적화 예외 유도 로직을 통해 삼성 기기에서의 95%+ 알람 성공률 확보
|
||||||
|
- **정확한 알람 권한 관리**: `AlarmPermissionUtil`을 통해 Android 14+ 알람/리마인더 권한 설정 인터페이스 개선
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **빌드 시스템 인프라**: Jetpack Compose 및 Material 3 환경 구축
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.6.3] - 2026-02-10
|
||||||
|
### Changed
|
||||||
|
- **리브랜딩**: 앱 전체에서 '닥잡아/dakjaba' 표기를 '교대링(Shiftring)'으로 통일
|
||||||
|
- **달력 근무 표기 변경**: 약어(주/석/야/맞/휴) → 풀네임(주간/석간/야간/맞교대/휴무)
|
||||||
|
- **메모 표시 레이아웃 수정**: 근무 텍스트 아래에 정확히 배치되도록 마진 및 제약조건 재설정
|
||||||
|
- **사용설명서 전면 업데이트**: 이모지 대신 Lucide 스타일 텍스트 아이콘 사용, 알람 테스트 안내 추가
|
||||||
|
- **알림 텍스트 브랜딩**: '교대 근무 알람' → '교대링 알람'
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **알람 테스트 수정**: 알람 테스트 버튼이 실제로 알람을 트리거하도록 테스트 알람 바이패스 로직 추가
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.5.8] - 2026-02-10
|
||||||
|
### Added
|
||||||
|
- **알람 동기화 시스템 전면 재정비**: 알람이 울리기 직전 근무 종류뿐만 아니라 설정된 '시간'까지 재검증하는 2중 동기화 로직을 도입하여, 어떤 상황에서도 정확한 알람이 울리도록 개선
|
||||||
|
### Fixed
|
||||||
|
- **다크 모드 눈 피로도 감소**: 근무별 배경색의 채도를 더 낮추고 부드러운 톤으로 조정하여 야간 사용 시 시각적 편안함 증대
|
||||||
|
- **다크 모드 가독성 수정**: 알람 추가 및 근무 변경 팝업의 배경과 텍스트 시인성 확보
|
||||||
|
- **버그 수정**: 야간 맞교대 근무 시 알람 시간 키값이 잘못 지정되던 문제 수정
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.5.7] - 2026-02-10
|
||||||
|
### Fixed
|
||||||
|
- **알람 동기화 긴급 수정**: 근무가 변경된 경우 이전 알람이 울리지 않도록 수신부(AlarmReceiver)에서 현재 근무를 재검증하는 로직 추가
|
||||||
|
- **다크 모드 팝업 시인성 개선**: 다크 모드에서 '알람 추가', '근무 변경' 팝업의 배경색이 보이지 않던 문제 수정 (테마 대응 컬러 적용)
|
||||||
|
- **근무 배경 색상 최적화**: 다크 모드에서 근무 배경색의 채도를 낮추어 눈의 피로도를 줄임
|
||||||
|
### Changed
|
||||||
|
- **용어 통일**: '스누즈' 표기를 '다시 울림'으로 일괄 변경
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.5.6] - 2026-02-09
|
||||||
|
### Changed
|
||||||
|
- **'오늘' 버튼 디자인 개편**: 달력 그리드 디자인과 조화로운 미니멀한 테두리 스타일로 변경
|
||||||
|
- **가독성 개선**: 라이트 모드에서 시인성이 낮았던 민트색 섹션 제목을 시인성이 높은 진보라색으로 변경하여 가독성 향상
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.5.5] - 2026-02-09
|
||||||
|
### Changed
|
||||||
|
- **설정 화면 레이아웃 최적화**: '회사 선택'과 '반 선택'을 한 행에 2열 그리드로 배치하여 공간 효율성 개선
|
||||||
|
- **용어 순화 및 변경**: 알람 설정 내 '스누즈 및 소리'를 사용자 친화적인 '다시 울림 및 소리'로 명칭 변경
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.5.4] - 2026-02-09
|
||||||
|
### Fixed
|
||||||
|
- **다크 모드 빈 격자 색상 수정**: 달력 시작/종료 전후의 빈 격자가 다크 모드에서도 테마에 맞는 색상으로 표시되도록 수정
|
||||||
|
### Improved
|
||||||
|
- **공간 활용성 극대화**: 각 격자의 높이를 상향(82dp) 조정하여, 하단의 남는 공간을 최소화하고 화면을 더 꽉 차게 보이도록 개선
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.5.3] - 2026-02-09
|
||||||
|
### Fixed
|
||||||
|
- **다크 모드 완벽 지원**: 하드코딩된 색상을 테마 리소스(bg_grid_cell_default, grid_divider 등)로 교체하여 다크 모드에서도 달력이 정상적으로 표시되도록 수정
|
||||||
|
- **사용자 편의성 강화**: 메인 화면의 알람 시간 표시 영역을 터치하면 즉시 알람 설정 화면으로 이동하도록 개선
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.5.2] - 2026-02-09
|
||||||
|
### Added
|
||||||
|
- **그리드 완성도 향상**: 달력 시작일 이전의 빈 공간에도 격자선을 표시하여 디자인적 일관성 확보
|
||||||
|
- **가독성 최적화**: 근무 표시 글자 크기 확대 및 격자 높이 상향 (66dp)
|
||||||
|
- **UI 시각적 개선**: '오늘' 버튼 디자인 개선 및 배경색/테두리 최적화
|
||||||
|
- **대비 및 명시성 강화**: 흰색 텍스트가 잘 보이지 않던 팀(A반)의 배경색을 더욱 진하게 조정
|
||||||
|
- **오늘 강조 변경**: 오늘 날짜를 연한 파란색 배경으로 강조하여 시각적 직관성 제공
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.5.1] - 2026-02-09
|
||||||
|
### Added
|
||||||
|
- **격자 디자인 고도화**: 일반 달력처럼 배경을 흰색으로 변경하고, 근무 표시(주, 석, 야 등)에만 개별 배경색 적용
|
||||||
|
- **공휴일 정보 통합**: 교대 달력 모드에서도 근무 표시 옆에 공휴일 이름을 함께 표시
|
||||||
|
- **화면 실용성 극대화**: 상단 헤더 및 알람바 높이를 축소하여 6주 달력도 스크롤 없이 한 화면에 표시
|
||||||
|
- **가독성 개선**: 근무 글자를 칸 좌측 상단 모서리에 밀착 배치하고 최적화된 글자 크기 적용
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.5.0] - 2026-02-09
|
||||||
|
### Added
|
||||||
|
- **달력 디자인 전면 개편**: 카드 스타일에서 세련된 격자(Grid) 스타일로 변경
|
||||||
|
- **근무 표기 최적화**: 좌측 상단에 한 글자(주, 석, 야, 맞, 휴)로 직관적인 근무 표시
|
||||||
|
- **가독성 강화**: 중앙에 큰 날짜 배치 및 날짜 옆 작은 공휴일 마커 추가
|
||||||
|
- **색상 체계 변경**: 주간(레몬), 석간(회색), 야간(검정), 휴무(빨강), 맞교대(보라)로 배경색 구분
|
||||||
|
- **전체 화면 최적화**: 불필요한 여백을 제거하고 화면을 최대한 활용하도록 레이아웃 수정
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.4.4] - 2026-02-09
|
||||||
|
### Changed
|
||||||
|
- **달력 행 수 최적화**: 1~4주만 필요한 달은 5행(35셀)만 표시하고 스크롤 없이 고정, 5주 이상 필요한 달만 6행(42셀)으로 스크롤 활성화
|
||||||
|
- **안전한 최적화 방식**: 레이아웃 높이 수정 없이, 데이터 패딩과 스크롤 설정만 조정하여 해상도에 관계없이 안정적으로 동작
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.4.3] - 2026-02-09
|
||||||
|
### Fixed
|
||||||
|
- **달력 화면 완전 복원**: 0.3.9 안정 버전의 달력 코드로 완전 롤백하여 달력이 보이지 않던 문제를 완벽하게 해결
|
||||||
|
- **해상도 최적화 이슈 제거**: 불안정한 동적 높이 조절 로직을 모두 제거하고 검증된 고정 레이아웃으로 복원
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.4.2] - 2026-02-09
|
||||||
|
### Fixed
|
||||||
|
- **달력 화면 완전 복구**: 달력 항목의 높이를 70dp로 명시적으로 고정하여, 일부 기기에서 레이아웃 측정 오류로 달력 내용이 보이지 않던 문제를 완벽하게 해결
|
||||||
|
- **스크롤 기능 강화**: 모든 해상도에서 화면 잘림 없이 달력을 확인할 수 있도록 스크롤 기능 상시 활성화 유지
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.4.1] - 2026-02-09
|
||||||
|
### Fixed
|
||||||
|
- **스크롤 제한 긴급 수정**: 작은 화면의 기기에서 달력 하단이 잘리는 현상을 수정하기 위해 스크롤 기능을 상시 활성화
|
||||||
|
- **달력 레이아웃 안정화**: 해상도 최적화 로직의 일부 불안정성을 제거하고 표준 높이(68dp)로 복구하여 안정성 확보
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.4.0] - 2026-02-09
|
||||||
|
### Added
|
||||||
|
- **버전 넘버링 체계 확립**: Patch가 9를 넘으면 Minor를 올리는 규칙 적용 (0.3.9 -> 0.4.0)
|
||||||
|
- **해상도별 달력 최적화**: 5행 달력은 스크롤 없이 고정하고, 6행일 때만 스크롤되도록 고도화
|
||||||
|
- **레이아웃 안정성**: 다양한 해상도의 폰에서도 달력 모양이 일정하게 유지되도록 수정
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **메인 공간 및 UI 개선**: 0.3.10의 변경사항(헤더 축소, 버튼 강조 등)을 정식 반영
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.3.9] - 2026-02-09
|
||||||
|
### Added
|
||||||
|
- **오늘 버튼 동작 개선**: 달력에서 '오늘' 클릭 시 보고 있던 조와 상관없이 나의 본래 조와 오늘 날짜 달력으로 즉시 복귀
|
||||||
|
- **사용자 알람 연동 버그 수정**: 이제 사용자 알람 추가/수정 시에도 오늘 근무표를 확인하여 본인 근무와 일치할 때만 스케줄링되도록 수정
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **업데이트 내역 가독성**: 변경사항 목록 하단에 불필요하게 표시되던 구분선(-) 제거
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **기본 설정 가독성**: 회사/반 선택 레이블 폰트 크기 확대 및 굵게 표시하여 식별력 강화
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [v0.6.0] - 2026-02-12
|
||||||
|
### Added
|
||||||
|
- **Major Rebranding**: App name changed to **"교대링" (Shiftring)** with English locale support.
|
||||||
|
- **Room Database Migration**: Replaced `SharedPreferences` with Room DB for robust storage of shift overrides and memos.
|
||||||
|
- **Daily Memos**: Added ability to save and view daily notes on the calendar.
|
||||||
|
- **Lucide Icons**: Integrated modern Lucide iconography system across the app.
|
||||||
|
- **Advanced Alarm Synchronization**: Reliable "double-check" system ensuring alarms match current shift and time preferences.
|
||||||
|
- **Additional Features Tab**: New section in settings for upcoming capabilities.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Dark Mode Visibility**: Improved "Night" (야간) shift visibility and dialog accessibility in dark theme.
|
||||||
|
- **Coroutines & Performance**: Refactored database operations to use Coroutines for smoother UI performance.
|
||||||
|
- **Icon Prefix**: Standardized all icons with `ic_` prefix.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.3.8] - 2026-02-09
|
||||||
|
### Added
|
||||||
|
- **지난 알람 숨김**: 오늘 이미 시간이 지난 사용자 알람은 달력 화면에서 표시되지 않도록 개선
|
||||||
|
- **가독성 향상**: 라이트 모드에서도 알람 시간이 잘 보이도록 색상 및 굵기 개선
|
||||||
|
|
||||||
|
### Updated
|
||||||
|
- **사용 설명서 개편**: 최신 기능 반영 및 불필요한 서식을 제거하여 깔끔하게 정리
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.3.7] - 2026-02-09
|
||||||
|
### Added
|
||||||
|
- **사용자 알람 근무표 연동**: 기타를 제외한 사용자 알람이 나의 근무표와 연동 (예: 주간 선택 시 주간 날에만 울림)
|
||||||
|
- **달력 팀 표시 개선**: 다이얼로그에서 나의 반 표시 시 "(나)" 추가
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **알람 표시 개선**: (사용자) 텍스트 제거, 색상으로 구분
|
||||||
|
- **여러 알람 표시**: 3개 이하면 모두 표시, 초과 시 "XX:XX 외 N개" 형식
|
||||||
|
- **설정 섹션 타이틀 강화**: 글자 크기 16sp로 확대, 굵게 표시
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.3.6] - 2026-02-09
|
||||||
|
### Added
|
||||||
|
- **사용자 알람 실제 작동**: 사용자 알람이 실제로 울리도록 알람 스케줄링 구현
|
||||||
|
- **알람 수정 기능**: 사용자 알람에 수정 버튼 추가
|
||||||
|
- **달력 알람 표시 개선**: 사용자 알람이 근무 알람보다 빠르면 함께 표시
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **알람 신뢰성 강화**: 알람 추가 시 즉시 스케줄링되도록 개선
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **근무별 알람시간 레이블 굵게 표시**: 주간/석간/야간/야맞 레이블을 굵게 표시
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.3.5] - 2026-02-09
|
||||||
|
### Added
|
||||||
|
- **사용자 알람 추가**: 알람 설정에서 '+ 알람 추가' 버튼으로 사용자 정의 알람 등록 가능
|
||||||
|
- **시간 및 근무 유형 선택**: 사용자 알람 추가 시 시간과 근무 유형(주간/석간/야간/기타) 설정
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **공지사항 버전 표시 수정**: 업데이트 내역에서 버전명이 정확하게 파싱되도록 로직 전면 개선
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.3.4] - 2026-02-09
|
||||||
|
### Improved
|
||||||
|
- **공지사항 버전 파싱 개선**: 업데이트 내역 상단에 버전명이 더 정확하게 표시되도록 로직 보강
|
||||||
|
- **사용 설명서 버전 자동화**: 설명서 상단의 앱 버전이 하드코딩 대신 현재 설치된 버전으로 자동 표시
|
||||||
|
- **날짜 숫자 크기 확대**: 달력 날짜 숫자를 더 크게(12sp) 키워 가독성 향상
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.3.3] - 2026-02-09
|
||||||
|
### Fixed
|
||||||
|
- **달력 스와이프 감도 개선**: RecyclerView 터치 간섭 문제를 해결하기 위해 `addOnItemTouchListener` 적용
|
||||||
|
- **스와이프 영역 확대**: 달력 내부뿐만 아니라 달력 컨테이너 전체에서 스와이프 제스처가 작동하도록 개선
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.3.2] - 2026-02-08
|
||||||
|
### Fixed
|
||||||
|
- **업데이트 내역 버전 파싱 수정**: 버전 번호가 정확하게 표시되도록 정규표현식 파싱 적용
|
||||||
|
- **근무 변경 팝업 불투명도 향상**: 배경 글자가 비치지 않도록 95% 불투명도 적용
|
||||||
|
- **공휴일/음력 표시 개선**: 카드 중앙에 크게 표시, 생략(...)없이 전체 표시
|
||||||
|
- **설명서 불필요한 내용 제거**: 알람 프라이버시 설명 삭제 (본인 폰에만 설정되므로 불필요)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.3.1] - 2026-02-08
|
||||||
|
### Improved
|
||||||
|
- **용어 통일**: '조' 표기를 '반'으로 전체 통일 (다른 반 근무, 설명서, 메뉴 등)
|
||||||
|
- **업데이트 내역 버전 표기**: 공지사항에 'v0.3.1 업데이트 내용' 형식으로 버전 표시
|
||||||
|
- **공휴일 글자 크게 표시**: 공휴일/음력 날짜 텍스트를 13sp 굵은 글씨로 표시
|
||||||
|
- **사용 설명서 최신화**: v0.3.1 기준 최신 기능 및 용어 반영
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.3.0] - 2026-02-08
|
||||||
|
### Improved
|
||||||
|
- **공휴일 체크 위치 고정**: 다른 조 달력 조회 시에도 공휴일 체크박스가 우측에 고정
|
||||||
|
- **음력 날짜 표시 개선**: 12.25 형식의 음력 날짜가 생략 없이 전체 표시
|
||||||
|
- **설명서 UI 통일**: 공지사항과 동일한 핑크 카드 스타일로 변경
|
||||||
|
- **공지사항 UI 업그레이드**: 메시 그라디언트 배경과 세련된 헤더 카드 적용
|
||||||
|
- **전체 UI/UX 통일감 강화**: 앱 전반에 걸쳐 일관된 디자인 언어 적용
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.9] - 2026-02-08
|
||||||
|
### Improved
|
||||||
|
- **공휴일 글자 크게 표시**: 공휴일 명칭도 근무 글자와 동일하게 크게 (14sp) 표시
|
||||||
|
- **다시 울림 옵션 추가**: 1분, 3분 스누즈 간격 추가 (총 8개 옵션)
|
||||||
|
- **알람 표시 개선**: 다른 조 달력 조회 시 알람 영역 완전히 숨김 처리
|
||||||
|
- **공지사항 표시 제한**: 최대 7개 항목만 표시하여 가독성 향상
|
||||||
|
- **사용 설명서 최신화**: v0.2.9 기준 모든 기능 반영
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.8] - 2026-02-08
|
||||||
|
### Improved
|
||||||
|
- **달력 근무 텍스트 대폭 확대**: 주간, 석간, 야간 등 근무 글자를 16sp로 키워 가독성 극대화
|
||||||
|
- **기본 달력 모드 변경**: 앱 실행 시 '교대달력'이 기본으로 표시 (공휴일 체크 해제 상태)
|
||||||
|
- **달력 레이아웃 균등 배분**: 날짜, 근무, 음력이 균등하게 배치되어 깔끔한 정렬
|
||||||
|
- **알람 표시 개선**: 다른 조 달력 조회 시 알람 시간이 표시되지 않음 (내 조만 알람 표시)
|
||||||
|
- **오늘 날짜 강조**: 오늘 날짜에 파란색 테두리로 눈에 띄게 표시
|
||||||
|
- **사용 설명서 UI 전면 개편**: Glassmorphism 3.0 디자인 적용, 앱 아이콘 헤더, 글래스 카드 스타일
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.7] - 2026-02-08
|
||||||
|
### Fixed
|
||||||
|
- **체크 표시 로직 수정**: 근무 중인 날에는 V 체크 표시가 나타나지 않도록 수정 (휴무/휴가일에만 표시)
|
||||||
|
- **사용 설명서 업데이트**: 최신 앱 기능(글래스모피즘 3.0, 탭 설정, 오늘 이동 등) 반영 및 갱신
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.6] - 2026-02-08
|
||||||
|
### Added
|
||||||
|
- **공휴일 모드**: '공휴일' 체크 시 근무 표시를 숨기고 공휴일 명칭만 표시 (일반 달력 모드)
|
||||||
|
- **근무 변경 팝업 디자인**: 근무 변경 화면에 Glassmorphism 3.0 디자인 적용
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.5] - 2026-02-08
|
||||||
|
### Fixed
|
||||||
|
- **용어 수정**: '근무표 직접 수정' 버튼 명칭을 **'사용 설명서'**로 정정 (기능과 명칭 불일치 해결)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.4] - 2026-02-08
|
||||||
|
### Fixed
|
||||||
|
- **다크 모드 가독성 개선**: 닫기 버튼 및 달력 날짜 텍스트 가독성 향상
|
||||||
|
- **UI/UX 개선**: 달력에 오늘 날짜로 이동하는 버튼 추가
|
||||||
|
- **디자인 수정**: 공휴일 체크 방식 변경 (V 체크 표시) 및 글래스모피즘 효과 강화
|
||||||
|
- **용어 수정**: '직접 입력 관리' → '근무표 직접 수정'으로 변경하여 이해도 향상
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.3] - 2026-02-08
|
||||||
|
### Changed
|
||||||
|
- **디자인 업그레이드 (Glassmorphism 3.0)**: 더욱 아름답고 세련된 반투명 디자인 적용
|
||||||
|
- **설정 화면 개편**: 기본 설정과 알람 설정 탭으로 분리하여 사용성 강화
|
||||||
|
- **완전 한글화**: 달력 요일 및 설정 메뉴 100% 한글 적용
|
||||||
|
- **회사 명칭 간소화**: '모나리자 전주' → '전주', '모나리자 논산' → '논산'으로 변경
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **알람 시간 초기화**: 회사 변경 시 해당 회사의 기본 출근 시간으로 자동 변경
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.2] - 2026-02-08
|
||||||
|
### Added
|
||||||
|
- **다크/라이트 모드 지원**: 설정 > 화면 테마에서 시스템/다크/라이트 모드 선택 가능
|
||||||
|
- **업데이트 내역 실시간 동기화**: 앱 내 변경사항이 서버와 즉시 연동되도록 개선
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **용어 수정**: '공장' → '회사', '스누즈' → '다시 울림'으로 변경하여 친숙함 강화
|
||||||
|
- **UI 개선**: 알람 화면 '미루기' 버튼 가독성 향상 및 텍스트 수정
|
||||||
|
- **설정 화면**: 하단에 앱 버전 정보 및 제작자(산적이얌) 표시 추가
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.1] - 2026-02-08
|
||||||
|
### Changed (UI Overhaul - Glassmorphism 2.0)
|
||||||
|
- **전체 디자인 리뉴얼**: 고급스러운 메쉬 그라데이션(Deep Purple-Blue) 배경 적용
|
||||||
|
- **설정 화면 개선**: 알람 시간 버튼 색상을 투명한 글래스 스타일로 변경하여 가독성 대폭 향상
|
||||||
|
- **메인 캘린더**: 투명 카드 UI 적용, 텍스트 색상을 배경에 맞춰 화이트 톤으로 최적화
|
||||||
|
- **알람 화면**: 슬라이더 및 시간 표시 가독성 개선, 부드러운 글래스 패널 적용
|
||||||
|
- **세부 디테일**: 섹션 타이틀 영문 표기, 아이콘 및 버튼 스타일 통일
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.2.0] - 2026-02-08
|
||||||
|
### Changed
|
||||||
|
- **스누즈 메시지 개선**: "스누즈 설정됨" → "X분 뒤 다시 울림" 형태로 설정된 시간 표시
|
||||||
|
- **알람 화면 글래스모피즘 적용**: 반투명 카드, 슬라이더, 썸네일에 부드러운 투명도 효과
|
||||||
|
- **알람 화면 배경 개선**: 다크 블루 그라데이션 팔레트로 세련됨 향상
|
||||||
|
- **설정 화면 버튼 개선**: 진한 색상을 부드러운 글래스모피즘 스타일로 변경
|
||||||
|
- **스누즈 슬라이더에 시간 표시**: 알람 화면에서 설정된 스누즈 시간을 바로 확인 가능
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.1.9] - 2026-02-08
|
||||||
|
### Changed
|
||||||
|
- 알람 화면: 알람 끄기(빨간색)를 위로, 스누즈(파란색)를 아래로 위치 변경
|
||||||
|
- 설정 화면: 글래스모피즘 디자인 적용 (반투명 카드, 부드러운 그라데이션 배경)
|
||||||
|
- 설정 화면: 이모지 대신 안드로이드 시스템 아이콘으로 대체하여 세련됨 향상
|
||||||
|
- 사용설명서: 한글 깨짐 수정 및 알람 해제 방법 내용 추가
|
||||||
|
- 변경사항: 불필요한 구분선 및 빈 불릿 필터링으로 가독성 개선
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.1.8] - 2026-02-08
|
||||||
|
### Changed
|
||||||
|
- **설정 화면 전면 개편**: 섹션별 카드 분리, 구분선 및 여백 적용으로 가독성 대폭 개선
|
||||||
|
- **알람 화면 UI 고도화**:
|
||||||
|
- 그라데이션 배경 및 대형 시간 표시로 프리미엄 느낌 강화
|
||||||
|
- 스누즈(블루) / 해제(레드) 슬라이더 색상 분리로 직관성 향상
|
||||||
|
- 슬라이더 핸들 디자인 고급화 (원형 + 중심 컬러)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.1.7] - 2026-02-08
|
||||||
|
### Added
|
||||||
|
- **슬라이더 방식 알람 UI**: 아이폰/갤럭시 스타일의 '밀어서 해제/스누즈' UI 도입
|
||||||
|
- **스누즈 (좌→우)**: 상단 슬라이더를 밀어서 잠시 미룸
|
||||||
|
- **알람 해제 (우→좌)**: 하단 슬라이더를 밀어서 정지
|
||||||
|
- **디자인 고도화**: 시각적인 조작 가이드(트랙 및 핸들)를 적용하여 직관성 및 미관 대폭 개선
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.1.6] - 2026-02-08
|
||||||
|
### Fixed
|
||||||
|
- 알람 화면 긴급 수정: 버튼 대신 **스와이프 전용 레이아웃** 도입 (우→좌: 정지, 좌→우: 스누즈)
|
||||||
|
- 화면 켜짐 보장: 잠금 화면에서도 알람 시 즉시 화면이 켜지도록 로직 강화
|
||||||
|
- 논산 야맞 알람 오류: 출근 시간(20:00) 1시간 전인 **19:00**에 알람이 기본 설정되도록 수정
|
||||||
|
- 축약어 적용: '다른 조 오늘 근무' 섹션에서도 '야간 맞교대' 대신 **'야맞'**으로 통일
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- 알람 화면에 현재 시간 표시 및 스와이프 안내 가이드 추가
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.1.5] - 2026-02-08
|
||||||
|
### Added
|
||||||
|
- 알람 스와이프 제스처 기능: **우→좌(알람 정지)**, **좌→우(스누즈)**
|
||||||
|
- 알람 시 화면 자동 켜짐 및 잠금화면 표시 기능 강화
|
||||||
|
- 변경사항(Notice) 화면에 '닫기' 버튼 추가
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- 논산 공장 야간 맞교대(야맞) 기본 시간을 **20:00**으로 조정 및 라벨 업데이트
|
||||||
|
- 달력 내 '야간 맞교대' 표기를 **'야맞'**으로 축약하여 가독성 개선
|
||||||
|
- 5행 달력의 스크롤을 완전히 고정하여 불필요한 움직임 제거
|
||||||
|
- 달력 제목 형식을 연.월(예: 2026.02)로 변경하여 더 깔끔하게 개선
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- 스누즈 버튼 클릭 시 알람이 즉시 다시 울리던 현상 수정
|
||||||
|
- 알람 정지 시 상단 바 알림이 사라지지 않던 문제 해결
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.1.4] - 2026-02-08
|
||||||
|
### Added
|
||||||
|
- 고급스러운 디자인 시스템 적용 (카드 곡률, 현대적 컬러 팔레트)
|
||||||
|
- 알람 테스트 기능 구현 (설정 > 5초 후 테스트 알람)
|
||||||
|
- 사용설명서 앱 내 뷰어 연동 및 최신 내용 반영
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- 달력 줄 수(5행/6행)에 따른 상하 스크롤 자동 제어
|
||||||
|
- 체인지로그 표시 시 불필요한 마크다운 기호를 제거하여 가독성 향상
|
||||||
|
- 설정 화면의 '깃허브 다운로드' 버튼 제거 (앱 내 업데이트로 통일)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- 음력 날짜가 표시되지 않거나 부정확하던 문제 수정
|
||||||
|
- 메인 화면 상단 알람 정보의 실시간 반영 로직 개선
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.1.3] - 2026-02-08
|
||||||
|
### Added
|
||||||
|
- 설정 내 '변경사항' 메뉴가 실제 CHANGELOG 내용을 표시하도록 연동
|
||||||
|
- '직접 입력' 근무 설정 시 알람 시간을 즉시 입력받도록 개선
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- '오늘 다른 조' 표시를 모든 팀(전주 4개, 논산 3개)이 보이도록 복원
|
||||||
|
- 일별 근무 변경 시 표준 근무(주간/석간/야간/야맞)는 설정된 시간을 즉시 적용하도록 간소화
|
||||||
|
- 야간 맞교대(야맞) 알람 기본 시간을 공장별로 차별화 (논산: 19:00, 전주: 18:00)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- 야간 맞교대 시간 설정이 저장되지 않던 문제 수정
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.1.2] - 2026-02-08
|
||||||
|
### Added
|
||||||
|
- 앱 내 직접 APK 다운로드 및 설치 기능 (진행률 표시 포함)
|
||||||
|
- 스와이프 제스처로 캘린더 월 탐색 기능
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- '오늘 다른 조' 표시를 현재 보고 있는 팀 제외한 팀만 표시하도록 간소화
|
||||||
|
- 캘린더 높이를 항상 6줄(42칸)로 고정하여 일관된 UI 제공
|
||||||
|
- 업데이트 다운로드 방식을 웹페이지 이동에서 앱 내 직접 다운로드로 변경
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- 팀 표시에서 지역명(전주/논산) 제거하여 UI 정리
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.1.1] - 2026-02-08
|
||||||
|
### Changed
|
||||||
|
- 릴리즈 저장소 분리 (dakjaba-releases)
|
||||||
|
- 버전 체크 URL 업데이트
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.1.0] - 2026-02-08
|
||||||
|
### Added
|
||||||
|
- 사용설명서(Manual) 메뉴 추가
|
||||||
|
- 설정 화면 내 수동 업데이트 메뉴 개선
|
||||||
|
- 달력 내 음력 표시 기능 추가
|
||||||
|
- 릴리즈 빌드 서명(Signing) 적용
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- 알람 설정 단위를 1분으로 세분화
|
||||||
|
- 반 선택 방식을 라디오 버튼에서 드롭다운(Spinner)으로 변경
|
||||||
|
- 전주 D반 복구 및 공장별 맞춤형 반 선택 로직 적용
|
||||||
|
- 하단 '오늘의 근무' 레이아웃 최적화
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- 설정 변경 시 달력에 즉시 반영되지 않던 문제 수정
|
||||||
|
- 공휴일 텍스트 잘림 현상 개선
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2026-02-01]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- 기본 알람 스케줄 엔진
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Doze 모드에서 알람 누락 문제 수정
|
||||||
47
app/src/main/assets/MANUAL.md
Normal file
47
app/src/main/assets/MANUAL.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# 교대링(Shiftring) 상세 사용 가이드
|
||||||
|
|
||||||
|
본 가이드는 **교대링 v1.1.8**의 주요 기능과 설정을 안내합니다. 별도의 복잡한 설정 없이도 **자신의 반(A/B/C/D)**만 선택하면 즉시 모든 일정과 알람이 세팅됩니다.
|
||||||
|
|
||||||
|
## 1. 스마트 달력 사용법
|
||||||
|
- **일정 한눈에 보기**: 달력에 주간(노랑), 석간(연두), 야간(보라), 휴무(빨강) 등 색상별로 근무가 자동 표시됩니다.
|
||||||
|
- **월 이동 제스처**: 화면을 좌우로 가볍게 밀어서(스와이프) 이전 달이나 다음 달로 빠르게 이동할 수 있습니다.
|
||||||
|
- **빠른 년/월 이동**: 상단 중앙의 **'2026년 02월'** 텍스트를 터치하면 휠 다이얼을 돌려 원하는 년도와 월로 즉시 이동할 수 있습니다.
|
||||||
|
- **오늘로 돌아오기**: 상단의 **'오늘'** 버튼을 누르면 언제 어디서든 현재 날짜로 즉시 돌아옵니다.
|
||||||
|
- **타 조 근무 확인**: 하단의 '오늘의 타 조 근무' 섹션에서 다른 조 이름을 터치하면, 해당 조의 달력 뷰로 잠시 전환됩니다.
|
||||||
|
|
||||||
|
## 2. 근무 변경 및 개인 메모
|
||||||
|
기본 스케줄 외의 변경 사항을 달력에 직접 기록하고 관리할 수 있습니다.
|
||||||
|
- **날짜 선택**: 수정하고 싶은 날짜를 터치하면 상세 설정 팝업이 나타납니다.
|
||||||
|
- **근무 상태 수정**: 연차, 교육, 월차, 반차 등 해당일의 상태를 선택하세요. 달력에 즉시 반영되며 관련 알람도 자동 조정됩니다.
|
||||||
|
- **메모장 활용**: 하단 메모란에 내용을 입력하고 저장하면, 달력 날짜 아래에 작은 점(•)이 표시되며 메모 내용을 확인할 수 있습니다.
|
||||||
|
- **설정 초기화**: 수정한 일정을 원래의 기본 순번대로 되돌리려면 **'원래대로'** 버튼을 누르세요.
|
||||||
|
|
||||||
|
## 3. 프리미엄 알람 시스템
|
||||||
|
최신 트렌드를 반영한 아름답고 신뢰할 수 있는 알람 기능을 제공합니다. 교대링은 **3단계 안전장치**를 통해 100% 신뢰도를 지향합니다.
|
||||||
|
- **자동 예약**: 선택된 근무(주/야/석)에 따라 알람 시간이 자동으로 계산되어 예약됩니다. (향후 30일치 사전 예약)
|
||||||
|
- **정밀 알람 엔진**: `AlarmClock` API를 통해 절전 모드에서도 정확하게 작동하며, 상단바에 알람 아이콘이 표시되어 작동 여부를 쉽게 확인할 수 있습니다.
|
||||||
|
- **실시간 동기화**: 근무를 변경하거나 설정에서 알람 시간을 바꾸는 즉시 전체 스케줄이 실시간으로 재구성됩니다.
|
||||||
|
- **시간 커스텀**: 설정(⚙️) → **알람 설정** 탭에서 각 근무별 기본 알람 시간을 본인의 기상 패턴에 맞게 수정할 수 있습니다.
|
||||||
|
- **전체 알람 마스터 스위치**: 알람 설정 페이지 좌측 상단의 **'전체 알람 켜짐/꺼짐'** 버튼으로 모든 예약을 일시 정지하거나 활성화할 수 있습니다.
|
||||||
|
- **럭셔리 디자인**: **글래스모피즘(유리 질감)**과 화려한 그라데이션이 적용된 알람 화면은 가독성과 디자인을 모두 잡았습니다.
|
||||||
|
- **직관적인 버튼 제어**:
|
||||||
|
- **다시 울림**: 상단 유리 질감 버튼을 누르면 설정된 간격만큼 알람을 미룹니다.
|
||||||
|
- **해제**: 중앙의 거대한 원형 버튼을 누르면 알람이 즉시 종료됩니다. (주변에 은은한 오로라 광채 애니메이션이 작동합니다)
|
||||||
|
- **부드러운 스위치**: 알람 항목의 온/오프 스위치는 가볍고 부드러운 애니메이션을 제공하며, 불필요한 배경 요소를 제거하여 시각적 이질감을 없앴습니다.
|
||||||
|
|
||||||
|
## 4. 물때표 및 특수 설정
|
||||||
|
- **물때표 표시**: 설정 → 기타 설정에서 **'물때표 보기'**를 활성화하면 달력 상단에 물때 정보가 나타납니다.
|
||||||
|
- **지역 전환**: 달력 상단의 지역 이름(군산, 변산, 여수, 태안)을 터치하여 간편하게 지역별 물때를 확인할 수 있습니다.
|
||||||
|
- **기본 공장 설정**: 본인이 속한 공장(전주 또는 논산)을 선택하여 공장별 특화된 교대 로직을 적용받으세요.
|
||||||
|
|
||||||
|
## 5. 데이터 백업 및 앱 공유
|
||||||
|
- **안전한 백업**: 설정 → 기타 설정에서 현재의 근무 기록과 메모를 파일로 저장하거나 다시 불러올 수 있습니다.
|
||||||
|
- **설치 파일 직접 전송**: **'앱 공유하기'** 기능을 통해 동료들에게 설치 파일(APK)을 직접 보내주어 간편한 설치를 도울 수 있습니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
알람이 누락되지 않도록 앱 실행 시 안내되는 **통합 권한 설정**을 반드시 완료해주세요:
|
||||||
|
1. 배터리 사용량을 **'제한 없음'**으로 설정 (배터리 최적화 제외)
|
||||||
|
2. **'정확한 알람 및 리마인더'** 권한 허용 (필수)
|
||||||
|
3. **'전체화면 알림'** 권한 허용 (잠금 화면 즉시 표시)
|
||||||
|
4. 알람 볼륨 및 진동 설정 확인
|
||||||
498
app/src/main/java/com/example/shiftalarm/AlarmActivity.kt
Normal file
498
app/src/main/java/com/example/shiftalarm/AlarmActivity.kt
Normal file
@@ -0,0 +1,498 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.app.KeyguardManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.media.AudioAttributes
|
||||||
|
import android.media.AudioManager
|
||||||
|
import android.media.MediaPlayer
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.os.PowerManager
|
||||||
|
import android.os.VibrationEffect
|
||||||
|
import android.os.Vibrator
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.WindowManager
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import com.example.shiftalarm.databinding.ActivityAlarmBinding
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
class AlarmActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var binding: ActivityAlarmBinding
|
||||||
|
private var mediaPlayer: MediaPlayer? = null
|
||||||
|
private var vibrator: Vibrator? = null
|
||||||
|
private var startX = 0f
|
||||||
|
|
||||||
|
// 5분 후 자동 스누즈
|
||||||
|
private val autoStopHandler = Handler(Looper.getMainLooper())
|
||||||
|
private val AUTO_STOP_DELAY = 5L * 60 * 1000
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
// 중요: 잠금 화면 위 표시 설정을 가장 먼저 적용
|
||||||
|
setupLockScreenFlags()
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
// ForegroundService가 실행 중이면 먼저 중지
|
||||||
|
stopService(Intent(this, AlarmForegroundService::class.java))
|
||||||
|
|
||||||
|
// Service 중지 후 약간의 지연을 두어 AudioFocus가 완전히 해제되도록 함
|
||||||
|
try {
|
||||||
|
Thread.sleep(100)
|
||||||
|
} catch (e: InterruptedException) {
|
||||||
|
// 무시
|
||||||
|
}
|
||||||
|
|
||||||
|
enableEdgeToEdge()
|
||||||
|
binding = ActivityAlarmBinding.inflate(layoutInflater)
|
||||||
|
binding.root.background = ContextCompat.getDrawable(this, R.drawable.bg_alarm_gradient)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
// 추가 윈도우 플래그 설정
|
||||||
|
setupWindowFlags()
|
||||||
|
|
||||||
|
val shift = intent.getStringExtra("EXTRA_SHIFT") ?: "근무"
|
||||||
|
binding.tvShiftType.text = if (shift == "SNOOZE") "다시 울림 알람" else "[$shift] 근무 알람"
|
||||||
|
|
||||||
|
val now = java.util.Calendar.getInstance()
|
||||||
|
val amPm = if (now.get(java.util.Calendar.AM_PM) == java.util.Calendar.AM) "오전" else "오후"
|
||||||
|
val hour = now.get(java.util.Calendar.HOUR)
|
||||||
|
val hourText = if (hour == 0) 12 else hour
|
||||||
|
val min = now.get(java.util.Calendar.MINUTE)
|
||||||
|
binding.tvCurrentTime.text = String.format("%s %d:%02d", amPm, hourText, min)
|
||||||
|
|
||||||
|
val today = LocalDate.now()
|
||||||
|
val dayOfWeek = today.dayOfWeek.getDisplayName(java.time.format.TextStyle.FULL, java.util.Locale.KOREAN)
|
||||||
|
binding.tvDate.text = String.format("%d월 %d일 %s", today.monthValue, today.dayOfMonth, dayOfWeek)
|
||||||
|
|
||||||
|
// 알람 시작 (화면 상태와 무관하게 항상 실행)
|
||||||
|
startAlarm()
|
||||||
|
setupControls()
|
||||||
|
|
||||||
|
// 5분 후 자동 스누즈
|
||||||
|
autoStopHandler.postDelayed({
|
||||||
|
Toast.makeText(this, "알람이 자동으로 다시 울림 설정되었습니다.", Toast.LENGTH_LONG).show()
|
||||||
|
snoozeAlarm()
|
||||||
|
stopAlarm()
|
||||||
|
finish()
|
||||||
|
}, AUTO_STOP_DELAY)
|
||||||
|
|
||||||
|
// 키가드(잠금화면) 상태 변화 리스너 등록
|
||||||
|
registerKeyguardListener()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 잠금 화면 관련 플래그를 super.onCreate 이전에 설정
|
||||||
|
*/
|
||||||
|
private fun setupLockScreenFlags() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||||
|
setShowWhenLocked(true)
|
||||||
|
setTurnScreenOn(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupWindowFlags() {
|
||||||
|
// ========================================
|
||||||
|
// 알람 화면이 패턴/지문보다 먼저 표시되도록 설정
|
||||||
|
// ========================================
|
||||||
|
// 중요: requestDismissKeyguard()를 호출하면 패턴/지문이 먼저 뜸
|
||||||
|
// 알람 화면을 먼저 띄우려면 FLAG_SHOW_WHEN_LOCKED만 사용해야 함
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||||
|
// 1. 가장 먼저: 화면 켜기 + 잠금화면 위에 표시
|
||||||
|
setShowWhenLocked(true)
|
||||||
|
setTurnScreenOn(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 화면 켜짐 유지
|
||||||
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
|
||||||
|
// 3. 하위 호환성: Android 8.0 이하
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1) {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
window.addFlags(
|
||||||
|
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
|
||||||
|
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Android 14+ 추가 플래그
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
|
window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN)
|
||||||
|
window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Android 10+ 레이아웃
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 전체화면 모드 (모든 기기 공통)
|
||||||
|
setFullscreenMode()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전체화면 모드 설정
|
||||||
|
*/
|
||||||
|
private fun isSamsungDevice(): Boolean {
|
||||||
|
val manufacturer = Build.MANUFACTURER?.lowercase() ?: ""
|
||||||
|
val brand = Build.BRAND?.lowercase() ?: ""
|
||||||
|
return manufacturer.contains("samsung") || brand.contains("samsung")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전체화면 모드 설정
|
||||||
|
*/
|
||||||
|
private fun setFullscreenMode() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
// Android 11+ (API 30+): WindowInsetsController 사용
|
||||||
|
window.setDecorFitsSystemWindows(false)
|
||||||
|
window.insetsController?.let { controller ->
|
||||||
|
controller.hide(android.view.WindowInsets.Type.statusBars() or android.view.WindowInsets.Type.navigationBars())
|
||||||
|
controller.systemBarsBehavior = android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Android 10 이하: systemUiVisibility 사용
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
window.decorView.systemUiVisibility = (
|
||||||
|
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
|
||||||
|
View.SYSTEM_UI_FLAG_FULLSCREEN or
|
||||||
|
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupControls() {
|
||||||
|
binding.btnSnooze.setOnClickListener {
|
||||||
|
handleSnooze()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swipe-to-dismiss for Stop Button
|
||||||
|
var startX = 0f
|
||||||
|
val dismissBtn = binding.btnDismiss
|
||||||
|
val maxSwipe = dpToPx(100f).toFloat()
|
||||||
|
|
||||||
|
dismissBtn.setOnTouchListener { v, event ->
|
||||||
|
when (event.action) {
|
||||||
|
MotionEvent.ACTION_DOWN -> {
|
||||||
|
startX = event.rawX
|
||||||
|
v.animate().cancel()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
MotionEvent.ACTION_MOVE -> {
|
||||||
|
val dx = event.rawX - startX
|
||||||
|
val clampedDx = if (dx > 0) dx.coerceAtMost(maxSwipe) else dx.coerceAtLeast(-maxSwipe)
|
||||||
|
v.translationX = clampedDx
|
||||||
|
|
||||||
|
// Visual feedback: scale up when near trigger
|
||||||
|
val ratio = abs(clampedDx) / maxSwipe
|
||||||
|
v.scaleX = 1f + (ratio * 0.15f)
|
||||||
|
v.scaleY = 1f + (ratio * 0.15f)
|
||||||
|
v.alpha = 1f - (ratio * 0.3f)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||||
|
val dx = event.rawX - startX
|
||||||
|
if (abs(dx) > maxSwipe * 0.8f) {
|
||||||
|
// Trigger Dismiss
|
||||||
|
(getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator)?.vibrate(50)
|
||||||
|
Toast.makeText(this, "알람 해제 완료", Toast.LENGTH_SHORT).show()
|
||||||
|
stopAlarm(); finish()
|
||||||
|
} else {
|
||||||
|
// Reset
|
||||||
|
v.animate()
|
||||||
|
.translationX(0f)
|
||||||
|
.scaleX(1f)
|
||||||
|
.scaleY(1f)
|
||||||
|
.alpha(1f)
|
||||||
|
.setDuration(300)
|
||||||
|
.start()
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pulse logic with enhanced glow
|
||||||
|
fun startPulse() {
|
||||||
|
binding.pulseCircle.scaleX = 0.85f; binding.pulseCircle.scaleY = 0.85f; binding.pulseCircle.alpha = 0.5f
|
||||||
|
binding.pulseCircle.animate()
|
||||||
|
.scaleX(1.3f).scaleY(1.3f).alpha(1.0f)
|
||||||
|
.setDuration(1500)
|
||||||
|
.withEndAction {
|
||||||
|
binding.pulseCircle.animate()
|
||||||
|
.scaleX(0.85f).scaleY(0.85f).alpha(0.5f)
|
||||||
|
.setDuration(1500)
|
||||||
|
.withEndAction { if(!isFinishing) startPulse() }
|
||||||
|
.start()
|
||||||
|
}
|
||||||
|
.start()
|
||||||
|
}
|
||||||
|
startPulse()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSnooze() {
|
||||||
|
(getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator)?.vibrate(50)
|
||||||
|
val snoozeRepeat = intent.getIntExtra("EXTRA_SNOOZE_REPEAT", 3)
|
||||||
|
val text = if (snoozeRepeat == 99) "다시 울림 설정됨" else "다시 울림 (${snoozeRepeat}회 남음)"
|
||||||
|
Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
|
||||||
|
snoozeAlarm(); stopAlarm(); finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dpToPx(dp: Float): Int {
|
||||||
|
return (dp * resources.displayMetrics.density).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startAlarm() {
|
||||||
|
if (mediaPlayer?.isPlaying == true) {
|
||||||
|
Log.d("AlarmActivity", "MediaPlayer가 이미 실행 중")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val soundUriStr = intent.getStringExtra("EXTRA_SOUND")
|
||||||
|
val alarmUri = if (!soundUriStr.isNullOrEmpty()) {
|
||||||
|
Uri.parse(soundUriStr)
|
||||||
|
} else {
|
||||||
|
val prefs = getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE)
|
||||||
|
val globalUriStr = prefs.getString("alarm_uri", null)
|
||||||
|
if (globalUriStr != null) Uri.parse(globalUriStr)
|
||||||
|
else android.provider.Settings.System.DEFAULT_ALARM_ALERT_URI
|
||||||
|
}
|
||||||
|
|
||||||
|
// AudioAttributes 강화: 화면 켜진 상태에서도 알람음이 울리도록
|
||||||
|
val audioAttrs = AudioAttributes.Builder()
|
||||||
|
.setUsage(AudioAttributes.USAGE_ALARM)
|
||||||
|
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||||
|
.setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED) // 볼륨 강제 적용
|
||||||
|
.build()
|
||||||
|
|
||||||
|
// AudioManager를 통해 알람 볼륨 설정
|
||||||
|
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||||
|
val originalVolume = audioManager.getStreamVolume(AudioManager.STREAM_ALARM)
|
||||||
|
val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM)
|
||||||
|
|
||||||
|
// 알람 볼륨을 최대로 설정 (사용자가 나중에 조정 가능)
|
||||||
|
try {
|
||||||
|
audioManager.setStreamVolume(AudioManager.STREAM_ALARM, maxVolume, 0)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w("AlarmActivity", "알람 볼륨 설정 실패", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
var mediaPlayerStarted = false
|
||||||
|
|
||||||
|
try {
|
||||||
|
mediaPlayer?.release()
|
||||||
|
mediaPlayer = MediaPlayer().apply {
|
||||||
|
setAudioAttributes(audioAttrs)
|
||||||
|
setDataSource(this@AlarmActivity, alarmUri!!)
|
||||||
|
isLooping = true
|
||||||
|
setVolume(1.0f, 1.0f) // 최대 볼륨
|
||||||
|
prepare()
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
mediaPlayerStarted = true
|
||||||
|
Log.d("AlarmActivity", "MediaPlayer 시작 성공 (사용자 지정음)")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("AlarmActivity", "MediaPlayer 시작 실패 (사용자 지정음), fallback 시도", e)
|
||||||
|
|
||||||
|
// Fallback 1: 시스템 기본 알람음
|
||||||
|
try {
|
||||||
|
val fallback = android.provider.Settings.System.DEFAULT_ALARM_ALERT_URI
|
||||||
|
mediaPlayer = MediaPlayer().apply {
|
||||||
|
setAudioAttributes(audioAttrs)
|
||||||
|
setDataSource(this@AlarmActivity, fallback)
|
||||||
|
isLooping = true
|
||||||
|
setVolume(1.0f, 1.0f)
|
||||||
|
prepare()
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
mediaPlayerStarted = true
|
||||||
|
Log.d("AlarmActivity", "MediaPlayer 시작 성공 (Fallback 1: 시스템 기본)")
|
||||||
|
} catch (e2: Exception) {
|
||||||
|
Log.e("AlarmActivity", "Fallback 1 실패", e2)
|
||||||
|
|
||||||
|
// Fallback 2: RingtoneManager에서 기본 알람 가져오기
|
||||||
|
try {
|
||||||
|
val ringtoneUri = android.media.RingtoneManager.getDefaultUri(android.media.RingtoneManager.TYPE_ALARM)
|
||||||
|
mediaPlayer = MediaPlayer().apply {
|
||||||
|
setAudioAttributes(audioAttrs)
|
||||||
|
setDataSource(this@AlarmActivity, ringtoneUri)
|
||||||
|
isLooping = true
|
||||||
|
setVolume(1.0f, 1.0f)
|
||||||
|
prepare()
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
mediaPlayerStarted = true
|
||||||
|
Log.d("AlarmActivity", "MediaPlayer 시작 성공 (Fallback 2: RingtoneManager)")
|
||||||
|
} catch (e3: Exception) {
|
||||||
|
Log.e("AlarmActivity", "모든 MediaPlayer 시작 실패", e3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 진동 시작 (알람음과 독립적으로 - 알람음 실패필도 진동은 울림)
|
||||||
|
vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||||
|
try {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val vibrationEffect = VibrationEffect.createWaveform(longArrayOf(0, 1000, 500, 1000), 0)
|
||||||
|
vibrator?.vibrate(vibrationEffect)
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
vibrator?.vibrate(longArrayOf(0, 1000, 500, 1000), 0)
|
||||||
|
}
|
||||||
|
Log.d("AlarmActivity", "진동 시작 성공")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("AlarmActivity", "진동 시작 실패", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 알람음 시작 실패 시 토스트 메시지
|
||||||
|
if (!mediaPlayerStarted) {
|
||||||
|
Toast.makeText(this, "알람음 재생에 실패했습니다. 진동으로 알려드립니다.", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun snoozeAlarm() {
|
||||||
|
val snoozeMin = intent.getIntExtra("EXTRA_SNOOZE", 5)
|
||||||
|
val snoozeRepeat = intent.getIntExtra("EXTRA_SNOOZE_REPEAT", 3)
|
||||||
|
val soundUriStr = intent.getStringExtra("EXTRA_SOUND")
|
||||||
|
|
||||||
|
if (snoozeRepeat > 0) {
|
||||||
|
val nextRepeat = if (snoozeRepeat == 99) 99 else snoozeRepeat - 1
|
||||||
|
scheduleSnooze(this, snoozeMin, soundUriStr, nextRepeat)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, "다시 울림 횟수를 모두 소모하여 알람을 종료합니다.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopAlarm() {
|
||||||
|
stopService(Intent(this, AlarmForegroundService::class.java))
|
||||||
|
|
||||||
|
try {
|
||||||
|
mediaPlayer?.let {
|
||||||
|
if (it.isPlaying) it.stop()
|
||||||
|
it.release()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {}
|
||||||
|
mediaPlayer = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
vibrator?.cancel()
|
||||||
|
} catch (e: Exception) {}
|
||||||
|
vibrator = null
|
||||||
|
|
||||||
|
val nm = getSystemService(Context.NOTIFICATION_SERVICE) as android.app.NotificationManager
|
||||||
|
nm.cancel(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
setIntent(intent)
|
||||||
|
val shift = intent.getStringExtra("EXTRA_SHIFT") ?: "근무"
|
||||||
|
binding.tvShiftType.text = if (shift == "SNOOZE") "다시 울림 알람" else "[$shift] 근무 알람"
|
||||||
|
|
||||||
|
stopAlarm()
|
||||||
|
startAlarm()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAttachedToWindow() {
|
||||||
|
super.onAttachedToWindow()
|
||||||
|
// 화면 켜짐 및 잠금 화면 위 표시 재적용
|
||||||
|
setupWindowFlags()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
autoStopHandler.removeCallbacksAndMessages(null)
|
||||||
|
stopAlarm()
|
||||||
|
unregisterKeyguardListener()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// 키가드(잠금화면) 상태 감지 및 알람 해제 처리
|
||||||
|
// ========================================
|
||||||
|
private var keyguardManager: KeyguardManager? = null
|
||||||
|
private var keyguardCallback: KeyguardManager.KeyguardDismissCallback? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 키가드(잠금화면) 상태 변화를 감지하여 알람을 적절히 처리
|
||||||
|
* 안드로이드 버전별로 다른 방식으로 처리
|
||||||
|
*/
|
||||||
|
private fun registerKeyguardListener() {
|
||||||
|
keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as? KeyguardManager
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
// Android 8.0+: KeyguardDismissCallback 사용
|
||||||
|
keyguardCallback = object : KeyguardManager.KeyguardDismissCallback() {
|
||||||
|
override fun onDismissError() {
|
||||||
|
Log.e("AlarmActivity", "Keyguard dismiss error")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDismissSucceeded() {
|
||||||
|
Log.d("AlarmActivity", "Keyguard dismissed successfully - 사용자가 패턴/지문으로 해제함")
|
||||||
|
// 패턴/지문 해제 후 알람 계속 울리게 하려면 여기서 아무것도 하지 않음
|
||||||
|
// 알람을 자동으로 멈추려면: stopAlarm(); finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDismissCancelled() {
|
||||||
|
Log.d("AlarmActivity", "Keyguard dismiss cancelled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unregisterKeyguardListener() {
|
||||||
|
keyguardCallback = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 현재 키가드(잠금화면)가 잠겨있는지 확인
|
||||||
|
*/
|
||||||
|
private fun isKeyguardLocked(): Boolean {
|
||||||
|
return keyguardManager?.isKeyguardLocked ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 현재 키가드(잠금화면)가 보안 잠금(패턴/PIN/지문)을 사용하는지 확인
|
||||||
|
*/
|
||||||
|
private fun isKeyguardSecure(): Boolean {
|
||||||
|
return keyguardManager?.isKeyguardSecure ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
// 홈 버튼이나 다른 앱으로 전환 시 알람 계속 울리도록 함
|
||||||
|
// 사용자가 의도적으로 알람을 해제하지 않았으므로
|
||||||
|
Log.d("AlarmActivity", "onPause - 알람 계속 유지")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
// 알람 화면이 백그라운드로 갔을 때
|
||||||
|
// 잠금화면이 다시 잠기면 알람을 멈추지 않고 계속 유지
|
||||||
|
Log.d("AlarmActivity", "onStop - 알람 계속 유지")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||||
|
super.onWindowFocusChanged(hasFocus)
|
||||||
|
if (hasFocus) {
|
||||||
|
// 알람 화면이 다시 포커스를 받으면 전체화면 모드 재적용
|
||||||
|
setFullscreenMode()
|
||||||
|
Log.d("AlarmActivity", "Window focus regained")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
app/src/main/java/com/example/shiftalarm/AlarmEventLogger.kt
Normal file
24
app/src/main/java/com/example/shiftalarm/AlarmEventLogger.kt
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
class AlarmEventLogger {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "AlarmEventLogger"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logAlarmEvent(event: String) {
|
||||||
|
val currentTime = System.currentTimeMillis()
|
||||||
|
Log.d(TAG, "Alarm Event: $event at $currentTime")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logAlarmSet(alarmId: Int, time: String) {
|
||||||
|
Log.i(TAG, "Alarm set: ID = $alarmId, Time = $time")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logAlarmTriggered(alarmId: Int) {
|
||||||
|
Log.w(TAG, "Alarm triggered: ID = $alarmId")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logAlarmCanceled(alarmId: Int) {
|
||||||
|
Log.e(TAG, "Alarm canceled: ID = $alarmId")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.IBinder
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
|
||||||
|
class AlarmForegroundService : Service() {
|
||||||
|
|
||||||
|
private val CHANNEL_ID = "SHIFT_ALARM_CHANNEL_V5"
|
||||||
|
private val NOTIFICATION_ID = 1
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
val shiftType = intent?.getStringExtra("EXTRA_SHIFT") ?: "근무"
|
||||||
|
val soundUri = intent?.getStringExtra("EXTRA_SOUND")
|
||||||
|
val snoozeMin = intent?.getIntExtra("EXTRA_SNOOZE", 5) ?: 5
|
||||||
|
val snoozeRepeat = intent?.getIntExtra("EXTRA_SNOOZE_REPEAT", 3) ?: 3
|
||||||
|
|
||||||
|
// 1. 알림 채널 생성
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val channel = NotificationChannel(
|
||||||
|
CHANNEL_ID,
|
||||||
|
"교대링 알람",
|
||||||
|
NotificationManager.IMPORTANCE_HIGH
|
||||||
|
).apply {
|
||||||
|
description = "알람이 울리는 동안 표시되는 알림입니다."
|
||||||
|
setSound(null, null) // 소리는 Activity에서 재생
|
||||||
|
enableVibration(false) // 진동은 Activity에서 재생
|
||||||
|
}
|
||||||
|
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
manager.createNotificationChannel(channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 전체화면 실행을 위한 PendingIntent
|
||||||
|
val fullScreenIntent = Intent(this, AlarmActivity::class.java).apply {
|
||||||
|
putExtra("EXTRA_SHIFT", shiftType)
|
||||||
|
putExtra("EXTRA_SOUND", soundUri)
|
||||||
|
putExtra("EXTRA_SNOOZE", snoozeMin)
|
||||||
|
putExtra("EXTRA_SNOOZE_REPEAT", snoozeRepeat)
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||||
|
}
|
||||||
|
|
||||||
|
val fullScreenPendingIntent = PendingIntent.getActivity(
|
||||||
|
this,
|
||||||
|
100,
|
||||||
|
fullScreenIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
// 3. 일원화된 단일 알림 생성
|
||||||
|
val contentText = if (shiftType == "SNOOZE") "다시 울림 알람입니다." else "오늘의 근무는 [$shiftType] 입니다."
|
||||||
|
|
||||||
|
val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
|
||||||
|
.setContentTitle("교대링 알람 작동 중")
|
||||||
|
.setContentText(contentText)
|
||||||
|
.setSmallIcon(R.drawable.ic_alarm_blue)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||||
|
.setCategory(NotificationCompat.CATEGORY_ALARM)
|
||||||
|
.setFullScreenIntent(fullScreenPendingIntent, true)
|
||||||
|
.setContentIntent(fullScreenPendingIntent)
|
||||||
|
.setOngoing(true)
|
||||||
|
.setAutoCancel(false)
|
||||||
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
// 4. Foreground 시작
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // Android 14+
|
||||||
|
startForeground(NOTIFICATION_ID, notification, android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
|
||||||
|
} else {
|
||||||
|
startForeground(NOTIFICATION_ID, notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
return START_NOT_STICKY
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent?): IBinder? = null
|
||||||
|
}
|
||||||
228
app/src/main/java/com/example/shiftalarm/AlarmPermissionUtil.kt
Normal file
228
app/src/main/java/com/example/shiftalarm/AlarmPermissionUtil.kt
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.AlarmManager
|
||||||
|
import android.app.AlertDialog
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.PowerManager
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
|
||||||
|
object AlarmPermissionUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전체 권한 상태를 확인하고 필요한 경우 통합 안내 다이얼로그를 표시합니다.
|
||||||
|
*/
|
||||||
|
fun checkAndRequestAllPermissions(activity: ComponentActivity) {
|
||||||
|
val missingPermissions = mutableListOf<String>()
|
||||||
|
|
||||||
|
// 1. 알림 권한 (Android 13+)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
missingPermissions.add("알림 표시 (알람 울림 확인)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 정확한 알람 권한 (Android 12+)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
val am = activity.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
|
if (!am.canScheduleExactAlarms()) {
|
||||||
|
missingPermissions.add("정확한 알람 (정시에 울림 보장)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 배터리 최적화 제외 (Android 6+)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
val pm = activity.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
if (!pm.isIgnoringBatteryOptimizations(activity.packageName)) {
|
||||||
|
missingPermissions.add("배터리 최적화 제외 (절전 모드 무시)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 전체화면 알림 권한 (Android 14+)
|
||||||
|
if (Build.VERSION.SDK_INT >= 34) {
|
||||||
|
val nm = activity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
if (!nm.canUseFullScreenIntent()) {
|
||||||
|
missingPermissions.add("전체화면 알림 (잠금 화면에서 즉시 표시)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missingPermissions.isNotEmpty()) {
|
||||||
|
showIntegratedPermissionDialog(activity, missingPermissions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showIntegratedPermissionDialog(activity: ComponentActivity, missing: List<String>) {
|
||||||
|
val message = StringBuilder("안정적인 알람 작동을 위해 아래 권한들이 필요합니다:\n\n")
|
||||||
|
missing.forEach { message.append("- $it\n") }
|
||||||
|
message.append("\n[확인]을 누르면 설정 화면으로 순차적으로 안내합니다.")
|
||||||
|
|
||||||
|
AlertDialog.Builder(activity)
|
||||||
|
.setTitle("권한 설정 안내")
|
||||||
|
.setMessage(message.toString())
|
||||||
|
.setPositiveButton("확인") { _, _ ->
|
||||||
|
startPermissionFlow(activity)
|
||||||
|
}
|
||||||
|
.setNegativeButton("나중에", null)
|
||||||
|
.setCancelable(false)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startPermissionFlow(activity: ComponentActivity) {
|
||||||
|
// 순차적으로 가장 중요한 것부터 요청
|
||||||
|
|
||||||
|
// 1. 알림 권한 (시스템 팝업)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 101)
|
||||||
|
return // 알림 권한 결과 콜백 이후 다음으로 넘어가도록 유도 (혹은 그냥 연달아 띄움)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 배터리 최적화 (시스템 팝업)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
val pm = activity.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
if (!pm.isIgnoringBatteryOptimizations(activity.packageName)) {
|
||||||
|
requestBatteryOptimization(activity)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 정확한 알람 (설정 화면)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
val am = activity.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
|
if (!am.canScheduleExactAlarms()) {
|
||||||
|
val intent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM).apply {
|
||||||
|
data = Uri.parse("package:${activity.packageName}")
|
||||||
|
}
|
||||||
|
activity.startActivity(intent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 전체화면 알림 (설정 화면)
|
||||||
|
if (Build.VERSION.SDK_INT >= 34) {
|
||||||
|
val nm = activity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
if (!nm.canUseFullScreenIntent()) {
|
||||||
|
try {
|
||||||
|
val intent = Intent("android.settings.MANAGE_APP_USE_FULL_SCREEN_INTENT").apply {
|
||||||
|
data = Uri.parse("package:${activity.packageName}")
|
||||||
|
}
|
||||||
|
activity.startActivity(intent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||||
|
data = Uri.parse("package:${activity.packageName}")
|
||||||
|
}
|
||||||
|
activity.startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestBatteryOptimization(context: Context) {
|
||||||
|
try {
|
||||||
|
val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
|
||||||
|
data = Uri.parse("package:${context.packageName}")
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
}
|
||||||
|
context.startActivity(intent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
try {
|
||||||
|
val intent = Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS).apply {
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
}
|
||||||
|
context.startActivity(intent)
|
||||||
|
} catch (e2: Exception) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestOverlayPermission(context: Context) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION).apply {
|
||||||
|
data = Uri.parse("package:${context.packageName}")
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
}
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestFullScreenIntentPermission(context: Context) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 34) {
|
||||||
|
try {
|
||||||
|
val intent = Intent("android.settings.MANAGE_APP_USE_FULL_SCREEN_INTENT").apply {
|
||||||
|
data = Uri.parse("package:${context.packageName}")
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
}
|
||||||
|
context.startActivity(intent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||||
|
data = Uri.parse("package:${context.packageName}")
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
}
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isAllPermissionsGranted(context: Context): Boolean {
|
||||||
|
var allGranted = true
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
allGranted = allGranted && ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
val am = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
|
allGranted = allGranted && am.canScheduleExactAlarms()
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
allGranted = allGranted && pm.isIgnoringBatteryOptimizations(context.packageName)
|
||||||
|
allGranted = allGranted && Settings.canDrawOverlays(context)
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= 34) {
|
||||||
|
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
allGranted = allGranted && nm.canUseFullScreenIntent()
|
||||||
|
}
|
||||||
|
return allGranted
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getBatteryOptimizationStatus(context: Context): Boolean {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
return pm.isIgnoringBatteryOptimizations(context.packageName)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getExactAlarmStatus(context: Context): Boolean {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
val am = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
|
return am.canScheduleExactAlarms()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getOverlayStatus(context: Context): Boolean {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
return Settings.canDrawOverlays(context)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFullScreenIntentStatus(context: Context): Boolean {
|
||||||
|
if (Build.VERSION.SDK_INT >= 34) {
|
||||||
|
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
return nm.canUseFullScreenIntent()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
103
app/src/main/java/com/example/shiftalarm/AlarmReceiver.kt
Normal file
103
app/src/main/java/com/example/shiftalarm/AlarmReceiver.kt
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.PowerManager
|
||||||
|
import android.util.Log
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
|
||||||
|
class AlarmReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
|
private val TAG = "AlarmReceiver"
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent?) {
|
||||||
|
Log.d(TAG, "===== 알람 수신 (Receiver) =====")
|
||||||
|
|
||||||
|
val alarmId = intent?.getIntExtra("EXTRA_ALARM_ID", -1) ?: -1
|
||||||
|
val isCustom = intent?.getBooleanExtra("EXTRA_IS_CUSTOM", false) ?: false
|
||||||
|
|
||||||
|
// 커스텀 알람인 경우 DB에서 여전히 유효한지 확인 (삭제된 알람이 울리는 문제 해결)
|
||||||
|
if (isCustom && alarmId != -1) {
|
||||||
|
val customAlarmId = intent.getIntExtra("EXTRA_UNIQUE_ID", -1)
|
||||||
|
if (customAlarmId != -1) {
|
||||||
|
// 비동기로 DB 확인
|
||||||
|
val scope = CoroutineScope(Dispatchers.IO)
|
||||||
|
scope.launch {
|
||||||
|
val repo = ShiftRepository(context)
|
||||||
|
val alarms = repo.getAllCustomAlarms()
|
||||||
|
val alarmExists = alarms.any { it.id == customAlarmId && it.isEnabled }
|
||||||
|
|
||||||
|
if (!alarmExists) {
|
||||||
|
Log.w(TAG, "삭제된 또는 비활성화된 알람입니다. 무시합니다. (ID: $customAlarmId)")
|
||||||
|
scope.cancel()
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
// 알람이 유효하면 직접 AlarmActivity 실행 + Foreground Service 시작
|
||||||
|
startAlarm(context, intent)
|
||||||
|
scope.cancel()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 일반 알람은 바로 직접 실행
|
||||||
|
startAlarm(context, intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startAlarm(context: Context, intent: Intent?) {
|
||||||
|
// WakeLock 획득 (화면 켜기 및 Activity 실행 보장)
|
||||||
|
val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
val wakeLock = pm.newWakeLock(
|
||||||
|
PowerManager.PARTIAL_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP,
|
||||||
|
"ShiftAlarm::AlarmWakeLock"
|
||||||
|
)
|
||||||
|
wakeLock.acquire(30 * 1000L) // 30초 - Activity 실행 및 초기화에 충분한 시간
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Foreground Service 시작 (알림 표시 및 시스템에 알람 실행 중 알림)
|
||||||
|
val serviceIntent = Intent(context, AlarmForegroundService::class.java).apply {
|
||||||
|
putExtra("EXTRA_SHIFT", intent?.getStringExtra("EXTRA_SHIFT") ?: "근무")
|
||||||
|
putExtra("EXTRA_SOUND", intent?.getStringExtra("EXTRA_SOUND"))
|
||||||
|
putExtra("EXTRA_SNOOZE", intent?.getIntExtra("EXTRA_SNOOZE", 5) ?: 5)
|
||||||
|
putExtra("EXTRA_SNOOZE_REPEAT", intent?.getIntExtra("EXTRA_SNOOZE_REPEAT", 3) ?: 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
context.startForegroundService(serviceIntent)
|
||||||
|
} else {
|
||||||
|
context.startService(serviceIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. AlarmActivity 직접 실행 (알람 화면 표시)
|
||||||
|
val activityIntent = Intent(context, AlarmActivity::class.java).apply {
|
||||||
|
putExtra("EXTRA_SHIFT", intent?.getStringExtra("EXTRA_SHIFT") ?: "근무")
|
||||||
|
putExtra("EXTRA_SOUND", intent?.getStringExtra("EXTRA_SOUND"))
|
||||||
|
putExtra("EXTRA_SNOOZE", intent?.getIntExtra("EXTRA_SNOOZE", 5) ?: 5)
|
||||||
|
putExtra("EXTRA_SNOOZE_REPEAT", intent?.getIntExtra("EXTRA_SNOOZE_REPEAT", 3) ?: 3)
|
||||||
|
// 중요: 새 태스크로 실행 (FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
// 기존 인스턴스 재사용 및 최상위로 가져오기
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||||
|
// 잠금 화면 위에 표시
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
|
||||||
|
// 화면 켜기
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
|
||||||
|
}
|
||||||
|
|
||||||
|
context.startActivity(activityIntent)
|
||||||
|
Log.d(TAG, "AlarmActivity 실행 완료")
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "알람 실행 실패", e)
|
||||||
|
} finally {
|
||||||
|
// WakeLock은 Activity가 화면을 켜고 나서 해제
|
||||||
|
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
||||||
|
if (wakeLock.isHeld) wakeLock.release()
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
299
app/src/main/java/com/example/shiftalarm/AlarmSyncManager.kt
Normal file
299
app/src/main/java/com/example/shiftalarm/AlarmSyncManager.kt
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 알람 동기화 관리자
|
||||||
|
* DB와 AlarmManager 간의 실시간 동기화를 보장합니다.
|
||||||
|
*
|
||||||
|
* 동기화 전략:
|
||||||
|
* 1. DB 작업과 AlarmManager 작업을 원자적으로 처리
|
||||||
|
* 2. 실패 시 롤백 메커니즘 제공
|
||||||
|
* 3. 동기화 상태 추적 및 재시도
|
||||||
|
*/
|
||||||
|
object AlarmSyncManager {
|
||||||
|
|
||||||
|
private const val TAG = "AlarmSyncManager"
|
||||||
|
private const val PREFS_NAME = "AlarmSyncPrefs"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 알람 추가 동기화
|
||||||
|
* DB에 추가 후 AlarmManager에 즉시 예약
|
||||||
|
*/
|
||||||
|
suspend fun addAlarm(context: Context, alarm: CustomAlarm): Result<Unit> = withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val repo = ShiftRepository(context)
|
||||||
|
|
||||||
|
// 1. DB에 알람 추가
|
||||||
|
val alarmId = repo.addCustomAlarm(alarm)
|
||||||
|
Log.d(TAG, "알람 DB 추가 완료: ID=$alarmId")
|
||||||
|
|
||||||
|
// 2. AlarmManager에 예약
|
||||||
|
val today = LocalDate.now(SEOUL_ZONE)
|
||||||
|
val customAlarms = repo.getAllCustomAlarms()
|
||||||
|
val addedAlarm = customAlarms.find { it.id == alarmId.toInt() }
|
||||||
|
|
||||||
|
if (addedAlarm == null) {
|
||||||
|
Log.w(TAG, "추가된 알람을 DB에서 찾을 수 없음: ID=$alarmId")
|
||||||
|
return@withContext Result.failure(Exception("알람을 찾을 수 없습니다"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addedAlarm.isEnabled) {
|
||||||
|
// 향후 30일치 예약
|
||||||
|
for (i in 0 until 30) {
|
||||||
|
val targetDate = today.plusDays(i.toLong())
|
||||||
|
val shift = repo.getShift(targetDate,
|
||||||
|
context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE)
|
||||||
|
.getString("selected_team", "A") ?: "A",
|
||||||
|
context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE)
|
||||||
|
.getString("selected_factory", "Jeonju") ?: "Jeonju"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (addedAlarm.shiftType == "기타" || addedAlarm.shiftType == shift) {
|
||||||
|
scheduleCustomAlarm(
|
||||||
|
context,
|
||||||
|
targetDate,
|
||||||
|
addedAlarm.id,
|
||||||
|
addedAlarm.shiftType,
|
||||||
|
addedAlarm.time,
|
||||||
|
addedAlarm.soundUri,
|
||||||
|
addedAlarm.snoozeInterval,
|
||||||
|
addedAlarm.snoozeRepeat
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(TAG, "알람 AlarmManager 예약 완료: ID=$alarmId")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 동기화 상태 저장
|
||||||
|
saveSyncStatus(context, "last_add_alarm", System.currentTimeMillis())
|
||||||
|
|
||||||
|
Result.success(Unit)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "알람 추가 동기화 실패", e)
|
||||||
|
Result.failure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 알람 수정 동기화
|
||||||
|
* DB 수정 후 기존 AlarmManager 예약 취소 후 재예약
|
||||||
|
*/
|
||||||
|
suspend fun updateAlarm(context: Context, alarm: CustomAlarm): Result<Unit> = withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val repo = ShiftRepository(context)
|
||||||
|
|
||||||
|
// 1. 기존 AlarmManager 예약 취소
|
||||||
|
cancelAllCustomAlarmSchedules(context, alarm.id)
|
||||||
|
Log.d(TAG, "기존 알람 예약 취소 완료: ID=${alarm.id}")
|
||||||
|
|
||||||
|
// 2. DB 업데이트
|
||||||
|
repo.updateCustomAlarm(alarm)
|
||||||
|
Log.d(TAG, "알람 DB 업데이트 완료: ID=${alarm.id}")
|
||||||
|
|
||||||
|
// 3. 활성화된 알람이면 재예약
|
||||||
|
if (alarm.isEnabled) {
|
||||||
|
val today = LocalDate.now(SEOUL_ZONE)
|
||||||
|
for (i in 0 until 30) {
|
||||||
|
val targetDate = today.plusDays(i.toLong())
|
||||||
|
val shift = repo.getShift(targetDate,
|
||||||
|
context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE)
|
||||||
|
.getString("selected_team", "A") ?: "A",
|
||||||
|
context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE)
|
||||||
|
.getString("selected_factory", "Jeonju") ?: "Jeonju"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (alarm.shiftType == "기타" || alarm.shiftType == shift) {
|
||||||
|
scheduleCustomAlarm(
|
||||||
|
context,
|
||||||
|
targetDate,
|
||||||
|
alarm.id,
|
||||||
|
alarm.shiftType,
|
||||||
|
alarm.time,
|
||||||
|
alarm.soundUri,
|
||||||
|
alarm.snoozeInterval,
|
||||||
|
alarm.snoozeRepeat
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(TAG, "알람 재예약 완료: ID=${alarm.id}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 동기화 상태 저장
|
||||||
|
saveSyncStatus(context, "last_update_alarm", System.currentTimeMillis())
|
||||||
|
|
||||||
|
Result.success(Unit)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "알람 수정 동기화 실패", e)
|
||||||
|
Result.failure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 알람 삭제 동기화
|
||||||
|
* AlarmManager 예약 먼저 취소 후 DB에서 삭제
|
||||||
|
*/
|
||||||
|
suspend fun deleteAlarm(context: Context, alarm: CustomAlarm): Result<Unit> = withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val repo = ShiftRepository(context)
|
||||||
|
|
||||||
|
// 1. AlarmManager 예약 취소 (DB 삭제 전에 먼저!)
|
||||||
|
cancelAllCustomAlarmSchedules(context, alarm.id)
|
||||||
|
Log.d(TAG, "알람 예약 취소 완료: ID=${alarm.id}")
|
||||||
|
|
||||||
|
// 2. DB에서 삭제
|
||||||
|
repo.deleteCustomAlarm(alarm)
|
||||||
|
Log.d(TAG, "알람 DB 삭제 완료: ID=${alarm.id}")
|
||||||
|
|
||||||
|
// 3. 동기화 상태 저장
|
||||||
|
saveSyncStatus(context, "last_delete_alarm", System.currentTimeMillis())
|
||||||
|
|
||||||
|
Result.success(Unit)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "알람 삭제 동기화 실패", e)
|
||||||
|
Result.failure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 알람 토글 동기화 (활성화/비활성화)
|
||||||
|
*/
|
||||||
|
suspend fun toggleAlarm(context: Context, alarm: CustomAlarm, enable: Boolean): Result<Unit> = withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val repo = ShiftRepository(context)
|
||||||
|
val updatedAlarm = alarm.copy(isEnabled = enable)
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
// 활성화: DB 업데이트 후 예약
|
||||||
|
repo.updateCustomAlarm(updatedAlarm)
|
||||||
|
val today = LocalDate.now(SEOUL_ZONE)
|
||||||
|
for (i in 0 until 30) {
|
||||||
|
val targetDate = today.plusDays(i.toLong())
|
||||||
|
val shift = repo.getShift(targetDate,
|
||||||
|
context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE)
|
||||||
|
.getString("selected_team", "A") ?: "A",
|
||||||
|
context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE)
|
||||||
|
.getString("selected_factory", "Jeonju") ?: "Jeonju"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (alarm.shiftType == "기타" || alarm.shiftType == shift) {
|
||||||
|
scheduleCustomAlarm(
|
||||||
|
context,
|
||||||
|
targetDate,
|
||||||
|
alarm.id,
|
||||||
|
alarm.shiftType,
|
||||||
|
alarm.time,
|
||||||
|
alarm.soundUri,
|
||||||
|
alarm.snoozeInterval,
|
||||||
|
alarm.snoozeRepeat
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(TAG, "알람 활성화 완료: ID=${alarm.id}")
|
||||||
|
} else {
|
||||||
|
// 비활성화: 예약 취소 후 DB 업데이트
|
||||||
|
cancelAllCustomAlarmSchedules(context, alarm.id)
|
||||||
|
repo.updateCustomAlarm(updatedAlarm)
|
||||||
|
Log.d(TAG, "알람 비활성화 완료: ID=${alarm.id}")
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSyncStatus(context, "last_toggle_alarm", System.currentTimeMillis())
|
||||||
|
Result.success(Unit)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "알람 토글 동기화 실패", e)
|
||||||
|
Result.failure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전체 알람 동기화 (앱 시작 시 호출)
|
||||||
|
*/
|
||||||
|
suspend fun syncAllAlarmsWithCheck(context: Context): Result<SyncResult> = withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "전체 알람 동기화 시작")
|
||||||
|
|
||||||
|
// 1. 기존 모든 알람 취소
|
||||||
|
val repo = ShiftRepository(context)
|
||||||
|
val allAlarms = repo.getAllCustomAlarms()
|
||||||
|
|
||||||
|
for (alarm in allAlarms) {
|
||||||
|
cancelAllCustomAlarmSchedules(context, alarm.id)
|
||||||
|
}
|
||||||
|
Log.d(TAG, "기존 모든 알람 취소 완료: ${allAlarms.size}개")
|
||||||
|
|
||||||
|
// 2. 활성화된 알람만 재예약
|
||||||
|
val enabledAlarms = allAlarms.filter { it.isEnabled }
|
||||||
|
val today = LocalDate.now(SEOUL_ZONE)
|
||||||
|
val prefs = context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE)
|
||||||
|
val team = prefs.getString("selected_team", "A") ?: "A"
|
||||||
|
val factory = prefs.getString("selected_factory", "Jeonju") ?: "Jeonju"
|
||||||
|
|
||||||
|
var scheduledCount = 0
|
||||||
|
for (alarm in enabledAlarms) {
|
||||||
|
for (i in 0 until 30) {
|
||||||
|
val targetDate = today.plusDays(i.toLong())
|
||||||
|
val shift = repo.getShift(targetDate, team, factory)
|
||||||
|
|
||||||
|
if (alarm.shiftType == "기타" || alarm.shiftType == shift) {
|
||||||
|
scheduleCustomAlarm(
|
||||||
|
context,
|
||||||
|
targetDate,
|
||||||
|
alarm.id,
|
||||||
|
alarm.shiftType,
|
||||||
|
alarm.time,
|
||||||
|
alarm.soundUri,
|
||||||
|
alarm.snoozeInterval,
|
||||||
|
alarm.snoozeRepeat
|
||||||
|
)
|
||||||
|
scheduledCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "알람 재예약 완료: ${enabledAlarms.size}개 알람, ${scheduledCount}개 예약")
|
||||||
|
|
||||||
|
// 3. 동기화 상태 저장
|
||||||
|
saveSyncStatus(context, "last_full_sync", System.currentTimeMillis())
|
||||||
|
|
||||||
|
Result.success(SyncResult(
|
||||||
|
totalAlarms = allAlarms.size,
|
||||||
|
enabledAlarms = enabledAlarms.size,
|
||||||
|
scheduledAlarms = scheduledCount
|
||||||
|
))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "전체 알람 동기화 실패", e)
|
||||||
|
Result.failure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 동기화 상태 저장
|
||||||
|
*/
|
||||||
|
private fun saveSyncStatus(context: Context, key: String, timestamp: Long) {
|
||||||
|
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
.edit()
|
||||||
|
.putLong(key, timestamp)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 마지막 동기화 시간 확인
|
||||||
|
*/
|
||||||
|
fun getLastSyncTime(context: Context, key: String): Long {
|
||||||
|
return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
.getLong(key, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 동기화 결과 데이터 클래스
|
||||||
|
*/
|
||||||
|
data class SyncResult(
|
||||||
|
val totalAlarms: Int,
|
||||||
|
val enabledAlarms: Int,
|
||||||
|
val scheduledAlarms: Int
|
||||||
|
)
|
||||||
|
}
|
||||||
350
app/src/main/java/com/example/shiftalarm/AlarmUtils.kt
Normal file
350
app/src/main/java/com/example/shiftalarm/AlarmUtils.kt
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.app.AlarmManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.LocalTime
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
val SEOUL_ZONE: ZoneId = ZoneId.of("Asia/Seoul")
|
||||||
|
const val TAG = "ShiftAlarm"
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 알람 ID 생성
|
||||||
|
// ============================================
|
||||||
|
fun getCustomAlarmId(date: LocalDate, uniqueId: Int): Int {
|
||||||
|
// Combine date and a unique ID from DB to avoid collisions
|
||||||
|
// Using (uniqueId % 1000) to keep it within a reasonable range
|
||||||
|
return 200000000 + (date.year % 100) * 1000000 + date.monthValue * 10000 + date.dayOfMonth * 100 + (uniqueId % 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 사용자 알람 예약
|
||||||
|
// ============================================
|
||||||
|
fun scheduleCustomAlarm(
|
||||||
|
context: Context,
|
||||||
|
date: LocalDate,
|
||||||
|
uniqueId: Int,
|
||||||
|
shiftType: String,
|
||||||
|
time: String,
|
||||||
|
soundUri: String? = null,
|
||||||
|
snoozeMin: Int = 5,
|
||||||
|
snoozeRepeat: Int = 3
|
||||||
|
) {
|
||||||
|
val alarmId = getCustomAlarmId(date, uniqueId)
|
||||||
|
val label = "사용자:$shiftType"
|
||||||
|
|
||||||
|
val parts = time.split(":")
|
||||||
|
if (parts.size != 2) return
|
||||||
|
val hour = parts[0].toIntOrNull() ?: return
|
||||||
|
val min = parts[1].toIntOrNull() ?: return
|
||||||
|
|
||||||
|
cancelAlarmInternal(context, alarmId)
|
||||||
|
|
||||||
|
val intent = Intent(context, AlarmReceiver::class.java).apply {
|
||||||
|
action = "com.example.shiftalarm.ALARM_TRIGGER"
|
||||||
|
putExtra("EXTRA_SHIFT", label)
|
||||||
|
putExtra("EXTRA_DATE", date.toString())
|
||||||
|
putExtra("EXTRA_TIME", time)
|
||||||
|
putExtra("EXTRA_ALARM_ID", alarmId)
|
||||||
|
putExtra("EXTRA_IS_CUSTOM", true)
|
||||||
|
putExtra("EXTRA_UNIQUE_ID", uniqueId) // DB 검증용
|
||||||
|
putExtra("EXTRA_SOUND", soundUri)
|
||||||
|
putExtra("EXTRA_SNOOZE", snoozeMin)
|
||||||
|
putExtra("EXTRA_SNOOZE_REPEAT", snoozeRepeat)
|
||||||
|
}
|
||||||
|
|
||||||
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
|
context, alarmId, intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
val targetDateTime = LocalDateTime.of(date, LocalTime.of(hour, min))
|
||||||
|
.withSecond(0).withNano(0)
|
||||||
|
|
||||||
|
val alarmTime = targetDateTime.atZone(SEOUL_ZONE).toInstant().toEpochMilli()
|
||||||
|
|
||||||
|
if (alarmTime > System.currentTimeMillis()) {
|
||||||
|
setExactAlarm(context, alarmTime, pendingIntent)
|
||||||
|
Log.d(TAG, "알람 예약 완료: $date $time (ID: $alarmId)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 알람 취소 (전체 범위)
|
||||||
|
// ============================================
|
||||||
|
fun cancelCustomAlarm(context: Context, date: LocalDate, uniqueId: Int) {
|
||||||
|
val alarmId = getCustomAlarmId(date, uniqueId)
|
||||||
|
cancelAlarmInternal(context, alarmId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 특정 알람의 모든 예약을 완전히 취소합니다.
|
||||||
|
* DB에서 삭제하기 전에 반드시 호출해야 합니다.
|
||||||
|
* 삭제한 알람이 울리는 문제를 해결하기 위해 365일치 + 과거 알람까지 모두 취소
|
||||||
|
*/
|
||||||
|
fun cancelAllCustomAlarmSchedules(context: Context, uniqueId: Int) {
|
||||||
|
val today = LocalDate.now(SEOUL_ZONE)
|
||||||
|
|
||||||
|
// 1. 과거 30일치 취소 (혹시 모를 과거 예약)
|
||||||
|
for (i in -30 until 0) {
|
||||||
|
val targetDate = today.plusDays(i.toLong())
|
||||||
|
cancelCustomAlarm(context, targetDate, uniqueId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 향후 365일치 모든 가능한 ID 취소 (1년치 완전 커버)
|
||||||
|
for (i in 0 until 365) {
|
||||||
|
val targetDate = today.plusDays(i.toLong())
|
||||||
|
cancelCustomAlarm(context, targetDate, uniqueId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 스누즈 알람도 취소 (스누즈는 999999 ID 사용)
|
||||||
|
cancelSnoozeAlarm(context)
|
||||||
|
|
||||||
|
// 4. 테스트 알람도 취소 (테스트는 888888 ID 사용)
|
||||||
|
cancelTestAlarm(context)
|
||||||
|
|
||||||
|
// 5. 해당 uniqueId와 관련된 모든 가능한 PendingIntent 취소 (추가 안전장치)
|
||||||
|
cancelAllPendingIntentsForUniqueId(context, uniqueId)
|
||||||
|
|
||||||
|
Log.d(TAG, "알람 예약 완전 취소 완료 (ID: $uniqueId, 범위: -30일 ~ +365일)")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 특정 uniqueId에 대한 모든 가능한 PendingIntent를 취소합니다.
|
||||||
|
* 알람 ID 생성 공식의 역연산을 통해 모든 가능성을 커버합니다.
|
||||||
|
*/
|
||||||
|
private fun cancelAllPendingIntentsForUniqueId(context: Context, uniqueId: Int) {
|
||||||
|
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
|
|
||||||
|
// uniqueId % 100의 모든 가능한 값에 대해 취소 시도
|
||||||
|
val baseId = uniqueId % 100
|
||||||
|
|
||||||
|
// 현재 연도 기준으로 여러 해에 걸친 가능한 ID들
|
||||||
|
val currentYear = LocalDate.now(SEOUL_ZONE).year % 100
|
||||||
|
val years = listOf(currentYear - 1, currentYear, currentYear + 1)
|
||||||
|
|
||||||
|
for (year in years) {
|
||||||
|
if (year < 0) continue
|
||||||
|
for (month in 1..12) {
|
||||||
|
for (day in 1..31) {
|
||||||
|
try {
|
||||||
|
val alarmId = 200000000 + year * 1000000 + month * 10000 + day * 100 + baseId
|
||||||
|
val intent = Intent(context, AlarmReceiver::class.java)
|
||||||
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
|
context, alarmId, intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
alarmManager.cancel(pendingIntent)
|
||||||
|
pendingIntent.cancel()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// 무시 - 유효하지 않은 날짜 조합
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "uniqueId $uniqueId 관련 모든 PendingIntent 취소 완료")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 스누즈 알람 취소
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 스누즈 알람 취소 - 모든 가능한 스누즈 ID 취소
|
||||||
|
*/
|
||||||
|
fun cancelSnoozeAlarm(context: Context) {
|
||||||
|
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
|
|
||||||
|
// 주요 스누즈 ID들 취소
|
||||||
|
val snoozeIds = listOf(999999, 999998, 999997, 999996, 999995)
|
||||||
|
|
||||||
|
for (snoozeId in snoozeIds) {
|
||||||
|
val intent = Intent(context, AlarmReceiver::class.java).apply {
|
||||||
|
action = "com.example.shiftalarm.SNOOZE"
|
||||||
|
}
|
||||||
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
|
context, snoozeId, intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
alarmManager.cancel(pendingIntent)
|
||||||
|
pendingIntent.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "스누즈 알람 취소 완료")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 테스트 알람 취소
|
||||||
|
*/
|
||||||
|
private fun cancelTestAlarm(context: Context) {
|
||||||
|
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
|
val intent = Intent(context, AlarmReceiver::class.java).apply {
|
||||||
|
action = "com.example.shiftalarm.ALARM_TRIGGER"
|
||||||
|
}
|
||||||
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
|
context, 888888, intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
alarmManager.cancel(pendingIntent)
|
||||||
|
pendingIntent.cancel()
|
||||||
|
Log.d(TAG, "테스트 알람 취소 완료")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cancelAlarmInternal(context: Context, alarmId: Int) {
|
||||||
|
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
|
val intent = Intent(context, AlarmReceiver::class.java)
|
||||||
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
|
context, alarmId, intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
alarmManager.cancel(pendingIntent)
|
||||||
|
pendingIntent.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 정밀 알람 설정 (setAlarmClock 우선)
|
||||||
|
// ============================================
|
||||||
|
private fun setExactAlarm(context: Context, triggerTime: Long, pendingIntent: PendingIntent) {
|
||||||
|
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
if (!alarmManager.canScheduleExactAlarms()) {
|
||||||
|
Log.e(TAG, "정확한 알람 권한 없음!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setAlarmClock은 Doze 모드에서도 정확하게 작동하며 상단바 알람 아이콘을 활성화함 (신뢰도 최고)
|
||||||
|
try {
|
||||||
|
val viewIntent = Intent(context, MainActivity::class.java).apply {
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
|
}
|
||||||
|
val viewPendingIntent = PendingIntent.getActivity(
|
||||||
|
context, 0, viewIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
val clockInfo = AlarmManager.AlarmClockInfo(triggerTime, viewPendingIntent)
|
||||||
|
alarmManager.setAlarmClock(clockInfo, pendingIntent)
|
||||||
|
Log.d(TAG, "setAlarmClock 예약 성공: ${java.util.Date(triggerTime)}")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "setAlarmClock 실패, fallback 사용", e)
|
||||||
|
try {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent)
|
||||||
|
Log.d(TAG, "setExactAndAllowWhileIdle 예약 성공")
|
||||||
|
} else {
|
||||||
|
alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent)
|
||||||
|
Log.d(TAG, "setExact 예약 성공")
|
||||||
|
}
|
||||||
|
} catch (e2: Exception) {
|
||||||
|
Log.e(TAG, "모든 알람 예약 방법 실패", e2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 스누즈
|
||||||
|
// ============================================
|
||||||
|
fun scheduleSnooze(context: Context, snoozeMin: Int, soundUri: String? = null, snoozeRepeat: Int = 3) {
|
||||||
|
val intent = Intent(context, AlarmReceiver::class.java).apply {
|
||||||
|
action = "com.example.shiftalarm.SNOOZE"
|
||||||
|
putExtra("EXTRA_SHIFT", "SNOOZE")
|
||||||
|
putExtra("EXTRA_SOUND", soundUri)
|
||||||
|
putExtra("EXTRA_SNOOZE", snoozeMin)
|
||||||
|
putExtra("EXTRA_SNOOZE_REPEAT", snoozeRepeat)
|
||||||
|
}
|
||||||
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
|
context, 999999, intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
val triggerTime = System.currentTimeMillis() + (snoozeMin * 60 * 1000)
|
||||||
|
setExactAlarm(context, triggerTime, pendingIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 테스트 알람 (5초 후)
|
||||||
|
// ============================================
|
||||||
|
fun scheduleTestAlarm(context: Context) {
|
||||||
|
val intent = Intent(context, AlarmReceiver::class.java).apply {
|
||||||
|
action = "com.example.shiftalarm.ALARM_TRIGGER"
|
||||||
|
putExtra("EXTRA_SHIFT", "테스트")
|
||||||
|
}
|
||||||
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
|
context, 888888, intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
val triggerTime = System.currentTimeMillis() + 5000
|
||||||
|
setExactAlarm(context, triggerTime, pendingIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 전체 동기화 (30일치 예약)
|
||||||
|
// ============================================
|
||||||
|
suspend fun syncAllAlarms(context: Context) {
|
||||||
|
Log.d(TAG, "===== 전체 알람 동기화 시작 (30일) =====")
|
||||||
|
val prefs = context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE)
|
||||||
|
val repo = ShiftRepository(context)
|
||||||
|
|
||||||
|
val today = LocalDate.now(SEOUL_ZONE)
|
||||||
|
val team = prefs.getString("selected_team", "A") ?: "A"
|
||||||
|
val factory = prefs.getString("selected_factory", "Jeonju") ?: "Jeonju"
|
||||||
|
|
||||||
|
// 1. 기존 알람 모두 취소 (안전장치)
|
||||||
|
// Custom 알람의 경우 ID가 uniqueId 기반이므로 모든 가능성 있는 ID를 취소하기는 어려움.
|
||||||
|
// 대신 AlarmManager에서 해당 PendingIntent를 정확히 취소해야 함.
|
||||||
|
// 하지만 uniqueId를 알 수 없으므로, 모든 날짜 루프에서 취소 시도.
|
||||||
|
|
||||||
|
val customAlarms = repo.getAllCustomAlarms()
|
||||||
|
|
||||||
|
for (i in 0 until 30) {
|
||||||
|
val targetDate = today.plusDays(i.toLong())
|
||||||
|
// 기본 알람 ID 취소 (이제 안 쓰지만 하위 호환/청소용)
|
||||||
|
val legacyId = 100000000 + (targetDate.year % 100) * 1000000 + targetDate.monthValue * 10000 + targetDate.dayOfMonth * 100
|
||||||
|
cancelAlarmInternal(context, legacyId)
|
||||||
|
|
||||||
|
// 커스텀 알람 취소
|
||||||
|
customAlarms.forEach { alarm ->
|
||||||
|
cancelCustomAlarm(context, targetDate, alarm.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ShiftAlarmDefaults.isMasterAlarmEnabled(prefs)) {
|
||||||
|
Log.d(TAG, "마스터 알람이 꺼져 있어 예약을 중단합니다.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 새로운 스케줄 생성
|
||||||
|
for (i in 0 until 30) {
|
||||||
|
val targetDate = today.plusDays(i.toLong())
|
||||||
|
val shift = repo.getShift(targetDate, team, factory)
|
||||||
|
|
||||||
|
for (alarm in customAlarms) {
|
||||||
|
if (!alarm.isEnabled) continue
|
||||||
|
|
||||||
|
// 근무 연동 조건 확인
|
||||||
|
if (alarm.shiftType == "기타" || alarm.shiftType == shift) {
|
||||||
|
scheduleCustomAlarm(
|
||||||
|
context,
|
||||||
|
targetDate,
|
||||||
|
alarm.id,
|
||||||
|
alarm.shiftType,
|
||||||
|
alarm.time,
|
||||||
|
alarm.soundUri,
|
||||||
|
alarm.snoozeInterval,
|
||||||
|
alarm.snoozeRepeat
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(TAG, "===== 전체 알람 동기화 완료 =====")
|
||||||
|
}
|
||||||
28
app/src/main/java/com/example/shiftalarm/AlarmWorker.kt
Normal file
28
app/src/main/java/com/example/shiftalarm/AlarmWorker.kt
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.app.AlarmManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.work.CoroutineWorker
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.LocalTime
|
||||||
|
import java.time.ZoneId
|
||||||
|
|
||||||
|
|
||||||
|
class AlarmWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
|
||||||
|
|
||||||
|
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
syncAllAlarms(applicationContext)
|
||||||
|
Result.success()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
Result.retry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
app/src/main/java/com/example/shiftalarm/AppDatabase.kt
Normal file
28
app/src/main/java/com/example/shiftalarm/AppDatabase.kt
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.room.*
|
||||||
|
|
||||||
|
@Database(entities = [ShiftOverride::class, DailyMemo::class, CustomAlarm::class], version = 3, exportSchema = false)
|
||||||
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
abstract fun shiftDao(): ShiftDao
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Volatile
|
||||||
|
private var INSTANCE: AppDatabase? = null
|
||||||
|
|
||||||
|
fun getDatabase(context: Context): AppDatabase {
|
||||||
|
return INSTANCE ?: synchronized(this) {
|
||||||
|
val instance = Room.databaseBuilder(
|
||||||
|
context.applicationContext,
|
||||||
|
AppDatabase::class.java,
|
||||||
|
"shift_database"
|
||||||
|
)
|
||||||
|
.fallbackToDestructiveMigration() // Simple for now
|
||||||
|
.build()
|
||||||
|
INSTANCE = instance
|
||||||
|
instance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
187
app/src/main/java/com/example/shiftalarm/AppUpdateManager.kt
Normal file
187
app/src/main/java/com/example/shiftalarm/AppUpdateManager.kt
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.ProgressDialog
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.io.BufferedInputStream
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
object AppUpdateManager {
|
||||||
|
|
||||||
|
private const val VERSION_URL = "https://git.webpluss.net/sanjeok77/ShiftRing/raw/branch/main/version.json"
|
||||||
|
|
||||||
|
fun checkUpdate(activity: Activity, silent: Boolean = false) {
|
||||||
|
val ctx = activity.applicationContext
|
||||||
|
val versionCheckUrl = "$VERSION_URL?t=${System.currentTimeMillis()}"
|
||||||
|
|
||||||
|
Thread {
|
||||||
|
try {
|
||||||
|
val url = URL(versionCheckUrl)
|
||||||
|
val connection = url.openConnection() as HttpURLConnection
|
||||||
|
connection.connectTimeout = 5000
|
||||||
|
connection.readTimeout = 5000
|
||||||
|
connection.requestMethod = "GET"
|
||||||
|
connection.useCaches = false
|
||||||
|
|
||||||
|
if (connection.responseCode == 200) {
|
||||||
|
val reader = connection.inputStream.bufferedReader()
|
||||||
|
val result = reader.readText()
|
||||||
|
reader.close()
|
||||||
|
|
||||||
|
val json = JSONObject(result)
|
||||||
|
val serverVersionName = json.getString("versionName")
|
||||||
|
val apkUrl = json.getString("apkUrl")
|
||||||
|
val changelog = json.optString("changelog", "버그 수정 및 성능 향상")
|
||||||
|
|
||||||
|
val pInfo = ctx.packageManager.getPackageInfo(ctx.packageName, 0)
|
||||||
|
val currentVersionName = pInfo.versionName ?: "0.0.0"
|
||||||
|
|
||||||
|
if (isNewerVersion(serverVersionName, currentVersionName)) {
|
||||||
|
activity.runOnUiThread {
|
||||||
|
showUpdateDialog(activity, serverVersionName, changelog, apkUrl)
|
||||||
|
}
|
||||||
|
} else if (!silent) {
|
||||||
|
activity.runOnUiThread {
|
||||||
|
Toast.makeText(ctx, "현재 최신 버전을 사용 중입니다. ($currentVersionName)", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!silent) {
|
||||||
|
activity.runOnUiThread {
|
||||||
|
Toast.makeText(ctx, "서버 연결 실패", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
if (!silent) {
|
||||||
|
activity.runOnUiThread {
|
||||||
|
Toast.makeText(ctx, "업데이트 확인 중 오류 발생", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isNewerVersion(server: String, current: String): Boolean {
|
||||||
|
try {
|
||||||
|
// Clean version strings (remove non-numeric suffixes if any)
|
||||||
|
val sClean = server.split("-")[0].split(" ")[0]
|
||||||
|
val cClean = current.split("-")[0].split(" ")[0]
|
||||||
|
|
||||||
|
val sParts = sClean.split(".").map { it.filter { char -> char.isDigit() }.let { p -> if (p.isEmpty()) 0 else p.toInt() } }
|
||||||
|
val cParts = cClean.split(".").map { it.filter { char -> char.isDigit() }.let { p -> if (p.isEmpty()) 0 else p.toInt() } }
|
||||||
|
|
||||||
|
val length = Math.max(sParts.size, cParts.size)
|
||||||
|
for (i in 0 until length) {
|
||||||
|
val s = if (i < sParts.size) sParts[i] else 0
|
||||||
|
val c = if (i < cParts.size) cParts[i] else 0
|
||||||
|
if (s > c) return true
|
||||||
|
if (s < c) return false
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
android.util.Log.e("AppUpdateManager", "Version comparison failed: ${e.message}")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showUpdateDialog(activity: Activity, version: String, changelog: String, apkUrl: String) {
|
||||||
|
com.google.android.material.dialog.MaterialAlertDialogBuilder(activity)
|
||||||
|
.setTitle("새로운 업데이트 발견 (v$version)")
|
||||||
|
.setMessage("업데이트 내용:\n$changelog\n\n지금 다운로드하시겠습니까?")
|
||||||
|
.setPositiveButton("다운로드") { _, _ ->
|
||||||
|
downloadAndInstallApk(activity, apkUrl, version)
|
||||||
|
}
|
||||||
|
.setNegativeButton("나중에", null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun downloadAndInstallApk(activity: Activity, apkUrl: String, version: String) {
|
||||||
|
val progressDialog = ProgressDialog(activity).apply {
|
||||||
|
setTitle("업데이트 다운로드 중")
|
||||||
|
setMessage("v$version 다운로드 중...")
|
||||||
|
setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
|
||||||
|
setCancelable(false)
|
||||||
|
max = 100
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread {
|
||||||
|
try {
|
||||||
|
val url = URL(apkUrl)
|
||||||
|
val connection = url.openConnection() as HttpURLConnection
|
||||||
|
connection.connectTimeout = 15000
|
||||||
|
connection.readTimeout = 15000
|
||||||
|
connection.requestMethod = "GET"
|
||||||
|
connection.connect()
|
||||||
|
|
||||||
|
val fileLength = connection.contentLength
|
||||||
|
val inputStream = BufferedInputStream(connection.inputStream)
|
||||||
|
|
||||||
|
val apkFile = File(activity.cacheDir, "update.apk")
|
||||||
|
val outputStream = FileOutputStream(apkFile)
|
||||||
|
|
||||||
|
val buffer = ByteArray(8192)
|
||||||
|
var total: Long = 0
|
||||||
|
var count: Int
|
||||||
|
|
||||||
|
while (inputStream.read(buffer).also { count = it } != -1) {
|
||||||
|
total += count
|
||||||
|
outputStream.write(buffer, 0, count)
|
||||||
|
|
||||||
|
if (fileLength > 0) {
|
||||||
|
val progress = (total * 100 / fileLength).toInt()
|
||||||
|
activity.runOnUiThread {
|
||||||
|
progressDialog.progress = progress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outputStream.flush()
|
||||||
|
outputStream.close()
|
||||||
|
inputStream.close()
|
||||||
|
connection.disconnect()
|
||||||
|
|
||||||
|
activity.runOnUiThread {
|
||||||
|
progressDialog.dismiss()
|
||||||
|
installApk(activity, apkFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
activity.runOnUiThread {
|
||||||
|
progressDialog.dismiss()
|
||||||
|
Toast.makeText(activity, "다운로드 실패: ${e.message}", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun installApk(activity: Activity, apkFile: File) {
|
||||||
|
try {
|
||||||
|
val apkUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
FileProvider.getUriForFile(activity, "${activity.packageName}.provider", apkFile)
|
||||||
|
} else {
|
||||||
|
Uri.fromFile(apkFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||||
|
setDataAndType(apkUri, "application/vnd.android.package-archive")
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
}
|
||||||
|
activity.startActivity(intent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
Toast.makeText(activity, "설치 실패: ${e.message}", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
178
app/src/main/java/com/example/shiftalarm/BackupManager.kt
Normal file
178
app/src/main/java/com/example/shiftalarm/BackupManager.kt
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.io.InputStreamReader
|
||||||
|
import java.io.OutputStreamWriter
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles data backup and restoration (Database + SharedPreferences).
|
||||||
|
* Format: JSON
|
||||||
|
*/
|
||||||
|
object BackupManager {
|
||||||
|
|
||||||
|
suspend fun backupData(context: Context, uri: Uri, dao: ShiftDao) = withContext(Dispatchers.IO) {
|
||||||
|
val overrides = dao.getAllOverrides()
|
||||||
|
val memos = dao.getAllMemos()
|
||||||
|
|
||||||
|
val json = JSONObject()
|
||||||
|
|
||||||
|
// 1. Backup Overrides
|
||||||
|
val overrideArray = JSONArray()
|
||||||
|
overrides.forEach {
|
||||||
|
overrideArray.put(JSONObject().apply {
|
||||||
|
put("date", it.date)
|
||||||
|
put("shift", it.shift)
|
||||||
|
put("team", it.team)
|
||||||
|
put("factory", it.factory)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
json.put("overrides", overrideArray)
|
||||||
|
|
||||||
|
// 1.5 Backup Custom Alarms
|
||||||
|
val customAlarms = dao.getAllCustomAlarms()
|
||||||
|
val customAlarmArray = JSONArray()
|
||||||
|
customAlarms.forEach {
|
||||||
|
customAlarmArray.put(JSONObject().apply {
|
||||||
|
put("time", it.time)
|
||||||
|
put("shiftType", it.shiftType)
|
||||||
|
put("isEnabled", it.isEnabled)
|
||||||
|
put("soundUri", it.soundUri)
|
||||||
|
put("snoozeInterval", it.snoozeInterval)
|
||||||
|
put("snoozeRepeat", it.snoozeRepeat)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
json.put("custom_alarms_v2", customAlarmArray)
|
||||||
|
|
||||||
|
// 2. Backup Memos
|
||||||
|
val memoArray = JSONArray()
|
||||||
|
memos.forEach {
|
||||||
|
memoArray.put(JSONObject().apply {
|
||||||
|
put("date", it.date)
|
||||||
|
put("content", it.content)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
json.put("memos", memoArray)
|
||||||
|
|
||||||
|
// 3. Backup Settings (SharedPreferences)
|
||||||
|
val prefs = context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE)
|
||||||
|
val settings = JSONObject()
|
||||||
|
prefs.all.forEach { (key, value) ->
|
||||||
|
if (value is String) settings.put(key, value)
|
||||||
|
else if (value is Boolean) settings.put(key, value)
|
||||||
|
else if (value is Int) settings.put(key, value)
|
||||||
|
else if (value is Float) settings.put(key, value.toDouble())
|
||||||
|
else if (value is Long) settings.put(key, value)
|
||||||
|
else if (value is Double) settings.put(key, value)
|
||||||
|
}
|
||||||
|
json.put("settings", settings)
|
||||||
|
|
||||||
|
json.put("magic", "SHIFTRING_BACKUP_V3")
|
||||||
|
json.put("timestamp", System.currentTimeMillis())
|
||||||
|
|
||||||
|
val finalString = json.toString()
|
||||||
|
val encodedBytes = android.util.Base64.encode(finalString.toByteArray(), android.util.Base64.DEFAULT)
|
||||||
|
|
||||||
|
context.contentResolver.openOutputStream(uri)?.use { os ->
|
||||||
|
os.write(encodedBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun restoreData(context: Context, uri: Uri, dao: ShiftDao) = withContext(Dispatchers.IO) {
|
||||||
|
val bytes = context.contentResolver.openInputStream(uri)?.use {
|
||||||
|
it.readBytes()
|
||||||
|
} ?: throw Exception("Failed to read file")
|
||||||
|
|
||||||
|
var content = ""
|
||||||
|
try {
|
||||||
|
// Try Base64 first (V3)
|
||||||
|
val decodedBytes = android.util.Base64.decode(bytes, android.util.Base64.DEFAULT)
|
||||||
|
content = String(decodedBytes)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Fallback to plain text (V1/V2)
|
||||||
|
content = String(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
val json = JSONObject(content)
|
||||||
|
|
||||||
|
val magic = json.optString("magic", "")
|
||||||
|
if (magic != "SHIFTRING_BACKUP_V1" && magic != "SHIFTRING_BACKUP_V2" && magic != "SHIFTRING_BACKUP_V3") {
|
||||||
|
throw Exception("올바르지 않은 백업 파일 형식입니다.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Restore Settings FIRST
|
||||||
|
if (json.has("settings")) {
|
||||||
|
val settings = json.getJSONObject("settings")
|
||||||
|
val prefs = context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE).edit()
|
||||||
|
prefs.clear()
|
||||||
|
val keys = settings.keys()
|
||||||
|
while(keys.hasNext()) {
|
||||||
|
val key = keys.next()
|
||||||
|
if (settings.isNull(key)) continue
|
||||||
|
|
||||||
|
val value = settings.get(key)
|
||||||
|
when(value) {
|
||||||
|
is Boolean -> prefs.putBoolean(key, value)
|
||||||
|
is Int -> prefs.putInt(key, value)
|
||||||
|
is String -> prefs.putString(key, value)
|
||||||
|
is Double -> prefs.putFloat(key, value.toFloat())
|
||||||
|
is Long -> prefs.putLong(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prefs.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
val restoredPrefs = context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE)
|
||||||
|
val fallbackFactory = restoredPrefs.getString("selected_factory", "Jeonju") ?: "Jeonju"
|
||||||
|
val fallbackTeam = restoredPrefs.getString("selected_team", "A") ?: "A"
|
||||||
|
|
||||||
|
// 2. Restore Overrides
|
||||||
|
if (json.has("overrides")) {
|
||||||
|
dao.clearOverrides()
|
||||||
|
val arr = json.getJSONArray("overrides")
|
||||||
|
for (i in 0 until arr.length()) {
|
||||||
|
val obj = arr.getJSONObject(i)
|
||||||
|
dao.insertOverride(ShiftOverride(
|
||||||
|
factory = obj.optString("factory", fallbackFactory),
|
||||||
|
team = obj.optString("team", fallbackTeam),
|
||||||
|
date = obj.getString("date"),
|
||||||
|
shift = obj.getString("shift")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.5 Restore Custom Alarms
|
||||||
|
if (json.has("custom_alarms_v2")) {
|
||||||
|
dao.clearCustomAlarms()
|
||||||
|
val arr = json.getJSONArray("custom_alarms_v2")
|
||||||
|
for (i in 0 until arr.length()) {
|
||||||
|
val obj = arr.getJSONObject(i)
|
||||||
|
dao.insertCustomAlarm(CustomAlarm(
|
||||||
|
time = obj.getString("time"),
|
||||||
|
shiftType = obj.getString("shiftType"),
|
||||||
|
isEnabled = obj.optBoolean("isEnabled", true),
|
||||||
|
soundUri = obj.optString("soundUri", null),
|
||||||
|
snoozeInterval = obj.optInt("snoozeInterval", 5),
|
||||||
|
snoozeRepeat = obj.optInt("snoozeRepeat", 3)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Restore Memos
|
||||||
|
if (json.has("memos")) {
|
||||||
|
dao.clearMemos()
|
||||||
|
val arr = json.getJSONArray("memos")
|
||||||
|
for (i in 0 until arr.length()) {
|
||||||
|
val obj = arr.getJSONObject(i)
|
||||||
|
dao.insertMemo(DailyMemo(
|
||||||
|
obj.getString("date"),
|
||||||
|
obj.getString("content")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
app/src/main/java/com/example/shiftalarm/BootReceiver.kt
Normal file
37
app/src/main/java/com/example/shiftalarm/BootReceiver.kt
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.work.ExistingPeriodicWorkPolicy
|
||||||
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
|
import androidx.work.PeriodicWorkRequestBuilder
|
||||||
|
import androidx.work.WorkManager
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class BootReceiver : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent?) {
|
||||||
|
if (intent?.action == Intent.ACTION_BOOT_COMPLETED) {
|
||||||
|
android.util.Log.d("ShiftAlarm", "[부팅] 기기 부팅 감지, 알람 복구 시작")
|
||||||
|
|
||||||
|
// 1) 즉시 1회 실행 → 당일 알람을 바로 복구
|
||||||
|
val immediateWork = OneTimeWorkRequestBuilder<AlarmWorker>().build()
|
||||||
|
WorkManager.getInstance(context).enqueueUniqueWork(
|
||||||
|
"BootAlarmRestore",
|
||||||
|
androidx.work.ExistingWorkPolicy.REPLACE,
|
||||||
|
immediateWork
|
||||||
|
)
|
||||||
|
|
||||||
|
// 2) 24시간 주기 반복 워커 등록
|
||||||
|
val periodicWork = PeriodicWorkRequestBuilder<AlarmWorker>(24, TimeUnit.HOURS)
|
||||||
|
.build()
|
||||||
|
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
|
||||||
|
"DailyShiftCheck",
|
||||||
|
androidx.work.ExistingPeriodicWorkPolicy.KEEP,
|
||||||
|
periodicWork
|
||||||
|
)
|
||||||
|
|
||||||
|
android.util.Log.d("ShiftAlarm", "[부팅] 알람 복구 워커 등록 완료")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
238
app/src/main/java/com/example/shiftalarm/CalendarAdapter.kt
Normal file
238
app/src/main/java/com/example/shiftalarm/CalendarAdapter.kt
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
data class DayShift(
|
||||||
|
val date: LocalDate?,
|
||||||
|
val shift: String?,
|
||||||
|
val hasMemo: Boolean = false,
|
||||||
|
val memoContent: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
class CalendarAdapter(
|
||||||
|
var days: List<DayShift>,
|
||||||
|
private val listener: OnDayClickListener,
|
||||||
|
var showHolidays: Boolean = true
|
||||||
|
) : RecyclerView.Adapter<CalendarAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
interface OnDayClickListener {
|
||||||
|
fun onDayClick(date: LocalDate, currentShift: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||||
|
val root: View = view.findViewById(R.id.dayRoot)
|
||||||
|
val dayNumber: TextView = view.findViewById(R.id.dayNumber)
|
||||||
|
val shiftChar: TextView = view.findViewById(R.id.shiftChar)
|
||||||
|
val holidayNameSmall: TextView = view.findViewById(R.id.holidayNameSmall)
|
||||||
|
val memoIndicator: ImageView = view.findViewById(R.id.memoIndicator)
|
||||||
|
val tvTide: TextView = view.findViewById(R.id.tvTide)
|
||||||
|
val memoContent: TextView = view.findViewById(R.id.memoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_day, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dpToPx(context: Context, dp: Float): Int {
|
||||||
|
return (dp * context.resources.displayMetrics.density).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val item = days[position]
|
||||||
|
val context = holder.itemView.context
|
||||||
|
|
||||||
|
if (item.date == null) {
|
||||||
|
holder.itemView.visibility = View.INVISIBLE
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.itemView.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
// Day Number
|
||||||
|
holder.dayNumber.text = item.date.dayOfMonth.toString()
|
||||||
|
|
||||||
|
// Holiday / Weekend logic
|
||||||
|
val isSunday = item.date.dayOfWeek == java.time.DayOfWeek.SUNDAY
|
||||||
|
val isSaturday = item.date.dayOfWeek == java.time.DayOfWeek.SATURDAY
|
||||||
|
val fullHolidayName = HolidayManager.getHolidayName(item.date)
|
||||||
|
val isToday = item.date == LocalDate.now()
|
||||||
|
|
||||||
|
// Day Number Color
|
||||||
|
if (fullHolidayName != null || isSunday) {
|
||||||
|
holder.dayNumber.setTextColor(Color.parseColor("#FF5252"))
|
||||||
|
} else if (isSaturday) {
|
||||||
|
holder.dayNumber.setTextColor(Color.parseColor("#448AFF"))
|
||||||
|
} else {
|
||||||
|
holder.dayNumber.setTextColor(ContextCompat.getColor(context, R.color.text_primary))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tide Display
|
||||||
|
val prefs = context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE)
|
||||||
|
val showTide = prefs.getBoolean("show_tide", false)
|
||||||
|
val tideLocation = prefs.getString("selected_tide_location", "군산") ?: "군산"
|
||||||
|
|
||||||
|
if (showTide) {
|
||||||
|
val tide = HolidayManager.getTide(item.date, tideLocation)
|
||||||
|
if (tide.isNotEmpty()) {
|
||||||
|
holder.tvTide.visibility = View.VISIBLE
|
||||||
|
holder.tvTide.text = tide
|
||||||
|
} else {
|
||||||
|
holder.tvTide.visibility = View.GONE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
holder.tvTide.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- Shift & Holiday Display Logic ---
|
||||||
|
holder.shiftChar.background = null
|
||||||
|
holder.shiftChar.text = ""
|
||||||
|
holder.holidayNameSmall.visibility = View.GONE
|
||||||
|
holder.shiftChar.textSize = 13f
|
||||||
|
|
||||||
|
// "반월", "반년" (Half-Monthly, Half-Yearly) Special Logic
|
||||||
|
// These are overrides or specific shifts that user sets.
|
||||||
|
// User requested: "월", "년" text. Half-filled background (Red + Transparent).
|
||||||
|
// Check exact string or "startswith" if logic changed?
|
||||||
|
// Logic in adapter `getShift` might return "반월", "반년".
|
||||||
|
|
||||||
|
if (showHolidays && fullHolidayName != null) {
|
||||||
|
// Holiday Mode (Priority): Show full holiday name, no circle
|
||||||
|
holder.shiftChar.text = fullHolidayName
|
||||||
|
holder.shiftChar.setTextColor(Color.parseColor("#FF5252"))
|
||||||
|
holder.shiftChar.textSize = 10f
|
||||||
|
holder.shiftChar.background = null
|
||||||
|
} else if (item.shift != null && item.shift != "비번") {
|
||||||
|
// Shift Mode
|
||||||
|
|
||||||
|
// Handle specific "Half" cases first
|
||||||
|
if (item.shift == "반월" || item.shift == "반년") {
|
||||||
|
holder.shiftChar.text = if (item.shift == "반월") "월" else "년"
|
||||||
|
holder.shiftChar.setTextColor(ContextCompat.getColor(context, R.color.black)) // Black for contrast on Half Red/Transparent
|
||||||
|
holder.shiftChar.textSize = 13f
|
||||||
|
holder.shiftChar.background = ContextCompat.getDrawable(context, R.drawable.bg_shift_half_red)
|
||||||
|
} else {
|
||||||
|
// Standard Logic
|
||||||
|
val shiftAbbreviation = when (item.shift) {
|
||||||
|
"주간" -> "주"
|
||||||
|
"석간" -> "석"
|
||||||
|
"야간" -> "야"
|
||||||
|
"주간 맞교대" -> "주맞"
|
||||||
|
"야간 맞교대" -> "야맞"
|
||||||
|
"휴무", "휴가" -> "휴"
|
||||||
|
"월차" -> "월"
|
||||||
|
"연차" -> "연"
|
||||||
|
"교육" -> "교"
|
||||||
|
else -> item.shift.take(1)
|
||||||
|
}
|
||||||
|
holder.shiftChar.text = shiftAbbreviation
|
||||||
|
holder.shiftChar.textSize = 15f
|
||||||
|
holder.shiftChar.setTypeface(null, android.graphics.Typeface.BOLD)
|
||||||
|
|
||||||
|
val shiftColorRes = when (item.shift) {
|
||||||
|
"주간" -> R.color.shift_lemon
|
||||||
|
"석간" -> R.color.shift_seok
|
||||||
|
"야간" -> R.color.shift_ya
|
||||||
|
"주간 맞교대" -> R.color.shift_jumat
|
||||||
|
"야간 맞교대" -> R.color.shift_yamat
|
||||||
|
"휴무", "휴가", "월차", "연차" -> R.color.shift_red
|
||||||
|
"교육" -> R.color.primary
|
||||||
|
else -> R.color.text_secondary
|
||||||
|
}
|
||||||
|
val shiftColor = ContextCompat.getColor(context, shiftColorRes)
|
||||||
|
|
||||||
|
if (isToday) {
|
||||||
|
// Today: Solid Circle
|
||||||
|
val background = ContextCompat.getDrawable(context, R.drawable.bg_shift_solid_v4) as? android.graphics.drawable.GradientDrawable
|
||||||
|
background?.setColor(shiftColor)
|
||||||
|
holder.shiftChar.background = background
|
||||||
|
holder.shiftChar.backgroundTintList = null
|
||||||
|
|
||||||
|
if (item.shift == "주간" || item.shift == "석간") {
|
||||||
|
holder.shiftChar.setTextColor(ContextCompat.getColor(context, R.color.black))
|
||||||
|
} else {
|
||||||
|
holder.shiftChar.setTextColor(Color.WHITE)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not Today: Stroke Circle
|
||||||
|
val background = ContextCompat.getDrawable(context, R.drawable.bg_shift_stroke_v4) as? android.graphics.drawable.GradientDrawable
|
||||||
|
background?.setStroke(dpToPx(context, 1.5f), shiftColor)
|
||||||
|
background?.setColor(Color.TRANSPARENT)
|
||||||
|
|
||||||
|
holder.shiftChar.background = background
|
||||||
|
holder.shiftChar.backgroundTintList = null
|
||||||
|
|
||||||
|
holder.shiftChar.setTextColor(shiftColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lunar date small display if requested or just default
|
||||||
|
if (!showHolidays && fullHolidayName != null) {
|
||||||
|
holder.holidayNameSmall.visibility = View.VISIBLE
|
||||||
|
holder.holidayNameSmall.text = fullHolidayName
|
||||||
|
} else {
|
||||||
|
// Ensure visibility GONE if not needed (e.g. standard day)
|
||||||
|
holder.holidayNameSmall.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double check: if showHolidays=true (Holiday mode), we handled it at top block.
|
||||||
|
// But if showHolidays=true and NO holiday, we show lunar date?
|
||||||
|
// User asked: "Overlap date and holiday text".
|
||||||
|
// My item_day.xml has holidayNameSmall at bottom now.
|
||||||
|
// If showHolidays=true, CalendarAdapter usually HIDES shiftChar and shows Holiday Name?
|
||||||
|
// Wait, standard logic (lines 84-91 above):
|
||||||
|
// If showHolidays && fullHolidayName != null -> shiftChar shows Name.
|
||||||
|
// If showHolidays && fullHolidayName == null -> shiftChar shows LUNAR DATE? (Old logic had this).
|
||||||
|
|
||||||
|
if (showHolidays && fullHolidayName == null) {
|
||||||
|
// Show Lunar Date in shiftChar instead of empty?
|
||||||
|
// Or shiftChar is empty, show small text?
|
||||||
|
// Previous code:
|
||||||
|
// holder.shiftChar.text = ""
|
||||||
|
// holder.holidayNameSmall.visibility = View.VISIBLE
|
||||||
|
// holder.holidayNameSmall.text = HolidayManager.getLunarDateString(item.date)
|
||||||
|
|
||||||
|
holder.shiftChar.text = HolidayManager.getLunarDateString(item.date)
|
||||||
|
holder.shiftChar.textSize = 10f
|
||||||
|
holder.shiftChar.setTextColor(ContextCompat.getColor(context, R.color.text_tertiary))
|
||||||
|
holder.shiftChar.background = null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Memo Indicator
|
||||||
|
holder.memoIndicator.visibility = View.GONE // Hide indicator, showing text instead
|
||||||
|
if (item.hasMemo && !item.memoContent.isNullOrEmpty()) {
|
||||||
|
holder.memoContent.visibility = View.VISIBLE
|
||||||
|
holder.memoContent.text = item.memoContent
|
||||||
|
} else {
|
||||||
|
holder.memoContent.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
// Today Border or Highlight
|
||||||
|
if (isToday) {
|
||||||
|
holder.root.setBackgroundResource(R.drawable.bg_grid_cell_today_v4)
|
||||||
|
} else {
|
||||||
|
holder.root.setBackgroundResource(R.drawable.bg_grid_cell_v4)
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.itemView.setOnClickListener {
|
||||||
|
if (item.date != null && item.shift != null) {
|
||||||
|
listener.onDayClick(item.date, item.shift)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = days.size
|
||||||
|
}
|
||||||
30
app/src/main/java/com/example/shiftalarm/Entities.kt
Normal file
30
app/src/main/java/com/example/shiftalarm/Entities.kt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
|
||||||
|
@Entity(tableName = "shift_overrides", primaryKeys = ["factory", "team", "date"])
|
||||||
|
data class ShiftOverride(
|
||||||
|
val factory: String,
|
||||||
|
val team: String,
|
||||||
|
val date: String, // YYYY-MM-DD
|
||||||
|
val shift: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Entity(tableName = "daily_memos")
|
||||||
|
data class DailyMemo(
|
||||||
|
@PrimaryKey
|
||||||
|
val date: String, // YYYY-MM-DD
|
||||||
|
val content: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Entity(tableName = "custom_alarms")
|
||||||
|
data class CustomAlarm(
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
val id: Int = 0,
|
||||||
|
val time: String, // HH:MM
|
||||||
|
val shiftType: String, // 주간, 석간, 야간 ... 기타
|
||||||
|
val isEnabled: Boolean = true,
|
||||||
|
val soundUri: String? = null,
|
||||||
|
val snoozeInterval: Int = 5,
|
||||||
|
val snoozeRepeat: Int = 3
|
||||||
|
)
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.example.shiftalarm.databinding.FragmentSettingsAdditionalBinding
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
class FragmentSettingsAdditional : Fragment() {
|
||||||
|
private var _binding: FragmentSettingsAdditionalBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
private val PREFS_NAME = "ShiftAlarmPrefs"
|
||||||
|
private var isUserInteraction = false
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
|
_binding = FragmentSettingsAdditionalBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
loadSettings()
|
||||||
|
setupListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val backupLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("application/json")) { uri ->
|
||||||
|
uri?.let {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
try {
|
||||||
|
val db = AppDatabase.getDatabase(requireContext())
|
||||||
|
BackupManager.backupData(requireContext(), it, db.shiftDao())
|
||||||
|
Toast.makeText(requireContext(), "백업이 완료되었습니다.", Toast.LENGTH_SHORT).show()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Toast.makeText(requireContext(), "백업 실패: ${e.message}", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val restoreLauncher = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
|
||||||
|
uri?.let {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
try {
|
||||||
|
val db = AppDatabase.getDatabase(requireContext())
|
||||||
|
BackupManager.restoreData(requireContext(), it, db.shiftDao())
|
||||||
|
androidx.appcompat.app.AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle("복구 완료")
|
||||||
|
.setMessage("데이터 복구가 완료되었습니다. 변경사항을 적용하기 위해 앱을 재시작해야 합니다.")
|
||||||
|
.setPositiveButton("앱 재시작") { _, _ ->
|
||||||
|
val intent = requireContext().packageManager.getLaunchIntentForPackage(requireContext().packageName)
|
||||||
|
intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
|
intent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
startActivity(intent)
|
||||||
|
requireActivity().finish()
|
||||||
|
}
|
||||||
|
.setCancelable(false)
|
||||||
|
.show()
|
||||||
|
loadSettings()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Toast.makeText(requireContext(), "복구 실패: ${e.message}", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadSettings() {
|
||||||
|
val prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
// Theme Spinner
|
||||||
|
val themeOptions = resources.getStringArray(R.array.theme_array)
|
||||||
|
val themeAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, themeOptions)
|
||||||
|
themeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
binding.themeSpinner.adapter = themeAdapter
|
||||||
|
|
||||||
|
val themeMode = prefs.getInt("theme_mode", androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||||
|
val themeIndex = when(themeMode) {
|
||||||
|
androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO -> 1
|
||||||
|
androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES -> 2
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
binding.themeSpinner.setSelection(themeIndex)
|
||||||
|
|
||||||
|
// Tide Switch
|
||||||
|
binding.switchTide.isChecked = prefs.getBoolean("show_tide", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupListeners() {
|
||||||
|
val prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
binding.themeSpinner.setOnTouchListener { _, _ ->
|
||||||
|
isUserInteraction = true
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.themeSpinner.onItemSelectedListener = object : android.widget.AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onItemSelected(parent: android.widget.AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||||
|
if (!isUserInteraction) return
|
||||||
|
|
||||||
|
val themeMode = when(position) {
|
||||||
|
1 -> androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
|
||||||
|
2 -> androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
|
||||||
|
else -> androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save and Apply
|
||||||
|
val currentMode = prefs.getInt("theme_mode", -1)
|
||||||
|
if (currentMode != themeMode) {
|
||||||
|
prefs.edit().putInt("theme_mode", themeMode).apply()
|
||||||
|
|
||||||
|
// Critical Guard: Only apply if it actually changes the global state
|
||||||
|
if (androidx.appcompat.app.AppCompatDelegate.getDefaultNightMode() != themeMode) {
|
||||||
|
androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode(themeMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun onNothingSelected(parent: android.widget.AdapterView<*>?) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tide Switch Listener (Fixed: properly saving now)
|
||||||
|
binding.switchTide.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
prefs.edit().putBoolean("show_tide", isChecked).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backup/Restore buttons
|
||||||
|
binding.btnBackup.setOnClickListener {
|
||||||
|
val dateStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmm"))
|
||||||
|
backupLauncher.launch("shiftring_backup_$dateStr.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnRestore.setOnClickListener {
|
||||||
|
restoreLauncher.launch(arrayOf("application/json"))
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnManual.setOnClickListener {
|
||||||
|
startActivity(Intent(requireContext(), ManualActivity::class.java))
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnNotice.setOnClickListener {
|
||||||
|
startActivity(Intent(requireContext(), NoticeActivity::class.java))
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnShareApp.setOnClickListener {
|
||||||
|
lifecycleScope.launch(kotlinx.coroutines.Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val context = requireContext()
|
||||||
|
val pm = context.packageManager
|
||||||
|
val appInfo = pm.getApplicationInfo(context.packageName, 0)
|
||||||
|
val apkFile = java.io.File(appInfo.sourceDir)
|
||||||
|
|
||||||
|
val cachePath = java.io.File(context.cacheDir, "apks")
|
||||||
|
cachePath.mkdirs()
|
||||||
|
val newFile = java.io.File(cachePath, "ShiftRing_Installer.apk")
|
||||||
|
|
||||||
|
apkFile.copyTo(newFile, overwrite = true)
|
||||||
|
|
||||||
|
val contentUri = androidx.core.content.FileProvider.getUriForFile(
|
||||||
|
context,
|
||||||
|
"${context.packageName}.provider",
|
||||||
|
newFile
|
||||||
|
)
|
||||||
|
|
||||||
|
val shareIntent = Intent(Intent.ACTION_SEND)
|
||||||
|
shareIntent.type = "application/vnd.android.package-archive"
|
||||||
|
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri)
|
||||||
|
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
|
||||||
|
kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.Main) {
|
||||||
|
startActivity(Intent.createChooser(shareIntent, "앱 설치 파일 공유하기"))
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.Main) {
|
||||||
|
Toast.makeText(requireContext(), "공유 실패: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnResetOverrides.setOnClickListener {
|
||||||
|
androidx.appcompat.app.AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle("데이터 초기화")
|
||||||
|
.setMessage("달력에서 개별적으로 바꾼 모든 근무와 알람 설정이 삭제됩니다. 계속하시겠습니까?")
|
||||||
|
.setPositiveButton("초기화") { _, _ ->
|
||||||
|
lifecycleScope.launch {
|
||||||
|
try {
|
||||||
|
val db = AppDatabase.getDatabase(requireContext())
|
||||||
|
val dao = db.shiftDao()
|
||||||
|
dao.clearOverrides()
|
||||||
|
|
||||||
|
// Immediately re-sync all alarms
|
||||||
|
syncAllAlarms(requireContext())
|
||||||
|
|
||||||
|
Toast.makeText(requireContext(), "모든 개별 설정이 삭제되고 알람이 재설정되었습니다.", Toast.LENGTH_SHORT).show()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Toast.makeText(requireContext(), "초기화 실패: ${e.message}", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setNegativeButton("취소", null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,574 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.TimePicker
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import com.google.android.material.materialswitch.MaterialSwitch
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.example.shiftalarm.databinding.FragmentSettingsAlarmBinding
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
class FragmentSettingsAlarm : Fragment(), SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
|
private var _binding: FragmentSettingsAlarmBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
private val PREFS_NAME = "ShiftAlarmPrefs"
|
||||||
|
private lateinit var repository: ShiftRepository
|
||||||
|
private var customAlarms: MutableList<CustomAlarm> = mutableListOf()
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentSettingsAlarmBinding.inflate(inflater, container, false)
|
||||||
|
repository = ShiftRepository(requireContext())
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
val prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
prefs.registerOnSharedPreferenceChangeListener(this)
|
||||||
|
|
||||||
|
setupListeners()
|
||||||
|
loadSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
refreshAlarmList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
val prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
prefs.unregisterOnSharedPreferenceChangeListener(this)
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||||
|
if (key == "master_alarm_enabled") {
|
||||||
|
sharedPreferences?.let {
|
||||||
|
updateMasterToggleUI(ShiftAlarmDefaults.isMasterAlarmEnabled(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadSettings() {
|
||||||
|
val prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
// Master Toggle Button State
|
||||||
|
updateMasterToggleUI(ShiftAlarmDefaults.isMasterAlarmEnabled(prefs))
|
||||||
|
|
||||||
|
// Migrate and Refresh
|
||||||
|
lifecycleScope.launch {
|
||||||
|
migrateFromPrefsIfNecessary()
|
||||||
|
refreshAlarmList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun migrateFromPrefsIfNecessary() {
|
||||||
|
val prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
val legacyJson = prefs.getString("custom_alarms", null)
|
||||||
|
if (legacyJson != null) {
|
||||||
|
try {
|
||||||
|
val arr = JSONArray(legacyJson)
|
||||||
|
for (i in 0 until arr.length()) {
|
||||||
|
val obj = arr.getJSONObject(i)
|
||||||
|
val alarm = CustomAlarm(
|
||||||
|
time = obj.getString("time"),
|
||||||
|
shiftType = obj.getString("shiftType"),
|
||||||
|
isEnabled = obj.optBoolean("enabled", true),
|
||||||
|
soundUri = obj.optString("soundUri", null),
|
||||||
|
snoozeInterval = obj.optInt("snoozeInterval", 5),
|
||||||
|
snoozeRepeat = obj.optInt("snoozeRepeat", 3)
|
||||||
|
)
|
||||||
|
repository.addCustomAlarm(alarm)
|
||||||
|
}
|
||||||
|
// Clear legacy data
|
||||||
|
prefs.edit().remove("custom_alarms").apply()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshAlarmList() {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
customAlarms = repository.getAllCustomAlarms().toMutableList()
|
||||||
|
refreshUI()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val soundTitleCache = mutableMapOf<String?, String>()
|
||||||
|
|
||||||
|
private fun updateMasterToggleUI(isEnabled: Boolean) {
|
||||||
|
if (isEnabled) {
|
||||||
|
binding.tvMasterStatus.text = "전체 알람 켜짐"
|
||||||
|
binding.tvMasterStatus.setTextColor(ContextCompat.getColor(requireContext(), R.color.primary))
|
||||||
|
binding.tvMasterStatus.backgroundTintList = android.content.res.ColorStateList.valueOf(Color.parseColor("#E3F2FD"))
|
||||||
|
} else {
|
||||||
|
binding.tvMasterStatus.text = "전체 알람 꺼짐"
|
||||||
|
binding.tvMasterStatus.setTextColor(ContextCompat.getColor(requireContext(), R.color.shift_red))
|
||||||
|
binding.tvMasterStatus.backgroundTintList = android.content.res.ColorStateList.valueOf(Color.parseColor("#FFEBEE"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupListeners() {
|
||||||
|
val prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
binding.tvMasterStatus.setOnClickListener {
|
||||||
|
val isEnabled = !ShiftAlarmDefaults.isMasterAlarmEnabled(prefs)
|
||||||
|
prefs.edit().putBoolean("master_alarm_enabled", isEnabled).apply()
|
||||||
|
updateMasterToggleUI(isEnabled)
|
||||||
|
|
||||||
|
val message = if (isEnabled) "전체 알람이 켜졌습니다." else "전체 알람이 꺼졌습니다."
|
||||||
|
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
|
||||||
|
|
||||||
|
// Resync immediately
|
||||||
|
lifecycleScope.launch { syncAllAlarms(requireContext()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnAddCustomAlarm.setOnClickListener {
|
||||||
|
showEditDialog(
|
||||||
|
title = "새 알람 추가",
|
||||||
|
currentTime = "07:00",
|
||||||
|
shiftType = "주간",
|
||||||
|
existingAlarm = null,
|
||||||
|
isNew = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnTestAlarm.setOnClickListener {
|
||||||
|
scheduleTestAlarm(requireContext())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshUI() {
|
||||||
|
val container = binding.alarmListContainer
|
||||||
|
container.removeAllViews()
|
||||||
|
|
||||||
|
for (alarm in customAlarms) {
|
||||||
|
val item = createAlarmRow(alarm.shiftType, alarm.time, alarm.isEnabled, isCustom = true, snoozeMin = alarm.snoozeInterval, snoozeRepeat = alarm.snoozeRepeat, soundUri = alarm.soundUri) { isToggle, isLongOrShort ->
|
||||||
|
if (isToggle) {
|
||||||
|
// AlarmSyncManager를 사용하여 토글 동기화
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val enable = !alarm.isEnabled
|
||||||
|
val result = AlarmSyncManager.toggleAlarm(requireContext(), alarm, enable)
|
||||||
|
if (result.isSuccess) {
|
||||||
|
Log.d("ShiftAlarm", "알람 토글 동기화 성공: ID=${alarm.id}, enabled=$enable")
|
||||||
|
} else {
|
||||||
|
Log.e("ShiftAlarm", "알람 토글 동기화 실패", result.exceptionOrNull())
|
||||||
|
Toast.makeText(requireContext(), "알람 상태 변경 중 오류가 발생했습니다.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
refreshAlarmList()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showEditDialog("사용자 알람", alarm.time, alarm.shiftType, existingAlarm = alarm, isNew = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
container.addView(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createAlarmRow(
|
||||||
|
shiftName: String,
|
||||||
|
time: String,
|
||||||
|
isEnabled: Boolean,
|
||||||
|
isCustom: Boolean,
|
||||||
|
snoozeMin: Int,
|
||||||
|
snoozeRepeat: Int,
|
||||||
|
soundUri: String?,
|
||||||
|
onAction: (isToggle: Boolean, isLongClick: Boolean) -> Unit
|
||||||
|
): View {
|
||||||
|
val view = layoutInflater.inflate(R.layout.item_alarm_unified, binding.alarmListContainer, false)
|
||||||
|
view.isFocusable = true
|
||||||
|
|
||||||
|
val shiftIndicator = view.findViewById<TextView>(R.id.shiftIndicator)
|
||||||
|
val tvTime = view.findViewById<TextView>(R.id.tvTime)
|
||||||
|
val tvAmPm = view.findViewById<TextView>(R.id.tvAmPm)
|
||||||
|
val tvSummary = view.findViewById<TextView>(R.id.tvSummary)
|
||||||
|
val alarmSwitch = view.findViewById<MaterialSwitch>(R.id.alarmSwitch)
|
||||||
|
val layoutAlarmSwitch = view.findViewById<View>(R.id.layoutAlarmSwitch)
|
||||||
|
|
||||||
|
val shortName = when(shiftName) {
|
||||||
|
"주간" -> "주"
|
||||||
|
"석간" -> "석"
|
||||||
|
"야간" -> "야"
|
||||||
|
"주간 맞교대" -> "주맞"
|
||||||
|
"야간 맞교대" -> "야맞"
|
||||||
|
"기타" -> "기타"
|
||||||
|
else -> shiftName.take(1)
|
||||||
|
}
|
||||||
|
shiftIndicator.text = shortName
|
||||||
|
|
||||||
|
val colorRes = when(shiftName) {
|
||||||
|
"주간" -> R.color.shift_lemon
|
||||||
|
"석간" -> R.color.shift_seok
|
||||||
|
"야간" -> R.color.shift_ya
|
||||||
|
"주간 맞교대" -> R.color.shift_jumat
|
||||||
|
"야간 맞교대" -> R.color.shift_yamat
|
||||||
|
else -> R.color.shift_gray
|
||||||
|
}
|
||||||
|
|
||||||
|
val context = requireContext()
|
||||||
|
val color = ContextCompat.getColor(context, colorRes)
|
||||||
|
val drawable = ContextCompat.getDrawable(context, R.drawable.bg_shift_stroke_v4) as android.graphics.drawable.GradientDrawable
|
||||||
|
drawable.mutate()
|
||||||
|
drawable.setStroke(dpToPx(2.5f), color)
|
||||||
|
shiftIndicator.background = drawable
|
||||||
|
shiftIndicator.setTextColor(color)
|
||||||
|
|
||||||
|
try {
|
||||||
|
val parts = time.split(":")
|
||||||
|
val h24 = parts[0].toInt()
|
||||||
|
val m = parts[1].toInt()
|
||||||
|
val h12 = if (h24 % 12 == 0) 12 else h24 % 12
|
||||||
|
tvTime.text = String.format("%02d:%02d", h12, m)
|
||||||
|
tvAmPm.text = if (h24 < 12) "오전" else "오후"
|
||||||
|
|
||||||
|
if (!isEnabled) {
|
||||||
|
tvTime.setTextColor(ContextCompat.getColor(context, R.color.text_tertiary))
|
||||||
|
tvAmPm.setTextColor(ContextCompat.getColor(context, R.color.text_tertiary))
|
||||||
|
tvSummary.setTextColor(ContextCompat.getColor(context, R.color.text_tertiary))
|
||||||
|
shiftIndicator.alpha = 0.4f
|
||||||
|
} else {
|
||||||
|
tvTime.setTextColor(ContextCompat.getColor(context, R.color.text_primary))
|
||||||
|
tvAmPm.setTextColor(ContextCompat.getColor(context, R.color.text_secondary))
|
||||||
|
tvSummary.setTextColor(ContextCompat.getColor(context, R.color.primary))
|
||||||
|
shiftIndicator.alpha = 1.0f
|
||||||
|
}
|
||||||
|
} catch (e: Exception) { tvTime.text = time }
|
||||||
|
|
||||||
|
val tvSoundNameView = view.findViewById<TextView>(R.id.tvSoundName)
|
||||||
|
val soundName = getSoundTitle(context, soundUri)
|
||||||
|
tvSummary.text = "${snoozeMin}분 간격, ${if(snoozeRepeat == 99) "계속" else snoozeRepeat.toString() + "회"}"
|
||||||
|
tvSoundNameView.text = soundName
|
||||||
|
|
||||||
|
val rowContents = view.findViewById<View>(R.id.rowContents)
|
||||||
|
rowContents.setOnClickListener { onAction(false, false) }
|
||||||
|
rowContents.setOnLongClickListener { onAction(false, true); true }
|
||||||
|
|
||||||
|
alarmSwitch.isChecked = isEnabled
|
||||||
|
layoutAlarmSwitch.setOnClickListener {
|
||||||
|
// onAction will handle the data update and re-sync
|
||||||
|
onAction(true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
private var currentDialogSoundUri: String? = null
|
||||||
|
private var tvSoundNameReference: android.widget.TextView? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 새 알람 추가 시 기본음으로 시스템 알람음 설정
|
||||||
|
* 무음 문제 해결을 위해 반드시 시스템 기본 알람음을 반환
|
||||||
|
*/
|
||||||
|
private fun getDefaultAlarmUri(context: Context): String {
|
||||||
|
// 1. 시스템 기본 알람음 (가장 우선)
|
||||||
|
val defaultUri = android.provider.Settings.System.DEFAULT_ALARM_ALERT_URI
|
||||||
|
if (defaultUri != null) {
|
||||||
|
Log.d("ShiftAlarm", "시스템 기본 알람음 URI: $defaultUri")
|
||||||
|
return defaultUri.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. RingtoneManager에서 알람 타입 기본값 가져오기
|
||||||
|
val fallbackUri = android.media.RingtoneManager.getDefaultUri(android.media.RingtoneManager.TYPE_ALARM)
|
||||||
|
if (fallbackUri != null) {
|
||||||
|
Log.d("ShiftAlarm", "Fallback 알람음 URI: $fallbackUri")
|
||||||
|
return fallbackUri.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 마지막 fallback: 알림음이라도 사용
|
||||||
|
val notificationUri = android.media.RingtoneManager.getDefaultUri(android.media.RingtoneManager.TYPE_NOTIFICATION)
|
||||||
|
if (notificationUri != null) {
|
||||||
|
Log.w("ShiftAlarm", "알람음 없음, 알림음 사용: $notificationUri")
|
||||||
|
return notificationUri.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 최후의 수단: 벨소리
|
||||||
|
val ringtoneUri = android.media.RingtoneManager.getDefaultUri(android.media.RingtoneManager.TYPE_RINGTONE)
|
||||||
|
if (ringtoneUri != null) {
|
||||||
|
Log.w("ShiftAlarm", "알림음 없음, 벨소리 사용: $ringtoneUri")
|
||||||
|
return ringtoneUri.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 이 경우는 거의 없지만, 안전장치
|
||||||
|
Log.e("ShiftAlarm", "어떤 기본 소리도 찾을 수 없음")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showEditDialog(
|
||||||
|
title: String, currentTime: String, shiftType: String, existingAlarm: CustomAlarm?, isNew: Boolean
|
||||||
|
) {
|
||||||
|
val dialogView = layoutInflater.inflate(R.layout.dialog_alarm_edit_spinner, null)
|
||||||
|
val dialog = AlertDialog.Builder(requireContext(), android.R.style.Theme_DeviceDefault_Light_NoActionBar_Fullscreen).setView(dialogView).create()
|
||||||
|
dialog.window?.setBackgroundDrawableResource(android.R.color.transparent)
|
||||||
|
dialog.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||||
|
|
||||||
|
val tvTitle = dialogView.findViewById<TextView>(R.id.dialogTitle)
|
||||||
|
val timePicker = dialogView.findViewById<TimePicker>(R.id.timePicker)
|
||||||
|
val tvSoundName = dialogView.findViewById<TextView>(R.id.tvSoundName)
|
||||||
|
tvSoundNameReference = tvSoundName
|
||||||
|
|
||||||
|
val btnSelectSound = dialogView.findViewById<View>(R.id.btnSelectSound)
|
||||||
|
val btnDelete = dialogView.findViewById<Button>(R.id.btnDelete)
|
||||||
|
val btnCancel = dialogView.findViewById<View>(R.id.btnCancel)
|
||||||
|
val btnSave = dialogView.findViewById<View>(R.id.btnSave)
|
||||||
|
|
||||||
|
// Initialize Values
|
||||||
|
var selectedSnooze = existingAlarm?.snoozeInterval ?: 5
|
||||||
|
var selectedRepeat = existingAlarm?.snoozeRepeat ?: 3
|
||||||
|
|
||||||
|
// 새 알람 생성 시 기본적으로 시스템 알람음 설정 (무음 문제 해결)
|
||||||
|
// 기존 알람 수정 시에도 soundUri가 비어있거나 null이면 기본값으로 설정
|
||||||
|
val existingUri = existingAlarm?.soundUri
|
||||||
|
val isExistingUriEmpty = existingUri.isNullOrEmpty() || existingUri == "null"
|
||||||
|
|
||||||
|
currentDialogSoundUri = if (isNew || isExistingUriEmpty) {
|
||||||
|
// 새 알람 또는 기존 알람의 소리가 설정되지 않은 경우: 반드시 기본 알람음으로 설정
|
||||||
|
val defaultUri = getDefaultAlarmUri(requireContext())
|
||||||
|
Log.d("ShiftAlarm", "기본 알람음 설정: $defaultUri (isNew=$isNew, isExistingUriEmpty=$isExistingUriEmpty)")
|
||||||
|
defaultUri
|
||||||
|
} else {
|
||||||
|
// 기존 알람 수정: 기존 값 유지
|
||||||
|
existingUri
|
||||||
|
}
|
||||||
|
|
||||||
|
// soundUri가 비어있는 경우 최종 안전장치
|
||||||
|
if (currentDialogSoundUri.isNullOrEmpty()) {
|
||||||
|
currentDialogSoundUri = getDefaultAlarmUri(requireContext())
|
||||||
|
Log.w("ShiftAlarm", "soundUri가 비어있어 기본값으로 재설정: $currentDialogSoundUri")
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("ShiftAlarm", "알람 ${if (isNew) "생성" else "수정"} - 최종 soundUri: $currentDialogSoundUri")
|
||||||
|
|
||||||
|
fun updateSoundName(uriStr: String?) {
|
||||||
|
if (uriStr.isNullOrEmpty() || uriStr == "null") {
|
||||||
|
tvSoundName.text = "기본 알람음"
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
val uri = android.net.Uri.parse(uriStr)
|
||||||
|
val ringtone = android.media.RingtoneManager.getRingtone(requireContext(), uri)
|
||||||
|
val title = ringtone?.getTitle(requireContext()) ?: "알람음"
|
||||||
|
tvSoundName.text = title
|
||||||
|
} catch (e: Exception) {
|
||||||
|
tvSoundName.text = "알람음"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateSoundName(currentDialogSoundUri)
|
||||||
|
|
||||||
|
// Snooze Interval Buttons
|
||||||
|
val snoozeButtons = listOf(
|
||||||
|
dialogView.findViewById<TextView>(R.id.snooze5),
|
||||||
|
dialogView.findViewById<TextView>(R.id.snooze10),
|
||||||
|
dialogView.findViewById<TextView>(R.id.snooze15),
|
||||||
|
dialogView.findViewById<TextView>(R.id.snooze30)
|
||||||
|
)
|
||||||
|
val snoozeValues = listOf(5, 10, 15, 30)
|
||||||
|
fun updateSnoozeUI() {
|
||||||
|
snoozeButtons.forEachIndexed { i, btn ->
|
||||||
|
val isSelected = snoozeValues[i] == selectedSnooze
|
||||||
|
btn.setBackgroundResource(if (isSelected) R.drawable.bg_pill_rect_selected else R.drawable.bg_pill_rect_unselected)
|
||||||
|
btn.setTextColor(if (isSelected) ContextCompat.getColor(requireContext(), R.color.white) else ContextCompat.getColor(requireContext(), R.color.text_secondary))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateSnoozeUI()
|
||||||
|
snoozeButtons.forEachIndexed { i, btn -> btn.setOnClickListener { selectedSnooze = snoozeValues[i]; updateSnoozeUI() } }
|
||||||
|
|
||||||
|
// Repeat Count Buttons
|
||||||
|
val repeatButtons = listOf(
|
||||||
|
dialogView.findViewById<TextView>(R.id.repeat3),
|
||||||
|
dialogView.findViewById<TextView>(R.id.repeat5),
|
||||||
|
dialogView.findViewById<TextView>(R.id.repeatForever)
|
||||||
|
)
|
||||||
|
val repeatValues = listOf(3, 5, 99)
|
||||||
|
fun updateRepeatUI() {
|
||||||
|
repeatButtons.forEachIndexed { i, btn ->
|
||||||
|
val isSelected = repeatValues[i] == selectedRepeat
|
||||||
|
btn.setBackgroundResource(if (isSelected) R.drawable.bg_pill_rect_selected else R.drawable.bg_pill_rect_unselected)
|
||||||
|
btn.setTextColor(if (isSelected) ContextCompat.getColor(requireContext(), R.color.white) else ContextCompat.getColor(requireContext(), R.color.text_secondary))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateRepeatUI()
|
||||||
|
repeatButtons.forEachIndexed { i, btn -> btn.setOnClickListener { selectedRepeat = repeatValues[i]; updateRepeatUI() } }
|
||||||
|
|
||||||
|
val cardShift = dialogView.findViewById<View>(R.id.cardShiftSelector)
|
||||||
|
var currentShift = shiftType
|
||||||
|
cardShift.visibility = View.VISIBLE
|
||||||
|
val shiftBtns = listOf(
|
||||||
|
dialogView.findViewById<TextView>(R.id.btnShiftJu),
|
||||||
|
dialogView.findViewById<TextView>(R.id.btnShiftSeok),
|
||||||
|
dialogView.findViewById<TextView>(R.id.btnShiftYa),
|
||||||
|
dialogView.findViewById<TextView>(R.id.btnShiftYaMat),
|
||||||
|
dialogView.findViewById<TextView>(R.id.btnShiftEtc)
|
||||||
|
)
|
||||||
|
val shiftTypes = listOf("주간", "석간", "야간", "야간 맞교대", "기타")
|
||||||
|
fun updateShiftUI() {
|
||||||
|
shiftBtns.forEachIndexed { i, btn ->
|
||||||
|
val isSelected = shiftTypes[i] == currentShift
|
||||||
|
val colorRes = when(shiftTypes[i]) {
|
||||||
|
"주간" -> R.color.shift_lemon; "석간" -> R.color.shift_seok; "야간" -> R.color.shift_ya
|
||||||
|
"야간 맞교대" -> R.color.shift_yamat; else -> R.color.shift_gray
|
||||||
|
}
|
||||||
|
val color = ContextCompat.getColor(requireContext(), colorRes)
|
||||||
|
if (isSelected) {
|
||||||
|
val d = ContextCompat.getDrawable(requireContext(), R.drawable.bg_shift_circle_v4) as android.graphics.drawable.GradientDrawable
|
||||||
|
d.mutate(); d.setColor(color); btn.background = d
|
||||||
|
btn.setTextColor(if (shiftTypes[i] == "야간") ContextCompat.getColor(requireContext(), R.color.white) else ContextCompat.getColor(requireContext(), R.color.black))
|
||||||
|
} else {
|
||||||
|
val d = ContextCompat.getDrawable(requireContext(), R.drawable.bg_shift_stroke_v4) as android.graphics.drawable.GradientDrawable
|
||||||
|
d.mutate(); d.setStroke(dpToPx(1.5f), color); btn.background = d; btn.setTextColor(color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateShiftUI()
|
||||||
|
shiftBtns.forEachIndexed { i, btn -> btn.setOnClickListener { currentShift = shiftTypes[i]; updateShiftUI() } }
|
||||||
|
|
||||||
|
tvTitle.text = if (isNew) "새 알람 추가" else "$shiftType 알람 수정"
|
||||||
|
timePicker.setIs24HourView(false)
|
||||||
|
val parts = currentTime.split(":")
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= 23) {
|
||||||
|
timePicker.hour = parts[0].toInt(); timePicker.minute = parts[1].toInt()
|
||||||
|
} else {
|
||||||
|
timePicker.currentHour = parts[0].toInt(); timePicker.currentMinute = parts[1].toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
btnSelectSound.setOnClickListener {
|
||||||
|
val intent = Intent(android.media.RingtoneManager.ACTION_RINGTONE_PICKER).apply {
|
||||||
|
putExtra(android.media.RingtoneManager.EXTRA_RINGTONE_TYPE, android.media.RingtoneManager.TYPE_ALARM)
|
||||||
|
putExtra(android.media.RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, if (currentDialogSoundUri != null) android.net.Uri.parse(currentDialogSoundUri) else null as android.net.Uri?)
|
||||||
|
}
|
||||||
|
startActivityForResult(intent, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isNew) {
|
||||||
|
btnDelete.visibility = View.VISIBLE
|
||||||
|
btnDelete.setOnClickListener {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
existingAlarm?.let {
|
||||||
|
// AlarmSyncManager를 사용하여 동기화된 삭제 수행
|
||||||
|
// DB 삭제 전 AlarmManager 취소가 보장됨
|
||||||
|
val result = AlarmSyncManager.deleteAlarm(requireContext(), it)
|
||||||
|
if (result.isSuccess) {
|
||||||
|
Log.d("ShiftAlarm", "알람 삭제 동기화 성공: ID=${it.id}")
|
||||||
|
} else {
|
||||||
|
Log.e("ShiftAlarm", "알람 삭제 동기화 실패", result.exceptionOrNull())
|
||||||
|
Toast.makeText(requireContext(), "알람 삭제 중 오류가 발생했습니다.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
refreshAlarmList()
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
btnCancel.setOnClickListener { dialog.dismiss() }
|
||||||
|
btnSave.setOnClickListener {
|
||||||
|
val h = if (android.os.Build.VERSION.SDK_INT >= 23) timePicker.hour else timePicker.currentHour
|
||||||
|
val m = if (android.os.Build.VERSION.SDK_INT >= 23) timePicker.minute else timePicker.currentMinute
|
||||||
|
val time = String.format("%02d:%02d", h, m)
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
if (isNew) {
|
||||||
|
val newAlarm = CustomAlarm(
|
||||||
|
time = time,
|
||||||
|
shiftType = currentShift,
|
||||||
|
isEnabled = true,
|
||||||
|
soundUri = currentDialogSoundUri,
|
||||||
|
snoozeInterval = selectedSnooze,
|
||||||
|
snoozeRepeat = selectedRepeat
|
||||||
|
)
|
||||||
|
// AlarmSyncManager를 사용하여 동기화된 추가 수행
|
||||||
|
val result = AlarmSyncManager.addAlarm(requireContext(), newAlarm)
|
||||||
|
if (result.isSuccess) {
|
||||||
|
Log.d("ShiftAlarm", "새 알람 추가 동기화 성공")
|
||||||
|
Toast.makeText(requireContext(), "알람이 추가되었습니다.", Toast.LENGTH_SHORT).show()
|
||||||
|
} else {
|
||||||
|
Log.e("ShiftAlarm", "새 알람 추가 동기화 실패", result.exceptionOrNull())
|
||||||
|
Toast.makeText(requireContext(), "알람 추가 중 오류가 발생했습니다.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val updated = existingAlarm!!.copy(
|
||||||
|
time = time,
|
||||||
|
shiftType = currentShift,
|
||||||
|
soundUri = currentDialogSoundUri,
|
||||||
|
snoozeInterval = selectedSnooze,
|
||||||
|
snoozeRepeat = selectedRepeat
|
||||||
|
)
|
||||||
|
// AlarmSyncManager를 사용하여 동기화된 수정 수행
|
||||||
|
val result = AlarmSyncManager.updateAlarm(requireContext(), updated)
|
||||||
|
if (result.isSuccess) {
|
||||||
|
Log.d("ShiftAlarm", "알람 수정 동기화 성공: ID=${updated.id}")
|
||||||
|
Toast.makeText(requireContext(), "알람이 수정되었습니다.", Toast.LENGTH_SHORT).show()
|
||||||
|
} else {
|
||||||
|
Log.e("ShiftAlarm", "알람 수정 동기화 실패", result.exceptionOrNull())
|
||||||
|
Toast.makeText(requireContext(), "알람 수정 중 오류가 발생했습니다.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
refreshAlarmList()
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialog.setOnDismissListener { tvSoundNameReference = null }
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dpToPx(dp: Float): Int {
|
||||||
|
return (dp * resources.displayMetrics.density).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: android.content.Intent?) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
if (resultCode == androidx.appcompat.app.AppCompatActivity.RESULT_OK) {
|
||||||
|
val uri = data?.getParcelableExtra<android.net.Uri>(android.media.RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
|
||||||
|
if (uri != null) {
|
||||||
|
currentDialogSoundUri = uri.toString()
|
||||||
|
try {
|
||||||
|
val ringtone = android.media.RingtoneManager.getRingtone(requireContext(), uri)
|
||||||
|
tvSoundNameReference?.text = ringtone.getTitle(requireContext())
|
||||||
|
} catch(e: Exception) {
|
||||||
|
tvSoundNameReference?.text = "사용자 지정음"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun getSoundTitle(context: Context, uriStr: String?): String {
|
||||||
|
if (soundTitleCache.containsKey(uriStr)) return soundTitleCache[uriStr]!!
|
||||||
|
|
||||||
|
// uriStr이 null이거나 비어있거나 "null" 문자열인 경우 기본음으로 처리
|
||||||
|
val title = if (uriStr.isNullOrEmpty() || uriStr == "null") {
|
||||||
|
"기본 알람음"
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
val uri = android.net.Uri.parse(uriStr)
|
||||||
|
val ringtone = android.media.RingtoneManager.getRingtone(context, uri)
|
||||||
|
ringtone?.getTitle(context) ?: "알람음"
|
||||||
|
} catch (e: Exception) {
|
||||||
|
"알람음"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
soundTitleCache[uriStr] = title
|
||||||
|
return title
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.example.shiftalarm.databinding.FragmentSettingsBasicBinding
|
||||||
|
import java.io.BufferedInputStream
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import android.app.ProgressDialog
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import android.provider.Settings
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
|
||||||
|
class FragmentSettingsBasic : Fragment() {
|
||||||
|
|
||||||
|
private var _binding: FragmentSettingsBasicBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
private val PREFS_NAME = "ShiftAlarmPrefs"
|
||||||
|
private var isUserInteraction = false
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentSettingsBasicBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
loadSettings()
|
||||||
|
setupListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadSettings() {
|
||||||
|
val prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
// Factory Spinner
|
||||||
|
setupFactorySpinner(prefs)
|
||||||
|
|
||||||
|
// Team Spinner
|
||||||
|
setupTeamSpinner(prefs)
|
||||||
|
|
||||||
|
// Version Info
|
||||||
|
try {
|
||||||
|
val pInfo = requireContext().packageManager.getPackageInfo(requireContext().packageName, 0)
|
||||||
|
binding.versionInfo.text = "Ver. ${pInfo.versionName} | 제작자: 산적이얌"
|
||||||
|
} catch (e: Exception) {
|
||||||
|
binding.versionInfo.text = "Ver. Unknown | 제작자: 산적이얌"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show/Hide Exact Alarm based on version
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
binding.btnExactAlarm.visibility = View.VISIBLE
|
||||||
|
binding.dividerExact.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.btnExactAlarm.visibility = View.GONE
|
||||||
|
binding.dividerExact.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupFactorySpinner(prefs: android.content.SharedPreferences) {
|
||||||
|
val savedFactory = prefs.getString("selected_factory", "Jeonju")
|
||||||
|
val factoryIndex = if (savedFactory == "Nonsan") 1 else 0
|
||||||
|
binding.factorySpinner.setSelection(factoryIndex)
|
||||||
|
|
||||||
|
binding.factorySpinner.setOnTouchListener { _, _ ->
|
||||||
|
isUserInteraction = true
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.factorySpinner.onItemSelectedListener = object : android.widget.AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onItemSelected(parent: android.widget.AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||||
|
if (!isUserInteraction) return
|
||||||
|
|
||||||
|
val isNonsan = position == 1
|
||||||
|
val factory = if (isNonsan) "Nonsan" else "Jeonju"
|
||||||
|
|
||||||
|
val currentFactory = prefs.getString("selected_factory", "Jeonju")
|
||||||
|
if (factory == currentFactory) {
|
||||||
|
// Just update team spinner without resetting times if same factory
|
||||||
|
updateTeamSpinner(isNonsan)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save immediately
|
||||||
|
val editor = prefs.edit()
|
||||||
|
editor.putString("selected_factory", factory)
|
||||||
|
editor.apply()
|
||||||
|
|
||||||
|
// CRUCIAL: Re-sync all alarms for the new factory
|
||||||
|
lifecycleScope.launch {
|
||||||
|
syncAllAlarms(requireContext())
|
||||||
|
Toast.makeText(requireContext(), "공장 설정이 변경되었습니다.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Team Spinner logic
|
||||||
|
updateTeamSpinner(isNonsan)
|
||||||
|
}
|
||||||
|
override fun onNothingSelected(parent: android.widget.AdapterView<*>?) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateTeamSpinner(isNonsan: Boolean) {
|
||||||
|
val currentSelection = binding.teamSpinner.selectedItemPosition
|
||||||
|
val teamOptions = if (isNonsan) {
|
||||||
|
arrayOf("A반", "B반", "C반")
|
||||||
|
} else {
|
||||||
|
arrayOf("A반", "B반", "C반", "D반")
|
||||||
|
}
|
||||||
|
|
||||||
|
val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, teamOptions)
|
||||||
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
binding.teamSpinner.adapter = adapter
|
||||||
|
|
||||||
|
if (currentSelection < teamOptions.size) {
|
||||||
|
binding.teamSpinner.setSelection(currentSelection)
|
||||||
|
} else {
|
||||||
|
binding.teamSpinner.setSelection(0)
|
||||||
|
if (isUserInteraction) {
|
||||||
|
Toast.makeText(requireContext(), "논산 회사는 D반이 없습니다. A반으로 설정됩니다.", Toast.LENGTH_SHORT).show()
|
||||||
|
val prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
prefs.edit().putString("selected_team", "A").apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupTeamSpinner(prefs: android.content.SharedPreferences) {
|
||||||
|
val savedFactory = prefs.getString("selected_factory", "Jeonju")
|
||||||
|
val isNonsan = savedFactory == "Nonsan"
|
||||||
|
updateTeamSpinner(isNonsan)
|
||||||
|
|
||||||
|
val savedTeam = prefs.getString("selected_team", "A")
|
||||||
|
val teamIndex = when (savedTeam) {
|
||||||
|
"A" -> 0
|
||||||
|
"B" -> 1
|
||||||
|
"C" -> 2
|
||||||
|
"D" -> if (isNonsan) 0 else 3
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
binding.teamSpinner.setSelection(teamIndex)
|
||||||
|
|
||||||
|
binding.teamSpinner.setOnTouchListener { _, _ ->
|
||||||
|
isUserInteraction = true
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.teamSpinner.onItemSelectedListener = object : android.widget.AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onItemSelected(parent: android.widget.AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||||
|
if (!isUserInteraction) return
|
||||||
|
|
||||||
|
val selectedTeam = when(position) {
|
||||||
|
0 -> "A"
|
||||||
|
1 -> "B"
|
||||||
|
2 -> "C"
|
||||||
|
3 -> "D"
|
||||||
|
else -> "A"
|
||||||
|
}
|
||||||
|
prefs.edit().putString("selected_team", selectedTeam).apply()
|
||||||
|
|
||||||
|
// CRUCIAL: Re-sync all alarms for the new team
|
||||||
|
lifecycleScope.launch {
|
||||||
|
syncAllAlarms(requireContext())
|
||||||
|
Toast.makeText(requireContext(), "${selectedTeam}반으로 알람이 재설정되었습니다.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNothingSelected(parent: android.widget.AdapterView<*>?) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
updatePermissionStatuses()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updatePermissionStatuses() {
|
||||||
|
val context = requireContext()
|
||||||
|
|
||||||
|
// 1. 배터리 (Battery)
|
||||||
|
val isBatteryIgnored = AlarmPermissionUtil.getBatteryOptimizationStatus(context)
|
||||||
|
binding.tvBatteryStatus.text = if (isBatteryIgnored) "[설정 완료: 절전 예외]" else "클릭하여 '제한 없음'으로 설정하세요"
|
||||||
|
binding.tvBatteryStatus.setTextColor(ContextCompat.getColor(context, if (isBatteryIgnored) R.color.primary else R.color.shift_red))
|
||||||
|
|
||||||
|
// 2. 정확한 알람 (Exact Alarm) - Android 12+
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
val isExactGranted = AlarmPermissionUtil.getExactAlarmStatus(context)
|
||||||
|
binding.tvExactStatus.text = if (isExactGranted) "[설정 완료: 정밀 알람]" else "필수: 클릭하여 권한을 허용하세요"
|
||||||
|
binding.tvExactStatus.setTextColor(ContextCompat.getColor(context, if (isExactGranted) R.color.primary else R.color.shift_red))
|
||||||
|
} else {
|
||||||
|
binding.btnExactAlarm.visibility = View.GONE
|
||||||
|
binding.dividerExact.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 다른 앱 위에 표시 (Overlay)
|
||||||
|
val isOverlayGranted = AlarmPermissionUtil.getOverlayStatus(context)
|
||||||
|
binding.tvOverlayStatus.text = if (isOverlayGranted) "[설정 완료: 화면 우위]" else "필수: 알람창 노출을 위해 허용하세요"
|
||||||
|
binding.tvOverlayStatus.setTextColor(ContextCompat.getColor(context, if (isOverlayGranted) R.color.primary else R.color.shift_red))
|
||||||
|
|
||||||
|
// 4. 전체화면 알림 (Full Screen Intent) - Android 14+
|
||||||
|
if (Build.VERSION.SDK_INT >= 34) {
|
||||||
|
val isFullScreenGranted = AlarmPermissionUtil.getFullScreenIntentStatus(context)
|
||||||
|
binding.tvFullScreenStatus.text = if (isFullScreenGranted) "[설정 완료: 전체화면]" else "필수: 안드로이드 14 이상 필수 설정"
|
||||||
|
binding.tvFullScreenStatus.setTextColor(ContextCompat.getColor(context, if (isFullScreenGranted) R.color.primary else R.color.shift_red))
|
||||||
|
binding.btnFullScreenIntent.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.btnFullScreenIntent.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupListeners() {
|
||||||
|
val prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
binding.btnBatteryOptimize.setOnClickListener {
|
||||||
|
AlarmPermissionUtil.requestBatteryOptimization(requireContext())
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnExactAlarm.setOnClickListener {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
val intent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM).apply {
|
||||||
|
data = Uri.parse("package:${requireContext().packageName}")
|
||||||
|
}
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnOverlayPermission.setOnClickListener {
|
||||||
|
AlarmPermissionUtil.requestOverlayPermission(requireContext())
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnFullScreenIntent.setOnClickListener {
|
||||||
|
AlarmPermissionUtil.requestFullScreenIntentPermission(requireContext())
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnPermissionSettings.setOnClickListener {
|
||||||
|
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||||
|
data = Uri.parse("package:${requireContext().packageName}")
|
||||||
|
}
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnCheckUpdate.setOnClickListener {
|
||||||
|
checkUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkUpdate() {
|
||||||
|
AppUpdateManager.checkUpdate(requireActivity(), silent = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.example.shiftalarm.databinding.FragmentSettingsLabBinding
|
||||||
|
|
||||||
|
class FragmentSettingsLab : Fragment() {
|
||||||
|
|
||||||
|
private var _binding: FragmentSettingsLabBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentSettingsLabBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
}
|
||||||
194
app/src/main/java/com/example/shiftalarm/HolidayManager.kt
Normal file
194
app/src/main/java/com/example/shiftalarm/HolidayManager.kt
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import java.time.DayOfWeek
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.ZoneId
|
||||||
|
import android.icu.util.ChineseCalendar
|
||||||
|
import android.os.Build
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 대한민국 공휴일 관리자 + 물때(Tide) 계산기.
|
||||||
|
* - 양력 고정 공휴일
|
||||||
|
* - 음력 공휴일 (ICU ChineseCalendar)
|
||||||
|
* - 대체공휴일
|
||||||
|
* - 물때 (7물때식: 서해안/남해서부 기준)
|
||||||
|
*/
|
||||||
|
object HolidayManager {
|
||||||
|
|
||||||
|
private val cache = mutableMapOf<Int, Map<LocalDate, String>>()
|
||||||
|
|
||||||
|
fun getHolidayName(date: LocalDate): String? {
|
||||||
|
return getHolidaysForYear(date.year)[date]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isHoliday(date: LocalDate): Boolean {
|
||||||
|
return getHolidaysForYear(date.year).containsKey(date)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getHolidaysForYear(year: Int): Map<LocalDate, String> {
|
||||||
|
return cache.getOrPut(year) { generateHolidays(year) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateHolidays(year: Int): Map<LocalDate, String> {
|
||||||
|
val holidays = mutableMapOf<LocalDate, String>()
|
||||||
|
addFixedHolidays(year, holidays)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
addLunarHolidays(year, holidays)
|
||||||
|
}
|
||||||
|
addSubstituteHolidays(holidays)
|
||||||
|
return holidays
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 양력 고정 공휴일 ──
|
||||||
|
private fun addFixedHolidays(year: Int, h: MutableMap<LocalDate, String>) {
|
||||||
|
h[LocalDate.of(year, 1, 1)] = "신정"
|
||||||
|
h[LocalDate.of(year, 3, 1)] = "삼일절"
|
||||||
|
h[LocalDate.of(year, 5, 5)] = "어린이날"
|
||||||
|
h[LocalDate.of(year, 6, 6)] = "현충일"
|
||||||
|
h[LocalDate.of(year, 8, 15)] = "광복절"
|
||||||
|
h[LocalDate.of(year, 10, 3)] = "개천절"
|
||||||
|
h[LocalDate.of(year, 10, 9)] = "한글날"
|
||||||
|
h[LocalDate.of(year, 12, 25)] = "성탄절"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 음력 공휴일 ──
|
||||||
|
private fun addLunarHolidays(year: Int, h: MutableMap<LocalDate, String>) {
|
||||||
|
lunarToSolar(year, 1, 1)?.let { seolnal ->
|
||||||
|
h[seolnal.minusDays(1)] = "설날 연휴"
|
||||||
|
h[seolnal] = "설날"
|
||||||
|
h[seolnal.plusDays(1)] = "설날 연휴"
|
||||||
|
}
|
||||||
|
lunarToSolar(year, 4, 8)?.let { buddha ->
|
||||||
|
h[buddha] = "부처님오신날"
|
||||||
|
}
|
||||||
|
lunarToSolar(year, 8, 15)?.let { chuseok ->
|
||||||
|
h[chuseok.minusDays(1)] = "추석 연휴"
|
||||||
|
h[chuseok] = "추석"
|
||||||
|
h[chuseok.plusDays(1)] = "추석 연휴"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 대체공휴일 (2025~ 기준) ──
|
||||||
|
private val SUBSTITUTE_ELIGIBLE = setOf(
|
||||||
|
"삼일절", "어린이날", "부처님오신날", "현충일", "광복절",
|
||||||
|
"개천절", "한글날", "성탄절",
|
||||||
|
"설날", "설날 연휴", "추석", "추석 연휴"
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun addSubstituteHolidays(holidays: MutableMap<LocalDate, String>) {
|
||||||
|
val occupied = holidays.keys.toMutableSet()
|
||||||
|
val substitutes = mutableListOf<Pair<LocalDate, String>>()
|
||||||
|
|
||||||
|
for ((date, name) in holidays.entries.sortedBy { it.key }) {
|
||||||
|
if (name !in SUBSTITUTE_ELIGIBLE) continue
|
||||||
|
val dow = date.dayOfWeek
|
||||||
|
if (dow == DayOfWeek.SATURDAY || dow == DayOfWeek.SUNDAY) {
|
||||||
|
var sub = date.plusDays(1)
|
||||||
|
while (sub.dayOfWeek == DayOfWeek.SATURDAY ||
|
||||||
|
sub.dayOfWeek == DayOfWeek.SUNDAY ||
|
||||||
|
sub in occupied
|
||||||
|
) {
|
||||||
|
sub = sub.plusDays(1)
|
||||||
|
}
|
||||||
|
substitutes.add(sub to "대체공휴일($name)")
|
||||||
|
occupied.add(sub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ((d, n) in substitutes) holidays[d] = n
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 음력 → 양력 변환 (ICU ChineseCalendar) ──
|
||||||
|
private fun lunarToSolar(gregorianYear: Int, lunarMonth: Int, lunarDay: Int): LocalDate? {
|
||||||
|
try {
|
||||||
|
val cc = ChineseCalendar()
|
||||||
|
val cal = java.util.GregorianCalendar(gregorianYear, 6, 1)
|
||||||
|
cc.timeInMillis = cal.timeInMillis
|
||||||
|
val chineseYear = cc.get(ChineseCalendar.EXTENDED_YEAR)
|
||||||
|
|
||||||
|
cc.set(ChineseCalendar.EXTENDED_YEAR, chineseYear)
|
||||||
|
cc.set(ChineseCalendar.MONTH, lunarMonth - 1)
|
||||||
|
cc.set(ChineseCalendar.DAY_OF_MONTH, lunarDay)
|
||||||
|
cc.set(ChineseCalendar.IS_LEAP_MONTH, 0)
|
||||||
|
|
||||||
|
val result = java.time.Instant.ofEpochMilli(cc.timeInMillis)
|
||||||
|
.atZone(ZoneId.of("Asia/Seoul")).toLocalDate()
|
||||||
|
|
||||||
|
if (result.year == gregorianYear) return result
|
||||||
|
|
||||||
|
cc.set(ChineseCalendar.EXTENDED_YEAR, chineseYear + 1)
|
||||||
|
cc.set(ChineseCalendar.MONTH, lunarMonth - 1)
|
||||||
|
cc.set(ChineseCalendar.DAY_OF_MONTH, lunarDay)
|
||||||
|
cc.set(ChineseCalendar.IS_LEAP_MONTH, 0)
|
||||||
|
|
||||||
|
val result2 = java.time.Instant.ofEpochMilli(cc.timeInMillis)
|
||||||
|
.atZone(ZoneId.of("Asia/Seoul")).toLocalDate()
|
||||||
|
|
||||||
|
return if (result2.year == gregorianYear) result2 else result
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 음력 날짜 문자열 (달력 표시용) ──
|
||||||
|
fun getLunarDateString(date: LocalDate): String {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
try {
|
||||||
|
val cc = ChineseCalendar()
|
||||||
|
cc.timeInMillis = date.atStartOfDay(ZoneId.of("Asia/Seoul"))
|
||||||
|
.toInstant().toEpochMilli()
|
||||||
|
val m = cc.get(ChineseCalendar.MONTH) + 1
|
||||||
|
val d = cc.get(ChineseCalendar.DAY_OF_MONTH)
|
||||||
|
return "$m.$d"
|
||||||
|
} catch (_: Exception) {}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 물때 계산 (7물때 및 8물때 고도화) ──
|
||||||
|
fun getTide(date: LocalDate, location: String = "군산"): String {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
try {
|
||||||
|
val cc = ChineseCalendar()
|
||||||
|
cc.timeInMillis = date.atStartOfDay(ZoneId.of("Asia/Seoul"))
|
||||||
|
.toInstant().toEpochMilli()
|
||||||
|
val d = cc.get(ChineseCalendar.DAY_OF_MONTH)
|
||||||
|
|
||||||
|
val is8Tide = location == "여수" // 여수 등 남해 일부는 8물때식 선호 경향
|
||||||
|
|
||||||
|
return if (is8Tide) {
|
||||||
|
// 8물때식 (남해/동해 기준)
|
||||||
|
when(d) {
|
||||||
|
in 1..7 -> "${d + 7}물"
|
||||||
|
8 -> "조금"
|
||||||
|
9 -> "무시"
|
||||||
|
in 10..22 -> "${d - 9}물"
|
||||||
|
23 -> "사리"
|
||||||
|
24 -> "조금"
|
||||||
|
25 -> "무시"
|
||||||
|
in 26..30 -> "${d - 25}물"
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 7물때식 (서해/남해서부 기준: 군산, 변산, 태안 등)
|
||||||
|
when(d) {
|
||||||
|
in 1..6 -> "${d + 6}물"
|
||||||
|
7 -> "13물"
|
||||||
|
8 -> "사리"
|
||||||
|
9 -> "조금"
|
||||||
|
10 -> "무시"
|
||||||
|
in 11..21 -> "${d - 10}물"
|
||||||
|
22 -> "12물"
|
||||||
|
23 -> "13물"
|
||||||
|
24 -> "사리"
|
||||||
|
25 -> "조금"
|
||||||
|
26 -> "무시"
|
||||||
|
in 27..30 -> "${d - 26}물"
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
691
app/src/main/java/com/example/shiftalarm/MainActivity.kt
Normal file
691
app/src/main/java/com/example/shiftalarm/MainActivity.kt
Normal file
@@ -0,0 +1,691 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.AlarmManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.GestureDetector
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.work.ExistingPeriodicWorkPolicy
|
||||||
|
import androidx.work.PeriodicWorkRequestBuilder
|
||||||
|
import androidx.work.WorkManager
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import com.example.shiftalarm.databinding.ActivityMainBinding
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.YearMonth
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var binding: ActivityMainBinding
|
||||||
|
private val PREFS_NAME = "ShiftAlarmPrefs"
|
||||||
|
private val KEY_TEAM = "selected_team"
|
||||||
|
private var currentViewMonth: YearMonth = YearMonth.now(ShiftCalculator.SEOUL_ZONE)
|
||||||
|
private var currentViewTeam: String = "A"
|
||||||
|
private lateinit var gestureDetector: GestureDetector
|
||||||
|
|
||||||
|
override fun onConfigurationChanged(newConfig: android.content.res.Configuration) {
|
||||||
|
super.onConfigurationChanged(newConfig)
|
||||||
|
// Smooth transition for theme change
|
||||||
|
finish()
|
||||||
|
startActivity(Intent(this, javaClass))
|
||||||
|
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
|
||||||
|
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
val density = resources.displayMetrics.density
|
||||||
|
val p = (8 * density).toInt()
|
||||||
|
v.setPadding(systemBars.left + p, systemBars.top + p, systemBars.right + p, systemBars.bottom + p)
|
||||||
|
insets
|
||||||
|
}
|
||||||
|
|
||||||
|
setupCalendar()
|
||||||
|
setupWorker()
|
||||||
|
setupSwipeGesture()
|
||||||
|
AppUpdateManager.checkUpdate(this, silent = true)
|
||||||
|
checkRoot()
|
||||||
|
|
||||||
|
val prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
currentViewTeam = prefs.getString(KEY_TEAM, "A") ?: "A"
|
||||||
|
|
||||||
|
// Default to Shift Calendar mode (checkbox unchecked)
|
||||||
|
binding.cbShowHolidays.isChecked = false
|
||||||
|
|
||||||
|
binding.btnSettings.setOnClickListener {
|
||||||
|
startActivity(Intent(this, SettingsActivity::class.java))
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.prevMonth.setOnClickListener {
|
||||||
|
currentViewMonth = currentViewMonth.minusMonths(1)
|
||||||
|
updateCalendar()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.monthTitle.setOnClickListener {
|
||||||
|
showMonthYearPicker()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.nextMonth.setOnClickListener {
|
||||||
|
currentViewMonth = currentViewMonth.plusMonths(1)
|
||||||
|
updateCalendar()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnToday.setOnClickListener {
|
||||||
|
val prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
currentViewTeam = prefs.getString(KEY_TEAM, "A") ?: "A"
|
||||||
|
currentViewMonth = YearMonth.now(ShiftCalculator.SEOUL_ZONE)
|
||||||
|
updateCalendar()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.cbShowHolidays.setOnCheckedChangeListener { _, _ ->
|
||||||
|
updateCalendar()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.alarmInfoBar.setOnClickListener {
|
||||||
|
val intent = Intent(this, SettingsActivity::class.java)
|
||||||
|
intent.putExtra("TARGET_TAB", 1) // 1 is Alarm tab
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tide Location Cycle Logic
|
||||||
|
val tideLocations = listOf("군산", "변산", "여수", "태안")
|
||||||
|
binding.btnTideLocation.setOnClickListener {
|
||||||
|
val currentLoc = prefs.getString("selected_tide_location", "군산") ?: "군산"
|
||||||
|
val nextIndex = (tideLocations.indexOf(currentLoc) + 1) % tideLocations.size
|
||||||
|
val nextLoc = tideLocations[nextIndex]
|
||||||
|
|
||||||
|
prefs.edit().putString("selected_tide_location", nextLoc).apply()
|
||||||
|
binding.btnTideLocation.text = nextLoc
|
||||||
|
updateCalendar()
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupWorker(), checkRoot() 등은 이미 호출됨
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateTideButtonVisibility() {
|
||||||
|
val prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
val showTide = prefs.getBoolean("show_tide", false)
|
||||||
|
val currentLoc = prefs.getString("selected_tide_location", "군산") ?: "군산"
|
||||||
|
|
||||||
|
if (showTide) {
|
||||||
|
binding.btnTideLocation.visibility = android.view.View.VISIBLE
|
||||||
|
binding.btnTideLocation.text = currentLoc
|
||||||
|
} else {
|
||||||
|
binding.btnTideLocation.visibility = android.view.View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupSwipeGesture() {
|
||||||
|
gestureDetector = GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() {
|
||||||
|
private val SWIPE_THRESHOLD = 100
|
||||||
|
private val SWIPE_VELOCITY_THRESHOLD = 100
|
||||||
|
|
||||||
|
override fun onFling(
|
||||||
|
e1: MotionEvent?,
|
||||||
|
e2: MotionEvent,
|
||||||
|
velocityX: Float,
|
||||||
|
velocityY: Float
|
||||||
|
): Boolean {
|
||||||
|
if (e1 == null) return false
|
||||||
|
|
||||||
|
val diffX = e2.x - e1.x
|
||||||
|
val diffY = e2.y - e1.y
|
||||||
|
|
||||||
|
if (abs(diffX) > abs(diffY)) {
|
||||||
|
if (abs(diffX) > SWIPE_THRESHOLD && abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
|
||||||
|
if (diffX > 0) {
|
||||||
|
// Swipe Right -> Previous Month
|
||||||
|
currentViewMonth = currentViewMonth.minusMonths(1)
|
||||||
|
updateCalendar()
|
||||||
|
} else {
|
||||||
|
// Swipe Left -> Next Month
|
||||||
|
currentViewMonth = currentViewMonth.plusMonths(1)
|
||||||
|
updateCalendar()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
binding.calendarGrid.addOnItemTouchListener(object : androidx.recyclerview.widget.RecyclerView.OnItemTouchListener {
|
||||||
|
override fun onInterceptTouchEvent(rv: androidx.recyclerview.widget.RecyclerView, e: MotionEvent): Boolean {
|
||||||
|
gestureDetector.onTouchEvent(e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
override fun onTouchEvent(rv: androidx.recyclerview.widget.RecyclerView, e: MotionEvent) {}
|
||||||
|
override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {}
|
||||||
|
})
|
||||||
|
|
||||||
|
binding.calendarContainer.setOnTouchListener { _, event ->
|
||||||
|
gestureDetector.onTouchEvent(event)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
val prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
currentViewTeam = prefs.getString(KEY_TEAM, "A") ?: "A"
|
||||||
|
|
||||||
|
updateTideButtonVisibility()
|
||||||
|
updateCalendar()
|
||||||
|
|
||||||
|
// 일원화된 통합 권한 체크 실행 (신뢰도 100% 보장)
|
||||||
|
AlarmPermissionUtil.checkAndRequestAllPermissions(this)
|
||||||
|
|
||||||
|
// 설정 변경 시 즉시 반영을 위한 강제 동기화 (30일 스케줄링)
|
||||||
|
lifecycleScope.launch {
|
||||||
|
syncAllAlarms(this@MainActivity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showMonthYearPicker() {
|
||||||
|
val dialogView = layoutInflater.inflate(R.layout.dialog_month_year_picker, null)
|
||||||
|
val yearPicker = dialogView.findViewById<android.widget.NumberPicker>(R.id.yearPicker)
|
||||||
|
val monthPicker = dialogView.findViewById<android.widget.NumberPicker>(R.id.monthPicker)
|
||||||
|
|
||||||
|
val currentYear = currentViewMonth.year
|
||||||
|
val currentMonth = currentViewMonth.monthValue
|
||||||
|
|
||||||
|
yearPicker.minValue = 2010
|
||||||
|
yearPicker.maxValue = 2050
|
||||||
|
yearPicker.value = currentYear
|
||||||
|
yearPicker.wrapSelectorWheel = false
|
||||||
|
|
||||||
|
monthPicker.minValue = 1
|
||||||
|
monthPicker.maxValue = 12
|
||||||
|
monthPicker.value = currentMonth
|
||||||
|
monthPicker.displayedValues = arrayOf("1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월")
|
||||||
|
|
||||||
|
val dialog = androidx.appcompat.app.AlertDialog.Builder(this, R.style.OneUI8_Dialog)
|
||||||
|
.setView(dialogView)
|
||||||
|
.setPositiveButton("이동") { _, _ ->
|
||||||
|
currentViewMonth = YearMonth.of(yearPicker.value, monthPicker.value)
|
||||||
|
updateCalendar()
|
||||||
|
}
|
||||||
|
.setNegativeButton("취소", null)
|
||||||
|
.create()
|
||||||
|
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
|
// 90% Screen Width for One UI 8.0 feel
|
||||||
|
val width = (resources.displayMetrics.widthPixels * 0.9).toInt()
|
||||||
|
dialog.window?.setLayout(width, android.view.ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||||
|
dialog.window?.setDimAmount(0.6f) // Darker dim to focus on popup
|
||||||
|
|
||||||
|
// Style buttons to look like One UI 8.0
|
||||||
|
dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).apply {
|
||||||
|
setTextColor(ContextCompat.getColor(this@MainActivity, R.color.primary))
|
||||||
|
textSize = 17f
|
||||||
|
setPadding(dpToPx(32f), dpToPx(16f), dpToPx(32f), dpToPx(16f))
|
||||||
|
}
|
||||||
|
dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEGATIVE).apply {
|
||||||
|
setTextColor(ContextCompat.getColor(this@MainActivity, R.color.text_secondary))
|
||||||
|
textSize = 17f
|
||||||
|
setPadding(dpToPx(32f), dpToPx(16f), dpToPx(32f), dpToPx(16f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dpToPx(dp: Float): Int {
|
||||||
|
return (dp * resources.displayMetrics.density).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupCalendar() {
|
||||||
|
binding.calendarGrid.layoutManager = GridLayoutManager(this, 7)
|
||||||
|
updateCalendar()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateCalendar() {
|
||||||
|
val prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
val selectedTeam = prefs.getString(KEY_TEAM, "A") ?: "A"
|
||||||
|
val factory = prefs.getString("selected_factory", "Jeonju") ?: "Jeonju"
|
||||||
|
|
||||||
|
// Today's Shift
|
||||||
|
val today = LocalDate.now(ShiftCalculator.SEOUL_ZONE)
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val db = AppDatabase.getDatabase(this@MainActivity)
|
||||||
|
val dao = db.shiftDao()
|
||||||
|
val repo = ShiftRepository(this@MainActivity)
|
||||||
|
|
||||||
|
// 전체 사용자 알람 로드 (일원화된 Room DB 사용)
|
||||||
|
val allCustomAlarms = repo.getAllCustomAlarms()
|
||||||
|
|
||||||
|
// 디스플레이 업데이트
|
||||||
|
if (currentViewTeam == selectedTeam) {
|
||||||
|
val shiftForMyTeam = withContext(Dispatchers.IO) { repo.getShift(today, selectedTeam, factory) }
|
||||||
|
updateAlarmTimeDisplay(today, shiftForMyTeam, factory, allCustomAlarms)
|
||||||
|
binding.alarmTimeText.visibility = android.view.View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.alarmTimeText.visibility = android.view.View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load overrides and memos for the month
|
||||||
|
val monthStr = currentViewMonth.toString()
|
||||||
|
val overrides = withContext(Dispatchers.IO) {
|
||||||
|
dao.getOverridesForMonth(factory, currentViewTeam, monthStr).associateBy { overrideItem -> overrideItem.date }
|
||||||
|
}
|
||||||
|
val memos = withContext(Dispatchers.IO) {
|
||||||
|
dao.getMemosForMonth(monthStr).associateBy { memoItem -> memoItem.date }
|
||||||
|
}
|
||||||
|
|
||||||
|
val days = generateDaysForMonthWithData(currentViewMonth, currentViewTeam, factory, overrides, memos)
|
||||||
|
|
||||||
|
val adapter = CalendarAdapter(days, object : CalendarAdapter.OnDayClickListener {
|
||||||
|
override fun onDayClick(date: LocalDate, currentShift: String) {
|
||||||
|
showDaySettingsDialog(date, currentShift)
|
||||||
|
}
|
||||||
|
}, binding.cbShowHolidays.isChecked)
|
||||||
|
|
||||||
|
binding.calendarGrid.adapter = adapter
|
||||||
|
binding.monthTitle.text = currentViewMonth.format(DateTimeFormatter.ofPattern("yyyy년 MM월"))
|
||||||
|
|
||||||
|
// Update Header Status Text with Permission Warning if needed
|
||||||
|
val shiftForViewingTeam = withContext(Dispatchers.IO) { repo.getShift(today, currentViewTeam, factory) }
|
||||||
|
val teamSuffix = if (currentViewTeam == selectedTeam) " (내 반)" else " (${currentViewTeam}반)"
|
||||||
|
|
||||||
|
if (currentViewTeam == selectedTeam && !AlarmPermissionUtil.getExactAlarmStatus(this@MainActivity)) {
|
||||||
|
binding.todayStatusText.text = "⚠️ 정확한 알람 권한이 필요합니다 (설정 필요)"
|
||||||
|
binding.todayStatusText.setTextColor(androidx.core.content.ContextCompat.getColor(this@MainActivity, R.color.warning_red))
|
||||||
|
} else {
|
||||||
|
binding.todayStatusText.text = "오늘의 근무: $shiftForViewingTeam$teamSuffix"
|
||||||
|
binding.todayStatusText.setTextColor(androidx.core.content.ContextCompat.getColor(this@MainActivity, R.color.text_secondary))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOtherTeamsLayout(today, factory, prefs)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateOtherTeamsLayout(today: LocalDate, factory: String, prefs: android.content.SharedPreferences) {
|
||||||
|
val teamColors = mapOf(
|
||||||
|
"A" to R.color.team_a_color,
|
||||||
|
"B" to R.color.team_b_color,
|
||||||
|
"C" to R.color.team_c_color,
|
||||||
|
"D" to R.color.team_d_color
|
||||||
|
)
|
||||||
|
val container = binding.otherTeamsContainer
|
||||||
|
container.removeAllViews()
|
||||||
|
|
||||||
|
val rowLayout = android.widget.LinearLayout(this).apply {
|
||||||
|
orientation = android.widget.LinearLayout.HORIZONTAL
|
||||||
|
layoutParams = android.widget.LinearLayout.LayoutParams(
|
||||||
|
android.widget.LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val allTeams = if (factory == "Nonsan") listOf("A", "B", "C") else listOf("A", "B", "C", "D")
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
for (t in allTeams) {
|
||||||
|
val shift = ShiftCalculator.getShift(today, t, factory)
|
||||||
|
val shortShift = when(shift) {
|
||||||
|
"주간" -> "주"
|
||||||
|
"석간" -> "석"
|
||||||
|
"야간" -> "야"
|
||||||
|
"주간 맞교대" -> "주맞"
|
||||||
|
"야간 맞교대" -> "야맞"
|
||||||
|
"휴무", "휴가" -> "휴"
|
||||||
|
else -> shift.take(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
val textView = android.widget.TextView(this@MainActivity).apply {
|
||||||
|
text = "${t}반 ($shortShift)"
|
||||||
|
setPadding(0, 24, 0, 24)
|
||||||
|
textSize = 12f
|
||||||
|
gravity = android.view.Gravity.CENTER
|
||||||
|
setTypeface(null, android.graphics.Typeface.BOLD)
|
||||||
|
|
||||||
|
if (currentViewTeam == t) {
|
||||||
|
setTextColor(android.graphics.Color.WHITE)
|
||||||
|
setBackgroundResource(R.drawable.bg_pill_rect_selected)
|
||||||
|
} else {
|
||||||
|
setTextColor(androidx.core.content.ContextCompat.getColor(context, teamColors[t]!!))
|
||||||
|
setBackgroundResource(R.drawable.bg_pill_rect_unselected)
|
||||||
|
}
|
||||||
|
|
||||||
|
layoutParams = android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply {
|
||||||
|
setMargins(4, 0, 4, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
setOnClickListener {
|
||||||
|
if (currentViewTeam != t) {
|
||||||
|
currentViewTeam = t
|
||||||
|
updateCalendar()
|
||||||
|
Toast.makeText(context, "${t}반 근무표를 표시합니다.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rowLayout.addView(textView)
|
||||||
|
}
|
||||||
|
container.addView(rowLayout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateDaysForMonthWithData(
|
||||||
|
month: YearMonth,
|
||||||
|
team: String,
|
||||||
|
factory: String,
|
||||||
|
overrides: Map<String, ShiftOverride>,
|
||||||
|
memos: Map<String, DailyMemo>
|
||||||
|
): List<DayShift> {
|
||||||
|
val daysInMonth = month.lengthOfMonth()
|
||||||
|
val firstDayOfMonth = month.atDay(1).dayOfWeek.value % 7
|
||||||
|
val actualDayCount = firstDayOfMonth + daysInMonth
|
||||||
|
val targetCells = if (actualDayCount <= 35) 35 else 42
|
||||||
|
|
||||||
|
val dayList = mutableListOf<DayShift>()
|
||||||
|
|
||||||
|
for (i in 0 until firstDayOfMonth) {
|
||||||
|
dayList.add(DayShift(null, null))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (day in 1..daysInMonth) {
|
||||||
|
val date = month.atDay(day)
|
||||||
|
val dateStr = date.toString()
|
||||||
|
|
||||||
|
val shift = overrides[dateStr]?.shift ?: ShiftCalculator.getShift(date, team, factory)
|
||||||
|
val memo = memos[dateStr]
|
||||||
|
val hasMemo = memo != null
|
||||||
|
val memoContent = memo?.content
|
||||||
|
|
||||||
|
dayList.add(DayShift(date, shift, hasMemo, memoContent))
|
||||||
|
}
|
||||||
|
|
||||||
|
while (dayList.size < targetCells) {
|
||||||
|
dayList.add(DayShift(null, null))
|
||||||
|
}
|
||||||
|
return dayList
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAlarmsStrForDate(date: LocalDate, shift: String, allAlarms: List<CustomAlarm>): List<String> {
|
||||||
|
val alarmTimes = mutableListOf<String>()
|
||||||
|
val isOff = shift == "휴무" || shift == "휴가"
|
||||||
|
|
||||||
|
for (alarm in allAlarms) {
|
||||||
|
if (alarm.isEnabled && (alarm.shiftType == "기타" || (!isOff && alarm.shiftType == shift))) {
|
||||||
|
if (!alarmTimes.contains(alarm.time)) {
|
||||||
|
alarmTimes.add(alarm.time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
alarmTimes.sort()
|
||||||
|
return alarmTimes
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateAlarmTimeDisplay(date: LocalDate, shift: String, factory: String, allAlarms: List<CustomAlarm>) {
|
||||||
|
val prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
// 0. Check Master Switch
|
||||||
|
if (!ShiftAlarmDefaults.isMasterAlarmEnabled(prefs)) {
|
||||||
|
binding.alarmTimeText.text = "전체 알람 꺼짐"
|
||||||
|
binding.alarmTimeText.visibility = android.view.View.VISIBLE
|
||||||
|
binding.alarmTimeText.setTextColor(androidx.core.content.ContextCompat.getColor(this, R.color.shift_red))
|
||||||
|
binding.alarmTimeText.setTypeface(null, android.graphics.Typeface.BOLD)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val todayStr = getAlarmsStrForDate(date, shift, allAlarms).firstOrNull() ?: "없음"
|
||||||
|
val tomorrowDate = date.plusDays(1)
|
||||||
|
val repo = ShiftRepository(this)
|
||||||
|
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
val tomorrowShiftFull = repo.getShift(tomorrowDate, currentViewTeam, factory)
|
||||||
|
val tomorrowAlarms = getAlarmsStrForDate(tomorrowDate, tomorrowShiftFull, allAlarms)
|
||||||
|
val tomorrowStr = tomorrowAlarms.firstOrNull() ?: "없음"
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
if (!ShiftAlarmDefaults.isMasterAlarmEnabled(prefs)) {
|
||||||
|
binding.alarmTimeText.text = "전체 알람 꺼짐"
|
||||||
|
binding.alarmTimeText.setTextColor(androidx.core.content.ContextCompat.getColor(this@MainActivity, R.color.shift_red))
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
|
|
||||||
|
val todayLabel = if (todayStr == "없음") "오늘: 없음" else "오늘: $todayStr"
|
||||||
|
val tomorrowLabel = if (tomorrowStr == "없음") "내일: 없음" else "내일: $tomorrowStr"
|
||||||
|
|
||||||
|
binding.alarmTimeText.text = "$todayLabel | $tomorrowLabel"
|
||||||
|
binding.alarmTimeText.visibility = android.view.View.VISIBLE
|
||||||
|
binding.alarmTimeText.setTypeface(null, android.graphics.Typeface.BOLD)
|
||||||
|
binding.alarmTimeText.setTextColor(androidx.core.content.ContextCompat.getColor(this@MainActivity, R.color.text_primary))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showDaySettingsDialog(date: LocalDate, currentShift: String) {
|
||||||
|
val dialogView = layoutInflater.inflate(R.layout.dialog_day_settings, null)
|
||||||
|
val dialog = androidx.appcompat.app.AlertDialog.Builder(this)
|
||||||
|
.setView(dialogView)
|
||||||
|
.create()
|
||||||
|
dialog.window?.setBackgroundDrawableResource(android.R.color.transparent)
|
||||||
|
|
||||||
|
val prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
val factory = prefs.getString("selected_factory", "Jeonju") ?: "Jeonju"
|
||||||
|
val team = prefs.getString(KEY_TEAM, "A") ?: "A"
|
||||||
|
|
||||||
|
// Set Title - One UI Style Header
|
||||||
|
val titleText = dialogView.findViewById<android.widget.TextView>(R.id.dialogTitle)
|
||||||
|
val subtitleText = dialogView.findViewById<android.widget.TextView>(R.id.dialogSubtitle)
|
||||||
|
|
||||||
|
subtitleText.text = if (date == LocalDate.now()) "오늘의 근무" else "${date.monthValue}월 ${date.dayOfMonth}일 근무"
|
||||||
|
titleText.text = "$currentShift"
|
||||||
|
|
||||||
|
// Button Handlers
|
||||||
|
val actionMap = mapOf(
|
||||||
|
R.id.btnJu to "주간",
|
||||||
|
R.id.btnJuMat to "주간 맞교대",
|
||||||
|
R.id.btnSeok to "석간",
|
||||||
|
R.id.btnYa to "야간",
|
||||||
|
R.id.btnYaMat to "야간 맞교대",
|
||||||
|
R.id.btnOff to "휴무",
|
||||||
|
R.id.btnWolcha to "월차",
|
||||||
|
R.id.btnYeoncha to "연차",
|
||||||
|
R.id.btnBanwol to "반월",
|
||||||
|
R.id.btnBannyeon to "반년",
|
||||||
|
R.id.btnEdu to "교육",
|
||||||
|
R.id.btnManual to "직접 입력",
|
||||||
|
R.id.btnReset to "원래대로"
|
||||||
|
)
|
||||||
|
|
||||||
|
fun applyStrokeStyle(viewId: Int, shiftType: String) {
|
||||||
|
val view = dialogView.findViewById<android.widget.TextView>(viewId)
|
||||||
|
val colorRes = when(shiftType) {
|
||||||
|
"주간" -> R.color.shift_ju
|
||||||
|
"석간" -> R.color.shift_seok
|
||||||
|
"야간" -> R.color.shift_ya
|
||||||
|
"주간 맞교대" -> R.color.shift_jumat
|
||||||
|
"야간 맞교대" -> R.color.shift_yamat
|
||||||
|
"휴무", "휴가" -> R.color.shift_off
|
||||||
|
"월차", "연차" -> R.color.secondary
|
||||||
|
"반월", "반년" -> R.color.shift_red
|
||||||
|
"교육" -> R.color.primary
|
||||||
|
"원래대로" -> R.color.text_secondary
|
||||||
|
else -> R.color.shift_gray
|
||||||
|
}
|
||||||
|
val color = androidx.core.content.ContextCompat.getColor(this, colorRes)
|
||||||
|
|
||||||
|
view.setTextColor(color)
|
||||||
|
|
||||||
|
// Create Stroke Drawable Programmatically
|
||||||
|
val drawable = android.graphics.drawable.GradientDrawable()
|
||||||
|
drawable.shape = android.graphics.drawable.GradientDrawable.OVAL
|
||||||
|
drawable.setColor(android.graphics.Color.TRANSPARENT)
|
||||||
|
val density = resources.displayMetrics.density
|
||||||
|
drawable.setStroke((1.5 * density).toInt(), color)
|
||||||
|
|
||||||
|
view.background = drawable
|
||||||
|
}
|
||||||
|
|
||||||
|
actionMap.forEach { (id, type) -> applyStrokeStyle(id, type) }
|
||||||
|
|
||||||
|
// Memo Handling
|
||||||
|
val etMemo = dialogView.findViewById<android.widget.EditText>(R.id.etMemo)
|
||||||
|
val repo = ShiftRepository(this)
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val existingMemo = repo.getMemo(date)
|
||||||
|
etMemo.setText(existingMemo ?: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((id, action) in actionMap) {
|
||||||
|
dialogView.findViewById<android.view.View>(id).setOnClickListener {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val content = etMemo.text.toString().trim()
|
||||||
|
repo.setMemo(date, content) // Save memo even when shift button clicked
|
||||||
|
handleDaySettingAction(date, action, team, factory)
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogView.findViewById<android.view.View>(R.id.btnClearMemo).setOnClickListener {
|
||||||
|
etMemo.setText("")
|
||||||
|
lifecycleScope.launch {
|
||||||
|
repo.setMemo(date, "")
|
||||||
|
updateCalendar()
|
||||||
|
Toast.makeText(this@MainActivity, "메모가 삭제되었습니다.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogView.findViewById<android.view.View>(R.id.btnClose).setOnClickListener {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val content = etMemo.text.toString().trim()
|
||||||
|
repo.setMemo(date, content)
|
||||||
|
updateCalendar()
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun handleDaySettingAction(date: LocalDate, selected: String, team: String, factory: String) {
|
||||||
|
val repo = ShiftRepository(this)
|
||||||
|
val prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
|
||||||
|
when (selected) {
|
||||||
|
"원래대로" -> {
|
||||||
|
repo.clearOverride(date, team, factory)
|
||||||
|
repo.setMemo(date, "") // Clear memo too on reset
|
||||||
|
android.widget.Toast.makeText(this, "원래 근무로 복구되었습니다.", android.widget.Toast.LENGTH_SHORT).show()
|
||||||
|
syncAllAlarms(this)
|
||||||
|
updateCalendar()
|
||||||
|
}
|
||||||
|
"직접 입력" -> {
|
||||||
|
showCustomInputDialog(date, repo, team, factory)
|
||||||
|
}
|
||||||
|
"주간", "석간", "야간", "주간 맞교대", "야간 맞교대" -> {
|
||||||
|
// Standard Shifts -> Override Shift WITHOUT manual time
|
||||||
|
repo.setOverride(date, selected, team, factory)
|
||||||
|
updateCalendar()
|
||||||
|
// Alarms are handled by syncAllAlarms/CustomAlarms during updateCalendar
|
||||||
|
syncAllAlarms(this)
|
||||||
|
android.widget.Toast.makeText(this, "${date} [$selected]로 설정되었습니다.", android.widget.Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
"휴무" -> {
|
||||||
|
// Standard Off
|
||||||
|
repo.setOverride(date, selected, team, factory)
|
||||||
|
updateCalendar()
|
||||||
|
syncAllAlarms(this)
|
||||||
|
android.widget.Toast.makeText(this, "${selected}로 설정되었습니다. 알람이 해제됩니다.", android.widget.Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// New Types: 월차, 연차, 반월, 반년, 교육 -> Saved as Override with no time
|
||||||
|
repo.setOverride(date, selected, team, factory)
|
||||||
|
updateCalendar()
|
||||||
|
syncAllAlarms(this)
|
||||||
|
android.widget.Toast.makeText(this, "${selected}(으)로 기록되었습니다. 알람이 해제됩니다.", android.widget.Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showCustomInputDialog(date: LocalDate, repo: ShiftRepository, team: String, factory: String) {
|
||||||
|
val layout = android.widget.LinearLayout(this).apply {
|
||||||
|
orientation = android.widget.LinearLayout.VERTICAL
|
||||||
|
setPadding(50, 40, 50, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
val etMemoInput = android.widget.EditText(this).apply {
|
||||||
|
hint = "메모 내용 (예: 회식, 교육, 출장)"
|
||||||
|
maxLines = 1
|
||||||
|
filters = arrayOf(android.text.InputFilter.LengthFilter(20))
|
||||||
|
}
|
||||||
|
|
||||||
|
layout.addView(etMemoInput)
|
||||||
|
|
||||||
|
androidx.appcompat.app.AlertDialog.Builder(this)
|
||||||
|
.setTitle("${date.monthValue}월 ${date.dayOfMonth}일 메모 입력")
|
||||||
|
.setView(layout)
|
||||||
|
.setPositiveButton("확인") { _, _ ->
|
||||||
|
val content = etMemoInput.text.toString().trim()
|
||||||
|
|
||||||
|
if (content.isNotEmpty()) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
// Direct Input -> Set as Memo.
|
||||||
|
// We do NOT change the shift (keep default or existing override).
|
||||||
|
// If user wants to change shift, they should use the specific buttons.
|
||||||
|
repo.setMemo(date, content)
|
||||||
|
updateCalendar()
|
||||||
|
android.widget.Toast.makeText(this@MainActivity, "메모가 저장되었습니다.", android.widget.Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
android.widget.Toast.makeText(this, "입력이 취소되었습니다.", android.widget.Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setNegativeButton("취소", null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun setupWorker() {
|
||||||
|
val workRequest = PeriodicWorkRequestBuilder<AlarmWorker>(24, TimeUnit.HOURS)
|
||||||
|
.setInitialDelay(calculateDelayToMidnight(), TimeUnit.MILLISECONDS)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
|
||||||
|
"DailyShiftCheck",
|
||||||
|
ExistingPeriodicWorkPolicy.KEEP,
|
||||||
|
workRequest
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateDelayToMidnight(): Long {
|
||||||
|
val seoulZone = java.time.ZoneId.of("Asia/Seoul")
|
||||||
|
val now = java.time.LocalDateTime.now(seoulZone)
|
||||||
|
val midnight = now.plusDays(1).withHour(0).withMinute(0).withSecond(1)
|
||||||
|
return java.time.Duration.between(now, midnight).toMillis()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkRoot() {
|
||||||
|
if (RootUtil.isDeviceRooted()) {
|
||||||
|
Toast.makeText(this, "⚠️ 루팅된 기기에서 시각적 오류나 알람 불안정이 발생할 수 있습니다.", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
60
app/src/main/java/com/example/shiftalarm/ManualActivity.kt
Normal file
60
app/src/main/java/com/example/shiftalarm/ManualActivity.kt
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import com.example.shiftalarm.databinding.ActivityManualBinding
|
||||||
|
|
||||||
|
class ManualActivity : AppCompatActivity() {
|
||||||
|
private lateinit var binding: ActivityManualBinding
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = ActivityManualBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
|
||||||
|
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||||
|
insets
|
||||||
|
}
|
||||||
|
|
||||||
|
setupManual()
|
||||||
|
|
||||||
|
binding.btnCloseManual.setOnClickListener {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupManual() {
|
||||||
|
try {
|
||||||
|
val versionName = try {
|
||||||
|
packageManager.getPackageInfo(packageName, 0).versionName
|
||||||
|
} catch (e: Exception) { "0.7.1" }
|
||||||
|
|
||||||
|
binding.manualVersionText.text = "교대링(Shiftring) v$versionName"
|
||||||
|
|
||||||
|
val rawContent = assets.open("MANUAL.md").bufferedReader().use { it.readText() }
|
||||||
|
|
||||||
|
// Premium Styling logic
|
||||||
|
val styledContent = rawContent
|
||||||
|
.replace(Regex("^# (.*)", RegexOption.MULTILINE), "<br><big><big><b>$1</b></big></big><br>")
|
||||||
|
.replace(Regex("^## (.*)", RegexOption.MULTILINE), "<br><br><font color='#00897B'><b>$1</b></font><br>")
|
||||||
|
.replace(Regex("^### (.*)", RegexOption.MULTILINE), "<br><br><b>$1</b><br>")
|
||||||
|
.replace(Regex("^- (.*)", RegexOption.MULTILINE), " • $1")
|
||||||
|
.replace("\n", "<br>")
|
||||||
|
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
||||||
|
binding.manualContent.text = android.text.Html.fromHtml(styledContent, android.text.Html.FROM_HTML_MODE_LEGACY)
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
binding.manualContent.text = android.text.Html.fromHtml(styledContent)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
binding.manualContent.text = "설명서를 불러오지 못했습니다."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
137
app/src/main/java/com/example/shiftalarm/NoticeActivity.kt
Normal file
137
app/src/main/java/com/example/shiftalarm/NoticeActivity.kt
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import com.example.shiftalarm.databinding.ActivityNoticeBinding
|
||||||
|
import java.io.BufferedReader
|
||||||
|
import java.io.InputStreamReader
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
class NoticeActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var binding: ActivityNoticeBinding
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = ActivityNoticeBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
|
||||||
|
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||||
|
insets
|
||||||
|
}
|
||||||
|
|
||||||
|
supportActionBar?.hide()
|
||||||
|
|
||||||
|
binding.btnCloseNotice.setOnClickListener {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.noticeRecyclerView.layoutManager = LinearLayoutManager(this)
|
||||||
|
fetchChangelog()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchChangelog() {
|
||||||
|
// GitHub Raw URL with cache busting
|
||||||
|
val baseUrl = "https://raw.githubusercontent.com/sanjeok77-tech/dakjaba-releases/main/CHANGELOG.md"
|
||||||
|
val urlString = "$baseUrl?t=${System.currentTimeMillis()}"
|
||||||
|
|
||||||
|
Thread {
|
||||||
|
try {
|
||||||
|
val url = URL(urlString)
|
||||||
|
val connection = url.openConnection() as HttpURLConnection
|
||||||
|
connection.connectTimeout = 5000
|
||||||
|
connection.readTimeout = 5000
|
||||||
|
connection.requestMethod = "GET"
|
||||||
|
connection.useCaches = false
|
||||||
|
|
||||||
|
if (connection.responseCode == 200) {
|
||||||
|
val reader = BufferedReader(InputStreamReader(connection.inputStream))
|
||||||
|
val content = reader.use { it.readText() }
|
||||||
|
|
||||||
|
runOnUiThread {
|
||||||
|
val notices = parseChangelog(content)
|
||||||
|
binding.noticeRecyclerView.adapter = NoticeAdapter(notices)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Exception("Server returned ${connection.responseCode}")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
runOnUiThread {
|
||||||
|
// Fallback to local asset
|
||||||
|
loadLocalChangelog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadLocalChangelog() {
|
||||||
|
try {
|
||||||
|
val content = assets.open("CHANGELOG.md").bufferedReader().use { it.readText() }
|
||||||
|
val notices = parseChangelog(content)
|
||||||
|
binding.noticeRecyclerView.adapter = NoticeAdapter(notices)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
val empty = listOf(NoticeItem("데이터 로드 실패", "", "변경사항을 불러올 수 없습니다."))
|
||||||
|
binding.noticeRecyclerView.adapter = NoticeAdapter(empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseChangelog(content: String): List<NoticeItem> {
|
||||||
|
val notices = mutableListOf<NoticeItem>()
|
||||||
|
val lines = content.lines()
|
||||||
|
|
||||||
|
var currentVersion = ""
|
||||||
|
var currentDate = ""
|
||||||
|
var currentBody = StringBuilder()
|
||||||
|
|
||||||
|
for (line in lines) {
|
||||||
|
val trimmed = line.trim()
|
||||||
|
// Skip empty lines or horizontal rules (any amount of dashes)
|
||||||
|
if (trimmed.isEmpty() || trimmed.matches(Regex("-{2,}"))) continue
|
||||||
|
|
||||||
|
// Handle version headers like "## v0.7.3" or "## [0.7.3]"
|
||||||
|
if (trimmed.startsWith("## v") || trimmed.startsWith("## [")) {
|
||||||
|
// Save previous version if exists
|
||||||
|
if (currentVersion.isNotEmpty() && currentBody.isNotBlank()) {
|
||||||
|
notices.add(NoticeItem("v$currentVersion 업데이트 정보", currentDate, currentBody.toString().trim()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse new version (matches v0.7.3 or [0.7.3])
|
||||||
|
val versionMatch = Regex("v?([\\d.]+)").find(trimmed)
|
||||||
|
currentVersion = versionMatch?.groupValues?.getOrNull(1) ?: ""
|
||||||
|
|
||||||
|
val dateMatch = Regex("(\\d{4}-\\d{2}-\\d{2})").find(trimmed)
|
||||||
|
currentDate = dateMatch?.groupValues?.getOrNull(1) ?: ""
|
||||||
|
|
||||||
|
currentBody = StringBuilder()
|
||||||
|
} else if (trimmed.startsWith("- **") || trimmed.startsWith("* **")) {
|
||||||
|
// Content line with bold key
|
||||||
|
val cleaned = trimmed
|
||||||
|
.replace(Regex("^[-*]\\s*\\*\\*(.+?)\\*\\*:?\\s*"), "▸ $1: ")
|
||||||
|
.replace("**", "")
|
||||||
|
currentBody.appendLine(cleaned)
|
||||||
|
} else if (trimmed.startsWith("-") || trimmed.startsWith("*")) {
|
||||||
|
// Regular bullet point
|
||||||
|
val cleaned = trimmed.replace(Regex("^[-*]\\s*"), "• ")
|
||||||
|
if (cleaned.length > 2) currentBody.appendLine(cleaned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add last version
|
||||||
|
if (currentVersion.isNotEmpty() && currentBody.isNotBlank()) {
|
||||||
|
notices.add(NoticeItem("v$currentVersion 업데이트 정보", currentDate, currentBody.toString().trim()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return notices.take(7)
|
||||||
|
}
|
||||||
|
}
|
||||||
30
app/src/main/java/com/example/shiftalarm/NoticeAdapter.kt
Normal file
30
app/src/main/java/com/example/shiftalarm/NoticeAdapter.kt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
|
class NoticeAdapter(private val notices: List<NoticeItem>) : RecyclerView.Adapter<NoticeAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||||
|
val title: TextView = view.findViewById(R.id.noticeTitle)
|
||||||
|
val date: TextView = view.findViewById(R.id.noticeDate)
|
||||||
|
val content: TextView = view.findViewById(R.id.noticeContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_notice, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val item = notices[position]
|
||||||
|
holder.title.text = item.title
|
||||||
|
holder.date.text = item.date
|
||||||
|
holder.content.text = item.content
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = notices.size
|
||||||
|
}
|
||||||
7
app/src/main/java/com/example/shiftalarm/NoticeItem.kt
Normal file
7
app/src/main/java/com/example/shiftalarm/NoticeItem.kt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
data class NoticeItem(
|
||||||
|
val title: String,
|
||||||
|
val date: String,
|
||||||
|
val content: String
|
||||||
|
)
|
||||||
46
app/src/main/java/com/example/shiftalarm/RootUtil.kt
Normal file
46
app/src/main/java/com/example/shiftalarm/RootUtil.kt
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
object RootUtil {
|
||||||
|
fun isDeviceRooted(): Boolean {
|
||||||
|
return checkRootMethod1() || checkRootMethod2() || checkRootMethod3()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkRootMethod1(): Boolean {
|
||||||
|
val buildTags = android.os.Build.TAGS
|
||||||
|
return buildTags != null && buildTags.contains("test-keys")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkRootMethod2(): Boolean {
|
||||||
|
val paths = arrayOf(
|
||||||
|
"/system/app/Superuser.apk",
|
||||||
|
"/sbin/su",
|
||||||
|
"/system/bin/su",
|
||||||
|
"/system/xbin/su",
|
||||||
|
"/data/local/xbin/su",
|
||||||
|
"/data/local/bin/su",
|
||||||
|
"/system/sd/xbin/su",
|
||||||
|
"/system/bin/failsafe/su",
|
||||||
|
"/data/local/su",
|
||||||
|
"/su/bin/su"
|
||||||
|
)
|
||||||
|
for (path in paths) {
|
||||||
|
if (File(path).exists()) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkRootMethod3(): Boolean {
|
||||||
|
var process: Process? = null
|
||||||
|
return try {
|
||||||
|
process = Runtime.getRuntime().exec(arrayOf("/system/xbin/which", "su"))
|
||||||
|
val reader = process.inputStream.bufferedReader()
|
||||||
|
reader.readLine() != null
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
false
|
||||||
|
} finally {
|
||||||
|
process?.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
app/src/main/java/com/example/shiftalarm/SettingsActivity.kt
Normal file
57
app/src/main/java/com/example/shiftalarm/SettingsActivity.kt
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import com.example.shiftalarm.databinding.ActivitySettingsBinding
|
||||||
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
|
|
||||||
|
class SettingsActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var binding: ActivitySettingsBinding
|
||||||
|
|
||||||
|
override fun onConfigurationChanged(newConfig: android.content.res.Configuration) {
|
||||||
|
super.onConfigurationChanged(newConfig)
|
||||||
|
// Refresh UI smoothly
|
||||||
|
finish()
|
||||||
|
startActivity(intent)
|
||||||
|
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = ActivitySettingsBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
|
||||||
|
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||||
|
insets
|
||||||
|
}
|
||||||
|
|
||||||
|
val adapter = SettingsPagerAdapter(this)
|
||||||
|
binding.viewPager.adapter = adapter
|
||||||
|
|
||||||
|
TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
|
||||||
|
tab.text = when (position) {
|
||||||
|
0 -> getString(R.string.tab_basic)
|
||||||
|
1 -> getString(R.string.tab_alarm)
|
||||||
|
2 -> getString(R.string.tab_additional)
|
||||||
|
3 -> getString(R.string.tab_lab)
|
||||||
|
else -> "설정"
|
||||||
|
}
|
||||||
|
}.attach()
|
||||||
|
|
||||||
|
// Jump to specific tab if requested
|
||||||
|
val targetTab = intent.getIntExtra("TARGET_TAB", 0)
|
||||||
|
binding.viewPager.setCurrentItem(targetTab, false)
|
||||||
|
|
||||||
|
binding.btnSave.text = "닫기"
|
||||||
|
binding.btnSave.setOnClickListener {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
|
|
||||||
|
class SettingsPagerAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {
|
||||||
|
override fun getItemCount(): Int = 4
|
||||||
|
|
||||||
|
override fun createFragment(position: Int): Fragment {
|
||||||
|
return when (position) {
|
||||||
|
0 -> FragmentSettingsBasic()
|
||||||
|
1 -> FragmentSettingsAlarm()
|
||||||
|
2 -> FragmentSettingsAdditional()
|
||||||
|
3 -> FragmentSettingsLab()
|
||||||
|
else -> FragmentSettingsBasic()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 알람 관련 전역 설정 및 유틸리티.
|
||||||
|
*/
|
||||||
|
object ShiftAlarmDefaults {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 마스터 알람 스위치 상태 확인.
|
||||||
|
*/
|
||||||
|
fun isMasterAlarmEnabled(prefs: SharedPreferences): Boolean {
|
||||||
|
// 기본값 TRUE
|
||||||
|
return prefs.getBoolean("master_alarm_enabled", true)
|
||||||
|
}
|
||||||
|
}
|
||||||
89
app/src/main/java/com/example/shiftalarm/ShiftCalculator.kt
Normal file
89
app/src/main/java/com/example/shiftalarm/ShiftCalculator.kt
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
|
|
||||||
|
object ShiftCalculator {
|
||||||
|
val SEOUL_ZONE: ZoneId = ZoneId.of("Asia/Seoul")
|
||||||
|
val BASE_DATE: LocalDate = LocalDate.of(2026, 2, 1)
|
||||||
|
|
||||||
|
// Provided list has 20 items.
|
||||||
|
// "석간 석간 석간 휴 휴" (5)
|
||||||
|
// "주간 주간 주간 주간 주간 휴 휴" (7)
|
||||||
|
// "야간 야간 야간 야간 야간 휴" (6)
|
||||||
|
// "석간 석간" (2)
|
||||||
|
// Total 20.
|
||||||
|
val cycle = listOf(
|
||||||
|
"석간", "석간", "석간", "휴무", "휴무",
|
||||||
|
"주간", "주간", "주간", "주간", "주간", "휴무", "휴무",
|
||||||
|
"야간", "야간", "야간", "야간", "야간", "휴무",
|
||||||
|
"석간", "석간"
|
||||||
|
)
|
||||||
|
|
||||||
|
val CYCLE_LENGTH = cycle.size
|
||||||
|
|
||||||
|
val TEAM_OFFSETS = mapOf(
|
||||||
|
"A" to 0,
|
||||||
|
"B" to 15,
|
||||||
|
"C" to 10,
|
||||||
|
"D" to 5
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getShift(date: LocalDate, team: String, factory: String = "Jeonju"): String {
|
||||||
|
return when (factory) {
|
||||||
|
"Nonsan" -> calculateNonsanShift(date, team)
|
||||||
|
else -> calculateJeonjuShift(date, team)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateJeonjuShift(date: LocalDate, team: String): String {
|
||||||
|
val teamOffset = TEAM_OFFSETS[team] ?: 0
|
||||||
|
val days = ChronoUnit.DAYS.between(BASE_DATE, date).toInt()
|
||||||
|
val index = Math.floorMod(days + teamOffset, CYCLE_LENGTH)
|
||||||
|
return cycle[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateNonsanShift(date: LocalDate, team: String): String {
|
||||||
|
// Nonsan Factory Logic
|
||||||
|
// Mon-Fri: Work, Sat-Sun: Rest (Off) -> "휴무"
|
||||||
|
// Base Date: 2026-02-09 (Monday)
|
||||||
|
// Groups: A, B, C
|
||||||
|
// User Requirement (Step 145):
|
||||||
|
// Feb 9 week: Day (주간)
|
||||||
|
// Feb 16 week: Night (야간)
|
||||||
|
// Feb 23 week: Evening (석간)
|
||||||
|
// Cycle: Day -> Night -> Evening
|
||||||
|
|
||||||
|
val dayOfWeek = date.dayOfWeek.value // 1=Mon, ..., 7=Sun
|
||||||
|
if (dayOfWeek >= 6) return "휴무" // Sat, Sun is OFF
|
||||||
|
|
||||||
|
// Base Date: 2026-02-09 (Monday)
|
||||||
|
val baseDateNonsan = LocalDate.of(2026, 2, 9)
|
||||||
|
|
||||||
|
// Calculate days between Monday of the target date and base date
|
||||||
|
// To be safe for "Any date before", we align target date to its Monday
|
||||||
|
val targetMonday = date.minusDays((dayOfWeek - 1).toLong()) // Align to Monday
|
||||||
|
val daysDiff = ChronoUnit.DAYS.between(baseDateNonsan, targetMonday).toInt()
|
||||||
|
val weeksPassed = daysDiff / 7
|
||||||
|
|
||||||
|
// Rotation Pattern: 주간 -> 야간 -> 석간
|
||||||
|
// Index: 0=주간, 1=야간, 2=석간
|
||||||
|
val rotation = listOf("주간", "야간", "석간")
|
||||||
|
|
||||||
|
// Start indices for 2026-02-09 (Week 0)
|
||||||
|
// A: 주간 (0) -> Matches User Specification
|
||||||
|
// B, C: Distributed to other shifts.
|
||||||
|
// Assuming A=0(Day), B=1(Night), C=2(Evening)
|
||||||
|
|
||||||
|
val startOffset = when (team) {
|
||||||
|
"A" -> 0 // 주간
|
||||||
|
"B" -> 1 // 야간
|
||||||
|
"C" -> 2 // 석간
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentIndex = Math.floorMod(startOffset + weeksPassed, 3)
|
||||||
|
return rotation[currentIndex]
|
||||||
|
}
|
||||||
|
}
|
||||||
60
app/src/main/java/com/example/shiftalarm/ShiftDao.kt
Normal file
60
app/src/main/java/com/example/shiftalarm/ShiftDao.kt
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import androidx.room.*
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface ShiftDao {
|
||||||
|
// Override Queries
|
||||||
|
@Query("SELECT * FROM shift_overrides WHERE factory = :factory AND team = :team AND date = :date")
|
||||||
|
suspend fun getOverride(factory: String, team: String, date: String): ShiftOverride?
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun insertOverride(override: ShiftOverride)
|
||||||
|
|
||||||
|
@Query("DELETE FROM shift_overrides WHERE factory = :factory AND team = :team AND date = :date")
|
||||||
|
suspend fun deleteOverride(factory: String, team: String, date: String)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM shift_overrides WHERE factory = :factory AND team = :team AND date LIKE :month || '%'")
|
||||||
|
suspend fun getOverridesForMonth(factory: String, team: String, month: String): List<ShiftOverride>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM shift_overrides")
|
||||||
|
suspend fun getAllOverrides(): List<ShiftOverride>
|
||||||
|
|
||||||
|
@Query("DELETE FROM shift_overrides")
|
||||||
|
suspend fun clearOverrides()
|
||||||
|
|
||||||
|
// Memo Queries
|
||||||
|
@Query("SELECT * FROM daily_memos WHERE date = :date")
|
||||||
|
suspend fun getMemo(date: String): DailyMemo?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM daily_memos WHERE date LIKE :month || '%'")
|
||||||
|
suspend fun getMemosForMonth(month: String): List<DailyMemo>
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun insertMemo(memo: DailyMemo)
|
||||||
|
|
||||||
|
@Query("DELETE FROM daily_memos WHERE date = :date")
|
||||||
|
suspend fun deleteMemo(date: String)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM daily_memos")
|
||||||
|
suspend fun getAllMemos(): List<DailyMemo>
|
||||||
|
|
||||||
|
@Query("DELETE FROM daily_memos")
|
||||||
|
suspend fun clearMemos()
|
||||||
|
|
||||||
|
// Custom Alarm Queries
|
||||||
|
@Query("SELECT * FROM custom_alarms ORDER BY time ASC")
|
||||||
|
suspend fun getAllCustomAlarms(): List<CustomAlarm>
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun insertCustomAlarm(alarm: CustomAlarm): Long
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun updateCustomAlarm(alarm: CustomAlarm)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun deleteCustomAlarm(alarm: CustomAlarm)
|
||||||
|
|
||||||
|
@Query("DELETE FROM custom_alarms")
|
||||||
|
suspend fun clearCustomAlarms()
|
||||||
|
}
|
||||||
60
app/src/main/java/com/example/shiftalarm/ShiftRepository.kt
Normal file
60
app/src/main/java/com/example/shiftalarm/ShiftRepository.kt
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package com.example.shiftalarm
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import java.time.LocalDate
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class ShiftRepository(private val context: Context) {
|
||||||
|
private val db = AppDatabase.getDatabase(context)
|
||||||
|
private val dao = db.shiftDao()
|
||||||
|
|
||||||
|
suspend fun getShift(date: LocalDate, team: String, factory: String): String = withContext(Dispatchers.IO) {
|
||||||
|
val override = dao.getOverride(factory, team, date.toString())
|
||||||
|
if (override != null) {
|
||||||
|
return@withContext override.shift
|
||||||
|
}
|
||||||
|
ShiftCalculator.getShift(date, team, factory)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setOverride(date: LocalDate, shift: String, team: String, factory: String) {
|
||||||
|
dao.insertOverride(ShiftOverride(factory, team, date.toString(), shift))
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun clearOverride(date: LocalDate, team: String, factory: String) {
|
||||||
|
dao.deleteOverride(factory, team, date.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getMemo(date: LocalDate): String? {
|
||||||
|
return dao.getMemo(date.toString())?.content
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setMemo(date: LocalDate, content: String) {
|
||||||
|
if (content.isEmpty()) {
|
||||||
|
dao.deleteMemo(date.toString())
|
||||||
|
} else {
|
||||||
|
dao.insertMemo(DailyMemo(date.toString(), content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom Alarms
|
||||||
|
suspend fun getAllCustomAlarms(): List<CustomAlarm> = withContext(Dispatchers.IO) {
|
||||||
|
dao.getAllCustomAlarms()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun addCustomAlarm(alarm: CustomAlarm): Long = withContext(Dispatchers.IO) {
|
||||||
|
dao.insertCustomAlarm(alarm)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateCustomAlarm(alarm: CustomAlarm) = withContext(Dispatchers.IO) {
|
||||||
|
dao.updateCustomAlarm(alarm)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun deleteCustomAlarm(alarm: CustomAlarm) = withContext(Dispatchers.IO) {
|
||||||
|
dao.deleteCustomAlarm(alarm)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun clearAllCustomAlarms() = withContext(Dispatchers.IO) {
|
||||||
|
dao.clearCustomAlarms()
|
||||||
|
}
|
||||||
|
}
|
||||||
5
app/src/main/res/color/sl_switch_track.xml
Normal file
5
app/src/main/res/color/sl_switch_track.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_checked="true" android:color="@color/primary" />
|
||||||
|
<item android:color="#20000000" /> <!-- Semi-transparent black instead of opaque gray -->
|
||||||
|
</selector>
|
||||||
12
app/src/main/res/drawable/bg_alarm_gradient.xml
Normal file
12
app/src/main/res/drawable/bg_alarm_gradient.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<gradient
|
||||||
|
android:type="radial"
|
||||||
|
android:centerX="30%"
|
||||||
|
android:centerY="20%"
|
||||||
|
android:gradientRadius="800dp"
|
||||||
|
android:startColor="#6c4bb5"
|
||||||
|
android:centerColor="#120b2d"
|
||||||
|
android:endColor="#000000" />
|
||||||
|
</shape>
|
||||||
13
app/src/main/res/drawable/bg_btn_today_themed.xml
Normal file
13
app/src/main/res/drawable/bg_btn_today_themed.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="#20000000">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="@android:color/transparent" />
|
||||||
|
<stroke
|
||||||
|
android:width="0.8dp"
|
||||||
|
android:color="@color/btn_today_text" />
|
||||||
|
<corners android:radius="6dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
14
app/src/main/res/drawable/bg_button_one_ui.xml
Normal file
14
app/src/main/res/drawable/bg_button_one_ui.xml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="#20007AFF">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="#0D007AFF" />
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="#1A007AFF" />
|
||||||
|
<corners android:radius="32dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
|
|
||||||
6
app/src/main/res/drawable/bg_card.xml
Normal file
6
app/src/main/res/drawable/bg_card.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="#FFFFFF"/>
|
||||||
|
<corners android:radius="24dp"/>
|
||||||
|
<stroke android:width="0dp" android:color="#00000000"/>
|
||||||
|
</shape>
|
||||||
8
app/src/main/res/drawable/bg_circle_shift.xml
Normal file
8
app/src/main/res/drawable/bg_circle_shift.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="@color/white" />
|
||||||
|
<size
|
||||||
|
android:width="44dp"
|
||||||
|
android:height="44dp" />
|
||||||
|
</shape>
|
||||||
5
app/src/main/res/drawable/bg_circle_snooze.xml
Normal file
5
app/src/main/res/drawable/bg_circle_snooze.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="#007AFF"/>
|
||||||
|
</shape>
|
||||||
4
app/src/main/res/drawable/bg_circle_snooze_v2.xml
Normal file
4
app/src/main/res/drawable/bg_circle_snooze_v2.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="#007AFF"/>
|
||||||
|
</shape>
|
||||||
5
app/src/main/res/drawable/bg_circle_stop.xml
Normal file
5
app/src/main/res/drawable/bg_circle_stop.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="#FF3B30"/>
|
||||||
|
</shape>
|
||||||
4
app/src/main/res/drawable/bg_circle_stop_v2.xml
Normal file
4
app/src/main/res/drawable/bg_circle_stop_v2.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="#FF3B30"/>
|
||||||
|
</shape>
|
||||||
6
app/src/main/res/drawable/bg_day_cell.xml
Normal file
6
app/src/main/res/drawable/bg_day_cell.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="@color/glass_panel_bg"/>
|
||||||
|
<corners android:radius="8dp"/>
|
||||||
|
<stroke android:width="1dp" android:color="@color/glass_panel_stroke"/>
|
||||||
|
</shape>
|
||||||
6
app/src/main/res/drawable/bg_day_cell_today.xml
Normal file
6
app/src/main/res/drawable/bg_day_cell_today.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="#E7F1FF"/>
|
||||||
|
<corners android:radius="8dp"/>
|
||||||
|
<stroke android:width="2dp" android:color="#0D6EFD"/>
|
||||||
|
</shape>
|
||||||
7
app/src/main/res/drawable/bg_day_glass.xml
Normal file
7
app/src/main/res/drawable/bg_day_glass.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="@color/glass_panel_bg"/>
|
||||||
|
<corners android:radius="12dp"/>
|
||||||
|
<stroke android:width="1dp" android:color="@color/glass_panel_stroke"/>
|
||||||
|
<gradient android:startColor="#10FFFFFF" android:endColor="#05FFFFFF" android:angle="45" />
|
||||||
|
</shape>
|
||||||
17
app/src/main/res/drawable/bg_day_today.xml
Normal file
17
app/src/main/res/drawable/bg_day_today.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
|
||||||
|
<!-- Today's date highlight: Primary color tint with stronger glass effect -->
|
||||||
|
<gradient
|
||||||
|
android:startColor="#400D6EFD"
|
||||||
|
android:endColor="#200D6EFD"
|
||||||
|
android:angle="135"/>
|
||||||
|
|
||||||
|
<corners android:radius="10dp"/>
|
||||||
|
|
||||||
|
<stroke
|
||||||
|
android:width="2dp"
|
||||||
|
android:color="#800D6EFD"/>
|
||||||
|
|
||||||
|
</shape>
|
||||||
7
app/src/main/res/drawable/bg_dialog_glass.xml
Normal file
7
app/src/main/res/drawable/bg_dialog_glass.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- Dialog background with more rounded corners for One UI 8 -->
|
||||||
|
<solid android:color="@color/dialog_bg"/>
|
||||||
|
<corners android:radius="28dp"/>
|
||||||
|
<stroke android:width="1dp" android:color="@color/glass_panel_stroke"/>
|
||||||
|
</shape>
|
||||||
9
app/src/main/res/drawable/bg_dialog_header.xml
Normal file
9
app/src/main/res/drawable/bg_dialog_header.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<gradient
|
||||||
|
android:angle="135"
|
||||||
|
android:startColor="#800381FE"
|
||||||
|
android:endColor="#805856D6"
|
||||||
|
android:type="linear"/>
|
||||||
|
<corners android:topLeftRadius="28dp" android:topRightRadius="28dp"/>
|
||||||
|
</shape>
|
||||||
6
app/src/main/res/drawable/bg_dialog_solid_v4.xml
Normal file
6
app/src/main/res/drawable/bg_dialog_solid_v4.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="@color/dialog_bg"/>
|
||||||
|
<corners android:radius="32dp"/>
|
||||||
|
<stroke android:width="1dp" android:color="@color/grid_divider"/>
|
||||||
|
</shape>
|
||||||
6
app/src/main/res/drawable/bg_dismiss_circle_v4.xml
Normal file
6
app/src/main/res/drawable/bg_dismiss_circle_v4.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="#0c091c" />
|
||||||
|
<stroke android:width="6dp" android:color="#998C6EFF" />
|
||||||
|
</shape>
|
||||||
19
app/src/main/res/drawable/bg_ghost_button.xml
Normal file
19
app/src/main/res/drawable/bg_ghost_button.xml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- 눌렸을 때 -->
|
||||||
|
<item android:state_pressed="true">
|
||||||
|
<shape>
|
||||||
|
<solid android:color="#30FFFFFF"/>
|
||||||
|
<corners android:radius="28dp"/>
|
||||||
|
<stroke android:width="1dp" android:color="#50FFFFFF"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<!-- 기본 -->
|
||||||
|
<item>
|
||||||
|
<shape>
|
||||||
|
<solid android:color="#00FFFFFF"/>
|
||||||
|
<corners android:radius="28dp"/>
|
||||||
|
<stroke android:width="0.5dp" android:color="#20FFFFFF"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
6
app/src/main/res/drawable/bg_glass_button.xml
Normal file
6
app/src/main/res/drawable/bg_glass_button.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="#40FFFFFF"/>
|
||||||
|
<corners android:radius="28dp"/>
|
||||||
|
<stroke android:width="1dp" android:color="#30FFFFFF"/>
|
||||||
|
</shape>
|
||||||
6
app/src/main/res/drawable/bg_glass_button_accent.xml
Normal file
6
app/src/main/res/drawable/bg_glass_button_accent.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="#605C6BC0"/>
|
||||||
|
<corners android:radius="28dp"/>
|
||||||
|
<stroke android:width="1dp" android:color="#40FFFFFF"/>
|
||||||
|
</shape>
|
||||||
6
app/src/main/res/drawable/bg_glass_button_light.xml
Normal file
6
app/src/main/res/drawable/bg_glass_button_light.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="@color/button_glass_bg"/>
|
||||||
|
<corners android:radius="28dp"/>
|
||||||
|
<stroke android:width="1dp" android:color="@color/glass_panel_stroke"/>
|
||||||
|
</shape>
|
||||||
6
app/src/main/res/drawable/bg_glass_card.xml
Normal file
6
app/src/main/res/drawable/bg_glass_card.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="#E6FFFFFF"/>
|
||||||
|
<corners android:radius="20dp"/>
|
||||||
|
<stroke android:width="1dp" android:color="#20000000"/>
|
||||||
|
</shape>
|
||||||
6
app/src/main/res/drawable/bg_glass_card_dark.xml
Normal file
6
app/src/main/res/drawable/bg_glass_card_dark.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="#30FFFFFF"/>
|
||||||
|
<corners android:radius="24dp"/>
|
||||||
|
<stroke android:width="1dp" android:color="#25FFFFFF"/>
|
||||||
|
</shape>
|
||||||
6
app/src/main/res/drawable/bg_glass_circle.xml
Normal file
6
app/src/main/res/drawable/bg_glass_circle.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="#33FFFFFF"/>
|
||||||
|
<stroke android:width="1dp" android:color="#4DFFFFFF"/>
|
||||||
|
</shape>
|
||||||
6
app/src/main/res/drawable/bg_glass_panel_v2.xml
Normal file
6
app/src/main/res/drawable/bg_glass_panel_v2.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="@color/glass_panel_bg"/>
|
||||||
|
<corners android:radius="16dp"/>
|
||||||
|
<stroke android:width="1dp" android:color="@color/glass_panel_stroke"/>
|
||||||
|
</shape>
|
||||||
7
app/src/main/res/drawable/bg_glass_panel_v3.xml
Normal file
7
app/src/main/res/drawable/bg_glass_panel_v3.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="@color/glass_panel_bg"/>
|
||||||
|
<corners android:radius="32dp"/>
|
||||||
|
<stroke android:width="0.8dp" android:color="@color/glass_panel_stroke"/>
|
||||||
|
</shape>
|
||||||
|
|
||||||
14
app/src/main/res/drawable/bg_glass_pill.xml
Normal file
14
app/src/main/res/drawable/bg_glass_pill.xml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<!-- Glass Element: 20% Translucent White -->
|
||||||
|
<solid android:color="#33FFFFFF" />
|
||||||
|
|
||||||
|
<!-- Full Pill Shape -->
|
||||||
|
<corners android:radius="100dp" />
|
||||||
|
|
||||||
|
<!-- Subtle Border: 30% Translucent White -->
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="#4DFFFFFF" />
|
||||||
|
</shape>
|
||||||
7
app/src/main/res/drawable/bg_glass_pill_v4.xml
Normal file
7
app/src/main/res/drawable/bg_glass_pill_v4.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners android:radius="44dp" />
|
||||||
|
<solid android:color="#26FFFFFF" />
|
||||||
|
<stroke android:width="1.8dp" android:color="#4DFFFFFF" />
|
||||||
|
</shape>
|
||||||
9
app/src/main/res/drawable/bg_glass_save_button.xml
Normal file
9
app/src/main/res/drawable/bg_glass_save_button.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<gradient
|
||||||
|
android:startColor="#7C4DFF"
|
||||||
|
android:endColor="#5C6BC0"
|
||||||
|
android:angle="135"/>
|
||||||
|
<corners android:radius="16dp"/>
|
||||||
|
<stroke android:width="1dp" android:color="#40FFFFFF"/>
|
||||||
|
</shape>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user