Skip to content
This repository was archived by the owner on Oct 12, 2025. It is now read-only.

Commit 8d82fe2

Browse files
committed
added Point Shop QR Code and cart functionality
1 parent 8874184 commit 8d82fe2

File tree

11 files changed

+323
-72
lines changed

11 files changed

+323
-72
lines changed

app/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ dependencies {
129129

130130
// Swipe-to-refresh
131131
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
132+
133+
//Json Parsing
134+
implementation 'com.google.code.gson:gson:2.8.9'
132135
}
133136

134137
apply plugin: 'com.google.gms.google-services'

app/src/main/java/org/hackillinois/android/API.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ interface API {
7979
@GET("shop/cart/qr/")
8080
suspend fun getCartQRCode(): QRResponse
8181

82+
@DELETE("shop/cart/{itemId}")
83+
suspend fun removeItemCart(@Path("itemId") itemId: String): Response<ResponseBody>
84+
8285
// STAFF
8386

8487
@POST("staff/attendance/")
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
package org.hackillinois.android.database.entity
22

3-
data class QRResponse(val qrCode: String)
3+
data class QRResponse(val QRCode: String)
Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package org.hackillinois.android.view.shop
22

33
import android.content.Context
4+
import android.graphics.Rect
45
import android.util.Log
56
import android.view.LayoutInflater
7+
import android.view.TouchDelegate
68
import android.view.View
79
import android.view.ViewGroup
810
import android.widget.ImageView
@@ -11,14 +13,37 @@ import androidx.recyclerview.widget.RecyclerView
1113
import com.bumptech.glide.Glide
1214
import org.hackillinois.android.R
1315
import org.hackillinois.android.database.entity.ShopItem
14-
import org.hackillinois.android.view.shop.ShopAdapter.OnBuyItemListener
1516

16-
class CartAdapter(private var cartItems: List<Pair<ShopItem, Int>>, private val buyItemListener: OnBuyItemListener) :
17-
RecyclerView.Adapter<CartAdapter.ViewHolder>() {
17+
class CartAdapter(
18+
private var cartItems: List<Pair<ShopItem, Int>>,
19+
private val listener: OnQuantityChangeListener
20+
) : RecyclerView.Adapter<CartAdapter.ViewHolder>() {
1821

1922
private lateinit var context: Context
2023

21-
inner class ViewHolder(parent: View) : RecyclerView.ViewHolder(parent)
24+
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
25+
val textViewSticker: TextView = view.findViewById(R.id.text_view_sticker)
26+
val quantityTextView: TextView = view.findViewById(R.id.number_text)
27+
val shopItemImageView: ImageView = view.findViewById(R.id.image_view_sticker_symbol)
28+
val plusButton: TextView = view.findViewById(R.id.button_plus)
29+
val minusButton: TextView = view.findViewById(R.id.button_minus)
30+
}
31+
32+
private fun expandTouchArea(targetView: View, extraPadding: Int) {
33+
val parentView = targetView.parent as? ViewGroup ?: return
34+
parentView.post {
35+
val rect = Rect()
36+
targetView.getHitRect(rect)
37+
rect.top -= extraPadding
38+
rect.left -= extraPadding
39+
rect.bottom += extraPadding
40+
rect.right += extraPadding
41+
42+
// Ensure we do not override existing touch delegates
43+
parentView.touchDelegate = TouchDelegate(rect, targetView)
44+
parentView.requestLayout() // Refresh layout so it applies
45+
}
46+
}
2247

2348
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
2449
val view = LayoutInflater.from(parent.context)
@@ -31,35 +56,34 @@ class CartAdapter(private var cartItems: List<Pair<ShopItem, Int>>, private val
3156

3257
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
3358
val (item, quantity) = cartItems[position]
34-
bind(item, quantity, holder.itemView)
35-
}
36-
37-
private fun bind(item: ShopItem, quantity: Int, itemView: View) {
38-
itemView.apply {
39-
val textViewSticker: TextView = findViewById(R.id.text_view_sticker)
40-
textViewSticker.text = item.name
41-
val quantiyTextView: TextView = findViewById(R.id.number_text)
42-
quantiyTextView.text = quantity.toString()
59+
holder.textViewSticker.text = item.name
60+
holder.quantityTextView.text = quantity.toString()
61+
Glide.with(context).load(item.imageURL).into(holder.shopItemImageView)
4362

44-
val shopItemImageView: ImageView = findViewById(R.id.image_view_sticker_symbol)
45-
Glide.with(context).load(item.imageURL).into(shopItemImageView)
63+
// Increase quantity using plus button
64+
holder.plusButton.setOnClickListener {
65+
val newQuantity = quantity + 1
66+
listener.onIncreaseQuantity(item, newQuantity)
67+
}
4668

47-
val plusButton: TextView = findViewById(R.id.button_plus)
69+
expandTouchArea(holder.plusButton, 100)
4870

49-
plusButton.setOnClickListener {
50-
Log.d("CartDebug", "Plus button clicked!")
51-
Log.d("Item ID: ", "" + item.itemId)
52-
buyItemListener.onBuyItem(item)
53-
}
71+
// Decrease quantity using minus button (allowing 0)
72+
holder.minusButton.setOnClickListener {
73+
val newQuantity = quantity - 1
74+
listener.onDecreaseQuantity(item, newQuantity)
5475
}
76+
77+
expandTouchArea(holder.minusButton, 100)
5578
}
5679

5780
fun updateCart(newCartItems: List<Pair<ShopItem, Int>>) {
5881
this.cartItems = newCartItems
5982
notifyDataSetChanged()
6083
}
6184

62-
interface OnBuyItemListener {
63-
fun onBuyItem(item: ShopItem)
85+
interface OnQuantityChangeListener {
86+
fun onIncreaseQuantity(item: ShopItem, newQuantity: Int)
87+
fun onDecreaseQuantity(item: ShopItem, newQuantity: Int)
6488
}
6589
}

app/src/main/java/org/hackillinois/android/view/shop/CartFragment.kt

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import org.hackillinois.android.R
1515
import org.hackillinois.android.database.entity.Cart
1616
import org.hackillinois.android.database.entity.ShopItem
1717

18-
class CartFragment : Fragment(), CartAdapter.OnBuyItemListener {
18+
class CartFragment : Fragment(), CartAdapter.OnQuantityChangeListener {
1919

2020
private lateinit var recyclerView: RecyclerView
2121
private lateinit var cartAdapter: CartAdapter
@@ -39,16 +39,15 @@ class CartFragment : Fragment(), CartAdapter.OnBuyItemListener {
3939
val backButton: View = view.findViewById(R.id.backButton)
4040
backButton.bringToFront()
4141
backButton.setOnClickListener {
42-
requireActivity().supportFragmentManager.popBackStack() // Go back to the previous fragment
42+
requireActivity().supportFragmentManager.popBackStack() // Go back to previous fragment
4343
}
4444

45-
// Handle Redeem Button Click
4645
val redeemButton: View = view.findViewById(R.id.redeemButton)
4746
redeemButton.setOnClickListener {
4847
val redeemFragment = RedeemFragment()
4948
requireActivity().supportFragmentManager.beginTransaction()
5049
.replace(R.id.contentFrame, redeemFragment)
51-
.addToBackStack(null) // Allows back navigation
50+
.addToBackStack(null)
5251
.commit()
5352
}
5453

@@ -58,16 +57,11 @@ class CartFragment : Fragment(), CartAdapter.OnBuyItemListener {
5857
private fun fetchCartData() {
5958
lifecycleScope.launch {
6059
try {
61-
// First, get all available shop items
6260
val shopItems: List<ShopItem> = App.getAPI().shop()
63-
64-
// Next, get the cart from the API
6561
val cart: Cart = App.getAPI().getCart()
6662
val items = mutableListOf<Pair<ShopItem, Int>>()
6763

68-
// Iterate over the map of itemId -> quantity from the cart.
6964
for ((itemId, quantity) in cart.items) {
70-
// Look up the ShopItem from the shopItems list using itemId
7165
val shopItem = shopItems.find { it.itemId == itemId }
7266
if (shopItem != null) {
7367
items.add(Pair(shopItem, quantity))
@@ -83,14 +77,13 @@ class CartFragment : Fragment(), CartAdapter.OnBuyItemListener {
8377
}
8478
}
8579

86-
override fun onBuyItem(item: ShopItem) {
80+
override fun onIncreaseQuantity(item: ShopItem, newQuantity: Int) {
8781
lifecycleScope.launch {
8882
try {
8983
val response = App.getAPI().addItemCart(item.itemId)
9084
if (response.isSuccessful) {
91-
// Update UI or local data with the new cart state
92-
fetchCartData()
9385
Log.d("CartDebug", "Item added: ${response.body()}")
86+
fetchCartData() // Refresh cart
9487
} else {
9588
Log.e("CartDebug", "Failed to add item: ${response.code()}")
9689
}
@@ -99,4 +92,29 @@ class CartFragment : Fragment(), CartAdapter.OnBuyItemListener {
9992
}
10093
}
10194
}
95+
96+
override fun onDecreaseQuantity(item: ShopItem, newQuantity: Int) {
97+
lifecycleScope.launch {
98+
try {
99+
val response = App.getAPI().removeItemCart(item.itemId)
100+
if (response.isSuccessful) {
101+
Log.d("CartDebug", "Item removed: ${response.body()}")
102+
if (newQuantity == 0) {
103+
// If quantity is zero, remove the item from UI
104+
fetchCartData()
105+
} else {
106+
// Just update the UI without full fetch
107+
cartItems = cartItems.map {
108+
if (it.first.itemId == item.itemId) Pair(it.first, newQuantity) else it
109+
}
110+
cartAdapter.updateCart(cartItems)
111+
}
112+
} else {
113+
Log.e("CartDebug", "Failed to remove item: ${response.code()}")
114+
}
115+
} catch (e: Exception) {
116+
Log.e("CartDebug", "Error removing item from cart", e)
117+
}
118+
}
119+
}
102120
}

app/src/main/java/org/hackillinois/android/view/shop/RedeemFragment.kt

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,30 @@
11
package org.hackillinois.android.view.shop
22

3+
import RedeemViewModel
4+
import android.graphics.Bitmap
5+
import android.graphics.Color
36
import android.os.Bundle
7+
import android.util.Log
48
import android.view.LayoutInflater
59
import android.view.View
610
import android.view.ViewGroup
11+
import android.widget.ImageView
12+
import android.widget.Toast
713
import androidx.fragment.app.Fragment
14+
import androidx.lifecycle.Observer
15+
import androidx.lifecycle.ViewModelProvider
16+
import com.google.zxing.BarcodeFormat
17+
import com.google.zxing.EncodeHintType
18+
import com.google.zxing.MultiFormatWriter
19+
import com.google.zxing.WriterException
820
import org.hackillinois.android.R
21+
import java.util.EnumMap
922

1023
class RedeemFragment : Fragment() {
1124

25+
private lateinit var qrCodeImage: ImageView
26+
private lateinit var redeemViewModel: RedeemViewModel
27+
1228
override fun onCreateView(
1329
inflater: LayoutInflater,
1430
container: ViewGroup?,
@@ -22,6 +38,62 @@ class RedeemFragment : Fragment() {
2238
backButton.setOnClickListener {
2339
requireActivity().supportFragmentManager.popBackStack() // Go back to previous fragment
2440
}
41+
42+
qrCodeImage = view.findViewById(R.id.qr_code_placeholder)
43+
44+
// Initialize and observe the ViewModel
45+
redeemViewModel = ViewModelProvider(this).get(RedeemViewModel::class.java)
46+
redeemViewModel.qrCodeLiveData.observe(viewLifecycleOwner, Observer { qrString ->
47+
Log.d("RedeemFragment", "Updated QR Code: $qrString")
48+
updateQRView(qrString)
49+
})
50+
51+
redeemViewModel.errorLiveData.observe(viewLifecycleOwner) { errorMessage ->
52+
Toast.makeText(requireContext(), errorMessage, Toast.LENGTH_SHORT).show()
53+
}
54+
2555
return view
2656
}
27-
}
57+
58+
private fun updateQRView(qrString: String) {
59+
if (qrCodeImage.width > 0 && qrCodeImage.height > 0) {
60+
Log.d("RedeemFragment", "Generating QR with text: $qrString")
61+
val bitmap = generateQR(qrString)
62+
qrCodeImage.setImageBitmap(bitmap)
63+
}
64+
}
65+
66+
private fun generateQR(text: String): Bitmap {
67+
val width = qrCodeImage.width
68+
val height = qrCodeImage.height
69+
70+
val pixels = IntArray(width * height)
71+
val multiFormatWriter = MultiFormatWriter()
72+
val hints = EnumMap<EncodeHintType, Any>(EncodeHintType::class.java)
73+
hints[EncodeHintType.MARGIN] = 0
74+
75+
try {
76+
val bitMatrix = multiFormatWriter.encode(text, BarcodeFormat.QR_CODE, width, height, hints)
77+
val clear = Color.TRANSPARENT
78+
val solid = Color.BLACK
79+
for (x in 0 until width) {
80+
for (y in 0 until height) {
81+
pixels[y * width + x] = if (bitMatrix.get(x, y)) solid else clear
82+
}
83+
}
84+
} catch (e: WriterException) {
85+
e.printStackTrace()
86+
}
87+
return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888)
88+
}
89+
90+
override fun onResume() {
91+
super.onResume()
92+
redeemViewModel.startAutoRefresh() // Resume QR refresh when fragment is visible
93+
}
94+
95+
override fun onPause() {
96+
super.onPause()
97+
redeemViewModel.stopAutoRefresh() // Pause QR refresh when fragment is hidden
98+
}
99+
}

app/src/main/java/org/hackillinois/android/view/shop/ShopAdapter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class ShopAdapter(private var itemList: List<ShopItem>, private val buyItemListe
7070
}
7171

7272
fun updateShop(shopItem: List<ShopItem>) {
73-
this.itemList = shopItem
73+
this.itemList = shopItem.sortedBy { it.quantity == 0 } // Move out-of-stock items to the end
7474
notifyDataSetChanged()
7575
}
7676

0 commit comments

Comments
 (0)