Skip to content

Commit

Permalink
[RFR-329] Add incentives and group selection to r4r UI (#51)
Browse files Browse the repository at this point in the history
* [RFR-329] Add achievements ui

* Only show achievements in R4R UI

* Calculate and show progress

* Format valid until into local format

* Fake get voucher request

* Add copy to clipboard handler

* [RFR-451] Add voucher details

* [RFR-461] Check voucher count

* [RFR-450] Ask group during registration

* [STAD-515] Upgrade SDK to 7.7.2

* [RFR-478] Set km goal

* Refactor code

* [RFR-467] Get auth token for incentives requests

* [RFR-467] Call hard-coded staging incentives API

* Cleanup and documentation

* [RFR-506] Inject incentives API URL
  • Loading branch information
hb0 authored Jun 2, 2023
1 parent 0115bfa commit c8b04da
Show file tree
Hide file tree
Showing 28 changed files with 825 additions and 34 deletions.
5 changes: 3 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
*
* @author Armin Schnabel
* @author Klemens Muthmann
* @version 2.3.0
* @version 2.4.0
* @since 1.0.0
*/

Expand Down Expand Up @@ -55,7 +55,7 @@ ext {
*/

// Cyface dependencies
cyfaceAndroidBackendVersion = "7.7.1" // Also update submodule commit ref
cyfaceAndroidBackendVersion = "7.7.2" // Also update submodule commit ref
cyfaceUtilsVersion = "3.3.7"
cyfaceEnergySettingsVersion = "3.3.3" // Also update submodule commit ref
cyfaceCameraServiceVersion = "4.1.11" // Also update submodule commit ref
Expand All @@ -78,6 +78,7 @@ ext {
roomVersion = "2.5.1"
lifecycleVersion = "2.6.1"
navigationVersion = "2.5.3"
volleyVersion = "1.2.1"

// Kotlin components
coroutinesVersion = "1.6.4"
Expand Down
4 changes: 3 additions & 1 deletion gradle.properties.template
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ githubUser=
githubToken=

cyface.api=
cyface.auth_api
cyface.auth_api=
cyface.incentives_api=

google.maps_api_key=
google.maps-api_key.r4r=
Expand All @@ -50,6 +51,7 @@ cyface.emulator_api=

cyface.staging_api=
cyface.staging_auth_api=
cyface.staging_incentives_api=
cyface.staging_user=
cyface.staging_password=

Expand Down
3 changes: 2 additions & 1 deletion ui/cyface/src/main/kotlin/de/cyface/app/LoginActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,8 @@ class LoginActivity : AccountAuthenticatorActivity() {
private fun setServerUrl() {
val storedServer = preferences!!.getString(AUTH_ENDPOINT_URL_SETTINGS_KEY, null)
val server = BuildConfig.authServer
Validate.notNull(server)
@Suppress("KotlinConstantConditions")
Validate.isTrue(server != "null")
if (storedServer == null || storedServer != server) {
Log.d(
TAG,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,8 @@ class RegistrationActivity : FragmentActivity() /* HCaptcha requires FragmentAct
val stored =
preferences!!.getString(AUTH_ENDPOINT_URL_SETTINGS_KEY, null)
val currentUrl = BuildConfig.authServer
Validate.notNull(currentUrl)
@Suppress("KotlinConstantConditions")
Validate.isTrue(currentUrl != "null")
if (stored == null || stored != currentUrl) {
Log.d(TAG, "Updating Auth API URL from $stored to $currentUrl")
val editor = preferences!!.edit()
Expand Down
2 changes: 2 additions & 0 deletions ui/r4r/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ android {
// Staging
buildConfigField "String", "cyfaceServer", "\"${project.findProperty('cyface.staging_api')}\""
buildConfigField "String", "authServer", "\"${project.findProperty('cyface.staging_auth_api')}\""
buildConfigField "String", "incentivesServer", "\"${project.findProperty('cyface.staging_incentives_api')}\""
buildConfigField "String", "testLogin", "\"${project.findProperty('cyface.staging_user')}\""
buildConfigField "String", "testPassword", "\"${project.findProperty('cyface.staging_password')}\""
manifestPlaceholders = [usesCleartextTraffic:"false"]
Expand All @@ -109,6 +110,7 @@ android {
// signingConfig is set by the CI
buildConfigField "String", "cyfaceServer", "\"${project.findProperty('cyface.api')}\""
buildConfigField "String", "authServer", "\"${project.findProperty('cyface.auth_api')}\""
buildConfigField "String", "incentivesServer", "\"${project.findProperty('cyface.incentives_api')}\""
manifestPlaceholders = [usesCleartextTraffic:"false"]
}
}
Expand Down
53 changes: 53 additions & 0 deletions ui/r4r/src/main/kotlin/de/cyface/app/r4r/Group.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2023 Cyface GmbH
*
* This file is part of the Cyface SDK for Android.
*
* The Cyface SDK for Android is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Cyface SDK for Android is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with the Cyface SDK for Android. If not, see <http://www.gnu.org/licenses/>.
*/
package de.cyface.app.r4r

/**
* The groups the user can choose from during registration.
*
* This way we can enable group-specific achievements like vouchers.
*
* @author Armin Schnabel
* @version 1.0.0
* @since 3.3.0
* @property databaseIdentifier The [String] which represents the enumeration value in the database.
* @property spinnerText The [String] which is shown in the `Spinner`.
*/
enum class Group(private val databaseIdentifier: String, val spinnerText: String) {
// Keep the spinnerText in sync with `res/values/groups.xml`
// Don't change the databaseIdentifier.
@Suppress("SpellCheckingInspection")
NONE_GERMAN("guest", "Kommune auswählen"),
NONE_ENGLISH("guest", "Choose municipality"),

@Suppress("SpellCheckingInspection")
KOETHEN("koethen", "Köthen"),
SCHKEUDITZ("schkeuditz", "Schkeuditz");

companion object {
private val spinnerTextValues = Group.values().associateBy(Group::spinnerText)

/**
* Returns the [Group] from the selected spinner text value.
*
* @param spinnerText The selected spinner text.
*/
fun fromSpinnerText(spinnerText: String) = spinnerTextValues[spinnerText]
}
}
3 changes: 2 additions & 1 deletion ui/r4r/src/main/kotlin/de/cyface/app/r4r/LoginActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,8 @@ class LoginActivity : AccountAuthenticatorActivity() {
private fun setServerUrl() {
val storedServer = preferences!!.getString(AUTH_ENDPOINT_URL_SETTINGS_KEY, null)
val server = BuildConfig.authServer
Validate.notNull(server)
@Suppress("KotlinConstantConditions")
Validate.isTrue(server != "null")
if (storedServer == null || storedServer != server) {
Log.d(
TAG,
Expand Down
28 changes: 28 additions & 0 deletions ui/r4r/src/main/kotlin/de/cyface/app/r4r/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ import de.cyface.app.utils.ServiceProvider
import de.cyface.app.utils.SharedConstants
import de.cyface.app.utils.SharedConstants.DEFAULT_SENSOR_FREQUENCY
import de.cyface.app.utils.SharedConstants.PREFERENCES_SYNCHRONIZATION_KEY
import de.cyface.app.utils.trips.incentives.Incentives
import de.cyface.app.utils.trips.incentives.Incentives.Companion.INCENTIVES_ENDPOINT_URL_SETTINGS_KEY
import de.cyface.datacapturing.CyfaceDataCapturingService
import de.cyface.datacapturing.DataCapturingListener
import de.cyface.datacapturing.exception.SetupException
Expand All @@ -57,6 +59,7 @@ import de.cyface.energy_settings.TrackingSettings.showProblematicManufacturerDia
import de.cyface.energy_settings.TrackingSettings.showRestrictedBackgroundProcessingWarningDialog
import de.cyface.persistence.model.ParcelableGeoLocation
import de.cyface.synchronization.Constants.AUTH_TOKEN_TYPE
import de.cyface.synchronization.SyncService
import de.cyface.synchronization.WiFiSurveyor
import de.cyface.uploader.exception.SynchronisationException
import de.cyface.utils.DiskConsumption
Expand Down Expand Up @@ -206,6 +209,10 @@ class MainActivity : AppCompatActivity(), ServiceProvider {

// Not showing manufacturer warning on each resume to increase likelihood that it's read
showProblematicManufacturerDialog(this, false, SUPPORT_EMAIL)

// Inject the Incentives API URL into the preferences, as the `Incentives` from `utils`
// cannot reach the `ui.rfr.BuildConfig`.
setIncentivesServerUrl()
}

/**
Expand Down Expand Up @@ -322,4 +329,25 @@ class MainActivity : AppCompatActivity(), ServiceProvider {
Validate.isTrue(existingAccounts.size < 2, "More than one account exists.")
return existingAccounts.isNotEmpty()
}

/**
* As long as the server URL is hardcoded we want to reset it when it's different from the
* default URL set in the [BuildConfig]. If not, hardcoded updates would not have an
* effect.
*/
private fun setIncentivesServerUrl() {
val storedServer = preferences.getString(INCENTIVES_ENDPOINT_URL_SETTINGS_KEY, null)
val server = BuildConfig.incentivesServer
@Suppress("KotlinConstantConditions")
Validate.isTrue(server != "null")
if (storedServer == null || storedServer != server) {
Log.d(
TAG,
"Updating Cyface Incentives API URL from " + storedServer + "to" + server
)
val editor = preferences.edit()
editor.putString(INCENTIVES_ENDPOINT_URL_SETTINGS_KEY, server)
editor.apply()
}
}
}
45 changes: 39 additions & 6 deletions ui/r4r/src/main/kotlin/de/cyface/app/r4r/RegistrationActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ import android.util.Patterns
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.ProgressBar
import android.widget.Spinner
import androidx.fragment.app.FragmentActivity
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textview.MaterialTextView
Expand Down Expand Up @@ -58,10 +61,11 @@ import java.util.regex.Pattern
* A registration screen that offers registration via email/password and captcha.
*
* @author Armin Schnabel
* @version 1.0.0
* @version 1.1.0
* @since 3.3.0
*/
class RegistrationActivity : FragmentActivity() /* HCaptcha requires FragmentActivity */ {
class RegistrationActivity : FragmentActivity() /* HCaptcha requires FragmentActivity */,
AdapterView.OnItemSelectedListener {

private lateinit var context: WeakReference<Context>
private var preferences: SharedPreferences? = null
Expand All @@ -80,6 +84,12 @@ class RegistrationActivity : FragmentActivity() /* HCaptcha requires FragmentAct
private var emailInput: TextInputEditText? = null
private var passwordInput: TextInputEditText? = null
private var passwordConfirmationInput: TextInputEditText? = null
private lateinit var groupSpinner: Spinner

/**
* The group selected by the user during registration.
*/
private var group: Group? = null
private var messageView: MaterialTextView? = null

/**
Expand All @@ -102,10 +112,22 @@ class RegistrationActivity : FragmentActivity() /* HCaptcha requires FragmentAct
preferences = PreferenceManager.getDefaultSharedPreferences(this)
setServerUrl() // TODO [CY-3735]: via Android's settings

// Set up the login form
// Set up the form
emailInput = findViewById(R.id.input_email)
passwordInput = findViewById(R.id.input_password)
passwordConfirmationInput = findViewById(R.id.input_password_confirmation)
groupSpinner = findViewById(R.id.group_spinner)
ArrayAdapter.createFromResource(
this,
R.array.groups,
android.R.layout.simple_spinner_item
).also { adapter ->
// Specify the layout to use when the list of choices appears
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
// Apply the adapter to the spinner
groupSpinner.adapter = adapter
}
groupSpinner.onItemSelectedListener = this
messageView = findViewById(R.id.registration_message)
registrationButton = findViewById(R.id.registration_button)
registrationButton!!.setOnClickListener { attemptRegistration() }
Expand All @@ -118,8 +140,16 @@ class RegistrationActivity : FragmentActivity() /* HCaptcha requires FragmentAct
setupHCaptcha(hCaptcha)
}

override fun onDestroy() {
super.onDestroy()
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
val selected = parent!!.getItemAtPosition(position).toString()
val group = Group.fromSpinnerText(selected)
Validate.notNull(group, "Unknown spinner text: $selected")
this.group = group
}

override fun onNothingSelected(parent: AdapterView<*>?) {
// Another interface callback
Log.d(TAG, "onNothingSelected")
}

private fun hCaptchaConfig(): HCaptchaConfig {
Expand Down Expand Up @@ -173,6 +203,7 @@ class RegistrationActivity : FragmentActivity() /* HCaptcha requires FragmentAct
Validate.notNull(emailInput!!.text)
Validate.notNull(passwordInput!!.text)
Validate.notNull(passwordConfirmationInput!!.text)
Validate.notNull(group)
val email = emailInput!!.text.toString()
val password = passwordInput!!.text.toString()
val passwordConfirmation = passwordConfirmationInput!!.text.toString()
Expand Down Expand Up @@ -260,6 +291,7 @@ class RegistrationActivity : FragmentActivity() /* HCaptcha requires FragmentAct
}
}
}

else -> {
reportError(e)
}
Expand Down Expand Up @@ -348,7 +380,8 @@ class RegistrationActivity : FragmentActivity() /* HCaptcha requires FragmentAct
val stored =
preferences!!.getString(AUTH_ENDPOINT_URL_SETTINGS_KEY, null)
val currentUrl = BuildConfig.authServer
Validate.notNull(currentUrl)
@Suppress("KotlinConstantConditions")
Validate.isTrue(currentUrl != "null")
if (stored == null || stored != currentUrl) {
Log.d(TAG, "Updating Auth API URL from $stored to $currentUrl")
val editor = preferences!!.edit()
Expand Down
7 changes: 7 additions & 0 deletions ui/r4r/src/main/res/layout/activity_registration.xml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>

<!-- Select event/group e.g. for voucher achievements -->
<!-- there is no simple Material Design Spinner, only weird workarounds -->
<Spinner
android:id="@+id/group_spinner"
android:layout_width="match_parent"
android:layout_height="@dimen/md_listitem_height" />

<!-- Messages, e.g. "registration successful, please activate your account -->
<com.google.android.material.textview.MaterialTextView
android:id="@+id/registration_message"
Expand Down
9 changes: 9 additions & 0 deletions ui/r4r/src/main/res/values-de/groups.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Keep in Sync with `Group` enum and `groups` translations! -->
<string-array name="groups">
<item>Kommune auswählen</item>
<item>Köthen</item>
<item>Schkeuditz</item>
</string-array>
</resources>
4 changes: 4 additions & 0 deletions ui/r4r/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<resources>

<!-- Registration -->
<!-- Keep in Sync with `Groups` enum and `groups` translations! -->
<string name="choose_municipality">Kommune auswählen</string>

<!-- Speed -->
<string name="capturing_inactive">Datenerfassung inaktiv</string>
</resources>
9 changes: 9 additions & 0 deletions ui/r4r/src/main/res/values-it/groups.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Keep in Sync with `Group` enum and `groups` translations! -->
<string-array name="groups">
<item>Choose municipality</item>
<item>Köthen</item>
<item>Schkeuditz</item>
</string-array>
</resources>
4 changes: 4 additions & 0 deletions ui/r4r/src/main/res/values-it/strings.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<resources>

<!-- Registration -->
<!-- Keep in Sync with `Groups` enum and `groups` translations! -->
<string name="choose_municipality">Choose Municipality</string>

<!-- Speed -->
<string name="capturing_inactive">capturing inactive</string>
</resources>
9 changes: 9 additions & 0 deletions ui/r4r/src/main/res/values/groups.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Keep in Sync with `Group` enum and `groups` translations! -->
<string-array name="groups">
<item>Choose municipality</item>
<item>Köthen</item>
<item>Schkeuditz</item>
</string-array>
</resources>
4 changes: 4 additions & 0 deletions ui/r4r/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
<resources>
<string name="app_name" translatable="false">Ready4Robots</string>

<!-- Registration -->
<!-- Keep in Sync with `Groups` enum and `groups` translations! -->
<string name="choose_municipality">Choose Municipality</string>

<!-- Speed -->
<string name="capturing_inactive">capturing inactive</string>
</resources>
Loading

0 comments on commit c8b04da

Please sign in to comment.