Skip to content

Commit

Permalink
DO NOT MERGE: androidApp: Initial support for tickets
Browse files Browse the repository at this point in the history
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
  • Loading branch information
theimpulson committed Oct 14, 2024
1 parent 00d094a commit 8c0bad2
Show file tree
Hide file tree
Showing 16 changed files with 601 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.navigation.compose.rememberNavController
import app.opass.ccip.android.ui.extensions.currentEventId
import app.opass.ccip.android.ui.extensions.sharedPreferences
import app.opass.ccip.android.ui.navigation.Screen
import app.opass.ccip.android.ui.navigation.SetupNavGraph
import app.opass.ccip.android.ui.theme.OPassTheme
import app.opass.ccip.android.utils.Preferences.CURRENT_EVENT_ID
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
Expand All @@ -24,7 +24,7 @@ class MainActivity : ComponentActivity() {
enableEdgeToEdge()
super.onCreate(savedInstanceState)

val currentEventId = sharedPreferences.getString(CURRENT_EVENT_ID, null)
val currentEventId = sharedPreferences.currentEventId

setContent {
OPassTheme {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

package app.opass.ccip.android.ui.components

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
Expand All @@ -15,25 +17,41 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController

@Composable
@OptIn(ExperimentalMaterial3Api::class)
fun TopAppBar(
title: String = String(),
subtitle: String = String(),
navHostController: NavHostController? = null,
navigationIcon: @Composable () -> Unit = { DefaultNavigationIcon(navHostController) },
actions: @Composable() (RowScope.() -> Unit) = {}
actions: @Composable (RowScope.() -> Unit) = {}
) {
CenterAlignedTopAppBar(
title = {
Text(
text = title,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.titleLarge
)
Column(
verticalArrangement = Arrangement.spacedBy(5.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = title,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.titleLarge
)
if (subtitle.isNotBlank()) {
Text(
text = subtitle,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.bodySmall
)
}
}
},
navigationIcon = navigationIcon,
actions = actions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2024 OPass
* SPDX-License-Identifier: GPL-3.0-only
*/

package app.opass.ccip.android.ui.extensions

import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import app.opass.ccip.android.ui.navigation.Screen

fun NavHostController.popBackToEventScreen(eventId: String) {
navigate(Screen.Event(eventId)) {
popUpTo(graph.findStartDestination().id) { inclusive = true }
launchSingleTop = true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: 2024 OPass
* SPDX-License-Identifier: GPL-3.0-only
*/

package app.opass.ccip.android.ui.extensions

import android.content.SharedPreferences
import androidx.core.content.edit

private const val CURRENT_EVENT_ID = "CURRENT_EVENT_ID"
private const val TOKEN = "TOKEN"

val SharedPreferences.currentEventId: String?
get() = this.getString(CURRENT_EVENT_ID, null)

fun SharedPreferences.saveCurrentEventId(eventId: String) {
return this.edit { putString(CURRENT_EVENT_ID, eventId) }
}

fun SharedPreferences.getToken(eventId: String): String? {
return this.getString("${eventId}_$TOKEN", null)
}

fun SharedPreferences.saveToken(eventId: String, token: String) {
return this.edit { putString("${eventId}_$TOKEN", token) }
}

fun SharedPreferences.removeToken(eventId: String) {
return this.edit { remove("${eventId}_$TOKEN") }
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import app.opass.ccip.android.ui.screens.event.EventScreen
import app.opass.ccip.android.ui.screens.eventpreview.EventPreviewScreen
import app.opass.ccip.android.ui.screens.schedule.ScheduleScreen
import app.opass.ccip.android.ui.screens.session.SessionScreen
import app.opass.ccip.android.ui.screens.ticket.TicketScreen

@Composable
fun SetupNavGraph(navHostController: NavHostController, startDestination: Screen) {
Expand Down Expand Up @@ -51,6 +52,10 @@ fun SetupNavGraph(navHostController: NavHostController, startDestination: Screen
composable<Screen.Session> { backStackEntry ->
backStackEntry.toRoute<Screen.Session>().SessionScreen(navHostController)
}

composable<Screen.Ticket> { backStackEntry ->
backStackEntry.toRoute<Screen.Ticket>().TicketScreen(navHostController)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,10 @@ sealed class Screen(@StringRes val title: Int, @DrawableRes val icon: Int) {
title = R.string.session,
icon = R.drawable.ic_podium
)

@Serializable
data class Ticket(val eventId: String) : Screen(
title = R.string.ticket,
icon = R.drawable.ic_ticket
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ fun Screen.Event.EventScreen(
val windowWidth = currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass
val context = LocalContext.current
val eventConfig by viewModel.eventConfig.collectAsStateWithLifecycle()
val attendee by viewModel.attendee.collectAsStateWithLifecycle()

var shouldShowBottomSheet by rememberSaveable { mutableStateOf(false) }

Expand All @@ -87,6 +88,7 @@ fun Screen.Event.EventScreen(
topBar = {
TopAppBar(
title = eventConfig?.name ?: String(),
subtitle = attendee?.userId ?: String(),
navigationIcon = {
IconButton(onClick = { shouldShowBottomSheet = true }) {
Icon(
Expand Down Expand Up @@ -127,8 +129,14 @@ fun Screen.Event.EventScreen(
maxItemsInEachRow = if (windowWidth == WindowWidthSizeClass.COMPACT) 4 else 6,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
// TODO: Show and hide features based on roles
eventConfig!!.features.fastForEach { feature ->

// Return early if feature is limited to certain attendee roles
// Roles requires attendee to be logged in by verifying their ticket
if (!feature.roles.isNullOrEmpty() && !feature.roles!!.contains(attendee?.role)) {
return@fastForEach
}

when (feature.type) {
FeatureType.ANNOUNCEMENT -> {
FeatureItem(
Expand Down Expand Up @@ -202,7 +210,9 @@ fun Screen.Event.EventScreen(
FeatureItem(
label = stringResource(id = R.string.ticket),
iconRes = R.drawable.ic_ticket
)
) {
navHostController.navigate(Screen.Ticket(this@EventScreen.id))
}
}

FeatureType.VENUE -> {
Expand Down Expand Up @@ -260,6 +270,7 @@ private fun HeaderImage(logoUrl: String?) {
.padding(horizontal = 32.dp)
.aspectRatio(2.0f)
.heightIn(max = 180.dp)
.clip(RoundedCornerShape(10.dp))
.shimmer(logoUrl == null),
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.primary)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@

package app.opass.ccip.android.ui.screens.event

import android.content.Context
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.opass.ccip.android.ui.extensions.getToken
import app.opass.ccip.android.ui.extensions.sharedPreferences
import app.opass.ccip.helpers.PortalHelper
import app.opass.ccip.network.models.eventconfig.EventConfig
import app.opass.ccip.network.models.fastpass.Attendee
import app.opass.ccip.network.models.schedule.Schedule
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
Expand All @@ -21,7 +26,8 @@ import java.text.SimpleDateFormat
@HiltViewModel
class EventViewModel @Inject constructor(
val sdf: SimpleDateFormat,
private val portalHelper: PortalHelper
private val portalHelper: PortalHelper,
@ApplicationContext private val context: Context
): ViewModel() {

private val TAG = EventViewModel::class.java.simpleName
Expand All @@ -32,6 +38,9 @@ class EventViewModel @Inject constructor(
private val _schedule: MutableStateFlow<Schedule?> = MutableStateFlow(null)
val schedule = _schedule.asStateFlow()

private val _attendee: MutableStateFlow<Attendee?> = MutableStateFlow(null)
val attendee = _attendee.asStateFlow()

private val _isRefreshing = MutableStateFlow(false)
val isRefreshing = _isRefreshing.asStateFlow()

Expand All @@ -40,6 +49,9 @@ class EventViewModel @Inject constructor(
try {
_isRefreshing.value = true
_eventConfig.value = portalHelper.getEventConfig(eventId, forceReload)

// Fetch attendee as well
getAttendee(eventId, forceReload)
} catch (exception: Exception) {
Log.e(TAG, "Failed to fetch event config", exception)
_eventConfig.value = null
Expand All @@ -59,4 +71,20 @@ class EventViewModel @Inject constructor(
}
}
}

private fun getAttendee(eventId: String, forceReload: Boolean = false) {
viewModelScope.launch {
try {
val token = context.sharedPreferences.getToken(eventId)
if (token != null) {
_attendee.value = portalHelper.getAttendee(eventId, token, forceReload)
} else {
_attendee.value = null
}
} catch (exception: Exception) {
Log.e(TAG, "Failed to fetch attendee", exception)
_attendee.value = null
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,15 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.edit
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import app.opass.ccip.android.R
import app.opass.ccip.android.ui.extensions.popBackToEventScreen
import app.opass.ccip.android.ui.extensions.saveCurrentEventId
import app.opass.ccip.android.ui.extensions.sharedPreferences
import app.opass.ccip.android.ui.extensions.shimmer
import app.opass.ccip.android.ui.navigation.Screen
import app.opass.ccip.android.utils.Preferences.CURRENT_EVENT_ID
import app.opass.ccip.network.models.event.Event
import coil.compose.SubcomposeAsyncImage
import coil.request.CachePolicy
Expand Down Expand Up @@ -94,13 +93,8 @@ fun Screen.EventPreview.EventPreviewScreen(
items(items = list!!, key = { e -> e.id }) { event: Event ->
EventPreviewItem(name = event.name, logoUrl = event.logoUrl) {
onEventSelected()
sharedPreferences.edit { putString(CURRENT_EVENT_ID, event.id) }
navHostController.navigate(Screen.Event(event.id)) {
popUpTo(navHostController.graph.findStartDestination().id) {
inclusive = true
}
launchSingleTop = true
}
sharedPreferences.saveCurrentEventId(event.id)
navHostController.popBackToEventScreen(event.id)
}
}
}
Expand Down Expand Up @@ -160,7 +154,7 @@ fun Screen.EventPreview.EventPreviewScreen(
key = { e -> e.id }
) { event: Event ->
EventPreviewItem(name = event.name, logoUrl = event.logoUrl) {
sharedPreferences.edit { putString(CURRENT_EVENT_ID, event.id) }
sharedPreferences.saveCurrentEventId(event.id)
navHostController.navigate(Screen.Event(event.id))
}
}
Expand Down
Loading

0 comments on commit 8c0bad2

Please sign in to comment.