-
Notifications
You must be signed in to change notification settings - Fork 557
/
EventDetailsFragment.kt
448 lines (399 loc) · 18.5 KB
/
EventDetailsFragment.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
package org.fossasia.openevent.general.event
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.CalendarContract
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.navigation.Navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import com.squareup.picasso.Callback
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.content_event.aboutEventContainer
import kotlinx.android.synthetic.main.content_event.locationContainer
import kotlinx.android.synthetic.main.content_event.organizerContainer
import kotlinx.android.synthetic.main.content_event.similarEventsContainer
import kotlinx.android.synthetic.main.content_event.view.eventDateDetailsFirst
import kotlinx.android.synthetic.main.content_event.view.eventDateDetailsSecond
import kotlinx.android.synthetic.main.content_event.view.eventDescription
import kotlinx.android.synthetic.main.content_event.view.eventLocationLinearLayout
import kotlinx.android.synthetic.main.content_event.view.eventLocationTextView
import kotlinx.android.synthetic.main.content_event.view.eventName
import kotlinx.android.synthetic.main.content_event.view.eventOrganiserDescription
import kotlinx.android.synthetic.main.content_event.view.eventOrganiserName
import kotlinx.android.synthetic.main.content_event.view.eventTimingLinearLayout
import kotlinx.android.synthetic.main.content_event.view.imageMap
import kotlinx.android.synthetic.main.content_event.view.locationUnderMap
import kotlinx.android.synthetic.main.content_event.view.eventImage
import kotlinx.android.synthetic.main.content_event.view.feedbackContainer
import kotlinx.android.synthetic.main.content_event.view.feedbackRv
import kotlinx.android.synthetic.main.content_event.view.organizerLogoIcon
import kotlinx.android.synthetic.main.content_event.view.nestedContentEventScroll
import kotlinx.android.synthetic.main.content_event.view.organizerName
import kotlinx.android.synthetic.main.content_event.view.refundPolicy
import kotlinx.android.synthetic.main.content_event.view.seeMore
import kotlinx.android.synthetic.main.content_event.view.seeMoreOrganizer
import kotlinx.android.synthetic.main.content_event.view.organizerContainer
import kotlinx.android.synthetic.main.fragment_event.view.buttonTickets
import kotlinx.android.synthetic.main.fragment_event.view.eventErrorCard
import kotlinx.android.synthetic.main.fragment_event.view.container
import kotlinx.android.synthetic.main.content_fetching_event_error.view.retry
import org.fossasia.openevent.general.CircleTransform
import org.fossasia.openevent.general.R
import org.fossasia.openevent.general.about.AboutEventFragmentArgs
import org.fossasia.openevent.general.event.EventUtils.loadMapUrl
import org.fossasia.openevent.general.event.feedback.FeedbackRecyclerAdapter
import org.fossasia.openevent.general.event.topic.SimilarEventsFragment
import org.fossasia.openevent.general.social.SocialLinksFragment
import org.fossasia.openevent.general.ticket.TicketsFragmentArgs
import org.fossasia.openevent.general.utils.Utils
import org.fossasia.openevent.general.utils.Utils.getAnimSlide
import org.fossasia.openevent.general.utils.Utils.requireDrawable
import org.fossasia.openevent.general.utils.extensions.nonNull
import org.fossasia.openevent.general.utils.nullToEmpty
import org.fossasia.openevent.general.utils.stripHtml
import org.koin.androidx.viewmodel.ext.android.viewModel
import timber.log.Timber
import java.util.Currency
import org.fossasia.openevent.general.utils.Utils.setToolbar
import java.lang.Exception
const val EVENT_ID = "eventId"
const val EVENT_TOPIC_ID = "eventTopicId"
const val EVENT_LOCATION = "eventLocation"
class EventDetailsFragment : Fragment() {
private val eventViewModel by viewModel<EventDetailsViewModel>()
private val safeArgs: EventDetailsFragmentArgs by navArgs()
private val feedbackAdapter = FeedbackRecyclerAdapter()
private lateinit var rootView: View
private var eventTopicId: Long? = null
private var eventLocation: String? = null
private lateinit var eventShare: Event
private var currency: String? = null
private val LINE_COUNT: Int = 3
private val LINE_COUNT_ORGANIZER: Int = 2
private var menuActionBar: Menu? = null
private var title: String = ""
private var runOnce: Boolean = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
eventViewModel.event
.nonNull()
.observe(this, Observer {
loadEvent(it)
eventShare = it
title = eventShare.name
// Update favorite icon and external event url menu option
activity?.invalidateOptionsMenu()
if (runOnce) {
loadSocialLinksFragment()
loadSimilarEventsFragment()
}
runOnce = false
Timber.d("Fetched events of id %d", safeArgs.eventId)
showEventErrorScreen(false)
setHasOptionsMenu(true)
})
eventViewModel.loadEventFeedback(safeArgs.eventId)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
rootView = inflater.inflate(R.layout.fragment_event, container, false)
setToolbar(activity)
setHasOptionsMenu(true)
rootView.feedbackRv.layoutManager = LinearLayoutManager(context)
rootView.feedbackRv.adapter = feedbackAdapter
rootView.buttonTickets.setOnClickListener {
loadTicketFragment()
}
eventViewModel.error
.nonNull()
.observe(viewLifecycleOwner, Observer {
showEventErrorScreen(true)
})
eventViewModel.eventFeedback.observe(viewLifecycleOwner, Observer {
feedbackAdapter.addAll(it)
if (it.isEmpty()) {
rootView.feedbackContainer.visibility = View.GONE
} else {
rootView.feedbackContainer.visibility = View.VISIBLE
}
})
eventViewModel.loadEvent(safeArgs.eventId)
rootView.retry.setOnClickListener {
eventViewModel.loadEvent(safeArgs.eventId)
}
// Set toolbar title to event name
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
rootView.nestedContentEventScroll.setOnScrollChangeListener { _, _, scrollY, _, _ ->
if (scrollY > rootView.eventName.height + rootView.eventImage.height)
/*Toolbar title set to name of Event if scrolled more than
combined height of eventImage and eventName views*/
setToolbar(activity, title)
else
// Toolbar title set to an empty string
setToolbar(activity)
}
}
return rootView
}
private fun loadEvent(event: Event) {
val startsAt = EventUtils.getEventDateTime(event.startsAt, event.timezone)
val endsAt = EventUtils.getEventDateTime(event.endsAt, event.timezone)
rootView.eventName.text = event.name
// Organizer Section
if (!event.organizerName.isNullOrEmpty()) {
rootView.eventOrganiserName.text = "by " + event.organizerName.nullToEmpty()
setTextField(rootView.eventOrganiserDescription, event.organizerDescription?.stripHtml()?.trim())
rootView.organizerName.text = event.organizerName.nullToEmpty()
rootView.eventOrganiserName.isVisible = true
organizerContainer.isVisible = true
Picasso.get()
.load(event.logoUrl)
.placeholder(requireDrawable(requireContext(), R.drawable.ic_person_black))
.transform(CircleTransform())
.into(rootView.organizerLogoIcon)
val organizerDescriptionListener = View.OnClickListener {
if (rootView.seeMoreOrganizer.text == getString(R.string.see_more)) {
rootView.seeMoreOrganizer.text = getString(R.string.see_less)
rootView.eventOrganiserDescription.minLines = 0
rootView.eventOrganiserDescription.maxLines = Int.MAX_VALUE
} else {
rootView.seeMoreOrganizer.text = getString(R.string.see_more)
rootView.eventOrganiserDescription.setLines(3)
}
}
rootView.eventOrganiserDescription.post {
if (rootView.eventOrganiserDescription.lineCount > LINE_COUNT_ORGANIZER) {
rootView.seeMoreOrganizer.isVisible = true
// Set up toggle organizer description
rootView.seeMoreOrganizer.setOnClickListener(organizerDescriptionListener)
rootView.eventOrganiserDescription.setOnClickListener(organizerDescriptionListener)
}
}
} else {
rootView.organizerContainer.isVisible = false
}
currency = Currency.getInstance(event.paymentCurrency).symbol
// About event on-click
val aboutEventOnClickListener = View.OnClickListener {
AboutEventFragmentArgs.Builder()
.setEventId(safeArgs.eventId)
.build()
.toBundle()
.also { bundle ->
findNavController(rootView).navigate(R.id.aboutEventFragment, bundle, getAnimSlide())
}
}
// Event Description Section
val description = event.description.nullToEmpty().stripHtml()
if (!description.isNullOrEmpty()) {
setTextField(rootView.eventDescription, description)
rootView.eventDescription.post {
if (rootView.eventDescription.lineCount > LINE_COUNT) {
rootView.seeMore.isVisible = true
// start about fragment
rootView.eventDescription.setOnClickListener(aboutEventOnClickListener)
rootView.seeMore.setOnClickListener(aboutEventOnClickListener)
}
}
} else {
aboutEventContainer.isVisible = false
}
// Map Section
if (!event.locationName.isNullOrEmpty()) {
locationContainer.isVisible = true
rootView.eventLocationTextView.text = event.locationName
}
// load location to map
val mapClickListener = View.OnClickListener { startMap(event) }
val locationNameIsEmpty = event.locationName.isNullOrEmpty()
locationContainer.isVisible = !locationNameIsEmpty
rootView.eventLocationLinearLayout.isVisible = !locationNameIsEmpty
rootView.locationUnderMap.isVisible = !locationNameIsEmpty
rootView.imageMap.isVisible = !locationNameIsEmpty
if (!locationNameIsEmpty) {
rootView.locationUnderMap.text = event.locationName
rootView.imageMap.setOnClickListener(mapClickListener)
rootView.eventLocationLinearLayout.setOnClickListener(mapClickListener)
Picasso.get()
.load(eventViewModel.loadMap(event))
.placeholder(R.drawable.ic_map_black)
.error(R.drawable.ic_map_black)
.into(rootView.imageMap)
}
// Date and Time section
rootView.eventDateDetailsFirst.text = EventUtils.getFormattedEventDateTimeRange(startsAt, endsAt)
rootView.eventDateDetailsSecond.text = EventUtils.getFormattedEventDateTimeRangeSecond(startsAt, endsAt)
// Refund policy
rootView.refundPolicy.text = event.refundPolicy
// Similar Events Section
if (event.eventTopic != null || !event.locationName.isNullOrBlank() ||
!event.searchableLocationName.isNullOrBlank()) {
similarEventsContainer.isVisible = true
eventTopicId = event.eventTopic?.id
eventLocation =
if (event.searchableLocationName.isNullOrBlank()) event.locationName
else event.searchableLocationName
}
// Set Cover Image
event.originalImageUrl?.let {
Picasso.get()
.load(it)
.placeholder(R.drawable.header)
.into(rootView.eventImage, object : Callback {
override fun onSuccess() {
rootView.eventImage.tag = "image_loading_successful"
}
override fun onError(e: Exception?) {
Timber.e(e)
}
})
}
// Add event to Calendar
val dateClickListener = View.OnClickListener { startCalendar(event) }
rootView.eventTimingLinearLayout.setOnClickListener(dateClickListener)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
activity?.onBackPressed()
true
}
R.id.add_to_calendar -> {
// Add event to Calendar
startCalendar(eventShare)
true
}
R.id.report_event -> {
reportEvent(eventShare)
true
}
R.id.open_external_event_url -> {
eventShare.externalEventUrl?.let { Utils.openUrl(requireContext(), it) }
true
}
R.id.favorite_event -> {
eventViewModel.setFavorite(safeArgs.eventId, !(eventShare.favorite))
true
}
R.id.event_share -> {
EventUtils.share(eventShare, rootView.eventImage)
return true
}
else -> super.onOptionsItemSelected(item)
}
}
private fun setTextField(textView: TextView, value: String?) {
when (value.isNullOrBlank()) {
true -> textView.isVisible = false
false -> textView.text = value
}
}
private fun startCalendar(event: Event) {
val intent = Intent(Intent.ACTION_INSERT)
intent.type = "vnd.android.cursor.item/event"
intent.putExtra(CalendarContract.Events.TITLE, event.name)
intent.putExtra(CalendarContract.Events.DESCRIPTION, event.description?.stripHtml())
intent.putExtra(CalendarContract.Events.EVENT_LOCATION, event.locationName)
intent.putExtra(CalendarContract.Events.CALENDAR_TIME_ZONE, event.timezone)
intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME,
EventUtils.getTimeInMilliSeconds(event.startsAt, event.timezone))
intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME,
EventUtils.getTimeInMilliSeconds(event.endsAt, event.timezone))
startActivity(intent)
}
private fun reportEvent(event: Event) {
val email = "support@eventyay.com"
val subject = "Report of ${event.name} (${event.identifier})"
val body = "Let us know what's wrong"
val emailIntent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:$email"))
emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject)
emailIntent.putExtra(Intent.EXTRA_TEXT, body)
startActivity(Intent.createChooser(emailIntent, "Chooser Title"))
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.event_details, menu)
menuActionBar = menu
}
override fun onPrepareOptionsMenu(menu: Menu) {
if (::eventShare.isInitialized) {
if (eventShare.externalEventUrl == null) {
menu.findItem(R.id.open_external_event_url).isVisible = false
}
setFavoriteIconFilled(eventShare.favorite)
}
super.onPrepareOptionsMenu(menu)
}
private fun loadTicketFragment() {
TicketsFragmentArgs.Builder()
.setEventId(safeArgs.eventId)
.setCurrency(currency)
.build()
.toBundle()
.also { bundle ->
findNavController(rootView).navigate(R.id.ticketsFragment, bundle, getAnimSlide())
}
}
private fun loadSocialLinksFragment() {
// Initialise SocialLinks Fragment
val socialLinksFragemnt = SocialLinksFragment()
val bundle = Bundle()
bundle.putLong(EVENT_ID, safeArgs.eventId)
socialLinksFragemnt.arguments = bundle
val transaction = childFragmentManager.beginTransaction()
transaction.add(R.id.frameContainerSocial, socialLinksFragemnt).commit()
}
private fun loadSimilarEventsFragment() {
// Initialise SimilarEvents Fragment
val similarEventsFragment = SimilarEventsFragment()
val bundle = Bundle()
bundle.putLong(EVENT_ID, safeArgs.eventId)
eventTopicId?.let { bundle.putLong(EVENT_TOPIC_ID, it) }
eventLocation?.let { bundle.putString(EVENT_LOCATION, it) }
similarEventsFragment.arguments = bundle
childFragmentManager.beginTransaction()
.replace(R.id.frameContainerSimilarEvents, similarEventsFragment).commit()
}
private fun startMap(event: Event) {
// start map intent
val mapUrl = loadMapUrl(event)
val mapIntent = Intent(Intent.ACTION_VIEW, Uri.parse(mapUrl))
val packageManager = activity?.packageManager
if (packageManager != null && mapIntent.resolveActivity(packageManager) != null) {
startActivity(mapIntent)
}
}
private fun setFavoriteIconFilled(filled: Boolean) {
val id = when {
filled -> R.drawable.ic_baseline_favorite_white
else -> R.drawable.ic_baseline_favorite_border_white
}
menuActionBar?.findItem(R.id.favorite_event)?.icon = ContextCompat.getDrawable(requireContext(), id)
}
private fun showEventErrorScreen(show: Boolean) {
rootView.container.isVisible = !show
rootView.eventErrorCard.isVisible = show
val menuItemSize = menuActionBar?.size() ?: 0
for (i in 0..(menuItemSize - 1)) {
menuActionBar?.getItem(i)?.isVisible = !show
}
}
override fun onDestroy() {
Picasso.get().cancelRequest(rootView.eventImage)
super.onDestroy()
}
}