diff --git a/.idea/assetWizardSettings.xml b/.idea/assetWizardSettings.xml
new file mode 100644
index 0000000..4e398ed
--- /dev/null
+++ b/.idea/assetWizardSettings.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/dictionaries/eric.xml b/.idea/dictionaries/eric.xml
new file mode 100644
index 0000000..e207c24
--- /dev/null
+++ b/.idea/dictionaries/eric.xml
@@ -0,0 +1,7 @@
+
+
+
+ acmi
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 51fa3e5..d04819c 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -5,7 +5,7 @@
diff --git a/README.md b/README.md
index 7ccf70a..c876f22 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,23 @@ View when the room is reserved:
+# Companion Mode
+-BetterVent now supports a Companion Mode; rather than just viewing the app as a kiosk, users can download the application and configure it to show locations throughout their calendar.
+
+
+
+-Choose from automatically discovered locations which ones to track
+
+
+
+
+
+-Get detailed information on a room's status with a simple click
+
+
+
+
+
# How do I get it?
Currently, BetterVent is not on the Play Store, but you can download the .apk file in the releases tab.
(I'll try to keep it up to date)
@@ -43,7 +60,7 @@ Currently, BetterVent is not on the Play Store, but you can download the .apk fi
- Better parsing of event keywords
- Set keywords that usually pertain to a location
- Colors
-
+
## Device Admin
To set the app as device admin (You need to do this before kiosk features work (Thanks, Google)) Connect to a computer and in the terminal (after installing adb) do this *BEFORE SETTING UP A GOOGLE ACCOUNT*:
diff --git a/app/build.gradle b/app/build.gradle
index e55aba0..6d2f5e1 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,16 +1,17 @@
apply plugin: 'com.android.application'
-apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: 'kotlin-kapt'
android {
- compileSdkVersion 28
+ compileSdkVersion 29
defaultConfig {
applicationId "edu.rit.csh.bettervent"
- minSdkVersion 23
- targetSdkVersion 28
+ minSdkVersion 26
+ targetSdkVersion 29
versionCode 1
versionName "1.0"
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
multiDexEnabled true
}
@@ -22,27 +23,34 @@ android {
}
}
+
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation 'com.android.support:appcompat-v7:28.0.0'
- implementation 'com.android.support:design:28.0.0'
- implementation 'com.android.support:support-vector-drawable:28.0.0'
+ implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
+ implementation 'androidx.recyclerview:recyclerview:1.0.0'
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'com.google.android.material:material:1.0.0'
+ implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
implementation 'org.jetbrains.anko:anko-commons:0.10.8'
implementation 'org.jetbrains.anko:anko-design:0.10.8'
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.12'
- androidTestImplementation 'com.android.support.test:runner:1.0.2'
- androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
- implementation 'com.android.support.constraint:constraint-layout:1.1.3'
+ androidTestImplementation 'androidx.test:runner:1.2.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
// Google Calendar API dependencies
- implementation 'com.google.android.gms:play-services:12.0.1'
- api 'com.google.apis:google-api-services-calendar:v3-rev119-1.19.1'
- api 'com.google.api-client:google-api-client:1.23.0'
- api 'com.google.api-client:google-api-client-android:1.23.0'
- api 'com.google.api-client:google-api-client-gson:1.19.1'
-
+ implementation 'com.google.api-client:google-api-client:1.23.0'
+ implementation 'com.google.oauth-client:google-oauth-client-jetty:1.23.0'
+ implementation 'com.google.apis:google-api-services-calendar:v3-rev305-1.23.0'
+ implementation 'com.google.android.gms:play-services-base:17.1.0'
implementation 'com.github.thellmund:Android-Week-View:3.1.3'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'com.google.android.gms:play-services-auth:17.0.0'
+ implementation 'com.google.api-client:google-api-client:1.23.0'
+ implementation 'com.google.api-client:google-api-client-android:1.23.0'
+ implementation 'com.google.apis:google-api-services-people:v1-rev4-1.22.0'
+ implementation 'com.google.http-client:google-http-client-gson:1.19.0'
}
configurations {
@@ -52,4 +60,7 @@ configurations {
}
repositories {
mavenCentral()
-}
\ No newline at end of file
+}
+androidExtensions {
+ experimental = true
+}
diff --git a/app/src/androidTest/java/edu/rit/csh/bettervent/ExampleInstrumentedTest.java b/app/src/androidTest/java/edu/rit/csh/bettervent/ExampleInstrumentedTest.java
index 13a8454..dbada70 100644
--- a/app/src/androidTest/java/edu/rit/csh/bettervent/ExampleInstrumentedTest.java
+++ b/app/src/androidTest/java/edu/rit/csh/bettervent/ExampleInstrumentedTest.java
@@ -1,8 +1,9 @@
package edu.rit.csh.bettervent;
import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4fb2e6f..e71dcbb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,13 +2,14 @@
+
-
-
@@ -20,23 +21,34 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Design.Light.NoActionBar">
-
+
+
+
-
+
+
-
+
diff --git a/app/src/main/java/edu/rit/csh/bettervent/ApiAsyncTask.kt b/app/src/main/java/edu/rit/csh/bettervent/ApiAsyncTask.kt
deleted file mode 100644
index b600073..0000000
--- a/app/src/main/java/edu/rit/csh/bettervent/ApiAsyncTask.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-package edu.rit.csh.bettervent
-
-import android.content.Context
-import android.content.SharedPreferences
-import android.os.AsyncTask
-import com.google.api.client.googleapis.extensions.android.gms.auth.GooglePlayServicesAvailabilityIOException
-import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
-import com.google.api.client.util.DateTime
-
-import com.google.api.services.calendar.model.*
-
-import java.io.IOException
-
-/**
- * An asynchronous task that handles the Google Calendar API call.
- * Placing the API calls in their own task ensures the UI stays responsive.
- */
-
-/**
- * Created by miguel on 5/29/15.
- */
-
-class ApiAsyncTask
-/**
- * Constructor.
- * @param activity MainActivity that spawned this task.
- */
-internal constructor(private val mainActivity: MainActivity) : AsyncTask() {
-
- private val appSettings: SharedPreferences? = null
-
- /**
- * Background task to call Google Calendar API.
- * @param params no parameters needed for this task.
- */
- override fun doInBackground(vararg params: Void): Void? {
- try {
- mainActivity.clearResultsText()
- mainActivity.updateResultsText(getDataFromApi(mainActivity.calendarID!!, mainActivity.maxResults))
-
- } catch (availabilityException: GooglePlayServicesAvailabilityIOException) {
- // mainActivity.showGooglePlayServicesAvailabilityErrorDialog(
- // availabilityException.getConnectionStatusCode()); //TODO: Display error when unable to fetch events.
- System.err.println("Error connecting to Google Play Services. Error code: " + availabilityException.connectionStatusCode)
-
-
- } catch (userRecoverableException: UserRecoverableAuthIOException) {
- mainActivity.startActivityForResult(
- userRecoverableException.intent,
- MainActivity.REQUEST_AUTHORIZATION)
-
- } catch (e: IOException) {
- mainActivity.updateStatus("The following error occurred: " + e.message)
- }
-
- return null
- }
-
- /**
- * Fetch a list of the next 10 events from the primary calendar.
- * @return List of Strings describing returned events.
- * @throws IOException
- */
- @Throws(IOException::class)
- private fun getDataFromApi(calendarID: String, maxResults: Int): List {
- // Load up app settings to fetch passwords and background colors.
- // System.out.println("*** Attempting to get data from API. ***");
- // List the next 10 events from the primary calendar.
- val now = DateTime(System.currentTimeMillis())
- val events = mainActivity.mService.events().list(calendarID)
- .setMaxResults(maxResults)
- .setTimeMin(now)
- .setOrderBy("startTime")
- .setSingleEvents(true)
- .execute()
-// println("*** items: " + events)
- return events.items
- }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/edu/rit/csh/bettervent/MainActivity.kt b/app/src/main/java/edu/rit/csh/bettervent/MainActivity.kt
deleted file mode 100644
index 1616bfc..0000000
--- a/app/src/main/java/edu/rit/csh/bettervent/MainActivity.kt
+++ /dev/null
@@ -1,447 +0,0 @@
-package edu.rit.csh.bettervent
-
-import android.accounts.Account
-import android.app.admin.DevicePolicyManager
-import android.content.ComponentName
-import android.support.design.widget.BottomNavigationView
-import android.support.design.widget.FloatingActionButton
-import android.support.v4.app.Fragment
-import android.support.v7.app.AppCompatActivity
-import android.os.Bundle
-import android.view.MenuItem
-
-import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential
-
-import android.accounts.AccountManager
-import android.app.Activity
-import android.app.Dialog
-import android.content.Context
-import android.content.Intent
-import android.content.SharedPreferences
-import android.net.ConnectivityManager
-import android.net.NetworkInfo
-import android.os.Handler
-import android.view.View
-import android.widget.TextClock
-import android.widget.Toast
-
-import com.google.android.gms.common.ConnectionResult
-import com.google.android.gms.common.GooglePlayServicesUtil
-import com.google.api.client.extensions.android.http.AndroidHttp
-import com.google.api.client.http.HttpTransport
-import com.google.api.client.json.JsonFactory
-import com.google.api.client.json.gson.GsonFactory
-import com.google.api.client.util.DateTime
-import com.google.api.client.util.ExponentialBackOff
-import com.google.api.services.calendar.CalendarScopes
-import com.google.api.services.calendar.model.*
-
-import java.lang.reflect.Array
-import java.util.ArrayList
-import java.util.Arrays
-
-class MainActivity : AppCompatActivity() {
-
- internal lateinit var mService: com.google.api.services.calendar.Calendar
-
- internal lateinit var credential: GoogleAccountCredential
- // This MainActivity gets the data from the API, and holds it
- // as a list. The Fragments then update themselves using that.
- private val APIOutList = ArrayList()
- internal val transport = AndroidHttp.newCompatibleTransport()
- internal val jsonFactory: JsonFactory = GsonFactory.getDefaultInstance()
- var calendarID: String? = null
- var maxResults: Int = 0
-
- private var mAppSettings: SharedPreferences? = null
-
- private lateinit var APIStatusMessage: String
- var isReserved = true
- private lateinit var bottomNav: BottomNavigationView
- private lateinit var refreshButton: FloatingActionButton
-
- private val navListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
- selectedFragment = null
- when (item.itemId) {
- R.id.navigation_status ->
- // selectedFragment = new StatusFragment();
- selectedFragment = StatusFragment.newInstance(APIOutList)
- R.id.navigation_schedule -> selectedFragment = ScheduleFragment.newInstance(APIOutList)
- R.id.navigation_quick_mode -> selectedFragment = QuickModeFragment()
- }
-
- // System.out.println("*** currentEventTitle: " + currentEventTitle);
- supportFragmentManager.beginTransaction().replace(R.id.fragment_container,
- selectedFragment!!).commit()
-
- true
- }
-
- /**
- * Checks whether the device currently has a network connection.
- * @return true if the device has a network connection, false otherwise.
- */
- private val isDeviceOnline: Boolean
- get() {
- val connMgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
- val networkInfo = connMgr.activeNetworkInfo
- return networkInfo != null && networkInfo.isConnected
- }
-
- /**
- * Check that Google Play services APK is installed and up to date. Will
- * launch an error dialog for the user to update Google Play Services if
- * possible.
- * @return true if Google Play Services is available and up to
- * date on this device; false otherwise.
- */
- private val isGooglePlayServicesAvailable: Boolean
- get() {
- val connectionStatusCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this)
- if (GooglePlayServicesUtil.isUserRecoverableError(connectionStatusCode)) {
- showGooglePlayServicesAvailabilityErrorDialog(connectionStatusCode)
- return false
- } else if (connectionStatusCode != ConnectionResult.SUCCESS) {
- return false
- }
- return true
- }
-
-
- /**
- * Checks the times of the first event in APIOutList (the List of Events generated by the API)
- * and if the current time is within those times, then the room is booked
- * and if the current time is not within those times, the room is free.
- * @return: true if the current time is outside of the time of the
- * next event, and false if vice-versa.
- */
- private// Then the room is currently in use.
- // If something weird happens, just assume the room is free.
- val isFree: Boolean
- get() {
- try {
- val now = DateTime(System.currentTimeMillis())
- val firstEventStart = APIOutList[0].start.dateTime
- val firstEventEnd = APIOutList[0].end.dateTime
- if (now.value > firstEventStart.value && now.value < firstEventEnd.value) {
- isReserved = true
- return false
- } else {
- isReserved = false
- return true
- }
- } catch (e: Exception) {
- isReserved = false
- return true
- }
-
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
-
-
- // Load up app settings to fetch passwords and background colors.
- mAppSettings = getSharedPreferences(
- getString(R.string.preference_file_key), Context.MODE_PRIVATE)
-
- // Must restart for these preferences to take hold.
- calendarID = mAppSettings!!.getString("edu.rit.csh.bettervent.calendarid", "")
- val maxResultsStr = mAppSettings!!.getString("edu.rit.csh.bettervent.maxresults", "")
- if (maxResultsStr !== "" && maxResultsStr != null)
- maxResults = Integer.parseInt(maxResultsStr)
- else {
- infoPrint("Max Results not set. Defaulting to 100.")
- maxResults = 100
- }
-
-
- //Following code allow the app packages to lock task in true kiosk mode
- // get policy manager
- val myDevicePolicyManager = getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
- // get this app package name
- val mDPM = ComponentName(this, AdminReceiver::class.java)
-
- if (myDevicePolicyManager.isDeviceOwnerApp(this.packageName)) {
- // get this app package name
- val packages = arrayOf(this.packageName)
- // mDPM is the admin package, and allow the specified packages to lock task
- myDevicePolicyManager.setLockTaskPackages(mDPM, packages)
- } else {
- Toast.makeText(applicationContext, "Not owner", Toast.LENGTH_LONG).show()
- }
-
- startLockTask()
-
-
- bottomNav = findViewById(R.id.bottom_navigation)
- bottomNav.setOnNavigationItemSelectedListener(navListener)
-
- refreshButton = findViewById(R.id.refresh_button)
-
- refreshButton.setOnClickListener {
- // TODO: figure out why you have to do this twice to make anything happen.
- refreshResults()
- refreshUI()
- }
-
- // Initialize credentials and service object.
- val settings = getPreferences(Context.MODE_PRIVATE)
- credential = GoogleAccountCredential.usingOAuth2(
- applicationContext, Arrays.asList(*SCOPES))
- .setBackOff(ExponentialBackOff())
- .setSelectedAccountName(settings.getString(PREF_ACCOUNT_NAME, null))
-
- mService = com.google.api.services.calendar.Calendar.Builder(
- transport, jsonFactory, credential)
- .setApplicationName("Google Calendar API Android Quickstart")
- .build()
-
- refreshResults()
- if (selectedFragment == null) {
- selectedFragment = StatusFragment.newInstance(APIOutList)
- supportFragmentManager.beginTransaction().replace(R.id.fragment_container,
- selectedFragment!!).commit()
- }
-
- centralClock = findViewById(R.id.central_clock)
-
- // Initialize API Refresher. Make sure to sign into a google account before launching the app.
- val handler = Handler()
- val runnable = object : Runnable {
- override fun run() {
- if (credential.selectedAccountName != null) {
- refreshResults()
- println(" *** Refreshed.")
- refreshUI()
- handler.postDelayed(this, 10000)
- }
- }
- }
-
- //Start API Refresher
- handler.postDelayed(runnable, 1000)
-
- }
-
- /**
- * Called whenever this activity is pushed to the foreground, such as after
- * a call to onCreate().
- */
- override fun onResume() {
- super.onResume()
- if (isGooglePlayServicesAvailable) {
- refreshResults()
- } else {
- APIStatusMessage = "Google Play Services required: " + "after installing, close and relaunch this app."
- }
- }
-
- /**
- * Called when an activity launched here (specifically, AccountPicker
- * and authorization) exits, giving you the requestCode you started it with,
- * the resultCode it returned, and any additional data from it.
- * @param requestCode code indicating which activity result is incoming.
- * @param resultCode code indicating the result of the incoming
- * activity result.
- * @param data Intent (containing result data) returned by incoming
- * activity result.
- */
- override fun onActivityResult(
- requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- infoPrint("API Request code returned: $requestCode")
- when (requestCode) {
- REQUEST_GOOGLE_PLAY_SERVICES -> if (resultCode == Activity.RESULT_OK) {
- refreshResults()
- } else {
- isGooglePlayServicesAvailable
- }
- REQUEST_ACCOUNT_PICKER -> {
- infoPrint("Pick your account.")
- if (resultCode == Activity.RESULT_OK && data != null &&
- data.extras != null) {
- infoPrint("Result = $resultCode")
- val accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)
- infoPrint("Account name = " + accountName!!)
- if (accountName != null) {
- // credential.setSelectedAccountName(accountName);
- credential.selectedAccount = Account(accountName, "edu.rit.csh.bettervent")
- infoPrint("Account name set. Account name = $accountName")
- infoPrint(credential.selectedAccountName)
- val settings = getPreferences(Context.MODE_PRIVATE)
- val editor = settings.edit()
- editor.putString(PREF_ACCOUNT_NAME, accountName)
- editor.commit()
- refreshResults()
- }
- } else if (resultCode == Activity.RESULT_CANCELED) {
- infoPrint("Account Unspecified")
- APIStatusMessage = "Account unspecified."
- }
- }
- REQUEST_AUTHORIZATION -> if (resultCode == Activity.RESULT_OK) {
- if (credential.selectedAccountName.length < 1)
- refreshResults()
- } else {
- chooseAccount()
- }
- }
-
- super.onActivityResult(requestCode, resultCode, data)
- }
-
- /**
- * Attempt to get a set of data from the Google Calendar API to display. If the
- * email address isn't known yet, then call chooseAccount() method so the
- * user can pick an account.
- */
- private fun refreshResults() {
- println("*** Refreshing results... ***")
- if (credential.selectedAccountName == null) {
- infoPrint("No account selected.")
- chooseAccount()
- } else {
- if (isDeviceOnline) {
- println("*** Executing APIAsyncTask. ***")
- ApiAsyncTask(this).execute()
- // TODO: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState ???
- } else {
- println("*** Can't refresh calendar. ***")
- APIStatusMessage = "No network connection available."
- }
- }
- }
-
- fun refreshUI() {
- try {
- when (selectedFragment!!.javaClass){
- StatusFragment::class.java -> {
- selectedFragment = StatusFragment.newInstance(APIOutList)
- supportFragmentManager.beginTransaction().replace(R.id.fragment_container,
- selectedFragment!!).commit()
- println(" *** Refreshed Status UI")
- }
- ScheduleFragment::class.java -> {
- selectedFragment = ScheduleFragment.newInstance(APIOutList)
- supportFragmentManager.beginTransaction().replace(R.id.fragment_container,
- selectedFragment!!).commit()
- println(" *** Refreshed Schedule UI")
- }
- else -> println(" *** UI is not status.")
- }
- } catch (e: Exception) {
- System.err.println("Caught Exception\n$e")
- }
-
- }
-
- /**
- * Clear any existing Google Calendar API data from the TextView and update
- * the header message; called from background threads and async tasks
- * that need to update the UI (in the UI thread).
- */
- fun clearResultsText() {
- runOnUiThread {
- APIStatusMessage = "Retrieving data…"
- APIStatusMessage = ""
- }
- }
-
- /**
- * Fill the data TextView with the given List of Strings; called from
- * background threads and async tasks that need to update the UI (in the
- * UI thread).
- * @param dataEvents a List of Strings to populate the main TextView with.
- */
- fun updateResultsText(dataEvents: List?) {
- runOnUiThread {
- if (dataEvents == null) {
- APIStatusMessage = "Error retrieving data!"
- } else if (dataEvents.size == 0) {
- APIStatusMessage = "No data found."
- println("*** No data found. ***")
- APIOutList.removeAll(APIOutList)
- } else {
- APIStatusMessage = "API Call Complete."
- infoPrint("*** Events found. *** $dataEvents")
- val eventKeyword = mAppSettings!!.getString("edu.rit.csh.bettervent.filterkeywords", "")
- APIOutList.removeAll(APIOutList)
- var eventFieldToCheck: String?
- for (event in dataEvents) {
- if (mAppSettings!!.getBoolean("edu.rit.csh.bettervent.filterbytitle", false)) {
- eventFieldToCheck = event.summary
- } else {
- eventFieldToCheck = event.location
- }
- infoPrint(eventFieldToCheck)
- if (eventKeyword!!.length > 0 && eventFieldToCheck != null) {
- if (eventFieldToCheck.toLowerCase().contains(eventKeyword.toLowerCase())) {
- APIOutList.add(event)
- }
- } else if (eventKeyword.length < 1) {
- APIOutList.add(event)
- }
- }
- isFree
- }
- }
- }
-
- /**
- * Show a status message in the list header TextView; called from background
- * threads and async tasks that need to update the UI (in the UI thread).
- * @param message a String to display in the UI header TextView.
- */
- fun updateStatus(message: String) {
- runOnUiThread { APIStatusMessage = message }
- }
-
- /**
- * Starts an activity in Google Play Services so the user can pick an
- * account.
- */
- private fun chooseAccount() {
- startActivityForResult(
- credential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER)
- }
-
- /**
- * Display an error dialog showing that Google Play Services is missing
- * or out of date.
- * @param connectionStatusCode code describing the presence (or lack of)
- * Google Play Services on this device.
- */
- internal fun showGooglePlayServicesAvailabilityErrorDialog(
- connectionStatusCode: Int) {
- runOnUiThread {
- val dialog = GooglePlayServicesUtil.getErrorDialog(
- connectionStatusCode,
- this@MainActivity,
- REQUEST_GOOGLE_PLAY_SERVICES)
- dialog.show()
- }
- }
-
- private fun infoPrint(info: Any?) {
- if (info != null)
- println("MAIN_: " + info!!)
- else println("MAIN_: null!?!?!?")
- }
-
- companion object {
-
- internal val REQUEST_ACCOUNT_PICKER = 1000
- internal val REQUEST_AUTHORIZATION = 1001
- internal val REQUEST_GOOGLE_PLAY_SERVICES = 1002
- private val PREF_ACCOUNT_NAME = "accountName"
- private val SCOPES = arrayOf(CalendarScopes.CALENDAR_READONLY)
-
- lateinit var centralClock: TextClock
-
- //UI Elements visible throughout the app
- var selectedFragment: Fragment? = null
- }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/edu/rit/csh/bettervent/StatusFragment.kt b/app/src/main/java/edu/rit/csh/bettervent/StatusFragment.kt
deleted file mode 100644
index 2114c86..0000000
--- a/app/src/main/java/edu/rit/csh/bettervent/StatusFragment.kt
+++ /dev/null
@@ -1,273 +0,0 @@
-package edu.rit.csh.bettervent
-
-import android.app.AlertDialog
-import android.content.Context
-import android.content.SharedPreferences
-import android.os.Bundle
-import android.support.v4.app.Fragment
-import android.text.InputType
-import android.text.method.PasswordTransformationMethod
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.EditText
-import android.widget.LinearLayout
-import android.widget.RelativeLayout
-
-import com.google.api.client.util.DateTime
-import com.google.api.services.calendar.model.Event
-import kotlinx.android.synthetic.main.fragment_status.*
-import kotlinx.android.synthetic.main.fragment_status.view.*
-import kotlinx.android.synthetic.main.password_alert.*
-import kotlinx.android.synthetic.main.password_alert.view.*
-import org.jetbrains.anko.alert
-import org.jetbrains.anko.noButton
-import org.jetbrains.anko.yesButton
-
-import java.io.Serializable
-
-class StatusFragment : Fragment() {
-
- private lateinit var appSettings: SharedPreferences // Settings object containing user preferences.
-
- var events: ArrayList = ArrayList()
-
- // Variables for storing what the status should read out as
- var currentTitle: String? = null
- lateinit var currentTime: String
- var nextTitle: String? = null
- lateinit var nextTime: String
-
- /**
- * Extract information from the bundle that may have been provided with the StatusFragment,
- * inflate status_layout and set it as the currently active view, then make references to all of
- * the various pieces of the UI so that the class can update the UI with the API data.
- *
- * @param inflater
- * @param container
- * @param savedInstanceState
- * @return
- */
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- infoPrint("Loaded Status Fragment.")
-
- // Load up app settings to fetch passwords and background colors.
- appSettings = context!!.getSharedPreferences(
- getString(R.string.preference_file_key), Context.MODE_PRIVATE)
-
- val args = arguments
- if (args != null) {
- events.addAll(args.getSerializable("events") as List)
- getCurrentAndNextEvents()
-
- } else {
- infoPrint("ERROR! NO DATA FOUND!")
- }
-
- val view = inflater.inflate(R.layout.fragment_status, container, false)
-
- MainActivity.centralClock.setTextColor(-0x1)
-
- fun showAlertWithFunction(onSuccess: () -> Unit){
- context!!.alert("Enter Password:"){
- val v = layoutInflater.inflate(R.layout.password_alert, null)
- customView = v
- fun checkPassword(pw: String){
- if (pw == appSettings!!.getString("edu.rit.csh.bettervent.password", "")) onSuccess()
- }
- yesButton { checkPassword(v.password_et.text.toString()) }
- noButton { dialog -> dialog.cancel() }
- }.show()
- }
-
- view.leave_button.setOnClickListener {
- showAlertWithFunction { System.exit(0) }
- }
-
- view.settings_button.setOnClickListener {
- MainActivity.selectedFragment = SettingsFragment()
-
- showAlertWithFunction { fragmentManager!!.beginTransaction().replace(R.id.fragment_container,
- MainActivity.selectedFragment as SettingsFragment).commit() }
- }
-
- if (currentTitle == null) {
- nextTime = ""
- nextTitle = nextTime
- currentTime = nextTitle as String
- currentTitle = currentTime
- }
- if (nextTitle == null) nextTitle = ""
- return view
- }
-
- /**
- * @param view
- * @param savedInstanceState
- */
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- infoPrint("Fragment Event Title: " + currentTitle!!)
- setRoomStatus()
- }
-
- /**
- *
- */
- private fun setRoomStatus() {
- // Set current status of the room
- if (currentTitle != "") {
- free_label.visibility = View.INVISIBLE
- reserved_label.visibility = View.VISIBLE
- event_title.text = currentTitle
- event_time!!.text = currentTime
- status_layout.setBackgroundColor(resources.getColor(R.color.CSHRed))
- } else {
- next_event_title.textSize = 64F
- next_event_time.textSize = 32F
-
- reserved_label.visibility = View.INVISIBLE
- free_label.visibility = View.VISIBLE
- event_title.text = ""
- event_time.text = ""
- event_title.visibility = View.GONE
- event_time.visibility = View.GONE
- status_layout.setBackgroundColor(resources.getColor(R.color.CSHGreen))
- }
-
- if (currentTitle == "") {
-// val next_label_params = next_label.layoutParams as RelativeLayout.LayoutParams
-// next_label_params.setMargins(0, 128, 0, 0) //substitute parameters for left, top, right, bottom
-// next_event_title.layoutParams = next_label_params
-
- val next_event_title_params = next_event_title.layoutParams as RelativeLayout.LayoutParams
- next_event_title_params.setMargins(0, 72, 0, 0) //substitute parameters for left, top, right, bottom
- next_event_title.layoutParams = next_event_title_params
-
- val separator_params = separator.layoutParams as RelativeLayout.LayoutParams
- separator_params.setMargins(0, 0, 0, 0) //substitute parameters for left, top, right, bottom
- separator.layoutParams = separator_params
-
-
- }
-
- // Set the future status of the room
- if (nextTitle != "") {
- next_label.visibility = View.VISIBLE
- next_event_title.text = nextTitle
- next_event_time.text = nextTime
- } else {
- val next_event_title_params = next_event_title.layoutParams as RelativeLayout.LayoutParams
- next_event_title_params.setMargins(0, 192, 0, 0) //substitute parameters for left, top, right, bottom
- next_event_title.layoutParams = next_event_title_params
-
- next_label.visibility = View.GONE
- next_event_time.visibility = View.GONE
- next_event_title.text = "There are no upcoming events."
- next_event_time.text = ""
- }
- }
-
- /**
- * Looks at the APIOutList (the List of Events generated by the API),
- * and based on how many there are and when they are, sets the string
- * values for currentEventTitle, currentEventTime, nextEventTitle, and
- * nextEventTime.
- */
- private fun getCurrentAndNextEvents() {
- if (events == null)
- infoPrint("There may have been an issue getting the data." + "\nor maybe there was no data.")
-
- if (events == null || events!!.isEmpty()) {
- nextTime = ""
- nextTitle = nextTime
- currentTime = nextTitle as String
- currentTitle = currentTime
- } else {
- //Here's all the data we'll need.
- val summary = events!![0].summary
- var start: DateTime? = events!![0].start.dateTime
- val end = events!![0].end.dateTime
-
- if (start == null) {
- // If the event will last all day then only use the event title.
- start = events!![0].start.date
- currentTitle = summary
- currentTime = "All day"
- } else {
- // If the event has a set start and end time then check if it's now or later.
- val now = DateTime(System.currentTimeMillis())
- if (start.value > now.value) {
- // If the first event will happen in the future
- // Then there is no current event.
- currentTitle = ""
- currentTime = ""
- nextTitle = summary
- nextTime = formatDateTime(start) + " — " + formatDateTime(end)
- } else {
- // Set current event to first event if it's happening right now.
- currentTitle = summary
- currentTime = formatDateTime(start) + " — " + formatDateTime(end)
- if (events!!.size > 1)
- // Get the next event after this one
- getNextEvent()
- }
- }
- }
- }
-
- /**
- * Takes the second index of APIOutList (the List of Events generated by the API)
- * and sets nextEventTitle and nextEventTime.
- */
- private fun getNextEvent() {
- try {
- val nextEventSummary = events!![1].summary
- var nextEventStart: DateTime? = events!![1].start.dateTime
- val nextEventEnd = events!![1].end.dateTime
- if (nextEventStart == null) {
- // All-day events don't have start times, so just use
- // the start date.
- nextEventStart = events!![1].start.date
- }
- nextTitle = nextEventSummary
- nextTime = formatDateTime(nextEventStart!!) + " — " + formatDateTime(nextEventEnd)
- } catch (e: Exception) {
- nextTitle = ""
- nextTime = ""
- }
- }
-
- /**
- * Method to format DateTimes into human-readable strings
- *
- * @param dateTime: DateTime to make readable
- * @return: HH:MM on YYYY/MM/DD
- */
- private fun formatDateTime(dateTime: DateTime): String {
- return if (dateTime.isDateOnly) {
- dateTime.toString()
- } else {
- val t = dateTime.toString().split("T".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
- val time = t[1].substring(0, 5)
- val date = t[0].split("-".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
- val dateString = date[0] + "/" + date[1] + "/" + date[2]
-
- "$time on $dateString"
- }
- }
-
- private fun infoPrint(info: String) {
- println("STAT_: $info")
- }
-
- companion object {
-
- fun newInstance(events: List): StatusFragment {
- val f = StatusFragment()
- val args = Bundle()
- args.putSerializable("events", events as Serializable)
- f.arguments = args
- return f
- }
- }
-}
diff --git a/app/src/main/java/edu/rit/csh/bettervent/view/CalendarInfoAdapter.kt b/app/src/main/java/edu/rit/csh/bettervent/view/CalendarInfoAdapter.kt
new file mode 100644
index 0000000..9eb74e6
--- /dev/null
+++ b/app/src/main/java/edu/rit/csh/bettervent/view/CalendarInfoAdapter.kt
@@ -0,0 +1,27 @@
+package edu.rit.csh.bettervent.view
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import edu.rit.csh.bettervent.R
+import kotlinx.android.synthetic.main.add_location_item.view.*
+
+class CalendarInfoAdapter(val context: Context, private val items: List, val onItemSelected: (CalendarInfo) -> Unit): RecyclerView.Adapter() {
+
+ override fun getItemCount() = items.size
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ holder.itemView.add_location_tv.text = items[position].name
+ holder.itemView.setOnClickListener { onItemSelected.invoke(items[position]) }
+ if (position == itemCount - 1) holder.itemView.divider.visibility = View.GONE
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val inflater = LayoutInflater.from(context)
+ return ViewHolder(inflater.inflate(R.layout.add_location_item, parent, false))
+ }
+
+ inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
+}
\ No newline at end of file
diff --git a/app/src/main/java/edu/rit/csh/bettervent/view/MainActivity.kt b/app/src/main/java/edu/rit/csh/bettervent/view/MainActivity.kt
new file mode 100644
index 0000000..1a88905
--- /dev/null
+++ b/app/src/main/java/edu/rit/csh/bettervent/view/MainActivity.kt
@@ -0,0 +1,276 @@
+package edu.rit.csh.bettervent.view
+
+import android.app.Activity
+import android.content.Context
+import android.content.DialogInterface
+import android.content.Intent
+import android.content.SharedPreferences
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.ViewModelProviders
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.google.android.gms.auth.api.signin.GoogleSignIn
+import com.google.android.gms.auth.api.signin.GoogleSignInAccount
+import com.google.android.gms.auth.api.signin.GoogleSignInClient
+import com.google.android.gms.auth.api.signin.GoogleSignInOptions
+import com.google.android.gms.common.ConnectionResult
+import com.google.android.gms.common.GooglePlayServicesUtil
+import com.google.android.gms.common.api.ApiException
+import com.google.android.gms.common.api.Scope
+import com.google.android.gms.tasks.Task
+import com.google.api.services.calendar.CalendarScopes
+import edu.rit.csh.bettervent.R
+import edu.rit.csh.bettervent.view.companion.CompanionActivity
+import edu.rit.csh.bettervent.view.kiosk.MainActivity
+import edu.rit.csh.bettervent.viewmodel.CompanionActivityViewModel
+import kotlinx.android.synthetic.main.activity_main.*
+import kotlinx.android.synthetic.main.add_location_alert.view.*
+import org.jetbrains.anko.alert
+import org.jetbrains.anko.okButton
+import java.util.*
+
+class MainActivity : AppCompatActivity(){
+ // This MainActivity gets the data from the API, and holds it
+ // as a list. The Fragments then update themselves using that.
+ private lateinit var mAppSettings: SharedPreferences
+ private lateinit var calendar: CalendarInfo
+ lateinit var signInClient: GoogleSignInClient
+ lateinit var model: CompanionActivityViewModel
+
+
+ /**
+ * Check that Google Play services APK is installed and up to date. Will
+ * launch an error dialog for the user to update Google Play Services if
+ * possible.
+ * @return true if Google Play Services is available and up to
+ * date on this device; false otherwise.
+ */
+ private val isGooglePlayServicesAvailable: Boolean
+ get() {
+ val connectionStatusCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this)
+ if (GooglePlayServicesUtil.isUserRecoverableError(connectionStatusCode)) {
+ showGooglePlayServicesAvailabilityErrorDialog(connectionStatusCode)
+ return false
+ } else if (connectionStatusCode != ConnectionResult.SUCCESS) {
+ return false
+ }
+ return true
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ // Load up app settings to fetch passwords and background colors.
+ mAppSettings = applicationContext.getSharedPreferences(
+ getString(R.string.preference_file_key), Context.MODE_PRIVATE)!!
+
+ model = ViewModelProviders.of(this).get(CompanionActivityViewModel::class.java)
+
+ val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
+ .requestScopes(Scope("https://www.googleapis.com/auth/calendar.readonly"))
+ .requestEmail()
+ .build()
+
+ calendar = CalendarInfo(
+ mAppSettings.getString("calendarName", "")!!,
+ mAppSettings.getString("edu.rit.csh.bettervent.calendarid", "")!!
+ )
+
+ calendar_name_tv.text = calendar.name
+
+ signInClient = GoogleSignIn.getClient(this, gso)
+
+ companion_btn.setOnClickListener { startCompanionActivity() }
+ kiosk_btn.setOnClickListener { startEventActivity() }
+ checkForAccount()
+
+ choose_account_btn.setOnClickListener { signOutThenIn() }
+
+ choose_calendar_btn.setOnClickListener { promptChooseCalendar() }
+ }
+
+ private fun startCompanionActivity() {
+ if (calendar.name.isBlank()) {
+ alert{
+ title = "You must select a calendar"
+ okButton { }
+ }.show()
+ return
+ }
+
+ mAppSettings.edit()
+ .putString("edu.rit.csh.bettervent.calendarid", calendar.id)
+ .putString("calendarName", calendar.name)
+ .apply()
+
+ val intent = Intent(this, CompanionActivity::class.java)
+ startActivity(intent)
+ }
+
+ private fun startEventActivity() {
+ if (calendar.name.isBlank()) {
+ alert{
+ title = "You must input a valid calendar ID"
+ okButton { }
+ }.show()
+ return
+ }
+
+ mAppSettings.edit()
+ .putString("edu.rit.csh.bettervent.calendarid", calendar.id)
+ .putString("calendarName", calendar.name)
+ .apply()
+
+
+ val intent = Intent(this, MainActivity::class.java)
+ startActivity(intent)
+ }
+
+ /**
+ * Called when an activity launched here (specifically, AccountPicker
+ * and authorization) exits, giving you the requestCode you started it with,
+ * the resultCode it returned, and any additional data from it.
+ * @param requestCode code indicating which activity result is incoming.
+ * @param resultCode code indicating the result of the incoming
+ * activity result.
+ * @param data Intent (containing result data) returned by incoming
+ * activity result.
+ */
+ override fun onActivityResult( requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ infoPrint("API Request code returned: $requestCode")
+ when (requestCode) {
+ REQUEST_GOOGLE_PLAY_SERVICES -> if (resultCode != Activity.RESULT_OK) {
+ isGooglePlayServicesAvailable
+ }
+ RC_SIGN_IN -> {
+ val task = GoogleSignIn.getSignedInAccountFromIntent(data)
+ handleSignInResult(task)
+ }
+ }
+
+ super.onActivityResult(requestCode, resultCode, data)
+ }
+
+ private fun handleSignInResult(task: Task){
+ try {
+ val account = task.getResult(ApiException::class.java)
+ mAppSettings.edit().putString(PREF_ACCOUNT_NAME, account?.email).apply()
+ checkForAccount()
+ model.refreshCalendarOptions()
+ } catch(e: ApiException){
+ Log.w("MainActivity", "signInResult failed; code=${e.statusCode}")
+ }
+ }
+
+ private fun signIn(){
+ startActivityForResult(signInClient.signInIntent, RC_SIGN_IN)
+ }
+
+ /**
+ * Allow the user to change accounts
+ */
+
+ private fun signOutThenIn(){
+ signInClient.signOut()
+ .addOnCompleteListener { signIn() }
+ }
+
+ /**
+ * Checks for a signed in account in the app; if one exists, it starts the MainActivity.
+ * Otherwise, it allows the user to choose an account
+ */
+
+ private fun checkForAccount(){
+ val accountName = mAppSettings.getString(PREF_ACCOUNT_NAME, "")!!
+ account_name_tv.text = accountName
+
+ if (accountName.isEmpty()){
+ signIn()
+ Log.i("MainActivity", "Begin chooseAccount")
+ } else {
+ enableUI()
+ }
+ }
+
+ /**
+ * Enables the UI to allow the user to choose which version they want to use,
+ * Kiosk or Companion
+ */
+ private fun enableUI() {
+ main_root.visibility = View.VISIBLE
+ }
+
+ private fun promptChooseCalendar() {
+ lateinit var dialog: DialogInterface
+ dialog = alert {
+ val v = layoutInflater.inflate(R.layout.add_location_alert, null)
+ v.add_location_rv.adapter =
+ CalendarInfoAdapter(applicationContext,
+ model.calendarItems) { item ->
+ dialog.dismiss()
+ selectCalendar(item)
+ }
+ v.add_location_rv.layoutManager = LinearLayoutManager(applicationContext)
+ customView = v
+ }.show()
+ }
+
+ private fun selectCalendar(cal: CalendarInfo){
+ calendar = cal
+ calendar_name_tv.text = cal.name
+ }
+
+ /**
+ * Display an error dialog showing that Google Play Services is missing
+ * or out of date.
+ * @param connectionStatusCode code describing the presence (or lack of)
+ * Google Play Services on this device.
+ */
+ private fun showGooglePlayServicesAvailabilityErrorDialog(
+ connectionStatusCode: Int) {
+ runOnUiThread {
+ val dialog = GooglePlayServicesUtil.getErrorDialog(
+ connectionStatusCode,
+ this@MainActivity,
+ REQUEST_GOOGLE_PLAY_SERVICES)
+ dialog.show()
+ }
+ }
+
+ private fun infoPrint(info: Any) {
+ println("MAIN_: $info")
+ }
+
+ companion object {
+ internal const val REQUEST_ACCOUNT_PICKER = 1000
+ internal const val RC_SIGN_IN = 1001
+ internal const val REQUEST_GOOGLE_PLAY_SERVICES = 1002
+ private const val PREF_ACCOUNT_NAME = "accountName"
+ val SCOPES = arrayOf(CalendarScopes.CALENDAR_READONLY)
+ }
+}
+
+data class Event(val summary: String, val start: Date,
+ val end: Date, val location: String)
+{
+ val isHappeningNow = hasStarted and !isOver
+
+ private val isOver: Boolean
+ get() {
+ val now = Date()
+ return now.after(end)
+ }
+
+
+ private val hasStarted: Boolean
+ get(){
+ val now = Date()
+ return start.before(now)
+ }
+}
+
+data class CalendarInfo(var name: String, var id: String)
\ No newline at end of file
diff --git a/app/src/main/java/edu/rit/csh/bettervent/view/companion/CompanionActivity.kt b/app/src/main/java/edu/rit/csh/bettervent/view/companion/CompanionActivity.kt
new file mode 100644
index 0000000..8c47505
--- /dev/null
+++ b/app/src/main/java/edu/rit/csh/bettervent/view/companion/CompanionActivity.kt
@@ -0,0 +1,170 @@
+package edu.rit.csh.bettervent.view.companion
+
+import android.content.DialogInterface
+import android.graphics.Color
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import android.os.Parcelable
+import android.view.ContextMenu
+import android.view.MenuItem
+import android.view.View
+import android.widget.AdapterView
+import android.widget.PopupMenu
+import androidx.lifecycle.ViewModelProviders
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
+import edu.rit.csh.bettervent.R
+import edu.rit.csh.bettervent.view.Event
+import edu.rit.csh.bettervent.viewmodel.CompanionActivityViewModel
+import kotlinx.android.parcel.Parcelize
+import kotlinx.android.synthetic.main.activity_companion.*
+import kotlinx.android.synthetic.main.add_location_alert.view.*
+import kotlinx.android.synthetic.main.location_view.view.*
+import kotlinx.android.synthetic.main.fragment_status.view.event_time
+import org.jetbrains.anko.alert
+import java.text.SimpleDateFormat
+import java.util.*
+
+class CompanionActivity : AppCompatActivity() {
+
+ lateinit var viewModel: CompanionActivityViewModel
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_companion)
+
+ locations_rv.layoutManager = LinearLayoutManager(this)
+
+ viewModel = ViewModelProviders.of(this).get(CompanionActivityViewModel::class.java)
+
+ refreshViewModel()
+
+ srl.setOnRefreshListener { refreshViewModel() }
+
+ add_fab.setOnClickListener { promptAddLocation() }
+ }
+
+ private fun refreshViewModel() {
+ srl.isRefreshing = true
+ viewModel.refresh {
+ locations_rv.adapter = LocationCardAdapter(this, getRoomStatusesFromMap(viewModel.eventsByLocation)) { openLocationFragment(it) }
+ if (viewModel.usedLocations.isEmpty()) {
+ tooltip.visibility = View.VISIBLE
+ vertical_glue.visibility = View.VISIBLE
+ } else {
+ tooltip.visibility = View.GONE
+ vertical_glue.visibility = View.GONE
+ }
+ srl.isRefreshing = false
+
+ registerForContextMenu(locations_rv)
+ }
+ }
+
+ private fun getRoomStatusesFromMap(map: Map): List {
+ return map.entries.map { entry ->
+ entry.value?.let { findRoomStatus(it) }
+ ?: RoomStatus(entry.key, false, "No upcoming events", "", getColor(R.color.CSHGreen))
+ }
+ }
+
+ private fun findRoomStatus(event: Event): RoomStatus {
+ val timeString = formatDates(event.start, event.end)
+
+ return if (event.isHappeningNow) {
+ RoomStatus(event.location, true, "Happening now: ${event.summary}", timeString, getColor(R.color.CSHRed))
+ } else {
+ RoomStatus(event.location, false, "Upcoming: ${event.summary}", timeString, getColor(R.color.CSHGreen))
+ }
+ }
+
+ private fun promptAddLocation() {
+ lateinit var dialog: DialogInterface
+ dialog = alert {
+ val v = layoutInflater.inflate(R.layout.add_location_alert, null)
+ v.add_location_rv.adapter =
+ LocationTextAdapter(applicationContext,
+ viewModel.allLocations.minus(viewModel.usedLocations).toList()) { location ->
+ dialog.dismiss()
+ viewModel.addUsedLocation(location)
+ refreshViewModel()
+ }
+ v.add_location_rv.layoutManager = LinearLayoutManager(applicationContext)
+ customView = v
+ }.show()
+ }
+
+ private fun openLocationFragment(roomStatus: RoomStatus) {
+ lateinit var dialog: DialogInterface
+ dialog = alert {
+ val v = layoutInflater.inflate(R.layout.location_view, null)
+ v.location_name.text = roomStatus.location
+ v.event_name.text = roomStatus.title
+ v.event_time.text = roomStatus.timeString
+ v.indicator.setBackgroundColor(roomStatus.color)
+ v.menu_ib.setOnClickListener {
+ PopupMenu(this@CompanionActivity, v.menu_ib).apply{
+ setOnMenuItemClickListener { item ->
+ when(item.itemId) {
+ R.id.delete_location -> {
+ viewModel.removeUsedLocation(roomStatus.location)
+ refreshViewModel()
+ dialog.dismiss()
+ true
+ }
+ else -> false
+ }
+ }
+ inflate(R.menu.location_menu)
+ show()
+ }
+ }
+
+ customView = v
+ }.show()
+ }
+}
+
+fun formatDates(d1: Date, d2: Date): String {
+ return when {
+ d2.isToday() -> "${d1.formatJustTime()} - ${d2.formatJustTime()}"
+ d1.isToday() -> "${d1.formatJustTime()} - ${d2.formatWithDay()}"
+ isSameDay(d1, d2) -> "${d1.formatWithDay()} - ${d2.formatJustTime()}"
+ else -> "${d1.formatWithDay()} - ${d2.formatWithDay()}"
+ }
+}
+
+fun isSameDay(d1: Date, d2: Date): Boolean {
+
+ val cal1 = Calendar.getInstance()
+ val cal2 = Calendar.getInstance()
+ cal1.time = d1
+ cal2.time = d2
+ return cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR) &&
+ cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)
+}
+
+fun Date.isToday(): Boolean {
+ return isSameDay(this, Date())
+}
+
+fun Date.formatJustTime(): String {
+ val simpleTimeFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
+ return simpleTimeFormat.format(this)
+}
+
+fun Date.formatWithDay(): String {
+ val simpleTimeFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
+ val simpleDateFormat = SimpleDateFormat("MM/dd", Locale.getDefault())
+ return "${simpleDateFormat.format(this)} ${simpleTimeFormat.format(this)}"
+}
+
+data class Location(var display: String,
+ val keys: Set)
+
+@Parcelize
+data class RoomStatus(val location: String,
+ val isBusy: Boolean,
+ val title: String,
+ val timeString: String,
+ val color: Int): Parcelable
\ No newline at end of file
diff --git a/app/src/main/java/edu/rit/csh/bettervent/view/companion/LocationCardAdapter.kt b/app/src/main/java/edu/rit/csh/bettervent/view/companion/LocationCardAdapter.kt
new file mode 100644
index 0000000..4a13751
--- /dev/null
+++ b/app/src/main/java/edu/rit/csh/bettervent/view/companion/LocationCardAdapter.kt
@@ -0,0 +1,38 @@
+package edu.rit.csh.bettervent.view.companion
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import edu.rit.csh.bettervent.R
+import kotlinx.android.synthetic.main.location_card.view.*
+
+class LocationCardAdapter(val context: Context, private val statuses: List, private val onItemClick: (RoomStatus) -> Unit):
+ RecyclerView.Adapter(){
+
+ override fun getItemCount(): Int = statuses.size
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val v = LayoutInflater.from(context).inflate(R.layout.location_card, parent, false)
+ return ViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val status = statuses[position]
+
+ holder.itemView.location_tv.text = status.location
+ if (status.isBusy){
+ holder.itemView.card_view.setCardBackgroundColor(context.getColor(R.color.CSHRed))
+ holder.itemView.title_tv.text = status.title
+ holder.itemView.time_tv.text = status.timeString
+ } else {
+ holder.itemView.card_view.setCardBackgroundColor(context.getColor(R.color.CSHGreen))
+ holder.itemView.title_tv.text = context.getString(R.string.room_open)
+ }
+
+ holder.itemView.setOnClickListener { onItemClick.invoke(status) }
+ }
+
+ inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
+}
\ No newline at end of file
diff --git a/app/src/main/java/edu/rit/csh/bettervent/view/companion/LocationTextAdapter.kt b/app/src/main/java/edu/rit/csh/bettervent/view/companion/LocationTextAdapter.kt
new file mode 100644
index 0000000..c8e0094
--- /dev/null
+++ b/app/src/main/java/edu/rit/csh/bettervent/view/companion/LocationTextAdapter.kt
@@ -0,0 +1,27 @@
+package edu.rit.csh.bettervent.view.companion
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import edu.rit.csh.bettervent.R
+import kotlinx.android.synthetic.main.add_location_item.view.*
+
+class LocationTextAdapter(val context: Context, private val locations: List, val onLocationSelected: (String) -> Unit): RecyclerView.Adapter() {
+
+ override fun getItemCount() = locations.size
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ holder.itemView.add_location_tv.text = locations[position]
+ holder.itemView.setOnClickListener { onLocationSelected.invoke(locations[position]) }
+ if (position == itemCount - 1) holder.itemView.divider.visibility = View.GONE
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val inflater = LayoutInflater.from(context)
+ return ViewHolder(inflater.inflate(R.layout.add_location_item, parent, false))
+ }
+
+ inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
+}
\ No newline at end of file
diff --git a/app/src/main/java/edu/rit/csh/bettervent/ParticipantListAdapter.kt b/app/src/main/java/edu/rit/csh/bettervent/view/kiosk/ParticipantListAdapter.kt
similarity index 96%
rename from app/src/main/java/edu/rit/csh/bettervent/ParticipantListAdapter.kt
rename to app/src/main/java/edu/rit/csh/bettervent/view/kiosk/ParticipantListAdapter.kt
index f1fa73f..762fd1b 100644
--- a/app/src/main/java/edu/rit/csh/bettervent/ParticipantListAdapter.kt
+++ b/app/src/main/java/edu/rit/csh/bettervent/view/kiosk/ParticipantListAdapter.kt
@@ -1,16 +1,15 @@
-package edu.rit.csh.bettervent
+package edu.rit.csh.bettervent.view.kiosk
import android.app.AlertDialog
import android.content.Context
-import android.content.DialogInterface
-import android.graphics.Typeface
-import android.support.v7.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView
import android.text.InputType
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.TextView
+import edu.rit.csh.bettervent.R
import java.util.ArrayList
diff --git a/app/src/main/java/edu/rit/csh/bettervent/QuickModeFragment.kt b/app/src/main/java/edu/rit/csh/bettervent/view/kiosk/QuickModeFragment.kt
similarity index 73%
rename from app/src/main/java/edu/rit/csh/bettervent/QuickModeFragment.kt
rename to app/src/main/java/edu/rit/csh/bettervent/view/kiosk/QuickModeFragment.kt
index f6d2204..7dc2ef0 100644
--- a/app/src/main/java/edu/rit/csh/bettervent/QuickModeFragment.kt
+++ b/app/src/main/java/edu/rit/csh/bettervent/view/kiosk/QuickModeFragment.kt
@@ -1,20 +1,22 @@
-package edu.rit.csh.bettervent
+package edu.rit.csh.bettervent.view.kiosk
import android.app.AlertDialog
-import android.content.DialogInterface
import android.graphics.Typeface
import android.os.Bundle
-import android.support.constraint.ConstraintLayout
-import android.support.v4.app.Fragment
-import android.support.v7.widget.LinearLayoutManager
-import android.support.v7.widget.RecyclerView
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.LinearLayoutManager
import android.text.InputType
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
+import android.widget.LinearLayout
import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import edu.rit.csh.bettervent.R
+import kotlinx.android.synthetic.main.fragment_quick_mode.*
+import kotlinx.android.synthetic.main.fragment_quick_mode.view.*
import java.util.ArrayList
@@ -22,39 +24,40 @@ class QuickModeFragment : Fragment() {
private val participants = ArrayList()
- private var quickModeLayout: ConstraintLayout? = null
+ private var quickModeLayout: LinearLayout? = null
- private var recyclerView: RecyclerView? = null
private var adapter: RecyclerView.Adapter<*>? = null
private var layoutManager: RecyclerView.LayoutManager? = null
private var participantsLabel: TextView? = null
private var nameSetLabel: TextView? = null
- private var eventName: TextView? = null
private var addButton: Button? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
infoPrint("Loaded QuickMode Fragment.")
val view = inflater.inflate(R.layout.fragment_quick_mode, container, false)
- MainActivity.centralClock.setTextColor(-0x1000000)
- quickModeLayout = view.findViewById(R.id.quick_mode_layout)
+ return view
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ quickModeLayout = view.findViewById(R.id.quick_mode_view)
- recyclerView = view.findViewById(R.id.participants_list)
// use a linear layout manager
- layoutManager = LinearLayoutManager(this.context)
- recyclerView!!.layoutManager = layoutManager
+ layoutManager = LinearLayoutManager(context)
+ participants_list.layoutManager = layoutManager
// specify an adapter
adapter = ParticipantListAdapter(this.context!!, participants)
- recyclerView!!.adapter = adapter
+ view.participants_list.adapter = adapter
participantsLabel = view.findViewById(R.id.label_participants)
nameSetLabel = view.findViewById(R.id.name_set_label)
- eventName = view.findViewById(R.id.event_name)
- eventName!!.setOnClickListener {
+ view.event_name.setOnClickListener {
val builder = AlertDialog.Builder(context)
builder.setTitle("Enter event title")
@@ -66,16 +69,15 @@ class QuickModeFragment : Fragment() {
// Set up the button
builder.setPositiveButton("OK") { dialog, which ->
val title = input.text.toString()
- eventName!!.text = title
+ view.event_name.text = title
//Change appearance of UI to indicate the room is reserved
addButton!!.isEnabled = true
quickModeLayout!!.setBackgroundColor(resources.getColor(R.color.CSHRed))
nameSetLabel!!.setTextColor(resources.getColor(R.color.white))
- eventName!!.setTextColor(resources.getColor(R.color.white))
+ view.event_name.setTextColor(resources.getColor(R.color.white))
participantsLabel!!.setTextColor(resources.getColor(R.color.white))
nameSetLabel!!.visibility = View.VISIBLE
- MainActivity.centralClock.setTextColor(-0x1)
- eventName!!.setTypeface(null, Typeface.BOLD)
+ view.event_name.setTypeface(null, Typeface.BOLD)
}
builder.setNegativeButton("Cancel") { dialog, which -> dialog.cancel() }
builder.show()
@@ -101,12 +103,10 @@ class QuickModeFragment : Fragment() {
adapter!!.notifyItemInserted(participants.size - 1)
infoPrint("Added new person.")
}
- builder.setNegativeButton("Cancel") { dialog, which -> dialog.cancel() }
+ builder.setNegativeButton("Cancel") { dialog, _ -> dialog.cancel() }
builder.show()
}
-
- return view
}
fun infoPrint(info: String) {
diff --git a/app/src/main/java/edu/rit/csh/bettervent/ScheduleFragment.kt b/app/src/main/java/edu/rit/csh/bettervent/view/kiosk/ScheduleFragment.kt
similarity index 61%
rename from app/src/main/java/edu/rit/csh/bettervent/ScheduleFragment.kt
rename to app/src/main/java/edu/rit/csh/bettervent/view/kiosk/ScheduleFragment.kt
index a3c7a99..d52c27c 100644
--- a/app/src/main/java/edu/rit/csh/bettervent/ScheduleFragment.kt
+++ b/app/src/main/java/edu/rit/csh/bettervent/view/kiosk/ScheduleFragment.kt
@@ -1,54 +1,55 @@
-package edu.rit.csh.bettervent
+package edu.rit.csh.bettervent.view.kiosk
import android.os.Bundle
-import android.support.v4.app.Fragment
+import android.util.Log
+import androidx.fragment.app.Fragment
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.lifecycle.ViewModelProviders
import com.alamkanak.weekview.DateTimeInterpreter
import com.alamkanak.weekview.MonthLoader
import com.alamkanak.weekview.WeekView
import com.alamkanak.weekview.WeekViewDisplayable
import com.alamkanak.weekview.WeekViewEvent
-import com.google.api.services.calendar.model.Event
+import edu.rit.csh.bettervent.R
+import edu.rit.csh.bettervent.view.Event
+import edu.rit.csh.bettervent.viewmodel.EventActivityViewModel
-import java.io.Serializable
import java.text.SimpleDateFormat
import java.util.ArrayList
import java.util.Calendar
import java.util.Locale
-class ScheduleFragment : Fragment(), MonthLoader.MonthChangeListener {
+class ScheduleFragment : Fragment(){
lateinit var weekView: WeekView
- private var events: List? = null
+ private lateinit var viewModel: EventActivityViewModel
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ viewModel = ViewModelProviders.of(requireActivity()).get(EventActivityViewModel::class.java)
+ }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
infoPrint("Loaded Schedule Fragment.")
val view = inflater.inflate(R.layout.fragment_schedule, container, false)
- val args = arguments
- if (args != null) {
- infoPrint("Found events data")
- events = args.getSerializable("events") as List
- if (events != null && events!!.isNotEmpty())
- infoPrint("First event title: " + events!![0].summary)
- } else {
- infoPrint("ERROR! NO DATA FOUND!")
- }
- MainActivity.centralClock.setTextColor(-0x1000000)
weekView = view.findViewById(R.id.week_view)
- weekView.setMonthChangeListener(this as MonthLoader.MonthChangeListener)
+ weekView.setMonthChangeListener(MonthChangeListener() as MonthLoader.MonthChangeListener)
weekView.numberOfVisibleDays = 7
// Lets change some dimensions to best fit the view.
weekView.columnGap = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2f, resources.displayMetrics).toInt()
weekView.setTimeColumnTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10f, resources.displayMetrics).toInt())
weekView.eventTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10f, resources.displayMetrics).toInt()
+
+ setupDateTimeInterpreter()
+
return view
}
@@ -67,7 +68,7 @@ class ScheduleFragment : Fragment(), MonthLoader.MonthChangeListener {
if (weekView.numberOfVisibleDays == 7) {
weekday = weekday[0].toString()
}
- return weekday.toUpperCase() + format.format(date.time)
+ return weekday.toUpperCase(Locale.getDefault()) + format.format(date.time)
}
override fun interpretTime(hour: Int): String {
@@ -76,6 +77,24 @@ class ScheduleFragment : Fragment(), MonthLoader.MonthChangeListener {
}
}
+ private fun getEvents(cal: Calendar): ArrayList{
+ val month = cal.get(Calendar.MONTH)
+ val year = cal.get(Calendar.YEAR)
+ val events = arrayListOf()
+
+ for (event in viewModel.events){
+ val eventCal = Calendar.getInstance()
+ eventCal.timeInMillis = event.start.time
+ val eventMonth = eventCal.get(Calendar.MONTH)
+ val eventYear = eventCal.get(Calendar.YEAR)
+
+ if (month == eventMonth && year == eventYear){
+ events.add(event)
+ }
+ }
+ return events
+ }
+
protected fun getEventTitle(time: Calendar): String {
val hour = time.get(Calendar.HOUR_OF_DAY)
val minute = time.get(Calendar.MINUTE)
@@ -85,26 +104,30 @@ class ScheduleFragment : Fragment(), MonthLoader.MonthChangeListener {
}
private fun infoPrint(info: String) {
- println("SCHE_: $info")
+ Log.i("ScheduleFragment", info)
}
- override fun onMonthChange(startDate: Calendar, endDate: Calendar): List> {
+ inner class MonthChangeListener: MonthLoader.MonthChangeListener{
- val weekViewEvents = ArrayList>()
+ override fun onMonthChange(startDate: Calendar, endDate: Calendar): List> {
- val color1 = resources.getColor(R.color.colorPrimaryDark)
+ val weekViewEvents = ArrayList>()
- if (events != null) {
- infoPrint("event size : " + events!!.size)
- for (i in events!!.indices) {
- val event = events!![i]
+ val color1 = resources.getColor(R.color.colorPrimaryDark)
+ infoPrint("event size : " + viewModel.events.size)
+
+ val events = getEvents(startDate)
+ for (i in events.indices) {
+ val event = viewModel.events[i]
val wve = WeekViewEvent()
+ infoPrint(event.toString())
+
// Set ID (not the Google Calendar ID).
- wve.setId(i.toLong())
+ wve.id = i.toLong()
// Set Title
- wve.setTitle(event.summary)
+ wve.title = event.summary
val newYear = startDate.get(Calendar.YEAR)
val newMonth = startDate.get(Calendar.MONTH)
@@ -113,17 +136,18 @@ class ScheduleFragment : Fragment(), MonthLoader.MonthChangeListener {
try {
// Start Time
val startCal = Calendar.getInstance()
- startCal.timeInMillis = event.start.dateTime.value
+ startCal.timeInMillis = event.start.time
startCal.set(Calendar.MONTH, newMonth)
startCal.set(Calendar.YEAR, newYear)
- wve.setStartTime(startCal)
+ Log.i("ScheduleFragment", "Startcal: $startCal")
+ wve.startTime = startCal
// End Time
val endCal = Calendar.getInstance()
- endCal.timeInMillis = event.end.dateTime.value
+ endCal.timeInMillis = event.end.time
endCal.set(Calendar.MONTH, newMonth)
endCal.set(Calendar.YEAR, newYear)
- wve.setEndTime(endCal)
+ wve.endTime = endCal
} catch (error: NullPointerException) {
error.printStackTrace()
wve.setIsAllDay(true)
@@ -135,18 +159,7 @@ class ScheduleFragment : Fragment(), MonthLoader.MonthChangeListener {
weekViewEvents.add(wve as WeekViewDisplayable)
}
- }
- return weekViewEvents
- }
-
- companion object {
-
- fun newInstance(events: List?): ScheduleFragment {
- val f = ScheduleFragment()
- val args = Bundle()
- args.putSerializable("events", events as Serializable)
- f.arguments = args
- return f
+ return weekViewEvents
}
}
}
diff --git a/app/src/main/java/edu/rit/csh/bettervent/SettingsFragment.kt b/app/src/main/java/edu/rit/csh/bettervent/view/kiosk/SettingsFragment.kt
similarity index 93%
rename from app/src/main/java/edu/rit/csh/bettervent/SettingsFragment.kt
rename to app/src/main/java/edu/rit/csh/bettervent/view/kiosk/SettingsFragment.kt
index 1de7124..fc94819 100644
--- a/app/src/main/java/edu/rit/csh/bettervent/SettingsFragment.kt
+++ b/app/src/main/java/edu/rit/csh/bettervent/view/kiosk/SettingsFragment.kt
@@ -1,17 +1,14 @@
-package edu.rit.csh.bettervent
+package edu.rit.csh.bettervent.view.kiosk
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
-import android.support.v4.app.Fragment
+import androidx.fragment.app.Fragment
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.widget.EditText
-import android.widget.RadioButton
-import android.widget.RadioGroup
-import kotlinx.android.synthetic.main.fragment_settings.*
+import edu.rit.csh.bettervent.R
import kotlinx.android.synthetic.main.fragment_settings.view.*
class SettingsFragment : Fragment() {
@@ -32,7 +29,7 @@ class SettingsFragment : Fragment() {
infoPrint("Loaded Settings Fragment.")
val view = inflater.inflate(R.layout.fragment_settings, container, false)
- appSettings = context!!.getSharedPreferences(
+ appSettings = requireActivity().applicationContext!!.getSharedPreferences(
getString(R.string.preference_file_key), Context.MODE_PRIVATE)
Log.i("test", appSettings.getString(filterKeywordsString, "test"))
@@ -45,8 +42,6 @@ class SettingsFragment : Fragment() {
"Password: [REDACTED]"
)
- MainActivity.centralClock.setTextColor(-0x1000000)
-
view.calendar_id_prompt.setText(appSettings.getString(calendarIDString, ""))
view.max_results_prompt.setText(appSettings.getString(maxResultsString, ""))
view.filtering_keywords_prompt.setText(appSettings.getString(filterKeywordsString, ""))
diff --git a/app/src/main/java/edu/rit/csh/bettervent/view/kiosk/StatusFragment.kt b/app/src/main/java/edu/rit/csh/bettervent/view/kiosk/StatusFragment.kt
new file mode 100644
index 0000000..18e9e0f
--- /dev/null
+++ b/app/src/main/java/edu/rit/csh/bettervent/view/kiosk/StatusFragment.kt
@@ -0,0 +1,160 @@
+package edu.rit.csh.bettervent.view.kiosk
+
+import android.content.Context
+import android.content.SharedPreferences
+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.lifecycle.ViewModelProviders
+import edu.rit.csh.bettervent.R
+import edu.rit.csh.bettervent.view.Event
+import edu.rit.csh.bettervent.viewmodel.EventActivityViewModel
+
+import kotlinx.android.synthetic.main.fragment_status.*
+import kotlinx.android.synthetic.main.password_alert.view.*
+import org.jetbrains.anko.alert
+import org.jetbrains.anko.noButton
+import org.jetbrains.anko.yesButton
+
+import java.text.SimpleDateFormat
+import java.util.*
+import kotlin.system.exitProcess
+
+class StatusFragment : Fragment(){
+
+ private lateinit var appSettings: SharedPreferences // Settings object containing user preferences.
+ private lateinit var viewModel: EventActivityViewModel
+ private lateinit var listener: OpenSettingsListener
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ infoPrint("Loaded Status Fragment.")
+
+ return inflater.inflate(R.layout.fragment_status, container, false)
+ }
+
+ /**
+ * @param view
+ * @param savedInstanceState
+ */
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ // Load up app settings to fetch passwords and background colors.
+ appSettings = requireActivity().applicationContext!!.getSharedPreferences(
+ getString(R.string.preference_file_key), Context.MODE_PRIVATE)
+
+ viewModel = ViewModelProviders.of(requireActivity()).get(EventActivityViewModel::class.java)
+
+ fun showAlertWithFunction(onSuccess: () -> Unit) {
+ context?.alert("Enter Password:") {
+ val v = layoutInflater.inflate(R.layout.password_alert, null)
+ customView = v
+ fun checkPassword(pw: String) {
+ if (pw == appSettings.getString("edu.rit.csh.bettervent.password", "")) onSuccess()
+ }
+ yesButton { checkPassword(v.password_et.text.toString()) }
+ noButton { dialog -> dialog.cancel() }
+ }?.show()
+ }
+
+ leave_button.setOnClickListener {
+ showAlertWithFunction { exitProcess(0) }
+ }
+
+ settings_button.setOnClickListener {
+ showAlertWithFunction { listener.openSettings() }
+ }
+ updateCurrentAndNextEventsInUI()
+ }
+
+ override fun onAttach(context: Context) {
+ context.let { super.onAttach(it)
+ if (it is OpenSettingsListener) {
+ listener = it
+ } else {
+ throw ClassCastException("$it must implement OpenSettingsListener.")
+ }
+ }
+ }
+
+ /**
+ * Looks at the APIOutList (the List of Events generated by the API),
+ * and based on how many there are and when they are, sets the string
+ * values for currentEventTitle, currentEventTime, nextEventTitle, and
+ * nextEventTime.
+ */
+
+ fun updateCurrentAndNextEventsInUI() {
+
+ infoPrint("Updating UI")
+
+ viewModel.events.also {
+ when {
+ it.isEmpty() -> {
+ setRoomAsEmpty(); setNoNextEvent()
+ }
+ it.size == 1 -> if (it[0].isHappeningNow) setCurrentEvent(it[0]) else setNextEvent(it[0])
+ else -> {
+ if (it[0].isHappeningNow){
+ setCurrentEvent(it[0])
+ setNextEvent(it[1])
+ } else {
+ setRoomAsEmpty()
+ setNextEvent(it[0])
+ }
+ }
+ }
+ }
+ }
+
+ private fun setRoomAsEmpty(){
+ free_label.visibility = View.VISIBLE
+ reserved_label.visibility = View.INVISIBLE
+ event_title.text = ""
+ event_time.text = ""
+ status_layout.setBackgroundColor(resources.getColor(R.color.CSHGreen))
+ }
+
+ private fun setNoNextEvent(){
+ next_label.visibility = View.INVISIBLE
+ next_event_time.text = ""
+ next_event_title.text = "There are no upcoming events."
+ }
+
+ private fun setCurrentEvent(e: Event){
+ free_label.visibility = View.INVISIBLE
+ reserved_label.visibility = View.VISIBLE
+ event_title.text = e.summary
+ event_time.text = "${formatDate(e.start)} - ${formatDate(e.end)}"
+ status_layout.setBackgroundColor(resources.getColor(R.color.CSHRed))
+ }
+
+ private fun setNextEvent(e: Event){
+ next_label.visibility = View.VISIBLE
+ next_event_time.text = "${formatDate(e.start)} - ${formatDate(e.end)}"
+ next_event_title.text = e.summary
+ }
+
+ /**
+ * Method to format DateTimes into human-readable strings
+ *
+ * @param dateTime: DateTime to make readable
+ * @return: HH:MM on YYYY/MM/DD
+ */
+ private fun formatDate(inputDate: Date): String {
+ val simpleTimeFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
+ val simpleDateFormat = SimpleDateFormat("yyyy/MM/dd", Locale.getDefault())
+ val time = simpleTimeFormat.format(inputDate)
+ val date = simpleDateFormat.format(inputDate)
+ return "$time on $date"
+ }
+
+ private fun infoPrint(info: String) {
+ Log.i("StatusFragment", info)
+ }
+}
+
+interface OpenSettingsListener{
+ fun openSettings()
+}
\ No newline at end of file
diff --git a/app/src/main/java/edu/rit/csh/bettervent/viewmodel/CompanionActivityViewModel.kt b/app/src/main/java/edu/rit/csh/bettervent/viewmodel/CompanionActivityViewModel.kt
new file mode 100644
index 0000000..3abf324
--- /dev/null
+++ b/app/src/main/java/edu/rit/csh/bettervent/viewmodel/CompanionActivityViewModel.kt
@@ -0,0 +1,152 @@
+package edu.rit.csh.bettervent.viewmodel
+
+import android.app.Application
+import android.content.Context
+import android.content.SharedPreferences
+import android.util.Log
+import androidx.lifecycle.AndroidViewModel
+import com.google.api.client.extensions.android.http.AndroidHttp
+import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential
+import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
+import com.google.api.client.json.JsonFactory
+import com.google.api.client.json.gson.GsonFactory
+import com.google.api.client.util.DateTime
+import com.google.api.client.util.ExponentialBackOff
+import com.google.api.services.calendar.Calendar
+import com.google.api.services.calendar.CalendarScopes
+import com.google.api.services.calendar.model.CalendarList
+import com.google.api.services.calendar.model.Events
+import edu.rit.csh.bettervent.R
+import edu.rit.csh.bettervent.view.CalendarInfo
+import edu.rit.csh.bettervent.view.Event
+import org.jetbrains.anko.doAsync
+import org.jetbrains.anko.uiThread
+import java.util.*
+import kotlin.collections.ArrayList
+
+class CompanionActivityViewModel(application: Application) : AndroidViewModel(application) {
+
+ private val settings: SharedPreferences =
+ application.applicationContext.getSharedPreferences(application.getString(R.string.preference_file_key), Context.MODE_PRIVATE)
+
+ private var mService: Calendar = getCalendarService()
+
+ private val locationsString = settings.getString("locations", "")!!
+
+ lateinit var calendarItems: List
+ val usedLocations = locationsString.split("|").filterNot { it.isBlank() }.toMutableSet()
+ val allLocations = mutableSetOf()
+ val eventsByLocation = mutableMapOf()
+
+ init {
+ refreshCalendarOptions()
+ }
+
+ fun refresh(onComplete: () -> Unit) {
+ updateEvents(onComplete)
+ }
+
+ fun addUsedLocation(location: String) {
+ var locationsString = settings.getString("locations", "")!!
+ locationsString = "$locationsString|$location"
+ settings.edit().putString("locations", locationsString).apply()
+ usedLocations.add(location)
+ }
+
+ fun removeUsedLocation(location: String) {
+ var locationsString = settings.getString("locations", "")!!
+ locationsString = locationsString.replace(location, "").replace("||", "")
+ settings.edit().putString("locations", locationsString).apply()
+ usedLocations.remove(location)
+ }
+
+ private fun getCalendarService(): Calendar {
+ val transport = AndroidHttp.newCompatibleTransport()
+ val jsonFactory: JsonFactory = GsonFactory.getDefaultInstance()
+
+ val credential = GoogleAccountCredential.usingOAuth2(
+ getApplication(), listOf(*SCOPES))
+ .setBackOff(ExponentialBackOff())
+ .setSelectedAccountName(settings.getString(PREF_ACCOUNT_NAME, ""))
+
+ return Calendar.Builder(
+ transport, jsonFactory, credential)
+ .setApplicationName("Google Calendar API Android Quickstart")
+ .build()
+ }
+
+ fun refreshCalendarOptions(){
+ mService = getCalendarService()
+ doAsync {
+ val inItems = mService.calendarList().list().execute()
+ uiThread {
+ calendarItems = inItems.items.map { it.toCalendarInfo() }
+ }
+ }
+ }
+
+ private fun updateEvents(f: () -> Unit) {
+ doAsync {
+ val events = getEventsFromServer(MAX_EVENTS)
+ uiThread {
+ handleEvents(parseEvents(events))
+ f.invoke()
+ }
+ }
+ }
+
+ private fun getEventsFromServer(maxEvents: Int): Events {
+ val calendarId = settings.getString("edu.rit.csh.bettervent.calendarid", "rti648k5hv7j3ae3a3rum8potk@group.calendar.google.com")
+
+ val now = DateTime(System.currentTimeMillis())
+
+ return mService.events().list(calendarId)
+ .setMaxResults(maxEvents)
+ .setTimeMin(now)
+ .setOrderBy("startTime")
+ .setSingleEvents(true)
+ .execute()
+ }
+
+ private fun parseEvents(calendarEvents: Events): MutableList {
+ val events = mutableListOf()
+
+ events.addAll(calendarEvents.items.mapNotNull { calendarEvent ->
+ calendarEvent.location?.let { allLocations.add(it) }
+ calendarEvent.parseToEvent()
+ })
+ Log.i("CompanionActivityViewModel", allLocations.toString())
+
+ return events
+ }
+
+ private fun handleEvents(inEvents: Collection) {
+ eventsByLocation.clear()
+ eventsByLocation.putAll(usedLocations.map { location ->
+ location to inEvents.firstOrNull { event ->
+ event.location.trim().toLowerCase(Locale.getDefault()) ==
+ location.trim().toLowerCase(Locale.getDefault())
+ }
+ })
+ }
+
+ companion object {
+ private const val PREF_ACCOUNT_NAME = "accountName"
+ private const val MAX_EVENTS = 50
+ private val SCOPES = arrayOf(CalendarScopes.CALENDAR_READONLY)
+ }
+
+ private fun com.google.api.services.calendar.model.Event.parseToEvent(): Event? {
+ location?.also {
+ return Event(summary,
+ Date(start.dateTime.value),
+ Date(end.dateTime.value),
+ location)
+ }
+ return null
+ }
+
+ private fun com.google.api.services.calendar.model.CalendarListEntry.toCalendarInfo(): CalendarInfo {
+ return CalendarInfo(summary, id)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/edu/rit/csh/bettervent/viewmodel/EventActivityViewModel.kt b/app/src/main/java/edu/rit/csh/bettervent/viewmodel/EventActivityViewModel.kt
new file mode 100644
index 0000000..87fb5b2
--- /dev/null
+++ b/app/src/main/java/edu/rit/csh/bettervent/viewmodel/EventActivityViewModel.kt
@@ -0,0 +1,128 @@
+package edu.rit.csh.bettervent.viewmodel
+
+import android.app.Application
+import android.content.Context
+import android.content.SharedPreferences
+import android.util.Log
+import androidx.lifecycle.AndroidViewModel
+import com.google.api.client.extensions.android.http.AndroidHttp
+import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential
+import com.google.api.client.json.JsonFactory
+import com.google.api.client.json.gson.GsonFactory
+import com.google.api.client.util.DateTime
+import com.google.api.client.util.ExponentialBackOff
+import com.google.api.services.calendar.Calendar
+import com.google.api.services.calendar.CalendarScopes
+import com.google.api.services.calendar.model.Events
+import edu.rit.csh.bettervent.R
+import edu.rit.csh.bettervent.view.Event
+import org.jetbrains.anko.doAsync
+import org.jetbrains.anko.uiThread
+import java.util.*
+import kotlin.collections.ArrayList
+
+class EventActivityViewModel(application: Application) : AndroidViewModel(application) {
+ val events: ArrayList = arrayListOf()
+
+ private val settings: SharedPreferences =
+ application.applicationContext.getSharedPreferences(application.getString(R.string.preference_file_key), Context.MODE_PRIVATE)
+
+ private val mService = getCalendarService()
+
+ fun refresh(onComplete: () -> Unit) {
+ updateEvents(onComplete)
+ }
+
+ private fun getCalendarService(): Calendar {
+ Log.i("EventActivityViewModel", settings.getString("test", "test")!!)
+ val transport = AndroidHttp.newCompatibleTransport()
+ val jsonFactory: JsonFactory = GsonFactory.getDefaultInstance()
+
+ val credential = GoogleAccountCredential.usingOAuth2(
+ getApplication(), listOf(*SCOPES))
+ .setBackOff(ExponentialBackOff())
+ .setSelectedAccountName(settings.getString(PREF_ACCOUNT_NAME, ""))
+
+ return Calendar.Builder(
+ transport, jsonFactory, credential)
+ .setApplicationName("Google Calendar API Android Quickstart")
+ .build()
+ }
+
+ private fun updateEvents(f: () -> Unit){
+ doAsync{
+ val events = getEventsFromServer()
+ uiThread {
+ handleEvents(parseEvents(events))
+ f.invoke()
+ }
+ }
+ }
+
+ private fun getEventsFromServer(): Events {
+ val calendarId = settings.getString("edu.rit.csh.bettervent.calendarid", "rti648k5hv7j3ae3a3rum8potk@group.calendar.google.com")
+
+ val maxResultsStr = settings.getString("edu.rit.csh.bettervent.maxresults", "100")
+ val maxResults = maxResultsStr?.let { Integer.parseInt(it) }
+
+ val now = DateTime(System.currentTimeMillis())
+ return mService.events().list(calendarId)
+ .setMaxResults(maxResults)
+ .setTimeMin(now)
+ .setOrderBy("startTime")
+ .setSingleEvents(true)
+ .execute()
+ }
+
+ private fun parseEvents(calendarEvents: Events): ArrayList{
+ val events = ArrayList()
+ for (calendarEvent in calendarEvents.items){
+ val event = calendarEvent.parseToEvent()
+ event?.also{
+ Log.i("MainActivity", "Event added: $event")
+ events.add(it)
+ }
+ }
+ return events
+ }
+
+ private fun handleEvents(inEvents: ArrayList){
+ events.removeAll(events)
+
+ if (inEvents.isNotEmpty()){
+ val eventKeyword = settings.getString("edu.rit.csh.bettervent.filterkeywords", "")!!
+ events.removeAll(events)
+ for (event in inEvents) {
+ val eventFieldToCheck = if (settings.getBoolean("edu.rit.csh.bettervent.filterbytitle", false)) {
+ event.summary
+ } else {
+ event.location
+ }
+ if (eventKeyword.isNotEmpty()) {
+ if (eventFieldToCheck.toLowerCase(Locale.getDefault()).contains(eventKeyword.toLowerCase(Locale.getDefault()))) {
+ events.add(event)
+ }
+ } else {
+ events.add(event)
+ }
+ }
+ }
+ }
+
+ companion object {
+ private const val PREF_ACCOUNT_NAME = "accountName"
+ private val SCOPES = arrayOf(CalendarScopes.CALENDAR_READONLY)
+ }
+
+
+
+ private fun com.google.api.services.calendar.model.Event.parseToEvent(): Event?{
+ location?.also{
+ return Event(summary,
+ Date(start.dateTime.value),
+ Date(end.dateTime.value),
+ location)
+ }
+ return null
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/color/bottom_nav.xml b/app/src/main/res/color/bottom_nav.xml
new file mode 100644
index 0000000..3d49bb6
--- /dev/null
+++ b/app/src/main/res/color/bottom_nav.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml
new file mode 100644
index 0000000..757f450
--- /dev/null
+++ b/app/src/main/res/drawable/ic_add.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml
new file mode 100644
index 0000000..09503ae
--- /dev/null
+++ b/app/src/main/res/drawable/ic_delete.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_home_black_24dp.xml b/app/src/main/res/drawable/ic_home_black_24dp.xml
deleted file mode 100644
index 70fb291..0000000
--- a/app/src/main/res/drawable/ic_home_black_24dp.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_more_vert.xml b/app/src/main/res/drawable/ic_more_vert.xml
new file mode 100644
index 0000000..dcfdb14
--- /dev/null
+++ b/app/src/main/res/drawable/ic_more_vert.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/rounded_rectangle.xml b/app/src/main/res/drawable/rounded_rectangle.xml
new file mode 100644
index 0000000..2b64cf1
--- /dev/null
+++ b/app/src/main/res/drawable/rounded_rectangle.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_companion.xml b/app/src/main/res/layout/activity_companion.xml
new file mode 100644
index 0000000..48c6239
--- /dev/null
+++ b/app/src/main/res/layout/activity_companion.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_event.xml b/app/src/main/res/layout/activity_event.xml
new file mode 100644
index 0000000..19ed3e9
--- /dev/null
+++ b/app/src/main/res/layout/activity_event.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 5171c38..f78d8c1 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,57 +1,95 @@
-
+ android:padding="20dp"
+ android:id="@+id/main_root"
+ android:visibility="invisible">
-
+
+
+
+
+
+
+
+
+
-
-
-
+
+
-
-
+
+
+
+
+ android:gravity="center"
+ android:orientation="horizontal" >
+
+
+
+
+
+
+
+
+
+
-
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/add_location_alert.xml b/app/src/main/res/layout/add_location_alert.xml
new file mode 100644
index 0000000..342383f
--- /dev/null
+++ b/app/src/main/res/layout/add_location_alert.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/add_location_item.xml b/app/src/main/res/layout/add_location_item.xml
new file mode 100644
index 0000000..11461bc
--- /dev/null
+++ b/app/src/main/res/layout/add_location_item.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_quick_mode.xml b/app/src/main/res/layout/fragment_quick_mode.xml
index 5287a42..8055612 100644
--- a/app/src/main/res/layout/fragment_quick_mode.xml
+++ b/app/src/main/res/layout/fragment_quick_mode.xml
@@ -1,90 +1,71 @@
-
+ android:background="@color/white"
+ android:orientation="vertical"
+ android:id="@+id/quick_mode_view"
+ android:gravity="center_horizontal">
-
+
-
+
-
+
-
-
+
-
+
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_schedule.xml b/app/src/main/res/layout/fragment_schedule.xml
index c204d89..38baa33 100644
--- a/app/src/main/res/layout/fragment_schedule.xml
+++ b/app/src/main/res/layout/fragment_schedule.xml
@@ -1,5 +1,5 @@
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml
index 75a2435..563cbf1 100644
--- a/app/src/main/res/layout/fragment_settings.xml
+++ b/app/src/main/res/layout/fragment_settings.xml
@@ -17,10 +17,10 @@
+ android:layout_marginTop="16dp">
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/location_view.xml b/app/src/main/res/layout/location_view.xml
new file mode 100644
index 0000000..bcf112b
--- /dev/null
+++ b/app/src/main/res/layout/location_view.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/location_menu.xml b/app/src/main/res/menu/location_menu.xml
new file mode 100644
index 0000000..704c752
--- /dev/null
+++ b/app/src/main/res/menu/location_menu.xml
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 5014c72..dacaca9 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -7,6 +7,7 @@
#E11C52
#000000
#FFFFFF
+ #6a57c7
#296ba0
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0b08466..95adb22 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -24,7 +24,18 @@
Enter event title...
Add
Participants:
-
+ preference_file
+
+
+ Hello blank fragment
+ Room Open!
+ Add a Location:
+ Press the plus button to add a location!
+ Locations
+
+ notification
+ BetterVent
+ Remove location
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index e09a606..e0a561c 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -6,6 +6,7 @@
- @color/colorPrimary
- @color/colorPrimaryDark
- @color/colorAccent
+ - @style/TextViewStyle
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/test/java/edu/rit/csh/bettervent/ExampleUnitTest.java b/app/src/test/java/edu/rit/csh/bettervent/ExampleUnitTest.java
index 57bdce0..bb8bb1a 100644
--- a/app/src/test/java/edu/rit/csh/bettervent/ExampleUnitTest.java
+++ b/app/src/test/java/edu/rit/csh/bettervent/ExampleUnitTest.java
@@ -2,6 +2,10 @@
import org.junit.Test;
+import java.util.Date;
+
+import edu.rit.csh.bettervent.view.Event;
+
import static org.junit.Assert.*;
/**
@@ -10,8 +14,22 @@
* @see Testing documentation
*/
public class ExampleUnitTest {
+
@Test
- public void addition_isCorrect() {
- assertEquals(4, 2 + 2);
+ public void event_happening_now(){
+ Date oneMinuteAgo = new Date(System.currentTimeMillis() - 60 * 1000);
+ Date oneMinuteLater = new Date(System.currentTimeMillis() + 60 * 1000);
+ Date oneHourAgo = new Date(System.currentTimeMillis() - 3600 * 1000);
+ Date oneHourLater = new Date(System.currentTimeMillis() + 3600 * 1000);
+
+ Event happeningNow = new Event("Summary", oneMinuteAgo, oneMinuteLater, "Location");
+ Event alsoHappeningNow = new Event("Summary", oneHourAgo, oneHourLater, "Location");
+ Event happeningLater = new Event("Summary", oneMinuteLater, oneHourLater, "Location");
+ Event happenedEarlier = new Event("Summary", oneHourAgo, oneMinuteAgo, "Location");
+
+ assertTrue(happeningNow.isHappeningNow());
+ assertTrue(alsoHappeningNow.isHappeningNow());
+ assertFalse(happeningLater.isHappeningNow());
+ assertFalse(happenedEarlier.isHappeningNow());
}
}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 27dab5e..2f603f2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -8,7 +8,7 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.5.0'
+ classpath 'com.android.tools.build:gradle:3.5.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
diff --git a/gradle.properties b/gradle.properties
index 82618ce..d546dea 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -6,6 +6,8 @@
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
+android.enableJetifier=true
+android.useAndroidX=true
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit