diff --git a/app/build.gradle b/app/build.gradle index 64e302b9a..8c0570960 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,8 +15,8 @@ android { defaultConfig { applicationId "org.rfcx.ranger" manifestPlaceholders = [auth0Domain: "@string/auth0_domain", auth0Scheme: "@string/auth0_scheme"] - versionCode 84 - versionName "1.2.5" + versionCode 87 + versionName "1.2.6" minSdkVersion 19 targetSdkVersion 29 multiDexEnabled true diff --git a/app/src/main/java/org/rfcx/ranger/RangerApplication.kt b/app/src/main/java/org/rfcx/ranger/RangerApplication.kt index f4fe0de19..fcf87a2dd 100644 --- a/app/src/main/java/org/rfcx/ranger/RangerApplication.kt +++ b/app/src/main/java/org/rfcx/ranger/RangerApplication.kt @@ -15,6 +15,7 @@ import org.koin.core.context.startKoin import org.koin.core.module.Module import org.rfcx.ranger.di.DataModule import org.rfcx.ranger.di.UiModule +import org.rfcx.ranger.service.CleanupAudioCacheWorker import org.rfcx.ranger.service.LocationCleanupWorker import org.rfcx.ranger.service.ReportCleanupWorker import org.rfcx.ranger.util.RealmHelper @@ -36,6 +37,7 @@ class RangerApplication : MultiDexApplication() { setupKoin() ReportCleanupWorker.enqueuePeriodically() LocationCleanupWorker.enqueuePeriodically() + CleanupAudioCacheWorker.enqueuePeriodically() if (BuildConfig.USE_STETHO) { Stetho.initialize(Stetho.newInitializerBuilder(this) diff --git a/app/src/main/java/org/rfcx/ranger/data/local/EventDb.kt b/app/src/main/java/org/rfcx/ranger/data/local/EventDb.kt index f80e732d9..e0afeb13b 100644 --- a/app/src/main/java/org/rfcx/ranger/data/local/EventDb.kt +++ b/app/src/main/java/org/rfcx/ranger/data/local/EventDb.kt @@ -38,6 +38,7 @@ class EventDb(val realm: Realm) { realm.use { it -> it.executeTransaction { val events = it.where(Event::class.java) + .sort("beginsAt",Sort.DESCENDING) .findAll() list.addAll(it.copyFromRealm(events)) } diff --git a/app/src/main/java/org/rfcx/ranger/data/remote/service/ServiceFactory.kt b/app/src/main/java/org/rfcx/ranger/data/remote/service/ServiceFactory.kt index c320a2c9e..be85a6531 100644 --- a/app/src/main/java/org/rfcx/ranger/data/remote/service/ServiceFactory.kt +++ b/app/src/main/java/org/rfcx/ranger/data/remote/service/ServiceFactory.kt @@ -18,6 +18,7 @@ import org.rfcx.ranger.data.remote.setusername.SetNameEndpoint import org.rfcx.ranger.data.remote.shortlink.ShortLinkEndpoint import org.rfcx.ranger.data.remote.site.SiteEndpoint import org.rfcx.ranger.data.remote.subscribe.SubscribeEndpoint +import org.rfcx.ranger.data.remote.terms.TermsEndpoint import org.rfcx.ranger.data.remote.usertouch.UserTouchEndPoint import org.rfcx.ranger.util.GsonProvider import org.rfcx.ranger.util.ImprovedDateTypeAdapter @@ -101,6 +102,12 @@ object ServiceFactory { .create(SubscribeEndpoint::class.java) } + fun makeTermsService(isDebug: Boolean, context: Context): TermsEndpoint { + return createRetrofit(BuildConfig.RANGER_DOMAIN, createAuthTokenOkHttpClient(isDebug, AuthTokenInterceptor(context)), + GsonProvider.getInstance().gson) + .create(TermsEndpoint::class.java) + } + private fun createRetrofit(baseUrl: String, okHttpClient: OkHttpClient, gson: Gson): Retrofit { return Retrofit.Builder().baseUrl(baseUrl) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) diff --git a/app/src/main/java/org/rfcx/ranger/data/remote/terms/TermsEndpoint.kt b/app/src/main/java/org/rfcx/ranger/data/remote/terms/TermsEndpoint.kt new file mode 100644 index 000000000..3cc103161 --- /dev/null +++ b/app/src/main/java/org/rfcx/ranger/data/remote/terms/TermsEndpoint.kt @@ -0,0 +1,12 @@ +package org.rfcx.ranger.data.remote.terms + +import io.reactivex.Single +import org.rfcx.ranger.entity.terms.TermsRequest +import org.rfcx.ranger.entity.terms.TermsResponse +import retrofit2.http.Body +import retrofit2.http.POST + +interface TermsEndpoint { + @POST("v1/users/accept-terms") + fun sendPayload(@Body body: TermsRequest): Single +} \ No newline at end of file diff --git a/app/src/main/java/org/rfcx/ranger/data/remote/terms/TermsRepository.kt b/app/src/main/java/org/rfcx/ranger/data/remote/terms/TermsRepository.kt new file mode 100644 index 000000000..ecf640291 --- /dev/null +++ b/app/src/main/java/org/rfcx/ranger/data/remote/terms/TermsRepository.kt @@ -0,0 +1,9 @@ +package org.rfcx.ranger.data.remote.terms + +import io.reactivex.Single +import org.rfcx.ranger.entity.terms.TermsRequest +import org.rfcx.ranger.entity.terms.TermsResponse + +interface TermsRepository { + fun sendBodyPayload(sendBody: TermsRequest): Single +} \ No newline at end of file diff --git a/app/src/main/java/org/rfcx/ranger/data/remote/terms/TermsRepositoryImp.kt b/app/src/main/java/org/rfcx/ranger/data/remote/terms/TermsRepositoryImp.kt new file mode 100644 index 000000000..187976f79 --- /dev/null +++ b/app/src/main/java/org/rfcx/ranger/data/remote/terms/TermsRepositoryImp.kt @@ -0,0 +1,11 @@ +package org.rfcx.ranger.data.remote.terms + +import io.reactivex.Single +import org.rfcx.ranger.entity.terms.TermsRequest +import org.rfcx.ranger.entity.terms.TermsResponse + +class TermsRepositoryImp(private val termsEndpoint: TermsEndpoint) : TermsRepository { + override fun sendBodyPayload(sendBody: TermsRequest): Single { + return termsEndpoint.sendPayload(sendBody) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/rfcx/ranger/data/remote/terms/TermsUseCase.kt b/app/src/main/java/org/rfcx/ranger/data/remote/terms/TermsUseCase.kt new file mode 100644 index 000000000..67ae01425 --- /dev/null +++ b/app/src/main/java/org/rfcx/ranger/data/remote/terms/TermsUseCase.kt @@ -0,0 +1,16 @@ +package org.rfcx.ranger.data.remote.terms + +import io.reactivex.Single +import org.rfcx.ranger.data.remote.domain.SingleUseCase +import org.rfcx.ranger.data.remote.domain.executor.PostExecutionThread +import org.rfcx.ranger.data.remote.domain.executor.ThreadExecutor +import org.rfcx.ranger.entity.terms.TermsRequest +import org.rfcx.ranger.entity.terms.TermsResponse + +class TermsUseCase(private val termsRepository: TermsRepository, + threadExecutor: ThreadExecutor, postExecutionThread: PostExecutionThread +) : SingleUseCase(threadExecutor, postExecutionThread) { + override fun buildUseCaseObservable(params: TermsRequest): Single { + return termsRepository.sendBodyPayload(params) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/rfcx/ranger/di/DataModule.kt b/app/src/main/java/org/rfcx/ranger/di/DataModule.kt index 55ff56cba..fbd44af8a 100644 --- a/app/src/main/java/org/rfcx/ranger/di/DataModule.kt +++ b/app/src/main/java/org/rfcx/ranger/di/DataModule.kt @@ -50,6 +50,9 @@ import org.rfcx.ranger.data.remote.subscribe.SubscribeUseCase import org.rfcx.ranger.data.remote.subscribe.unsubscribe.UnsubscribeRepository import org.rfcx.ranger.data.remote.subscribe.unsubscribe.UnsubscribeRepositoryImp import org.rfcx.ranger.data.remote.subscribe.unsubscribe.UnsubscribeUseCase +import org.rfcx.ranger.data.remote.terms.TermsRepository +import org.rfcx.ranger.data.remote.terms.TermsRepositoryImp +import org.rfcx.ranger.data.remote.terms.TermsUseCase import org.rfcx.ranger.data.remote.usertouch.CheckUserTouchUseCase import org.rfcx.ranger.data.remote.usertouch.UserTouchRepository import org.rfcx.ranger.data.remote.usertouch.UserTouchRepositoryImp @@ -110,6 +113,9 @@ object DataModule { single { UnsubscribeRepositoryImp(get()) } bind UnsubscribeRepository::class single { UnsubscribeUseCase(get(), get(), get()) } + single { TermsRepositoryImp(get()) } bind TermsRepository::class + single { TermsUseCase(get(), get(), get()) } + } val remoteModule = module { @@ -125,6 +131,7 @@ object DataModule { factory { ServiceFactory.makePasswordService(BuildConfig.DEBUG, androidContext()) } factory { ServiceFactory.makeProfilePhotoService(BuildConfig.DEBUG, androidContext()) } factory { ServiceFactory.makeSubscribeService(BuildConfig.DEBUG, androidContext()) } + factory { ServiceFactory.makeTermsService(BuildConfig.DEBUG, androidContext()) } } val localModule = module { diff --git a/app/src/main/java/org/rfcx/ranger/di/UiModule.kt b/app/src/main/java/org/rfcx/ranger/di/UiModule.kt index b5f354ab9..597d86414 100644 --- a/app/src/main/java/org/rfcx/ranger/di/UiModule.kt +++ b/app/src/main/java/org/rfcx/ranger/di/UiModule.kt @@ -14,6 +14,7 @@ import org.rfcx.ranger.view.alerts.guardian.alertType.AlertValueViewModel import org.rfcx.ranger.view.login.InvitationCodeViewModel import org.rfcx.ranger.view.login.LoginViewModel import org.rfcx.ranger.view.login.SetUserNameViewModel +import org.rfcx.ranger.view.login.TermsAndServiceViewModel import org.rfcx.ranger.view.map.MapDetailViewModel import org.rfcx.ranger.view.map.MapViewModel import org.rfcx.ranger.view.map.ReportViewPagerFragmentViewModel @@ -67,5 +68,6 @@ object UiModule { viewModel { LoginViewModel(androidContext(), get()) } viewModel { InvitationCodeViewModel(androidContext(), get()) } viewModel { SetUserNameViewModel(androidContext(), get()) } + viewModel { TermsAndServiceViewModel(androidContext(), get()) } } } \ No newline at end of file diff --git a/app/src/main/java/org/rfcx/ranger/entity/terms/Terms.kt b/app/src/main/java/org/rfcx/ranger/entity/terms/Terms.kt new file mode 100644 index 000000000..76870774d --- /dev/null +++ b/app/src/main/java/org/rfcx/ranger/entity/terms/Terms.kt @@ -0,0 +1,12 @@ +package org.rfcx.ranger.entity.terms + +import com.google.gson.annotations.SerializedName + +open class TermsRequest( + val app: String +) + +open class TermsResponse( + @SerializedName("success") + val success: Boolean +) \ No newline at end of file diff --git a/app/src/main/java/org/rfcx/ranger/entity/user/InvitationCodeRequest.kt b/app/src/main/java/org/rfcx/ranger/entity/user/InvitationCodeRequest.kt index 460a8ca8c..0f8fdbace 100644 --- a/app/src/main/java/org/rfcx/ranger/entity/user/InvitationCodeRequest.kt +++ b/app/src/main/java/org/rfcx/ranger/entity/user/InvitationCodeRequest.kt @@ -4,5 +4,11 @@ import com.google.gson.annotations.SerializedName data class InvitationCodeRequest( @SerializedName("code") - val code: String + val code: String, + + @SerializedName("accept_terms") + val acceptTerms: String, + + @SerializedName("app") + val app: String ) \ No newline at end of file diff --git a/app/src/main/java/org/rfcx/ranger/entity/user/UserAuthResponse.kt b/app/src/main/java/org/rfcx/ranger/entity/user/UserAuthResponse.kt index 77bb55742..fe3213bf3 100644 --- a/app/src/main/java/org/rfcx/ranger/entity/user/UserAuthResponse.kt +++ b/app/src/main/java/org/rfcx/ranger/entity/user/UserAuthResponse.kt @@ -6,7 +6,7 @@ package org.rfcx.ranger.entity.user data class UserAuthResponse (val guid: String, val email: String?, val nickname: String?, val idToken: String, val accessToken: String?, val refreshToken: String?, - val roles: Set = setOf(), val accessibleSites: Set = setOf(), val defaultSite: String? = null, val picture: String?) { + val roles: Set = setOf(), val accessibleSites: Set = setOf(), val defaultSite: String? = null, val picture: String?, val consentGivenRangerApp: String?) { val isRanger: Boolean get() = roles.contains("rfcxUser") && defaultSite != null diff --git a/app/src/main/java/org/rfcx/ranger/service/CleanupAudioCacheWorker.kt b/app/src/main/java/org/rfcx/ranger/service/CleanupAudioCacheWorker.kt new file mode 100644 index 000000000..c19e60e19 --- /dev/null +++ b/app/src/main/java/org/rfcx/ranger/service/CleanupAudioCacheWorker.kt @@ -0,0 +1,74 @@ +package org.rfcx.ranger.service + +import android.content.Context +import androidx.lifecycle.LiveData +import androidx.work.* +import java.io.File +import java.util.* +import java.util.concurrent.TimeUnit + +class CleanupAudioCacheWorker(val context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) { + + private val audioDirectory = File(context.cacheDir, DownLoadEventWorker.AUDIOS_SUB_DIRECTORY) + + override fun doWork(): Result { + removeAudioCache() + removeAudioInCache(applicationContext) + return Result.success() + } + + /* + /* for version higher or equal 1.2.6 + remove .opus all opus in 'audios' cache directory + */ + */ + private fun removeAudioCache() { + val date = Date(System.currentTimeMillis()) + audioDirectory.listFiles()?.forEach { + if (it.isFile + && (it.name.substring(it.name.lastIndexOf(".") + 1) == "opus") + && it.isLastModifiedOlderThan2Weeks(date)) { + try { + it.delete() + } catch (e: Exception) { + e.printStackTrace() + } + } + } + } + + /* for old version below 1.2.6 + remove .opus all opus in main cache directory + */ + private fun removeAudioInCache(context: Context) { + val date = Date(System.currentTimeMillis()) + context.cacheDir.listFiles()?.forEach { + if (it.isFile && it.isLastModifiedOlderThan2Weeks(date)) { + try { + it.delete() + } catch (e: Exception) { + e.printStackTrace() + } + } + } + } + + private fun File.isLastModifiedOlderThan2Weeks(date: Date): Boolean { + return (date.time - this.lastModified()) > TWO_WEEKS + } + + companion object { + const val TWO_WEEKS: Long = 60 * 1000 * 60 * 24 * 14 + private const val UNIQUE_WORK_KEY = "CleanupAudioCacheWorker" + const val AUDIOS_SUB_DIRECTORY = "audios" + + fun enqueuePeriodically() { + val workRequest = PeriodicWorkRequestBuilder(1, TimeUnit.DAYS).build() + WorkManager.getInstance().enqueueUniquePeriodicWork(UNIQUE_WORK_KEY, ExistingPeriodicWorkPolicy.REPLACE, workRequest) + } + + fun workInfos(): LiveData> { + return WorkManager.getInstance().getWorkInfosForUniqueWorkLiveData(UNIQUE_WORK_KEY) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/rfcx/ranger/service/DownLoadEventWorker.kt b/app/src/main/java/org/rfcx/ranger/service/DownLoadEventWorker.kt index ba58c2701..db776050f 100644 --- a/app/src/main/java/org/rfcx/ranger/service/DownLoadEventWorker.kt +++ b/app/src/main/java/org/rfcx/ranger/service/DownLoadEventWorker.kt @@ -13,6 +13,7 @@ import okio.sink import org.rfcx.ranger.BuildConfig import org.rfcx.ranger.data.local.EventDb import org.rfcx.ranger.entity.event.Event +import org.rfcx.ranger.service.CleanupAudioCacheWorker.Companion.TWO_WEEKS import org.rfcx.ranger.util.RealmHelper import retrofit2.Call import retrofit2.Response @@ -21,25 +22,32 @@ import retrofit2.http.GET import retrofit2.http.Url import java.io.File import java.io.IOException +import java.util.* import java.util.concurrent.TimeUnit class DownLoadEventWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) { - + private val audioDirectory = File(context.cacheDir, AUDIOS_SUB_DIRECTORY) private val needTobeDownloadEvent = arrayListOf() override fun doWork(): Result { Log.d(TAG, "doWork") + + if (!audioDirectory.exists()) { + audioDirectory.mkdir() + } + val nowDate = Date(System.currentTimeMillis()) val eventDb = EventDb(Realm.getInstance(RealmHelper.migrationConfig())) val events = eventDb.getEventsSync() needTobeDownloadEvent.clear() for (event in events) { - val file = File(applicationContext.cacheDir, "${event.audioId}.opus") - if (!file.exists()) { - needTobeDownloadEvent.add(event) + if ((nowDate.time - event.beginsAt.time) <= TWO_WEEKS) { + val file = File(audioDirectory, "${event.audioId}.opus") + if (!file.exists()) + needTobeDownloadEvent.add(event) + } else { + break } } - startDownload() - return if (needTobeDownloadEvent.isNotEmpty()) Result.retry() else Result.success() } @@ -69,8 +77,8 @@ class DownLoadEventWorker(context: Context, workerParams: WorkerParameters) : Wo private fun saveFile(context: Context, response: Response, fileName: String, callback: (Boolean) -> Unit) { - val temp = File(context.cacheDir, "$fileName _temp") - val file = File(context.cacheDir, fileName) + val temp = File(audioDirectory, "$fileName _temp") + val file = File(audioDirectory, fileName) if (file.exists()) { callback.invoke(true) @@ -106,7 +114,7 @@ class DownLoadEventWorker(context: Context, workerParams: WorkerParameters) : Wo companion object { private const val TAG = "DownLoadEventWorker" private const val UNIQUE_WORK_KEY = "DownLoadEventWorkerUniqueKey" - + const val AUDIOS_SUB_DIRECTORY = "audios" fun enqueue() { val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) diff --git a/app/src/main/java/org/rfcx/ranger/util/CredentialKeeper.kt b/app/src/main/java/org/rfcx/ranger/util/CredentialKeeper.kt index 3a354c71e..9bc72b83a 100644 --- a/app/src/main/java/org/rfcx/ranger/util/CredentialKeeper.kt +++ b/app/src/main/java/org/rfcx/ranger/util/CredentialKeeper.kt @@ -31,6 +31,9 @@ class CredentialKeeper(val context: Context) { if (user.picture != null) { preferences.putString(Preferences.IMAGE_PROFILE, user.picture) } + if (user.consentGivenRangerApp != null) { + preferences.putBoolean(Preferences.CONSENT_GIVEN, true) + } preferences.putStringSet(Preferences.ROLES, user.roles) preferences.putStringSet(Preferences.ACCESSIBLE_SITES, user.accessibleSites) if (user.defaultSite != null) { diff --git a/app/src/main/java/org/rfcx/ranger/util/CredentialVerifier.kt b/app/src/main/java/org/rfcx/ranger/util/CredentialVerifier.kt index 9b05d227c..67e6403ec 100644 --- a/app/src/main/java/org/rfcx/ranger/util/CredentialVerifier.kt +++ b/app/src/main/java/org/rfcx/ranger/util/CredentialVerifier.kt @@ -24,6 +24,7 @@ class CredentialVerifier(val context: Context) { // Parsing JWT Token val metaDataKey = getString(R.string.auth0_metadata_key) + val userMetaDataKey = getString(R.string.auth0_user_metadata_key) val withoutSignature = token.substring(0, token.lastIndexOf('.') + 1) try { val untrusted = Jwts.parser().parseClaimsJwt(withoutSignature) @@ -64,12 +65,26 @@ class CredentialVerifier(val context: Context) { } } + val userUntrusted = Jwts.parser().parseClaimsJwt(withoutSignature) + var consentGivenRangerApp: String? = null + + if (userUntrusted.body[userMetaDataKey] == null) { + consentGivenRangerApp = null + } + + val userMetadata = userUntrusted.body[userMetaDataKey] + if (userMetadata != null) { + if ((userMetadata is HashMap<*, *>)) { + consentGivenRangerApp = userMetadata["consentGivenRangerApp"] as String? + } + } + when { guid.isNullOrEmpty() -> { return Err(getString(R.string.an_error_occurred)) } else -> { - return Ok(UserAuthResponse(guid, email, nickname, token, credentials.accessToken, credentials.refreshToken, roles, accessibleSites, defaultSite, picture)) + return Ok(UserAuthResponse(guid, email, nickname, token, credentials.accessToken, credentials.refreshToken, roles, accessibleSites, defaultSite, picture, consentGivenRangerApp)) } } } catch (e: Exception) { diff --git a/app/src/main/java/org/rfcx/ranger/util/Preferences.kt b/app/src/main/java/org/rfcx/ranger/util/Preferences.kt index dafcba503..2fde6f899 100644 --- a/app/src/main/java/org/rfcx/ranger/util/Preferences.kt +++ b/app/src/main/java/org/rfcx/ranger/util/Preferences.kt @@ -43,6 +43,8 @@ class Preferences(context: Context) { const val IMAGE_PROFILE = "${PREFIX}IMAGE_PROFILE" const val EMAIL_SUBSCRIBE = "${PREFIX}EMAIL_SUBSCRIBE" const val COORDINATES_FORMAT = "${PREFIX}COORDINATES_FORMAT" + const val CONSENT_GIVEN = "${PREFIX}CONSENT_GIVEN" + const val IS_RANGER = "${PREFIX}IS_RANGER" } init { diff --git a/app/src/main/java/org/rfcx/ranger/view/alert/AlertBottomDialogViewModel.kt b/app/src/main/java/org/rfcx/ranger/view/alert/AlertBottomDialogViewModel.kt index a15765e61..58ea5b4dc 100644 --- a/app/src/main/java/org/rfcx/ranger/view/alert/AlertBottomDialogViewModel.kt +++ b/app/src/main/java/org/rfcx/ranger/view/alert/AlertBottomDialogViewModel.kt @@ -24,6 +24,7 @@ import org.rfcx.ranger.entity.event.Confidence import org.rfcx.ranger.entity.event.Event import org.rfcx.ranger.entity.event.EventReview import org.rfcx.ranger.entity.event.ReviewEventFactory +import org.rfcx.ranger.service.CleanupAudioCacheWorker.Companion.AUDIOS_SUB_DIRECTORY import org.rfcx.ranger.service.ReviewEventSyncWorker import org.rfcx.ranger.util.NetworkNotConnection import org.rfcx.ranger.util.getNameEmail @@ -146,8 +147,8 @@ class AlertBottomDialogViewModel(private val context: Context, } else { audioUrl } - - val audioFile = File(context.cacheDir, "${eventResult?.audioId}.opus") + val audiosDirectory = File(context.cacheDir, AUDIOS_SUB_DIRECTORY) + val audioFile = File(audiosDirectory, "${eventResult?.audioId}.opus") val mediaSource = if (audioFile.exists()) { ExtractorMediaSource.Factory(descriptorFactory).createMediaSource(Uri.fromFile(audioFile)) } else { @@ -236,7 +237,7 @@ class AlertBottomDialogViewModel(private val context: Context, } eventDb.saveEvent(event) _reviewEvent.value = Result.Success(Pair(event, requests)) - _eventState.value = EventState.REVIEWED // invoke state to review + _eventState.value = EventState.REVIEWED // invoke state to review } }, requests) diff --git a/app/src/main/java/org/rfcx/ranger/view/login/InvitationCodeViewModel.kt b/app/src/main/java/org/rfcx/ranger/view/login/InvitationCodeViewModel.kt index 19dd9bd8b..edf8a1a26 100644 --- a/app/src/main/java/org/rfcx/ranger/view/login/InvitationCodeViewModel.kt +++ b/app/src/main/java/org/rfcx/ranger/view/login/InvitationCodeViewModel.kt @@ -62,7 +62,7 @@ class InvitationCodeViewModel(private val context: Context, private val sendInvi override fun onError(e: Throwable) { callback(false) } - }, InvitationCodeRequest(code)) + }, InvitationCodeRequest(code, USER_ACCEPTED, APP_NAME)) } private fun refreshToken(callback: (Boolean) -> Unit) { @@ -94,6 +94,11 @@ class InvitationCodeViewModel(private val context: Context, private val sendInvi } }) } + + companion object { + const val USER_ACCEPTED = "true" + const val APP_NAME = "RangerApp" + } } enum class SubmitState { diff --git a/app/src/main/java/org/rfcx/ranger/view/login/LoginActivityNew.kt b/app/src/main/java/org/rfcx/ranger/view/login/LoginActivityNew.kt index ed1b6eb34..69e08a8dc 100644 --- a/app/src/main/java/org/rfcx/ranger/view/login/LoginActivityNew.kt +++ b/app/src/main/java/org/rfcx/ranger/view/login/LoginActivityNew.kt @@ -33,7 +33,15 @@ class LoginActivityNew : BaseActivity(), LoginListener { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login_new) val eventGuId = getEventFromIntentIfHave(intent) - if (CredentialKeeper(this).hasValidCredentials() && getSiteName().isNotEmpty() && getUserNickname().substring(0, 1) != "+") { + + val preferenceHelper = Preferences.getInstance(this) + val isConsentGiven = preferenceHelper.getBoolean(Preferences.CONSENT_GIVEN,false) + + if(CredentialKeeper(this).hasValidCredentials() && !isConsentGiven) { + supportFragmentManager.beginTransaction() + .add(loginContainer.id, TermsAndServiceFragment(), + "TermsAndServiceFragment").commit() + } else if (CredentialKeeper(this).hasValidCredentials() && getSiteName().isNotEmpty() && getUserNickname().substring(0, 1) != "+") { MainActivityNew.startActivity(this@LoginActivityNew, eventGuId) finish() } else { @@ -77,6 +85,13 @@ class LoginActivityNew : BaseActivity(), LoginListener { } + override fun openTermsAndServiceFragment() { + supportFragmentManager.beginTransaction() + .replace(loginContainer.id, TermsAndServiceFragment(), + "TermsAndServiceFragment").commit() + + } + private fun getEventFromIntentIfHave(intent: Intent?) :String? { if (intent?.hasExtra("event_guid") == true) { return intent.getStringExtra("event_guid") @@ -89,4 +104,5 @@ interface LoginListener { fun openMain() fun openInvitationCodeFragment() fun openSetUserNameFragmentFragment() + fun openTermsAndServiceFragment() } diff --git a/app/src/main/java/org/rfcx/ranger/view/login/LoginFragment.kt b/app/src/main/java/org/rfcx/ranger/view/login/LoginFragment.kt index 824cfe5eb..fd8db0b7d 100644 --- a/app/src/main/java/org/rfcx/ranger/view/login/LoginFragment.kt +++ b/app/src/main/java/org/rfcx/ranger/view/login/LoginFragment.kt @@ -90,8 +90,8 @@ class LoginFragment : BaseFragment() { loginViewModel.redirectPage.observe(this, Observer { loginRedirect -> when (loginRedirect) { LoginRedirect.MAIN_PAGE -> listener.openMain() - LoginRedirect.INVITE_CODE_PAGE -> listener.openInvitationCodeFragment() LoginRedirect.SET_USER_NAME -> listener.openSetUserNameFragmentFragment() + LoginRedirect.TERMS_AND_SERVICE -> listener.openTermsAndServiceFragment() else -> loading(false) } }) diff --git a/app/src/main/java/org/rfcx/ranger/view/login/LoginViewModel.kt b/app/src/main/java/org/rfcx/ranger/view/login/LoginViewModel.kt index d073adfe1..25fb593e1 100644 --- a/app/src/main/java/org/rfcx/ranger/view/login/LoginViewModel.kt +++ b/app/src/main/java/org/rfcx/ranger/view/login/LoginViewModel.kt @@ -159,18 +159,25 @@ class LoginViewModel(private val context: Context, private val checkUserTouchUse // } fun checkUserDetail(userAuthResponse: UserAuthResponse) { + val preferenceHelper = Preferences.getInstance(context) CredentialKeeper(context).save(userAuthResponse) checkUserTouchUseCase.execute(object : DisposableSingleObserver() { override fun onSuccess(t: Boolean) { + val isConsentGiven = preferenceHelper.getBoolean(Preferences.CONSENT_GIVEN) + if (userAuthResponse.isRanger) { - if (context.getUserNickname().substring(0, 1) == "+") { + preferenceHelper.putBoolean(Preferences.IS_RANGER, true) + if (!isConsentGiven) { + _redirectPage.postValue(LoginRedirect.TERMS_AND_SERVICE) + } else if (context.getUserNickname().substring(0, 1) == "+") { _redirectPage.postValue(LoginRedirect.SET_USER_NAME) } else { _redirectPage.postValue(LoginRedirect.MAIN_PAGE) } } else { - _redirectPage.postValue(LoginRedirect.INVITE_CODE_PAGE) + preferenceHelper.putBoolean(Preferences.IS_RANGER, false) + _redirectPage.postValue(LoginRedirect.TERMS_AND_SERVICE) } } @@ -183,5 +190,5 @@ class LoginViewModel(private val context: Context, private val checkUserTouchUse } enum class LoginRedirect { - MAIN_PAGE, INVITE_CODE_PAGE, SET_USER_NAME + MAIN_PAGE, SET_USER_NAME, TERMS_AND_SERVICE } \ No newline at end of file diff --git a/app/src/main/java/org/rfcx/ranger/view/login/TermsAndServiceFragment.kt b/app/src/main/java/org/rfcx/ranger/view/login/TermsAndServiceFragment.kt new file mode 100644 index 000000000..d4d12c208 --- /dev/null +++ b/app/src/main/java/org/rfcx/ranger/view/login/TermsAndServiceFragment.kt @@ -0,0 +1,69 @@ +package org.rfcx.ranger.view.login + + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.Observer +import kotlinx.android.synthetic.main.fragment_terms_and_service.* +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.rfcx.ranger.R +import org.rfcx.ranger.data.remote.success +import org.rfcx.ranger.util.Preferences +import org.rfcx.ranger.view.MainActivityNew +import org.rfcx.ranger.view.base.BaseFragment + +class TermsAndServiceFragment : BaseFragment() { + lateinit var listener: LoginListener + private val termsAndServiceViewModel: TermsAndServiceViewModel by viewModel() + + override fun onAttach(context: Context) { + super.onAttach(context) + listener = (context as LoginListener) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_terms_and_service, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + termsAndConditionsWebview.loadUrl("https://rfcx.org/terms-of-service-ranger-app-text-only") + + checkBox.setOnClickListener { + submitButton.isEnabled = checkBox.isChecked + } + + submitButton.setOnClickListener { + val preferenceHelper = context?.let { it1 -> Preferences.getInstance(it1) } + val isRanger = preferenceHelper?.getBoolean(Preferences.IS_RANGER, false) + + if (isRanger != null) { + if(isRanger){ + termsAndServiceViewModel.acceptTerms() + } else { + listener.openInvitationCodeFragment() + } + } + + termsAndServiceViewModel.consentGivenState.observe(this, Observer { + it.success({ state -> + if (state) { + context?.let { it1 -> MainActivityNew.startActivity(it1, null) } + } + }, { + termsProgressBar.visibility = View.GONE + submitButton.visibility = View.VISIBLE + + }, { + termsProgressBar.visibility = View.VISIBLE + submitButton.visibility = View.INVISIBLE + }) + }) + } + } +} diff --git a/app/src/main/java/org/rfcx/ranger/view/login/TermsAndServiceViewModel.kt b/app/src/main/java/org/rfcx/ranger/view/login/TermsAndServiceViewModel.kt new file mode 100644 index 000000000..271052b24 --- /dev/null +++ b/app/src/main/java/org/rfcx/ranger/view/login/TermsAndServiceViewModel.kt @@ -0,0 +1,89 @@ +package org.rfcx.ranger.view.login + +import android.content.Context +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.auth0.android.Auth0 +import com.auth0.android.authentication.AuthenticationAPIClient +import com.auth0.android.authentication.AuthenticationException +import com.auth0.android.callback.BaseCallback +import com.auth0.android.result.Credentials +import io.reactivex.observers.DisposableSingleObserver +import org.rfcx.ranger.R +import org.rfcx.ranger.data.remote.Result +import org.rfcx.ranger.data.remote.terms.TermsUseCase +import org.rfcx.ranger.entity.Err +import org.rfcx.ranger.entity.Ok +import org.rfcx.ranger.entity.terms.TermsRequest +import org.rfcx.ranger.entity.terms.TermsResponse +import org.rfcx.ranger.util.CredentialKeeper +import org.rfcx.ranger.util.CredentialVerifier +import org.rfcx.ranger.util.Preferences +import org.rfcx.ranger.util.getResultError + +class TermsAndServiceViewModel(private val context: Context, private val termsUseCase: TermsUseCase) : ViewModel() { + + private val auth0 by lazy { + val auth0 = Auth0(context.getString(R.string.auth0_client_id), context.getString(R.string.auth0_domain)) + //auth0.isLoggingEnabled = true + auth0.isOIDCConformant = true + auth0 + } + + private val authentication by lazy { + AuthenticationAPIClient(auth0) + } + + private val _consentGivenState = MutableLiveData>() + val consentGivenState: LiveData> get() = _consentGivenState + + fun acceptTerms() { + _consentGivenState.value = Result.Loading + + termsUseCase.execute(object : DisposableSingleObserver() { + override fun onSuccess(t: TermsResponse) { + if (t.success) { + refreshToken { + if (it) { + _consentGivenState.postValue(Result.Success(true)) + } + } + } + } + + override fun onError(e: Throwable) { + _consentGivenState.value = e.getResultError() + } + }, TermsRequest("RangerApp")) + } + + private fun refreshToken(callback: (Boolean) -> Unit) { + val refreshToken = Preferences.getInstance(context).getString(Preferences.REFRESH_TOKEN) + if (refreshToken == null) { + callback(false) + return + } + + authentication.renewAuth(refreshToken).start(object : BaseCallback { + override fun onSuccess(credentials: Credentials) { + when (val result = CredentialVerifier(context).verify(credentials)) { + is Err -> { + callback(false) + } + is Ok -> { + val userAuthResponse = result.value + if (userAuthResponse.isRanger) { + CredentialKeeper(context).save(userAuthResponse) + } + callback(userAuthResponse.isRanger) + } + } + } + + override fun onFailure(error: AuthenticationException) { + callback(false) + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/rfcx/ranger/view/report/ReportActivity.kt b/app/src/main/java/org/rfcx/ranger/view/report/ReportActivity.kt index 917947fb7..2a8d4d8fa 100644 --- a/app/src/main/java/org/rfcx/ranger/view/report/ReportActivity.kt +++ b/app/src/main/java/org/rfcx/ranger/view/report/ReportActivity.kt @@ -16,6 +16,7 @@ import android.view.MenuItem import android.view.View import android.widget.ScrollView import android.widget.Toast +import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.databinding.DataBindingUtil import androidx.recyclerview.widget.LinearLayoutManager @@ -44,6 +45,7 @@ import java.io.File import java.io.IOException import java.util.* import org.rfcx.ranger.databinding.ActivityReportBinding +import kotlin.concurrent.timerTask class ReportActivity : BaseReportImageActivity(), OnMapReadyCallback { @@ -59,6 +61,8 @@ class ReportActivity : BaseReportImageActivity(), OnMapReadyCallback { private var lastLocation: Location? = null private val analytics by lazy { Analytics(this) } + private val waitingForLocationTimer: Timer = Timer() + private lateinit var binding: ActivityReportBinding private fun bitmapDescriptorFromVector(context: Context, vectorResId: Int): BitmapDescriptor? { @@ -127,6 +131,7 @@ class ReportActivity : BaseReportImageActivity(), OnMapReadyCallback { supportFragmentManager.beginTransaction().remove(it).commitAllowingStateLoss() } stopPlaying() + waitingForLocationTimer.cancel() } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -215,14 +220,31 @@ class ReportActivity : BaseReportImageActivity(), OnMapReadyCallback { if (isDestroyed) return locationManager?.removeUpdates(locationListener) locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager? + waitingForLocationTimer.schedule(timerTask { + if (lastLocation == null) { + runOnUiThread { + showLocationMessageError(getString(R.string.in_air_plane_mode)) + AlertDialog.Builder(this@ReportActivity) + .setTitle(null) + .setMessage("${getString(R.string.in_air_plane_mode)}. ${getString(R.string.please_try_again_later)}") + .setCancelable(true) + .setPositiveButton(R.string.button_ok) { dialog, _ -> + dialog.dismiss() + finish() + }.show() + } + } + }, 30 * 1000) // waiting for 30 sec if cannot get location show error try { locationManager?.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5 * 1000L, 0f, locationListener) lastLocation = locationManager?.getLastKnownLocation(LocationManager.GPS_PROVIDER) showLocationFinding() lastLocation?.let { markRangerLocation(it) } } catch (ex: SecurityException) { + showLocationMessageError(getString(R.string.in_air_plane_mode)) ex.printStackTrace() } catch (ex: IllegalArgumentException) { + showLocationMessageError(getString(R.string.in_air_plane_mode)) ex.printStackTrace() } diff --git a/app/src/main/res/layout/fragment_terms_and_service.xml b/app/src/main/res/layout/fragment_terms_and_service.xml new file mode 100644 index 000000000..cde272683 --- /dev/null +++ b/app/src/main/res/layout/fragment_terms_and_service.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 6df68a946..e412e4095 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -28,6 +28,15 @@ Crear nuevos informes Ver informes de ubicación Aplicación Introducción + Coordenadas + + DD + DDM + DMS + + 0.00000°N 0.00000°E + 0°0.0000\'N 0°0.0000\'E + 0°0\'0.0\"N 0°0\'0.0\"E Entrar @@ -37,6 +46,9 @@ Por favor, díganos su nombre Nombre + He leído y estoy de acuerdo con estos Términos y Servicio + Seguir + Un error ha ocurrido. Por favor intente de nuevo. A Ranger App le gustaría usar tu ubicación @@ -236,6 +248,7 @@ La contraseña ha sido cambiada con éxito. Algo salió mal. Por favor, inténtelo de nuevo más tarde. Cuenta + inténtelo de nuevo más tarde Cambio de perfil de fotos Editar perfil diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index e6b893934..6db3fcafc 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -28,6 +28,15 @@ Membuat laporan baru Lihat laporan berdasarkan lokasi App Intro + Koordinat + + DD + DDM + DMS + + 0.00000°N 0.00000°E + 0°0.0000\'N 0°0.0000\'E + 0°0\'0.0\"N 0°0\'0.0\"E Masuk @@ -37,6 +46,9 @@ Silakan beritahu kami nama Anda Nama + Saya telah membaca dan menyetujui Syarat dan Layanan + Terus + terjadi kesalahan, silahkan coba lagi Aplikasi Ranger ingin menggunakan lokasi Anda @@ -236,6 +248,7 @@ Password telah berhasil diubah. Ada yang salah. Silakan coba lagi nanti. Akun + Silakan coba lagi nanti Ganti foto profil Sunting profil diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml new file mode 100644 index 000000000..d79031643 --- /dev/null +++ b/app/src/main/res/values-nl/strings.xml @@ -0,0 +1,265 @@ + + + + E-mail + Wachtwoord + Log in + Verbinden met Facebook + Ga verder met telefoonnummer + Vul e-mail + Vul wachtwoord + foute gebruikersnaam of wachtwoord + Service onbeschikbaar + OF + Aangedreven door + + + De volgende + Overspringen + Gedaan + U vindt er ook deze les in het tabblad Profile + Naam van de voogd + Schakel het volgen als je op patrouille om uw positie te melden + Wanneer de gebeurtenis gedetecteerd + Aantal bevestigde beoordelingen + Vakjes geven mogelijke kettingzaag gebeurtenissen + Bevestigen dat het een kettingzaag + Weigeren het is niet een kettingzaag + Maak nieuwe rapporten + Rapporten te bekijken per locatie + App intro + Coördinaten + + + Inloggen + voorleggen + + Wat moeten we je bellen? + Vertel ons uw naam + Naam + + Ik heb gelezen en ga akkoord met deze voorwaarden en service + Doorgaan met + + + Er is een fout opgetreden. Probeer het opnieuw. + Ranger App wilt uw locatie te gebruiken + Ranger App willen audio op te nemen + Ranger App wilt uw camera te gebruiken + Ranger App wilt uw camera te gebruiken + instellingen + + + Hi, %s! + In functie + Niet volgen + Set voogd groep in Instellingen + Gelieve set voogd groep + verzenden naar + + + Verslag doen van + WAT? + WANNEER? + voorleggen + RECORD\'S (Optioneel) + FOTO (optioneel) + Locatie is niet gevonden. Probeer het opnieuw. + Notes (optioneel) + Notes + Doe wat aantekeningen hier + Sommige foto bestaat al + Deze foto\'s bestaan + Deze foto bestaat al + loopt af in 24 uur + + + Laden locatie… + Schakel de locatie-instellingen van uw apparaat in + + + Bezig met laden… + Location tracking + Aan + Uit + Plaats + Guardian groep + Meldingen + + DD + DDM + DMS + + 0.00000°N 0.00000°E + 0°0.0000\'N 0°0.0000\'E + 0°0\'0.0\"N 0°0\'0.0\"E + + Laatste \nMonth + Laatste \nWeek + Laatste \n24 uur + Nu + Binnen een maand + Binnen een week + Binnen 24 uur + Op dat moment + + Houd de knop om op te nemen notities + Nieuw paswoord + Bevestig nieuw wachtwoord + Verander wachtwoord + + + Verbinding maken met een website + Vul hier uw uitnodiging code + Code + Ongeldige code uit te nodigen + + + %d meldingen + %d rapport + %d locatie inchecken + Wachten op het netwerk… + beginnen uploaden… + uploaden… + Upload compleet! + "Er waren geen incidenten onlangs. \nGeniet van je dag :)" + Kan geen audio te spelen, probeer het later opnieuw. + + Camera + Galerij + + + verslag Geschiedenis + Je hebt geen rapporten + %d foto\'s (gesynchroniseerd) + %1d\'s (%2d-gesynchroniseerde) + rapport detail + + Kan uw locatie te bepalen + Schakel de vliegtuigmodus uit + + Plaats + Foto\'s + audio notities + + TOESTAND + KAART + ALERT + PROFIEL + + Zie detail + Net nu + %dm + %dh + %dd + %dw + Follow-up later + Open kaart + + OK + Nee + Ja + Geen + + + Uw wekelijkse-status + Rapport geschiedenis + Recente alerts + Bekijk meer + minuten op plicht + ingediende verslagen + alert beoordeeld + %s, %s + %s foto + %s foto (%s nog niet gesynchroniseerd) + %s foto\'s + %s foto\'s (%s nog niet gesynchroniseerd) + Geen rapporten in de afgelopen 7 dagen. \ndruk op + om er een te maken. + Guardian groep niet gevonden. Je moet een groep in en ontvang meldingen te stellen! + + Later + Stel Guardian Group in + + + Alle Media + Voorbeeld + Toepassen + Toepassen(%1$d) + Terug + Camera + Nog geen media + OK + Je hebt max selecteerbare bereikt + U kunt alleen selecteert maximaal %1$d mediabestanden + Onder kwaliteit + Over kwaliteit + Niet ondersteund bestandstype + Kan geen foto\'s en video\'s tegelijk selecteren + Geen App gevonden ter ondersteuning van video preview + Kan de foto\'s die groter zijn dan %1$d MB niet selecteren + %1$d foto\'s meer dan %2$d MB. Original zal uitgevinkt + origineel + OK + OK(%1$d) + + + + Door Guardian + Alle waarschuwingen + Gisteren + Geen internet, probeer het opnieuw. + Guardian groep niet gevonden. Je moet een groep in en ontvang meldingen te stellen! + Er is geen invallend Onlangs hebben zich + Geniet van je dag :) + Zie Oudere + Er is geen evenementen meer + Laatst beoordeeld door + Nog niet beoordeeld + + + kettingzaag + geweerschot + voertuig + overtreder + anders + + + door pushmelding + per email + Afmelden + Vul uw nieuwe wachtwoord + Bevestig uw nieuwe wachtwoord + Wachtwoord moet minimaal 6 tekens + Bevestig wachtwoord komt niet overeen + Het wachtwoord is gewijzigd. + Er is iets fout gegaan. Probeer het later opnieuw. + Extra + App versie + Terugkoppeling + Beoordeel de app + Wijzigingen opslaan + Probeer het later opnieuw. + + Terugkoppeling ingediend. Bedankt voor je tijd :) + Indienen van gebruikersfeedback mislukt! Probeer het opnieuw. + Opnieuw + Feedback sturen + Van: + Laat app feedback of deel uw ideeën + Je hebt max selecteerbare bereikt. Probeer het verwijderen van een aantal items voor het toevoegen van meer bijlagen. + Account + + Profiel wijzigen Photo + Bewerk profiel + + + Guardian Group List + + On-plicht en bijhouden heeft ingeschakeld + %d minuten geregistreerd + + afwijzen + bevestigen + Verplicht! + Afmelden voor %s mislukt. + \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 86989e16b..a8e8d9a59 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -28,6 +28,15 @@ Criar novos relatórios Visualizar relatórios por localização App Intro + Coordenadas + + DD + DDM + DMS + + 0.00000°N 0.00000°E + 0°0.0000\'N 0°0.0000\'E + 0°0\'0.0\"N 0°0\'0.0\"E Assinar em @@ -37,6 +46,9 @@ Por favor, diga-nos o seu nome Nome + Eu li e concordo com os Termos e Serviço + Continuar + Ocorreu um erro. Por favor, tente de novo. O aplicativo Ranger gostaria de usar sua localização @@ -236,6 +248,7 @@ A senha foi alterada com sucesso. Algo deu errado. Por favor, tente novamente mais tarde. Conta + tente novamente mais tarde. Alterar foto do perfil Editar Perfil diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 99b4908b5..71758d518 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -28,7 +28,15 @@ สร้างรายงานใหม่ ดูรายงานตามพื้นที่ คู่มือการใช้งานแอป + รูปแบบพิกัด GPS + DD + DDM + DMS + + 0.00000°N 0.00000°E + 0°0.0000\'N 0°0.0000\'E + 0°0\'0.0\"N 0°0\'0.0\"E เข้าสู่ระบบ บันทึก @@ -37,6 +45,9 @@ กรุณาบอกชื่อคุณกับเรา ชื่อ + ข้าพเจ้าได้อ่านและยอมรับข้อกำหนดและบริการดังกล่าว + ถัดไป + เกิดข้อผิดพลาด กรุณาลองใหม่อีกครั้ง แอป Ranger ต้องการใช้ตำแหน่งของคุณ @@ -235,6 +246,7 @@ การเปลี่ยนรหัสผ่านสำเร็จ มีบางอย่างผิดพลาด กรุณาลองใหม่อีกครั้ง บัญชีผู้ใช้ + มีบางอย่างผิดพลาด กรุณาลองใหม่อีกครั้ง เปลี่ยนรูปโปรไฟล์ แก้ไขโปรไฟล์ diff --git a/app/src/main/res/values/environment.xml b/app/src/main/res/values/environment.xml index ed63e8255..a924bfd0c 100644 --- a/app/src/main/res/values/environment.xml +++ b/app/src/main/res/values/environment.xml @@ -8,6 +8,7 @@ https://rfcx.eu.auth0.com/userinfo openid email profile offline_access https://rfcx.org/app_metadata + https://rfcx.org/user_metadata AIzaSyC-NLZPDq0jl8G5ZOvX48OeTylK8XR0M0s diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 22db05551..9040f0daf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -38,6 +38,9 @@ Please tell us your name Name + I have read and agree to these Terms and Service + Continue + An error occurred. Please try again. Ranger App would like to use your location @@ -235,6 +238,7 @@ Feedback Rate the app Save Changes + Please try again later Feedback submitted. Thank you for your time :) Feedback submission failed! Please try again.