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: Notification Fragment #1622

Merged
merged 1 commit into from
May 14, 2019
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
12 changes: 11 additions & 1 deletion app/src/main/java/org/fossasia/openevent/general/di/Modules.kt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ import org.fossasia.openevent.general.event.types.EventTypesApi
import org.fossasia.openevent.general.event.topic.SimilarEventsViewModel
import org.fossasia.openevent.general.favorite.FavoriteEventsRecyclerAdapter
import org.fossasia.openevent.general.favorite.FavoriteEventsViewModel
import org.fossasia.openevent.general.notification.Notification
import org.fossasia.openevent.general.notification.NotificationApi
import org.fossasia.openevent.general.notification.NotificationService
import org.fossasia.openevent.general.notification.NotificationViewModel
import org.fossasia.openevent.general.order.Charge
import org.fossasia.openevent.general.order.ConfirmOrder
import org.fossasia.openevent.general.order.Order
Expand Down Expand Up @@ -176,6 +180,10 @@ val apiModule = module {
val retrofit: Retrofit = get()
retrofit.create(SponsorApi::class.java)
}
single {
val retrofit: Retrofit = get()
retrofit.create(NotificationApi::class.java)
}

factory { AuthHolder(get()) }
factory { AuthService(get(), get(), get()) }
Expand All @@ -190,6 +198,7 @@ val apiModule = module {
factory { SessionService(get(), get()) }
factory { Resource() }
factory { MutableConnectionLiveData() }
factory { NotificationService(get()) }
}

val viewModelModule = module {
Expand Down Expand Up @@ -219,6 +228,7 @@ val viewModelModule = module {
viewModel { SmartAuthViewModel() }
viewModel { SpeakerViewModel(get(), get()) }
viewModel { SponsorsViewModel(get(), get()) }
viewModel { NotificationViewModel(get(), get(), get(), get()) }
}

val networkModule = module {
Expand Down Expand Up @@ -260,7 +270,7 @@ val networkModule = module {
CustomForm::class.java, EventLocation::class.java, EventType::class.java,
EventSubTopic::class.java, Feedback::class.java, Speaker::class.java,
Session::class.java, SessionType::class.java, MicroLocation::class.java,
Sponsor::class.java, EventFAQ::class.java)
Sponsor::class.java, EventFAQ::class.java, Notification::class.java)

Retrofit.Builder()
.client(get())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,30 @@ object EventUtils {
}
}

fun getFormattedDateWithoutWeekday(date: ZonedDateTime): String {
val dateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("MMM d, y")
return try {
dateFormat.format(date)
} catch (e: IllegalArgumentException) {
Timber.e(e, "Error formatting Date")
""
}
}

fun getFormattedWeekDay(date: ZonedDateTime): String {
val dateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("EEE")
return try {
dateFormat.format(date)
} catch (e: IllegalArgumentException) {
Timber.e(e, "Error formatting Date")
""
}
}

fun getDayDifferenceFromToday(date: String): Long {
return (System.currentTimeMillis() - EventUtils.getTimeInMilliSeconds(date, null)) / (1000 * 60 * 60 * 24)
}

