From cc7b8c3042f4b781e974d4ab97ef36090b8c9377 Mon Sep 17 00:00:00 2001 From: meiron03 Date: Thu, 29 Feb 2024 19:30:03 -0500 Subject: [PATCH 01/89] Fix memory leaks (#605) * Add canaryleak to detect memory leaks unregister local broadcast receiver in GsrReservationsFragment when fragment is destroyed to prevent memory leak. TODO: remove LocalBroadcastManager entirely because that shit deprecated. remove LocalBroadCastManager from HomeFragment since it is unused (and was causing memory leaks) * Fix more memory leaks lol * remove unused import * fix equality checks for Posts and get rid of unused code in Platform --- .../labs/pennmobile/api/Platform.java | 18 ----- .../pennapps/labs/pennmobile/classes/Post.kt | 71 +++++++++++++++---- 2 files changed, 57 insertions(+), 32 deletions(-) diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/Platform.java b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/Platform.java index 88c84862..d12b9702 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/Platform.java +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/Platform.java @@ -14,24 +14,6 @@ public interface Platform { String platformBaseUrl = "https://platform.pennlabs.org"; String campusExpressBaseUrl = "https://prod.campusexpress.upenn.edu/api/v1"; - @FormUrlEncoded - @POST("/accounts/token/") - void getAccessToken( - @Field("code") String authCode, - @Field("grant_type") String grantType, - @Field("client_id") String clientID, - @Field("redirect_uri") String redirectURI, - @Field("code_verifier") String codeVerifier, - Callback callback); - - @FormUrlEncoded - @POST("/accounts/token/") - void refreshAccessToken( - @Field("refresh_token") String refreshToken, - @Field("grant_type") String grantType, - @Field("client_id") String clientID, - Callback callback); - @FormUrlEncoded @POST("/accounts/introspect/") void getUser( diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/Post.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/Post.kt index 4a92d1c8..71a09eca 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/Post.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/Post.kt @@ -1,5 +1,6 @@ package com.pennapps.labs.pennmobile.classes +import android.util.Log import com.google.gson.annotations.Expose import com.google.gson.annotations.SerializedName /** @@ -10,52 +11,52 @@ class Post { @SerializedName("id") @Expose - internal val id : Int? = 0 + internal val id : Int? = null @SerializedName("club_code") @Expose - internal val clubCode : String? = "" + internal val clubCode : String? = null @SerializedName("title") @Expose - internal val title : String? = "" + internal val title : String? = null @SerializedName("subtitle") @Expose - internal val subtitle : String? = "" + internal val subtitle : String? = null @SerializedName("post_url") @Expose - internal val postUrl : String? = "" + internal val postUrl : String? = null @SerializedName("image_url") @Expose - internal val imageUrl : String? = "" + internal val imageUrl : String? = null @SerializedName("created_date") @Expose - internal val createdDate : String? = "" + internal val createdDate : String? = null @SerializedName("start_date") @Expose - internal val startDate : String? = "" + internal val startDate : String? = null @SerializedName("expire_date") @Expose - internal val expireDate : String? = "" + internal val expireDate : String? = null @SerializedName("club_comment") @Expose - internal val club_comment : String? = "" + internal val club_comment : String? = null @SerializedName("admin_comment") @Expose - internal val admin_comment : String? = "" + internal val admin_comment : String? = null @SerializedName("status") @Expose - internal val status : String? = "" + internal val status : String? = null @SerializedName("target_populations") @Expose @@ -65,8 +66,50 @@ class Post { return id.toString() + ", " + clubCode + "" } + override fun equals(other: Any?) : Boolean { + Log.i("CellUpdates", "fuck") + // note: target_populations is not included because it's unused and structural + return when (other) { + is Post -> { + this.id == other.id && + this.clubCode == other.clubCode && + this.title == other.title && + this.subtitle == other.subtitle && + this.postUrl == other.postUrl && + this.imageUrl == other.imageUrl && + this.createdDate == other.createdDate && + this.startDate == other.startDate && + this.expireDate == other.expireDate && + this.club_comment == other.club_comment && + this.admin_comment == other.admin_comment && + this.status == other.status && + this.target_populations == other.target_populations + } + else -> false + } + } - fun getResults(): Any { - return true + override fun hashCode() : Int { + // lazy hash function but we don't use this method anyways + + val idHash = id.hashCode().toString() + val clubCodeHash = clubCode.hashCode().toString() + val titleHash = title.hashCode().toString() + val subtitleHash = subtitle.hashCode().toString() + val postUrlHash = postUrl.hashCode().toString() + val imageUrlHash = imageUrl.hashCode().toString() + val createdDateHash = createdDate.hashCode().toString() + val startDateHash = startDate.hashCode().toString() + val expireDateHash = expireDate.hashCode().toString() + val clubCommentHash = club_comment.hashCode().toString() + val adminCommentHash = admin_comment.hashCode().toString() + val statusHash = status.hashCode().toString() + val targetPopulationsHash = target_populations.hashCode().toString() + + return (idHash + clubCodeHash + titleHash + + subtitleHash + postUrlHash + imageUrlHash + + createdDateHash + startDateHash + expireDateHash + + clubCommentHash + adminCommentHash + statusHash + + targetPopulationsHash).hashCode() } } \ No newline at end of file From 22d7ba9b033ade804eaa15eabf10f6c9d2c92fdf Mon Sep 17 00:00:00 2001 From: meiron03 Date: Fri, 1 Mar 2024 18:06:34 -0500 Subject: [PATCH 02/89] Fix Guest Mode (#606) * Fix Guest Mode (it does some suspicious function calls that we should fix in the future but sounds like a future problem) * Remove unused imports and cleanup code --- .../java/com/pennapps/labs/pennmobile/HomeFragment.kt | 7 +++++-- .../java/com/pennapps/labs/pennmobile/LoginFragment.kt | 3 +++ .../pennapps/labs/pennmobile/api/OAuth2NetworkManager.kt | 9 ++++++++- .../labs/pennmobile/classes/HomepageViewModel.kt | 9 ++++----- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/HomeFragment.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/HomeFragment.kt index ed4a314e..2322f2de 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/HomeFragment.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/HomeFragment.kt @@ -153,10 +153,12 @@ class HomeFragment : Fragment() { val deviceID = OAuth2NetworkManager(mActivity).getDeviceId() val bearerToken = "Bearer " + sp.getString(getString(R.string.access_token), "").toString() + val isLoggedIn = !sp.getBoolean(mActivity.getString(R.string.guest_mode), false) + lifecycleScope.launch(Dispatchers.Default) { // set adapter if it is null if (binding.homeCellsRv.adapter == null) { - homepageViewModel.populateHomePageCells(studentLife, bearerToken, deviceID) + homepageViewModel.populateHomePageCells(studentLife, isLoggedIn, bearerToken, deviceID) withContext(Dispatchers.Main) { binding.homeCellsRv.adapter = HomeAdapter(homepageViewModel) binding.homeCellsRv.visibility = View.INVISIBLE @@ -164,7 +166,8 @@ class HomeFragment : Fragment() { binding.homeRefreshLayout.isRefreshing = false } } else { // otherwise, call updateHomePageCells which only updates the cells that are changed - val updatedIndices = homepageViewModel.updateHomePageCells(studentLife, bearerToken, deviceID) + val updatedIndices = homepageViewModel.updateHomePageCells(studentLife, isLoggedIn, + bearerToken, deviceID) withContext(Dispatchers.Main) { updatedIndices.forEach { pos -> binding.homeCellsRv.adapter!!.notifyItemChanged(pos) diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/LoginFragment.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/LoginFragment.kt index b3c81189..a63beeb9 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/LoginFragment.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/LoginFragment.kt @@ -63,6 +63,9 @@ class LoginFragment : Fragment() { editor.remove(getString(R.string.pennkey)) editor.remove(getString(R.string.access_token)) editor.remove(getString(R.string.accountID)) + editor.putString(getString(R.string.access_token), "") + editor.putString(getString(R.string.refresh_token), "") + editor.putString(getString(R.string.expires_in), "") editor.putBoolean(getString(R.string.guest_mode), true) editor.apply() mActivity.startHomeFragment() diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/OAuth2NetworkManager.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/OAuth2NetworkManager.kt index 7834dad0..ef0486fe 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/OAuth2NetworkManager.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/OAuth2NetworkManager.kt @@ -5,7 +5,6 @@ import android.provider.Settings import android.util.Log import androidx.lifecycle.lifecycleScope import com.google.firebase.crashlytics.FirebaseCrashlytics - import com.pennapps.labs.pennmobile.BuildConfig import com.pennapps.labs.pennmobile.MainActivity import com.pennapps.labs.pennmobile.R @@ -29,6 +28,14 @@ class OAuth2NetworkManager(private var mActivity: MainActivity) { @Synchronized fun getAccessToken(function: () -> Unit) { + // if guest mode, then just do network request dangerously(TEMPORARY FIX, PLEASE DO SOMETHING + // ABOUT THIS IN FUTURE) + val guestMode = sp.getBoolean(mActivity.getString(R.string.guest_mode), false) + if (guestMode) { + function.invoke() + return + } + mActivity.lifecycleScope.launch { val tokenMutex = mActivity.tokenMutex tokenMutex.lock() diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/HomepageViewModel.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/HomepageViewModel.kt index 560426d1..56b93b94 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/HomepageViewModel.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/HomepageViewModel.kt @@ -104,10 +104,10 @@ class HomepageViewModel : HomepageDataModel, ViewModel() { * Returns a list of updated cell positions. */ @Synchronized - fun updateHomePageCells(studentLife: StudentLife, bearerToken: String, + fun updateHomePageCells(studentLife: StudentLife, isLoggedIn: Boolean, bearerToken: String, deviceID: String) : List { val prevList = homepageCells.toList() - populateHomePageCells(studentLife, bearerToken, deviceID) + populateHomePageCells(studentLife, isLoggedIn, bearerToken, deviceID) val updatedIndices = mutableListOf() @@ -128,9 +128,8 @@ class HomepageViewModel : HomepageDataModel, ViewModel() { * This function requires a correct (non-expired) bearerToken!! */ @Synchronized - fun populateHomePageCells(studentLife: StudentLife, bearerToken: String, deviceID: String) { - val isLoggedIn = bearerToken != "Bearer " - + fun populateHomePageCells(studentLife: StudentLife, + isLoggedIn: Boolean, bearerToken: String, deviceID: String) { if (isLoggedIn) { val latch = CountDownLatch(NUM_CELLS_LOGGED_IN) getPolls(studentLife, bearerToken, deviceID, latch) From 0b8955cc610dfab9d5719f70e395a1dce98d61ba Mon Sep 17 00:00:00 2001 From: Joe MacDougall <159717864+joemacd@users.noreply.github.com> Date: Fri, 12 Apr 2024 23:33:17 -0400 Subject: [PATCH 03/89] rebase attempt #2 (#611) Co-authored-by: meiron03 --- PennMobile/build.gradle | 38 ++++--- .../labs/pennmobile/adapters/HomeAdapter.kt | 30 +++++- .../adapters/HomeGsrReservationAdapter.kt | 102 ++++++++++++++++++ .../labs/pennmobile/classes/GSRCell.kt | 7 ++ .../labs/pennmobile/classes/GSRReservation.kt | 17 +++ .../pennmobile/classes/HomepageViewModel.kt | 23 +++- .../src/main/res/layout/gsr_list_item.xml | 87 +++++++++++++++ .../src/main/res/layout/home_gsr_card.xml | 92 ++++++++++++++++ build.gradle | 4 +- 9 files changed, 379 insertions(+), 21 deletions(-) create mode 100644 PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/HomeGsrReservationAdapter.kt create mode 100644 PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/GSRCell.kt create mode 100644 PennMobile/src/main/res/layout/gsr_list_item.xml create mode 100644 PennMobile/src/main/res/layout/home_gsr_card.xml diff --git a/PennMobile/build.gradle b/PennMobile/build.gradle index 6e2a23a7..32f542f5 100644 --- a/PennMobile/build.gradle +++ b/PennMobile/build.gradle @@ -7,6 +7,7 @@ plugins { } apply plugin: 'kotlin-android-extensions' +apply plugin: "kotlin-android" android { namespace 'com.pennapps.labs.pennmobile' @@ -35,6 +36,7 @@ android { packagingOptions { pickFirst 'META-INF/LICENSE.txt' pickFirst 'META-INF/NOTICE.txt' + exclude 'META-INF/rxjava.properties' } } @@ -68,6 +70,7 @@ dependencies { implementation 'com.squareup.picasso:picasso:2.71828' implementation 'com.squareup.okhttp:okhttp:2.7.5' implementation 'io.reactivex:rxandroid:1.2.1' + implementation "io.reactivex.rxjava2:rxandroid:2.0.2" implementation 'androidx.browser:browser:1.5.0' implementation 'androidx.recyclerview:recyclerview:1.3.2' implementation 'androidx.legacy:legacy-support-v4:1.0.0' @@ -91,6 +94,7 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation 'com.kaspersky.android-components:kaspresso:1.5.3' implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0' implementation 'com.squareup.retrofit2:converter-moshi:2.9.0' implementation 'com.squareup.retrofit2:converter-scalars:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' @@ -116,7 +120,7 @@ String getPlatformRedirectUri() { // Code Coverage: https://www.raywenderlich.com/10562143-continuous-integration-for-android#toc-anchor-014 jacoco { - toolVersion = "0.8.11" + toolVersion = "0.8.11" } // https://stackoverflow.com/questions/68065743/cannot-run-gradle-test-tasks-because-of-java-lang-noclassdeffounderror-jdk-inte @@ -131,7 +135,7 @@ def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', // Location of generated output classes def debugTree = fileTree(dir: "$project.buildDir/tmp/kotlin-classes/debug", - excludes: fileFilter) + excludes: fileFilter) // Source code directory def mainSrc = "$project.projectDir/src/main/java" @@ -139,20 +143,20 @@ def mainSrc = "$project.projectDir/src/main/java" // Task declaration task jacocoTestReport(type: JacocoReport) { - // Runs only after the dependencies are executed - dependsOn = ['testDebugUnitTest', 'createDebugCoverageReport'] - // Export formats - /*reports { - xml.enabled = true - html.enabled = true - }*/ - - sourceDirectories.setFrom(files([mainSrc])) - classDirectories.setFrom(files([debugTree])) - - // Inform Gradle where the files generated by test cases - are located - executionData.from = fileTree(dir: project.buildDir, includes: [ - 'jacoco/testDebugUnitTest.exec' + // Runs only after the dependencies are executed + dependsOn = ['testDebugUnitTest', 'createDebugCoverageReport'] + // Export formats + /*reports { + xml.enabled = true + html.enabled = true + }*/ + + sourceDirectories.setFrom(files([mainSrc])) + classDirectories.setFrom(files([debugTree])) + + // Inform Gradle where the files generated by test cases - are located + executionData.from = fileTree(dir: project.buildDir, includes: [ + 'jacoco/testDebugUnitTest.exec' // 'outputs/code_coverage/debugAndroidTest/connected/*.ec' - ]) + ]) } diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/HomeAdapter.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/HomeAdapter.kt index 7d5d0279..71b6e316 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/HomeAdapter.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/HomeAdapter.kt @@ -43,6 +43,7 @@ import com.pennapps.labs.pennmobile.classes.LaundryCell import com.pennapps.labs.pennmobile.classes.NewsCell import com.pennapps.labs.pennmobile.classes.PollCell import com.pennapps.labs.pennmobile.classes.PostCell +import com.pennapps.labs.pennmobile.classes.GSRCell import com.pennapps.labs.pennmobile.components.sneaker.Utils.convertToDp import com.pennapps.labs.pennmobile.utils.Utils import eightbitlab.com.blurview.RenderScriptBlur @@ -50,6 +51,10 @@ import kotlinx.android.synthetic.main.home_base_card.view.* import kotlinx.android.synthetic.main.home_base_card.view.home_card_rv import kotlinx.android.synthetic.main.home_base_card.view.home_card_subtitle import kotlinx.android.synthetic.main.home_base_card.view.home_card_title +import kotlinx.android.synthetic.main.home_gsr_card.view.home_gsr_button +import kotlinx.android.synthetic.main.home_gsr_card.view.home_gsr_rv +import kotlinx.android.synthetic.main.home_gsr_card.view.home_gsr_subtitle +import kotlinx.android.synthetic.main.home_gsr_card.view.home_gsr_title import kotlinx.android.synthetic.main.poll_card.view.* import kotlinx.android.synthetic.main.home_news_card.view.* import kotlinx.android.synthetic.main.home_post_card.view.* @@ -106,6 +111,9 @@ class HomeAdapter(private val dataModel: HomepageDataModel) : POLL -> { ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.poll_card, parent, false)) } + GSR_BOOKING -> { + ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.home_gsr_card, parent, false)) + } NOT_SUPPORTED -> { ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.empty_view, parent, false)) } @@ -114,7 +122,6 @@ class HomeAdapter(private val dataModel: HomepageDataModel) : } } } - override fun onBindViewHolder(holder: ViewHolder, position: Int) { val cell = dataModel.getCell(position) when (cell.type) { @@ -124,6 +131,7 @@ class HomeAdapter(private val dataModel: HomepageDataModel) : "laundry" -> bindLaundryCell(holder, cell as LaundryCell) "post" -> bindPostCell(holder, cell as PostCell) "poll" -> bindPollCell(holder, cell as PollCell, position) + "gsr_booking" -> bindGSRCell(holder, cell as GSRCell) "none" -> Log.i("HomeAdapter", "Empty cell at position $position") else -> Log.i("HomeAdapter", "Unsupported type of data at position $position") } @@ -470,6 +478,26 @@ class HomeAdapter(private val dataModel: HomepageDataModel) : } } + private fun bindGSRCell(holder: ViewHolder, cell: GSRCell) { + holder.itemView.home_gsr_title?.text = "Reservations" + holder.itemView.home_gsr_subtitle?.text = "Group Study Rooms" + val reservations = cell.reservations + holder.itemView.home_gsr_rv?.layoutManager = LinearLayoutManager(mContext, + LinearLayoutManager.VERTICAL, false) + holder.itemView.home_gsr_button?.text = "Book a Room" + holder.itemView.home_gsr_button?.setOnClickListener { + mActivity.setTab(MainActivity.GSR_ID) + for (fragment in mActivity.supportFragmentManager.fragments) { + if(fragment is GsrTabbedFragment) { + fragment.viewPager.currentItem = 0 + } + } + } + holder.itemView.home_gsr_rv?.adapter = HomeGsrReservationAdapter(reservations) + } + + + // Chrome custom tabs to launch news site internal inner class NewsCustomTabsServiceConnection : CustomTabsServiceConnection() { diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/HomeGsrReservationAdapter.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/HomeGsrReservationAdapter.kt new file mode 100644 index 00000000..3ae1a4f1 --- /dev/null +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/HomeGsrReservationAdapter.kt @@ -0,0 +1,102 @@ +package com.pennapps.labs.pennmobile.adapters + +import android.content.Context +import android.os.Bundle +import android.util.Log +import androidx.fragment.app.FragmentTransaction +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.View.GONE +import android.view.ViewGroup +import android.widget.Button +import android.widget.ImageView +import android.widget.TextView +import android.widget.Toast +import androidx.fragment.app.Fragment +import com.bumptech.glide.Glide +import com.pennapps.labs.pennmobile.GsrReservationsFragment +import com.pennapps.labs.pennmobile.GsrTabbedFragment +import com.pennapps.labs.pennmobile.MainActivity +import com.pennapps.labs.pennmobile.MenuFragment +import com.pennapps.labs.pennmobile.R +import com.pennapps.labs.pennmobile.api.StudentLife +import com.pennapps.labs.pennmobile.classes.GSRReservation +import com.squareup.picasso.Picasso +import kotlinx.android.synthetic.main.dining_list_item.view.* +import kotlinx.android.synthetic.main.gsr_list_item.view.item_gsr_date +import kotlinx.android.synthetic.main.gsr_list_item.view.item_gsr_image +import kotlinx.android.synthetic.main.gsr_list_item.view.item_gsr_location +import kotlinx.android.synthetic.main.gsr_reservation.view.gsr_reservation_cancel_btn +import kotlinx.android.synthetic.main.gsr_reservation.view.gsr_reservation_date_tv +import kotlinx.android.synthetic.main.gsr_reservation.view.gsr_reservation_iv +import kotlinx.android.synthetic.main.gsr_reservation.view.gsr_reservation_location_tv +import org.joda.time.format.DateTimeFormat +import org.joda.time.format.DateTimeFormatter +import rx.android.schedulers.AndroidSchedulers + +class HomeGsrReservationAdapter (reservations: List) : RecyclerView.Adapter() { + private var activeReservations: List = reservations + + private lateinit var itemImage: ImageView + private lateinit var itemLocation: TextView + private lateinit var itemDate: TextView + + + private lateinit var mContext: Context + private lateinit var mActivity: MainActivity + private lateinit var mStudentLife: StudentLife + override fun onBindViewHolder(holder: HomeGsrReservationAdapter.ViewHolder, position: Int) { + val currentReservation = activeReservations[position] + val location = currentReservation.name + val formatter: DateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ssZ") + val from = formatter.parseDateTime(currentReservation.fromDate) + val to = formatter.parseDateTime(currentReservation.toDate) + val day = from.toString("EEEE, MMMM d") + val fromHour = from.toString("h:mm a") + val toHour = to.toString("h:mm a") + + val imageUrl = currentReservation.info?.get("thumbnail") ?: "https://s3.us-east-2.amazonaws.com/labs.api/dining/MBA+Cafe.jpg" + Picasso.get().load(imageUrl).fit().centerCrop().into(holder.itemView.item_gsr_image) + + holder.itemView.item_gsr_location.text = location + holder.itemView.item_gsr_date.text = day + "\n" + fromHour + "-" + toHour + + holder.itemView.setOnClickListener { + // Moves to GSR Booking Tab + mActivity.setTab(MainActivity.GSR_ID) + + // Changes tab to "My Reservations" tab (from "Book a Room" tab) + for (fragment in mActivity.supportFragmentManager.fragments) { + if(fragment is GsrTabbedFragment) { + fragment.viewPager.currentItem = 1 + } + } + } + + } + + override fun getItemCount(): Int { + return activeReservations.size + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeGsrReservationAdapter.ViewHolder { + mContext = parent.context + mActivity = mContext as MainActivity + mStudentLife = MainActivity.studentLifeInstance + + val view = LayoutInflater.from(parent.context).inflate(R.layout.gsr_list_item, parent, false) + return ViewHolder(view) + } + inner class ViewHolder (itemView: View) : RecyclerView.ViewHolder(itemView) { + + init { + itemImage = itemView.item_gsr_image + itemLocation = itemView.item_gsr_location + itemDate = itemView.item_gsr_date + + } + + } +} \ No newline at end of file diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/GSRCell.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/GSRCell.kt new file mode 100644 index 00000000..5f8f55ee --- /dev/null +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/GSRCell.kt @@ -0,0 +1,7 @@ +package com.pennapps.labs.pennmobile.classes + +data class GSRCell(val reservations: List) : HomeCell() { + init { + type = "gsr_booking" + } +} \ No newline at end of file diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/GSRReservation.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/GSRReservation.kt index 74b163c5..8d38041d 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/GSRReservation.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/GSRReservation.kt @@ -37,4 +37,21 @@ class GSRReservation { @Expose @JvmField var info: Map? = null + + override fun equals(other: Any?): Boolean { + return other is GSRReservation && this.booking_id == other.booking_id && this.name == other.name + && this.fromDate == other.fromDate && this.toDate == other.toDate && this.gid == other.gid + && this.lid == other.lid + } + + override fun hashCode(): Int { + var result = (booking_id?.hashCode() ?: 0) + result = 31 * result + (name?.hashCode() ?: 0) + result = 31 * result + (fromDate?.hashCode() ?: 0) + result = 31 * result + (toDate?.hashCode() ?: 0) + result = 31 * result + (gid?.hashCode() ?: 0) + result = 31 * result + (lid?.hashCode() ?: 0) + result = 31 * result + (info?.hashCode() ?: 0) + return result + } } \ No newline at end of file diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/HomepageViewModel.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/HomepageViewModel.kt index 56b93b94..f59e607d 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/HomepageViewModel.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/HomepageViewModel.kt @@ -25,7 +25,7 @@ import java.util.concurrent.CountDownLatch class HomepageViewModel : HomepageDataModel, ViewModel() { companion object { - private const val NUM_CELLS = 6 + private const val NUM_CELLS = 7 private const val NUM_CELLS_LOGGED_IN = NUM_CELLS private const val NUM_CELLS_GUEST = 2 @@ -37,6 +37,7 @@ class HomepageViewModel : HomepageDataModel, ViewModel() { private const val POST_POS = 3 private const val DINING_POS = 4 private const val LAUNDRY_POS = 5 + private const val GSR_POS = 6 private const val TAG = "HomepageVM" private const val UPDATE_TAG = "CellUpdate" @@ -138,6 +139,7 @@ class HomepageViewModel : HomepageDataModel, ViewModel() { getLaundry(studentLife, bearerToken, latch) getPosts(studentLife, bearerToken, latch) getDiningPrefs(studentLife, bearerToken, latch) + getGSRReservations(studentLife, bearerToken, latch) // waits until all of the network calls are processed latch.await() } else { @@ -285,6 +287,25 @@ class HomepageViewModel : HomepageDataModel, ViewModel() { }) } + private fun getGSRReservations(studentLife: StudentLife, bearerToken: String, latch: CountDownLatch) { + studentLife.getGsrReservations(bearerToken).subscribe({ reservationsList -> + if (reservationsList.isEmpty()) { + addCell(HomeCell(), GSR_POS) + + } else { + val gsrCell = GSRCell(reservationsList) + Log.i(TAG, "Loaded GSR Reservations") + addCell(gsrCell, GSR_POS) + } + latch.countDown() + },{ + throwable -> + Log.i(TAG, "Could not load GSR reservations") + throwable.printStackTrace() + latch.countDown() + }) + } + private fun setPostBlurView(status: Boolean) = runBlocking { postBlurMutex.withLock { postBlurViewLoaded = status diff --git a/PennMobile/src/main/res/layout/gsr_list_item.xml b/PennMobile/src/main/res/layout/gsr_list_item.xml new file mode 100644 index 00000000..e152e45d --- /dev/null +++ b/PennMobile/src/main/res/layout/gsr_list_item.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PennMobile/src/main/res/layout/home_gsr_card.xml b/PennMobile/src/main/res/layout/home_gsr_card.xml new file mode 100644 index 00000000..39ff628e --- /dev/null +++ b/PennMobile/src/main/res/layout/home_gsr_card.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + +