diff --git a/PennMobile/src/main/AndroidManifest.xml b/PennMobile/src/main/AndroidManifest.xml
index 27cc004d..09b7e79e 100644
--- a/PennMobile/src/main/AndroidManifest.xml
+++ b/PennMobile/src/main/AndroidManifest.xml
@@ -27,6 +27,7 @@
android:theme="@style/AppTheme.Launcher"
android:usesCleartextTraffic="true"
tools:targetApi="m">
+
@@ -39,6 +40,19 @@
android:resource="@xml/dining_hall_widget_info" />
+
+
+
+
+
+
+
+
+
> getGsrReservations(
+ @Header("Authorization") String bearerToken
+ );
+}
diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/gsr/widget/GsrReservationsWidget.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/gsr/widget/GsrReservationsWidget.kt
new file mode 100644
index 00000000..2c7b4f73
--- /dev/null
+++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/gsr/widget/GsrReservationsWidget.kt
@@ -0,0 +1,119 @@
+package com.pennapps.labs.pennmobile.gsr.widget
+
+import android.app.PendingIntent
+import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProvider
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.widget.RemoteViews
+import com.google.gson.GsonBuilder
+import com.google.gson.reflect.TypeToken
+import com.pennapps.labs.pennmobile.MainActivity
+import com.pennapps.labs.pennmobile.R
+import com.pennapps.labs.pennmobile.api.GsrReservationsRequest
+import com.pennapps.labs.pennmobile.api.Serializer
+import com.pennapps.labs.pennmobile.gsr.classes.GSRReservation
+import com.squareup.okhttp.OkHttpClient
+import retrofit.RestAdapter
+import retrofit.client.OkClient
+import retrofit.converter.GsonConverter
+import java.util.concurrent.TimeUnit
+
+class GsrReservationsWidget : AppWidgetProvider() {
+ override fun onUpdate(
+ context: Context,
+ appWidgetManager: AppWidgetManager,
+ appWidgetIds: IntArray,
+ ) {
+ for (appWidgetId in appWidgetIds) {
+ val mainActivityIntent = Intent(context, MainActivity::class.java)
+ mainActivityIntent.apply {
+ flags += Intent.FLAG_ACTIVITY_NEW_TASK
+ flags += Intent.FLAG_ACTIVITY_CLEAR_TOP
+ }
+
+ mainActivityIntent.putExtra("Gsr_Tab_Switch", 1)
+
+ val serviceIntent = Intent(context, GsrReservationsWidgetAdapter::class.java)
+ val pendingIntent: PendingIntent =
+ PendingIntent.getActivity(
+ context,
+ appWidgetId,
+ mainActivityIntent,
+ PendingIntent.FLAG_IMMUTABLE,
+ )
+ serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
+
+ // setData allows the system to distinguish between different service intents. Without
+ // setData, onGetViewFactory is called only once for multiple widgets and send
+ // the same intent to all of them.
+ serviceIntent.setData(Uri.parse(serviceIntent.toUri(Intent.URI_INTENT_SCHEME)))
+
+ // Setting up the widget remoteViews; change cardview to something else
+ val views = RemoteViews(context.packageName, R.layout.gsr_reservations_widget)
+ views.setRemoteAdapter(R.id.gsr_reservation_widget_stack_view, serviceIntent)
+ views.setEmptyView(
+ R.id.gsr_reservation_widget_stack_view,
+ R.id.gsr_reservation_widget_empty_view,
+ )
+
+ // Setting up the intents for the remoteview for both when it is empty and
+ // when it loads the collection view (in this case we use setPendingIntentTemplate to
+ // the entire stackView that contains all the items).
+ views.setPendingIntentTemplate(R.id.gsr_reservation_widget_stack_view, pendingIntent)
+ views.setOnClickPendingIntent(R.id.gsr_reservation_widget_empty_view, pendingIntent)
+
+ // Notify appwidgetviewdata has changed to call getViewAt to set up the widget UI
+ // and handle update for every appwidget item in the Collection widget.
+ appWidgetManager.notifyAppWidgetViewDataChanged(
+ appWidgetId,
+ R.id.gsr_reservation_widget_stack_view,
+ )
+ appWidgetManager.updateAppWidget(appWidgetId, views)
+ }
+ }
+
+ // onEnabled and onDisabled are typically used for alarmManager testing and logs to check whether
+ // appwidget is properly enabled/disabled.
+ override fun onEnabled(context: Context) {
+ }
+
+ override fun onDisabled(context: Context) {
+ }
+
+ companion object {
+ private var mGSRReservationsRequest: GsrReservationsRequest? = null
+
+ @JvmStatic
+ val gsrReservationsRequestInstance: GsrReservationsRequest
+ get() {
+ if (mGSRReservationsRequest == null) {
+ val gsonBuilder = GsonBuilder()
+
+ // RegisterTypeAdapter with GsrReservationSerializer
+ // since we are only accessing gsr reservations
+ gsonBuilder.registerTypeAdapter(
+ object : TypeToken?>() {}.type,
+ Serializer.GsrReservationSerializer(),
+ )
+
+ val gson = gsonBuilder.create()
+ val okHttpClient = OkHttpClient()
+ okHttpClient.setConnectTimeout(35, TimeUnit.SECONDS) // Connection timeout
+ okHttpClient.setReadTimeout(35, TimeUnit.SECONDS) // Read timeout
+ okHttpClient.setWriteTimeout(35, TimeUnit.SECONDS) // Write timeout
+
+ val restAdapter =
+ RestAdapter
+ .Builder()
+ .setConverter(GsonConverter(gson))
+ .setClient(OkClient(okHttpClient))
+ .setEndpoint("https://pennmobile.org/api")
+ .build()
+ mGSRReservationsRequest = restAdapter.create(GsrReservationsRequest::class.java)
+ }
+ return mGSRReservationsRequest!!
+ }
+ }
+}
diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/gsr/widget/GsrReservationsWidgetAdapter.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/gsr/widget/GsrReservationsWidgetAdapter.kt
new file mode 100644
index 00000000..8cec89ea
--- /dev/null
+++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/gsr/widget/GsrReservationsWidgetAdapter.kt
@@ -0,0 +1,136 @@
+package com.pennapps.labs.pennmobile.gsr.widget
+
+import android.appwidget.AppWidgetManager
+import android.content.Context
+import android.content.Intent
+import android.graphics.BitmapFactory
+import android.os.Bundle
+import android.widget.RemoteViews
+import android.widget.RemoteViewsService
+import androidx.preference.PreferenceManager
+import com.google.firebase.crashlytics.FirebaseCrashlytics
+import com.pennapps.labs.pennmobile.R
+import com.pennapps.labs.pennmobile.api.GsrReservationsRequest
+import com.pennapps.labs.pennmobile.gsr.classes.GSRReservation
+import org.joda.time.format.DateTimeFormat
+import org.joda.time.format.DateTimeFormatter
+import rx.Observable
+import java.net.HttpURLConnection
+import java.net.URL
+
+class GsrReservationsWidgetAdapter : RemoteViewsService() {
+ override fun onGetViewFactory(intent: Intent): RemoteViewsFactory = GsrReservationWidgetFactory(applicationContext, intent)
+
+ class GsrReservationWidgetFactory(
+ private val context: Context,
+ intent: Intent,
+ ) : RemoteViewsFactory {
+ private var mGsrReservationsRequest: GsrReservationsRequest? = null
+ private var appWidgetId: Int =
+ intent.getIntExtra(
+ AppWidgetManager.EXTRA_APPWIDGET_ID,
+ AppWidgetManager.INVALID_APPWIDGET_ID,
+ )
+ private var dataSet: List = emptyList()
+ private var sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
+
+ override fun onCreate() {
+ mGsrReservationsRequest = GsrReservationsWidget.gsrReservationsRequestInstance
+ getWidgetGsrReservations()
+ }
+
+ // Not used since already handled
+ override fun onDataSetChanged() {
+ }
+
+ override fun onDestroy() {
+ }
+
+ override fun getCount(): Int = dataSet.size
+
+ // TODO("Get building name(?), and hopefully support click behavior")
+ override fun getViewAt(index: Int): RemoteViews {
+ val reservation = dataSet[index]
+ val roomName = reservation.name
+
+ val formatter: DateTimeFormatter =
+ DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ssZ")
+ val from = formatter.parseDateTime(reservation.fromDate)
+ val to = formatter.parseDateTime(reservation.toDate)
+ val day = from.toString("EEEE, MMMM d")
+ val fromHour = from.toString("h:mm a")
+ val toHour = to.toString("h:mm a")
+
+ val imageUrl =
+ reservation.info?.get("thumbnail")
+ ?: "https://s3.us-east-2.amazonaws.com/labs.api/dining/MBA+Cafe.jpg"
+
+ val views = RemoteViews(context.packageName, R.layout.gsr_reservations_widget_item)
+ views.setTextViewText(R.id.gsr_reservation_widget_item_location_tv, roomName)
+ views.setTextViewText(
+ R.id.gsr_reservation_widget_item_time_tv,
+ "$day\n$fromHour-$toHour",
+ )
+
+ try {
+ val urlConnection = URL(imageUrl)
+ val connection =
+ urlConnection
+ .openConnection() as HttpURLConnection
+ connection.doInput = true
+ connection.connect()
+ val input = connection.inputStream
+ val myBitmap = BitmapFactory.decodeStream(input)
+ views.setImageViewBitmap(R.id.gsr_reservation_widget_item_iv, myBitmap)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+
+ val extras = Bundle()
+ val fillInIntent = Intent()
+ fillInIntent.putExtras(extras)
+ views.setOnClickFillInIntent(R.id.gsr_reservation_widget_item_root, fillInIntent)
+
+ return views
+ }
+
+ override fun getLoadingView(): RemoteViews? = null
+
+ override fun getViewTypeCount(): Int = 1
+
+ override fun getItemId(id: Int): Long = id.toLong()
+
+ override fun hasStableIds(): Boolean = true
+
+ private fun getWidgetGsrReservations() {
+ try {
+ if (mGsrReservationsRequest != null) {
+ val token =
+ sharedPreferences.getString(
+ context.getString(R.string.access_token),
+ "",
+ )
+ mGsrReservationsRequest!!
+ .getGsrReservations("Bearer $token")
+ .flatMap { reservations -> Observable.from(reservations) }
+ .flatMap { reservation ->
+ Observable.just(reservation)
+ }.toList()
+ .subscribe { reservations ->
+ dataSet = reservations
+ println("subscribed")
+ val appWidgetManager: AppWidgetManager =
+ AppWidgetManager.getInstance(context)
+ appWidgetManager.notifyAppWidgetViewDataChanged(
+ appWidgetId,
+ R.id.gsr_reservation_widget_stack_view,
+ )
+ }
+ }
+ } catch (e: Exception) {
+ FirebaseCrashlytics.getInstance().recordException(e)
+ e.printStackTrace()
+ }
+ }
+ }
+}
diff --git a/PennMobile/src/main/res/layout/fragment_dining_holder.xml b/PennMobile/src/main/res/layout/fragment_dining_holder.xml
index 8a6cffdb..5ebf6f16 100644
--- a/PennMobile/src/main/res/layout/fragment_dining_holder.xml
+++ b/PennMobile/src/main/res/layout/fragment_dining_holder.xml
@@ -43,6 +43,10 @@
android:maxLines="1"
/>
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PennMobile/src/main/res/layout/gsr_reservations_widget_item.xml b/PennMobile/src/main/res/layout/gsr_reservations_widget_item.xml
new file mode 100644
index 00000000..5a979c13
--- /dev/null
+++ b/PennMobile/src/main/res/layout/gsr_reservations_widget_item.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PennMobile/src/main/res/values/ids.xml b/PennMobile/src/main/res/values/ids.xml
index 419d846a..e889caf6 100644
--- a/PennMobile/src/main/res/values/ids.xml
+++ b/PennMobile/src/main/res/values/ids.xml
@@ -7,4 +7,7 @@
+
+
+
diff --git a/PennMobile/src/main/res/xml/gsr_reservations_widget_info.xml b/PennMobile/src/main/res/xml/gsr_reservations_widget_info.xml
new file mode 100644
index 00000000..08b5a4a9
--- /dev/null
+++ b/PennMobile/src/main/res/xml/gsr_reservations_widget_info.xml
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/ktlint.jar b/ktlint.jar
new file mode 100644
index 00000000..da6183c6
Binary files /dev/null and b/ktlint.jar differ