Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT/#133] 알림 화면 연결 #142

Merged
merged 16 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,15 @@
<activity
android:name=".presentation.signin.SocialWebViewActivity"
android:exported="false" />
<activity
android:name=".presentation.onboarding.AccessLocationActivity"
android:exported="false" />
<activity
android:name=".presentation.signup.terms.TermsWebViewActivity"
android:exported="false" />
<activity
android:name=".presentation.group.join.JoinFriendListActivity"
android:exported="false" />
<activity
android:name=".presentation.notification.AlertsListActivity"
android:exported="false" />

<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.google.firebase.messaging.RemoteMessage
import com.teumteum.data.model.request.toDeviceToken
import com.teumteum.data.service.UserService
import com.teumteum.domain.TeumTeumDataStore
import com.teumteum.domain.entity.Message
import com.teumteum.teumteum.R
import com.teumteum.teumteum.presentation.splash.SplashActivity
import dagger.hilt.android.AndroidEntryPoint
Expand Down Expand Up @@ -52,16 +53,19 @@ class TeumMessagingService : FirebaseMessagingService() {
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)

if (dataStore.isLogin) {
if (message.data.isNotEmpty() && message.data["title"].toString() != EMPTY) {
sendNotificationAlarm(
Message(message.data["title"].toString(), message.data["content"].toString())
)
}
else {
message.notification?.let {
sendNotificationAlarm(Message(it.title.toString(), it.body.toString()))
if (dataStore.onNotification) {
if (dataStore.isLogin) {
val alertMessage = Message("", "", "")
if (message.data.isNotEmpty()) {
alertMessage.title = message.notification?.title.toString()
alertMessage.body = message.notification?.body.toString()
alertMessage.type = message.data["type"].toString()
}
if (alertMessage.type == END_MEETING) {
alertMessage.meetingId = message.data["meetingId"]?.toInt()
alertMessage.participants = message.data["participants"]?.toList()?.map { it.digitToInt() }
}
if (alertMessage.title.isNotEmpty()) sendNotificationAlarm(alertMessage)
}
}
}
Expand All @@ -70,6 +74,8 @@ class TeumMessagingService : FirebaseMessagingService() {
val requestCode = (System.currentTimeMillis() % 10000).toInt()
val intent = Intent(this, SplashActivity::class.java)
intent.putExtra("isFromAlarm", true)
intent.putExtra("message", message)
Timber.tag("teum-alerts").d("message: ${message.title}, ${message.type}, ${message.body}, ${message.meetingId}, ${message.participants.toString()}")
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
val pendingIntent =
PendingIntent.getActivity(
Expand All @@ -94,7 +100,6 @@ class TeumMessagingService : FirebaseMessagingService() {

companion object {
const val EMPTY = "null"
private const val END_MEETING = "END_MEETING"
}

private data class Message(var title: String, var body: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import androidx.navigation.ui.setupWithNavController
import com.teumteum.base.BindingActivity
import com.teumteum.base.util.extension.boolExtra
import com.teumteum.base.util.extension.intExtra
import com.teumteum.domain.entity.Message
import com.teumteum.teumteum.R
import com.teumteum.teumteum.databinding.ActivityMainBinding
import com.teumteum.teumteum.presentation.home.HomeFragmentDirections
Expand Down Expand Up @@ -44,7 +45,20 @@ class MainActivity : BindingActivity<ActivityMainBinding>(R.layout.activity_main


if (isFromAlarm) {
val action = HomeFragmentDirections.actionHomeFragmentToFragmentFamiliar()
val message = intent.getSerializableExtra(MESSAGE) as Message
var action = HomeFragmentDirections.actionHomeFragmentToFragmentFamiliar()
when (message.type) {
BEFORE_MEETING -> {
}
END_MEETING -> {
// val meetingId = message.meetingId
// val participants = message.participants
// action = HomeFragmentDirections.{홈 -> 유저리뷰로 이동하는 navi}
}
RECOMMEND_USER -> {
action = HomeFragmentDirections.actionHomeFragmentToFragmentMyPage()
}
}
val navHostFragment = supportFragmentManager.findFragmentById(R.id.fl_main) as NavHostFragment
navHostFragment.navController.navigate(action)
}
Expand Down Expand Up @@ -138,7 +152,14 @@ class MainActivity : BindingActivity<ActivityMainBinding>(R.layout.activity_main
}
fun getIntent(context: Context, id: Int, isFromAlarm: Boolean = false) = Intent(context, MainActivity::class.java).apply {
putExtra("id", id)
putExtra("isFromAlarm", isFromAlarm)
putExtra(IS_FROM_ALARM, isFromAlarm)
}

private const val IS_FROM_ALARM = "isFromAlarm"
private const val MESSAGE = "message"

private const val BEFORE_MEETING = "BEFORE_MEETING"
private const val END_MEETING = "END_MEETING"
private const val RECOMMEND_USER = "RECOMMEND_USER"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.teumteum.teumteum.presentation.group.GroupListUiState
import com.teumteum.teumteum.presentation.group.GroupListViewModel
import com.teumteum.teumteum.presentation.group.join.GroupDetailActivity
import com.teumteum.teumteum.presentation.group.search.SearchActivity
import com.teumteum.teumteum.presentation.notification.AlertsListActivity
import com.teumteum.teumteum.util.callback.CustomBackPressedCallback
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
Expand Down Expand Up @@ -80,7 +81,12 @@ class HomeFragment :
AppBarMenu.IconStyle(
resourceId = R.drawable.ic_bell,
useRippleEffect = false,
clickEvent = null
clickEvent = {
Intent(requireActivity(), AlertsListActivity::class.java).apply {
startActivity(this)
(activity as? BindingActivity<*>)?.openActivitySlideAnimation()
}
}
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,12 @@ class SettingViewModel @Inject constructor(
return false
}

private val _alarmState = MutableStateFlow(false)
private val _alarmState = MutableStateFlow(settingRepository.getNotification())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기 변동 확인했슴다

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

감삼당 ~~~👍🏻👍🏻

val alarmState: StateFlow<Boolean> = _alarmState.asStateFlow()

fun onToggleChange(newToggleState: Boolean) {
_alarmState.value = newToggleState
settingRepository.setNotification(newToggleState)
_alarmState.value = settingRepository.getNotification()
}

private val _settingStatus = MutableStateFlow(SettingStatus.DEFAULT)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.teumteum.teumteum.presentation.notification

import android.os.Bundle
import androidx.activity.viewModels
import androidx.core.view.isVisible
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.teumteum.base.BindingActivity
import com.teumteum.base.component.appbar.AppBarLayout
import com.teumteum.base.component.appbar.AppBarMenu
import com.teumteum.base.databinding.LayoutCommonAppbarBinding
import com.teumteum.base.util.extension.defaultToast
import com.teumteum.domain.entity.TeumAlert
import com.teumteum.teumteum.R
import com.teumteum.teumteum.databinding.ActivityAlertsListBinding
import com.teumteum.teumteum.presentation.group.GroupListUiState
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach

@AndroidEntryPoint
class AlertsListActivity
: BindingActivity<ActivityAlertsListBinding>(R.layout.activity_alerts_list), AppBarLayout {

private lateinit var adapter: AlertsListAdapter
private val viewModel by viewModels<AlertsViewModel>()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

initAppBarLayout()
initList()
viewModel.getAlerts()
observe()
}

override val appBarBinding: LayoutCommonAppbarBinding
get() = binding.appBar

override fun initAppBarLayout() {
setAppBarHeight(48)

addMenuToLeft(
AppBarMenu.IconStyle(
resourceId = R.drawable.ic_arrow_left_l,
useRippleEffect = false,
clickEvent = ::finish
)
)
setAppBarTitleText(R.string.alerts_list_title)
}

private fun initItems() {
adapter.setItems(listOf(
TeumAlert("틈 채우기", "한별님이 당신을 추천했어요!", RECOMMEND_USER, "2024-01-30T16:33:00", false),
TeumAlert("틈 채우기", "한별님이 당신을 추천했어요!", RECOMMEND_USER, "2024-01-31T16:33:00", false),
TeumAlert("틈 채우기", "한별님이 당신을 추천했어요!", RECOMMEND_USER, "2024-02-10T16:33:00", false),
TeumAlert("틈 채우기", "한별님이 당신을 추천했어요!", RECOMMEND_USER, "2024-02-11T16:33:00", false),
TeumAlert("틈 채우기", "한별님이 당신을 추천했어요!", RECOMMEND_USER, "2024-02-12T09:33:00", false),
TeumAlert("틈 채우기", "한별님이 당신을 추천했어요!", RECOMMEND_USER, "2024-02-12T10:33:00", false)
))
}

private fun observe() {
viewModel.alertsData.flowWithLifecycle(lifecycle)
.onEach {
binding.clEmpty.isVisible = it is AlertsListUiState.Empty
binding.tvNoticeEmpty.isVisible = it !is AlertsListUiState.Empty
binding.rvAlertsList.isVisible = it !is AlertsListUiState.Empty
when (it) {
is AlertsListUiState.SetAlerts -> {
adapter.setItems(it.data)
}
is AlertsListUiState.Failure -> {
defaultToast(it.msg)
}
else -> {}
}
}.launchIn(lifecycleScope)
}

private fun initList() {
adapter = AlertsListAdapter {
when (it.type) {
// 알림 리스트에서 클릭 시 이동하는 정책이 있다면
BEFORE_MEETING -> {
// 틈틈으로 이동
// openActivitySlideAnimation()
}
END_MEETING -> {
// 해당 모임 종료 화면으로 이동
// openActivitySlideAnimation()
}
RECOMMEND_USER -> {
// 마이페이지로 이동
// openActivitySlideAnimation()
}
}
}
binding.rvAlertsList.adapter = adapter
}

companion object {
private const val BEFORE_MEETING = "BEFORE_MEETING"
private const val END_MEETING = "END_MEETING"
private const val RECOMMEND_USER = "RECOMMEND_USER"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.teumteum.teumteum.presentation.notification

import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.teumteum.base.R.*
import com.teumteum.base.util.extension.setOnSingleClickListener
import com.teumteum.domain.entity.TeumAlert
import com.teumteum.teumteum.databinding.ItemAlertsListBinding
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

class AlertsListAdapter(private val itemClick: (TeumAlert) -> (Unit)) :
RecyclerView.Adapter<AlertsListAdapter.AlertsListViewHolder>() {
private val alertList = mutableListOf<TeumAlert>()

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): AlertsListViewHolder {
val binding = ItemAlertsListBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return AlertsListViewHolder(binding, parent.context, itemClick)
}

override fun onBindViewHolder(holder: AlertsListViewHolder, position: Int) {
holder.onBind(alertList[position])
}

override fun getItemCount(): Int = alertList.size

fun setItems(newItems: List<TeumAlert>) {
alertList.clear()
alertList.addAll(newItems.sortedByDescending {
LocalDateTime.parse(it.createdAt, DateTimeFormatter.ISO_LOCAL_DATE_TIME)
})
notifyDataSetChanged()
}

class AlertsListViewHolder(
private val binding: ItemAlertsListBinding,
private val context: Context,
private val itemClick: (TeumAlert) -> (Unit)
) : RecyclerView.ViewHolder(binding.root) {

fun onBind(item: TeumAlert) {
binding.tvTitle.text = item.title
binding.tvContent.text = item.body
binding.tvTime.text = getTimeDifference(item.createdAt)

if (!item.isRead) {
binding.root.setBackgroundColor(context.getColor(color.elevation_level01))
} else {
binding.root.setBackgroundColor(context.getColor(color.transparent))
}
binding.root.setOnSingleClickListener {
itemClick(item)
}
}

private fun getTimeDifference(inputDateTime: String): String {
val currentTime = LocalDateTime.now()
val formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME
val inputTime = LocalDateTime.parse(inputDateTime, formatter)

val diffInMinutes = java.time.Duration.between(inputTime, currentTime).toMinutes()
val diffInHours = java.time.Duration.between(inputTime, currentTime).toHours()

return when {
diffInMinutes < 60 -> "${diffInMinutes}분 전"
diffInHours < 24 -> "${diffInHours}시간 전"
else -> "${inputTime.monthValue}월 ${inputTime.dayOfMonth}일"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.teumteum.teumteum.presentation.notification

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.teumteum.domain.entity.TeumAlert
import com.teumteum.domain.repository.UserRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class AlertsViewModel @Inject constructor(
private val repository: UserRepository
) : ViewModel() {

private val _alertsData = MutableStateFlow<AlertsListUiState>(AlertsListUiState.Init)
val alertsData: StateFlow<AlertsListUiState> = _alertsData

fun getAlerts() {
viewModelScope.launch {
repository.getAlerts()
.onSuccess {
if (it.alerts.isEmpty()) _alertsData.value = AlertsListUiState.Empty
else _alertsData.value = AlertsListUiState.SetAlerts(it.alerts)
}
.onFailure {
_alertsData.value = AlertsListUiState.Failure("알림 가져오기 실패")
}
}
}
}

sealed interface AlertsListUiState {
object Init : AlertsListUiState
object Empty : AlertsListUiState
data class SetAlerts(val data: List<TeumAlert>) : AlertsListUiState
data class Failure(val msg: String) : AlertsListUiState
}
Loading
Loading