Skip to content
Snippets Groups Projects
Commit 73b11e5c authored by jorplaz's avatar jorplaz
Browse files

Merge branch 'develop' into 'main'

Develop

See merge request !8
parents 353ba34e ebc0da42
No related branches found
No related tags found
1 merge request!8Develop
Showing
with 322 additions and 810 deletions
# PlazaLazoJorge_2022_frontend
#RetoBici Front End
## Instalación Front End
La aplicación se debe instalar en un dispositivo Android con una versión
5.0 (API 21) como mínimo, pero es recomendable por rendimiento utilizar
un dispositivo con la última versión disponible. Junto a esta memoria se
entrega el fichero `retobici.apk`, al descargarse en el dispositivo
móvil, normalmente se ubica en la carpeta *Descargas* del dispositivo,
al ejecutarse el sistema Android pedirá permisos para instalar
aplicaciones externas, tras confirmar la aplicación será instalada.
## Getting started
Hay que detallar ciertos aspectos de la configuración del proyecto
Android que podrían variar dependiendo del entorno. Hay que destacar que
todos estos valores ya se encuentran definidos en la APK y que esta
sección se detalla por si fuera necesario en un futuro.
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
Sería necesario agregar las claves de Mapbox que se han generado
previamente.
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
1. La clave secreta tiene que agregarse en dos ubicaciones
## Add your files
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
1. En un fichero de configuración de Gradle, editar o crear si no
existe el fichero\
`~/.gradle/gradle.properties` agregando el siguiente contenido\
`MAPBOX_DOWNLOADS_TOKEN=clave-secreta-mapbox`
2. En el fichero
`Retobici/plazalazojorge_2022_frontend/settings.gradle` se tiene
que agregar el repositorio Mapbox, agregando el siguiente contenido en el ficherp `setting.gradle`
```
cd existing_repo
git remote add origin https://gitlab.inf.uva.es/jorplaz/plazalazojorge_2022_frontend.git
git branch -M main
git push -uf origin main
repositories {
google()
mavenCentral()
maven {
url 'https://api.mapbox.com/downloads/v2/releases/maven'
authentication {
basic(BasicAuthentication)
}
credentials {
// Do not change the username below.
// This should always be `mapbox` (not your username).
username = "mapbox"
// Use the secret token you stored in gradle.properties as the password
password = "clave-secreta-mapbox"
}
}
maven { url 'https://jitpack.io' }
}
```
## Integrate with your tools
- [ ] [Set up project integrations](https://gitlab.inf.uva.es/jorplaz/plazalazojorge_2022_frontend/-/settings/integrations)
## Collaborate with your team
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
2. La clave pública se agrega en el fichero `res/values/mapbox_access_token.xml`
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License
For open source projects, say how it is licensed.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
Por último destacar que para que el Front End se comunique con el Back End es necesario que en el siguiente fichero:
`es/uva/retobici/frontend/core/di/NetworkModule.kt`
en el que se configura *Retrofit*, el cliente HTTP, configurar la misma dirección IP
que la que se use en el Back End. El código sería el siguiente:
```
@Singleton
@Provides
fun providesRetrofit():Retrofit{
return Retrofit.Builder()
.baseUrl("http://192.168.65.9:8000/api/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
```
\ No newline at end of file
......@@ -31,6 +31,9 @@ android {
}
kotlinOptions {
jvmTarget = '1.8'
freeCompilerArgs += [
"-Xopt-in=kotlin.RequiresOptIn"
]
}
buildFeatures {
......@@ -43,19 +46,19 @@ android {
dependencies {
// Mapbox Navigation SDK
implementation "com.mapbox.navigation:android:2.4.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.0"
implementation "androidx.core:core-ktx:1.7.0"
implementation "com.google.android.material:material:1.6.0"
implementation "androidx.appcompat:appcompat:1.4.1"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21"
implementation "androidx.core:core-ktx:1.8.0"
implementation "com.google.android.material:material:1.6.1"
implementation "androidx.appcompat:appcompat:1.4.2"
implementation "androidx.cardview:cardview:1.0.0"
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation "com.google.android.gms:play-services-location:19.0.1"
implementation "com.google.android.gms:play-services-location:20.0.0"
implementation "androidx.multidex:multidex:2.0.1"
implementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1"
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
implementation 'androidx.navigation:navigation-fragment-ktx:2.4.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.4.2'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
......@@ -67,25 +70,38 @@ dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1")
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1"
implementation 'androidx.annotation:annotation:1.4.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation 'com.mapbox.maps:android:10.4.0'
implementation 'com.mapbox.maps:android:10.5.0-beta.1'
implementation "com.mapbox.navigation:ui-dropin:2.5.0-alpha.3"
implementation("androidx.compose.material3:material3:1.0.0-alpha12")
implementation("androidx.compose.material3:material3-window-size-class:1.0.0-alpha12")
implementation("androidx.compose.material3:material3:1.0.0-alpha13")
implementation("androidx.compose.material3:material3-window-size-class:1.0.0-alpha13")
implementation "com.google.dagger:hilt-android:2.41"
kapt "com.google.dagger:hilt-android-compiler:2.41"
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1'
testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0"
testImplementation "io.mockk:mockk:1.12.2"
testImplementation "androidx.arch.core:core-testing:2.1.0"
//QR code scanner
implementation 'com.github.yuriy-budiyev:code-scanner:2.3.2'
implementation("androidx.cardview:cardview:1.0.0")
implementation 'com.github.bumptech.glide:glide:4.13.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.13.0'
implementation 'com.github.bumptech.glide:glide:4.13.1'
kapt 'com.github.bumptech.glide:compiler:4.13.0'
implementation("androidx.datastore:datastore-preferences:1.0.0")
implementation 'de.hdodenhof:circleimageview:3.1.0'
implementation 'androidx.core:core-splashscreen:1.0.0-rc01'
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.mapbox.navigation.examples">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature
android:name="android.hardware.camera"
android:required="false"/>
<uses-feature android:name="android.hardware.camera" android:required="false"/>
<application
android:usesCleartextTraffic="true"
android:name="es.uva.retobici.frontend.ApplicationHilt"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
android:theme="@style/Theme.App.Starting"
tools:targetApi="m">
<activity
android:name="es.uva.retobici.frontend.MasterActivity"
android:exported="false"
android:exported="true"
android:label="@string/title_activity_master"
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name="es.uva.retobici.frontend.MainActivity"
android:exported="true">
android:theme="@style/Theme.App.Starting">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="es.uva.retobici.frontend.fetchroute.FetchARouteActivity"
android:exported="false"
android:screenOrientation="fullSensor" />
<activity
android:name="es.uva.retobici.frontend.camera.ShowCameraTransitionsActivity"
android:exported="false"
android:screenOrientation="fullSensor" />
<activity
android:name="es.uva.retobici.frontend.turnbyturn.TurnByTurnExperienceActivity"
android:exported="false"
android:screenOrientation="fullSensor" />
<activity
android:name="es.uva.retobici.frontend.waypoints.MultipleWaypointsActivity"
android:exported="false"
android:screenOrientation="fullSensor" />
</application>
</manifest>
\ No newline at end of file
package es.uva.retobici.frontend
import android.content.Context
import androidx.core.content.ContextCompat
import com.mapbox.maps.R
import es.uva.retobici.frontend.camera.ShowCameraTransitionsActivity
import es.uva.retobici.frontend.fetchroute.FetchARouteActivity
import es.uva.retobici.frontend.waypoints.MultipleWaypointsActivity
import es.uva.retobici.frontend.turnbyturn.TurnByTurnExperienceActivity
fun Context.examplesList() = listOf(
MapboxExample(
ContextCompat.getDrawable(this, R.drawable.mapbox_logo_helmet),
getString(com.mapbox.navigation.examples.R.string.title_turn_by_turn),
getString(com.mapbox.navigation.examples.R.string.description_turn_by_turn),
TurnByTurnExperienceActivity::class.java
),
MapboxExample(
ContextCompat.getDrawable(this, R.drawable.mapbox_compass_icon),
getString(com.mapbox.navigation.examples.R.string.title_multiple_way_points),
getString(com.mapbox.navigation.examples.R.string.description_multiple_way_points),
MasterActivity::class.java
),
//hide
MapboxExample(
ContextCompat.getDrawable(this, R.drawable.mapbox_compass_icon),
getString(com.mapbox.navigation.examples.R.string.title_fetch_route),
getString(com.mapbox.navigation.examples.R.string.description_fetch_route),
FetchARouteActivity::class.java
),
//hide
MapboxExample(
ContextCompat.getDrawable(this, R.drawable.mapbox_compass_icon),
getString(com.mapbox.navigation.examples.R.string.title_camera_transitions),
getString(com.mapbox.navigation.examples.R.string.description_camera_transitions),
ShowCameraTransitionsActivity::class.java
),
//hide
MapboxExample(
ContextCompat.getDrawable(this, R.drawable.mapbox_compass_icon),
getString(com.mapbox.navigation.examples.R.string.title_multiple_way_points),
getString(com.mapbox.navigation.examples.R.string.description_multiple_way_points),
MultipleWaypointsActivity::class.java
),
)
package es.uva.retobici.frontend
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import com.mapbox.android.core.permissions.PermissionsListener
import com.mapbox.android.core.permissions.PermissionsManager
import com.mapbox.android.core.permissions.PermissionsManager.areLocationPermissionsGranted
import com.mapbox.navigation.examples.R
import com.mapbox.navigation.examples.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity(), PermissionsListener {
private val permissionsManager = PermissionsManager(this)
private lateinit var binding: ActivityMainBinding
private lateinit var examplesAdapter: MapboxExamplesAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!isMapboxTokenProvided()) {
showNoTokenErrorDialog()
return
}
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
if (areLocationPermissionsGranted(this)) {
requestStoragePermission()
} else {
permissionsManager.requestLocationPermissions(this)
}
bindExamples()
}
override fun onExplanationNeeded(permissionsToExplain: MutableList<String>?) {
Toast.makeText(
this,
"This app needs location and storage permissions in order to show its functionality.",
Toast.LENGTH_LONG
).show()
}
override fun onPermissionResult(granted: Boolean) {
if (granted) {
requestStoragePermission()
} else {
Toast.makeText(
this,
"You didn't grant the permissions required to use the app",
Toast.LENGTH_LONG
).show()
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
private fun requestStoragePermission() {
val permission = Manifest.permission.WRITE_EXTERNAL_STORAGE
val permissionsNeeded: MutableList<String> = ArrayList()
if (
ContextCompat.checkSelfPermission(this, permission) !=
PackageManager.PERMISSION_GRANTED
) {
permissionsNeeded.add(permission)
ActivityCompat.requestPermissions(
this,
permissionsNeeded.toTypedArray(),
10
)
}
}
private fun bindExamples() {
val examples = examplesList()
examplesAdapter = MapboxExamplesAdapter(examples) {
startActivity(Intent(this@MainActivity, examples[it].activity))
}
binding.examplesRecycler.apply {
layoutManager = LinearLayoutManager(
this@MainActivity,
LinearLayoutManager.VERTICAL,
false
)
adapter = examplesAdapter
}
}
private fun isMapboxTokenProvided() =
getString(R.string.mapbox_access_token) != MAPBOX_ACCESS_TOKEN_PLACEHOLDER
private fun showNoTokenErrorDialog() {
AlertDialog.Builder(this)
.setTitle(getString(R.string.noTokenDialogTitle))
.setMessage(getString(R.string.noTokenDialogBody))
.setCancelable(false)
.setPositiveButton("Ok") { _, _ ->
finish()
}
.show()
}
}
private const val MAPBOX_ACCESS_TOKEN_PLACEHOLDER = "YOUR_MAPBOX_ACCESS_TOKEN_GOES_HERE"
package es.uva.retobici.frontend
import android.graphics.drawable.Drawable
data class MapboxExample(
val image: Drawable?,
val title: String,
val description: String,
val activity: Class<*>
)
package es.uva.retobici.frontend
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.mapbox.navigation.examples.databinding.MapboxItemViewRecyclerBinding
class MapboxExamplesAdapter(
private val examplesList: List<MapboxExample>,
private val itemClickLambda: (position: Int) -> Unit
) : RecyclerView.Adapter<MapboxExamplesAdapter.MapboxExamplesViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MapboxExamplesViewHolder {
val binding = MapboxItemViewRecyclerBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return MapboxExamplesViewHolder(binding)
}
override fun getItemCount(): Int = examplesList.size
override fun onBindViewHolder(holder: MapboxExamplesViewHolder, position: Int) {
with(holder) { bindItem(examplesList[position]) }
}
inner class MapboxExamplesViewHolder(
private val viewBinding: MapboxItemViewRecyclerBinding
) : RecyclerView.ViewHolder(viewBinding.root) {
fun bindItem(example: MapboxExample) {
viewBinding.itemImage.setImageDrawable(example.image)
viewBinding.itemTitle.text = example.title
viewBinding.itemDescription.text = example.description
viewBinding.root.setOnClickListener {
itemClickLambda(layoutPosition)
}
}
}
}
......@@ -2,12 +2,13 @@ package es.uva.retobici.frontend
import android.Manifest
import android.content.pm.PackageManager
import android.location.Location
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.navigation.NavigationView
import androidx.navigation.findNavController
......@@ -19,28 +20,44 @@ import androidx.drawerlayout.widget.DrawerLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.get
import androidx.lifecycle.asLiveData
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import com.mapbox.android.core.location.LocationEngineProvider
import com.mapbox.android.core.permissions.PermissionsListener
import com.mapbox.android.core.permissions.PermissionsManager
import com.mapbox.navigation.examples.databinding.ActivityMasterBinding
import com.mapbox.navigation.examples.R
import dagger.hilt.android.AndroidEntryPoint
import es.uva.retobici.utilities.LocationListeningCallback
import es.uva.retobici.frontend.data.repositories.UserPreferences
import es.uva.retobici.frontend.ui.viewmodels.LoginViewModel
import javax.inject.Inject
@AndroidEntryPoint
class MasterActivity: AppCompatActivity(), PermissionsListener {
@Inject lateinit var userPreferences: UserPreferences
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var binding: ActivityMasterBinding
private val permissionsManager = PermissionsManager(this)
private val loginViewModel: LoginViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
super.onCreate(savedInstanceState)
if (!isMapboxTokenProvided()) {
showNoTokenErrorDialog()
return
}
binding = ActivityMasterBinding.inflate(layoutInflater)
setContentView(binding.root)
if (!PermissionsManager.areLocationPermissionsGranted(this)) {
permissionsManager.requestLocationPermissions(this)
}
setSupportActionBar(binding.appBarMaster.topAppBar)
binding.appBarMaster.progressIndicator.visibility = View.GONE
......@@ -54,17 +71,65 @@ class MasterActivity : AppCompatActivity(), PermissionsListener {
// menu should be considered as top level destinations.
appBarConfiguration = AppBarConfiguration(
setOf(
R.id.nav_home, R.id.nav_gallery, R.id.nav_gallery, R.id.nav_slideshow
R.id.nav_home, R.id.loginFragment
), drawerLayout
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
loginViewModel.logoutResult.observe(this){ logout ->
if (logout){
Snackbar.make(binding.root,
"Se ha cerrado sesión correctamente",
Snackbar.LENGTH_LONG)
.setAction("OK") {}
.show()
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.master, menu)
return true
userPreferences.email.asLiveData().observe(this){ email ->
val linearLayout = binding.navView.getHeaderView(0) as LinearLayout
val profileImage = linearLayout[0]
val userEmail = linearLayout[1] as TextView
val logout = binding.navView[1] as TextView
val login = binding.navView.menu.findItem(R.id.loginFragment)
if (email != null && email != "invalid") {
profileImage.visibility = View.VISIBLE
logout.visibility = View.VISIBLE
login.isVisible = false
userEmail.text = email
}else{
profileImage.visibility = View.GONE
logout.visibility = View.GONE
login.isVisible = true
userEmail.text = getString(R.string.User_not_loggedin)
}
}
userPreferences.points.asLiveData().observe(this){ points ->
val linearLayout = binding.navView.getHeaderView(0) as LinearLayout
val profileImage = linearLayout[0]
val userPoints = linearLayout[2] as TextView
val logout = binding.navView[1] as TextView
val login = binding.navView.menu.findItem(R.id.loginFragment)
if (points != -1 && points != null) {
profileImage.visibility = View.VISIBLE
logout.visibility = View.VISIBLE
login.isVisible = false
userPoints.text = "Puntos: $points"
}else{
profileImage.visibility = View.GONE
logout.visibility = View.GONE
login.isVisible = true
userPoints.text = getString(R.string.user_not_logged_desc)
}
}
val logoutButton = binding.navView[1] as TextView
logoutButton.setOnClickListener {
loginViewModel.logOut()
drawerLayout.close()
}
}
override fun onSupportNavigateUp(): Boolean {
......@@ -72,8 +137,6 @@ class MasterActivity : AppCompatActivity(), PermissionsListener {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
private val permissionsManager = PermissionsManager(this)
override fun onExplanationNeeded(permissionsToExplain: MutableList<String>?) {
Toast.makeText(
this,
......@@ -119,6 +182,20 @@ class MasterActivity : AppCompatActivity(), PermissionsListener {
}
}
private fun isMapboxTokenProvided() =
getString(R.string.mapbox_access_token) != MAPBOX_ACCESS_TOKEN_PLACEHOLDER
private fun showNoTokenErrorDialog() {
AlertDialog.Builder(this)
.setTitle(getString(R.string.noTokenDialogTitle))
.setMessage(getString(R.string.noTokenDialogBody))
.setCancelable(false)
.setPositiveButton("Ok") { _, _ ->
finish()
}
.show()
}
fun loading(visible: Boolean){
if (visible){
binding.appBarMaster.progressIndicator.visibility = View.VISIBLE
......@@ -127,3 +204,4 @@ class MasterActivity : AppCompatActivity(), PermissionsListener {
}
}
}
private const val MAPBOX_ACCESS_TOKEN_PLACEHOLDER = "YOUR_MAPBOX_ACCESS_TOKEN_GOES_HERE"
\ No newline at end of file
<resources>
<string name="title_camera_transitions">Use camera to frame the map</string>
<string name="description_camera_transitions">Use NavigationCamera to show various map camera transitions using live location and routing data.</string>
</resources>
package es.uva.retobici.frontend.core.di
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
import androidx.datastore.preferences.SharedPreferencesMigration
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.preferencesDataStoreFile
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import es.uva.retobici.frontend.core.utility.Constants.PREFERENCES_NAME
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object DataStore {
@Singleton
@Provides
fun provideDataStore(@ApplicationContext context: Context): DataStore<Preferences> {
return PreferenceDataStoreFactory.create(
corruptionHandler = ReplaceFileCorruptionHandler(produceNewData = { emptyPreferences() }),
migrations = listOf(SharedPreferencesMigration(context, PREFERENCES_NAME)),
scope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
produceFile = { context.preferencesDataStoreFile(PREFERENCES_NAME) }
)
}
}
package es.uva.retobici.frontend.core.di
import com.mapbox.maps.MapboxMap
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class) //activity
class MapsModule {
}
\ No newline at end of file
......@@ -4,10 +4,7 @@ import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import es.uva.retobici.frontend.data.source.api.BikeAPI
import es.uva.retobici.frontend.data.source.api.RewardAPI
import es.uva.retobici.frontend.data.source.api.RouteAPI
import es.uva.retobici.frontend.data.source.api.StopAPI
import es.uva.retobici.frontend.data.source.api.*
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Singleton
......@@ -21,7 +18,7 @@ class NetworkModule {
@Provides
fun providesRetrofit():Retrofit{
return Retrofit.Builder()
.baseUrl("https://c4804667-8963-4bac-9b82-cee4ef549e3a.mock.pstmn.io")
.baseUrl("http://192.168.1.15:8000/api/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
......@@ -61,4 +58,13 @@ class NetworkModule {
fun providesRewardApi(retrofit: Retrofit): RewardAPI {
return retrofit.create(RewardAPI::class.java)
}
/**
* This is provided because you can not Inject an interface
*/
@Singleton
@Provides
fun providesUserApi(retrofit: Retrofit): UserAPI {
return retrofit.create(UserAPI::class.java)
}
}
\ No newline at end of file
package es.uva.retobici.frontend.core.utility
object Constants {
const val PREFERENCES_NAME = "user"
}
\ No newline at end of file
package es.uva.retobici.frontend.core.utility
import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.*
class Timer {
private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.Default + job)
val seconds = MutableLiveData(0)
private fun startCoroutineTimer(delayMillis: Long = 0, repeatMillis: Long = 0, action: () -> Unit) = scope.launch(Dispatchers.IO) {
seconds.postValue(0)
delay(delayMillis)
if (repeatMillis > 0) {
while (true) {
action()
delay(repeatMillis)
}
} else {
action()
}
}
private val timer: Job = startCoroutineTimer(delayMillis = 0, repeatMillis = 1000) {
seconds.postValue(seconds.value!!.plus(1))
//Log.d("Timer", "Background - tick $seconds")
//doSomethingBackground()
/*
scope.launch(Dispatchers.Main) {
//Log.d("Timer", "Main thread - tick")
//doSomethingMainThread()
}
*/
}
fun cancelTimer() {
timer.cancel()
}
}
\ No newline at end of file
package es.uva.retobici.frontend.data.models
import es.uva.retobici.frontend.domain.model.Stop
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class StopProvider @Inject constructor(){
var stops:List<Stop> = emptyList()
/*
private val oldstops = listOf<Stop>(
Stop(1,-4.731,41.653,"Plaza Mayor"),
Stop(2,-4.726,41.652,"Teatro Calderon"),
Stop(3,-4.729,41.647,"Plaza Poniente"),
Stop(4,-4.725,41.648,"Plaza Mayor"),
)
fun getStops():List<Stop>{
return oldstops
}
*/
}
\ No newline at end of file
......@@ -3,22 +3,22 @@ package es.uva.retobici.frontend.data.repositories
import es.uva.retobici.frontend.data.source.api.BikeAPI
import es.uva.retobici.frontend.data.source.dto.toBikeModel
import es.uva.retobici.frontend.domain.model.Bike
import es.uva.retobici.frontend.domain.model.Route
import es.uva.retobici.frontend.domain.model.Stop
import es.uva.retobici.frontend.domain.repository.BikeRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import javax.inject.Inject
class BikeRemoteDataSource @Inject constructor(
private val api:BikeAPI
private val api:BikeAPI,
private val userPreferences: UserPreferences
): BikeRepository {
override suspend fun unlockBike(bike: Int): Bike {
return withContext(Dispatchers.IO){
val response = api.postUnlockBike(bike)
val token = userPreferences.authToken.first()
val response = api.postUnlockBike(bike, "Bearer ${token!!}")
// ?: if response is null empty list
//response.body()?.map { it.toStopModel() } ?: emptyList()
//TODO check the nullable response
response.body()!!.toBikeModel()
}
}
......
......
......@@ -5,25 +5,42 @@ import es.uva.retobici.frontend.data.source.dto.toRewardModel
import es.uva.retobici.frontend.domain.model.Reward
import es.uva.retobici.frontend.domain.repository.RewardRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import okhttp3.Dispatcher
import javax.inject.Inject
class RewardRemoteDataSource @Inject constructor(
private val api: RewardAPI
private val api: RewardAPI,
private val userPreferences: UserPreferences
): RewardRepository {
override suspend fun getAllRewards(): List<Reward> {
return withContext(Dispatchers.IO){
val response = api.getAllRewards()
response.body()?.map { it.toRewardModel() } ?: emptyList()
}
}
override suspend fun getUserRewards(): List<Reward> {
return withContext(Dispatchers.IO){
val authToken: String? = userPreferences.authToken.first()
val response = api.getUserRewards("Bearer $authToken")
response.body()?.map { it.toRewardModel() } ?: emptyList()
}
}
override suspend fun getUserNotRedeemed(): List<Reward> {
return withContext(Dispatchers.IO){
val authToken: String? = userPreferences.authToken.first()
val response = api.getUserNotRedeemed("Bearer $authToken")
response.body()?.map { it.toRewardModel() } ?: emptyList()
}
}
override suspend fun obtainReward(reward: Reward): Reward {
return withContext(Dispatchers.IO){
val response = api.postObtainReward(reward.id)
val authToken: String? = userPreferences.authToken.first()
val response = api.postObtainReward("Bearer $authToken", reward.id)
response.body()!!.toRewardModel()
}
}
......
......
package es.uva.retobici.frontend.data.repositories
import android.util.Log
import es.uva.retobici.frontend.data.source.api.RouteAPI
import es.uva.retobici.frontend.data.source.dto.toRouteModel
import es.uva.retobici.frontend.domain.model.Bike
import es.uva.retobici.frontend.domain.model.Route
import es.uva.retobici.frontend.domain.repository.RouteRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import javax.inject.Inject
class RouteRemoteDataSource @Inject constructor(
private val api:RouteAPI
private val api:RouteAPI,
private val userPreferences: UserPreferences
): RouteRepository {
override suspend fun startRoute(bike: Bike): Route {
return withContext(Dispatchers.IO){
val response = api.postStartRoute(1, bike.id)
val token = userPreferences.authToken.first()
val response = api.postStartRoute("Bearer $token",bike.bike_id)
response.body()!!.toRouteModel()
}
}
override suspend fun finishRoute(route: Route): Route {
return withContext(Dispatchers.IO){
val response = api.postFinishRoute(route.id, route)
// ?: if response is null empty list
//response.body()?.map { it.toStopModel() } ?: emptyList()
//TODO check the nullable response
val token = userPreferences.authToken.first()
Log.d("calor", route.toString())
val response = api.postFinishRoute("Bearer $token", route)
Log.d("calor", response.toString())
response.body()!!.toRouteModel()
}
}
......
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment