Skip to content
This repository has been archived by the owner on Jun 6, 2021. It is now read-only.

Commit

Permalink
feat: 앱 내 알림 인터페이스 개선
Browse files Browse the repository at this point in the history
  • Loading branch information
potados99 committed Dec 30, 2020
1 parent 695cc63 commit 3532e21
Show file tree
Hide file tree
Showing 23 changed files with 218 additions and 73 deletions.
10 changes: 10 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<uses-feature android:name="android.hardware.camera.any" />

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.INTERNET" />

<application
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,12 @@ class Navigator(
.show()
}

fun showOrderFinishedNotification(activity: FragmentActivity, onConfirm: () -> Unit) {
fun showOrderFinishedNotification(activity: FragmentActivity, title: String, body: String) {
AlertDialog
.Builder(activity)
.setTitle(context.getString(R.string.title_order_ready))
.setMessage(context.getString(R.string.description_order_ready))
.setPositiveButton(context.getString(R.string.button_confirm)) { _, _ ->
onConfirm()
}
.setTitle(title)
.setMessage(body)
.setPositiveButton(context.getString(R.string.button_confirm)) { _, _ -> }
.setCancelable(false) // Force!
.show()
}
Expand Down
23 changes: 16 additions & 7 deletions app/src/main/java/com/inu/cafeteria/feature/main/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ import android.content.Intent
import android.os.Bundle
import android.view.animation.AnimationUtils
import androidx.activity.viewModels
import androidx.annotation.IdRes
import androidx.cardview.widget.CardView
import androidx.lifecycle.LiveData
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.inu.cafeteria.GlobalConfig
import com.inu.cafeteria.R
Expand Down Expand Up @@ -126,10 +128,12 @@ class MainActivity : NavigationActivity() {
eventHandler.onCreate(this, savedInstanceState)

setOfflineView()
setWaitingOrderTabBadge()
setSupportTabBadge()
observeLoginEvent() // to then fetch unread answers.
}


private fun setOfflineView() {
val eggs = getEasterEggs(this)

Expand All @@ -154,23 +158,28 @@ class MainActivity : NavigationActivity() {
}
}

private fun setWaitingOrderTabBadge() {
setTabBadge(viewModel.numberOfFinishedOrders, R.id.tab_order)
}

private fun setSupportTabBadge() {
withNonNull(findViewById<BottomNavigationView>(R.id.bottom_nav)) {
setTabBadge(viewModel.numberOfUnreadAnswers, R.id.tab_support)
}

observe(viewModel.numberOfUnreadAnswers) { numberOfNotifications ->
private fun setTabBadge(numberOfNotifications: LiveData<Int>, @IdRes tabItemId: Int) {
withNonNull(findViewById<BottomNavigationView>(R.id.bottom_nav)) {
observe(numberOfNotifications) { numberOfNotifications ->
numberOfNotifications ?: return@observe

Timber.i("Notifications left: $numberOfNotifications")

if (numberOfNotifications > 0) {
getOrCreateBadge(R.id.tab_support).apply {
getOrCreateBadge(tabItemId).apply {
backgroundColor = getColor(R.color.orange)
isVisible = true
number = numberOfNotifications
}
} else {
getBadge(R.id.tab_support)?.isVisible = false
removeBadge(R.id.tab_support)
getBadge(tabItemId)?.isVisible = false
removeBadge(tabItemId)
}
}
}
Expand Down
28 changes: 20 additions & 8 deletions app/src/main/java/com/inu/cafeteria/feature/main/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,9 @@ import com.inu.cafeteria.common.navigation.Navigator
import com.inu.cafeteria.entities.Notice
import com.inu.cafeteria.repository.DeviceStatusRepository
import com.inu.cafeteria.repository.InteractionRepository
import com.inu.cafeteria.repository.WaitingOrderRepository
import com.inu.cafeteria.service.AccountService
import com.inu.cafeteria.usecase.CheckForUpdate
import com.inu.cafeteria.usecase.DismissNotice
import com.inu.cafeteria.usecase.FetchNotifications
import com.inu.cafeteria.usecase.GetNewNotice
import com.inu.cafeteria.usecase.*
import org.koin.core.inject
import timber.log.Timber

Expand All @@ -40,13 +38,17 @@ class MainViewModel : BaseViewModel() {
private val getNewNotice: GetNewNotice by inject()
private val dismissNotice: DismissNotice by inject()
private val shouldIUpdate: CheckForUpdate by inject()
private val fetchNotifications: FetchNotifications by inject()
private val getWaitingOrders: GetWaitingOrders by inject()
private val fetchUnreadAnswers: FetchUnreadAnswers by inject()

private val statusRepository: DeviceStatusRepository by inject()

private val interactionRepository: InteractionRepository by inject()
val numberOfUnreadAnswers = interactionRepository.getNumberOfUnreadAnswersLiveData()

private val waitingOrderRepository: WaitingOrderRepository by inject()
val numberOfFinishedOrders = waitingOrderRepository.getNumberOfFinishedOrdersLiveData()

private val accountService: AccountService by inject()
val loggedInStatus = accountService.loggedInStatus()

Expand All @@ -58,6 +60,9 @@ class MainViewModel : BaseViewModel() {

checkNewNotice(activity)
checkForUpdate(activity)

// This is a global thing, so that it happens on MainViewModel.
checkForFinishedOrders()
}

fun onLoggedIn() {
Expand All @@ -67,7 +72,7 @@ class MainViewModel : BaseViewModel() {
}

// This is a global thing, so that it happens on MainViewModel.
checkForNotifications()
checkForUnreadAnswers()
}

private fun checkNewNotice(activity: MainActivity) {
Expand Down Expand Up @@ -102,8 +107,15 @@ class MainViewModel : BaseViewModel() {
}
}

private fun checkForNotifications() {
fetchNotifications(Unit) {

private fun checkForFinishedOrders() {
getWaitingOrders(Unit) {
it.onError(::handleFailure)
}
}

private fun checkForUnreadAnswers() {
fetchUnreadAnswers(Unit) {
it.onError(::handleFailure)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package com.inu.cafeteria.feature.order

import android.view.animation.AnimationUtils
import com.inu.cafeteria.R
import com.inu.cafeteria.common.base.GenericAdapter
import com.inu.cafeteria.databinding.WaitingOrderItemBinding
Expand All @@ -37,5 +38,15 @@ class WaitingOrderAdapter : GenericAdapter<WaitingOrderView, WaitingOrderItemBin
onClickDelete(item.orderId)
}
}

with(holder.binding.number) {
clearAnimation()

if (item.done) {
startAnimation(
AnimationUtils.loadAnimation(context, R.anim.alpha_animation)
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.Bundle
import android.os.VibrationEffect
import android.os.Vibrator
import android.view.View
import android.view.animation.AnimationUtils
import androidx.annotation.RequiresApi
import androidx.databinding.BindingAdapter
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.DividerItemDecoration
Expand All @@ -49,11 +52,9 @@ class WaitingOrderFragment : BaseFragment() {
private val navigator: Navigator by inject()

/** Receives in-app firebase notification when app is active */
private val pushNumberNotificationReceiver = object: BroadcastReceiver() {
private val pushNumberNotificationReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
handleNotificationMessage(
intent?.getParcelableExtra("message") ?: return
)
onPushNumberNotification(intent?.getParcelableExtra("message") ?: return)
}
}

Expand Down Expand Up @@ -85,6 +86,12 @@ class WaitingOrderFragment : BaseFragment() {
viewModel.fetchWaitingOrders()
}

override fun onPause() {
super.onPause()

viewModel.deleteFinishedOrders()
}

override fun onDestroy() {
super.onDestroy()

Expand Down Expand Up @@ -128,32 +135,31 @@ class WaitingOrderFragment : BaseFragment() {
}

private fun clearAllOrderNotifications() {
// TODO: this does not work.
val notificationManager = activity?.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager ?: return

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Cancel notifications only on push number notification channel
val channelIdToClearNotifications = getString(R.string.push_number_notification_channel_id)
// Warning: this will cancel notifications in other channels.
notificationManager.cancelAll()
}

notificationManager.activeNotifications.forEach {
if (it.notification.channelId == channelIdToClearNotifications) {
notificationManager.cancel(it.id)
}
}
} else {
// Cancel all notifications
notificationManager.cancelAll()
private fun onPushNumberNotification(message: RemoteMessage) {
viewModel.fetchWaitingOrders()

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrate()
}
}

private fun handleNotificationMessage(message: RemoteMessage) {
val orderId = message.data["order_id"]?.toInt() ?: return
val title = message.notification?.title ?: context?.getString(R.string.title_order_ready) ?: "주문하신 음식이 나왔어요!"
val body = message.notification?.body ?: context?.getString(R.string.description_order_ready) ?: "픽업대에서 기다리고 있습니다 :)"

navigator.showOrderFinishedNotification(activity ?: return, title, body)
}

viewModel.markOrderReady(orderId)
@RequiresApi(Build.VERSION_CODES.O)
private fun vibrate() {
val vibrator = context?.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator
val effect = VibrationEffect.createOneShot(200, VibrationEffect.DEFAULT_AMPLITUDE)

navigator.showOrderFinishedNotification(activity ?: return) {
viewModel.fetchWaitingOrders()
}
vibrator?.vibrate(effect)
}

companion object {
Expand All @@ -164,5 +170,13 @@ class WaitingOrderFragment : BaseFragment() {

(view.adapter as? WaitingOrderAdapter)?.items = orders
}

@JvmStatic
@BindingAdapter("areOrdersLoading")
fun setAreOrdersLoading(view: RecyclerView, isLoading: Boolean?) {
isLoading ?: return

(view.adapter as? WaitingOrderAdapter)?.isLoading = isLoading
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ package com.inu.cafeteria.feature.order

data class WaitingOrderView(
val orderId: Int,
val done: Boolean,
val waitingNumber: String,
val cafeteriaDisplayName: String,
val done: Boolean
)
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,38 @@ class WaitingOrderViewModel : BaseViewModel() {
private val _orders = MutableLiveData<List<WaitingOrderView>>()
val orders: LiveData<List<WaitingOrderView>> = _orders

private val _loading = MutableLiveData(false)
val loading: LiveData<Boolean> = _loading

fun fetchWaitingOrders() {
_loading.value = true

getWaitingOrders(Unit) {
it.onSuccess(::handleWaitingOrders).onError(::handleFailure)
it
.onSuccess(::handleWaitingOrders)
.onError(::handleFailure)
}
}

private fun handleWaitingOrders(orders: List<WaitingOrder>) {
getCafeteriaOnly(Unit) { result ->
result.onSuccess { handleWaitingOrdersWithCafeteria(orders, it) }.onError(::handleFailure)
result
.onSuccess { handleWaitingOrdersWithCafeteria(orders, it) }
.onError(::handleFailure)
.finally { _loading.value = false }
}
}

private fun handleWaitingOrdersWithCafeteria(orders: List<WaitingOrder>, cafeteria: List<Cafeteria>) {
private fun handleWaitingOrdersWithCafeteria(
orders: List<WaitingOrder>,
cafeteria: List<Cafeteria>
) {
_orders.value = orders.map { order ->
WaitingOrderView(
orderId = order.id,
done = order.done,
waitingNumber = String.format("%04d", order.number) /*this is necessary*/,
cafeteriaDisplayName = getCafeteriaNameById(cafeteria, order.cafeteriaId),
done = false
cafeteriaDisplayName = getCafeteriaNameById(cafeteria, order.cafeteriaId)
)
}
}
Expand All @@ -72,19 +85,20 @@ class WaitingOrderViewModel : BaseViewModel() {
}

fun deleteWaitingOrder(orderId: Int) {
_loading.value = true

deleteWaitingOrder(orderId) {
it.onSuccess { fetchWaitingOrders()/*refresh*/ }.onError(::handleFailure)
it
.onSuccess { fetchWaitingOrders()/*refresh*/ }
.onError(::handleFailure)
.finally { _loading.value = false }
}
}

fun markOrderReady(orderId: Int) {
_orders.value = _orders.value?.map {
if (it.orderId == orderId) {
it.copy(done = true)
} else {
it
}
}
fun deleteFinishedOrders() {
_orders.value
?.filter { it.done }
?.forEach { deleteWaitingOrder(it.orderId) }
}

override fun handleFailure(e: Exception) {
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/inu/cafeteria/injection/module.kt
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ val myModules = module {

/** Mark answer read */
single {
FetchNotifications(
FetchUnreadAnswers(
interactionRepo = get()
)
}
Expand Down
Loading

0 comments on commit 3532e21

Please sign in to comment.