Initial commit: HotDeal Alarm Android App
Features: - Multi-site hot deal scraping (Ppomppu, Clien, Ruriweb, Coolenjoy) - Site filter with color-coded badges - Board display names (e.g., ppomppu8 -> 알리뽐뿌) - Anti-bot protection with request delays and User-Agent rotation - Keyword matching and notifications - Material Design 3 UI
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
package com.hotdeal.alarm.service
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.work.ForegroundInfo
|
||||
import com.hotdeal.alarm.R
|
||||
import com.hotdeal.alarm.domain.model.HotDeal
|
||||
import com.hotdeal.alarm.presentation.main.MainActivity
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* 알림 서비스
|
||||
*/
|
||||
@Singleton
|
||||
class NotificationService @Inject constructor(
|
||||
@ApplicationContext private val context: Context
|
||||
) {
|
||||
companion object {
|
||||
const val CHANNEL_URGENT = "hotdeal_urgent"
|
||||
const val CHANNEL_NORMAL = "hotdeal_normal"
|
||||
const val CHANNEL_BACKGROUND = "hotdeal_background"
|
||||
|
||||
const val NOTIFICATION_ID_FOREGROUND = 1000
|
||||
const val NOTIFICATION_ID_NEW_DEAL = 1001
|
||||
const val NOTIFICATION_ID_KEYWORD = 1002
|
||||
}
|
||||
|
||||
private val notificationManager = NotificationManagerCompat.from(context)
|
||||
|
||||
init {
|
||||
createNotificationChannels()
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림 채널 생성
|
||||
*/
|
||||
private fun createNotificationChannels() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channels = listOf(
|
||||
NotificationChannel(
|
||||
CHANNEL_URGENT,
|
||||
context.getString(R.string.notification_channel_urgent),
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
).apply {
|
||||
description = context.getString(R.string.notification_channel_urgent_desc)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_NORMAL,
|
||||
context.getString(R.string.notification_channel_normal),
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
).apply {
|
||||
description = context.getString(R.string.notification_channel_normal_desc)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_BACKGROUND,
|
||||
context.getString(R.string.notification_channel_background),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
description = context.getString(R.string.notification_channel_background_desc)
|
||||
}
|
||||
)
|
||||
|
||||
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
manager.createNotificationChannels(channels)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 포그라운드 서비스용 Notification 생성
|
||||
*/
|
||||
fun createForegroundInfo(): ForegroundInfo {
|
||||
val notification = NotificationCompat.Builder(context, CHANNEL_BACKGROUND)
|
||||
.setContentTitle("핫딜 모니터링 중")
|
||||
.setContentText("새로운 핫딜을 확인하고 있습니다")
|
||||
.setSmallIcon(android.R.drawable.ic_menu_search)
|
||||
.setOngoing(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.build()
|
||||
|
||||
return ForegroundInfo(NOTIFICATION_ID_FOREGROUND, notification)
|
||||
}
|
||||
|
||||
/**
|
||||
* 새 핫딜 알림
|
||||
*/
|
||||
fun showNewDealsNotification(deals: List<HotDeal>) {
|
||||
if (!hasNotificationPermission()) return
|
||||
|
||||
val intent = Intent(context, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
}
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
context, 0, intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val notification = if (deals.size == 1) {
|
||||
NotificationCompat.Builder(context, CHANNEL_NORMAL)
|
||||
.setContentTitle(context.getString(R.string.notification_new_deal))
|
||||
.setContentText(deals.first().title)
|
||||
.setSmallIcon(android.R.drawable.ic_menu_send)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
.build()
|
||||
} else {
|
||||
NotificationCompat.Builder(context, CHANNEL_NORMAL)
|
||||
.setContentTitle(context.getString(R.string.notification_new_deal))
|
||||
.setContentText("새로운 핫딜 ${deals.size}개")
|
||||
.setSmallIcon(android.R.drawable.ic_menu_send)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
.setStyle(
|
||||
NotificationCompat.BigTextStyle()
|
||||
.bigText(deals.take(5).joinToString("\n") { "• ${it.title}" })
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
notificationManager.notify(NOTIFICATION_ID_NEW_DEAL, notification)
|
||||
}
|
||||
|
||||
/**
|
||||
* 키워드 매칭 알림 (긴급)
|
||||
*/
|
||||
fun showKeywordMatchNotification(deals: List<HotDeal>) {
|
||||
if (!hasNotificationPermission()) return
|
||||
|
||||
val intent = Intent(context, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
}
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
context, 0, intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val notification = NotificationCompat.Builder(context, CHANNEL_URGENT)
|
||||
.setContentTitle(context.getString(R.string.notification_keyword_match))
|
||||
.setContentText(deals.first().title)
|
||||
.setSmallIcon(android.R.drawable.ic_menu_send)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setStyle(
|
||||
NotificationCompat.BigTextStyle()
|
||||
.bigText(deals.take(5).joinToString("\n") { "• ${it.title}" })
|
||||
)
|
||||
.build()
|
||||
|
||||
notificationManager.notify(NOTIFICATION_ID_KEYWORD, notification)
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림 권한 확인
|
||||
*/
|
||||
private fun hasNotificationPermission(): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
context.checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS) ==
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림 채널 활성화 여부 확인
|
||||
*/
|
||||
fun isNotificationChannelEnabled(channelId: String): Boolean {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = notificationManager.getNotificationChannel(channelId)
|
||||
return channel?.importance != NotificationManager.IMPORTANCE_NONE
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 알림 채널이 활성화되어 있는지 확인
|
||||
*/
|
||||
fun areAllChannelsEnabled(): Boolean {
|
||||
return isNotificationChannelEnabled(CHANNEL_URGENT) &&
|
||||
isNotificationChannelEnabled(CHANNEL_NORMAL)
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림 권한이 있는지 확인 (public)
|
||||
*/
|
||||
fun checkNotificationPermission(): Boolean {
|
||||
return hasNotificationPermission()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user