/**
* share event detail along with event image
* if image loading is successful then imageView tag will be set to String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
Expand Down Expand Up @@ -75,7 +78,7 @@ class EventsFragment : Fragment(), ScrollToTop {
savedInstanceState: Bundle?
): View? {
rootView = inflater.inflate(R.layout.fragment_events, container, false)

setHasOptionsMenu(true)
if (preference.getString(SAVED_LOCATION).isNullOrEmpty()) {
findNavController(requireActivity(), R.id.frameContainer).navigate(R.id.welcomeFragment)
}
Expand Down Expand Up @@ -197,6 +200,20 @@ class EventsFragment : Fragment(), ScrollToTop {
type = hashTag))
}

override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.events, menu)
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.notifications -> {
findNavController(rootView).navigate(EventsFragmentDirections.actionEventsToNotification())
true
}
else -> super.onOptionsItemSelected(item)
}
}

private fun showNoInternetScreen(show: Boolean) {
rootView.homeScreenLL.visibility = if (!show) View.VISIBLE else View.GONE
rootView.noInternetCard.visibility = if (show) View.VISIBLE else View.GONE
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.fossasia.openevent.general.notification

import com.fasterxml.jackson.databind.PropertyNamingStrategy
import com.fasterxml.jackson.databind.annotation.JsonNaming
import com.github.jasminb.jsonapi.IntegerIdHandler
import com.github.jasminb.jsonapi.annotations.Id
import com.github.jasminb.jsonapi.annotations.Type
import io.reactivex.annotations.NonNull

@Type("notification")
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy::class)
data class Notification(
@Id(IntegerIdHandler::class)
@NonNull
val id: Long,
val message: String? = null,
val receivedAt: String? = null,
val isRead: Boolean? = null,
val title: String? = null,
val deletedAt: String? = null
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.fossasia.openevent.general.notification

import io.reactivex.Completable
import io.reactivex.Single
import retrofit2.http.DELETE
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.PATCH
import retrofit2.http.Path

interface NotificationApi {

@GET("users/{userId}/notifications?sort=message")
fun getNotifications(@Path("userId") userId: Long): Single<List<Notification>>

@PATCH("notifications/{notification_id}")
fun updateNotification(
@Path("notification_id") notificationId: Long,
@Body notification: Notification
): Single<List<Notification>>

@DELETE("notifications/{notification_id}")
fun deleteNotification(@Path("notification_id") notificationId: Long): Completable
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package org.fossasia.openevent.general.notification

import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.navigation.Navigation
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.content_no_internet.view.retry
import kotlinx.android.synthetic.main.content_no_internet.view.noInternetCard
import kotlinx.android.synthetic.main.fragment_notification.view.notificationRecycler
import kotlinx.android.synthetic.main.fragment_notification.view.swiperefresh
import kotlinx.android.synthetic.main.fragment_notification.view.shimmerNotifications
import kotlinx.android.synthetic.main.fragment_notification.view.notificationCoordinatorLayout
import kotlinx.android.synthetic.main.fragment_notification.view.noNotification
import org.fossasia.openevent.general.R
import org.fossasia.openevent.general.auth.LoginFragmentArgs
import org.fossasia.openevent.general.utils.Utils
import org.fossasia.openevent.general.utils.Utils.setToolbar
import org.fossasia.openevent.general.utils.extensions.nonNull
import org.jetbrains.anko.design.snackbar
import org.koin.androidx.viewmodel.ext.android.viewModel

class NotificationFragment : Fragment() {
private val notificationViewModel by viewModel<NotificationViewModel>()
private val recyclerAdapter = NotificationsRecyclerAdapter()
private lateinit var rootView: View

override fun onStart() {
super.onStart()
if (!notificationViewModel.isLoggedIn()) {
redirectToLogin()
}
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
rootView = inflater.inflate(R.layout.fragment_notification, container, false)
setToolbar(activity, getString(R.string.title_notifications), true)
setHasOptionsMenu(true)

if (notificationViewModel.isLoggedIn()) {
Copy link
Member

Choose a reason for hiding this comment

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

Why are you checking again for authentication?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To prevent the api call if not logged in.
then in onStart, redirect to login.

Copy link
Member

Choose a reason for hiding this comment

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

But onCreateView is always called after onStart, so can't you remove onStart here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

bro no, onStart is called after onActivityCreated
https://developer.android.com/guide/components/fragments

initObservers()
if (notificationViewModel.notifications.value == null) {
notificationViewModel.getNotifications()
}
rootView.notificationRecycler.layoutManager = LinearLayoutManager(requireContext())
rootView.notificationRecycler.adapter = recyclerAdapter
}
return rootView
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

rootView.retry.setOnClickListener {
notificationViewModel.getNotifications()
}

rootView.swiperefresh.setOnRefreshListener {
notificationViewModel.getNotifications()
}
}

private fun initObservers() {
notificationViewModel.notifications
.nonNull()
.observe(viewLifecycleOwner, Observer {
showNoNotifications(it.isEmpty())
recyclerAdapter.addAll(it)
recyclerAdapter.notifyDataSetChanged()
})

notificationViewModel.error
.nonNull()
.observe(viewLifecycleOwner, Observer {
rootView.swiperefresh.isRefreshing = false
rootView.notificationCoordinatorLayout.snackbar(it)
})

notificationViewModel.progress
.nonNull()
.observe(viewLifecycleOwner, Observer {
if (it) {
rootView.shimmerNotifications.startShimmer()
rootView.noNotification.isVisible = false
rootView.notificationRecycler.isVisible = false
} else {
rootView.shimmerNotifications.stopShimmer()
rootView.swiperefresh.isRefreshing = it
}
rootView.shimmerNotifications.isVisible = it
})

notificationViewModel.noInternet
.nonNull()
.observe(viewLifecycleOwner, Observer {
if (it) {
rootView.notificationCoordinatorLayout
.snackbar(resources.getString(R.string.no_internet_connection_message))
rootView.swiperefresh.isRefreshing = !it
}
showNoInternet(it && notificationViewModel.notifications.value.isNullOrEmpty())
})
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
activity?.onBackPressed()
true
}
else -> super.onOptionsItemSelected(item)
}
}

private fun showNoInternet(visible: Boolean) {
rootView.noInternetCard.isVisible = visible
rootView.notificationRecycler.isVisible = !visible
}

private fun showNoNotifications(visible: Boolean) {
rootView.noNotification.isVisible = visible
rootView.notificationRecycler.isVisible = !visible
}

private fun redirectToLogin() {
LoginFragmentArgs(getString(R.string.log_in_first))
.toBundle()
.also {
Navigation.findNavController(rootView).navigate(R.id.loginFragment, it, Utils.getAnimFade())
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.fossasia.openevent.general.notification

import io.reactivex.Single

class NotificationService(
private val notificationApi: NotificationApi
) {
fun getNotifications(userId: Long): Single<List<Notification>> {
return notificationApi.getNotifications(userId)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.fossasia.openevent.general.notification

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import org.fossasia.openevent.general.R
import org.fossasia.openevent.general.auth.AuthHolder
import org.fossasia.openevent.general.common.SingleLiveEvent
import org.fossasia.openevent.general.data.Network
import org.fossasia.openevent.general.data.Resource
import org.fossasia.openevent.general.utils.extensions.withDefaultSchedulers
import timber.log.Timber

class NotificationViewModel(
private val notificationService: NotificationService,
private val authHolder: AuthHolder,
private val network: Network,
private val resource: Resource
) : ViewModel() {

private val compositeDisposable = CompositeDisposable()
private val mutableNotifications = MutableLiveData<List<Notification>>()
val notifications: LiveData<List<Notification>> = mutableNotifications

private val mutableProgress = MutableLiveData<Boolean>()
val progress: LiveData<Boolean> = mutableProgress

private val mutableNoInternet = SingleLiveEvent<Boolean>()
val noInternet: LiveData<Boolean> = mutableNoInternet

private val mutableError = SingleLiveEvent<String>()
val error: LiveData<String> = mutableError

fun getId() = authHolder.getId()

fun isLoggedIn() = authHolder.isLoggedIn()

fun getNotifications() {

if (!isConnected()) {
return
}

compositeDisposable += notificationService.getNotifications(getId())
.withDefaultSchedulers()
.doOnSubscribe {
mutableProgress.value = true
}.doFinally {
mutableProgress.value = false
}.subscribe({
mutableNotifications.value = it
Timber.d("Notification retrieve successful")
}, {
mutableError.value = resource.getString(R.string.msg_failed_to_load_notification)
Timber.d(it, resource.getString(R.string.msg_failed_to_load_notification))
})
}

fun isConnected(): Boolean {
val isConnected = network.isNetworkConnected()
mutableNoInternet.value = !isConnected
return isConnected
}
}
Loading