Skip to content

Commit 0ca2875

Browse files
Merge pull request #653 from pennlabs/david/insights-refactoring
Dining Insights Refactor
2 parents f4b557e + 2f97c29 commit 0ca2875

27 files changed

+1491
-252
lines changed

PennMobile/build.gradle

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ plugins {
33
alias(libs.plugins.kotlin.android)
44
alias(libs.plugins.google.services)
55
alias(libs.plugins.firebase.crashlytics)
6+
// Removed problematic compose compiler alias for Kotlin 1.9.x compatibility
67
id 'jacoco'
78
id 'kotlin-parcelize'
89
id 'com.google.devtools.ksp'
@@ -11,9 +12,13 @@ plugins {
1112

1213
android {
1314
namespace 'com.pennapps.labs.pennmobile'
15+
1416
buildFeatures {
1517
buildConfig = true
18+
viewBinding true
19+
compose true
1620
}
21+
1722
buildTypes {
1823
debug {
1924
matchingFallbacks = ['qa', 'release']
@@ -22,17 +27,18 @@ android {
2227
}
2328
release {}
2429
}
30+
2531
compileSdk 35
26-
buildFeatures {
27-
viewBinding true
28-
compose true
29-
}
32+
3033
composeOptions {
34+
// Matches Kotlin 1.9.24 defined in your libs.versions.toml
3135
kotlinCompilerExtensionVersion = "1.5.14"
3236
}
37+
3338
kotlinOptions {
3439
jvmTarget = "1.8"
3540
}
41+
3642
defaultConfig {
3743
minSdkVersion 26
3844
targetSdkVersion 35
@@ -41,6 +47,7 @@ android {
4147
buildConfigField ("String", "PLATFORM_REDIRECT_URI", getPlatformRedirectUri())
4248
buildConfigField ("String", "PLATFORM_CLIENT_ID", getPlatformClientID())
4349
}
50+
4451
packagingOptions {
4552
resources {
4653
excludes += ['META-INF/rxjava.properties']
@@ -51,6 +58,10 @@ android {
5158

5259
dependencies {
5360
implementation fileTree(include: ['*.jar'], dir: 'libs')
61+
// If you want the foundation layout, use the bundle or the specific activity compose you have defined
62+
implementation libs.androidx.activity.compose
63+
implementation libs.androidx.material3.android
64+
// implementation libs.androidx.foundation.layout
5465

5566
androidTestImplementation libs.androidx.espresso.core
5667
androidTestImplementation libs.androidx.junit
@@ -113,65 +124,57 @@ dependencies {
113124
ksp libs.androidx.room.compiler
114125
ksp libs.androidx.hilt.compiler
115126

116-
testImplementation libs.junit
127+
implementation libs.androidx.constraintlayout
128+
implementation libs.androidx.hilt.navigation.compose
129+
130+
implementation libs.androidx.hilt.android
131+
ksp libs.androidx.hilt.compiler
117132
}
118133

119134
String getPlatformClientID() {
120135
def propFile = rootProject.file("./local.properties")
121136
def properties = new Properties()
122-
properties.load(new FileInputStream(propFile))
123-
System.out.println(properties.toString())
124-
return properties['PLATFORM_CLIENT_ID']
137+
if (propFile.exists()) {
138+
properties.load(new FileInputStream(propFile))
139+
return properties['PLATFORM_CLIENT_ID']
140+
}
141+
return ""
125142
}
126143

127144
String getPlatformRedirectUri() {
128145
def propFile = rootProject.file("./local.properties")
129146
def properties = new Properties()
130-
properties.load(new FileInputStream(propFile))
131-
System.out.println(properties.toString())
132-
return properties['PLATFORM_REDIRECT_URI']
147+
if (propFile.exists()) {
148+
properties.load(new FileInputStream(propFile))
149+
return properties['PLATFORM_REDIRECT_URI']
150+
}
151+
return ""
133152
}
134153

135-
// Code Coverage: https://www.raywenderlich.com/10562143-continuous-integration-for-android#toc-anchor-014
136-
137154
jacoco {
138155
toolVersion = "0.8.11"
139156
}
140157

141-
// https://stackoverflow.com/questions/68065743/cannot-run-gradle-test-tasks-because-of-java-lang-noclassdeffounderror-jdk-inte
142158
tasks.withType(Test).configureEach {
143159
jacoco.includeNoLocationClasses = true
144160
jacoco.excludes = ['jdk.internal.*']
145161
}
146162

147-
// Files with such regex patterns are to be excluded
148163
def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*',
149164
'**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
150165

151-
// Location of generated output classes
152166
def debugTree = fileTree(dir: "$project.layout.buildDirectory/tmp/kotlin-classes/debug",
153167
excludes: fileFilter)
154168

155-
// Source code directory
156169
def mainSrc = "$project.projectDir/src/main/java"
157170

158-
// Task declaration
159-
160171
tasks.register('jacocoTestReport', JacocoReport) {
161-
// Runs only after the dependencies are executed
162172
dependsOn = ['testDebugUnitTest', 'createDebugCoverageReport']
163-
// Export formats
164-
/*reports {
165-
xml.enabled = true
166-
html.enabled = true
167-
}*/
168173

169174
sourceDirectories.setFrom(files([mainSrc]))
170175
classDirectories.setFrom(files([debugTree]))
171176

172-
// Inform Gradle where the files generated by test cases - are located
173177
executionData.from = fileTree(dir: project.layout.buildDirectory, includes: [
174178
'jacoco/testDebugUnitTest.exec'
175-
// 'outputs/code_coverage/debugAndroidTest/connected/*.ec'
176179
])
177-
}
180+
}

PennMobile/src/main/java/com/pennapps/labs/pennmobile/MainActivity.kt

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import android.net.ConnectivityManager
1111
import android.net.NetworkCapabilities
1212
import android.os.Build
1313
import android.os.Bundle
14+
import android.os.Handler
15+
import android.os.Looper
1416
import android.os.StrictMode
1517
import android.util.Log
1618
import android.view.LayoutInflater
@@ -154,6 +156,27 @@ class MainActivity : AppCompatActivity() {
154156
}
155157
}
156158

159+
override fun onConfigurationChanged(newConfig: Configuration) {
160+
super.onConfigurationChanged(newConfig)
161+
162+
// Delay so that UI is properly rendered. Force consistency
163+
Handler(Looper.getMainLooper()).postDelayed({
164+
val currentPosition = binding.include.mainViewPager.currentItem
165+
166+
val menuItemId =
167+
when (currentPosition) {
168+
MainPagerAdapter.HOME_POSITION -> R.id.nav_home
169+
MainPagerAdapter.DINING_POSITION -> R.id.nav_dining
170+
MainPagerAdapter.GSR_POSITION -> R.id.nav_gsr
171+
MainPagerAdapter.LAUNDRY_POSITION -> R.id.nav_laundry
172+
MainPagerAdapter.MORE_POSITION -> R.id.nav_more
173+
else -> R.id.nav_home
174+
}
175+
176+
binding.include.expandableBottomBar.selectedItemId = menuItemId
177+
}, 100)
178+
}
179+
157180
/**
158181
* Sets up an auth state listener. The authState is RequiresLogin when the user's access token
159182
* is expired. As a result, we need to start the Login Fragment.
@@ -192,7 +215,20 @@ class MainActivity : AppCompatActivity() {
192215
binding.include.expandableBottomBar.selectedItemId = id
193216
}
194217

195-
fun setSelectedTab(id: Int) {}
218+
fun setSelectedTab(id: Int) {
219+
val menuItemId =
220+
when (id) {
221+
HOME -> R.id.nav_home
222+
DINING -> R.id.nav_dining
223+
GSR -> R.id.nav_gsr
224+
LAUNDRY -> R.id.nav_laundry
225+
MORE -> R.id.nav_more
226+
else -> R.id.nav_home
227+
}
228+
229+
// Set the bottom nav selected item
230+
binding.include.expandableBottomBar.selectedItemId = menuItemId
231+
}
196232

197233
fun closeKeyboard() {
198234
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager

PennMobile/src/main/java/com/pennapps/labs/pennmobile/PennMobile.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ class PennMobile : MultiDexApplication() {
2020
.Builder()
2121
.detectUnsafeIntentLaunch()
2222
.penaltyLog()
23-
.penaltyDeath()
2423
.build(),
2524
)
2625
}

PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/CampusExpress.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import retrofit2.Response
77
import retrofit2.http.GET
88
import retrofit2.http.Header
99
import retrofit2.http.Query
10-
import rx.Observable
1110

1211
/**
1312
* Created by Julius Snipes on 09/23/2022.
@@ -24,14 +23,14 @@ interface CampusExpress {
2423
): Response<CampusExpressAccessTokenResponse>
2524

2625
@GET("dining/currentBalance")
27-
fun getCurrentDiningBalances(
26+
suspend fun getCurrentDiningBalances(
2827
@Header("x-authorization") bearerToken: String?,
29-
): Observable<DiningBalances>
28+
): DiningBalances
3029

3130
@GET("dining/pastBalances")
32-
fun getPastDiningBalances(
31+
suspend fun getPastDiningBalances(
3332
@Header("x-authorization") bearerToken: String?,
3433
@Query("start_date") startDate: String?,
3534
@Query("end_date") endDate: String?,
36-
): Observable<DiningBalancesList>
35+
): DiningBalancesList
3736
}

PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/CampusExpressNetworkManager.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
package com.pennapps.labs.pennmobile.api
22

3+
import android.content.Context
34
import androidx.preference.PreferenceManager
4-
import com.pennapps.labs.pennmobile.MainActivity
55
import com.pennapps.labs.pennmobile.R
66
import java.util.Calendar
77
import java.util.Date
88

99
class CampusExpressNetworkManager(
10-
private var mActivity: MainActivity,
10+
private var context: Context,
1111
) {
12-
private val sp = PreferenceManager.getDefaultSharedPreferences(mActivity)
12+
private val sp = PreferenceManager.getDefaultSharedPreferences(context)
1313
val editor = sp?.edit()
1414

1515
fun getAccessToken(): String? {
16-
val expiresIn = sp.getLong(mActivity.getString(R.string.campus_token_expires_in), 0L)
16+
val expiresIn = sp.getLong(context.getString(R.string.campus_token_expires_in), 0L)
1717
if (expiresIn != 0L) {
1818
val calendar = Calendar.getInstance()
1919
val expiresAt = Date()
@@ -23,7 +23,7 @@ class CampusExpressNetworkManager(
2323
if (calendar.time >= expiresAt) { // if it has expired, refresh access token
2424
return ""
2525
}
26-
return sp.getString(mActivity.getString(R.string.campus_express_token), "")
26+
return sp.getString(context.getString(R.string.campus_express_token), "")
2727
} else {
2828
return ""
2929
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.pennapps.labs.pennmobile.di
2+
3+
import com.pennapps.labs.pennmobile.api.CampusExpress
4+
import com.pennapps.labs.pennmobile.api.Platform
5+
import dagger.Module
6+
import dagger.Provides
7+
import dagger.hilt.InstallIn
8+
import dagger.hilt.components.SingletonComponent
9+
import retrofit2.Retrofit
10+
import retrofit2.converter.gson.GsonConverterFactory
11+
import javax.inject.Named
12+
import javax.inject.Singleton
13+
14+
@Module
15+
@InstallIn(SingletonComponent::class)
16+
object CampusExpressModule {
17+
18+
@Provides
19+
@Singleton
20+
@Named("CampusExpressRetrofit") // Tag this specific instance
21+
fun provideRetrofit(): Retrofit =
22+
Retrofit
23+
.Builder()
24+
.baseUrl(Platform.campusExpressBaseUrl)
25+
.addConverterFactory(GsonConverterFactory.create())
26+
.build()
27+
28+
@Provides
29+
@Singleton
30+
fun provideCampusExpress(
31+
@Named("CampusExpressRetrofit") retrofit: Retrofit // Tell Hilt to use the tagged instance
32+
): CampusExpress = retrofit.create(CampusExpress::class.java)
33+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.pennapps.labs.pennmobile.di
2+
3+
import android.content.Context
4+
import com.pennapps.labs.pennmobile.api.CampusExpressNetworkManager
5+
import dagger.Module
6+
import dagger.Provides
7+
import dagger.hilt.InstallIn
8+
import dagger.hilt.android.qualifiers.ApplicationContext
9+
import dagger.hilt.components.SingletonComponent
10+
import javax.inject.Singleton
11+
12+
@Module
13+
@InstallIn(SingletonComponent::class)
14+
object CampusExpressNetworkManagerModule {
15+
@Provides
16+
@Singleton
17+
fun provideCampusExpressNetworkManager(
18+
@ApplicationContext context: Context,
19+
): CampusExpressNetworkManager = CampusExpressNetworkManager(context)
20+
}

PennMobile/src/main/java/com/pennapps/labs/pennmobile/dining/adapters/DiningInsightsCardAdapter.kt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import com.github.mikephil.charting.formatter.ValueFormatter
2020
import com.github.mikephil.charting.highlight.Highlight
2121
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet
2222
import com.github.mikephil.charting.listener.OnChartValueSelectedListener
23-
import com.pennapps.labs.pennmobile.MainActivity
2423
import com.pennapps.labs.pennmobile.R
2524
import com.pennapps.labs.pennmobile.databinding.DiningBalancesCardBinding
2625
import com.pennapps.labs.pennmobile.databinding.DiningPredictionsCardBinding
@@ -44,7 +43,6 @@ class DiningInsightsCardAdapter(
4443
private var cells: ArrayList<DiningInsightCell>,
4544
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
4645
private lateinit var mContext: Context
47-
private lateinit var mActivity: MainActivity
4846

4947
companion object {
5048
// Types of Home Cells
@@ -55,15 +53,14 @@ class DiningInsightsCardAdapter(
5553
private const val DINING_SWIPES_PREDICTIONS = 3
5654

5755
const val START_DAY_OF_SEMESTER = "2026-01-14"
58-
private const val DAYS_IN_SEMESTER = 117f
56+
const val DAYS_IN_SEMESTER = 117f
5957
}
6058

6159
override fun onCreateViewHolder(
6260
parent: ViewGroup,
6361
viewType: Int,
6462
): RecyclerView.ViewHolder {
6563
mContext = parent.context
66-
mActivity = mContext as MainActivity
6764

6865
return when (viewType) {
6966
DINING_BALANCE -> {

0 commit comments

Comments
 (0)