Skip to content

Commit ea273ef

Browse files
committed
Summary: Add call for speaker section
Detail: - Set up menu action to get to Speakers Call Section Fixes: #1631
1 parent f18e2a4 commit ea273ef

File tree

18 files changed

+2078
-7
lines changed

18 files changed

+2078
-7
lines changed

app/schemas/org.fossasia.openevent.general.OpenEventDatabase/6.json

Lines changed: 1642 additions & 0 deletions
Large diffs are not rendered by default.

app/src/main/java/org/fossasia/openevent/general/OpenEventDatabase.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import org.fossasia.openevent.general.auth.UserDao
1313
import org.fossasia.openevent.general.event.Event
1414
import org.fossasia.openevent.general.event.EventDao
1515
import org.fossasia.openevent.general.event.EventIdConverter
16+
import org.fossasia.openevent.general.speakercall.SpeakersCall
17+
import org.fossasia.openevent.general.speakercall.SpeakersCallDao
1618
import org.fossasia.openevent.general.event.subtopic.EventSubTopicConverter
1719
import org.fossasia.openevent.general.event.topic.EventTopic
1820
import org.fossasia.openevent.general.event.topic.EventTopicConverter
@@ -41,7 +43,7 @@ import org.fossasia.openevent.general.ticket.TicketIdConverter
4143

4244
@Database(entities = [Event::class, User::class, SocialLink::class, Ticket::class, Attendee::class,
4345
EventTopic::class, Order::class, CustomForm::class, Speaker::class, SpeakerWithEvent::class, Sponsor::class,
44-
SponsorWithEvent::class, Session::class], version = 6)
46+
SponsorWithEvent::class, Session::class, SpeakersCall::class], version = 6)
4547
@TypeConverters(EventIdConverter::class, EventTopicConverter::class, EventTypeConverter::class,
4648
EventSubTopicConverter::class, TicketIdConverter::class, MicroLocationConverter::class,
4749
AttendeeIdConverter::class, ListAttendeeIdConverter::class, SessionTypeConverter::class, TrackConverter::class)
@@ -70,4 +72,6 @@ abstract class OpenEventDatabase : RoomDatabase() {
7072
abstract fun sponsorWithEventDao(): SponsorWithEventDao
7173

7274
abstract fun sessionDao(): SessionDao
75+
76+
abstract fun speakersCallDao(): SpeakersCallDao
7377
}

