From bb914a8cf936aac4a59bb881fee4952569d047a8 Mon Sep 17 00:00:00 2001 From: ratree Date: Thu, 27 Feb 2020 12:26:34 +0700 Subject: [PATCH 01/30] CA-2055 Show loading make sure it already delete all local db before open Login page --- .../rfcx/ranger/view/profile/ProfileFragment.kt | 14 ++++++++++++++ .../rfcx/ranger/view/profile/ProfileViewModel.kt | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/app/src/main/java/org/rfcx/ranger/view/profile/ProfileFragment.kt b/app/src/main/java/org/rfcx/ranger/view/profile/ProfileFragment.kt index 3481c8185..ae8b5bb13 100644 --- a/app/src/main/java/org/rfcx/ranger/view/profile/ProfileFragment.kt +++ b/app/src/main/java/org/rfcx/ranger/view/profile/ProfileFragment.kt @@ -23,6 +23,8 @@ import org.rfcx.ranger.view.LocationTrackingViewModel import org.rfcx.ranger.view.MainActivityEventListener import org.rfcx.ranger.view.base.BaseFragment import org.rfcx.ranger.view.tutorial.TutorialActivity +import android.app.ProgressDialog +import android.os.Handler class ProfileFragment : BaseFragment() { @@ -73,6 +75,18 @@ class ProfileFragment : BaseFragment() { locationTrackingViewModel.locationTrackingState.observe(this, Observer { profileViewModel.onTracingStatusChange() }) + + val progressDialog = ProgressDialog(context) + progressDialog.setMessage("Loading...") + progressDialog.setCancelable(false) + + profileViewModel.logoutState.observe(this, Observer { + if (it) { + progressDialog.show() + } else { + progressDialog.dismiss() + } + }) } private fun setOnClickButton() { diff --git a/app/src/main/java/org/rfcx/ranger/view/profile/ProfileViewModel.kt b/app/src/main/java/org/rfcx/ranger/view/profile/ProfileViewModel.kt index 92f371eca..dfcac0177 100644 --- a/app/src/main/java/org/rfcx/ranger/view/profile/ProfileViewModel.kt +++ b/app/src/main/java/org/rfcx/ranger/view/profile/ProfileViewModel.kt @@ -3,6 +3,7 @@ package org.rfcx.ranger.view.profile import android.content.Context import android.util.Log import android.widget.Toast +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import io.reactivex.observers.DisposableSingleObserver @@ -26,6 +27,9 @@ class ProfileViewModel(private val context: Context, private val profileData: Pr val sendToEmail = MutableLiveData() val guardianGroup = MutableLiveData() + private val _logoutState = MutableLiveData() + val logoutState: LiveData = _logoutState + init { getSiteName() locationTracking.value = profileData.getTracking() @@ -79,6 +83,7 @@ class ProfileViewModel(private val context: Context, private val profileData: Pr } fun onLogout() { + _logoutState.value = true if(profileData.getReceiveNotificationByEmail()){ unsubscribeUseCase.execute(object : DisposableSingleObserver() { override fun onSuccess(t: SubscribeResponse) { @@ -86,6 +91,7 @@ class ProfileViewModel(private val context: Context, private val profileData: Pr } override fun onError(e: Throwable) { + _logoutState.value = false } }, SubscribeRequest(listOf(context.getGuardianGroup().toString()))) } else { From b0c4420eac737918c6b04f10dda8dde7550c60ad Mon Sep 17 00:00:00 2001 From: ratree Date: Thu, 27 Feb 2020 12:47:53 +0700 Subject: [PATCH 02/30] CA-2061 Change icon on Login page --- app/src/main/res/drawable/ic_ranger_icon.xml | 27 ++++++++++++++++++++ app/src/main/res/layout/fragment_login.xml | 8 +++--- 2 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 app/src/main/res/drawable/ic_ranger_icon.xml diff --git a/app/src/main/res/drawable/ic_ranger_icon.xml b/app/src/main/res/drawable/ic_ranger_icon.xml new file mode 100644 index 000000000..900456025 --- /dev/null +++ b/app/src/main/res/drawable/ic_ranger_icon.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_login.xml b/app/src/main/res/layout/fragment_login.xml index 7f402376a..0d98ade0a 100644 --- a/app/src/main/res/layout/fragment_login.xml +++ b/app/src/main/res/layout/fragment_login.xml @@ -29,9 +29,9 @@ android:id="@+id/bellLogoImageView" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="@dimen/margin_padding_xlarge" + android:layout_marginTop="@dimen/margin_padding_small" android:layout_marginBottom="@dimen/margin_padding_small" - android:src="@drawable/ic_notification_green" + android:src="@drawable/ic_ranger_icon" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -42,7 +42,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/margin_padding_normal" - android:layout_marginTop="@dimen/margin_padding_normal" + android:layout_marginTop="@dimen/margin_padding_small" android:layout_marginEnd="@dimen/margin_padding_normal" android:text="@string/app_name" android:textStyle="bold" @@ -57,7 +57,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="@dimen/margin_padding_normal" - android:layout_marginTop="@dimen/margin_padding_xlarge" + android:layout_marginTop="@dimen/margin_padding_normal" android:layout_marginEnd="@dimen/margin_padding_normal" android:hint="@string/email" android:imeOptions="actionNext" From f5cfa2fb1756c4798a50d9383154ff0496f3be5b Mon Sep 17 00:00:00 2001 From: ratree Date: Thu, 27 Feb 2020 20:12:18 +0700 Subject: [PATCH 03/30] CA-2047 Improve Setting page --- app/src/main/res/layout/fragment_profile.xml | 193 +++++++++++-------- app/src/main/res/values-es/strings.xml | 1 + app/src/main/res/values-in/strings.xml | 1 + app/src/main/res/values-pt/strings.xml | 1 + app/src/main/res/values-th/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 6 files changed, 115 insertions(+), 83 deletions(-) diff --git a/app/src/main/res/layout/fragment_profile.xml b/app/src/main/res/layout/fragment_profile.xml index bda77e5f0..2facdd6db 100644 --- a/app/src/main/res/layout/fragment_profile.xml +++ b/app/src/main/res/layout/fragment_profile.xml @@ -90,67 +90,51 @@ - + + android:background="?attr/selectableItemBackground" + android:onClick="@{onClickGuardingGroup}" + android:orientation="vertical" + android:paddingTop="@dimen/margin_padding_normal" + android:paddingBottom="@dimen/margin_padding_small"> + android:layout_marginStart="@dimen/margin_padding_normal" + android:layout_marginEnd="@dimen/margin_padding_small" + android:text="@string/guardian_group_label" /> - - - - - + android:layout_marginStart="@dimen/margin_padding_normal" + android:layout_marginEnd="@dimen/margin_padding_small" + android:text="@{viewModel.guardianGroup}" + tools:text="Osa Conservation" /> @@ -159,7 +143,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/margin_padding_normal" - android:layout_marginTop="@dimen/margin_padding_small" android:layout_marginEnd="@dimen/margin_padding_normal" android:orientation="horizontal"> @@ -200,13 +183,13 @@ android:gravity="center" android:orientation="vertical"> - + + + + + + android:layout_marginStart="@dimen/margin_padding_normal" + android:layout_marginEnd="@dimen/margin_padding_normal" + android:orientation="horizontal"> + android:layout_gravity="center_vertical" + android:layout_weight="1" + android:text="@string/location_tracking_label" + android:textSize="@dimen/text_small" /> - + android:clickable="true" + android:focusable="true" + android:onClick="@{onClickLocationTracking}"> + + + + + android:layout_height="10dp" + android:background="@color/grey_light" /> + android:text="@string/app_intro" /> + android:text="@string/profile_feedback_label" /> + android:text="@string/profile_rate_app_label" /> + + + + Confirmar contraseña no coincide La contraseña ha sido cambiada con éxito. Algo salió mal. Por favor, inténtelo de nuevo más tarde. + Cuenta Lista de grupos de la guarda diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 784e32c89..2946bdb94 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -233,6 +233,7 @@ Konfirmasi sandi tidak cocok Password telah berhasil diubah. Ada yang salah. Silakan coba lagi nanti. + Akun Daftar wali Grup diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 7e961df07..9a0be1c7a 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -233,6 +233,7 @@ Confirmar senha não corresponde A senha foi alterada com sucesso. Algo deu errado. Por favor, tente novamente mais tarde. + Conta Lista Grupo Guardião diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 3f163f7f6..2734def3a 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -232,6 +232,7 @@ การยืนยันรหัสผ่านไม่ตรงกัน การเปลี่ยนรหัสผ่านสำเร็จ มีบางอย่างผิดพลาด กรุณาลองใหม่อีกครั้ง + บัญชีผู้ใช้ กลุ่ม Guardian diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2a7866a54..0a45a9a13 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -232,6 +232,7 @@ From: Leave app feedback or share your ideas You have reached max selectable. Try remove some items before adding more attachments. + Account Guardian Group List From f354a9a7b3f3bc88e7c845b2677072daeb27b980 Mon Sep 17 00:00:00 2001 From: Anuphap Suwannamas Date: Sun, 1 Mar 2020 20:44:15 +0700 Subject: [PATCH 04/30] CA-2050 - Fixed after change guardian group remove cache event api and observe remove events from db --- .../ranger/data/local/CachedEndpointDb.kt | 8 ++++-- .../org/rfcx/ranger/data/local/EventDb.kt | 16 +++++++++++ .../remote/domain/alert/GetEventsUseCase.kt | 7 +++-- .../main/java/org/rfcx/ranger/di/UiModule.kt | 2 +- .../view/profile/GuardianGroupActivity.kt | 18 +++--------- .../view/profile/GuardianGroupViewModel.kt | 28 +++++++++++++++++-- 6 files changed, 57 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/org/rfcx/ranger/data/local/CachedEndpointDb.kt b/app/src/main/java/org/rfcx/ranger/data/local/CachedEndpointDb.kt index ed2ffb7b4..73008fe5f 100644 --- a/app/src/main/java/org/rfcx/ranger/data/local/CachedEndpointDb.kt +++ b/app/src/main/java/org/rfcx/ranger/data/local/CachedEndpointDb.kt @@ -15,8 +15,12 @@ class CachedEndpointDb(val realm: Realm) { } fun clearCachedEndpoint(endpoint: String) { - realm.where(CachedEndpoint::class.java).like(CachedEndpoint.FIELD_ENDPOINT, - "$endpoint*").findAll().deleteAllFromRealm() + realm.use { it -> + it.executeTransaction { + it.where(CachedEndpoint::class.java).like(CachedEndpoint.FIELD_ENDPOINT, + "$endpoint*").findAll().deleteAllFromRealm() + } + } } fun hasCachedEndpoint(endpoint: String, hours: Double = 1.0): Boolean { 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 167919a06..2a9e8928e 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 @@ -121,6 +121,22 @@ class EventDb(val realm: Realm) { } } + fun deleteAllEvents(callback: (Boolean) -> Unit) { + realm.use { realm -> + realm.executeTransactionAsync({ bgRealm -> + bgRealm.delete(Event::class.java) + }, { + // success + callback(true) + realm.close() + }, { + // fail + callback(false) + realm.close() + }) + } + } + fun deleteAllEvents() { realm.use { it -> it.executeTransaction { diff --git a/app/src/main/java/org/rfcx/ranger/data/remote/domain/alert/GetEventsUseCase.kt b/app/src/main/java/org/rfcx/ranger/data/remote/domain/alert/GetEventsUseCase.kt index 80a8b5759..074ab9ab4 100644 --- a/app/src/main/java/org/rfcx/ranger/data/remote/domain/alert/GetEventsUseCase.kt +++ b/app/src/main/java/org/rfcx/ranger/data/remote/domain/alert/GetEventsUseCase.kt @@ -57,8 +57,11 @@ class GetEventsUseCase(private val eventRepository: EventRepository, if (isStarting) { val eventCached = eventDb.getEvents() val r = events.filter { - eventCached.firstOrNull { cached -> cached.id == it.id && - cached.reviewCreated.time == it.reviewCreated.time } == null // new event? + + eventCached.firstOrNull { cached -> cached.id == it.id + && cached.rejectedCount == it.rejectedCount + && cached.confirmedCount == it.confirmedCount + } == null // new event? } // has new event? if (r.isNotEmpty()) { 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 e4ed7cbe3..b5f354ab9 100644 --- a/app/src/main/java/org/rfcx/ranger/di/UiModule.kt +++ b/app/src/main/java/org/rfcx/ranger/di/UiModule.kt @@ -57,7 +57,7 @@ object UiModule { val profileModule = module { viewModel { ProfileViewModel(androidContext(), get(), get(), get()) } - viewModel { GuardianGroupViewModel(androidContext(), get(), get(), get(), get()) } + viewModel { GuardianGroupViewModel(androidContext(), get(), get(), get(), get(), get()) } viewModel { FeedbackViewModel(androidContext()) } viewModel { PasswordChangeViewModel(get()) } viewModel { EditProfileViewModel(androidContext(), get()) } diff --git a/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupActivity.kt b/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupActivity.kt index d1a82ade8..00d370a2d 100644 --- a/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupActivity.kt +++ b/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupActivity.kt @@ -52,21 +52,11 @@ class GuardianGroupActivity : BaseActivity() { guardianGroupAdapter.mOnItemClickListener = object : OnItemClickListener { override fun onItemClick(guardianGroup: GuardianGroup) { - viewModel.removeAllEvent() - analytics.trackSetGuardianGroupEvent() - viewModel.subscribeByEmail(guardianGroup.shortname) - // TODO what happens on failure? loadingProgress.visibility = View.VISIBLE - - val preferences = Preferences.getInstance(this@GuardianGroupActivity) - preferences.putString(Preferences.SELECTED_GUARDIAN_GROUP_FULLNAME, guardianGroup.name) - - // TODO should be in the VM - CloudMessaging.unsubscribe(this@GuardianGroupActivity) { - CloudMessaging.setGroup(this@GuardianGroupActivity, guardianGroup.shortname) - CloudMessaging.subscribeIfRequired(this@GuardianGroupActivity) { - finish() - } + analytics.trackSetGuardianGroupEvent() + viewModel.changeGuardianGroup( guardianGroup) { + loadingProgress.visibility = View.INVISIBLE + finish() } } } diff --git a/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupViewModel.kt b/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupViewModel.kt index f2ab07304..455c72816 100644 --- a/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupViewModel.kt +++ b/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import io.reactivex.observers.DisposableSingleObserver +import org.rfcx.ranger.data.local.CachedEndpointDb import org.rfcx.ranger.data.local.EventDb import org.rfcx.ranger.data.remote.ResponseCallback import org.rfcx.ranger.data.remote.Result @@ -16,6 +17,7 @@ import org.rfcx.ranger.entity.SubscribeRequest import org.rfcx.ranger.entity.SubscribeResponse import org.rfcx.ranger.entity.event.GuardianGroupFactory import org.rfcx.ranger.entity.guardian.GuardianGroup +import org.rfcx.ranger.util.CloudMessaging import org.rfcx.ranger.util.Preferences import org.rfcx.ranger.util.getGuardianGroup import org.rfcx.ranger.util.getResultError @@ -23,7 +25,8 @@ import org.rfcx.ranger.util.getResultError class GuardianGroupViewModel(private val context: Context, private val getGuardianGroups: GetGuardianGroups, private val eventDb: EventDb, private val subscribeUseCase: SubscribeUseCase, - private val unsubscribeUseCase: UnsubscribeUseCase) : ViewModel() { + private val unsubscribeUseCase: UnsubscribeUseCase, + private val cachedEndpointDb: CachedEndpointDb) : ViewModel() { private val _items = MutableLiveData>>() val items: LiveData>> get() = _items @@ -57,6 +60,7 @@ class GuardianGroupViewModel(private val context: Context, private val getGuardi } override fun onError(e: Throwable) { + // TODO what happens on failure? } }, SubscribeRequest(listOf(context.getGuardianGroup().toString()))) } @@ -75,8 +79,26 @@ class GuardianGroupViewModel(private val context: Context, private val getGuardi /** * remove all events when select guardian group */ - fun removeAllEvent() { - eventDb.deleteAllEvents() + fun changeGuardianGroup(guardianGroup: GuardianGroup, callback: () -> Unit) { + eventDb.deleteAllEvents { + val preferences = Preferences.getInstance(context) + preferences.putString(Preferences.SELECTED_GUARDIAN_GROUP_FULLNAME, guardianGroup.name) + + // clear cache endpoint + cachedEndpointDb.clearCachedEndpoint("guardians/group/") + cachedEndpointDb.clearCachedEndpoint("v2/events/?guardian_groups[]=") + + // sub email + subscribeByEmail(guardianGroup.shortname) + + // sub&unsub email? + CloudMessaging.unsubscribe(context) { + CloudMessaging.setGroup(context, guardianGroup.shortname) + CloudMessaging.subscribeIfRequired(context) { + callback() + } + } + } } } From a27aaed7f1e1aad420a8a8e0ddacc9ef408a18d3 Mon Sep 17 00:00:00 2001 From: Anuphap Suwannamas Date: Sun, 1 Mar 2020 20:46:39 +0700 Subject: [PATCH 05/30] CA-2050 Fixed status list not show loading in session of recent events --- .../ranger/view/alerts/AllAlertsViewModel.kt | 17 +++++---- .../ranger/view/status/StatusViewModel.kt | 17 +++++---- .../view/status/adapter/StatusAdapter.kt | 35 +++++++++---------- 3 files changed, 35 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/org/rfcx/ranger/view/alerts/AllAlertsViewModel.kt b/app/src/main/java/org/rfcx/ranger/view/alerts/AllAlertsViewModel.kt index 08e3ed0b6..a0d0431b0 100644 --- a/app/src/main/java/org/rfcx/ranger/view/alerts/AllAlertsViewModel.kt +++ b/app/src/main/java/org/rfcx/ranger/view/alerts/AllAlertsViewModel.kt @@ -1,16 +1,13 @@ package org.rfcx.ranger.view.alerts import android.content.Context -import android.widget.Toast import androidx.lifecycle.* import io.realm.RealmResults -import org.rfcx.ranger.R import org.rfcx.ranger.adapter.entity.BaseItem import org.rfcx.ranger.data.local.EventDb import org.rfcx.ranger.data.local.ProfileData import org.rfcx.ranger.data.remote.ResponseCallback import org.rfcx.ranger.data.remote.Result -import org.rfcx.ranger.data.remote.domain.alert.GetEventUseCase import org.rfcx.ranger.data.remote.domain.alert.GetEventsUseCase import org.rfcx.ranger.entity.event.Event import org.rfcx.ranger.entity.event.EventsRequestFactory @@ -41,11 +38,9 @@ class AllAlertsViewModel(private val context: Context, var isLoadMore = false private val eventObserve = Observer> { - if (it.isNotEmpty()) { - val cacheEvents = eventDb.getEvents() - this.currentOffset = cacheEvents.size - handleAlerts(events = cacheEvents) - } + val cacheEvents = eventDb.getEvents() + this.currentOffset = cacheEvents.size + handleAlerts(events = cacheEvents) } init { @@ -55,12 +50,16 @@ class AllAlertsViewModel(private val context: Context, fetchEvents() } - fun refresh() { + private fun clearDate() { currentOffset = 0 totalItemCount = 0 isLoadMore = false _alertsList = listOf() items.clear() + } + + fun refresh() { + clearDate() loadEvents() } diff --git a/app/src/main/java/org/rfcx/ranger/view/status/StatusViewModel.kt b/app/src/main/java/org/rfcx/ranger/view/status/StatusViewModel.kt index 8774c6538..88ce38c4e 100644 --- a/app/src/main/java/org/rfcx/ranger/view/status/StatusViewModel.kt +++ b/app/src/main/java/org/rfcx/ranger/view/status/StatusViewModel.kt @@ -31,7 +31,10 @@ import org.rfcx.ranger.localdb.ReportImageDb import org.rfcx.ranger.service.ImageUploadWorker import org.rfcx.ranger.service.LocationSyncWorker import org.rfcx.ranger.service.ReportSyncWorker -import org.rfcx.ranger.util.* +import org.rfcx.ranger.util.Preferences +import org.rfcx.ranger.util.asLiveData +import org.rfcx.ranger.util.isNetworkAvailable +import org.rfcx.ranger.util.replace import org.rfcx.ranger.view.map.ImageState import org.rfcx.ranger.view.status.adapter.StatusAdapter import java.util.concurrent.TimeUnit @@ -78,10 +81,8 @@ class StatusViewModel(private val context: Context, private val reportDb: Report } private val eventObserve = Observer> { it -> - if (it.isNotEmpty()) { - val events = eventDb.getEvents() - updateRecentAlerts(events) - } + val events = eventDb.getEvents() + updateRecentAlerts(events) } private val _locationTracking = MutableLiveData() @@ -96,8 +97,8 @@ class StatusViewModel(private val context: Context, private val reportDb: Report private val _reportItems = MutableLiveData>() val reportItems: LiveData> = _reportItems - private val _alertItems = MutableLiveData>() - val alertItems: LiveData> = _alertItems + private val _alertItems = MutableLiveData?>() + val alertItems: LiveData?> = _alertItems private val _syncInfo = MutableLiveData() val syncInfo: LiveData = _syncInfo @@ -301,6 +302,8 @@ class StatusViewModel(private val context: Context, private val reportDb: Report // start load val group = profileData.getGuardianGroup() ?: return // has guardian group + _alertItems.value = null + val requestFactory = EventsRequestFactory(listOf(group.shortname), "measured_at", "DESC", 3, 0, group.values) eventsUserCase.execute(object : ResponseCallback, Int>> { override fun onSuccess(t: Pair, Int>) { diff --git a/app/src/main/java/org/rfcx/ranger/view/status/adapter/StatusAdapter.kt b/app/src/main/java/org/rfcx/ranger/view/status/adapter/StatusAdapter.kt index 95bb99654..073ecc404 100644 --- a/app/src/main/java/org/rfcx/ranger/view/status/adapter/StatusAdapter.kt +++ b/app/src/main/java/org/rfcx/ranger/view/status/adapter/StatusAdapter.kt @@ -48,7 +48,7 @@ class StatusAdapter(private val statusTitle: String?, private val alertTitle: St private var profile: ProfileItem? = null private var stat: UserStatusItem? = null private var reports: ArrayList? = arrayListOf() - private var alerts: ArrayList? = arrayListOf() + private var alerts: ArrayList? = null private var syncInfo: SyncInfoItem? = null fun updateHeader(header: ProfileItem) { @@ -71,8 +71,8 @@ class StatusAdapter(private val statusTitle: String?, private val alertTitle: St update() } - fun updateAlertList(newLists: List) { - if (newLists.isNotEmpty()) { + fun updateAlertList(newLists: List?) { + if (newLists != null) { alerts = arrayListOf() alerts?.addAll(newLists) } else { @@ -107,22 +107,21 @@ class StatusAdapter(private val statusTitle: String?, private val alertTitle: St newList.add(TitleItem(it)) } - if (alerts != null && alerts!!.isEmpty()) { - when { - context?.getGuardianGroup() == null -> // not have group - newList.add(AlertSetGuardianGroupItem()) - context.getGuardianGroup() !== null -> - newList.add(AlertLoading()) - alerts!!.size == 0 -> // have group but not have alert - newList.add(AlertEmpty()) - } - } - if (alerts != null && alerts!!.isNotEmpty()) { - newList.addAll(alerts!!) - - seeMoreButton?.let { - newList.add(SeeMoreItem(it)) + if (context?.getGuardianGroup() == null) { + newList.add(AlertSetGuardianGroupItem()) // have no group + } else { + if (alerts != null) { + if (alerts!!.isNotEmpty()) { + newList.addAll(alerts!!) + seeMoreButton?.let { + newList.add(SeeMoreItem(it)) + } + } else { + newList.add(AlertEmpty()) // alert is empty + } + } else { + newList.add(AlertLoading()) // alert is loading } } From fc5c010d74a289e65bbabc0e2a6f1c8b9b7710da Mon Sep 17 00:00:00 2001 From: Anuphap Suwannamas Date: Mon, 2 Mar 2020 12:45:15 +0700 Subject: [PATCH 06/30] Fixed issue from pull request --- .../ranger/view/alerts/AllAlertsViewModel.kt | 4 +- .../view/profile/GuardianGroupActivity.kt | 8 ++-- .../view/profile/GuardianGroupViewModel.kt | 44 +++++++++++-------- app/src/main/res/values-es/strings.xml | 1 + app/src/main/res/values-in/strings.xml | 1 + app/src/main/res/values-pt/strings.xml | 1 + app/src/main/res/values-th/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 8 files changed, 37 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/org/rfcx/ranger/view/alerts/AllAlertsViewModel.kt b/app/src/main/java/org/rfcx/ranger/view/alerts/AllAlertsViewModel.kt index a0d0431b0..7a9803c7d 100644 --- a/app/src/main/java/org/rfcx/ranger/view/alerts/AllAlertsViewModel.kt +++ b/app/src/main/java/org/rfcx/ranger/view/alerts/AllAlertsViewModel.kt @@ -50,7 +50,7 @@ class AllAlertsViewModel(private val context: Context, fetchEvents() } - private fun clearDate() { + private fun clearData() { currentOffset = 0 totalItemCount = 0 isLoadMore = false @@ -59,7 +59,7 @@ class AllAlertsViewModel(private val context: Context, } fun refresh() { - clearDate() + clearData() loadEvents() } diff --git a/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupActivity.kt b/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupActivity.kt index 00d370a2d..83d04bf32 100644 --- a/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupActivity.kt +++ b/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupActivity.kt @@ -12,8 +12,6 @@ import org.rfcx.ranger.R import org.rfcx.ranger.data.remote.success import org.rfcx.ranger.entity.guardian.GuardianGroup import org.rfcx.ranger.util.Analytics -import org.rfcx.ranger.util.CloudMessaging -import org.rfcx.ranger.util.Preferences import org.rfcx.ranger.util.handleError import org.rfcx.ranger.view.base.BaseActivity @@ -54,9 +52,11 @@ class GuardianGroupActivity : BaseActivity() { override fun onItemClick(guardianGroup: GuardianGroup) { loadingProgress.visibility = View.VISIBLE analytics.trackSetGuardianGroupEvent() - viewModel.changeGuardianGroup( guardianGroup) { + viewModel.changeGuardianGroup(guardianGroup) { loadingProgress.visibility = View.INVISIBLE - finish() + if (it) { + finish() + } } } } diff --git a/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupViewModel.kt b/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupViewModel.kt index 455c72816..858d3a34e 100644 --- a/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupViewModel.kt +++ b/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupViewModel.kt @@ -1,10 +1,12 @@ package org.rfcx.ranger.view.profile import android.content.Context +import android.widget.Toast import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import io.reactivex.observers.DisposableSingleObserver +import org.rfcx.ranger.R import org.rfcx.ranger.data.local.CachedEndpointDb import org.rfcx.ranger.data.local.EventDb import org.rfcx.ranger.data.remote.ResponseCallback @@ -49,7 +51,7 @@ class GuardianGroupViewModel(private val context: Context, private val getGuardi }, GuardianGroupFactory()) } - fun subscribeByEmail(guardianGroup: String) { + private fun subscribeByEmail(guardianGroup: String) { val preference = Preferences.getInstance(context) val isSubscribe = preference.getBoolean(Preferences.EMAIL_SUBSCRIBE, false) @@ -60,7 +62,8 @@ class GuardianGroupViewModel(private val context: Context, private val getGuardi } override fun onError(e: Throwable) { - // TODO what happens on failure? + Toast.makeText(context, context.getString(R.string.error_unsubscribe_by_email, + context.getGuardianGroup().toString()), Toast.LENGTH_SHORT).show() } }, SubscribeRequest(listOf(context.getGuardianGroup().toString()))) } @@ -79,24 +82,29 @@ class GuardianGroupViewModel(private val context: Context, private val getGuardi /** * remove all events when select guardian group */ - fun changeGuardianGroup(guardianGroup: GuardianGroup, callback: () -> Unit) { + fun changeGuardianGroup(guardianGroup: GuardianGroup, callback: (Boolean) -> Unit) { eventDb.deleteAllEvents { - val preferences = Preferences.getInstance(context) - preferences.putString(Preferences.SELECTED_GUARDIAN_GROUP_FULLNAME, guardianGroup.name) - - // clear cache endpoint - cachedEndpointDb.clearCachedEndpoint("guardians/group/") - cachedEndpointDb.clearCachedEndpoint("v2/events/?guardian_groups[]=") - - // sub email - subscribeByEmail(guardianGroup.shortname) - - // sub&unsub email? - CloudMessaging.unsubscribe(context) { - CloudMessaging.setGroup(context, guardianGroup.shortname) - CloudMessaging.subscribeIfRequired(context) { - callback() + if (it) { + val preferences = Preferences.getInstance(context) + preferences.putString(Preferences.SELECTED_GUARDIAN_GROUP_FULLNAME, guardianGroup.name) + + // clear cache endpoint + cachedEndpointDb.clearCachedEndpoint("guardians/group/") + cachedEndpointDb.clearCachedEndpoint("v2/events/?guardian_groups[]=") + + // sub&unsub email + subscribeByEmail(guardianGroup.shortname) + + // sub&unsub noti + CloudMessaging.unsubscribe(context) { + CloudMessaging.setGroup(context, guardianGroup.shortname) + CloudMessaging.subscribeIfRequired(context) { + callback(true) + } } + } else { + Toast.makeText(context, R.string.something_is_wrong, Toast.LENGTH_SHORT).show() + callback(false) } } } diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index ddbeeee14..0e4cd6636 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -250,5 +250,6 @@ rechazar confirmar Necesario! + Darse de baja de %s falla. \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 73729bf17..05fbbfb8f 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -250,5 +250,6 @@ menolak Konfirmasi Yg dibutuhkan! + Berhenti berlangganan dari %s gagal. \ 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 3db4c3585..2a8c99937 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -250,5 +250,6 @@ rejeitar confirme Requeridos! + Cancelar inscrição de %s falhou. \ No newline at end of file diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index bffd9ae69..b0cb6a7fe 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -248,5 +248,6 @@ ปฏิเสธ ยืนยัน จำเป็นต้องกรอก! + ยกเลิกการรับข้อมูลจาก %s ล้มเหลว \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 28d1b8a02..4e0e9832a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -246,4 +246,5 @@ reject confirm Required! + Unsubscribe from %s fail. \ No newline at end of file From 12ac8c2910521e0978aaf6f3a0525dff6b29f189 Mon Sep 17 00:00:00 2001 From: ratree Date: Mon, 2 Mar 2020 16:51:08 +0700 Subject: [PATCH 07/30] CA-2050 Fixed issue about show empty page on AllAerts and after change guardian group --- .../rfcx/ranger/data/local/CachedEndpointDb.kt | 15 ++------------- .../java/org/rfcx/ranger/data/local/EventDb.kt | 8 ++++++-- .../data/remote/domain/alert/GetEventsUseCase.kt | 15 +++++++++------ .../rfcx/ranger/view/alerts/AlertViewModel.kt | 7 ++++--- .../rfcx/ranger/view/alerts/AlertsFragment.kt | 16 +++++++++------- .../rfcx/ranger/view/alerts/AllAlertsFragment.kt | 3 ++- .../view/profile/GuardianGroupViewModel.kt | 4 ---- app/src/main/res/layout/fragment_alerts.xml | 10 ++++++++++ app/src/main/res/layout/fragment_empty_alert.xml | 1 + 9 files changed, 43 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/org/rfcx/ranger/data/local/CachedEndpointDb.kt b/app/src/main/java/org/rfcx/ranger/data/local/CachedEndpointDb.kt index 73008fe5f..0ae125916 100644 --- a/app/src/main/java/org/rfcx/ranger/data/local/CachedEndpointDb.kt +++ b/app/src/main/java/org/rfcx/ranger/data/local/CachedEndpointDb.kt @@ -7,19 +7,8 @@ import java.util.* class CachedEndpointDb(val realm: Realm) { fun updateCachedEndpoint(endpoint: String) { - realm.use { it -> - it.executeTransaction { - it.copyToRealmOrUpdate(CachedEndpoint(endpoint, Date())) - } - } - } - - fun clearCachedEndpoint(endpoint: String) { - realm.use { it -> - it.executeTransaction { - it.where(CachedEndpoint::class.java).like(CachedEndpoint.FIELD_ENDPOINT, - "$endpoint*").findAll().deleteAllFromRealm() - } + realm.executeTransaction { + it.copyToRealmOrUpdate(CachedEndpoint(endpoint, Date())) } } 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 2a9e8928e..f80e732d9 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 @@ -3,6 +3,7 @@ package org.rfcx.ranger.data.local import io.realm.Realm import io.realm.RealmResults import io.realm.Sort +import org.rfcx.ranger.entity.CachedEndpoint import org.rfcx.ranger.entity.event.Event import org.rfcx.ranger.entity.event.EventReview @@ -125,14 +126,17 @@ class EventDb(val realm: Realm) { realm.use { realm -> realm.executeTransactionAsync({ bgRealm -> bgRealm.delete(Event::class.java) + // clear cache endpoint + bgRealm.where(CachedEndpoint::class.java).like(CachedEndpoint.FIELD_ENDPOINT, + "guardians/group/*").findAll().deleteAllFromRealm() + bgRealm.where(CachedEndpoint::class.java).like(CachedEndpoint.FIELD_ENDPOINT, + "v2/events/?guardian_groups[]=*").findAll().deleteAllFromRealm() }, { // success callback(true) - realm.close() }, { // fail callback(false) - realm.close() }) } } diff --git a/app/src/main/java/org/rfcx/ranger/data/remote/domain/alert/GetEventsUseCase.kt b/app/src/main/java/org/rfcx/ranger/data/remote/domain/alert/GetEventsUseCase.kt index 074ab9ab4..030ee2b23 100644 --- a/app/src/main/java/org/rfcx/ranger/data/remote/domain/alert/GetEventsUseCase.kt +++ b/app/src/main/java/org/rfcx/ranger/data/remote/domain/alert/GetEventsUseCase.kt @@ -56,12 +56,15 @@ class GetEventsUseCase(private val eventRepository: EventRepository, if (events.isNotEmpty()) { if (isStarting) { val eventCached = eventDb.getEvents() - val r = events.filter { - - eventCached.firstOrNull { cached -> cached.id == it.id - && cached.rejectedCount == it.rejectedCount - && cached.confirmedCount == it.confirmedCount - } == null // new event? + var r = listOf() + if (events.isNotEmpty() && eventCached.isNotEmpty()) { + r = events.filter { + eventCached.firstOrNull { cached -> + cached.id == it.id + && cached.rejectedCount == it.rejectedCount + && cached.confirmedCount == it.confirmedCount + } == null // new event? + } } // has new event? if (r.isNotEmpty()) { diff --git a/app/src/main/java/org/rfcx/ranger/view/alerts/AlertViewModel.kt b/app/src/main/java/org/rfcx/ranger/view/alerts/AlertViewModel.kt index e0ab9029e..e74f97ee2 100644 --- a/app/src/main/java/org/rfcx/ranger/view/alerts/AlertViewModel.kt +++ b/app/src/main/java/org/rfcx/ranger/view/alerts/AlertViewModel.kt @@ -1,11 +1,9 @@ package org.rfcx.ranger.view.alerts import android.content.Context -import android.widget.Toast import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import org.rfcx.ranger.R import org.rfcx.ranger.data.local.ProfileData import org.rfcx.ranger.data.remote.ResponseCallback import org.rfcx.ranger.data.remote.domain.alert.GetEventsUseCase @@ -21,6 +19,7 @@ class AlertViewModel(private val context: Context, private val profileData: Prof private val _observeGuardianGroup = MutableLiveData() val observeGuardianGroup: LiveData = _observeGuardianGroup + private var _isLoading: Boolean = false fun resumed() { hasGuardianGroup = profileData.hasGuardianGroup() @@ -29,6 +28,7 @@ class AlertViewModel(private val context: Context, private val profileData: Prof } private fun loadAlerts() { + _isLoading = true val group = profileData.getGuardianGroup() ?: return val requestFactory = EventsRequestFactory(listOf(group.shortname), "measured_at", "DESC", @@ -36,10 +36,11 @@ class AlertViewModel(private val context: Context, private val profileData: Prof eventsUserCase.execute(object : ResponseCallback, Int>> { override fun onSuccess(t: Pair, Int>) { + _isLoading = false } override fun onError(e: Throwable) { - + _isLoading = false } }, requestFactory) } diff --git a/app/src/main/java/org/rfcx/ranger/view/alerts/AlertsFragment.kt b/app/src/main/java/org/rfcx/ranger/view/alerts/AlertsFragment.kt index f807e9bc2..9b5d8a5e0 100644 --- a/app/src/main/java/org/rfcx/ranger/view/alerts/AlertsFragment.kt +++ b/app/src/main/java/org/rfcx/ranger/view/alerts/AlertsFragment.kt @@ -19,10 +19,12 @@ import org.rfcx.ranger.view.alert.AlertBottomDialogFragment import org.rfcx.ranger.view.alert.AlertListener import org.rfcx.ranger.view.base.BaseFragment -class AlertsFragment : BaseFragment(), AlertListener, AlertsNewInstanceListener { +class AlertsFragment : BaseFragment(), AlertListener, AlertsParentListener { private val alertViewModel: AlertViewModel by viewModel() private val analytics by lazy { context?.let { Analytics(it) } } + private val groupAlertsFragment by lazy { GroupAlertsFragment.newInstance() } + private val allAlertsFragment by lazy { AllAlertsFragment.newInstance() } private val observeGuardianGroup = Observer { if (it) { @@ -113,16 +115,16 @@ class AlertsFragment : BaseFragment(), AlertListener, AlertsNewInstanceListener private fun startTabSelected(position: Int) { when (position) { 0 -> { - startFragment(GroupAlertsFragment.newInstance(), GroupAlertsFragment.tag) + startFragment(groupAlertsFragment, GroupAlertsFragment.tag) } 1 -> { - startFragment(AllAlertsFragment.newInstance(), AllAlertsFragment.tag) + startFragment(allAlertsFragment, AllAlertsFragment.tag) } } } - override fun emptyAlert() { - startFragment(EmptyAlertFragment.newInstance(), EmptyAlertFragment.tag) + override fun showEmptyView(show: Boolean) { + emptyView.visibility = if (show) View.VISIBLE else View.GONE } private fun startFragment(fragment: Fragment, tag: String) { @@ -157,6 +159,6 @@ class AlertsFragment : BaseFragment(), AlertListener, AlertsNewInstanceListener } } -interface AlertsNewInstanceListener { - fun emptyAlert() +interface AlertsParentListener { + fun showEmptyView(show: Boolean) } \ No newline at end of file diff --git a/app/src/main/java/org/rfcx/ranger/view/alerts/AllAlertsFragment.kt b/app/src/main/java/org/rfcx/ranger/view/alerts/AllAlertsFragment.kt index b79259b95..a3e8d9374 100644 --- a/app/src/main/java/org/rfcx/ranger/view/alerts/AllAlertsFragment.kt +++ b/app/src/main/java/org/rfcx/ranger/view/alerts/AllAlertsFragment.kt @@ -44,8 +44,9 @@ class AllAlertsFragment : BaseFragment(), AlertClickListener { it.success({ items -> alertsSwipeRefresh.isRefreshing = false if (items.isEmpty()) { - (parentFragment as AlertsNewInstanceListener?)?.emptyAlert() + (parentFragment as AlertsParentListener?)?.showEmptyView(true) } else { + (parentFragment as AlertsParentListener?)?.showEmptyView(false) val newList = mutableListOf() items.forEach { item -> newList.add(item.copy()) } alertsAdapter.submitList(newList) diff --git a/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupViewModel.kt b/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupViewModel.kt index 858d3a34e..54e6c0d32 100644 --- a/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupViewModel.kt +++ b/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupViewModel.kt @@ -88,10 +88,6 @@ class GuardianGroupViewModel(private val context: Context, private val getGuardi val preferences = Preferences.getInstance(context) preferences.putString(Preferences.SELECTED_GUARDIAN_GROUP_FULLNAME, guardianGroup.name) - // clear cache endpoint - cachedEndpointDb.clearCachedEndpoint("guardians/group/") - cachedEndpointDb.clearCachedEndpoint("v2/events/?guardian_groups[]=") - // sub&unsub email subscribeByEmail(guardianGroup.shortname) diff --git a/app/src/main/res/layout/fragment_alerts.xml b/app/src/main/res/layout/fragment_alerts.xml index 6aeb4452f..443d1ade5 100644 --- a/app/src/main/res/layout/fragment_alerts.xml +++ b/app/src/main/res/layout/fragment_alerts.xml @@ -30,4 +30,14 @@ android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@+id/alertsTabLayout" /> + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_empty_alert.xml b/app/src/main/res/layout/fragment_empty_alert.xml index 451395496..1710d2306 100644 --- a/app/src/main/res/layout/fragment_empty_alert.xml +++ b/app/src/main/res/layout/fragment_empty_alert.xml @@ -2,6 +2,7 @@ Date: Tue, 3 Mar 2020 12:14:21 +0700 Subject: [PATCH 08/30] CA-2046 Fixed should not select guardian group when show loading and delete GetGuardianGroupDisposable function --- .../ranger/view/profile/GuardianGroupActivity.kt | 9 +++++++-- .../ranger/view/profile/GuardianGroupViewModel.kt | 14 -------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupActivity.kt b/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupActivity.kt index 83d04bf32..c9adfb153 100644 --- a/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupActivity.kt +++ b/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupActivity.kt @@ -1,5 +1,6 @@ package org.rfcx.ranger.view.profile +import android.app.ProgressDialog import android.content.Context import android.content.Intent import android.os.Bundle @@ -27,6 +28,10 @@ class GuardianGroupActivity : BaseActivity() { setupToolbar() + val progressDialog = ProgressDialog(this) + progressDialog.setMessage("Loading...") + progressDialog.setCancelable(false) + // setup list guardianGroupRecycler.apply { layoutManager = LinearLayoutManager(this@GuardianGroupActivity) @@ -50,11 +55,11 @@ class GuardianGroupActivity : BaseActivity() { guardianGroupAdapter.mOnItemClickListener = object : OnItemClickListener { override fun onItemClick(guardianGroup: GuardianGroup) { - loadingProgress.visibility = View.VISIBLE + progressDialog.show() analytics.trackSetGuardianGroupEvent() viewModel.changeGuardianGroup(guardianGroup) { - loadingProgress.visibility = View.INVISIBLE if (it) { + progressDialog.dismiss() finish() } } diff --git a/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupViewModel.kt b/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupViewModel.kt index 54e6c0d32..b7a85bfdd 100644 --- a/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupViewModel.kt +++ b/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupViewModel.kt @@ -105,17 +105,3 @@ class GuardianGroupViewModel(private val context: Context, private val getGuardi } } } - -class GetGuardianGroupDisposable( - private val liveData: MutableLiveData>>) - : BaseDisposableSingle>() { - - override fun onError(e: Throwable, error: Result>) { - liveData.value = error - } - - override fun onSuccess(success: Result>) { - liveData.value = success - } - -} \ No newline at end of file From e5dedf91f7e251d0b31f4403fd99f24d266d1ce1 Mon Sep 17 00:00:00 2001 From: Anuphap Suwannamas Date: Tue, 3 Mar 2020 18:26:34 +0700 Subject: [PATCH 09/30] CA-2062 - Create logs of location service working and store it on firestore --- .../ranger/service/LocationTrackerService.kt | 66 ++++++++++++------- .../rfcx/ranger/util/LocationServiceLogs.kt | 49 ++++++++++++++ 2 files changed, 92 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/org/rfcx/ranger/util/LocationServiceLogs.kt diff --git a/app/src/main/java/org/rfcx/ranger/service/LocationTrackerService.kt b/app/src/main/java/org/rfcx/ranger/service/LocationTrackerService.kt index 1f6ae0c15..ecb2290b6 100644 --- a/app/src/main/java/org/rfcx/ranger/service/LocationTrackerService.kt +++ b/app/src/main/java/org/rfcx/ranger/service/LocationTrackerService.kt @@ -1,6 +1,7 @@ package org.rfcx.ranger.service import android.Manifest +import android.annotation.SuppressLint import android.app.* import android.content.Context import android.content.Intent @@ -8,21 +9,23 @@ import android.content.pm.PackageManager import android.graphics.BitmapFactory import android.location.* import android.media.RingtoneManager -import android.os.* +import android.os.Binder +import android.os.Build +import android.os.Bundle +import android.os.IBinder import android.util.Log import androidx.annotation.RequiresApi import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat import com.google.android.gms.location.LocationRequest +import com.google.firebase.firestore.FirebaseFirestore import io.realm.Realm import org.rfcx.ranger.BuildConfig import org.rfcx.ranger.R import org.rfcx.ranger.data.local.WeeklySummaryData import org.rfcx.ranger.entity.location.CheckIn import org.rfcx.ranger.localdb.LocationDb -import org.rfcx.ranger.util.Analytics -import org.rfcx.ranger.util.Preferences -import org.rfcx.ranger.util.RealmHelper +import org.rfcx.ranger.util.* import org.rfcx.ranger.view.MainActivityNew import java.util.* import java.util.concurrent.TimeUnit @@ -54,19 +57,16 @@ class LocationTrackerService : Service() { private var mLocationManager: LocationManager? = null private var isLocationAvailability: Boolean = true private var trackingStatTimer: Timer? = null + private var trackingWorkTimer: Timer? = null + private var trackingSatelliteTimer: Timer? = null private lateinit var weeklySummaryData: WeeklySummaryData var lastUpdated: Date? = null private val analytics by lazy { Analytics(this) } private var satelliteCount = 0 - private val delayTime = 1000L * 30L // 30 seconds - private var satelliteHandler: Handler? = null - private val satelliteRunnable = object : Runnable { - override fun run() { - analytics.trackSatelliteCount(satelliteCount) - satelliteHandler?.postDelayed(this, delayTime) - } - } + // Logs location service + private val logDb = FirebaseFirestore.getInstance() + private var logDocumentId: String? = null fun calculateTime(newTime: Date, lastTime: Date): Long { val differenceTime1 = newTime.time - lastTime.time @@ -142,14 +142,13 @@ class LocationTrackerService : Service() { override fun onSatelliteStatusChanged(status: GnssStatus?) { super.onSatelliteStatusChanged(status) val satCount = status?.satelliteCount ?: 0 - Log.i(TAG, "satellite count = $satCount") satelliteCount = satCount } }) } else { mLocationManager?.addGpsStatusListener { event -> if (event == GpsStatus.GPS_EVENT_SATELLITE_STATUS) { - var satCount : Int + var satCount: Int try { val status = mLocationManager?.getGpsStatus(null) val sat = status?.satellites?.iterator() @@ -163,7 +162,6 @@ class LocationTrackerService : Service() { e.printStackTrace() satCount = 0 // set min of satellite? } - Log.i(TAG, "satellite count = $satCount") satelliteCount = satCount } } @@ -178,9 +176,21 @@ class LocationTrackerService : Service() { getNotificationManager().notify(NOTIFICATION_LOCATION_ID, createLocationTrackerNotification(isLocationAvailability)) } - // Start handle run - satelliteHandler = Handler() - satelliteHandler?.postDelayed(satelliteRunnable, delayTime) + // Tracking last know location timer + trackingWorkTimer?.cancel() + logDocumentId = null // clear + LocationServiceLogs.start(logDb, this.getUserEmail()) { successful, documentId -> + if (successful) { + this.logDocumentId = documentId + documentId?.let { startLogLastLocation(it) } + } + } + + // Tracking satellite + trackingSatelliteTimer?.cancel() + trackingSatelliteTimer = fixedRateTimer("satellite_timer", false, 30 * 1000, 30 * 1000) { + analytics.trackSatelliteCount(satelliteCount) // tracking satellite count per 30s + } } catch (ex: SecurityException) { ex.printStackTrace() Log.w(TAG, "fail to request location update, ignore", ex) @@ -191,12 +201,21 @@ class LocationTrackerService : Service() { } + @SuppressLint("MissingPermission") + private fun startLogLastLocation(documentId: String) { + trackingWorkTimer = fixedRateTimer("last_location_timer", false, 0, 20 * 1000) { + val lastLocation = mLocationManager?.getLastKnownLocation(LocationManager.GPS_PROVIDER) + LocationServiceLogs.addLastKnowLocation(logDb, documentId, lastLocation) + } + } + override fun onDestroy() { super.onDestroy() Log.e(TAG, "onDestroy") - clearSatelliteHandler() mLocationManager?.removeUpdates(locationListener) - trackingStatTimer?.cancel() + // set end time of tracking service + logDocumentId?.let { LocationServiceLogs.setEndTime(logDb, it) } + clearTimer() } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { @@ -263,8 +282,9 @@ class LocationTrackerService : Service() { } } - private fun clearSatelliteHandler() { - satelliteHandler?.removeCallbacks(satelliteRunnable) - satelliteHandler = null + private fun clearTimer() { + trackingStatTimer?.cancel() + trackingSatelliteTimer?.cancel() + trackingWorkTimer?.cancel() } } \ No newline at end of file diff --git a/app/src/main/java/org/rfcx/ranger/util/LocationServiceLogs.kt b/app/src/main/java/org/rfcx/ranger/util/LocationServiceLogs.kt new file mode 100644 index 000000000..80c58ee55 --- /dev/null +++ b/app/src/main/java/org/rfcx/ranger/util/LocationServiceLogs.kt @@ -0,0 +1,49 @@ +package org.rfcx.ranger.util + +import android.location.Location +import android.util.Log +import com.google.firebase.firestore.FirebaseFirestore +import java.sql.Timestamp + +object LocationServiceLogs { + private const val db_collection_logs = "location-service-logs" + + fun start(db: FirebaseFirestore, from: String, callback: (Boolean, String?) -> Unit) { + val docData = hashMapOf("from" to from, + "start_time" to Timestamp(System.currentTimeMillis()).toString(), + "end_time" to "") + db.collection(db_collection_logs) + .add(docData) + .addOnSuccessListener { documentReference -> + callback(true, documentReference.id) + } + .addOnFailureListener { e -> + Log.e("LocationServiceLogs", "create new log: $e") + callback(false, null) + } + } + + + fun addLastKnowLocation(db: FirebaseFirestore, documentId:String, location: Location?) { + val docRef = db.collection(db_collection_logs).document(documentId) + + val docData = hashMapOf("location" to location.getLatLng(), + "time" to Timestamp(System.currentTimeMillis()).toString()) + + docRef.collection("locations") + .add(docData) + .addOnCompleteListener { + Log.i("LocationServiceLogs", "add location log: ${location.getLatLng()}") + } + } + + fun setEndTime(db: FirebaseFirestore, documentId: String) { + val docRef = db.collection(db_collection_logs).document(documentId) + docRef.update("end_time", Timestamp(System.currentTimeMillis()).toString()) + } + + fun Location?.getLatLng(): String { + this ?: return "null" + return "${this.latitude},${this.longitude}" + } +} \ No newline at end of file From 3c9e985fb9f1842e4a09dab5754289dbcd8a961c Mon Sep 17 00:00:00 2001 From: ratree Date: Tue, 3 Mar 2020 18:57:59 +0700 Subject: [PATCH 10/30] CA-2046 Add progress dialog in dependencies --- app/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index eb158bb05..b627ab9b9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -73,6 +73,9 @@ dependencies { /* viewpager2 */ implementation "androidx.viewpager2:viewpager2:1.0.0-alpha01" + /* dialog loading */ + implementation 'com.github.d-max:spots-dialog:1.1@aar' + /* firebase */ implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'com.google.firebase:firebase-core:17.2.0' From e61a0ec60e6d52b8017ec8ba77ca9f6bb38c1be2 Mon Sep 17 00:00:00 2001 From: ratree Date: Tue, 3 Mar 2020 18:58:49 +0700 Subject: [PATCH 11/30] CA-2046 Add progress dialog --- .../ranger/view/profile/GuardianGroupActivity.kt | 13 ++++++++----- app/src/main/res/values/dialog_loading.xml | 9 +++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 app/src/main/res/values/dialog_loading.xml diff --git a/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupActivity.kt b/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupActivity.kt index c9adfb153..9c9f8d4aa 100644 --- a/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupActivity.kt +++ b/app/src/main/java/org/rfcx/ranger/view/profile/GuardianGroupActivity.kt @@ -1,5 +1,6 @@ package org.rfcx.ranger.view.profile +import android.app.AlertDialog import android.app.ProgressDialog import android.content.Context import android.content.Intent @@ -7,6 +8,7 @@ import android.os.Bundle import android.view.View import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager +import dmax.dialog.SpotsDialog import kotlinx.android.synthetic.main.activity_guardian_group.* import org.koin.androidx.viewmodel.ext.android.viewModel import org.rfcx.ranger.R @@ -28,9 +30,10 @@ class GuardianGroupActivity : BaseActivity() { setupToolbar() - val progressDialog = ProgressDialog(this) - progressDialog.setMessage("Loading...") - progressDialog.setCancelable(false) + val dialog: AlertDialog = SpotsDialog.Builder() + .setContext(this) + .setTheme(R.style.Dialog_Loading) + .build() // setup list guardianGroupRecycler.apply { @@ -55,11 +58,11 @@ class GuardianGroupActivity : BaseActivity() { guardianGroupAdapter.mOnItemClickListener = object : OnItemClickListener { override fun onItemClick(guardianGroup: GuardianGroup) { - progressDialog.show() + dialog.show() analytics.trackSetGuardianGroupEvent() viewModel.changeGuardianGroup(guardianGroup) { if (it) { - progressDialog.dismiss() + dialog.dismiss() finish() } } diff --git a/app/src/main/res/values/dialog_loading.xml b/app/src/main/res/values/dialog_loading.xml new file mode 100644 index 000000000..17a82b113 --- /dev/null +++ b/app/src/main/res/values/dialog_loading.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file From fdf96a82d0d8d510934148d9a2f13237e209e722 Mon Sep 17 00:00:00 2001 From: ratree Date: Tue, 3 Mar 2020 19:02:14 +0700 Subject: [PATCH 12/30] CA-2046 Add translation language --- app/src/main/res/values-es/strings.xml | 1 + app/src/main/res/values-in/strings.xml | 1 + app/src/main/res/values-pt/strings.xml | 1 + app/src/main/res/values-th/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 5 files changed, 5 insertions(+) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 0e4cd6636..73e9c1aa0 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -73,6 +73,7 @@ Cargando ubicación… + Cargando… !Por favor active la ubicación de su dispositivo! Seguimiento de la ubicación Encendido diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 05fbbfb8f..748b67dbc 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -74,6 +74,7 @@ Silakan nyalakan lokasi perangkat! + Memuat… Pelacakan lokasi Di Mati diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 2a8c99937..894fe887a 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -73,6 +73,7 @@ Carregando localização… + Carregando… Ative a localização do dispositivo! Rastreamento de localização Ligado diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index b0cb6a7fe..06efa8842 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -73,6 +73,7 @@ กรุณาเปิดตำแหน่งอุปกรณ์ + กำลังโหลด... การติดตามตำแหน่ง เปิด ปิด diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4e0e9832a..1ba23349e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -74,6 +74,7 @@ Please enable your device\'s location settings + Loading… Location tracking On Off From f8b24f80c5310cb417d9916e3bdb37d55d1ea92c Mon Sep 17 00:00:00 2001 From: ratree Date: Wed, 4 Mar 2020 12:06:06 +0700 Subject: [PATCH 13/30] CA-2046 Change dialog spot color to colorPrimary --- app/src/main/res/values/dialog_loading.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/dialog_loading.xml b/app/src/main/res/values/dialog_loading.xml index 17a82b113..0a533cc24 100644 --- a/app/src/main/res/values/dialog_loading.xml +++ b/app/src/main/res/values/dialog_loading.xml @@ -3,7 +3,7 @@ \ No newline at end of file From 149de1b55ef7fc9f6061592360258e3a7d9986b0 Mon Sep 17 00:00:00 2001 From: ratree Date: Wed, 4 Mar 2020 12:13:16 +0700 Subject: [PATCH 14/30] CA-2055 Change to use another progress dialog --- .../org/rfcx/ranger/view/profile/ProfileFragment.kt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/rfcx/ranger/view/profile/ProfileFragment.kt b/app/src/main/java/org/rfcx/ranger/view/profile/ProfileFragment.kt index 65b7db8a1..eab42d77e 100644 --- a/app/src/main/java/org/rfcx/ranger/view/profile/ProfileFragment.kt +++ b/app/src/main/java/org/rfcx/ranger/view/profile/ProfileFragment.kt @@ -1,6 +1,7 @@ package org.rfcx.ranger.view.profile import android.annotation.SuppressLint +import android.app.AlertDialog import android.content.Context import android.content.Intent import android.net.Uri @@ -24,6 +25,7 @@ import org.rfcx.ranger.view.profile.editprofile.EditProfileActivity import org.rfcx.ranger.view.tutorial.TutorialActivity import android.app.ProgressDialog import android.os.Handler +import dmax.dialog.SpotsDialog class ProfileFragment : BaseFragment() { @@ -85,15 +87,16 @@ class ProfileFragment : BaseFragment() { profileViewModel.onTracingStatusChange() }) - val progressDialog = ProgressDialog(context) - progressDialog.setMessage("Loading...") - progressDialog.setCancelable(false) + val dialog: AlertDialog = SpotsDialog.Builder() + .setContext(context) + .setTheme(R.style.Dialog_Loading) + .build() profileViewModel.logoutState.observe(this, Observer { if (it) { - progressDialog.show() + dialog.show() } else { - progressDialog.dismiss() + dialog.dismiss() } }) } From deb6c6e2f387536f62b8e045645e3e06a1a6751c Mon Sep 17 00:00:00 2001 From: ratree Date: Thu, 5 Mar 2020 15:41:36 +0700 Subject: [PATCH 15/30] CA-2073 Fix can't upload profile image more than 2000px x 2000px --- .../editprofile/EditProfileViewModel.kt | 74 ++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/rfcx/ranger/view/profile/editprofile/EditProfileViewModel.kt b/app/src/main/java/org/rfcx/ranger/view/profile/editprofile/EditProfileViewModel.kt index 98072f23c..b48b029ee 100644 --- a/app/src/main/java/org/rfcx/ranger/view/profile/editprofile/EditProfileViewModel.kt +++ b/app/src/main/java/org/rfcx/ranger/view/profile/editprofile/EditProfileViewModel.kt @@ -1,6 +1,8 @@ package org.rfcx.ranger.view.profile.editprofile import android.content.Context +import android.content.ContextWrapper +import android.graphics.Bitmap import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -15,6 +17,10 @@ import org.rfcx.ranger.entity.ProfilePhotoResponse import org.rfcx.ranger.util.getResultError import org.rfcx.ranger.util.updateUserProfile import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.OutputStream +import java.util.* class EditProfileViewModel(private val context: Context, private val profilePhotoUseCase: ProfilePhotoUseCase) : ViewModel() { @@ -25,7 +31,7 @@ class EditProfileViewModel(private val context: Context, private val profilePhot _status.value = Result.Loading val imageFile = File(path) - val compressedFile = compressFile(context, imageFile) + val compressedFile = checkSizeImage(context, imageFile) profilePhotoUseCase.execute(object : DisposableSingleObserver() { override fun onSuccess(t: ProfilePhotoResponse) { @@ -46,11 +52,75 @@ class EditProfileViewModel(private val context: Context, private val profilePhot return MultipartBody.Part.createFormData("file", file.name, requestFile) } - private fun compressFile(context: Context?, file: File): File { + private fun checkSizeImage(context: Context?, file: File): File { + val compressed = Compressor(context) + .setQuality(75) + .compressToBitmap(file) + val excessHeight = compressed.height - 2000 + val excessWidth = compressed.width - 2000 + + if (compressed.width > 2000 && compressed.height > 2000) { + when ((excessWidth).compareTo(excessHeight)) { + -1 -> { // less than + return bitmapToFile(resizeBitmap(compressed, compressed.width - excessHeight, compressed.height - excessHeight)) + } + 0 -> { // equals + return bitmapToFile(resizeBitmap(compressed, compressed.width - excessWidth, compressed.height - excessHeight)) + } + 1 -> { // more than + return bitmapToFile(resizeBitmap(compressed, compressed.width - excessWidth, compressed.height - excessWidth)) + } + } + } else if (compressed.width > 2000) { + return bitmapToFile(resizeBitmap(compressed, compressed.width - excessWidth, compressed.height - excessWidth)) + } else if (compressed.height > 2000) { + return bitmapToFile(resizeBitmap(compressed, compressed.width - excessHeight, compressed.height - excessHeight)) + } + + val compressedFile = Compressor(context) + .setQuality(75) + .compressToFile(file) + if (compressedFile.length() > 1_000_000) { + return compressFile(context, compressedFile) + } + return compressedFile + } + + // Method to save an bitmap to a file + private fun bitmapToFile(bitmap: Bitmap): File { + // Get the context wrapper + val wrapper = ContextWrapper(context) + // Initialize a new file instance to save bitmap object + var file = wrapper.getDir("Images", Context.MODE_PRIVATE) + file = File(file, "${UUID.randomUUID()}.jpg") + + try { + // Compress the bitmap and save in jpg format + val stream: OutputStream = FileOutputStream(file) + bitmap.compress(Bitmap.CompressFormat.JPEG, 75, stream) + stream.flush() + stream.close() + } catch (e: IOException) { + e.printStackTrace() + } + + if (file.length() > 2_000_000) { + return compressFile(context, file) + } + + return file + } + + private fun resizeBitmap(bitmap: Bitmap, width: Int, height: Int): Bitmap { + return Bitmap.createScaledBitmap(bitmap, width, height, false) + } + + private fun compressFile(context: Context?, file: File): File { if (file.length() <= 0) { return file } + val compressed = Compressor(context) .setQuality(75) .compressToFile(file) From b03af88eb4afa1f0da78c4d3b4a03134f5c9c255 Mon Sep 17 00:00:00 2001 From: ratree Date: Thu, 5 Mar 2020 19:14:49 +0700 Subject: [PATCH 16/30] CA-2073 Fixed scaled bitmap maintaining aspect ratio --- .../editprofile/EditProfileViewModel.kt | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/rfcx/ranger/view/profile/editprofile/EditProfileViewModel.kt b/app/src/main/java/org/rfcx/ranger/view/profile/editprofile/EditProfileViewModel.kt index b48b029ee..cb913813c 100644 --- a/app/src/main/java/org/rfcx/ranger/view/profile/editprofile/EditProfileViewModel.kt +++ b/app/src/main/java/org/rfcx/ranger/view/profile/editprofile/EditProfileViewModel.kt @@ -56,25 +56,33 @@ class EditProfileViewModel(private val context: Context, private val profilePhot val compressed = Compressor(context) .setQuality(75) .compressToBitmap(file) - val excessHeight = compressed.height - 2000 - val excessWidth = compressed.width - 2000 + val imageHeight = compressed.height + val imageWidth = compressed.width + var newWidth = 0 + var newHeight = 0 - if (compressed.width > 2000 && compressed.height > 2000) { - when ((excessWidth).compareTo(excessHeight)) { + if (imageHeight > 2000 && imageWidth > 2000) { + when ((imageWidth).compareTo(imageHeight)) { -1 -> { // less than - return bitmapToFile(resizeBitmap(compressed, compressed.width - excessHeight, compressed.height - excessHeight)) + newWidth = (imageWidth * 2000) / imageHeight + newHeight = 2000 } 0 -> { // equals - return bitmapToFile(resizeBitmap(compressed, compressed.width - excessWidth, compressed.height - excessHeight)) + newWidth = 2000 + newHeight = 2000 } 1 -> { // more than - return bitmapToFile(resizeBitmap(compressed, compressed.width - excessWidth, compressed.height - excessWidth)) + newWidth = 2000 + newHeight = (imageHeight * 2000) / imageWidth } } + return bitmapToFile(Bitmap.createScaledBitmap(compressed, newWidth, newHeight, false)) } else if (compressed.width > 2000) { - return bitmapToFile(resizeBitmap(compressed, compressed.width - excessWidth, compressed.height - excessWidth)) + newHeight = (imageHeight * 2000) / imageWidth + return bitmapToFile(Bitmap.createScaledBitmap(compressed, 2000, newHeight, false)) } else if (compressed.height > 2000) { - return bitmapToFile(resizeBitmap(compressed, compressed.width - excessHeight, compressed.height - excessHeight)) + newWidth = (imageWidth * 2000) / imageHeight + return bitmapToFile(Bitmap.createScaledBitmap(compressed, newWidth, 2000, false)) } val compressedFile = Compressor(context) @@ -112,10 +120,6 @@ class EditProfileViewModel(private val context: Context, private val profilePhot return file } - private fun resizeBitmap(bitmap: Bitmap, width: Int, height: Int): Bitmap { - return Bitmap.createScaledBitmap(bitmap, width, height, false) - } - private fun compressFile(context: Context?, file: File): File { if (file.length() <= 0) { return file From 3872b58df53c65067f71d0171cc4356ee1566865 Mon Sep 17 00:00:00 2001 From: ratree Date: Mon, 9 Mar 2020 09:41:00 +0700 Subject: [PATCH 17/30] CA-2047 Added change profile photo button --- .../rfcx/ranger/view/profile/ProfileFragment.kt | 2 ++ app/src/main/res/layout/fragment_profile.xml | 14 ++++++++++++++ app/src/main/res/values-in/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/rfcx/ranger/view/profile/ProfileFragment.kt b/app/src/main/java/org/rfcx/ranger/view/profile/ProfileFragment.kt index eab42d77e..b109d58d5 100644 --- a/app/src/main/java/org/rfcx/ranger/view/profile/ProfileFragment.kt +++ b/app/src/main/java/org/rfcx/ranger/view/profile/ProfileFragment.kt @@ -76,9 +76,11 @@ class ProfileFragment : BaseFragment() { val loginWith = context?.let { Preferences.getInstance(it).getString(Preferences.LOGIN_WITH) } if (loginWith == LOGIN_WITH_EMAIL) { + changeImageProfileTextView.visibility = View.VISIBLE changePasswordTextView.visibility = View.VISIBLE userProfileImageView.visibility = View.VISIBLE } else { + changeImageProfileTextView.visibility = View.GONE changePasswordTextView.visibility = View.GONE userProfileImageView.visibility = View.GONE } diff --git a/app/src/main/res/layout/fragment_profile.xml b/app/src/main/res/layout/fragment_profile.xml index 37c7e7d9e..d104613a2 100644 --- a/app/src/main/res/layout/fragment_profile.xml +++ b/app/src/main/res/layout/fragment_profile.xml @@ -403,6 +403,20 @@ android:textColor="@color/text_primary" android:textSize="@dimen/text_small" /> + + Ada yang salah. Silakan coba lagi nanti. Akun - Ganti Foto Profil + Ganti foto profil Sunting profil diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5f12f2306..19d86b83e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -236,7 +236,7 @@ You have reached max selectable. Try remove some items before adding more attachments. Account - Change Profile Photo + Change profile photo Edit Profile From 03c4751c9e324eba5d430d3031f5408d4fc82851 Mon Sep 17 00:00:00 2001 From: ratree Date: Mon, 9 Mar 2020 12:31:53 +0700 Subject: [PATCH 18/30] Remove ic_ranger_icon from drawable and added in drawable-xxhdpi and drawable-xxxhdpi --- .../ic_ranger_icon.xml | 0 .../main/res/drawable-xxhdpi/ic_ranger_icon.png | Bin 0 -> 24612 bytes .../res/drawable-xxxhdpi/ic_ranger_icon.png | Bin 0 -> 34813 bytes 3 files changed, 0 insertions(+), 0 deletions(-) rename app/src/main/res/{drawable => drawable-v24}/ic_ranger_icon.xml (100%) create mode 100644 app/src/main/res/drawable-xxhdpi/ic_ranger_icon.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_ranger_icon.png diff --git a/app/src/main/res/drawable/ic_ranger_icon.xml b/app/src/main/res/drawable-v24/ic_ranger_icon.xml similarity index 100% rename from app/src/main/res/drawable/ic_ranger_icon.xml rename to app/src/main/res/drawable-v24/ic_ranger_icon.xml diff --git a/app/src/main/res/drawable-xxhdpi/ic_ranger_icon.png b/app/src/main/res/drawable-xxhdpi/ic_ranger_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cf5a3581b0a928c815fc8ed1f63dc3c7aee6cd5a GIT binary patch literal 24612 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 S!Wno6L`FhUy!@?E!2bbs^8R}O literal 0 HcmV?d00001 From 5e9d0341a622d3d99c9717e44bcbe519c3d8431f Mon Sep 17 00:00:00 2001 From: ratree Date: Mon, 9 Mar 2020 12:33:17 +0700 Subject: [PATCH 19/30] Fixed dialog loading --- .../rfcx/ranger/view/profile/ProfileFragment.kt | 17 ++++++++++++----- .../ranger/view/profile/ProfileViewModel.kt | 1 + 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/rfcx/ranger/view/profile/ProfileFragment.kt b/app/src/main/java/org/rfcx/ranger/view/profile/ProfileFragment.kt index b109d58d5..01a45210c 100644 --- a/app/src/main/java/org/rfcx/ranger/view/profile/ProfileFragment.kt +++ b/app/src/main/java/org/rfcx/ranger/view/profile/ProfileFragment.kt @@ -35,6 +35,13 @@ class ProfileFragment : BaseFragment() { lateinit var listener: MainActivityEventListener private lateinit var viewDataBinding: FragmentProfileBinding + private val dialog: AlertDialog by lazy { + SpotsDialog.Builder() + .setContext(context) + .setTheme(R.style.Dialog_Loading) + .build() + } + override fun onAttach(context: Context) { super.onAttach(context) listener = (context as MainActivityEventListener) @@ -89,11 +96,6 @@ class ProfileFragment : BaseFragment() { profileViewModel.onTracingStatusChange() }) - val dialog: AlertDialog = SpotsDialog.Builder() - .setContext(context) - .setTheme(R.style.Dialog_Loading) - .build() - profileViewModel.logoutState.observe(this, Observer { if (it) { dialog.show() @@ -158,6 +160,11 @@ class ProfileFragment : BaseFragment() { profileViewModel.updateSiteName() } + override fun onPause() { + super.onPause() + dialog.dismiss() + } + private fun handleShowSnackbarResult(requestCode: Int, resultCode: Int, intentData: Intent?) { if (requestCode != REQUEST_CODE || resultCode != RESULT_CODE || intentData == null) return diff --git a/app/src/main/java/org/rfcx/ranger/view/profile/ProfileViewModel.kt b/app/src/main/java/org/rfcx/ranger/view/profile/ProfileViewModel.kt index ad4a85e31..43eb3d22d 100644 --- a/app/src/main/java/org/rfcx/ranger/view/profile/ProfileViewModel.kt +++ b/app/src/main/java/org/rfcx/ranger/view/profile/ProfileViewModel.kt @@ -90,6 +90,7 @@ class ProfileViewModel(private val context: Context, private val profileData: Pr if(profileData.getReceiveNotificationByEmail()){ unsubscribeUseCase.execute(object : DisposableSingleObserver() { override fun onSuccess(t: SubscribeResponse) { + _logoutState.value = false context.logout() } From 57a37987803c91adf1689ccdb4bad8fa8bf69ee2 Mon Sep 17 00:00:00 2001 From: ratree Date: Mon, 9 Mar 2020 12:50:34 +0700 Subject: [PATCH 20/30] Fixed padding in tutorial --- .../java/org/rfcx/ranger/view/tutorial/TutorialActivity.kt | 6 +++--- app/src/main/res/drawable/ic_dot_grey.xml | 4 ++-- app/src/main/res/drawable/ic_dot_white.xml | 4 ++-- app/src/main/res/layout/activity_tutorial.xml | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/rfcx/ranger/view/tutorial/TutorialActivity.kt b/app/src/main/java/org/rfcx/ranger/view/tutorial/TutorialActivity.kt index 687e4b631..4eac6dcda 100644 --- a/app/src/main/java/org/rfcx/ranger/view/tutorial/TutorialActivity.kt +++ b/app/src/main/java/org/rfcx/ranger/view/tutorial/TutorialActivity.kt @@ -86,7 +86,7 @@ class TutorialActivity : AppCompatActivity() { for (i in indicators.indices) { indicators[i] = ImageView(applicationContext) indicators[i].apply { - this?.setImageDrawable(context.getImage(R.drawable.ic_dot_white)) + this?.setImageDrawable(context.getImage(R.drawable.ic_dot_grey)) this?.layoutParams = layoutParams } indicatorsContainer.addView(indicators[i]) @@ -99,10 +99,10 @@ class TutorialActivity : AppCompatActivity() { val view: View = indicatorsContainer.getChildAt(i) if (view is ImageView) { if (i == index) { - view.setImageDrawable(this.getImage(R.drawable.ic_dot_grey)) + view.setImageDrawable(this.getImage(R.drawable.ic_dot_white)) } else { - view.setImageDrawable(this.getImage(R.drawable.ic_dot_white)) + view.setImageDrawable(this.getImage(R.drawable.ic_dot_grey)) } } diff --git a/app/src/main/res/drawable/ic_dot_grey.xml b/app/src/main/res/drawable/ic_dot_grey.xml index 38960d7bc..541fd0e44 100644 --- a/app/src/main/res/drawable/ic_dot_grey.xml +++ b/app/src/main/res/drawable/ic_dot_grey.xml @@ -1,6 +1,6 @@ + app:layout_constraintBottom_toBottomOf="@id/nextTextView" + app:layout_constraintTop_toTopOf="@id/nextTextView"/> \ No newline at end of file From f3410869a967e9728a014f5ac3ce918c53e2120c Mon Sep 17 00:00:00 2001 From: ratree Date: Mon, 9 Mar 2020 14:18:45 +0700 Subject: [PATCH 21/30] CA-2101 User can get GPS coords for an event (DMS) --- .../java/org/rfcx/ranger/util/EventExt.kt | 32 +++++++++++++++++++ .../view/alert/AlertBottomDialogFragment.kt | 6 ++++ .../view/map/MapDetailBottomSheetFragment.kt | 2 +- app/src/main/res/drawable/ic_pin_grey.xml | 5 +++ .../main/res/layout/fragment_dialog_alert.xml | 12 +++++++ 5 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/ic_pin_grey.xml diff --git a/app/src/main/java/org/rfcx/ranger/util/EventExt.kt b/app/src/main/java/org/rfcx/ranger/util/EventExt.kt index 11b4002af..ea4cfe1c2 100644 --- a/app/src/main/java/org/rfcx/ranger/util/EventExt.kt +++ b/app/src/main/java/org/rfcx/ranger/util/EventExt.kt @@ -2,10 +2,13 @@ package org.rfcx.ranger.util import android.annotation.SuppressLint import android.content.Context +import android.location.Location +import com.google.protobuf.ByteString import org.rfcx.ranger.R import org.rfcx.ranger.adapter.entity.BaseItem import org.rfcx.ranger.entity.event.Event import org.rfcx.ranger.entity.event.ReviewEventFactory +import kotlin.math.absoluteValue fun Event.getIconRes(): Int { @@ -57,6 +60,35 @@ fun Event.toEventItem(state: String?): EventItem { return EventItem(this, s) } +fun Event.latitudeAsDMS(decimalPlace: Int): String { + val direction = if (this.latitude!! > 0) "N" else "S" + var strLatitude = Location.convert(latitude!!.absoluteValue, Location.FORMAT_SECONDS) + strLatitude = replaceDelimiters(strLatitude, decimalPlace) + strLatitude += " $direction" + return strLatitude +} + +fun Event.longitudeAsDMS(decimalPlace: Int): String { + val direction = if (this.longitude!! > 0) "W" else "E" + var strLongitude = Location.convert(this.longitude!!.absoluteValue, Location.FORMAT_SECONDS) + strLongitude = replaceDelimiters(strLongitude, decimalPlace) + strLongitude += " $direction" + return strLongitude +} + +private fun replaceDelimiters(str: String, decimalPlace: Int): String { + var str = str + str = str.replaceFirst(":".toRegex(), "°") + str = str.replaceFirst(":".toRegex(), "'") + val pointIndex = str.indexOf(".") + val endIndex = pointIndex + 1 + decimalPlace + if (endIndex < str.length) { + str = str.substring(0, endIndex) + } + str += "\"" + return str +} + data class EventItem(var event: Event, var state: State = State.NONE) : BaseItem { @SuppressLint("DefaultLocale") diff --git a/app/src/main/java/org/rfcx/ranger/view/alert/AlertBottomDialogFragment.kt b/app/src/main/java/org/rfcx/ranger/view/alert/AlertBottomDialogFragment.kt index fe11a2558..0255011ae 100644 --- a/app/src/main/java/org/rfcx/ranger/view/alert/AlertBottomDialogFragment.kt +++ b/app/src/main/java/org/rfcx/ranger/view/alert/AlertBottomDialogFragment.kt @@ -135,6 +135,12 @@ class AlertBottomDialogFragment : BaseBottomSheetDialogFragment() { agreeTextView.text = it.confirmedCount.toString() rejectTextView.text = it.rejectedCount.toString() + if (it.latitude != null || it.longitude != null) { + locationTextView.text = StringBuilder(it.latitudeAsDMS(5)) + .append(",") + .append(it.longitudeAsDMS(5)) + } + if (state == "CONFIRM") { linearLayout.visibility = View.VISIBLE updateConfirmCount(it) diff --git a/app/src/main/java/org/rfcx/ranger/view/map/MapDetailBottomSheetFragment.kt b/app/src/main/java/org/rfcx/ranger/view/map/MapDetailBottomSheetFragment.kt index c9f2dca79..976a347bf 100644 --- a/app/src/main/java/org/rfcx/ranger/view/map/MapDetailBottomSheetFragment.kt +++ b/app/src/main/java/org/rfcx/ranger/view/map/MapDetailBottomSheetFragment.kt @@ -44,7 +44,7 @@ class MapDetailBottomSheetFragment : BaseFragment() { if (report != null) { reportTypeNameTextView.text = context?.let { report.getLocalisedValue(it) } val latLon = StringBuilder( - report.longitude.limitDecimalPlace(6)) + report.latitude.limitDecimalPlace(6)) .append(",") .append(report.longitude.limitDecimalPlace(6)) reportLocationTextView.text = latLon diff --git a/app/src/main/res/drawable/ic_pin_grey.xml b/app/src/main/res/drawable/ic_pin_grey.xml new file mode 100644 index 000000000..90f026ccf --- /dev/null +++ b/app/src/main/res/drawable/ic_pin_grey.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_dialog_alert.xml b/app/src/main/res/layout/fragment_dialog_alert.xml index 521703957..478108a82 100644 --- a/app/src/main/res/layout/fragment_dialog_alert.xml +++ b/app/src/main/res/layout/fragment_dialog_alert.xml @@ -71,6 +71,7 @@ android:layout_height="@dimen/report_icon_size" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" tools:src="@drawable/ic_vehicle" /> + + From 83cd8a1625ed8100722c603e509a48c43eba8f7e Mon Sep 17 00:00:00 2001 From: Komkrit Banglad Date: Mon, 9 Mar 2020 15:22:55 +0700 Subject: [PATCH 22/30] CA-2062 Unregister Gnss callback when service stopped. --- .../ranger/service/LocationTrackerService.kt | 71 ++++++++++++------- 1 file changed, 45 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/org/rfcx/ranger/service/LocationTrackerService.kt b/app/src/main/java/org/rfcx/ranger/service/LocationTrackerService.kt index ecb2290b6..50fb91852 100644 --- a/app/src/main/java/org/rfcx/ranger/service/LocationTrackerService.kt +++ b/app/src/main/java/org/rfcx/ranger/service/LocationTrackerService.kt @@ -111,6 +111,38 @@ class LocationTrackerService : Service() { } + private val gnssStatusCallback = @RequiresApi(Build.VERSION_CODES.N) + object : GnssStatus.Callback() { + override fun onSatelliteStatusChanged(status: GnssStatus?) { + super.onSatelliteStatusChanged(status) + val satCount = status?.satelliteCount ?: 0 + satelliteCount = satCount + } + } + + @Deprecated("For old version") + @SuppressLint("MissingPermission") + private val gpsStatusListener = GpsStatus.Listener { event -> + if (event == GpsStatus.GPS_EVENT_SATELLITE_STATUS) { + var satCount: Int + try { + val status = mLocationManager?.getGpsStatus(null) + val sat = status?.satellites?.iterator() + satCount = 0 + if (sat != null) { + while (sat.hasNext()) { + satCount++ + } + } + } catch (e: java.lang.Exception) { + e.printStackTrace() + satCount = 0 // set min of satellite? + } + satelliteCount = satCount + } + } + + override fun onBind(p0: Intent?): IBinder? { return binder } @@ -138,33 +170,9 @@ class LocationTrackerService : Service() { // Get satellite count if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - mLocationManager?.registerGnssStatusCallback(object : GnssStatus.Callback() { - override fun onSatelliteStatusChanged(status: GnssStatus?) { - super.onSatelliteStatusChanged(status) - val satCount = status?.satelliteCount ?: 0 - satelliteCount = satCount - } - }) + mLocationManager?.registerGnssStatusCallback(gnssStatusCallback) } else { - mLocationManager?.addGpsStatusListener { event -> - if (event == GpsStatus.GPS_EVENT_SATELLITE_STATUS) { - var satCount: Int - try { - val status = mLocationManager?.getGpsStatus(null) - val sat = status?.satellites?.iterator() - satCount = 0 - if (sat != null) { - while (sat.hasNext()) { - satCount++ - } - } - } catch (e: java.lang.Exception) { - e.printStackTrace() - satCount = 0 // set min of satellite? - } - satelliteCount = satCount - } - } + mLocationManager?.addGpsStatusListener(gpsStatusListener) } // Start notification on duty tracking @@ -213,6 +221,17 @@ class LocationTrackerService : Service() { super.onDestroy() Log.e(TAG, "onDestroy") mLocationManager?.removeUpdates(locationListener) + + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + mLocationManager?.unregisterGnssStatusCallback(gnssStatusCallback) + } else { + mLocationManager?.removeGpsStatusListener(gpsStatusListener) + } + } catch (e: Exception) { + e.printStackTrace() + } + // set end time of tracking service logDocumentId?.let { LocationServiceLogs.setEndTime(logDb, it) } clearTimer() From d361e411782ed5828b1fc865bc05fcdd35a307ed Mon Sep 17 00:00:00 2001 From: ratree Date: Mon, 9 Mar 2020 15:40:41 +0700 Subject: [PATCH 23/30] CA-2101 Added set coordinates page --- app/src/main/AndroidManifest.xml | 2 + .../ranger/view/profile/ProfileFragment.kt | 5 + .../coordinates/CoordinatesActivity.kt | 70 +++++++ .../main/res/layout/activity_coordinates.xml | 180 ++++++++++++++++++ app/src/main/res/layout/fragment_profile.xml | 17 ++ app/src/main/res/values/strings.xml | 9 + 6 files changed, 283 insertions(+) create mode 100644 app/src/main/java/org/rfcx/ranger/view/profile/coordinates/CoordinatesActivity.kt create mode 100644 app/src/main/res/layout/activity_coordinates.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1948c28fb..de4f4ff94 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -36,6 +36,8 @@ android:resource="@xml/file_paths" /> + diff --git a/app/src/main/java/org/rfcx/ranger/view/profile/ProfileFragment.kt b/app/src/main/java/org/rfcx/ranger/view/profile/ProfileFragment.kt index 01a45210c..043fc85f1 100644 --- a/app/src/main/java/org/rfcx/ranger/view/profile/ProfileFragment.kt +++ b/app/src/main/java/org/rfcx/ranger/view/profile/ProfileFragment.kt @@ -26,6 +26,7 @@ import org.rfcx.ranger.view.tutorial.TutorialActivity import android.app.ProgressDialog import android.os.Handler import dmax.dialog.SpotsDialog +import org.rfcx.ranger.view.profile.coordinates.CoordinatesActivity class ProfileFragment : BaseFragment() { @@ -153,6 +154,10 @@ class ProfileFragment : BaseFragment() { viewDataBinding.onClickProfilePhoto = View.OnClickListener { context?.let { it1 -> EditProfileActivity.startActivity(it1) } } + + viewDataBinding.onClickCoordinates = View.OnClickListener { + context?.let { it1 -> CoordinatesActivity.startActivity(it1) } + } } override fun onStart() { diff --git a/app/src/main/java/org/rfcx/ranger/view/profile/coordinates/CoordinatesActivity.kt b/app/src/main/java/org/rfcx/ranger/view/profile/coordinates/CoordinatesActivity.kt new file mode 100644 index 000000000..3c8fbec3b --- /dev/null +++ b/app/src/main/java/org/rfcx/ranger/view/profile/coordinates/CoordinatesActivity.kt @@ -0,0 +1,70 @@ +package org.rfcx.ranger.view.profile.coordinates + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import kotlinx.android.synthetic.main.activity_coordinates.* +import kotlinx.android.synthetic.main.activity_feedback.toolbar +import org.rfcx.ranger.R + +class CoordinatesActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_coordinates) + + setupToolbar() + + ddLayout.setOnClickListener { + + checkDDImageView.visibility = View.VISIBLE + checkDDMImageView.visibility = View.INVISIBLE + checkDMSImageView.visibility = View.INVISIBLE + finish() + + } + + ddmLayout.setOnClickListener { + + checkDDImageView.visibility = View.INVISIBLE + checkDDMImageView.visibility = View.VISIBLE + checkDMSImageView.visibility = View.INVISIBLE + finish() + + } + + dmsLayout.setOnClickListener { + + checkDDImageView.visibility = View.INVISIBLE + checkDDMImageView.visibility = View.INVISIBLE + checkDMSImageView.visibility = View.VISIBLE + finish() + + } + + } + + private fun setupToolbar() { + setSupportActionBar(toolbar) + supportActionBar?.apply { + setDisplayHomeAsUpEnabled(true) + setDisplayShowHomeEnabled(true) + elevation = 0f + title = getString(R.string.coordinates) + } + } + + override fun onSupportNavigateUp(): Boolean { + onBackPressed() + return true + } + + companion object { + fun startActivity(context: Context) { + val intent = Intent(context, CoordinatesActivity::class.java) + context.startActivity(intent) + } + } +} diff --git a/app/src/main/res/layout/activity_coordinates.xml b/app/src/main/res/layout/activity_coordinates.xml new file mode 100644 index 000000000..ee2586ea2 --- /dev/null +++ b/app/src/main/res/layout/activity_coordinates.xml @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_profile.xml b/app/src/main/res/layout/fragment_profile.xml index d104613a2..b6f92204b 100644 --- a/app/src/main/res/layout/fragment_profile.xml +++ b/app/src/main/res/layout/fragment_profile.xml @@ -39,6 +39,10 @@ + + + + Create new reports View reports by location App intro + Coordinates Sign in @@ -82,6 +83,14 @@ Guardian group Notifications + 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 + Last\nMonth Last\nWeek Last\n24 hours From cc902c48c71877e8aa7a30d4881a4d9f84d3df02 Mon Sep 17 00:00:00 2001 From: ratree Date: Mon, 9 Mar 2020 16:08:45 +0700 Subject: [PATCH 24/30] CA-2101 Added "COORDINATES_FORMAT" in preferences --- .../java/org/rfcx/ranger/util/Preferences.kt | 1 + .../coordinates/CoordinatesActivity.kt | 50 +++++++++++++------ 2 files changed, 36 insertions(+), 15 deletions(-) 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 6552a131d..dafcba503 100644 --- a/app/src/main/java/org/rfcx/ranger/util/Preferences.kt +++ b/app/src/main/java/org/rfcx/ranger/util/Preferences.kt @@ -42,6 +42,7 @@ class Preferences(context: Context) { const val IS_FIRST_TIME = "${PREFIX}IS_FIRST_TIME" const val IMAGE_PROFILE = "${PREFIX}IMAGE_PROFILE" const val EMAIL_SUBSCRIBE = "${PREFIX}EMAIL_SUBSCRIBE" + const val COORDINATES_FORMAT = "${PREFIX}COORDINATES_FORMAT" } init { diff --git a/app/src/main/java/org/rfcx/ranger/view/profile/coordinates/CoordinatesActivity.kt b/app/src/main/java/org/rfcx/ranger/view/profile/coordinates/CoordinatesActivity.kt index 3c8fbec3b..d8ba02fbe 100644 --- a/app/src/main/java/org/rfcx/ranger/view/profile/coordinates/CoordinatesActivity.kt +++ b/app/src/main/java/org/rfcx/ranger/view/profile/coordinates/CoordinatesActivity.kt @@ -8,6 +8,7 @@ import androidx.appcompat.app.AppCompatActivity import kotlinx.android.synthetic.main.activity_coordinates.* import kotlinx.android.synthetic.main.activity_feedback.toolbar import org.rfcx.ranger.R +import org.rfcx.ranger.util.Preferences class CoordinatesActivity : AppCompatActivity() { @@ -17,35 +18,50 @@ class CoordinatesActivity : AppCompatActivity() { setupToolbar() + val preferences = Preferences.getInstance(this) + var format = preferences.getString(Preferences.COORDINATES_FORMAT, DD_FORMAT) + showChecker(format) + ddLayout.setOnClickListener { - - checkDDImageView.visibility = View.VISIBLE - checkDDMImageView.visibility = View.INVISIBLE - checkDMSImageView.visibility = View.INVISIBLE + preferences.putString(Preferences.COORDINATES_FORMAT, DD_FORMAT) + showChecker(DD_FORMAT) finish() - } ddmLayout.setOnClickListener { - - checkDDImageView.visibility = View.INVISIBLE - checkDDMImageView.visibility = View.VISIBLE - checkDMSImageView.visibility = View.INVISIBLE + preferences.putString(Preferences.COORDINATES_FORMAT, DDM_FORMAT) + showChecker(DDM_FORMAT) finish() - } dmsLayout.setOnClickListener { - - checkDDImageView.visibility = View.INVISIBLE - checkDDMImageView.visibility = View.INVISIBLE - checkDMSImageView.visibility = View.VISIBLE + preferences.putString(Preferences.COORDINATES_FORMAT, DMS_FORMAT) + showChecker(DMS_FORMAT) finish() - } } + private fun showChecker(format: String) { + when (format) { + DD_FORMAT -> { + checkDDImageView.visibility = View.VISIBLE + checkDDMImageView.visibility = View.INVISIBLE + checkDMSImageView.visibility = View.INVISIBLE + } + DDM_FORMAT -> { + checkDDImageView.visibility = View.INVISIBLE + checkDDMImageView.visibility = View.VISIBLE + checkDMSImageView.visibility = View.INVISIBLE + } + DMS_FORMAT -> { + checkDDImageView.visibility = View.INVISIBLE + checkDDMImageView.visibility = View.INVISIBLE + checkDMSImageView.visibility = View.VISIBLE + } + } + } + private fun setupToolbar() { setSupportActionBar(toolbar) supportActionBar?.apply { @@ -62,6 +78,10 @@ class CoordinatesActivity : AppCompatActivity() { } companion object { + const val DD_FORMAT = "DD" + const val DDM_FORMAT = "DDM" + const val DMS_FORMAT = "DMS" + fun startActivity(context: Context) { val intent = Intent(context, CoordinatesActivity::class.java) context.startActivity(intent) From d91c89c0788763e997500cb0bf160a15796fd1a9 Mon Sep 17 00:00:00 2001 From: ratree Date: Mon, 9 Mar 2020 16:30:09 +0700 Subject: [PATCH 25/30] CA-2101 Show coordinates format in Setting page --- .../main/java/org/rfcx/ranger/util/UserExt.kt | 6 ++++ .../ranger/view/profile/ProfileViewModel.kt | 4 +++ .../coordinates/CoordinatesActivity.kt | 4 +-- app/src/main/res/layout/fragment_profile.xml | 32 +++++++++++++++---- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/rfcx/ranger/util/UserExt.kt b/app/src/main/java/org/rfcx/ranger/util/UserExt.kt index bfe71c4f3..4babb9f0b 100644 --- a/app/src/main/java/org/rfcx/ranger/util/UserExt.kt +++ b/app/src/main/java/org/rfcx/ranger/util/UserExt.kt @@ -7,6 +7,7 @@ import io.jsonwebtoken.Jwts import io.realm.Realm import org.rfcx.ranger.localdb.SiteGuardianDb import org.rfcx.ranger.view.login.LoginActivityNew +import org.rfcx.ranger.view.profile.coordinates.CoordinatesActivity.Companion.DD_FORMAT fun Context.getTokenID(): String? { val idToken = Preferences.getInstance(this).getString(Preferences.ID_TOKEN, "") @@ -123,4 +124,9 @@ fun Context?.getUserProfile(): String? { fun Context?.getNameEmail(): String { return getUserEmail().split("@")[0] +} + +fun Context?.getCoordinatesFormat(): String? { + val preferences = this?.let { Preferences.getInstance(it) } + return preferences?.getString(Preferences.COORDINATES_FORMAT, DD_FORMAT) } \ No newline at end of file diff --git a/app/src/main/java/org/rfcx/ranger/view/profile/ProfileViewModel.kt b/app/src/main/java/org/rfcx/ranger/view/profile/ProfileViewModel.kt index 43eb3d22d..c6ab65980 100644 --- a/app/src/main/java/org/rfcx/ranger/view/profile/ProfileViewModel.kt +++ b/app/src/main/java/org/rfcx/ranger/view/profile/ProfileViewModel.kt @@ -15,6 +15,7 @@ import org.rfcx.ranger.data.remote.subscribe.unsubscribe.UnsubscribeUseCase import org.rfcx.ranger.entity.SubscribeRequest import org.rfcx.ranger.entity.SubscribeResponse import org.rfcx.ranger.util.* +import org.rfcx.ranger.view.profile.coordinates.CoordinatesActivity.Companion.DD_FORMAT class ProfileViewModel(private val context: Context, private val profileData: ProfileData, private val subscribeUseCase: SubscribeUseCase, private val unsubscribeUseCase: UnsubscribeUseCase) : ViewModel() { @@ -26,6 +27,7 @@ class ProfileViewModel(private val context: Context, private val profileData: Pr val userName = MutableLiveData() val sendToEmail = MutableLiveData() val guardianGroup = MutableLiveData() + val formatCoordinates = MutableLiveData() private val _logoutState = MutableLiveData() val logoutState: LiveData = _logoutState @@ -38,10 +40,12 @@ class ProfileViewModel(private val context: Context, private val profileData: Pr appVersion.value = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE}) " userName.value = profileData.getUserNickname() sendToEmail.value = "${context.getString(R.string.sent_to)} ${context.getUserEmail()}" + formatCoordinates.value = "${context.getCoordinatesFormat()}" } fun resumed(){ getSiteName() + formatCoordinates.value = "${context.getCoordinatesFormat()}" } private fun getSiteName() { diff --git a/app/src/main/java/org/rfcx/ranger/view/profile/coordinates/CoordinatesActivity.kt b/app/src/main/java/org/rfcx/ranger/view/profile/coordinates/CoordinatesActivity.kt index d8ba02fbe..084569fe3 100644 --- a/app/src/main/java/org/rfcx/ranger/view/profile/coordinates/CoordinatesActivity.kt +++ b/app/src/main/java/org/rfcx/ranger/view/profile/coordinates/CoordinatesActivity.kt @@ -9,6 +9,7 @@ import kotlinx.android.synthetic.main.activity_coordinates.* import kotlinx.android.synthetic.main.activity_feedback.toolbar import org.rfcx.ranger.R import org.rfcx.ranger.util.Preferences +import org.rfcx.ranger.util.getCoordinatesFormat class CoordinatesActivity : AppCompatActivity() { @@ -19,8 +20,7 @@ class CoordinatesActivity : AppCompatActivity() { setupToolbar() val preferences = Preferences.getInstance(this) - var format = preferences.getString(Preferences.COORDINATES_FORMAT, DD_FORMAT) - showChecker(format) + this.getCoordinatesFormat()?.let { showChecker(it) } ddLayout.setOnClickListener { preferences.putString(Preferences.COORDINATES_FORMAT, DD_FORMAT) diff --git a/app/src/main/res/layout/fragment_profile.xml b/app/src/main/res/layout/fragment_profile.xml index b6f92204b..862c8d7d8 100644 --- a/app/src/main/res/layout/fragment_profile.xml +++ b/app/src/main/res/layout/fragment_profile.xml @@ -307,18 +307,36 @@ - + android:paddingBottom="@dimen/margin_padding_small"> + + + + + + Date: Tue, 10 Mar 2020 11:36:42 +0700 Subject: [PATCH 26/30] CA-2101 Added replace delimiters for DD and DDM --- .../java/org/rfcx/ranger/util/EventExt.kt | 81 +++++++++++++++---- .../view/alert/AlertBottomDialogFragment.kt | 7 +- .../main/res/layout/fragment_dialog_alert.xml | 1 + 3 files changed, 69 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/rfcx/ranger/util/EventExt.kt b/app/src/main/java/org/rfcx/ranger/util/EventExt.kt index ea4cfe1c2..c0bd12a6d 100644 --- a/app/src/main/java/org/rfcx/ranger/util/EventExt.kt +++ b/app/src/main/java/org/rfcx/ranger/util/EventExt.kt @@ -3,11 +3,13 @@ package org.rfcx.ranger.util import android.annotation.SuppressLint import android.content.Context import android.location.Location -import com.google.protobuf.ByteString import org.rfcx.ranger.R import org.rfcx.ranger.adapter.entity.BaseItem import org.rfcx.ranger.entity.event.Event import org.rfcx.ranger.entity.event.ReviewEventFactory +import org.rfcx.ranger.view.profile.coordinates.CoordinatesActivity.Companion.DDM_FORMAT +import org.rfcx.ranger.view.profile.coordinates.CoordinatesActivity.Companion.DD_FORMAT +import org.rfcx.ranger.view.profile.coordinates.CoordinatesActivity.Companion.DMS_FORMAT import kotlin.math.absoluteValue fun Event.getIconRes(): Int { @@ -60,23 +62,45 @@ fun Event.toEventItem(state: String?): EventItem { return EventItem(this, s) } -fun Event.latitudeAsDMS(decimalPlace: Int): String { - val direction = if (this.latitude!! > 0) "N" else "S" - var strLatitude = Location.convert(latitude!!.absoluteValue, Location.FORMAT_SECONDS) - strLatitude = replaceDelimiters(strLatitude, decimalPlace) - strLatitude += " $direction" - return strLatitude -} - -fun Event.longitudeAsDMS(decimalPlace: Int): String { - val direction = if (this.longitude!! > 0) "W" else "E" - var strLongitude = Location.convert(this.longitude!!.absoluteValue, Location.FORMAT_SECONDS) - strLongitude = replaceDelimiters(strLongitude, decimalPlace) - strLongitude += " $direction" - return strLongitude +fun Event.locationCoordinates(context: Context): String { + val directionLatitude = if (latitude!! > 0) "N" else "S" + val directionLongitude = if (longitude!! > 0) "E" else "W" + + var strLatitude = "" + var strLongitude = "" + + when (context.getCoordinatesFormat()) { + DD_FORMAT -> { + strLatitude = Location.convert(latitude!!.absoluteValue, Location.FORMAT_DEGREES) + strLongitude = Location.convert(longitude!!.absoluteValue, Location.FORMAT_DEGREES) + + strLatitude = "${replaceDelimitersDD(strLatitude, 5)}$directionLatitude" + strLongitude = "${replaceDelimitersDD(strLongitude, 5)}$directionLongitude" + } + DDM_FORMAT -> { + strLatitude = Location.convert(latitude!!.absoluteValue, Location.FORMAT_MINUTES) + strLongitude = Location.convert(longitude!!.absoluteValue, Location.FORMAT_MINUTES) + + strLatitude = "${replaceDelimitersDDM(strLatitude, 4)}$directionLatitude" + strLongitude = "${replaceDelimitersDDM(strLongitude, 4)}$directionLongitude" + } + DMS_FORMAT -> { + strLatitude = Location.convert(latitude!!.absoluteValue, Location.FORMAT_SECONDS) + strLongitude = Location.convert(longitude!!.absoluteValue, Location.FORMAT_SECONDS) + + strLatitude = "${replaceDelimitersDMS(strLatitude, 1)}$directionLatitude" + strLongitude = "${replaceDelimitersDMS(strLongitude, 1)}$directionLongitude" + } + } + + val location = StringBuilder(strLatitude) + .append(", ") + .append(strLongitude) + + return location.toString() } -private fun replaceDelimiters(str: String, decimalPlace: Int): String { +private fun replaceDelimitersDMS(str: String, decimalPlace: Int): String { var str = str str = str.replaceFirst(":".toRegex(), "°") str = str.replaceFirst(":".toRegex(), "'") @@ -89,10 +113,33 @@ private fun replaceDelimiters(str: String, decimalPlace: Int): String { return str } +private fun replaceDelimitersDD(str: String, decimalPlace: Int): String { + var str = str + val pointIndex = str.indexOf(".") + val endIndex = pointIndex + 1 + decimalPlace + if (endIndex < str.length) { + str = str.substring(0, endIndex) + } + str += "°" + return str +} + +private fun replaceDelimitersDDM(str: String, decimalPlace: Int): String { + var str = str + str = str.replaceFirst(":".toRegex(), "°") + val pointIndex = str.indexOf(".") + val endIndex = pointIndex + 1 + decimalPlace + if (endIndex < str.length) { + str = str.substring(0, endIndex) + } + str += "\'" + return str +} + data class EventItem(var event: Event, var state: State = State.NONE) : BaseItem { @SuppressLint("DefaultLocale") - fun getReviewerName(context: Context) : String { + fun getReviewerName(context: Context): String { return if (state != State.NONE) { if (event.firstNameReviewer.isNotBlank()) { event.firstNameReviewer diff --git a/app/src/main/java/org/rfcx/ranger/view/alert/AlertBottomDialogFragment.kt b/app/src/main/java/org/rfcx/ranger/view/alert/AlertBottomDialogFragment.kt index 0255011ae..4dc675654 100644 --- a/app/src/main/java/org/rfcx/ranger/view/alert/AlertBottomDialogFragment.kt +++ b/app/src/main/java/org/rfcx/ranger/view/alert/AlertBottomDialogFragment.kt @@ -136,9 +136,10 @@ class AlertBottomDialogFragment : BaseBottomSheetDialogFragment() { rejectTextView.text = it.rejectedCount.toString() if (it.latitude != null || it.longitude != null) { - locationTextView.text = StringBuilder(it.latitudeAsDMS(5)) - .append(",") - .append(it.longitudeAsDMS(5)) + locationTextView.visibility = View.VISIBLE + locationTextView.text = context?.let { it1 -> it.locationCoordinates(it1) } + } else { + locationTextView.visibility = View.GONE } if (state == "CONFIRM") { diff --git a/app/src/main/res/layout/fragment_dialog_alert.xml b/app/src/main/res/layout/fragment_dialog_alert.xml index 478108a82..f8a2086e4 100644 --- a/app/src/main/res/layout/fragment_dialog_alert.xml +++ b/app/src/main/res/layout/fragment_dialog_alert.xml @@ -203,6 +203,7 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/margin_padding_small" android:drawableStart="@drawable/ic_pin_grey" + android:visibility="gone" app:layout_constraintStart_toStartOf="@+id/reviewedTextView" app:layout_constraintTop_toBottomOf="@id/reviewedTextView" tools:text=" 0.000000000" /> From ff15ba69c18ea6d79147345a3ef227ceea538e85 Mon Sep 17 00:00:00 2001 From: ratree Date: Tue, 10 Mar 2020 11:47:54 +0700 Subject: [PATCH 27/30] CA-2101 Remove constraint bottom --- app/src/main/res/layout/fragment_dialog_alert.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/res/layout/fragment_dialog_alert.xml b/app/src/main/res/layout/fragment_dialog_alert.xml index f8a2086e4..2d056bb94 100644 --- a/app/src/main/res/layout/fragment_dialog_alert.xml +++ b/app/src/main/res/layout/fragment_dialog_alert.xml @@ -162,7 +162,6 @@ android:layout_marginTop="@dimen/margin_padding_small" android:orientation="horizontal" android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/alertTitleLayout"> From a2045e0f33e166da9696d87869f8a15e90ce6ef7 Mon Sep 17 00:00:00 2001 From: ratree Date: Tue, 10 Mar 2020 15:03:07 +0700 Subject: [PATCH 28/30] CA-2101 Check nullable before do some operation --- .../java/org/rfcx/ranger/util/EventExt.kt | 67 ++++++++++--------- .../view/alert/AlertBottomDialogFragment.kt | 2 +- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/org/rfcx/ranger/util/EventExt.kt b/app/src/main/java/org/rfcx/ranger/util/EventExt.kt index c0bd12a6d..bf370dc52 100644 --- a/app/src/main/java/org/rfcx/ranger/util/EventExt.kt +++ b/app/src/main/java/org/rfcx/ranger/util/EventExt.kt @@ -62,42 +62,43 @@ fun Event.toEventItem(state: String?): EventItem { return EventItem(this, s) } -fun Event.locationCoordinates(context: Context): String { - val directionLatitude = if (latitude!! > 0) "N" else "S" - val directionLongitude = if (longitude!! > 0) "E" else "W" - - var strLatitude = "" - var strLongitude = "" - - when (context.getCoordinatesFormat()) { - DD_FORMAT -> { - strLatitude = Location.convert(latitude!!.absoluteValue, Location.FORMAT_DEGREES) - strLongitude = Location.convert(longitude!!.absoluteValue, Location.FORMAT_DEGREES) - - strLatitude = "${replaceDelimitersDD(strLatitude, 5)}$directionLatitude" - strLongitude = "${replaceDelimitersDD(strLongitude, 5)}$directionLongitude" - } - DDM_FORMAT -> { - strLatitude = Location.convert(latitude!!.absoluteValue, Location.FORMAT_MINUTES) - strLongitude = Location.convert(longitude!!.absoluteValue, Location.FORMAT_MINUTES) - - strLatitude = "${replaceDelimitersDDM(strLatitude, 4)}$directionLatitude" - strLongitude = "${replaceDelimitersDDM(strLongitude, 4)}$directionLongitude" - } - DMS_FORMAT -> { - strLatitude = Location.convert(latitude!!.absoluteValue, Location.FORMAT_SECONDS) - strLongitude = Location.convert(longitude!!.absoluteValue, Location.FORMAT_SECONDS) - - strLatitude = "${replaceDelimitersDMS(strLatitude, 1)}$directionLatitude" - strLongitude = "${replaceDelimitersDMS(strLongitude, 1)}$directionLongitude" +fun Event.locationCoordinates(context: Context): StringBuilder? { + if (latitude != null && longitude != null) { + val directionLatitude = if (latitude!! > 0) "N" else "S" + val directionLongitude = if (longitude!! > 0) "E" else "W" + + var strLatitude = "" + var strLongitude = "" + + when (context.getCoordinatesFormat()) { + DD_FORMAT -> { + strLatitude = Location.convert(latitude!!.absoluteValue, Location.FORMAT_DEGREES) + strLongitude = Location.convert(longitude!!.absoluteValue, Location.FORMAT_DEGREES) + + strLatitude = "${replaceDelimitersDD(strLatitude, 5)}$directionLatitude" + strLongitude = "${replaceDelimitersDD(strLongitude, 5)}$directionLongitude" + } + DDM_FORMAT -> { + strLatitude = Location.convert(latitude!!.absoluteValue, Location.FORMAT_MINUTES) + strLongitude = Location.convert(longitude!!.absoluteValue, Location.FORMAT_MINUTES) + + strLatitude = "${replaceDelimitersDDM(strLatitude, 4)}$directionLatitude" + strLongitude = "${replaceDelimitersDDM(strLongitude, 4)}$directionLongitude" + } + DMS_FORMAT -> { + strLatitude = Location.convert(latitude!!.absoluteValue, Location.FORMAT_SECONDS) + strLongitude = Location.convert(longitude!!.absoluteValue, Location.FORMAT_SECONDS) + + strLatitude = "${replaceDelimitersDMS(strLatitude, 1)}$directionLatitude" + strLongitude = "${replaceDelimitersDMS(strLongitude, 1)}$directionLongitude" + } } + return StringBuilder(strLatitude) + .append(", ") + .append(strLongitude) } - val location = StringBuilder(strLatitude) - .append(", ") - .append(strLongitude) - - return location.toString() + return null } private fun replaceDelimitersDMS(str: String, decimalPlace: Int): String { diff --git a/app/src/main/java/org/rfcx/ranger/view/alert/AlertBottomDialogFragment.kt b/app/src/main/java/org/rfcx/ranger/view/alert/AlertBottomDialogFragment.kt index 4dc675654..ba8d3ebfa 100644 --- a/app/src/main/java/org/rfcx/ranger/view/alert/AlertBottomDialogFragment.kt +++ b/app/src/main/java/org/rfcx/ranger/view/alert/AlertBottomDialogFragment.kt @@ -135,7 +135,7 @@ class AlertBottomDialogFragment : BaseBottomSheetDialogFragment() { agreeTextView.text = it.confirmedCount.toString() rejectTextView.text = it.rejectedCount.toString() - if (it.latitude != null || it.longitude != null) { + if (it.latitude != null && it.longitude != null) { locationTextView.visibility = View.VISIBLE locationTextView.text = context?.let { it1 -> it.locationCoordinates(it1) } } else { From f96426e89c7d1b072c5e2745b5ce434c9b37703a Mon Sep 17 00:00:00 2001 From: ratree Date: Tue, 10 Mar 2020 16:39:52 +0700 Subject: [PATCH 29/30] CA-2101 Change name of variable --- .../java/org/rfcx/ranger/util/EventExt.kt | 71 +++++++++---------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/org/rfcx/ranger/util/EventExt.kt b/app/src/main/java/org/rfcx/ranger/util/EventExt.kt index bf370dc52..c49d5f634 100644 --- a/app/src/main/java/org/rfcx/ranger/util/EventExt.kt +++ b/app/src/main/java/org/rfcx/ranger/util/EventExt.kt @@ -62,7 +62,7 @@ fun Event.toEventItem(state: String?): EventItem { return EventItem(this, s) } -fun Event.locationCoordinates(context: Context): StringBuilder? { +fun Event.locationCoordinates(context: Context): String? { if (latitude != null && longitude != null) { val directionLatitude = if (latitude!! > 0) "N" else "S" val directionLongitude = if (longitude!! > 0) "E" else "W" @@ -75,66 +75,65 @@ fun Event.locationCoordinates(context: Context): StringBuilder? { strLatitude = Location.convert(latitude!!.absoluteValue, Location.FORMAT_DEGREES) strLongitude = Location.convert(longitude!!.absoluteValue, Location.FORMAT_DEGREES) - strLatitude = "${replaceDelimitersDD(strLatitude, 5)}$directionLatitude" - strLongitude = "${replaceDelimitersDD(strLongitude, 5)}$directionLongitude" + strLatitude = "${replaceDelimitersDD(strLatitude)}$directionLatitude" + strLongitude = "${replaceDelimitersDD(strLongitude)}$directionLongitude" } DDM_FORMAT -> { strLatitude = Location.convert(latitude!!.absoluteValue, Location.FORMAT_MINUTES) strLongitude = Location.convert(longitude!!.absoluteValue, Location.FORMAT_MINUTES) - strLatitude = "${replaceDelimitersDDM(strLatitude, 4)}$directionLatitude" - strLongitude = "${replaceDelimitersDDM(strLongitude, 4)}$directionLongitude" + strLatitude = "${replaceDelimitersDDM(strLatitude)}$directionLatitude" + strLongitude = "${replaceDelimitersDDM(strLongitude)}$directionLongitude" } DMS_FORMAT -> { strLatitude = Location.convert(latitude!!.absoluteValue, Location.FORMAT_SECONDS) strLongitude = Location.convert(longitude!!.absoluteValue, Location.FORMAT_SECONDS) - strLatitude = "${replaceDelimitersDMS(strLatitude, 1)}$directionLatitude" - strLongitude = "${replaceDelimitersDMS(strLongitude, 1)}$directionLongitude" + strLatitude = "${replaceDelimitersDMS(strLatitude)}$directionLatitude" + strLongitude = "${replaceDelimitersDMS(strLongitude)}$directionLongitude" } } return StringBuilder(strLatitude) .append(", ") - .append(strLongitude) + .append(strLongitude).toString() } - return null } -private fun replaceDelimitersDMS(str: String, decimalPlace: Int): String { - var str = str - str = str.replaceFirst(":".toRegex(), "°") - str = str.replaceFirst(":".toRegex(), "'") - val pointIndex = str.indexOf(".") - val endIndex = pointIndex + 1 + decimalPlace - if (endIndex < str.length) { - str = str.substring(0, endIndex) +private fun replaceDelimitersDMS(str: String): String { + var strDMSFormat = str + strDMSFormat = strDMSFormat.replaceFirst(":".toRegex(), "°") + strDMSFormat = strDMSFormat.replaceFirst(":".toRegex(), "'") + val pointIndex = strDMSFormat.indexOf(".") + val endIndex = pointIndex + 2 + if (endIndex < strDMSFormat.length) { + strDMSFormat = strDMSFormat.substring(0, endIndex) } - str += "\"" - return str + strDMSFormat += "\"" + return strDMSFormat } -private fun replaceDelimitersDD(str: String, decimalPlace: Int): String { - var str = str - val pointIndex = str.indexOf(".") - val endIndex = pointIndex + 1 + decimalPlace - if (endIndex < str.length) { - str = str.substring(0, endIndex) +private fun replaceDelimitersDD(str: String): String { + var strDDFormat = str + val pointIndex = strDDFormat.indexOf(".") + val endIndex = pointIndex + 6 + if (endIndex < strDDFormat.length) { + strDDFormat = strDDFormat.substring(0, endIndex) } - str += "°" - return str + strDDFormat += "°" + return strDDFormat } -private fun replaceDelimitersDDM(str: String, decimalPlace: Int): String { - var str = str - str = str.replaceFirst(":".toRegex(), "°") - val pointIndex = str.indexOf(".") - val endIndex = pointIndex + 1 + decimalPlace - if (endIndex < str.length) { - str = str.substring(0, endIndex) +private fun replaceDelimitersDDM(str: String): String { + var strDDMFormat = str + strDDMFormat = strDDMFormat.replaceFirst(":".toRegex(), "°") + val pointIndex = strDDMFormat.indexOf(".") + val endIndex = pointIndex + 5 + if (endIndex < strDDMFormat.length) { + strDDMFormat = strDDMFormat.substring(0, endIndex) } - str += "\'" - return str + strDDMFormat += "\'" + return strDDMFormat } data class EventItem(var event: Event, var state: State = State.NONE) : BaseItem { From 09a8c05dfdb1278ac946e22d8446cd8ea46a3a88 Mon Sep 17 00:00:00 2001 From: Komkrit Banglad Date: Tue, 10 Mar 2020 20:37:14 +0700 Subject: [PATCH 30/30] Update version name to 1.2.5 version code to 84. --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b627ab9b9..64e302b9a 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 83 - versionName "1.2.4" + versionCode 84 + versionName "1.2.5" minSdkVersion 19 targetSdkVersion 29 multiDexEnabled true