Skip to content

Commit

Permalink
Merge pull request #487 from HackIllinois/staff-attendance-scanner
Browse files Browse the repository at this point in the history
Staff attendance QR code scanner
  • Loading branch information
leahlud authored Sep 27, 2023
2 parents 0bb4383 + c6ba419 commit 75da02c
Show file tree
Hide file tree
Showing 42 changed files with 449 additions and 366 deletions.
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
android:roundIcon="@mipmap/ic_launcher_23_round"
android:theme="@style/AppTheme">


<receiver android:name=".notifications.NotificationReceiver" />
<receiver
android:exported="true"
Expand Down
6 changes: 5 additions & 1 deletion app/src/main/java/org/hackillinois/android/API.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ import org.hackillinois.android.model.profile.ProfileList
import org.hackillinois.android.model.projects.ProjectsList
import org.hackillinois.android.notifications.DeviceToken
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.*

interface API {

// AUTH
// note: @GET("auth/login/{provider}/") is not part of this file because the URL
// note: @GET("auth/login/{provider}/") is not called in this file because the URL
// is directly accessed in the LoginActivity.kt file to get to the OAuth page

@GET("auth/roles/")
Expand Down Expand Up @@ -50,6 +51,9 @@ interface API {
@POST("event/staff/checkin/")
suspend fun checkInUserAsStaff(@Body userTokenEventIdPair: UserTokenEventIdPair): EventCheckInAsStaffResponse

@POST("event/staff/attendance/")
suspend fun staffMeetingCheckIn(@Body eventId: MeetingEventId): Response<Void>

// NOTIFICATIONS

@POST("notifications/device/")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,34 +72,6 @@ data class Event(
return list
}

// fun getIndoorMapAndDirectionInfo() = locations.flatMap {
// it.tags.map { tag -> getMapAndDirectionForTag(tag) }
// }

// @Ignore
// private val ecebLatLng = LatLng(40.115071, -88.228196)
// @Ignore
// private val siebelLatLng = LatLng(40.113833, -88.224903)
// @Ignore
// private val kenneyLatLng = LatLng(40.1131422, -88.229537)
// @Ignore
// private val dclLatLng = LatLng(40.1133081, -88.2288307)

// @Ignore
// private val mapAndDirectionInfoMap: Map<String, IndoorMapAndDirectionInfo> = mapOf(
// "KENNEY" to IndoorMapAndDirectionInfo("Kenney", R.drawable.kenney, kenneyLatLng),
// "DCL" to IndoorMapAndDirectionInfo("DCL", R.drawable.dcl, dclLatLng),
// "ECEB1" to IndoorMapAndDirectionInfo("ECEB Floor 1", R.drawable.eceb_floor_1, ecebLatLng),
// "ECEB2" to IndoorMapAndDirectionInfo("ECEB Floor 2", R.drawable.eceb_floor_2, ecebLatLng),
// "ECEB3" to IndoorMapAndDirectionInfo("ECEB Floor 3", R.drawable.eceb_floor_3, ecebLatLng),
// "SIEBEL0" to IndoorMapAndDirectionInfo("Siebel Basement", R.drawable.siebel_floor_0, siebelLatLng),
// "SIEBEL1" to IndoorMapAndDirectionInfo("Siebel Floor 1", R.drawable.siebel_floor_1, siebelLatLng),
// "SIEBEL2" to IndoorMapAndDirectionInfo("Siebel Floor 2", R.drawable.siebel_floor_2, siebelLatLng)
// )

// private fun getMapAndDirectionForTag(tag: String) = mapAndDirectionInfoMap[tag]
// ?: IndoorMapAndDirectionInfo("Siebel Floor 1", R.drawable.siebel_floor_1, siebelLatLng)

override fun getType() = 1
}

Expand All @@ -118,6 +90,9 @@ data class IndoorMapAndDirectionInfo(

data class EventCode(val code: String)

data class MeetingEventId(val eventId: String)

data class MeetingCheckInResponse(var status: String)
data class EventCheckInResponse(
val newPoints: Int,
val totalPoints: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,5 @@ data class Roles(
@ColumnInfo(name = "key")
var key = 1

fun isStaff() = roles.contains("Staff")
fun isAdmin() = roles.contains("Admin")
fun isStaff() = roles.contains("STAFF")
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,10 @@ class EventRepository {
return responseString
}
suspend fun checkInEvent(code: String): EventCheckInResponse {
Log.d("send event token", code)
var apiResponse: EventCheckInResponse = EventCheckInResponse(-1, -1, "")
var apiResponse = EventCheckInResponse(-1, -1, "")

withContext(Dispatchers.IO) {
try {
Log.d("Sending code: ", code)

apiResponse = App.getAPI().eventCodeCheckIn(EventCode(code))
Log.d("code sent!", apiResponse.toString())
} catch (e: Exception) {
Expand All @@ -76,6 +73,21 @@ class EventRepository {
return apiResponse
}

suspend fun checkInMeeting(eventId: String): MeetingCheckInResponse {
var apiResponse = MeetingCheckInResponse("")

withContext(Dispatchers.IO) {
try {
val body = MeetingEventId(eventId)
App.getAPI().staffMeetingCheckIn(body)
apiResponse.status = "Success" // no response is sent from API, so just manually done
} catch (e: Exception) {
Log.d("STAFF MEETING API ERROR", "${e.message}")
}
}
return apiResponse
}

suspend fun checkInEventAsStaff(userToken: String, eventId: String): EventCheckInAsStaffResponse {
val userTokenEventIdPair = UserTokenEventIdPair(userToken, eventId)
Log.d("send event token", userTokenEventIdPair.toString())
Expand All @@ -88,10 +100,10 @@ class EventRepository {
false,
RegistrationData(
AttendeeData(
listOf()
)
)
)
listOf(),
),
),
),
)

withContext(Dispatchers.IO) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ class LoginActivity : AppCompatActivity() {

// verify user's roles are correct
if (getOAuthProvider() == "google") {
verifyRole(api, jwt.token, "Staff")
verifyRole(api, jwt.token, "STAFF")
} else {
verifyRole(api, jwt.token, "Attendee")
verifyRole(api, jwt.token, "ATTENDEE")
}
} catch (e: Exception) {
showFailedToLogin(e.message)
Expand All @@ -97,12 +97,13 @@ class LoginActivity : AppCompatActivity() {
GlobalScope.launch {
try {
val loginRoles: Roles = api.roles()
Log.d("ROLES", "${loginRoles.roles}")
// Check if user's roles are correct. If not, display corresponding error message
if (loginRoles.roles.contains(role)) {
JWTUtilities.writeJWT(applicationContext, jwt) // save JWT to sharedPreferences for auto-login in the future
launchMainActivity()
} else {
if (role == "Staff") {
if (role == "STAFF") {
showFailedToLoginStaff()
} else {
showFailedToLoginAttendee()
Expand Down
68 changes: 42 additions & 26 deletions app/src/main/java/org/hackillinois/android/view/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.ImageButton
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import androidx.lifecycle.ViewModelProviders
import androidx.lifecycle.ViewModelProvider
import com.google.firebase.FirebaseApp
import com.google.firebase.messaging.FirebaseMessaging
import kotlinx.android.synthetic.main.activity_main.*
Expand All @@ -26,6 +26,7 @@ import org.hackillinois.android.view.leaderboard.LeaderboardFragment
import org.hackillinois.android.view.onboarding.OnboardingActivity
import org.hackillinois.android.view.profile.ProfileFragment
import org.hackillinois.android.view.scanner.ScannerFragment
import org.hackillinois.android.view.scanner.StaffScannerFragment
import org.hackillinois.android.view.schedule.ScheduleFragment
import org.hackillinois.android.viewmodel.MainViewModel
import kotlin.concurrent.thread
Expand All @@ -35,8 +36,7 @@ class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel

private var currentSelection = 0

// var groupMatchingSelectedProfile: Profile? = null
private var onScanner = false

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -48,31 +48,35 @@ class MainActivity : AppCompatActivity() {
val startFragment = HomeFragment()
supportFragmentManager.beginTransaction().replace(R.id.contentFrame, startFragment).commit()

viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java).apply {
// ViewModelProvider(this).get(ProfileViewModel::class.java)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java).apply {
init()
val owner = this@MainActivity
}
updateFirebaseToken()
}

private fun setupBottomAppBar() {
// by default, home button is selected
val selectedIconColor = ContextCompat.getColor(this, R.color.selectedAppBarIcon)
val unselectedIconColor = ContextCompat.getColor(this, R.color.unselectedAppBarIcon)

bottomAppBar.homeButton.setColorFilter(selectedIconColor)

val bottomBarButtons = listOf(
bottomAppBar.homeButton,
bottomAppBar.scheduleButton,
bottomAppBar.leaderboard,
bottomAppBar.profile
bottomAppBar.profile,
)

// by default, home button is selected
bottomAppBar.homeButton.setColorFilter(selectedIconColor)

// make all buttons unselectedColor and then set selected button to selectedColor
bottomBarButtons.forEach { button ->
button.setOnClickListener { view ->
val newSelection = bottomBarButtons.indexOf(button)
if (onScanner) {
onScanner = false
}
if (newSelection != currentSelection) {
currentSelection = newSelection

Expand All @@ -92,19 +96,41 @@ class MainActivity : AppCompatActivity() {
}

private fun setupCodeEntrySheet() {
val inflater: LayoutInflater = layoutInflater

val scannerFragment = ScannerFragment()

code_entry_fab.setOnClickListener {
// set currentSelection to invalid index since scanner was selected
currentSelection = -1

// check if user is staff or attendee
if (!hasLoggedIn()) {
val toast = Toast.makeText(applicationContext, getString(R.string.login_error_msg), Toast.LENGTH_LONG)
// ((toast.view as LinearLayout).getChildAt(0) as TextView).gravity = Gravity.CENTER_HORIZONTAL
toast.show()
// Snackbar.make(findViewById(android.R.id.content), getString(R.string.login_error_msg), Snackbar.LENGTH_SHORT).show()
} else {
switchFragment(scannerFragment, true)
// set all bottom bar buttons to be the unselected color
val bottomBarButtons = listOf(
bottomAppBar.homeButton,
bottomAppBar.scheduleButton,
bottomAppBar.leaderboard,
bottomAppBar.profile,
)
val unselectedIconColor = ContextCompat.getColor(this, R.color.unselectedAppBarIcon)
bottomBarButtons.forEach { (it as ImageButton).setColorFilter(unselectedIconColor) }

// if staff, send them to fragment to select meeting attendance or attendee check-in
// if attendee, send them right to the scanner fragment
val scannerFragment = ScannerFragment()
val staffScannerFragment = StaffScannerFragment()
if (isStaff()) {
// check if already on scanner attendance page for staff
if (!onScanner) {
switchFragment(staffScannerFragment, false)
}
} else {
switchFragment(scannerFragment, true)
bottomAppBar.visibility = View.INVISIBLE
code_entry_fab.visibility = View.INVISIBLE
}
}
onScanner = true
}
}

Expand All @@ -118,16 +144,6 @@ class MainActivity : AppCompatActivity() {
transaction.commit()
}

fun switchFragmentWithAnimation(fragment: Fragment, addToBackStack: Boolean, anim: R.anim) {
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.contentFrame, fragment)
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
if (addToBackStack) {
transaction.addToBackStack(null)
}
transaction.commit()
}

private fun updateFirebaseToken() {
FirebaseApp.initializeApp(applicationContext)
FirebaseMessaging.getInstance().getToken().addOnSuccessListener { firebaseToken ->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.hackillinois.android.view.eventinfo

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
Expand Down Expand Up @@ -43,13 +42,12 @@ class EventInfoFragment : Fragment(), OnMapReadyCallback {
viewModel.event.observe(
this,
Observer { event ->
Log.i("EventInfoFragment", event.toString())
currentEvent = event
updateEventUI(currentEvent)
if (mapIsReady && !mapUpdated) {
setupMap()
}
}
},
)
viewModel.isFavorited.observe(this, Observer { updateFavoritedUI(it) })
}
Expand All @@ -75,7 +73,6 @@ class EventInfoFragment : Fragment(), OnMapReadyCallback {
googleMap.isIndoorEnabled = true

mapIsReady = true
Log.i("EventInfoFragment", "Map is ready")
if (currentEvent != null) {
setupMap()
}
Expand All @@ -84,7 +81,7 @@ class EventInfoFragment : Fragment(), OnMapReadyCallback {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
savedInstanceState: Bundle?,
): View? {
val view = inflater.inflate(R.layout.fragment_event_info, container, false)
view.exit_button.setOnClickListener { activity?.onBackPressed() }
Expand All @@ -98,7 +95,6 @@ class EventInfoFragment : Fragment(), OnMapReadyCallback {
}

private fun setupMap() {
Log.i("EventInfoFragment", "Setting up map")
addMarkersToMap(currentEvent!!)
if (currentEvent!!.locations.isNotEmpty()) {
moveCameraToLocation(currentEvent!!.locations.first())
Expand Down Expand Up @@ -130,20 +126,14 @@ class EventInfoFragment : Fragment(), OnMapReadyCallback {
}

private fun addMarkersToMap(event: Event) {
Log.i("EventInfoFragment", "Adding Markers to map")
event.locations.forEach { eventLocation ->
val latLng = LatLng(eventLocation.latitude, eventLocation.longitude)
var marker = googleMap.addMarker(
MarkerOptions()
.position(latLng)
.title(eventLocation.description)
.title(eventLocation.description),
)
Log.i("Map", marker.toString())
}

Log.i("Map", viewModel.event?.value.toString())
Log.i("Map", viewModel.event?.value?.locations.toString())
Log.i("Map", "hello")
}

private fun updateEventUI(event: Event?) {
Expand Down
Loading

0 comments on commit 75da02c

Please sign in to comment.