Skip to content

Commit 3572e9b

Browse files
authored
[Feat/#5] ban app list (#6)
* [setting#5] 앱 구동 및 포그라운드 서비스를 위한 Android 권한 설정 추가 - `AndroidManifest.xml`에 앱 사용 기록 접근(`PACKAGE_USAGE_STATS`), 시스템 오버레이(`SYSTEM_ALERT_WINDOW`), 배터리 최적화 제외(`REQUEST_IGNORE_BATTERY_OPTIMIZATIONS`) 권한을 추가하였습니다. - 알림 표시(`POST_NOTIFICATIONS`) 및 포그라운드 서비스(`FOREGROUND_SERVICE`, `FOREGROUND_SERVICE_SPECIAL_USE`) 관련 권한을 추가하였습니다. - 인터넷 연결(`INTERNET`) 및 전체 패키지 조회(`QUERY_ALL_PACKAGES`) 권한을 설정하였습니다. * [Feat#5] UrlImage 공통 컴포넌트 추가 및 Coil 라이브러리 의존성 추가 - `core:designsystem` 모듈에 URL을 통해 이미지를 로드하는 공통 컴포넌트인 `UrlImage`를 추가하였습니다. - `UrlImage` 컴포넌트 구현을 위해 `coil-compose` 의존성을 `build.gradle.kts`에 추가하고 `AsyncImage`를 활용하도록 개발하였습니다. - Compose Preview 환경에서 이미지 영역을 확인할 수 있도록 `LocalInspectionMode`일 때 표시될 플레이스홀더 이미지(`img_fake_red.xml`)를 추가하였습니다. - `UrlImage`의 기본 `ContentScale` 설정을 `Fit`으로 지정하고, 외부에서 `Modifier` 및 설명을 주입받을 수 있도록 구성하였습니다. * [feat#5] core:permission 모듈 추가 및 권한 관리 로직 구현 - `core:permission` 모듈을 신규 생성하고 관련 빌드 설정(`build.gradle.kts`) 및 `AndroidManifest.xml`을 추가하였습니다. - 권한 상태 확인 및 설정 페이지 이동을 담당하는 `PermissionManager` 인터페이스와 그 구현체인 `PermissionManagerImpl`을 작성하였습니다. - `PermissionType` 열거형을 통해 USAGE_STATS, OVERLAY, BATTERY_OPTIMIZATION, POST_NOTIFICATIONS 등 관리 대상을 정의하였습니다. - 각 권한별 실제 부여 여부를 체크하는 `PermissionChecker` 로직(AppOpsManager, Settings.canDrawOverlays 등 활용)을 구현하였습니다. - 권한 관련 비즈니스 로직 처리를 위한 UseCase(`AreAllFocusPermissionsGrantedUseCase`, `CheckFocusPermissionsUseCase`)를 추가하였습니다. - Hilt를 사용하여 `PermissionManager` 의존성 주입을 위한 `PermissionModule`을 구성하였습니다. * [Add#5] core:service 및 core:work 모듈 추가 - `core:service` 모듈을 생성하고 `AndroidManifest.xml`, `build.gradle.kts`, `.gitignore` 등 기본 설정을 완료하였습니다. - `core:service` 모듈에 `android-library` 및 `android-compose` 컨벤션 플러그인을 적용하였습니다. - `core:work` 모듈을 생성하고 `AndroidManifest.xml`, `build.gradle.kts`, `.gitignore` 등 기본 설정을 완료하였습니다. - `core:work` 모듈에 `android-library` 및 `hilt` 컨벤션 플러그인을 적용하였습니다. - 각 모듈의 네임스페이스를 `core.service` 및 `core.work`로 설정하였습니다. * [Feat#5] data:forbidden 모듈 추가 및 설치된 앱 목록 조회 기능 구현 - `data:forbidden` 모듈을 신규 생성하고 `build.gradle.kts` 및 기본 프로젝트 설정을 완료하였습니다. - `ForbiddenAppRepositoryImpl`을 구현하여 기기에 설치된 사용자 앱 목록을 가져오는 기능을 추가하였습니다. - `PackageManager`를 사용하여 `CATEGORY_LAUNCHER` 속성을 가진 앱들을 필터링하고 이름순으로 정렬하도록 구현하였습니다. - Hilt를 이용한 의존성 주입을 위해 `ForbiddenRepositoryModule`을 추가하고 `ForbiddenAppRepository`를 바인딩하였습니다. - `domain:forbidden` 모듈에 대한 의존성을 추가하였습니다. * [Feat#5] `domain:forbidden` 모듈 생성 및 설치된 앱 조회 기능 구현 - `presentation/build.gradle.kts`를 `domain/forbidden/build.gradle.kts`로 이동하고, `kindl.kotlin.jvm` 컨벤션 플러그인 및 `javax.inject` 의존성을 적용하였습니다. - 설치된 앱 정보를 담는 `InstalledAppModel` 데이터 클래스를 추가하였습니다. - 앱 목록 조회를 위한 `ForbiddenAppRepository` 인터페이스를 정의하였습니다. - 의존성 주입(`@Inject`)을 지원하는 `GetInstalledAppsUseCase`를 구현하여 비즈니스 로직을 추가하였습니다. - `domain/forbidden` 모듈에 대한 `.gitignore` 설정을 추가하였습니다. * [Feat#5] presentation:home 모듈 추가 및 메인 화면 네비게이션/권한 로직 구현 - `presentation:home` 모듈을 신규 생성하고 기본 빌드 설정 및 ProGuard 규칙을 추가하였습니다. - `presentation:main` 모듈에 `MainBottomBar` 컴포넌트를 추가하고 `MainTab`에 홈 항목을 정의하였습니다. - `MainViewModel`, `MainUiState`, `MainSideEffect`를 추가하여 권한 체크 및 설정 이동 로직을 구현하였습니다. - `MainScreen`에서 `Scaffold`와 `NavHost`를 활용하여 바텀바 표시 및 화면 전환 애니메이션(Slide) 설정을 추가하였습니다. - `MainAppState`에 기본 시작 목적지(`Splash`)를 설정하고, `core:permission` 의존성을 추가하였습니다. * [Chore#5] 프로젝트 모듈 구조 확장 및 신규 모듈 추가 - `settings.gradle.kts` 내 모듈 정의 방식을 가독성 향상을 위해 그룹화된 `include` 형식으로 개선하였습니다. - `core` 레이어에 `:core:permission`, `:core:service`, `:core:work` 모듈을 추가하였습니다. - `data` 및 `domain` 레이어를 계층화하고 `:data:forbidden`, `:domain:forbidden` 모듈을 각각 추가하였습니다. - `presentation` 레이어에 `:presentation:home` 모듈을 신규 추가하였습니다. * [Feat#5] core:localstorage 모듈 추가 및 권한 정보 관리 기능 구현 - `core:localstorage` 모듈을 신규 생성하고 DataStore 의존성 및 기본 빌드 설정을 추가하였습니다. - `DataStoreConstant`를 정의하여 데이터스토어 이름과 알림 거절 횟수 기록을 위한 키(`notification_denied_count`)를 추가하였습니다. - `PermissionInfoManager` 인터페이스와 `PermissionInfoManagerImpl` 구현체를 추가하여 알림 거절 횟수 저장 및 조회 로직을 구현하였습니다. - Hilt를 이용한 의존성 주입을 위해 `LocalStorageModule`을 작성하고 `DataStore<Preferences>` 및 `PermissionInfoManager` 인스턴스 제공 설정을 완료하였습니다. * [Refactor#5] `domain:forbidden` 모듈 내 불변 컬렉션 적용 및 UseCase 반환 타입 변경 - `domain:forbidden` 모듈의 `build.gradle.kts`에 `kotlinx.immutable` 라이브러리 의존성을 추가하였습니다. - `GetInstalledAppsUseCase`의 반환 타입을 `List`에서 `ImmutableList`로 변경하여 데이터의 불변성을 보장하고 안정성을 개선하였습니다. * [Feat#5] 홈 화면 구현 및 설치된 앱 목록 표시 기능 추가 - `presentation:home` 모듈에 `HomeScreen` 및 `HomeRoute`를 추가하고 `LazyColumn`을 사용하여 앱 목록 레이아웃을 구성하였습니다. - `HomeViewModel`과 `HomeState`를 구현하여 `GetInstalledAppsUseCase`로부터 데이터를 가져오고 UI 상태를 관리하는 로직을 추가하였습니다. - `ForbiddenAppUiModel` 및 `ForbiddenAppItem` 컴포넌트를 추가하여 각 앱 항목의 아이콘과 이름을 표시하도록 구현하였습니다. - `HomeNavigation`을 정의하여 `NavGraph` 등록 및 화면 이동을 위한 `navigateToHome` 함수를 추가하였습니다. - `domain:forbidden` 모듈에 대한 의존성을 추가하였습니다. * [Feat#5] 권한 관리 화면 구현 및 메인 네비게이션 연동 - `PermissionScreen`을 추가하여 필수 권한(사용 정보, 다른 앱 위에 표시, 배터리 최적화, 알림) 요청 및 설정 이동 UI를 구현하였습니다. - `MainViewModel`에 `PermissionInfoManager`를 통합하여 알림 권한 거부 횟수 및 영구 거절 상태 관리 로직을 추가하였습니다. - `MainScreen`에서 권한 미부여 시 `PermissionScreen`을 노출하도록 제어하고, 권한 종류 및 상태에 따른 요청 로직(런처 사용 또는 설정 앱 이동)을 세분화하였습니다. - `MainAppState`의 시작 목적지를 `Home`으로 변경하고, `MainTab` 클릭 시 실제 화면 이동이 동작하도록 `NavHost` 설정을 업데이트하였습니다. - `MainBottomBar`와 `MainTab`의 리소스 참조 방식을 개선하고, `presentation:home` 및 `core:localstorage` 의존성을 추가하였습니다. * [Refactor#5] `UrlImage` 컴포넌트 유연성 개선 및 모듈 의존성 추가 - `UrlImage` 컴포넌트의 `url` 매개변수 타입을 `String`에서 `Any?`로 변경하여 다양한 이미지 소스 타입을 지원하도록 개선하였습니다. - `app` 모듈의 `build.gradle.kts`에 `data:forbidden` 모듈 의존성을 추가하였습니다. * [Chore#5] 코드 스타일 개선 및 불필요한 프로젝트 파일 정리 - `PermissionManagerImpl`에서 불필요한 `@Singleton` 어노테이션을 제거하고 미사용 임포트를 정리하였습니다. - `ForbiddenRepositoryModule`의 코드 가독성 향상을 위해 공백을 추가하였습니다. - `data` 및 `domain` 디렉토리 내의 불필요한 `.gitkeep` 파일을 삭제하였습니다. * [Refactor#5] `ForbiddenAppUiModel` 구조 개선 및 앱 아이콘 로드 로직 수정 - `ForbiddenAppUiModel`에서 `appIcon` 필드를 `icon: Drawable?`로 변경하고, 고유 식별을 위한 `appPackageName` 필드를 추가하였습니다. - `HomeViewModel`에서 `PackageManager`를 사용하여 설치된 앱의 실제 아이콘을 로드하는 비동기 로직(`loadApps`)을 구현하였습니다. - `HomeScreen`의 `LazyColumn`에서 `appPackageName`을 키(key)로 사용하고 `contentType`을 정의하여 리스트 렌더링 성능을 최적화하였습니다. - `ForbiddenAppUiModel`의 어노테이션을 `@Immutable`에서 `@Stable`로 변경하였습니다. - `presentation:main` 모듈의 패키지 경로 누락 수정 및 불필요한 import 구문을 정리하였습니다. * [Feat#5] 접근성 권한 추가 및 권한 관리 로직 개선 - `PermissionType`에 `ACCESSIBILITY`를 추가하고, `POST_NOTIFICATIONS`의 필수 여부(`isFocusRequired`)를 `false`로 변경하였습니다. - `PermissionChecker`에 `Accessibility` 객체를 추가하여 앱의 접근성 서비스 활성화 여부를 확인하는 로직을 구현하였습니다. - `PermissionManager` 인터페이스의 `openSettings` 반환 타입을 `Intent?`로 변경하고, `PermissionManagerImpl`에서 접근성 설정 화면으로 이동하는 인텐트 처리 로직을 추가하였습니다. - `PermissionChecker.allGranted` 컴패니언 함수를 추가하여 모든 권한의 부여 상태를 한 번에 확인할 수 있도록 개선하였습니다. - `PermissionChecker` 내 불필요한 어노테이션(`@SuppressLint`, `@Suppress`)을 제거하고 코드를 정리하였습니다. * [Feat#5] 접근 권한 추가 및 데이터 레이어 접근 제어자 수정 - `PermissionScreen`에 `ACCESSIBILITY` 권한 타입을 추가하고, 권한 요청을 위한 UI 문구(접근 권한)를 정의하였습니다. - `ForbiddenAppRepositoryImpl` 및 `ForbiddenRepositoryModule`에 `internal` 접근 제어자를 적용하여 모듈 내 캡슐화를 강화하였습니다. - `MainSideEffect.OpenSettings`의 `intent` 매개변수 타입을 `Intent?`로 변경하여 Null 허용이 가능하도록 수정하였습니다.
1 parent e185f90 commit 3572e9b

62 files changed

Lines changed: 1283 additions & 32 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.idea/compiler.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/gradle.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/misc.xml

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ dependencies {
5151
// core
5252
implementation(projects.core.network)
5353

54+
// data
55+
implementation(projects.data.forbidden)
56+
5457
// presentation
5558
implementation(projects.presentation.main)
5659

app/src/main/AndroidManifest.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
33
xmlns:tools="http://schemas.android.com/tools">
44

5+
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
6+
tools:ignore="ProtectedPermissions" />
7+
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
8+
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
9+
10+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
11+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"
12+
tools:ignore="ForegroundServicesPolicy" />
13+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
14+
15+
<uses-permission android:name="android.permission.INTERNET"/>
16+
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
17+
tools:ignore="PackageVisibilityPolicy,QueryAllPackagesPermission" />
18+
519
<application
620
android:name="com.kindl.KindlApplication"
721
android:allowBackup="true"

core/designsystem/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@ plugins {
88
android {
99
setNamespace("core.designsystem")
1010
}
11+
12+
dependencies {
13+
implementation(libs.coil.compose)
14+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.kindl.core.designsystem.component
2+
3+
import androidx.compose.foundation.Image
4+
import androidx.compose.foundation.layout.size
5+
import androidx.compose.runtime.Composable
6+
import androidx.compose.ui.Modifier
7+
import androidx.compose.ui.graphics.vector.ImageVector
8+
import androidx.compose.ui.layout.ContentScale
9+
import androidx.compose.ui.platform.LocalInspectionMode
10+
import androidx.compose.ui.res.vectorResource
11+
import androidx.compose.ui.tooling.preview.Preview
12+
import androidx.compose.ui.unit.dp
13+
import coil.compose.AsyncImage
14+
import com.kindl.core.designsystem.R
15+
16+
@Composable
17+
fun UrlImage(
18+
url: Any?,
19+
modifier: Modifier = Modifier,
20+
contentScale: ContentScale = ContentScale.Fit,
21+
contentDescription: String? = null,
22+
) {
23+
if (LocalInspectionMode.current) {
24+
Image(
25+
imageVector = ImageVector.vectorResource(R.drawable.img_fake_red),
26+
contentDescription = contentDescription,
27+
contentScale = contentScale,
28+
modifier = modifier
29+
)
30+
} else {
31+
AsyncImage(
32+
model = url,
33+
contentDescription = contentDescription,
34+
contentScale = contentScale,
35+
modifier = modifier
36+
)
37+
}
38+
}
39+
40+
@Preview
41+
@Composable
42+
fun UrlImagePreview() {
43+
UrlImage(
44+
url = "",
45+
modifier = Modifier.size(100.dp),
46+
)
47+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:width="100dp"
4+
android:height="100dp"
5+
android:viewportWidth="100"
6+
android:viewportHeight="100">
7+
<path
8+
android:pathData="M0,0h100v100h-100z"
9+
android:fillColor="#FF3737"/>
10+
</vector>

core/localstorage/build.gradle.kts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import com.kindl.buildlogic.setNamespace
2+
3+
plugins {
4+
alias(libs.plugins.kindl.android.library)
5+
alias(libs.plugins.kindl.hilt)
6+
alias(libs.plugins.kindl.serialization)
7+
}
8+
9+
android {
10+
setNamespace("core.localstorage")
11+
}
12+
13+
dependencies {
14+
//core
15+
implementation(projects.core.common)
16+
17+
implementation(libs.javax.inject)
18+
implementation(libs.androidx.datastore.preferences)
19+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.focpet.core.localstorage.constant
2+
3+
import androidx.datastore.preferences.core.intPreferencesKey
4+
5+
object DataStoreConstant {
6+
const val DATA_STORE_NAME = "kindl_datastore"
7+
val KEY_NOTIFICATION_DENIED_COUNT = intPreferencesKey("notification_denied_count")
8+
}

0 commit comments

Comments
 (0)