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: Fetch settings and divide events view model #2104

Merged
merged 1 commit into from
Jul 15, 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
1,973 changes: 1,973 additions & 0 deletions app/schemas/org.fossasia.openevent.general.OpenEventDatabase/8.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import org.fossasia.openevent.general.sessions.SessionDao
import org.fossasia.openevent.general.sessions.microlocation.MicroLocationConverter
import org.fossasia.openevent.general.sessions.sessiontype.SessionTypeConverter
import org.fossasia.openevent.general.sessions.track.TrackConverter
import org.fossasia.openevent.general.settings.Settings
import org.fossasia.openevent.general.settings.SettingsDao
import org.fossasia.openevent.general.social.SocialLink
import org.fossasia.openevent.general.social.SocialLinksDao
import org.fossasia.openevent.general.speakercall.SpeakersCallConverter
Expand All @@ -49,7 +51,8 @@ import org.fossasia.openevent.general.ticket.TicketIdConverter

@Database(entities = [Event::class, User::class, SocialLink::class, Ticket::class, Attendee::class,
EventTopic::class, Order::class, CustomForm::class, Speaker::class, SpeakerWithEvent::class, Sponsor::class,
SponsorWithEvent::class, Session::class, SpeakersCall::class, Feedback::class, Notification::class], version = 7)
SponsorWithEvent::class, Session::class, SpeakersCall::class, Feedback::class, Notification::class,
Settings::class], version = 8)
@TypeConverters(EventIdConverter::class, EventTopicConverter::class, EventTypeConverter::class,
EventSubTopicConverter::class, TicketIdConverter::class, MicroLocationConverter::class, UserIdConverter::class,
AttendeeIdConverter::class, ListAttendeeIdConverter::class, SessionTypeConverter::class, TrackConverter::class,
Expand Down Expand Up @@ -85,4 +88,6 @@ abstract class OpenEventDatabase : RoomDatabase() {
abstract fun feedbackDao(): FeedbackDao

abstract fun notificationDao(): NotificationDao

abstract fun settingsDao(): SettingsDao
}
119 changes: 119 additions & 0 deletions app/src/main/java/org/fossasia/openevent/general/StartupViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package org.fossasia.openevent.general

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.auth.AuthHolder
import org.fossasia.openevent.general.auth.AuthService
import org.fossasia.openevent.general.auth.RequestPasswordReset
import org.fossasia.openevent.general.auth.forgot.PasswordReset
import org.fossasia.openevent.general.common.SingleLiveEvent
import org.fossasia.openevent.general.data.Preference
import org.fossasia.openevent.general.data.Resource
import org.fossasia.openevent.general.event.NEW_NOTIFICATIONS
import org.fossasia.openevent.general.notification.NotificationService
import org.fossasia.openevent.general.settings.SettingsService
import org.fossasia.openevent.general.utils.HttpErrors
import org.fossasia.openevent.general.utils.extensions.withDefaultSchedulers
import retrofit2.HttpException
import timber.log.Timber

class StartupViewModel(
private val preference: Preference,
private val resource: Resource,
private val authHolder: AuthHolder,
private val authService: AuthService,
private val notificationService: NotificationService,
private val settingsService: SettingsService
) : ViewModel() {
private val compositeDisposable = CompositeDisposable()
val mutableNewNotifications = MutableLiveData<Boolean>()
val newNotifications: LiveData<Boolean> = mutableNewNotifications
private val mutableDialogProgress = MutableLiveData<Boolean>()
val dialogProgress: LiveData<Boolean> = mutableDialogProgress
private val mutableIsRefresh = MutableLiveData<Boolean>()
val isRefresh: LiveData<Boolean> = mutableIsRefresh
private val mutableResetPasswordEmail = MutableLiveData<String>()
val resetPasswordEmail: LiveData<String> = mutableResetPasswordEmail
private val mutableMessage = SingleLiveEvent<String>()
val message: LiveData<String> = mutableMessage

fun isLoggedIn() = authHolder.isLoggedIn()

fun getId() = authHolder.getId()

fun syncNotifications() {
if (!isLoggedIn())
return
compositeDisposable += notificationService.syncNotifications(getId())
.withDefaultSchedulers()
.subscribe({ list ->
list?.forEach {
if (!it.isRead) {
preference.putBoolean(NEW_NOTIFICATIONS, true)
mutableNewNotifications.value = true
}
}
}, {
if (it is HttpException) {
if (authHolder.isLoggedIn() && it.code() == HttpErrors.UNAUTHORIZED) {
logoutAndRefresh()
}
}
Timber.e(it, "Error fetching notifications")
})
}

private fun logoutAndRefresh() {
compositeDisposable += authService.logout()
.withDefaultSchedulers()
.subscribe({
mutableIsRefresh.value = true
}, {
Timber.e(it, "Error while logout")
mutableMessage.value = resource.getString(R.string.error)
})
}

fun checkAndReset(token: String, newPassword: String) {
val resetRequest = RequestPasswordReset(PasswordReset(token, newPassword))
if (authHolder.isLoggedIn()) {
compositeDisposable += authService.logout()
.withDefaultSchedulers()
.doOnSubscribe {
mutableDialogProgress.value = true
}.subscribe {
resetPassword(resetRequest)
}
} else
resetPassword(resetRequest)
}

private fun resetPassword(resetRequest: RequestPasswordReset) {
compositeDisposable += authService.resetPassword(resetRequest)
.withDefaultSchedulers()
.doOnSubscribe {
mutableDialogProgress.value = true
}.doFinally {
mutableDialogProgress.value = false
}.subscribe({
Timber.e(it.toString())
mutableMessage.value = resource.getString(R.string.reset_password_message)
mutableResetPasswordEmail.value = it.email
}, {
Timber.e(it, "Failed to reset password")
})
}

fun fetchSettings() {
compositeDisposable += settingsService.fetchSettings()
.withDefaultSchedulers()
.subscribe({
Timber.d("Settings fetched successfully")
}, {
Timber.e(it, "Error in fetching settings form API")
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,6 @@ import java.util.Calendar
import java.util.Currency
import kotlin.collections.ArrayList

private const val COUNT_DOWN_TIME = 15 // in minutes

class AttendeeFragment : Fragment(), ComplexBackPressFragment {

private lateinit var rootView: View
Expand Down Expand Up @@ -260,9 +258,13 @@ class AttendeeFragment : Fragment(), ComplexBackPressFragment {
.observe(viewLifecycleOwner, Observer {
loadEventDetailsUI(it)
setupPaymentOptions(it)
if (attendeeViewModel.pendingOrder.value != null) {
setupCountDownTimer(it)
}
})

attendeeViewModel.getSettings()
Copy link
Member

Choose a reason for hiding this comment

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

There is no guarantee that settings have been fetched till the next statement is executed

Copy link
Member Author

Choose a reason for hiding this comment

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

Then onErrorResumeNext will execute and settings will fetch from api.

Copy link
Member

@iamareebjamal iamareebjamal Jul 12, 2019

Choose a reason for hiding this comment

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

Still there is no guarantee that settings have been fetched till the next statement is executed, as getSettings is asynchronous

Copy link
Member Author

Choose a reason for hiding this comment

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

Sorry, I don't understand which next statement. During fetching/getting settings progress dialog is displaying and if settings fetch successfully or there is an error(=15), value for mutableOrderExpiryTime is set and only then countdown timer will start.

Copy link
Member

Choose a reason for hiding this comment

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

The statement which is immediately proceeding getSettings

attendeeViewModel.orderExpiryTime
.nonNull()
.observe(viewLifecycleOwner, Observer {
setupCountDownTimer(it)
})

val currentEvent = attendeeViewModel.event.value
Expand All @@ -271,33 +273,22 @@ class AttendeeFragment : Fragment(), ComplexBackPressFragment {
else {
setupPaymentOptions(currentEvent)
loadEventDetailsUI(currentEvent)
if (attendeeViewModel.pendingOrder.value != null) {
setupCountDownTimer(currentEvent)
}
}
}

private fun setupPendingOrder() {
attendeeViewModel.pendingOrder
.nonNull()
.observe(viewLifecycleOwner, Observer {
attendeeViewModel.event.value?.let {
setupCountDownTimer(it)
}
})

val currentPendingOrder = attendeeViewModel.pendingOrder.value
if (currentPendingOrder == null) {
attendeeViewModel.initializeOrder(safeArgs.eventId)
}
}

private fun setupCountDownTimer(event: Event) {
private fun setupCountDownTimer(orderExpiryTime: Int) {
rootView.timeoutCounterLayout.visibility = View.VISIBLE
rootView.timeoutInfoTextView.text =
getString(R.string.ticket_timeout_info_message, event.orderExpiryTime.toString())
getString(R.string.ticket_timeout_info_message, orderExpiryTime.toString())

val timeLeft: Long = if (attendeeViewModel.timeout == -1L) COUNT_DOWN_TIME * 60 * 1000L
val timeLeft: Long = if (attendeeViewModel.timeout == -1L) orderExpiryTime * 60 * 1000L
else attendeeViewModel.timeout
timer = object : CountDownTimer(timeLeft, 1000) {
override fun onFinish() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.fossasia.openevent.general.order.Charge
import org.fossasia.openevent.general.order.ConfirmOrder
import org.fossasia.openevent.general.order.Order
import org.fossasia.openevent.general.order.OrderService
import org.fossasia.openevent.general.settings.SettingsService
import org.fossasia.openevent.general.ticket.Ticket
import org.fossasia.openevent.general.ticket.TicketService
import org.fossasia.openevent.general.utils.HttpErrors
Expand All @@ -38,6 +39,7 @@ const val PAYMENT_MODE_ONSITE = "onsite"
const val PAYMENT_MODE_CHEQUE = "cheque"
const val PAYMENT_MODE_PAYPAL = "paypal"
const val PAYMENT_MODE_STRIPE = "stripe"
private const val ORDER_EXPIRY_TIME = 15

class AttendeeViewModel(
private val attendeeService: AttendeeService,
Expand All @@ -46,6 +48,7 @@ class AttendeeViewModel(
private val orderService: OrderService,
private val ticketService: TicketService,
private val authService: AuthService,
private val settingsService: SettingsService,
private val resource: Resource
) : ViewModel() {

Expand All @@ -71,6 +74,8 @@ class AttendeeViewModel(
val pendingOrder: LiveData<Order> = mutablePendingOrder
private val mutableStripeOrderMade = MutableLiveData<Boolean>()
val stripeOrderMade: LiveData<Boolean> = mutableStripeOrderMade
private val mutableOrderExpiryTime = MutableLiveData<Int>()
val orderExpiryTime: LiveData<Int> = mutableOrderExpiryTime

val attendees = ArrayList<Attendee>()
private val attendeesForOrder = ArrayList<Attendee>()
Expand Down Expand Up @@ -391,6 +396,21 @@ class AttendeeViewModel(
return true
}

fun getSettings() {
compositeDisposable += settingsService.fetchSettings()
.withDefaultSchedulers()
.doOnSubscribe {
mutableProgress.value = true
}.doFinally {
mutableProgress.value = false
}.subscribe({
mutableOrderExpiryTime.value = it.orderExpiryTime
}, {
mutableOrderExpiryTime.value = ORDER_EXPIRY_TIME
Timber.e(it, "Error fetching settings")
})
}

override fun onCleared() {
super.onCleared()
compositeDisposable.clear()
Expand Down
21 changes: 18 additions & 3 deletions app/src/main/java/org/fossasia/openevent/general/di/Modules.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.fossasia.openevent.general.BuildConfig
import org.fossasia.openevent.general.OpenEventDatabase
import org.fossasia.openevent.general.StartupViewModel
import org.fossasia.openevent.general.about.AboutEventViewModel
import org.fossasia.openevent.general.attendees.Attendee
import org.fossasia.openevent.general.attendees.AttendeeApi
Expand Down Expand Up @@ -90,6 +91,9 @@ import org.fossasia.openevent.general.sessions.SessionViewModel
import org.fossasia.openevent.general.sessions.microlocation.MicroLocation
import org.fossasia.openevent.general.sessions.sessiontype.SessionType
import org.fossasia.openevent.general.sessions.track.Track
import org.fossasia.openevent.general.settings.Settings
import org.fossasia.openevent.general.settings.SettingsApi
import org.fossasia.openevent.general.settings.SettingsService
import org.fossasia.openevent.general.settings.SettingsViewModel
import org.fossasia.openevent.general.social.SocialLink
import org.fossasia.openevent.general.social.SocialLinkApi
Expand Down Expand Up @@ -194,6 +198,10 @@ val apiModule = module {
val retrofit: Retrofit = get()
retrofit.create(DiscountApi::class.java)
}
single {
val retrofit: Retrofit = get()
retrofit.create(SettingsApi::class.java)
}

factory { AuthHolder(get()) }
factory { AuthService(get(), get(), get(), get(), get()) }
Expand All @@ -208,19 +216,21 @@ val apiModule = module {
factory { SessionService(get(), get()) }
factory { NotificationService(get(), get()) }
factory { FeedbackService(get(), get()) }
factory { SettingsService(get(), get()) }
}

val viewModelModule = module {
viewModel { LoginViewModel(get(), get(), get()) }
viewModel { EventsViewModel(get(), get(), get(), get(), get(), get(), get(), get()) }
viewModel { EventsViewModel(get(), get(), get(), get(), get()) }
viewModel { StartupViewModel(get(), get(), get(), get(), get(), get()) }
viewModel { ProfileViewModel(get(), get()) }
viewModel { SignUpViewModel(get(), get(), get()) }
viewModel {
EventDetailsViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) }
viewModel { SessionViewModel(get(), get(), get()) }
viewModel { SearchViewModel(get(), get()) }
viewModel { SearchResultsViewModel(get(), get(), get(), get(), get()) }
viewModel { AttendeeViewModel(get(), get(), get(), get(), get(), get(), get()) }
viewModel { AttendeeViewModel(get(), get(), get(), get(), get(), get(), get(), get()) }
viewModel { SearchLocationViewModel(get(), get()) }
viewModel { SearchTimeViewModel(get()) }
viewModel { SearchTypeViewModel(get(), get(), get()) }
Expand Down Expand Up @@ -292,7 +302,7 @@ val networkModule = module {
EventSubTopic::class.java, Feedback::class.java, Speaker::class.java,
Session::class.java, SessionType::class.java, MicroLocation::class.java, SpeakersCall::class.java,
Sponsor::class.java, EventFAQ::class.java, Notification::class.java, Track::class.java,
DiscountCode::class.java)
DiscountCode::class.java, Settings::class.java)

Retrofit.Builder()
.client(get())
Expand Down Expand Up @@ -385,4 +395,9 @@ val databaseModule = module {
val database: OpenEventDatabase = get()
database.notificationDao()
}

factory {
val database: OpenEventDatabase = get()
database.settingsDao()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ data class Event(
val latitude: Double? = null,
val longitude: Double? = null,
val refundPolicy: String? = null,
val orderExpiryTime: Int = 10,

val canPayByStripe: Boolean = false,
val canPayByCheque: Boolean = false,
Expand Down
Loading