diff --git a/PennMobile/src/main/AndroidManifest.xml b/PennMobile/src/main/AndroidManifest.xml index c78ddfcb..e309e37f 100644 --- a/PennMobile/src/main/AndroidManifest.xml +++ b/PennMobile/src/main/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="62" + android:versionName="2.1.0"> - + - diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/AboutFragment.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/AboutFragment.kt index ba29d736..9e8bcd2a 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/AboutFragment.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/AboutFragment.kt @@ -60,10 +60,10 @@ class AboutFragment : Fragment() { view.our_team_rv?.layoutManager = GridLayoutManager(context, 3) view.alumni_rv?.layoutManager = GridLayoutManager(context, 3) - val members = arrayListOf("Davies Lumumba", "Rohan Chhaya", "Vishesh Patel", - "Julius Snipes", "Belinda Xi", "Ansh Nagwekar", "Zhiyan Lu") + val members = arrayListOf("Rohan Chhaya", "Anna Jiang", "Julius Snipes", + "Zhiyan Lu", "Belinda Xi","Vishesh Patel", "Ali Krema", "Jenny Li", "Sruthi Kurada") val alumni = arrayListOf("Marta GarcĂ­a Ferreiro", "Varun Ramakrishnan", "Sahit Penmatcha", - "Anna Wang", "Sophia Ye", "Awad Irfan", "Liz Powell", "Anna Jiang") + "Anna Wang", "Sophia Ye", "Awad Irfan", "Liz Powell", "Davies Lumumba") view.our_team_rv?.adapter = AboutAdapter(members) view.alumni_rv?.adapter = AboutAdapter(alumni) diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/CampusExpressLoginFragment.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/CampusExpressLoginFragment.kt new file mode 100644 index 00000000..b34f3abc --- /dev/null +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/CampusExpressLoginFragment.kt @@ -0,0 +1,202 @@ +package com.pennapps.labs.pennmobile + +import android.content.SharedPreferences +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.util.Log +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse +import android.webkit.WebView +import android.webkit.WebViewClient +import android.widget.Button +import android.widget.LinearLayout +import android.widget.Toast +import androidx.fragment.app.FragmentTransaction +import androidx.preference.PreferenceManager +import com.pennapps.labs.pennmobile.api.CampusExpress +import com.pennapps.labs.pennmobile.classes.Account +import com.pennapps.labs.pennmobile.classes.CampusExpressAccessTokenResponse +import kotlinx.android.synthetic.main.fragment_login_webview.view.* +import org.apache.commons.lang3.RandomStringUtils +import retrofit.Callback +import retrofit.RetrofitError +import retrofit.client.Response +import java.security.MessageDigest +import java.util.* + +// TODO: Rename parameter arguments, choose names that match +// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER +private const val ARG_PARAM1 = "param1" +private const val ARG_PARAM2 = "param2" + +/** + * A simple [Fragment] subclass. + * Use the [CampusExpressLoginFragment.newInstance] factory method to + * create an instance of this fragment. + */ +class CampusExpressLoginFragment : Fragment() { + lateinit var webView: WebView + lateinit var headerLayout: LinearLayout + lateinit var cancelButton: Button + lateinit var user: Account + private var mCampusExpress: CampusExpress? = null + private lateinit var mActivity: MainActivity + lateinit var sp: SharedPreferences + lateinit var codeChallenge: String + lateinit var codeVerifier: String + lateinit var state: String + lateinit var campusExpressAuthUrl: String + lateinit var clientID: String + lateinit var redirectUri: String + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_campus_express_login, container, false) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + mCampusExpress = MainActivity.campusExpressInstance + mActivity = activity as MainActivity + sp = PreferenceManager.getDefaultSharedPreferences(mActivity) + + // These values are added to the BuildConfig at runtime, to allow GitHub Actions + // to build the app without pushing the secrets to GitHub + clientID = "5c09c08b240a56d22f06b46789d0528a" + redirectUri = "https://pennlabs.org/pennmobile/android/campus_express_callback/" + codeVerifier = RandomStringUtils.randomAlphanumeric(64) + codeChallenge = getCodeChallenge(codeVerifier) + state = getStateString() + campusExpressAuthUrl = "https://prod.campusexpress.upenn.edu/api/v1/oauth/authorize" + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + webView = view.findViewById(R.id.webView) + headerLayout = view.linear_layout + cancelButton = view.findViewById(R.id.cancel_button) + val uri = Uri.parse(campusExpressAuthUrl) + .buildUpon() + .appendQueryParameter("response_type", "code") + .appendQueryParameter("client_id", clientID) + .appendQueryParameter("state", state) + .appendQueryParameter("scope", "read") + .appendQueryParameter("code_challenge", codeChallenge) + .appendQueryParameter("code_challenge_method", "S256") + .appendQueryParameter("redirect_uri", redirectUri) + .build() + webView.loadUrl(uri.toString()) + val webSettings = webView.settings + webSettings.setJavaScriptEnabled(true) + webSettings.javaScriptCanOpenWindowsAutomatically = true; + webView.webViewClient = MyWebViewClient() + + cancelButton.setOnClickListener { + parentFragmentManager.popBackStack() + } + + } + + inner class MyWebViewClient : WebViewClient() { + + override fun onReceivedHttpError(view: WebView?, request: WebResourceRequest?, errorResponse: WebResourceResponse?) { + super.onReceivedHttpError(view, request, errorResponse) + view?.visibility = View.INVISIBLE + headerLayout.visibility = View.INVISIBLE + } + + override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean { + if (url.contains("callback") && url.contains("?code=")) { + val urlArr = url.split("?code=", "&state=").toTypedArray() + val authCode = urlArr[1] + val clientState = urlArr[2] + initiateAuthentication(authCode) + } + return super.shouldOverrideUrlLoading(view, url) + } + } + + private fun goToDiningInsights(refresh: Boolean) { + if(refresh) { + val fragment = DiningInsightsFragment() + parentFragmentManager.beginTransaction() + .replace(R.id.campus_express_page, fragment) + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) + .addToBackStack(null) + .commit() + } else { + parentFragmentManager.popBackStack() + } + } + + private fun initiateAuthentication(authCode: String) { + mCampusExpress?.getAccessToken(authCode, + "authorization_code", clientID, redirectUri, codeVerifier, + object : Callback { + + override fun success(t: CampusExpressAccessTokenResponse?, response: Response?) { + if (response?.status == 200) { + val accessToken = t?.accessToken + val expiresIn = t?.expiresIn + val editor = sp.edit() + if (accessToken != null) { + editor.putString(mActivity.getString(R.string.campus_express_token),accessToken) + } + if (expiresIn != null) { + val currentDate = Date() + currentDate.time = currentDate.time + (expiresIn * 1000) + val expiresAt = currentDate.time + editor.putLong(mActivity.getString(R.string.campus_token_expires_in), expiresAt) + } + editor.apply() + goToDiningInsights(true) + } + } + + override fun failure(error: RetrofitError) { + Log.e("Campus Webview", "Error fetching access token $error") + Toast.makeText(context, "Error getting campus express authorization", Toast.LENGTH_SHORT).show() + goToDiningInsights(false) + } + }) + } + + private fun getCodeChallenge(codeVerifier: String): String { + + // Hash the code verifier + val md = MessageDigest.getInstance("SHA-256") + val byteArr = md.digest(codeVerifier.toByteArray()) + + // Base-64 encode + var codeChallenge = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Base64.getEncoder().encodeToString(byteArr) + } else { + String( + android.util.Base64.encode(byteArr, android.util.Base64.DEFAULT), + Charsets.UTF_8) + } + + // Replace characters to make it web safe + codeChallenge = codeChallenge.replace("=", "") + codeChallenge = codeChallenge.replace("+", "-") + codeChallenge = codeChallenge.replace("/", "_") + + return codeChallenge + } + + private fun getStateString(): String { + var stateString = RandomStringUtils.randomAlphanumeric(64) + + // Replace characters to make it web safe + stateString = stateString.replace("=", "") + stateString = stateString.replace("+", "-") + stateString = stateString.replace("/", "_") + + return stateString + } +} \ No newline at end of file diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/DiningFragment.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/DiningFragment.kt index 444a999c..816a1fb9 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/DiningFragment.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/DiningFragment.kt @@ -62,13 +62,12 @@ class DiningFragment : Fragment() { v.dining_halls_recycler_view?.layoutManager = LinearLayoutManager(mActivity, LinearLayoutManager.VERTICAL, false) v.dining_swiperefresh.setOnRefreshListener { getDiningHalls() } getDiningHalls() - initAppBar(v) + // initAppBar(v) return v } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setTitle("Dining") } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -159,14 +158,13 @@ class DiningFragment : Fragment() { view?.let {displaySnack(it, "Just Updated")} } }, { + Log.e("DiningFragment", "Error getting dining halls", it); mActivity.runOnUiThread { Log.e("Dining", "Could not load Dining page", it) loadingPanel?.visibility = View.GONE - internetConnectionDining?.setBackgroundColor(resources.getColor(R.color.logo_dark_blue)) - //R.color.darkRedBackground)) - internetConnection_message_dining?.setText("Pardon our dust, new features coming soon!") - //getString(R.string.internet_error)) - internetConnectionDining?.visibility = View.VISIBLE + //internetConnectionDining?.setBackgroundColor(resources.getColor(R.color.darkRedBackground)) + //internetConnection_message_dining?.text = getString(R.string.internet_error) + //internetConnectionDining?.visibility = View.VISIBLE no_results?.visibility = View.VISIBLE dining_swiperefresh?.isRefreshing = false } @@ -182,16 +180,6 @@ class DiningFragment : Fragment() { } } - private fun initAppBar(view: View) { - if (Build.VERSION.SDK_INT > 16) { - (view.appbar_home.layoutParams as CoordinatorLayout.LayoutParams).behavior = ToolbarBehavior() - } - view.date_view.text = Utils.getCurrentSystemTime() - } - - private fun setTitle(title: CharSequence) { - title_view.text = title - } /** * Shows SnackBar message right below the app bar diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/DiningHolderFragment.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/DiningHolderFragment.kt new file mode 100644 index 00000000..69acc95e --- /dev/null +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/DiningHolderFragment.kt @@ -0,0 +1,103 @@ +package com.pennapps.labs.pennmobile + +import android.os.Build +import android.os.Bundle +import android.util.Log +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import androidx.annotation.RequiresApi +import androidx.coordinatorlayout.widget.CoordinatorLayout +import com.google.android.material.snackbar.Snackbar +import com.google.android.material.tabs.TabLayoutMediator +import com.pennapps.labs.pennmobile.adapters.DiningAdapter +import com.pennapps.labs.pennmobile.adapters.DiningPagerAdapter +import com.pennapps.labs.pennmobile.classes.DiningHall +import com.pennapps.labs.pennmobile.classes.Venue +import com.pennapps.labs.pennmobile.components.collapsingtoolbar.ToolbarBehavior +import com.pennapps.labs.pennmobile.utils.Utils +import kotlinx.android.synthetic.main.fragment_dining.* +import kotlinx.android.synthetic.main.fragment_dining.view.* +import kotlinx.android.synthetic.main.fragment_dining_holder.* +import kotlinx.android.synthetic.main.fragment_dining_holder.view.* +import kotlinx.android.synthetic.main.loading_panel.* +import kotlinx.android.synthetic.main.no_results.* +import rx.Observable + +class DiningHolderFragment : Fragment() { + + lateinit var pagerAdapter: DiningPagerAdapter + private lateinit var mActivity: MainActivity + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + mActivity = activity as MainActivity + mActivity.closeKeyboard() + + } + + @RequiresApi(Build.VERSION_CODES.M) + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.fragment_dining_holder, container, false) + view.dining_swiperefresh?.setOnRefreshListener { getConnected() } + view.dining_swiperefresh?.setColorSchemeResources(R.color.color_accent, R.color.color_primary) + getConnected() + initAppBar(view) + // Inflate the layout for this fragment + return view + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + pagerAdapter = DiningPagerAdapter(childFragmentManager, lifecycle) + pager?.setAdapter(pagerAdapter) + pager.setUserInputEnabled(false) + TabLayoutMediator(tabLayout, pager) { tab, position -> + if (position == 0) { + tab.text = "Dining" + } else { + tab.text = "Insights" + } + }.attach() + setTitle("Dining") + } + + @RequiresApi(Build.VERSION_CODES.M) + private fun getConnected() { + //displays banner if not connected + if (!isOnline(context)) { + internetConnectionDiningHolder?.setBackgroundColor(resources.getColor(R.color.darkRedBackground)) + internetConnection_message_dining_holder?.text = getString(R.string.internet_error) + internetConnectionDiningHolder?.visibility = View.VISIBLE + // loadingPanel?.visibility = View.GONE + } else { + internetConnectionDiningHolder?.visibility = View.GONE + // loadingPanel?.visibility = View.GONE + // dining_swiperRefresh_holder?.isRefreshing = false + } + } + + override fun onResume() { + super.onResume() + mActivity.removeTabs() + //mActivity.toolbar.visibility = View.GONE + mActivity.setTitle(R.string.dining) + if (Build.VERSION.SDK_INT > 17) { + mActivity.setSelectedTab(MainActivity.DINING) + } + } + + private fun initAppBar(view: View) { + if (Build.VERSION.SDK_INT > 16) { + (view.appbar_home_holder.layoutParams as CoordinatorLayout.LayoutParams).behavior = ToolbarBehavior() + } + view.date_view.text = Utils.getCurrentSystemTime() + } + + private fun setTitle(title: CharSequence) { + title_view.text = title + } +} \ No newline at end of file diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/DiningInfoFragment.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/DiningInfoFragment.kt index ec8d20dd..7a5bd9d9 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/DiningInfoFragment.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/DiningInfoFragment.kt @@ -2,7 +2,6 @@ package com.pennapps.labs.pennmobile import android.content.Context import android.graphics.Color -import android.graphics.Typeface import android.net.ConnectivityManager import android.os.Build import android.os.Bundle @@ -54,11 +53,17 @@ class DiningInfoFragment : Fragment() { val days = mDiningHall?.venue?.allHours() ?: ArrayList() var vertical = ArrayList() for (day in days) { - vertical = addDiningHour(day, vertical) + if (hasMeals(day)) { + vertical = addDiningHour(day, vertical) + } } } } + private fun hasMeals(day: VenueInterval): Boolean { + return day.meals.isNotEmpty(); + } + private fun addDiningHour(day: VenueInterval, vertical: ArrayList): ArrayList { val textView = TextView(mActivity) val intervalFormatter = DateTimeFormat.forPattern("yyyy-MM-dd") diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/DiningInsightsFragment.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/DiningInsightsFragment.kt new file mode 100644 index 00000000..b816d685 --- /dev/null +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/DiningInsightsFragment.kt @@ -0,0 +1,124 @@ +package com.pennapps.labs.pennmobile + +import android.os.Build +import android.os.Bundle +import android.util.Log +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.RequiresApi +import androidx.fragment.app.FragmentTransaction +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.pennapps.labs.pennmobile.adapters.DiningInsightsCardAdapter +import com.pennapps.labs.pennmobile.api.CampusExpress +import com.pennapps.labs.pennmobile.api.CampusExpressNetworkManager +import com.pennapps.labs.pennmobile.classes.DiningBalances +import com.pennapps.labs.pennmobile.classes.DiningInsightCell +import com.pennapps.labs.pennmobile.classes.DollarsSpentCell +import kotlinx.android.synthetic.main.fragment_dining.* +import kotlinx.android.synthetic.main.fragment_dining.view.* +import kotlinx.android.synthetic.main.fragment_dining_insights.* +import kotlinx.android.synthetic.main.fragment_dining_insights.view.* +import kotlinx.android.synthetic.main.fragment_home.view.* +import java.util.* +import kotlin.collections.ArrayList + + +/** + * A simple [Fragment] subclass. + * Use the [DiningInsightsFragment.newInstance] factory method to + * create an instance of this fragment. + */ +class DiningInsightsFragment : Fragment() { + + private lateinit var mActivity : MainActivity + private lateinit var mCampusExpress: CampusExpress + private lateinit var networkManager: CampusExpressNetworkManager + private lateinit var cells : ArrayList + private lateinit var insightsrv : RecyclerView + @RequiresApi(Build.VERSION_CODES.O) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + mCampusExpress = MainActivity.campusExpressInstance + mActivity = activity as MainActivity + mActivity.closeKeyboard() + } + + @RequiresApi(Build.VERSION_CODES.O) + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.fragment_dining_insights, container, false) + view.dining_insights_refresh?.setOnRefreshListener { refresh() } + view.dining_insights_refresh?.setColorSchemeResources(R.color.color_accent, R.color.color_primary) + view.insightsrv.layoutManager = LinearLayoutManager( + context, + LinearLayoutManager.VERTICAL, false) + networkManager = CampusExpressNetworkManager(mActivity) + val diningBalance = DollarsSpentCell() + diningBalance.type = "dining_balance" + cells = ArrayList() + cells.add(diningBalance) + insightsrv = view.insightsrv + insightsrv.adapter = DiningInsightsCardAdapter(cells) + val networkManager = CampusExpressNetworkManager(mActivity) + val accessToken = networkManager.getAccessToken() + if (accessToken == "") { + val fragment = CampusExpressLoginFragment() + parentFragmentManager.beginTransaction() + .replace(R.id.dining_insights_page, fragment) + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) + .addToBackStack("DiningInsightsFragment") + .commit() + } + getInsights(accessToken) + // Inflate the layout for this fragment + return view + } + + + + override fun onResume() { + super.onResume() + } + + private fun refresh() { + val accessToken = networkManager.getAccessToken() + if (accessToken == "") { + dining_insights_refresh?.isRefreshing = false + val fragment = CampusExpressLoginFragment() + parentFragmentManager.beginTransaction() + .replace(R.id.dining_insights_page, fragment) + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) + .addToBackStack("DiningInsightsFragment") + .commit() + } else { + dining_insights_refresh?.isRefreshing = true + getInsights(accessToken) + } + } + + + + + private fun getInsights(accessToken: String?) { + val bearerToken = "Bearer $accessToken" + mCampusExpress.getCurrentDiningBalances(bearerToken).subscribe( { t: DiningBalances? -> + activity?.runOnUiThread { + val diningBalanceCell = cells[0] + diningBalanceCell.diningBalances = t + (insightsrv.adapter as DiningInsightsCardAdapter).notifyItemChanged(0) + dining_insights_refresh?.isRefreshing = false + } }, + { throwable -> + activity?.runOnUiThread { + Log.e("DiningInsightsFragment", "Error getting balances", throwable) + dining_insights_refresh?.isRefreshing = false + } + }) + + } + +} \ No newline at end of file diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/GsrFragment.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/GsrFragment.kt index a09e090f..1e50b919 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/GsrFragment.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/GsrFragment.kt @@ -293,6 +293,7 @@ class GsrFragment : Fragment() { val endString = currSlot.endTime if (startString != null && endString != null) { + val endTime = gsrSlotFormatter.parseDateTime(endString) if (endTime.isAfter(selectedDateTime)) { availableSlotsAfterSelectedTime.add(currSlot) @@ -317,7 +318,8 @@ class GsrFragment : Fragment() { val stringStartTime = startTime.toString(timeFormatter) val stringEndTime = endTime.toString(timeFormatter) val start = startingSlot.startTime ?: "" - val end = startingSlot.endTime ?: "" + //note that end uses ending slot to account for 30+ min booking times + val end = endingSlot.endTime ?: "" val gsrName = gsrRoom.name ?: "" val gsrRoomId = gsrRoom.room_id ?: 0 insertGSRSlot(gsrName, "$stringStartTime-$stringEndTime", diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/GsrTabbedFragment.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/GsrTabbedFragment.kt index b773a8a2..35c570b0 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/GsrTabbedFragment.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/GsrTabbedFragment.kt @@ -20,7 +20,7 @@ import kotlinx.android.synthetic.main.fragment_gsr_tabs.view.* import kotlinx.android.synthetic.main.fragment_gsr_tabs.view.appbar_home import kotlinx.android.synthetic.main.fragment_gsr_tabs.view.date_view import kotlinx.android.synthetic.main.include_main.* -import kotlinx.android.synthetic.main.fragment_dining.view.title_view as title_view1 +import kotlinx.android.synthetic.main.fragment_dining_holder.view.title_view as title_view1 class GsrTabbedFragment : Fragment() { 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 e3502c87..010bac4e 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/HomeFragment.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/HomeFragment.kt @@ -86,9 +86,9 @@ class HomeFragment : Fragment() { val sessionID = sp.getString(getString(R.string.huntsmanGSR_SessionID), "") val accountID = sp.getString(getString(R.string.accountID), "") val deviceID = OAuth2NetworkManager(mActivity).getDeviceId() + OAuth2NetworkManager(mActivity).getAccessToken() val bearerToken = "Bearer " + sp.getString(getString(R.string.access_token), "").toString() Log.i("HomeFragment", bearerToken) - OAuth2NetworkManager(mActivity).getAccessToken() //displays banner if not connected if (!isOnline(context)) { @@ -103,8 +103,38 @@ class HomeFragment : Fragment() { // get API data val homepageCells = mutableListOf() + + for (i in 1..5) { + homepageCells.add(HomeCell()) + } + val studentLife = MainActivity.studentLifeInstance if (bearerToken != "Bearer ") { + + studentLife.getNews().subscribe({ article -> + mActivity.runOnUiThread { + val newsCell = HomeCell() + newsCell.info = HomeCellInfo() + newsCell.info?.article = article + newsCell.type = "news" + homepageCells.set(1, newsCell) + home_cells_rv?.adapter = HomeAdapter(ArrayList(homepageCells)) + loadingPanel?.visibility = View.GONE + home_refresh_layout?.isRefreshing = false + } + }, { throwable -> + mActivity.runOnUiThread { + Log.e("Home", "Could not load news", throwable) + throwable.printStackTrace() + Toast.makeText(mActivity, "Could not load news", Toast.LENGTH_LONG).show() + loadingPanel?.visibility = View.GONE + internetConnectionHome?.setBackgroundColor(resources.getColor(R.color.darkRedBackground)) + internetConnection_message?.text = getString(R.string.internet_error) + internetConnectionHome?.visibility = View.VISIBLE + home_refresh_layout?.isRefreshing = false + } + }) + studentLife.getDiningPreferences(bearerToken).subscribe({ preferences -> mActivity.runOnUiThread { val list = preferences.preferences @@ -124,7 +154,7 @@ class HomeFragment : Fragment() { } diningCellInfo.venues = venues diningCell.info = diningCellInfo - homepageCells.add(0, diningCell) + homepageCells.set(2, diningCell) home_cells_rv?.adapter = HomeAdapter(ArrayList(homepageCells)) loadingPanel?.visibility = View.GONE internetConnectionHome?.visibility = View.GONE @@ -142,39 +172,17 @@ class HomeFragment : Fragment() { home_refresh_layout?.isRefreshing = false } }) - studentLife.getNews().subscribe({ article -> - mActivity.runOnUiThread { - val newsCell = HomeCell() - newsCell.info = HomeCellInfo() - newsCell.info?.article = article - newsCell.type = "news" - homepageCells.add((homepageCells.size * .25).toInt(), newsCell) - home_cells_rv?.adapter = HomeAdapter(ArrayList(homepageCells)) - loadingPanel?.visibility = View.GONE - home_refresh_layout?.isRefreshing = false - } - }, { throwable -> - mActivity.runOnUiThread { - Log.e("Home", "Could not load news", throwable) - throwable.printStackTrace() - Toast.makeText(mActivity, "Could not load news", Toast.LENGTH_LONG).show() - loadingPanel?.visibility = View.GONE - internetConnectionHome?.setBackgroundColor(resources.getColor(R.color.darkRedBackground)) - internetConnection_message?.text = getString(R.string.internet_error) - internetConnectionHome?.visibility = View.VISIBLE - home_refresh_layout?.isRefreshing = false - } - }) + studentLife.getCalendar().subscribe({ events -> mActivity.runOnUiThread { val calendar = HomeCell() calendar.type = "calendar" calendar.events = events - homepageCells.add((homepageCells.size * .50).toInt(), calendar) + homepageCells.set(0, calendar) val gsrBookingCell = HomeCell() gsrBookingCell.type = "gsr_booking" gsrBookingCell.buildings = arrayListOf("Huntsman Hall", "Weigle") - homepageCells.add((homepageCells.size * .75).toInt(), gsrBookingCell) + homepageCells.set(3, gsrBookingCell) home_cells_rv?.adapter = HomeAdapter(ArrayList(homepageCells)) loadingPanel?.visibility = View.GONE home_refresh_layout?.isRefreshing = false @@ -191,6 +199,7 @@ class HomeFragment : Fragment() { home_refresh_layout?.isRefreshing = false } }) + studentLife.getLaundryPref(bearerToken).subscribe({ preferences -> mActivity.runOnUiThread { val venues = mutableListOf() @@ -201,7 +210,7 @@ class HomeFragment : Fragment() { laundryCellInfo.roomId = preferences[0] } laundryCell.info = laundryCellInfo - homepageCells.add(homepageCells.size, laundryCell) + homepageCells.set(4, laundryCell) home_cells_rv?.adapter = HomeAdapter(ArrayList(homepageCells)) loadingPanel?.visibility = View.GONE internetConnectionHome?.visibility = View.GONE @@ -219,6 +228,7 @@ class HomeFragment : Fragment() { home_refresh_layout?.isRefreshing = false } }) + /*studentLife.getHomePage(bearerToken).subscribe({ cells -> mActivity.runOnUiThread { val gsrBookingCell = HomeCell() @@ -244,17 +254,12 @@ class HomeFragment : Fragment() { } }) */ } else { - studentLife.getNews().subscribe({ article -> + studentLife.getCalendar().subscribe({ events -> mActivity.runOnUiThread { - val newsCell = HomeCell() - newsCell.info = HomeCellInfo() - newsCell.info?.article = article - newsCell.type = "news" - homepageCells.add(homepageCells.size, newsCell) - val gsrBookingCell = HomeCell() - gsrBookingCell.type = "gsr_booking" - gsrBookingCell.buildings = arrayListOf("Huntsman Hall", "Weigle") - homepageCells.add(homepageCells.size, gsrBookingCell) + val calendar = HomeCell() + calendar.type = "calendar" + calendar.events = events + homepageCells.add(0, calendar) home_cells_rv?.adapter = HomeAdapter(ArrayList(homepageCells)) loadingPanel?.visibility = View.GONE home_refresh_layout?.isRefreshing = false @@ -271,12 +276,18 @@ class HomeFragment : Fragment() { home_refresh_layout?.isRefreshing = false } }) - studentLife.getCalendar().subscribe({ events -> + + studentLife.getNews().subscribe({ article -> mActivity.runOnUiThread { - val calendar = HomeCell() - calendar.type = "calendar" - calendar.events = events - homepageCells.add(homepageCells.size, calendar) + val newsCell = HomeCell() + newsCell.info = HomeCellInfo() + newsCell.info?.article = article + newsCell.type = "news" + homepageCells.add(homepageCells.size, newsCell) + val gsrBookingCell = HomeCell() + gsrBookingCell.type = "gsr_booking" + gsrBookingCell.buildings = arrayListOf("Huntsman Hall", "Weigle") + homepageCells.add(homepageCells.size, gsrBookingCell) home_cells_rv?.adapter = HomeAdapter(ArrayList(homepageCells)) loadingPanel?.visibility = View.GONE home_refresh_layout?.isRefreshing = false @@ -293,6 +304,7 @@ class HomeFragment : Fragment() { home_refresh_layout?.isRefreshing = false } }) + } } diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/MainActivity.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/MainActivity.kt index 243054d9..39ed9dd1 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/MainActivity.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/MainActivity.kt @@ -39,6 +39,7 @@ import com.google.gson.GsonBuilder import com.google.gson.reflect.TypeToken import com.pennapps.labs.pennmobile.api.StudentLife import com.pennapps.labs.pennmobile.api.OAuth2NetworkManager +import com.pennapps.labs.pennmobile.api.CampusExpress import com.pennapps.labs.pennmobile.api.Platform import com.pennapps.labs.pennmobile.api.Serializer.* import com.pennapps.labs.pennmobile.classes.* @@ -134,12 +135,12 @@ class MainActivity : AppCompatActivity() { "Home" -> if (fragmentManager.backStackEntryCount > 0) { fragment = HomeFragment() } - "Dining" -> fragment = DiningFragment() + "Dining" -> fragment = DiningHolderFragment() "GSR" -> fragment = GsrTabbedFragment() "Laundry" -> fragment = LaundryFragment() "More" -> fragment = MoreFragment() } - fragmentTransact(fragment) + fragmentTransact(fragment, true) } } @@ -205,10 +206,14 @@ class MainActivity : AppCompatActivity() { } } - fun fragmentTransact(fragment: Fragment?) { + fun fragmentTransact(fragment: Fragment?, popBackStack: Boolean) { if (fragment != null) { runOnUiThread { try { + //TODO ALI COMMENT + if (popBackStack) { + fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) + } fragmentManager.beginTransaction() .replace(R.id.content_frame, fragment) .addToBackStack(null) @@ -264,6 +269,8 @@ class MainActivity : AppCompatActivity() { private var mStudentLife: StudentLife? = null private var mPlatform: Platform? = null + private var mCampusExpress: CampusExpress? = null + //GSR Notifications internal var GSRIntents: HashMap = hashMapOf() internal var GSRAlarmManager: AlarmManager? = null @@ -277,10 +284,27 @@ class MainActivity : AppCompatActivity() { ) } } - fun setNotificationId(id: Int) { notificationId = id } + + @JvmStatic + val campusExpressInstance: CampusExpress + get() { + if (mCampusExpress == null) { + val gsonBuilder = GsonBuilder() + val gson = gsonBuilder.create() + val restAdapter = RestAdapter.Builder() + .setConverter(GsonConverter(gson)) + .setLogLevel(RestAdapter.LogLevel.FULL) + .setLog(AndroidLog("Campus Express")) + .setEndpoint(Platform.campusExpressBaseUrl) + .build() + mCampusExpress = restAdapter.create(CampusExpress::class.java) + } + return mCampusExpress!! + } + @JvmStatic val platformInstance: Platform get() { diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/SettingsFragment.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/SettingsFragment.kt index c551ccb0..b3c3fc16 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/SettingsFragment.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/SettingsFragment.kt @@ -91,6 +91,8 @@ class SettingsFragment : PreferenceFragmentCompat() { editor.remove(getString(R.string.accountID)) editor.remove(getString(R.string.access_token)) editor.remove(getString(R.string.guest_mode)) + editor.remove(getString(R.string.campus_express_token)) + editor.remove(getString(R.string.campus_token_expires_in)) editor.apply() dialog.cancel() mActivity.startLoginFragment() diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/AboutAdapter.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/AboutAdapter.kt index 494da6d8..7ffaf0ee 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/AboutAdapter.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/AboutAdapter.kt @@ -41,6 +41,9 @@ class AboutAdapter(private var members: ArrayList) "Julius Snipes" -> R.drawable.julius "Ansh Nagwekar" -> R.drawable.ansh "Zhiyan Lu" -> R.drawable.zhiyan + "Ali Krema" -> R.drawable.ali + "Jenny Li" -> R.drawable.jenny + "Sruthi Kurada" -> R.drawable.sruthi else -> null } if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M){ diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/DiningInsightsCardAdapter.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/DiningInsightsCardAdapter.kt new file mode 100644 index 00000000..9e0ec091 --- /dev/null +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/DiningInsightsCardAdapter.kt @@ -0,0 +1,91 @@ +package com.pennapps.labs.pennmobile.adapters + +import android.content.Context +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.pennapps.labs.pennmobile.* +import com.pennapps.labs.pennmobile.classes.DiningInsightCell + +class DiningInsightsCardAdapter(private var cells: ArrayList) : + RecyclerView.Adapter() { + + private lateinit var mContext: Context + private lateinit var mActivity: MainActivity + + companion object { + // Types of Home Cells + private const val NOT_SUPPORTED = -1 + private const val DINING_BALANCE = 0 + private const val DINING_DOLLARS_SPENT = 1 + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + mContext = parent.context + mActivity = mContext as MainActivity + + return when (viewType) { + DINING_BALANCE -> { + ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.dining_balances_card, parent, false)) + } + DINING_DOLLARS_SPENT -> { + ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.dining_spent_card, parent, false)) + } + NOT_SUPPORTED -> { + ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.empty_view, parent, false)) + } + else -> { + ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.empty_view, parent, false)) + } + } + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val cell = cells[position] + when (cell.type) { + "dining_balance" -> bindDiningBalanceCells(holder, cell) + "dining_dollars_spent" -> bindDollarsSpentReservationsCell(holder, cell) + else -> Log.i("HomeAdapter", "Unsupported type of data at position $position") + } + } + + override fun getItemCount(): Int { + return cells.size + } + + inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val view = itemView + } + + override fun getItemViewType(position: Int): Int { + val cell = cells[position] + return when (cell.type) { + "dining_balance" -> DINING_BALANCE + "dining_dollars_spent" -> DINING_DOLLARS_SPENT + else -> NOT_SUPPORTED + } + } + + private fun bindDollarsSpentReservationsCell(holder: ViewHolder, cell: DiningInsightCell) { + // Populate dining dollars spent card + + } + + private fun bindDiningBalanceCells(holder: ViewHolder, cell: DiningInsightCell) { + val v = holder.view + val diningBalances = cell.diningBalances + val diningDollars = "$" + (diningBalances?.diningDollars ?: "0.00") + val swipes = diningBalances?.regularVisits ?: 0 + val guestSwipes = diningBalances?.guestVisits ?: 0 + val tvDiningDollarsAmount = (v.findViewById(R.id.dining_dollars_amount) as TextView) + tvDiningDollarsAmount.text = diningDollars + val tvRegularSwipesAmount = (v.findViewById(R.id.swipes_amount) as TextView) + tvRegularSwipesAmount.text = swipes.toString() + val tvGuestSwipesAmount = (v.findViewById(R.id.guest_swipes_amount) as TextView) + tvGuestSwipesAmount.text = guestSwipes.toString() + } + +} \ No newline at end of file diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/DiningPagerAdapter.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/DiningPagerAdapter.kt new file mode 100644 index 00000000..9bd02d5c --- /dev/null +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/DiningPagerAdapter.kt @@ -0,0 +1,38 @@ +package com.pennapps.labs.pennmobile.adapters + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.pennapps.labs.pennmobile.DiningFragment +import com.pennapps.labs.pennmobile.DiningInsightsFragment +import java.util.* + +class DiningPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle?) : FragmentStateAdapter(fragmentManager, lifecycle!!) { + override fun createFragment(position: Int): Fragment { + if (position == 0) { + return DiningFragment() + } else { + return DiningInsightsFragment() + } + } + + override fun getItemCount(): Int { + return COUNT + } + + override fun getItemId(position: Int): Long { + if (position == HOME_POSITION) { + return HOME_POSITION.toLong() + } else if (position == INSIGHTS_POSITION) { + return INSIGHTS_POSITION.toLong() + } + return createFragment(position).hashCode().toLong() + } + + companion object { + private const val HOME_POSITION = 0 + private const val INSIGHTS_POSITION = 1 + val COUNT = 2 + } +} \ No newline at end of file diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/GsrRoomAdapter.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/GsrRoomAdapter.kt index 0aadb938..61e79f50 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/GsrRoomAdapter.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/GsrRoomAdapter.kt @@ -6,7 +6,6 @@ import androidx.recyclerview.widget.RecyclerView import android.view.LayoutInflater import android.view.ViewGroup import com.pennapps.labs.pennmobile.* -import com.pennapps.labs.pennmobile.classes.GSRRoom import org.joda.time.DateTime class GsrRoomAdapter(internal var timeRanges: ArrayList, internal var ids: ArrayList, @@ -53,7 +52,6 @@ class GsrRoomAdapter(internal var timeRanges: ArrayList, internal var id return gsrRoomHolder } - override fun onBindViewHolder(holder: GsrRoomHolder, position: Int) { if (position < itemCount) { val time = timeRanges[position] 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 9d00d3ae..8d44ce35 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,7 +43,7 @@ import rx.Observable class HomeAdapter(private var cells: ArrayList) : - RecyclerView.Adapter() { + RecyclerView.Adapter() { private lateinit var mContext: Context private lateinit var mActivity: MainActivity @@ -140,7 +140,7 @@ class HomeAdapter(private var cells: ArrayList) : holder.itemView.home_card_subtitle.text = "GSR RESERVATIONS" holder.itemView.home_card_rv.layoutManager = LinearLayoutManager(mContext, - LinearLayoutManager.VERTICAL, false) + LinearLayoutManager.VERTICAL, false) holder.itemView.home_card_rv.adapter = GsrReservationsAdapter(ArrayList(reservations)) } @@ -149,30 +149,30 @@ class HomeAdapter(private var cells: ArrayList) : holder.itemView.home_card_subtitle.text = "DINING HALLS" holder.itemView.dining_prefs_btn.visibility = View.VISIBLE holder.itemView.dining_prefs_btn.setOnClickListener { - mActivity.fragmentTransact(DiningSettingsFragment()) + mActivity.fragmentTransact(DiningSettingsFragment(), false) } mStudentLife.venues() - .flatMap { venues -> Observable.from(venues) } - .flatMap { venue -> - val hall = DiningFragment.createHall(venue) - Observable.just(hall) - } - .toList() - .subscribe { diningHalls -> - mActivity.runOnUiThread { - val favorites: ArrayList = arrayListOf() - val favoritesIdList: List? = cell.info?.venues - diningHalls.forEach { - if (favoritesIdList?.contains(it.id) == true) { - favorites.add(it) - } + .flatMap { venues -> Observable.from(venues) } + .flatMap { venue -> + val hall = DiningFragment.createHall(venue) + Observable.just(hall) + } + .toList() + .subscribe { diningHalls -> + mActivity.runOnUiThread { + val favorites: ArrayList = arrayListOf() + val favoritesIdList: List? = cell.info?.venues + diningHalls.forEach { + if (favoritesIdList?.contains(it.id) == true) { + favorites.add(it) } - holder.itemView.home_card_rv.layoutManager = LinearLayoutManager(mContext, - LinearLayoutManager.VERTICAL, false) - holder.itemView.home_card_rv.adapter = DiningCardAdapter(favorites) } + holder.itemView.home_card_rv.layoutManager = LinearLayoutManager(mContext, + LinearLayoutManager.VERTICAL, false) + holder.itemView.home_card_rv.adapter = DiningCardAdapter(favorites) } + } } private fun bindNewsCell(holder: ViewHolder, cell: HomeCell) { @@ -182,10 +182,10 @@ class HomeAdapter(private var cells: ArrayList) : holder.itemView.home_news_timestamp.text = article?.timestamp?.trim() Picasso.get() - .load(article?.imageUrl) - .fit() - .centerCrop() - .into(holder.itemView.home_news_iv) + .load(article?.imageUrl) + .fit() + .centerCrop() + .into(holder.itemView.home_news_iv) /** Adds dynamically generated accent color from the fetched image to the news card */ var accentColor: Int = getColor(mContext, R.color.black) @@ -201,7 +201,7 @@ class HomeAdapter(private var cells: ArrayList) : // Change all the components to match the accent color palette vibrantSwatch?.titleTextColor?.let { DrawableCompat.setTint(DrawableCompat.wrap(holder.itemView.news_card_logo.drawable), - ColorUtils.setAlphaComponent(it, 150)) + ColorUtils.setAlphaComponent(it, 150)) DrawableCompat.setTint(DrawableCompat.wrap(holder.itemView.news_info_icon.drawable), it) DrawableCompat.setTint(DrawableCompat.wrap(holder.itemView.dot_divider.drawable), it) holder.itemView.button.setTextColor(ColorUtils.setAlphaComponent(it, 150)) @@ -211,10 +211,10 @@ class HomeAdapter(private var cells: ArrayList) : holder.itemView.home_news_timestamp.setTextColor(it) } holder.itemView.news_card_container.background = BitmapDrawable( - holder.view.resources, - bitmap) + holder.view.resources, + bitmap) holder.itemView.blurView - .setOverlayColor(ColorUtils.setAlphaComponent(accentColor, 150)) + .setOverlayColor(ColorUtils.setAlphaComponent(accentColor, 150)) } } @@ -225,13 +225,13 @@ class HomeAdapter(private var cells: ArrayList) : holder.itemView.home_news_subtitle.visibility = View.VISIBLE holder.itemView.home_news_title.setPadding(0, 0, 0, 0) holder.itemView.blurView - .setOverlayColor(ColorUtils.setAlphaComponent(accentColor, 250)) + .setOverlayColor(ColorUtils.setAlphaComponent(accentColor, 250)) } View.VISIBLE -> { holder.itemView.home_news_subtitle.visibility = View.GONE holder.itemView.home_news_title.setPadding(0, 0, 0, convertToDp(mContext, 8f)) holder.itemView.blurView - .setOverlayColor(ColorUtils.setAlphaComponent(accentColor, 150)) + .setOverlayColor(ColorUtils.setAlphaComponent(accentColor, 150)) } } } @@ -239,13 +239,13 @@ class HomeAdapter(private var cells: ArrayList) : /** Sets up blur view on news card */ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { holder.itemView.blurView.setupWith(holder.itemView.news_card_container) - .setFrameClearDrawable(ColorDrawable(getColor(mContext, R.color.white))) - .setBlurAlgorithm(RenderScriptBlur(mContext)) - .setBlurRadius(25f) - .setHasFixedTransformationMatrix(true) + .setFrameClearDrawable(ColorDrawable(getColor(mContext, R.color.white))) + .setBlurAlgorithm(RenderScriptBlur(mContext)) + .setBlurRadius(25f) + .setHasFixedTransformationMatrix(true) } else { holder.itemView.blurView.setBackgroundColor(ColorUtils - .setAlphaComponent(getColor(mContext, R.color.black), 225)) + .setAlphaComponent(getColor(mContext, R.color.black), 225)) } holder.itemView.button.setOnClickListener { @@ -258,17 +258,17 @@ class HomeAdapter(private var cells: ArrayList) : share?.type = "text/plain" builder?.setToolbarColor(0x3E50B4) builder?.setStartAnimations( - mContext, - R.anim.abc_popup_enter, - R.anim.abc_popup_exit) + mContext, + R.anim.abc_popup_enter, + R.anim.abc_popup_exit) CustomTabsClient.bindCustomTabsService( - mContext, - NewsFragment.CUSTOM_TAB_PACKAGE_NAME, connection) + mContext, + NewsFragment.CUSTOM_TAB_PACKAGE_NAME, connection) if (mContext.isChromeCustomTabsSupported()) { share?.putExtra(Intent.EXTRA_TEXT, url) builder?.addMenuItem( - "Share", PendingIntent.getActivity( + "Share", PendingIntent.getActivity( mContext, 0, share, PendingIntent.FLAG_CANCEL_CURRENT)) customTabsIntent = builder?.build() @@ -297,7 +297,7 @@ class HomeAdapter(private var cells: ArrayList) : holder.itemView.home_card_subtitle.text = "UNIVERSITY NOTIFICATIONS" holder.itemView.home_card_rv.layoutManager = LinearLayoutManager(mContext, - LinearLayoutManager.VERTICAL, false) + LinearLayoutManager.VERTICAL, false) holder.itemView.home_card_rv.adapter = UniversityEventAdapter(eventList) } @@ -311,10 +311,10 @@ class HomeAdapter(private var cells: ArrayList) : val roomID = cell.info?.roomId ?: 0 holder.itemView.home_card_subtitle.text = "LAUNDRY" holder.itemView.home_card_rv.layoutManager = LinearLayoutManager(mContext, - LinearLayoutManager.VERTICAL, false) + LinearLayoutManager.VERTICAL, false) val params : ConstraintLayout.LayoutParams = - holder.itemView.home_card_rv.layoutParams as ConstraintLayout.LayoutParams + holder.itemView.home_card_rv.layoutParams as ConstraintLayout.LayoutParams params.setMargins(0, 0, 0, 0) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { params.marginStart = 0 @@ -339,7 +339,7 @@ class HomeAdapter(private var cells: ArrayList) : holder.itemView.home_card_subtitle.text = "GROUP STUDY ROOMS" holder.itemView.home_card_rv.layoutManager = LinearLayoutManager(mContext, - LinearLayoutManager.VERTICAL, false) + LinearLayoutManager.VERTICAL, false) holder.itemView.home_card_rv.adapter = HomeGsrBuildingAdapter(ArrayList(buildings)) } @@ -362,15 +362,15 @@ class HomeAdapter(private var cells: ArrayList) : share?.type = "text/plain" builder?.setToolbarColor(0x3E50B4) builder?.setStartAnimations(mContext, - R.anim.abc_popup_enter, - R.anim.abc_popup_exit) + R.anim.abc_popup_enter, + R.anim.abc_popup_exit) CustomTabsClient.bindCustomTabsService(mContext, - NewsFragment.CUSTOM_TAB_PACKAGE_NAME, connection) + NewsFragment.CUSTOM_TAB_PACKAGE_NAME, connection) if (mContext.isChromeCustomTabsSupported()) { share?.putExtra(Intent.EXTRA_TEXT, url) builder?.addMenuItem("Share", PendingIntent.getActivity(mContext, 0, - share, PendingIntent.FLAG_CANCEL_CURRENT)) + share, PendingIntent.FLAG_CANCEL_CURRENT)) customTabsIntent = builder?.build() customTabsIntent?.launchUrl(mActivity, Uri.parse(url)) } else { @@ -393,10 +393,10 @@ class HomeAdapter(private var cells: ArrayList) : // For now, we only use Feature cards for Spring Fling so we show the Fling Fragment holder.itemView.home_post_card.setOnClickListener { - mActivity.fragmentTransact(FlingFragment()) + mActivity.fragmentTransact(FlingFragment(), false) } } - // Chrome custom tabs to launch news site + // Chrome custom tabs to launch news site internal inner class NewsCustomTabsServiceConnection : CustomTabsServiceConnection() { diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/HomeGsrBuildingAdapter.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/HomeGsrBuildingAdapter.kt index e7420752..d57ad6c8 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/HomeGsrBuildingAdapter.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/adapters/HomeGsrBuildingAdapter.kt @@ -9,6 +9,7 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.fragment.app.FragmentManager import com.pennapps.labs.pennmobile.GsrTabbedFragment import com.pennapps.labs.pennmobile.R import com.pennapps.labs.pennmobile.classes.CalendarEvent @@ -41,7 +42,7 @@ class HomeGsrBuildingAdapter(private var buildings: ArrayList) holder.itemView.home_gsr_building_iv.setImageResource(R.drawable.weigle) } holder.itemView.setOnClickListener { - fragmentTransact(GsrTabbedFragment()) + fragmentTransact(GsrTabbedFragment(), false) } } @@ -54,12 +55,15 @@ class HomeGsrBuildingAdapter(private var buildings: ArrayList) val view = itemView } - private fun fragmentTransact(fragment: Fragment?) { + private fun fragmentTransact(fragment: Fragment?, popBackStack: Boolean) { if (fragment != null) { if (mContext is FragmentActivity) { try { val activity = mContext as FragmentActivity val fragmentManager = activity.supportFragmentManager + if (popBackStack) { + fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) + } fragmentManager.beginTransaction() .replace(R.id.content_frame, fragment) .addToBackStack("Main Activity") diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/CampusExpress.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/CampusExpress.kt new file mode 100644 index 00000000..a0e2bb5c --- /dev/null +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/CampusExpress.kt @@ -0,0 +1,43 @@ +package com.pennapps.labs.pennmobile.api + +import com.pennapps.labs.pennmobile.classes.AccessTokenResponse +import com.pennapps.labs.pennmobile.classes.CampusExpressAccessTokenResponse +import com.pennapps.labs.pennmobile.classes.DiningBalances +import com.pennapps.labs.pennmobile.classes.GetUserResponse +import retrofit.Callback +import retrofit.http.Field +import retrofit.http.FormUrlEncoded +import retrofit.http.Header +import retrofit.http.GET +import retrofit.http.POST +import retrofit.http.Query +import rx.Observable + +/** + * Created by Julius Snipes on 09/23/2022. + * Retrofit interface to the Campus Express API + */ +interface CampusExpress { + + @GET("/oauth/token") + fun getAccessToken( + @Query("code") authCode: String?, + @Query("grant_type") grantType: String?, + @Query("client_id") clientID: String?, + @Query("redirect_uri") redirectURI: String?, + @Query("code_verifier") codeVerifier: String?, + callback: Callback + ) + + @GET("/dining/currentBalance") + fun getCurrentDiningBalances( + @Header("x-authorization") bearerToken: String?) : Observable + + @FormUrlEncoded + @POST("/accounts/introspect/") + fun getUser( + @Header("Authorization") authorizationHeader: String?, + @Field("token") token: String?, + callback: Callback? + ) +} \ No newline at end of file diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/CampusExpressNetworkManager.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/CampusExpressNetworkManager.kt new file mode 100644 index 00000000..f78849e0 --- /dev/null +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/CampusExpressNetworkManager.kt @@ -0,0 +1,30 @@ +package com.pennapps.labs.pennmobile.api + +import android.preference.PreferenceManager +import com.pennapps.labs.pennmobile.MainActivity +import com.pennapps.labs.pennmobile.R +import java.util.* + +class CampusExpressNetworkManager(private var mActivity: MainActivity) { + + private val sp = PreferenceManager.getDefaultSharedPreferences(mActivity) + val editor = sp?.edit() + + + fun getAccessToken() : String? { + val expiresIn = sp.getLong(mActivity.getString(R.string.campus_token_expires_in), 0L) + if (expiresIn != 0L) { + val calendar = Calendar.getInstance() + val expiresAt = Date() + expiresAt.time = expiresIn + calendar.time = Date() + + if (calendar.time >= expiresAt) { // if it has expired, refresh access token + return "" + } + return sp.getString(mActivity.getString(R.string.campus_express_token), "") + } else { + return "" + } + } +} \ No newline at end of file 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 1e942b3e..30460f0b 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,6 +14,7 @@ public interface Platform { String platformBaseUrl = "https://platform.pennlabs.org"; + String campusExpressBaseUrl = "https://prod.campusexpress.upenn.edu/api/v1"; @FormUrlEncoded @POST("/accounts/token/") diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/Serializer.java b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/Serializer.java index d5f3cdcc..7aa6df72 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/Serializer.java +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/api/Serializer.java @@ -64,16 +64,16 @@ private static Venue parseVenue(JsonObject jsonVenue) { Venue venue = new Venue(); venue.setId(jsonVenue.get("id").getAsInt()); venue.setName(jsonVenue.get("name").getAsString()); - venue.setVenueType(jsonVenue.get("venueType").getAsString()); + // venue.setVenueType(jsonVenue.get("venueType").getAsString()); List venueIntervals = new ArrayList<>(); - JsonArray jsonVenueIntervals = jsonVenue.get("dateHours").getAsJsonArray(); + JsonArray jsonVenueIntervals = jsonVenue.get("days").getAsJsonArray(); for (int i = 0; i < jsonVenueIntervals.size(); i++) { VenueInterval venueInterval = new VenueInterval(); List mealIntervals = new ArrayList(); JsonObject jsonVenueInterval = jsonVenueIntervals.get(i).getAsJsonObject(); - JsonElement jsonMealIntervalsElement = jsonVenueInterval.get("meal"); - JsonArray jsonMealIntervals = null; + JsonElement jsonMealIntervalsElement = jsonVenueInterval.get("dayparts"); venueInterval.setDate(jsonVenueInterval.get("date").getAsString()); + JsonArray jsonMealIntervals = null; try { jsonMealIntervals = jsonMealIntervalsElement.getAsJsonArray(); } catch (Exception e) { @@ -83,9 +83,9 @@ private static Venue parseVenue(JsonObject jsonVenue) { for (int j = 0; j < jsonMealIntervals.size(); j++) { VenueInterval.MealInterval mealInterval = new VenueInterval.MealInterval(); JsonObject jsonMeal = jsonMealIntervals.get(j).getAsJsonObject(); - mealInterval.setClose(jsonMeal.get("close").getAsString()); - mealInterval.setType(jsonMeal.get("type").getAsString()); - mealInterval.setOpen(jsonMeal.get("open").getAsString()); + mealInterval.setClose(jsonMeal.get("endtime").getAsString().substring(11)); + mealInterval.setType(jsonMeal.get("label").getAsString()); + mealInterval.setOpen(jsonMeal.get("starttime").getAsString().substring(11)); mealIntervals.add(mealInterval); } venueInterval.setMeals(mealIntervals); @@ -99,9 +99,7 @@ public static class VenueSerializer implements JsonDeserializer> { public List deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException { List res = new LinkedList<>(); - JsonArray venueArray = je - .getAsJsonObject().get("document") - .getAsJsonObject().get("venue").getAsJsonArray(); + JsonArray venueArray = je.getAsJsonArray(); for (int i = 0; i < venueArray.size(); i++) { JsonObject venue = venueArray.get(i).getAsJsonObject(); res.add(parseVenue(venue)); diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/CampusExpressAccessTokenResponse.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/CampusExpressAccessTokenResponse.kt new file mode 100644 index 00000000..dc32a138 --- /dev/null +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/CampusExpressAccessTokenResponse.kt @@ -0,0 +1,13 @@ +package com.pennapps.labs.pennmobile.classes + +import com.google.gson.annotations.Expose +import com.google.gson.annotations.SerializedName + +class CampusExpressAccessTokenResponse { + @SerializedName("expires_in") + @Expose + var expiresIn: Long? = null + @SerializedName("access_token") + @Expose + var accessToken: String? = null +} \ No newline at end of file diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/DiningBalances.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/DiningBalances.kt new file mode 100644 index 00000000..2fa21670 --- /dev/null +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/DiningBalances.kt @@ -0,0 +1,16 @@ +package com.pennapps.labs.pennmobile.classes + +import com.google.gson.annotations.Expose +import com.google.gson.annotations.SerializedName + +open class DiningBalances { + @SerializedName("dining_dollars") + @Expose + var diningDollars: String? = null + @SerializedName("regular_visits") + @Expose + var regularVisits: Int? = null + @SerializedName("guest_visits") + @Expose + var guestVisits: Int? = null +} \ No newline at end of file diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/DiningInsightCell.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/DiningInsightCell.kt new file mode 100644 index 00000000..b2cc7e24 --- /dev/null +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/DiningInsightCell.kt @@ -0,0 +1,12 @@ +package com.pennapps.labs.pennmobile.classes + +import com.google.gson.annotations.Expose +import com.google.gson.annotations.SerializedName + +open class DiningInsightCell { + @SerializedName("type") + @Expose + var type: String? = null + + var diningBalances : DiningBalances? = null +} \ No newline at end of file diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/DollarsSpentCell.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/DollarsSpentCell.kt new file mode 100644 index 00000000..fa82fd12 --- /dev/null +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/classes/DollarsSpentCell.kt @@ -0,0 +1,8 @@ +package com.pennapps.labs.pennmobile.classes + +import com.google.gson.annotations.Expose +import com.google.gson.annotations.SerializedName + +class DollarsSpentCell : DiningInsightCell() { + +} \ No newline at end of file diff --git a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/PreferenceFragment.kt b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/PreferenceFragment.kt index 95f3dc32..ef8a9c81 100644 --- a/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/PreferenceFragment.kt +++ b/PennMobile/src/main/java/com/pennapps/labs/pennmobile/more/PreferenceFragment.kt @@ -93,19 +93,19 @@ class PreferenceFragment : PreferenceFragmentCompat() { val newsFeaturePref: Preference? = findPreference("pref_news_feature") newsFeaturePref?.setOnPreferenceClickListener { - mActivity.fragmentTransact(NewsFragment()) + mActivity.fragmentTransact(NewsFragment(), false) return@setOnPreferenceClickListener true } val contactsFeaturePref: Preference? = findPreference("pref_contacts_feature") contactsFeaturePref?.setOnPreferenceClickListener { - mActivity.fragmentTransact(SupportFragment()) + mActivity.fragmentTransact(SupportFragment(), false) return@setOnPreferenceClickListener true } val aboutFeaturePref: Preference? = findPreference("pref_about_feature") aboutFeaturePref?.setOnPreferenceClickListener { - mActivity.fragmentTransact(AboutFragment()) + mActivity.fragmentTransact(AboutFragment(), false) return@setOnPreferenceClickListener true } diff --git a/PennMobile/src/main/res/drawable/ali.jpg b/PennMobile/src/main/res/drawable/ali.jpg new file mode 100644 index 00000000..267e3ee4 Binary files /dev/null and b/PennMobile/src/main/res/drawable/ali.jpg differ diff --git a/PennMobile/src/main/res/drawable/dining_insights_circle.xml b/PennMobile/src/main/res/drawable/dining_insights_circle.xml new file mode 100644 index 00000000..9f719ede --- /dev/null +++ b/PennMobile/src/main/res/drawable/dining_insights_circle.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/PennMobile/src/main/res/drawable/dining_insights_rectangle.xml b/PennMobile/src/main/res/drawable/dining_insights_rectangle.xml new file mode 100644 index 00000000..8539d5ff --- /dev/null +++ b/PennMobile/src/main/res/drawable/dining_insights_rectangle.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PennMobile/src/main/res/drawable/ic_baseline_people_alt_24.xml b/PennMobile/src/main/res/drawable/ic_baseline_people_alt_24.xml new file mode 100644 index 00000000..58169a9a --- /dev/null +++ b/PennMobile/src/main/res/drawable/ic_baseline_people_alt_24.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/PennMobile/src/main/res/drawable/jenny.jpg b/PennMobile/src/main/res/drawable/jenny.jpg new file mode 100644 index 00000000..89ddf92d Binary files /dev/null and b/PennMobile/src/main/res/drawable/jenny.jpg differ diff --git a/PennMobile/src/main/res/drawable/sruthi.jpg b/PennMobile/src/main/res/drawable/sruthi.jpg new file mode 100644 index 00000000..9e93ecbe Binary files /dev/null and b/PennMobile/src/main/res/drawable/sruthi.jpg differ diff --git a/PennMobile/src/main/res/layout/dining_balances_card.xml b/PennMobile/src/main/res/layout/dining_balances_card.xml new file mode 100644 index 00000000..5556199b --- /dev/null +++ b/PennMobile/src/main/res/layout/dining_balances_card.xml @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PennMobile/src/main/res/layout/dining_spent_card.xml b/PennMobile/src/main/res/layout/dining_spent_card.xml new file mode 100644 index 00000000..334a6617 --- /dev/null +++ b/PennMobile/src/main/res/layout/dining_spent_card.xml @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PennMobile/src/main/res/layout/fragment_campus_express_login.xml b/PennMobile/src/main/res/layout/fragment_campus_express_login.xml new file mode 100644 index 00000000..89e72b72 --- /dev/null +++ b/PennMobile/src/main/res/layout/fragment_campus_express_login.xml @@ -0,0 +1,58 @@ + + + + + + + +