app/src/main/java/org/fossasia/openevent/general/di/Modules.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ import org.fossasia.openevent.general.sessions.Session
7878
import org.fossasia.openevent.general.sessions.SessionApi
7979
import org.fossasia.openevent.general.sessions.SessionService
8080
import org.fossasia.openevent.general.event.faq.EventFAQViewModel
81+
import org.fossasia.openevent.general.speakercall.SpeakersCall
8182
import org.fossasia.openevent.general.sessions.SessionViewModel
8283
import org.fossasia.openevent.general.sessions.microlocation.MicroLocation
8384
import org.fossasia.openevent.general.sessions.sessiontype.SessionType
@@ -86,6 +87,7 @@ import org.fossasia.openevent.general.settings.SettingsViewModel
8687
import org.fossasia.openevent.general.social.SocialLink
8788
import org.fossasia.openevent.general.social.SocialLinkApi
8889
import org.fossasia.openevent.general.social.SocialLinksService
90+
import org.fossasia.openevent.general.speakercall.SpeakersCallViewModel
8991
import org.fossasia.openevent.general.speakers.Speaker
9092
import org.fossasia.openevent.general.speakers.SpeakerApi
9193
import org.fossasia.openevent.general.speakers.SpeakerService
@@ -186,7 +188,7 @@ val apiModule = module {
186188
factory { AuthHolder(get()) }
187189
factory { AuthService(get(), get(), get(), get(), get()) }
188190

189-
factory { EventService(get(), get(), get(), get(), get(), get(), get(), get()) }
191+
factory { EventService(get(), get(), get(), get(), get(), get(), get(), get(), get()) }
190192
factory { SpeakerService(get(), get(), get()) }
191193
factory { SponsorService(get(), get(), get()) }
192194
factory { TicketService(get(), get()) }
@@ -224,6 +226,7 @@ val viewModelModule = module {
224226
viewModel { SponsorsViewModel(get(), get()) }
225227
viewModel { NotificationViewModel(get(), get(), get(), get()) }
226228
viewModel { AuthViewModel(get(), get(), get()) }
229+
viewModel { SpeakersCallViewModel(get(), get()) }
227230
}
228231

229232
val networkModule = module {
@@ -264,7 +267,7 @@ val networkModule = module {
264267
AttendeeId::class.java, Charge::class.java, Paypal::class.java, ConfirmOrder::class.java,
265268
CustomForm::class.java, EventLocation::class.java, EventType::class.java,
266269
EventSubTopic::class.java, Feedback::class.java, Speaker::class.java,
267-
Session::class.java, SessionType::class.java, MicroLocation::class.java,
270+
Session::class.java, SessionType::class.java, MicroLocation::class.java, SpeakersCall::class.java,
268271
Sponsor::class.java, EventFAQ::class.java, Notification::class.java, Track::class.java)
269272

270273
Retrofit.Builder()
@@ -343,4 +346,9 @@ val databaseModule = module {
343346
val database: OpenEventDatabase = get()
344347
database.sponsorDao()
345348
}
349+
350+
factory {
351+
val database: OpenEventDatabase = get()
352+
database.speakersCallDao()
353+
}
346354
}

app/src/main/java/org/fossasia/openevent/general/event/EventApi.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.fossasia.openevent.general.event
22

33
import io.reactivex.Single
4+
import org.fossasia.openevent.general.speakercall.SpeakersCall
45
import retrofit2.http.GET
56
import retrofit2.http.Path
67
import retrofit2.http.Query
@@ -21,4 +22,7 @@ interface EventApi {
2122

2223
@GET("events")
2324
fun eventsUnderUser(@Query("filter") eventId: String): Single<List<Event>>
25+
26+
@GET("events/{eventId}/speakers-call")
27+
fun getSpeakerCallForEvent(@Path("eventId") id: Long): Single<SpeakersCall>
2428
}

app/src/main/java/org/fossasia/openevent/general/event/EventDetailsFragment.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,13 @@ class EventDetailsFragment : Fragment() {
467467
eventViewModel.event.value?.let { eventViewModel.setFavorite(safeArgs.eventId, !it.favorite) }
468468
true
469469
}
470+
R.id.call_for_speakers -> {
471+
eventViewModel.event.value?.let {
472+
findNavController(rootView).navigate(EventDetailsFragmentDirections
473+
.actionEventDetailsToSpeakersCall(it.id, it.timezone))
474+
}
475+
true
476+
}
470477
R.id.event_share -> {
471478
eventViewModel.event.value?.let { EventUtils.share(it, rootView.eventImage) }
472479
return true

app/src/main/java/org/fossasia/openevent/general/event/EventService.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import org.fossasia.openevent.general.event.topic.EventTopicApi
1414
import org.fossasia.openevent.general.event.topic.EventTopicsDao
1515
import org.fossasia.openevent.general.event.types.EventType
1616
import org.fossasia.openevent.general.event.types.EventTypesApi
17+
import org.fossasia.openevent.general.speakercall.SpeakersCall
18+
import org.fossasia.openevent.general.speakercall.SpeakersCallDao
1719
import java.util.Date
1820

1921
class EventService(
@@ -24,7 +26,8 @@ class EventService(
2426
private val eventTypesApi: EventTypesApi,
2527
private val eventLocationApi: EventLocationApi,
2628
private val eventFeedbackApi: FeedbackApi,
27-
private val eventFAQApi: EventFAQApi
29+
private val eventFAQApi: EventFAQApi,
30+
private val speakersCallDao: SpeakersCallDao
2831
) {
2932

3033
fun getEvents(): Flowable<List<Event>> {
@@ -154,6 +157,13 @@ class EventService(
154157
}
155158
}
156159

160+
fun getSpeakerCall(id: Long): Single<SpeakersCall> =
161+
speakersCallDao.getSpeakerCall(id).onErrorResumeNext {
162+
eventApi.getSpeakerCallForEvent(id).doAfterSuccess {
163+
speakersCallDao.insertSpeakerCall(it)
164+
}
165+
}
166+
157167
private fun buildQuery(eventIds: List<Long>): String {
158168
var subQuery = ""
159169

app/src/main/java/org/fossasia/openevent/general/sessions/SessionFragment.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import org.fossasia.openevent.general.event.EventUtils
4747
import org.fossasia.openevent.general.utils.Utils
4848
import org.fossasia.openevent.general.utils.Utils.setToolbar
4949
import org.fossasia.openevent.general.utils.extensions.nonNull
50+
import org.fossasia.openevent.general.utils.stripHtml
5051
import org.jetbrains.anko.design.snackbar
5152
import org.koin.androidx.viewmodel.ext.android.viewModel
5253

@@ -220,7 +221,7 @@ class SessionFragment : Fragment() {
220221
when (description.isNullOrBlank()) {
221222
true -> rootView.sessionDetailAbstractContainer.visibility = View.GONE
222223
false -> {
223-
rootView.sessionDetailAbstract.text = description
224+
rootView.sessionDetailAbstract.text = description.stripHtml()
224225
val sessionAbstractClickListener = View.OnClickListener {
225226
if (rootView.sessionDetailAbstractSeeMore.text == getString(R.string.see_more)) {
226227
rootView.sessionDetailAbstractSeeMore.text = getString(R.string.see_less)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.fossasia.openevent.general.speakercall
2+
3+
import androidx.room.Entity
4+
import androidx.room.PrimaryKey
5+
import com.fasterxml.jackson.databind.PropertyNamingStrategy
6+
import com.fasterxml.jackson.databind.annotation.JsonNaming
7+
import com.github.jasminb.jsonapi.LongIdHandler
8+
import com.github.jasminb.jsonapi.annotations.Id
9+
import com.github.jasminb.jsonapi.annotations.Type
10+
11+
@Type("speakers-call")
12+
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy::class)
13+
@Entity
14+
data class SpeakersCall(
15+
@Id(LongIdHandler::class)
16+
@PrimaryKey
17+
val id: Long,
18+
val announcement: String,
19+
val startsAt: String,
20+
val endsAt: String,
21+
val hash: String?,
22+
val privacy: String?
23+
)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.fossasia.openevent.general.speakercall
2+
3+
import androidx.room.Dao
4+
import androidx.room.Insert
5+
import androidx.room.OnConflictStrategy.REPLACE
6+
import androidx.room.Query
7+
import io.reactivex.Single
8+
9+
@Dao
10+
interface SpeakersCallDao {
11+
12+
@Insert(onConflict = REPLACE)
13+
fun insertSpeakerCall(speakers: SpeakersCall)
14+
15+
@Query("SELECT * from SpeakersCall WHERE id = :id")
16+
fun getSpeakerCall(id: Long): Single<SpeakersCall>
17+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package org.fossasia.openevent.general.speakercall
2+
3+
import android.os.Bundle
4+
import android.view.LayoutInflater
5+
import android.view.MenuItem
6+
import android.view.View
7+
import android.view.ViewGroup
8+
import androidx.core.view.isVisible
9+
import androidx.fragment.app.Fragment
10+
import androidx.lifecycle.Observer
11+
import androidx.navigation.fragment.navArgs
12+
import kotlinx.android.synthetic.main.fragment_speakers_call.view.speakersCallDescription
13+
import kotlinx.android.synthetic.main.fragment_speakers_call.view.speakersCallEmptyView
14+
import kotlinx.android.synthetic.main.fragment_speakers_call.view.timeStatus
15+
import kotlinx.android.synthetic.main.fragment_speakers_call.view.speakersCallTimeDetail
16+
import kotlinx.android.synthetic.main.fragment_speakers_call.view.speakersCallContainer
17+
import kotlinx.android.synthetic.main.fragment_speakers_call.view.submitProposalButton
18+
import kotlinx.android.synthetic.main.fragment_speakers_call.view.progressBar
19+
import org.fossasia.openevent.general.R
20+
import org.fossasia.openevent.general.event.EventUtils.getEventDateTime
21+
import org.fossasia.openevent.general.event.EventUtils.getFormattedDate
22+
import org.koin.androidx.viewmodel.ext.android.viewModel
23+
import org.fossasia.openevent.general.utils.Utils.setToolbar
24+
import org.fossasia.openevent.general.utils.extensions.nonNull
25+
import org.fossasia.openevent.general.utils.stripHtml
26+
import org.jetbrains.anko.design.snackbar
27+
import org.threeten.bp.DateTimeUtils
28+
import java.util.Date
29+
30+
class SpeakersCallFragment : Fragment() {
31+
32+
private lateinit var rootView: View
33+
private val speakersCallViewModel by viewModel<SpeakersCallViewModel>()
34+
private val safeArgs: SpeakersCallFragmentArgs by navArgs()
35+
36+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
37+
rootView = inflater.inflate(R.layout.fragment_speakers_call, container, false)
38+
39+
setToolbar(activity, getString(R.string.call_for_speakers))
40+
setHasOptionsMenu(true)
41+
42+
speakersCallViewModel.errorMessage
43+
.nonNull()
44+
.observe(viewLifecycleOwner, Observer {
45+
rootView.snackbar(it)
46+
showEmptyView(true)
47+
})
48+
49+
speakersCallViewModel.progress
50+
.nonNull()
51+
.observe(viewLifecycleOwner, Observer {
52+
rootView.progressBar.isVisible = it
53+
})
54+
55+
speakersCallViewModel.speakersCall
56+
.nonNull()
57+
.observe(viewLifecycleOwner, Observer {
58+
loadSpeakersCallSection(it)
59+
showEmptyView(false)
60+
})
61+
62+
speakersCallViewModel.loadSpeakersCall(safeArgs.eventId)
63+
return rootView
64+
}
65+
66+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
67+
super.onViewCreated(view, savedInstanceState)
68+
rootView.submitProposalButton.setOnClickListener {
69+
// TODO: Set up submit proposal for event
70+
}
71+
}
72+
73+
override fun onOptionsItemSelected(item: MenuItem): Boolean {
74+
return when (item.itemId) {
75+
android.R.id.home -> {
76+
activity?.onBackPressed()
77+
true
78+
}
79+
else -> super.onOptionsItemSelected(item)
80+
}
81+
}
82+
83+
private fun showEmptyView(show: Boolean) {
84+
rootView.speakersCallContainer.visibility = if (show) View.GONE else View.VISIBLE
85+
rootView.speakersCallEmptyView.visibility = if (show) View.VISIBLE else View.GONE
86+
if (show) rootView.submitProposalButton.visibility = View.GONE
87+
}
88+
89+
private fun loadSpeakersCallSection(speakersCall: SpeakersCall) {
90+
val startAt = getEventDateTime(speakersCall.startsAt, safeArgs.timeZone)
91+
val endAt = getEventDateTime(speakersCall.endsAt, safeArgs.timeZone)
92+
val startTime: Date = DateTimeUtils.toDate(startAt.toInstant())
93+
val endTime: Date = DateTimeUtils.toDate(endAt.toInstant())
94+
val currentTime = Date()
95+
96+
rootView.speakersCallDescription.text = speakersCall.announcement.stripHtml()
97+
if (currentTime < startTime) {
98+
rootView.timeStatus.visibility = View.GONE
99+
rootView.speakersCallTimeDetail.text = "Call for speakers will open at ${getFormattedDate(startAt)}"
100+
rootView.submitProposalButton.visibility = View.GONE
101+
} else if (startTime < currentTime && currentTime < endTime) {
102+
rootView.timeStatus.setImageDrawable(resources.getDrawable(R.drawable.ic_speakers_call_open))
103+
rootView.speakersCallTimeDetail.text = "Call for speakers will open until ${getFormattedDate(endAt)}"
104+
rootView.submitProposalButton.visibility = View.VISIBLE
105+
} else {
106+
rootView.timeStatus.setImageDrawable(resources.getDrawable(R.drawable.ic_speakers_call_closed))
107+
rootView.speakersCallTimeDetail.text = "Call for speakers has closed at ${getFormattedDate(endAt)}"
108+
rootView.submitProposalButton.visibility = View.GONE
109+
}
110+
}
111+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.fossasia.openevent.general.speakercall
2+
3+
import androidx.lifecycle.LiveData
4+
import androidx.lifecycle.MutableLiveData
5+
import androidx.lifecycle.ViewModel
6+
import io.reactivex.disposables.CompositeDisposable
7+
import io.reactivex.rxkotlin.plusAssign
8+
import org.fossasia.openevent.general.R
9+
import org.fossasia.openevent.general.common.SingleLiveEvent
10+
import org.fossasia.openevent.general.data.Resource
11+
import org.fossasia.openevent.general.event.EventService
12+
import org.fossasia.openevent.general.utils.extensions.withDefaultSchedulers
13+
import timber.log.Timber
14+
15+
class SpeakersCallViewModel(
16+
private val eventService: EventService,
17+
private val resource: Resource
18+
) : ViewModel() {
19+
20+
private val compositeDisposable = CompositeDisposable()
21+
22+
private val mutableSpeakersCall = MutableLiveData<SpeakersCall>()
23+
val speakersCall: LiveData<SpeakersCall> = mutableSpeakersCall
24+
private val mutableError = SingleLiveEvent<String>()
25+
val errorMessage: LiveData<String> = mutableError
26+
private val mutableProgress = MutableLiveData<Boolean>(true)
27+
val progress: LiveData<Boolean> = mutableProgress
28+
29+
fun loadSpeakersCall(eventId: Long) {
30+
if (eventId == -1L) {
31+
mutableError.value = resource.getString(R.string.error_fetching_event_section_message,
32+
resource.getString(R.string.speakers_call))
33+
mutableProgress.value = false
34+
return
35+
}
36+
37+
compositeDisposable += eventService.getSpeakerCall(eventId)
38+
.withDefaultSchedulers()
39+
.doOnSubscribe {
40+
mutableProgress.value = true
41+
}
42+
.doFinally {
43+
mutableProgress.value = false
44+
}
45+
.subscribe({
46+
mutableSpeakersCall.value = it
47+
}, {
48+
mutableError.value = resource.getString(R.string.error_fetching_event_section_message,
49+
resource.getString(R.string.speakers_call))
50+
Timber.e(it, "Error fetching speakers call for event $eventId")
51+
})
52+
}
53+
54+
override fun onCleared() {
55+
super.onCleared()
56+
compositeDisposable.clear()
57+
}
58+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<vector android:height="24dp" android:viewportHeight="40"
2+
android:viewportWidth="40" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
3+
<path android:fillColor="#FF000000" android:pathData="M8.5,26.36A6.88,6.88 0,0 1,3.64 23a6.36,6.36 0,0 1,-0.79 -4.5A6.67,6.67 0,0 1,9.65 13a6.69,6.69 0,0 1,0.67 13.32c0.57,0.1 1.12,0.17 1.66,0.3a8.39,8.39 0,0 1,6.71 7.74c0,0.43 0.06,0.85 0.05,1.28A1.34,1.34 0,0 1,17.35 37H1.48A1.33,1.33 0,0 1,0.09 35.78,8.86 8.86,0 0,1 2.52,29a9.11,9.11 0,0 1,5.69 -2.59Z"/>
4+
<path android:fillColor="#FF000000" android:pathData="M39.91,17.65A20.07,20.07 0,0 1,34.1 31.86,1.35 1.35,0 1,1 32.17,30 18.18,18.18 0,0 0,35 26.13,17.53 17.53,0 0,0 32.62,5.81l-0.53,-0.57a1.33,1.33 0,0 1,0.06 -1.89,1.26 1.26,0 0,1 1.85,0A31.71,31.71 0,0 1,36.55 6.5a19.38,19.38 0,0 1,3.3 9.64c0,0.26 0,0.52 0.05,0.78S39.91,17.4 39.91,17.65Z"/>
5+
<path android:fillColor="#FF000000" android:pathData="M31.5,17.29a12,12 0,0 1,-3.33 8.57,1.35 1.35,0 1,1 -2,-1.85 9,9 0,0 0,2.58 -6.59,8.84 8.84,0 0,0 -2.52,-6.06 1.45,1.45 0,0 1,-0.48 -1.29,1.34 1.34,0 0,1 2.27,-0.73 11.44,11.44 0,0 1,3.22 6.13C31.41,16.19 31.45,16.91 31.5,17.29Z"/>
6+
<path android:fillColor="#FF000000" android:pathData="M24.88,17.66A6.3,6.3 0,0 1,23.14 22a1.35,1.35 0,1 1,-2 -1.81,3.61 3.61,0 0,0 0,-5.09 1.35,1.35 0,0 1,0.06 -1.95,1.33 1.33,0 0,1 1.91,0.12A6.36,6.36 0,0 1,24.88 17.66Z"/>
7+
</vector>

0 commit comments

Comments
 (0)