From 19177019f70fe2764bd22f2b87ddf62d73972801 Mon Sep 17 00:00:00 2001 From: Sebastian Gansca Date: Sun, 24 Nov 2019 15:38:27 +0200 Subject: [PATCH 01/29] Ignore lockOrientation for activities --- app/src/main/AndroidManifest.xml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 82b9d64a..08533953 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -50,16 +50,19 @@ android:launchMode="singleTop" android:screenOrientation="portrait" android:theme="@style/AppTheme.NoActionBar" - android:windowSoftInputMode="adjustPan" /> + android:windowSoftInputMode="adjustPan" + tools:ignore="LockedOrientationActivity" /> + android:theme="@style/AppTheme.NoActionBar" + tools:ignore="LockedOrientationActivity" /> + android:theme="@style/AppTheme.Onboarding" + tools:ignore="LockedOrientationActivity" /> Date: Sun, 24 Nov 2019 15:42:36 +0200 Subject: [PATCH 02/29] Add log extension functions --- .../code4/monitorizarevot/helper/LogUtils.kt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 app/src/main/java/ro/code4/monitorizarevot/helper/LogUtils.kt diff --git a/app/src/main/java/ro/code4/monitorizarevot/helper/LogUtils.kt b/app/src/main/java/ro/code4/monitorizarevot/helper/LogUtils.kt new file mode 100644 index 00000000..45bc9ebd --- /dev/null +++ b/app/src/main/java/ro/code4/monitorizarevot/helper/LogUtils.kt @@ -0,0 +1,29 @@ +package ro.code4.monitorizarevot.helper + +import android.util.Log + +/** + * Utility class to easier use the [Log] class in Kotlin files + * It takes by default the classes' canonical name or uses [StringUtils.EMPTY], + * if the class is null or it is used outside of a class + * + * @author Sebastian Gansca on 24.11.2019 + */ +fun Any.logV(message: String, tag: String? = null) = Log.v(buildTag(this::class.java.name, tag), message) + +fun Any.logD(message: String, tag: String? = null) = Log.d(buildTag(this::class.java.name, tag), message) + +fun Any.logI(message: String, tag: String? = null) = Log.i(buildTag(this::class.java.name, tag), message) + +fun Any.logW(message: String, tag: String? = null) = Log.w(buildTag(this::class.java.name, tag), message) + +fun Any.logW(message: String, error: Throwable, tag: String? = null) = Log.w(buildTag(this::class.java.name, tag), message, error) + +fun Any.logE(message: String, tag: String? = null) = Log.e(buildTag(this::class.java.name, tag), message) + +fun Any.logE(message: String, error: Throwable, tag: String? = null) = Log.e(buildTag(this::class.java.name, tag), message, error) + +private fun buildTag(className: String?, tag: String?): String = when { + !className.isNullOrEmpty() -> className!! + else -> tag ?: "ro.code4.monitorizarevot.default" +} \ No newline at end of file From 8498dd65f579f63e9ce6d32dea91a67dbdf71b08 Mon Sep 17 00:00:00 2001 From: Catalin Prata Date: Sun, 24 Nov 2019 16:57:50 +0200 Subject: [PATCH 03/29] Fixes code4romania#161 Show the 'has notes' icon for questions also in the question details screen - fixed an update issue on the Adapter, use the internal list instead of the duplicated one to avoid invalid data display - added a new note icon on the detail screen the will be visible if the current question has notes - adapted the text of the "add note" button to changed based on the hasNotes count - grouped the gradle test imports --- app/build.gradle | 10 ++++----- .../adapters/QuestionDetailsAdapter.kt | 21 ++++++++++++------- .../forms/questions/BaseQuestionViewModel.kt | 1 - .../main/res/layout/item_question_details.xml | 16 ++++++++++++-- app/src/main/res/values-ro-rRO/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 6 files changed, 34 insertions(+), 16 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index ae2eed08..03d24501 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -140,10 +140,6 @@ dependencies { implementation "org.koin:koin-android:$koinVersion" implementation "org.koin:koin-android-viewmodel:$koinVersion" - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test.ext:junit:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' - implementation 'org.parceler:parceler-api:1.1.12' kapt 'org.parceler:parceler:1.1.12' @@ -152,7 +148,6 @@ dependencies { kapt "androidx.room:room-compiler:$roomVersion" implementation "androidx.room:room-ktx:$roomVersion" implementation "androidx.room:room-rxjava2:$roomVersion" - androidTestImplementation "androidx.room:room-testing:$roomVersion" implementation 'com.sylversky.fontreplacer:fontreplacer:1.0' implementation 'com.yqritc:recyclerview-flexibledivider:1.4.0' @@ -163,6 +158,11 @@ dependencies { implementation "androidx.viewpager2:viewpager2:1.0.0-rc01" + // Testing + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + androidTestImplementation "androidx.room:room-testing:$roomVersion" } // TODO: Uncomment this to enable FirebaseAnalytics and Crashlytics diff --git a/app/src/main/java/ro/code4/monitorizarevot/adapters/QuestionDetailsAdapter.kt b/app/src/main/java/ro/code4/monitorizarevot/adapters/QuestionDetailsAdapter.kt index 1bd42dfe..d653fbd8 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/adapters/QuestionDetailsAdapter.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/adapters/QuestionDetailsAdapter.kt @@ -32,10 +32,12 @@ import ro.code4.monitorizarevot.widget.RadioButtonWithDetails class QuestionDetailsAdapter constructor( - private val context: Context, - private val items: ArrayList + private val context: Context ) : ListAdapter(DIFF_CALLBACK) { + constructor(context: Context, items: ArrayList) : this(context) { + submitList(items) + } private var params: LinearLayout.LayoutParams = LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, @@ -69,7 +71,7 @@ class QuestionDetailsAdapter constructor( } override fun getItemId(position: Int): Long = - items[position].questionWithAnswers.question.id.toLong() + getItem(position).questionWithAnswers.question.id.toLong() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = @@ -87,10 +89,8 @@ class QuestionDetailsAdapter constructor( return ViewHolder(view) } - override fun getItemCount(): Int = items.size - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val item = items[position] + val item = getItem(position) when (getItemViewType(position)) { TYPE_MULTI_CHOICE, TYPE_MULTI_CHOICE_DETAILS -> setupMultiChoice( @@ -119,8 +119,13 @@ class QuestionDetailsAdapter constructor( holder.itemView.syncText.visibility = View.INVISIBLE } } + holder.itemView.hasNoteIcon.visibility = if (hasNotes) View.VISIBLE else View.GONE holder.itemView.questionCode.text = code holder.itemView.question.text = text + holder.itemView.addNoteButton.text = + if (hasNotes) context.getString(R.string.view_add_note_to_question) + else context.getString(R.string.add_note_to_question) + holder.itemView.addNoteButton.setOnClickListener { listener.addNoteFor(this) } @@ -212,9 +217,9 @@ class QuestionDetailsAdapter constructor( override fun getItemViewType(position: Int): Int = - items[position].questionWithAnswers.question.questionType + getItem(position).questionWithAnswers.question.questionType - public override fun getItem(position: Int): QuestionDetailsListItem = items[position] + public override fun getItem(position: Int): QuestionDetailsListItem = super.getItem(position) interface OnClickListener { fun addNoteFor(question: Question) diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/BaseQuestionViewModel.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/BaseQuestionViewModel.kt index 9bb6da45..7a267880 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/BaseQuestionViewModel.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/BaseQuestionViewModel.kt @@ -25,7 +25,6 @@ abstract class BaseQuestionViewModel : BaseFormViewModel() { repository.getAnswersForForm(countyCode, pollingStationNumber, formId) ).observeForever { processList(it.first, it.second) - } } diff --git a/app/src/main/res/layout/item_question_details.xml b/app/src/main/res/layout/item_question_details.xml index f028bfb3..04241dd8 100644 --- a/app/src/main/res/layout/item_question_details.xml +++ b/app/src/main/res/layout/item_question_details.xml @@ -20,14 +20,26 @@ android:layout_width="@dimen/small_icon_size" android:layout_height="@dimen/small_icon_size" android:layout_marginTop="@dimen/margin" - android:layout_marginEnd="@dimen/margin" + android:layout_marginEnd="@dimen/xsmall_margin" android:adjustViewBounds="true" android:contentDescription="@null" android:visibility="invisible" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toStartOf="@+id/hasNoteIcon" app:layout_constraintTop_toTopOf="parent" tools:src="@drawable/ic_synced" /> + + Trimite salvat local Adaugă notă la întrebarea curentă + Vezi/adaugă note la întrebarea curentă Adaugă o notă diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ed938197..c758a6f5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -105,6 +105,7 @@ Send saved locally Add note to current question + View/add notes to current question Add a note From 6e36c0b506f111f57fe5af210cf3e2369b582e6f Mon Sep 17 00:00:00 2001 From: Sebastian Gansca Date: Mon, 25 Nov 2019 09:43:52 +0200 Subject: [PATCH 04/29] Add analytics to About page and change fragments inheritance order --- .../code4/monitorizarevot/analytics/Event.kt | 14 +++++++ .../repositories/Repository.kt | 1 + .../ui/base/BaseAnalyticsFragment.kt | 29 ++++++++++++-- .../{BaseFragment.kt => ViewModelFragment.kt} | 6 +-- .../monitorizarevot/ui/forms/FormsFragment.kt | 6 ++- .../ui/forms/FormsListFragment.kt | 18 +++------ .../questions/QuestionsDetailsFragment.kt | 4 +- .../forms/questions/QuestionsListFragment.kt | 5 +-- .../monitorizarevot/ui/guide/GuideFragment.kt | 3 +- .../ui/login/LoginViewModel.kt | 11 +++-- .../monitorizarevot/ui/notes/NoteFragment.kt | 3 +- .../details/PollingStationDetailsFragment.kt | 5 +-- .../PollingStationSelectionFragment.kt | 3 +- .../ui/settings/AboutFragment.kt | 40 +++++++++++++++---- 14 files changed, 102 insertions(+), 46 deletions(-) create mode 100644 app/src/main/java/ro/code4/monitorizarevot/analytics/Event.kt rename app/src/main/java/ro/code4/monitorizarevot/ui/base/{BaseFragment.kt => ViewModelFragment.kt} (76%) diff --git a/app/src/main/java/ro/code4/monitorizarevot/analytics/Event.kt b/app/src/main/java/ro/code4/monitorizarevot/analytics/Event.kt new file mode 100644 index 00000000..ded379c4 --- /dev/null +++ b/app/src/main/java/ro/code4/monitorizarevot/analytics/Event.kt @@ -0,0 +1,14 @@ +package ro.code4.monitorizarevot.analytics + +enum class Event { + SCREEN_OPEN, + BUTTON_CLICK, + MANUAL_SYNC +} + +enum class ParamKey { + NAME, + NUMBER_NOT_SYNCED +} + +data class Param(val key: ParamKey, val value: Any) \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt b/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt index e574b4a2..789985be 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt @@ -50,6 +50,7 @@ class Repository : KoinComponent { private var syncInProgress = false fun login(user: User): Observable = loginInterface.login(user) + fun registerForNotification(token: String): Observable = loginInterface.registerForNotification(token) diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseAnalyticsFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseAnalyticsFragment.kt index 609e3303..6d5d6828 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseAnalyticsFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseAnalyticsFragment.kt @@ -1,22 +1,43 @@ package ro.code4.monitorizarevot.ui.base import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment import com.google.firebase.analytics.FirebaseAnalytics import org.koin.android.ext.android.inject -import ro.code4.monitorizarevot.R +import ro.code4.monitorizarevot.analytics.Event +import ro.code4.monitorizarevot.analytics.Param +import ro.code4.monitorizarevot.analytics.ParamKey +import ro.code4.monitorizarevot.helper.logW import ro.code4.monitorizarevot.interfaces.AnalyticsScreenName -abstract class BaseAnalyticsFragment : BaseFragment(), AnalyticsScreenName { +abstract class BaseAnalyticsFragment : Fragment(), AnalyticsScreenName { private val firebaseAnalytics: FirebaseAnalytics by inject() + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + logAnalyticsEvent( + Event.SCREEN_OPEN, + Param(ParamKey.NAME, this.javaClass.simpleName)) + } + override fun onResume() { super.onResume() firebaseAnalytics.setCurrentScreen(activity!!, getString(screenName), null) } - fun logAnalyticsEvent(event: String, eventData: Bundle) { - firebaseAnalytics.logEvent(event, eventData) + fun logAnalyticsEvent(event: Event, vararg params: Param) { + val bundle = Bundle() + for ((k, v) in params ) { + when (v) { + is String -> bundle.putString(k.name, v) + is Int -> bundle.putInt(k.name, v) + else -> logW("Not implemented bundle params for ${v.javaClass}") + } + } + firebaseAnalytics.logEvent(event.name, bundle) } } \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/base/ViewModelFragment.kt similarity index 76% rename from app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseFragment.kt rename to app/src/main/java/ro/code4/monitorizarevot/ui/base/ViewModelFragment.kt index e4ce3d31..c93d8310 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/base/ViewModelFragment.kt @@ -1,18 +1,14 @@ package ro.code4.monitorizarevot.ui.base import android.content.Context -import android.net.ConnectivityManager -import android.net.NetworkCapabilities -import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.fragment.app.Fragment import ro.code4.monitorizarevot.interfaces.Layout import ro.code4.monitorizarevot.interfaces.ViewModelSetter -abstract class BaseFragment : Fragment(), Layout, +abstract class ViewModelFragment : BaseAnalyticsFragment(), Layout, ViewModelSetter { lateinit var mContext: Context diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsFragment.kt index 7d6d5098..26d92b1f 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsFragment.kt @@ -13,13 +13,15 @@ import ro.code4.monitorizarevot.helper.Constants.FORM import ro.code4.monitorizarevot.helper.Constants.QUESTION import ro.code4.monitorizarevot.helper.changePollingStation import ro.code4.monitorizarevot.helper.replaceFragment -import ro.code4.monitorizarevot.ui.base.BaseFragment +import ro.code4.monitorizarevot.ui.base.ViewModelFragment import ro.code4.monitorizarevot.ui.forms.questions.QuestionsDetailsFragment import ro.code4.monitorizarevot.ui.forms.questions.QuestionsListFragment import ro.code4.monitorizarevot.ui.main.MainActivity import ro.code4.monitorizarevot.ui.notes.NoteFragment -class FormsFragment : BaseFragment() { +class FormsFragment : ViewModelFragment() { + override val screenName: Int + get() = R.string.menu_forms override val layout: Int get() = R.layout.fragment_main diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsListFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsListFragment.kt index 88d252cb..c4a0efb9 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsListFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsListFragment.kt @@ -7,17 +7,19 @@ import android.view.View import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.snackbar.Snackbar -import com.google.firebase.analytics.FirebaseAnalytics import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration import kotlinx.android.synthetic.main.fragment_forms.* import org.koin.android.viewmodel.ext.android.getSharedViewModel import ro.code4.monitorizarevot.R import ro.code4.monitorizarevot.adapters.FormDelegationAdapter +import ro.code4.monitorizarevot.analytics.Event +import ro.code4.monitorizarevot.analytics.Param +import ro.code4.monitorizarevot.analytics.ParamKey import ro.code4.monitorizarevot.helper.isOnline -import ro.code4.monitorizarevot.ui.base.BaseAnalyticsFragment +import ro.code4.monitorizarevot.ui.base.ViewModelFragment -class FormsListFragment : BaseAnalyticsFragment() { +class FormsListFragment : ViewModelFragment() { companion object { val TAG = FormsListFragment::class.java.simpleName @@ -55,7 +57,7 @@ class FormsListFragment : BaseAnalyticsFragment() { syncButton.setOnClickListener { // TODO send number of unsynced items - logSyncManuallyEvent(0) + logAnalyticsEvent(Event.MANUAL_SYNC, Param(ParamKey.NUMBER_NOT_SYNCED, 0)) if (!mContext.isOnline()) { Snackbar.make(syncButton, getString(R.string.form_sync_no_internet), Snackbar.LENGTH_SHORT) @@ -76,12 +78,4 @@ class FormsListFragment : BaseAnalyticsFragment() { ) } } - - private fun logSyncManuallyEvent(numberOfNotSynced: Int) { - val bundle = Bundle() - bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, getString(R.string.analytics_event_manual_sync)) - bundle.putInt(FirebaseAnalytics.Param.VALUE, numberOfNotSynced) - - logAnalyticsEvent(FirebaseAnalytics.Event.SELECT_CONTENT, bundle) - } } \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsDetailsFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsDetailsFragment.kt index 2472a911..1c52d54f 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsDetailsFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsDetailsFragment.kt @@ -21,11 +21,11 @@ import ro.code4.monitorizarevot.data.model.Question import ro.code4.monitorizarevot.helper.Constants import ro.code4.monitorizarevot.helper.addOnLayoutChangeListenerForGalleryEffect import ro.code4.monitorizarevot.helper.addOnScrollListenerForGalleryEffect -import ro.code4.monitorizarevot.ui.base.BaseAnalyticsFragment +import ro.code4.monitorizarevot.ui.base.ViewModelFragment import ro.code4.monitorizarevot.ui.forms.FormsViewModel -class QuestionsDetailsFragment : BaseAnalyticsFragment(), +class QuestionsDetailsFragment : ViewModelFragment(), QuestionDetailsAdapter.OnClickListener { override fun addNoteFor(question: Question) { baseViewModel.selectedNotes(question) diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsListFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsListFragment.kt index 61925b97..2d0ca824 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsListFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/questions/QuestionsListFragment.kt @@ -15,11 +15,10 @@ import ro.code4.monitorizarevot.R import ro.code4.monitorizarevot.adapters.QuestionDelegationAdapter import ro.code4.monitorizarevot.data.model.FormDetails import ro.code4.monitorizarevot.helper.Constants.FORM -import ro.code4.monitorizarevot.ui.base.BaseAnalyticsFragment -import ro.code4.monitorizarevot.ui.base.BaseFragment +import ro.code4.monitorizarevot.ui.base.ViewModelFragment import ro.code4.monitorizarevot.ui.forms.FormsViewModel -class QuestionsListFragment : BaseAnalyticsFragment() { +class QuestionsListFragment : ViewModelFragment() { override val layout: Int get() = R.layout.fragment_list diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/guide/GuideFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/guide/GuideFragment.kt index 4f67c500..4eca4818 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/guide/GuideFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/guide/GuideFragment.kt @@ -9,9 +9,10 @@ import org.koin.android.ext.android.inject import ro.code4.monitorizarevot.R import ro.code4.monitorizarevot.helper.WebClient import ro.code4.monitorizarevot.ui.base.BaseAnalyticsFragment +import ro.code4.monitorizarevot.ui.base.ViewModelFragment import ro.code4.monitorizarevot.widget.ProgressDialogFragment -class GuideFragment : BaseAnalyticsFragment(), WebClient.WebLoaderListener { +class GuideFragment : ViewModelFragment(), WebClient.WebLoaderListener { override val layout: Int diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginViewModel.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginViewModel.kt index d8b491d3..3d6c9d4e 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginViewModel.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginViewModel.kt @@ -1,6 +1,7 @@ package ro.code4.monitorizarevot.ui.login import android.content.SharedPreferences +import android.util.Log import androidx.lifecycle.LiveData import com.google.firebase.iid.FirebaseInstanceId import io.reactivex.android.schedulers.AndroidSchedulers @@ -9,10 +10,7 @@ import org.koin.core.inject import ro.code4.monitorizarevot.BuildConfig import ro.code4.monitorizarevot.data.model.User import ro.code4.monitorizarevot.data.model.response.LoginResponse -import ro.code4.monitorizarevot.helper.Result -import ro.code4.monitorizarevot.helper.SingleLiveEvent -import ro.code4.monitorizarevot.helper.hasCompletedOnboarding -import ro.code4.monitorizarevot.helper.saveToken +import ro.code4.monitorizarevot.helper.* import ro.code4.monitorizarevot.repositories.Repository import ro.code4.monitorizarevot.ui.base.BaseViewModel import ro.code4.monitorizarevot.ui.onboarding.OnboardingActivity @@ -33,11 +31,13 @@ class LoginViewModel : BaseViewModel() { } private fun onSuccessfulLogin(loginResponse: LoginResponse, firebaseToken: String) { + logD("onSuccessfulLogin") sharedPreferences.saveToken(loginResponse.accessToken) registerForNotification(firebaseToken) } private fun onSuccessfulRegisteredForNotification() { + logD("onSuccessfulRegisteredForNotification") if (sharedPreferences.hasCompletedOnboarding()) { loginLiveData.postValue(Result.Success(PollingStationActivity::class.java)) } else { @@ -66,6 +66,7 @@ class LoginViewModel : BaseViewModel() { } fun login(phone: String, password: String, firebaseToken: String) { + logD("login: $phone : $password -> $firebaseToken") disposables.add( loginRepository.login(User(phone, password, firebaseToken)) .subscribeOn(Schedulers.io()) @@ -77,6 +78,7 @@ class LoginViewModel : BaseViewModel() { } private fun registerForNotification(firebaseToken: String) { + logD("registerForNotification with $firebaseToken") disposables.add( loginRepository.registerForNotification(firebaseToken) .subscribeOn(Schedulers.io()) @@ -86,6 +88,7 @@ class LoginViewModel : BaseViewModel() { } override fun onError(throwable: Throwable) { + logE("onError ${throwable.message}", throwable) loginLiveData.postValue(Result.Failure(throwable)) } } diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/notes/NoteFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/notes/NoteFragment.kt index 3553e982..3ea9690a 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/notes/NoteFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/notes/NoteFragment.kt @@ -34,9 +34,10 @@ import ro.code4.monitorizarevot.helper.Constants.REQUEST_CODE_GALLERY import ro.code4.monitorizarevot.helper.Constants.REQUEST_CODE_RECORD_VIDEO import ro.code4.monitorizarevot.helper.Constants.REQUEST_CODE_TAKE_PHOTO import ro.code4.monitorizarevot.ui.base.BaseAnalyticsFragment +import ro.code4.monitorizarevot.ui.base.ViewModelFragment import ro.code4.monitorizarevot.ui.forms.FormsViewModel -class NoteFragment : BaseAnalyticsFragment(), PermissionManager.PermissionListener { +class NoteFragment : ViewModelFragment(), PermissionManager.PermissionListener { override val layout: Int get() = R.layout.fragment_note diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/section/details/PollingStationDetailsFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/section/details/PollingStationDetailsFragment.kt index ef047b46..5040f525 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/section/details/PollingStationDetailsFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/section/details/PollingStationDetailsFragment.kt @@ -11,13 +11,12 @@ import kotlinx.android.synthetic.main.fragment_polling_station_selection.continu import kotlinx.android.synthetic.main.widget_change_polling_station_bar.* import org.koin.android.viewmodel.ext.android.getSharedViewModel import ro.code4.monitorizarevot.R -import ro.code4.monitorizarevot.ui.base.BaseAnalyticsFragment -import ro.code4.monitorizarevot.ui.base.BaseFragment +import ro.code4.monitorizarevot.ui.base.ViewModelFragment import ro.code4.monitorizarevot.ui.section.PollingStationViewModel import java.util.* -class PollingStationDetailsFragment : BaseAnalyticsFragment() { +class PollingStationDetailsFragment : ViewModelFragment() { companion object { val TAG = PollingStationDetailsFragment::class.java.simpleName diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionFragment.kt index bdfc9fba..9c72bb3d 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionFragment.kt @@ -11,11 +11,12 @@ import org.koin.android.viewmodel.ext.android.getSharedViewModel import org.koin.android.viewmodel.ext.android.viewModel import ro.code4.monitorizarevot.R import ro.code4.monitorizarevot.ui.base.BaseAnalyticsFragment +import ro.code4.monitorizarevot.ui.base.ViewModelFragment import ro.code4.monitorizarevot.ui.section.PollingStationViewModel import ro.code4.monitorizarevot.widget.ProgressDialogFragment -class PollingStationSelectionFragment : BaseAnalyticsFragment() { +class PollingStationSelectionFragment : ViewModelFragment() { private val progressDialog: ProgressDialogFragment by lazy { ProgressDialogFragment().also { diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/settings/AboutFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/settings/AboutFragment.kt index cc959c5f..f1a6a7df 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/settings/AboutFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/settings/AboutFragment.kt @@ -7,14 +7,20 @@ import android.text.method.LinkMovementMethod import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.fragment.app.Fragment import kotlinx.android.synthetic.main.fragment_about.* import ro.code4.monitorizarevot.BuildConfig import ro.code4.monitorizarevot.R +import ro.code4.monitorizarevot.analytics.Event +import ro.code4.monitorizarevot.analytics.Param +import ro.code4.monitorizarevot.analytics.ParamKey import ro.code4.monitorizarevot.helper.browse +import ro.code4.monitorizarevot.helper.logW import ro.code4.monitorizarevot.helper.toHtml +import ro.code4.monitorizarevot.ui.base.BaseAnalyticsFragment -class AboutFragment : Fragment() { +class AboutFragment : BaseAnalyticsFragment() { + override val screenName: Int + get() = R.string.menu_about companion object { val TAG = AboutFragment::class.java.simpleName @@ -36,16 +42,20 @@ class AboutFragment : Fragment() { content.text = getString(R.string.about_content).toHtml() content.movementMethod = LinkMovementMethod.getInstance() - optionChangeLanguage.setOnClickListener { - //TODO - } + optionChangeLanguage.setOnClickListener { onChangeLanguageClicked(view) } + + optionContact.setOnClickListener { onContactClicked(view) } - optionContact.setOnClickListener { onContactClicked() } + optionViewPolicy.setOnClickListener { onViewPolicyClicked(view) } + } - optionViewPolicy.setOnClickListener { context?.browse(BuildConfig.PRIVACY_WEB_URL) } + private fun onChangeLanguageClicked(view: View) { + logClickEvent(view) + // TODO: Implement } - private fun onContactClicked() { + private fun onContactClicked(view: View) { + logClickEvent(view) val emailIntent = Intent( Intent.ACTION_SENDTO, Uri.fromParts("mailto", BuildConfig.SUPPORT_EMAIL, null) @@ -58,4 +68,18 @@ class AboutFragment : Fragment() { ) } + private fun onViewPolicyClicked(view: View) { + logClickEvent(view) + val result = context?.browse(BuildConfig.PRIVACY_WEB_URL) + if (!result!!) { + logW("No app to view " + BuildConfig.PRIVACY_WEB_URL) + } + } + + private fun logClickEvent(view: View) { + logAnalyticsEvent( + Event.BUTTON_CLICK, + Param(ParamKey.NAME, resources.getResourceEntryName(view.id)) + ) + } } \ No newline at end of file From f93357a2d03f88f49deca9cbfb312552a9f2d933 Mon Sep 17 00:00:00 2001 From: Sebastian Gansca Date: Fri, 27 Dec 2019 08:43:42 +0200 Subject: [PATCH 05/29] Use of log extension functions in LoginViewModel --- .../ui/login/LoginViewModel.kt | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginViewModel.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginViewModel.kt index d8b491d3..f13cea1a 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginViewModel.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginViewModel.kt @@ -9,10 +9,7 @@ import org.koin.core.inject import ro.code4.monitorizarevot.BuildConfig import ro.code4.monitorizarevot.data.model.User import ro.code4.monitorizarevot.data.model.response.LoginResponse -import ro.code4.monitorizarevot.helper.Result -import ro.code4.monitorizarevot.helper.SingleLiveEvent -import ro.code4.monitorizarevot.helper.hasCompletedOnboarding -import ro.code4.monitorizarevot.helper.saveToken +import ro.code4.monitorizarevot.helper.* import ro.code4.monitorizarevot.repositories.Repository import ro.code4.monitorizarevot.ui.base.BaseViewModel import ro.code4.monitorizarevot.ui.onboarding.OnboardingActivity @@ -52,6 +49,7 @@ class LoginViewModel : BaseViewModel() { if (it.isSuccessful && firebaseToken != null) { login(phone, password, firebaseToken) } else { + logW("Failed to get firebase token!") onError(Throwable()) } }.addOnFailureListener(this::onError) @@ -60,6 +58,7 @@ class LoginViewModel : BaseViewModel() { if (BuildConfig.DEBUG && exception is IllegalStateException) { login(phone, password, "1234") } else { + logW("Exception while trying to get firebase token!") onError(exception) } } @@ -71,8 +70,12 @@ class LoginViewModel : BaseViewModel() { .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ loginResponse -> + logD("Login successful! Token received!") onSuccessfulLogin(loginResponse, firebaseToken) - }, this::onError) + }, { throwable -> + logE("Login failed!", throwable) + onError(throwable) + }) ) } @@ -81,7 +84,13 @@ class LoginViewModel : BaseViewModel() { loginRepository.registerForNotification(firebaseToken) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ onSuccessfulRegisteredForNotification() }, this::onError) + .subscribe({ + logD("Registered for notifications on firebase!") + onSuccessfulRegisteredForNotification() + }, { throwable -> + logE("Register for notification failed!", throwable) + onError(throwable) + }) ) } From 03d08cd4168e2dc1ae3b9065c768de2b681a0aee Mon Sep 17 00:00:00 2001 From: Sebastian Gansca Date: Fri, 27 Dec 2019 09:10:31 +0200 Subject: [PATCH 06/29] Add logging to logAnalyticsEvent method --- .../ro/code4/monitorizarevot/ui/base/BaseAnalyticsFragment.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseAnalyticsFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseAnalyticsFragment.kt index 6d5d6828..f931e628 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseAnalyticsFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseAnalyticsFragment.kt @@ -8,6 +8,7 @@ import org.koin.android.ext.android.inject import ro.code4.monitorizarevot.analytics.Event import ro.code4.monitorizarevot.analytics.Param import ro.code4.monitorizarevot.analytics.ParamKey +import ro.code4.monitorizarevot.helper.logD import ro.code4.monitorizarevot.helper.logW import ro.code4.monitorizarevot.interfaces.AnalyticsScreenName @@ -30,6 +31,7 @@ abstract class BaseAnalyticsFragment : Fragment(), AnalyticsScreenName { } fun logAnalyticsEvent(event: Event, vararg params: Param) { + logD("logAnalyticsEvent: ${event.name}") val bundle = Bundle() for ((k, v) in params ) { when (v) { From 277ea67c9d16a5778dd62c18bf611b6348a0aec5 Mon Sep 17 00:00:00 2001 From: Sebastian Gansca Date: Fri, 27 Dec 2019 09:46:22 +0200 Subject: [PATCH 07/29] Extend Event and Param with custom titles --- .../ro/code4/monitorizarevot/analytics/Event.kt | 17 ++++++++++------- .../ui/base/BaseAnalyticsFragment.kt | 6 +++--- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/ro/code4/monitorizarevot/analytics/Event.kt b/app/src/main/java/ro/code4/monitorizarevot/analytics/Event.kt index ded379c4..99e0a6fc 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/analytics/Event.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/analytics/Event.kt @@ -1,14 +1,17 @@ package ro.code4.monitorizarevot.analytics -enum class Event { - SCREEN_OPEN, - BUTTON_CLICK, - MANUAL_SYNC +enum class Event(val title: String) { + SCREEN_OPEN("screen_open"), + BUTTON_CLICK("button_click"), + MANUAL_SYNC("manual_sync"), + LOGIN_FAILED("login_failed"), + TAP_CALL("tap_call"), + TAP_CHANGE_STATION("tap_change_station") } -enum class ParamKey { - NAME, - NUMBER_NOT_SYNCED +enum class ParamKey(val title: String) { + NAME("name"), + NUMBER_NOT_SYNCED("number_not_synced") } data class Param(val key: ParamKey, val value: Any) \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseAnalyticsFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseAnalyticsFragment.kt index f931e628..f429c446 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseAnalyticsFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseAnalyticsFragment.kt @@ -35,11 +35,11 @@ abstract class BaseAnalyticsFragment : Fragment(), AnalyticsScreenName { val bundle = Bundle() for ((k, v) in params ) { when (v) { - is String -> bundle.putString(k.name, v) - is Int -> bundle.putInt(k.name, v) + is String -> bundle.putString(k.title, v) + is Int -> bundle.putInt(k.title, v) else -> logW("Not implemented bundle params for ${v.javaClass}") } } - firebaseAnalytics.logEvent(event.name, bundle) + firebaseAnalytics.logEvent(event.title, bundle) } } \ No newline at end of file From 7c2095903148e761811c17b865565b4633732467 Mon Sep 17 00:00:00 2001 From: Sebastian Gansca Date: Fri, 27 Dec 2019 09:46:40 +0200 Subject: [PATCH 08/29] Add login failed event Add call and change station event --- .../ro/code4/monitorizarevot/ui/login/LoginViewModel.kt | 5 +++++ .../java/ro/code4/monitorizarevot/ui/main/MainActivity.kt | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginViewModel.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginViewModel.kt index f0797c56..c83c1bd3 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginViewModel.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/login/LoginViewModel.kt @@ -3,11 +3,14 @@ package ro.code4.monitorizarevot.ui.login import android.content.SharedPreferences import android.util.Log import androidx.lifecycle.LiveData +import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.iid.FirebaseInstanceId import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers +import org.koin.android.ext.android.inject import org.koin.core.inject import ro.code4.monitorizarevot.BuildConfig +import ro.code4.monitorizarevot.analytics.Event import ro.code4.monitorizarevot.data.model.User import ro.code4.monitorizarevot.data.model.response.LoginResponse import ro.code4.monitorizarevot.helper.* @@ -20,6 +23,7 @@ class LoginViewModel : BaseViewModel() { private val loginRepository: Repository by inject() private val sharedPreferences: SharedPreferences by inject() + private val firebaseAnalytics: FirebaseAnalytics by inject() private val loginLiveData = SingleLiveEvent>>() @@ -77,6 +81,7 @@ class LoginViewModel : BaseViewModel() { logD("Login successful! Token received!") onSuccessfulLogin(loginResponse, firebaseToken) }, { throwable -> + firebaseAnalytics.logEvent(Event.LOGIN_FAILED.title, null) logE("Login failed!", throwable) onError(throwable) }) diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/main/MainActivity.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/main/MainActivity.kt index 70ee7e42..82aa9ebe 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/main/MainActivity.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/main/MainActivity.kt @@ -10,10 +10,13 @@ import androidx.navigation.ui.NavigationUI.onNavDestinationSelected import androidx.navigation.ui.navigateUp import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupWithNavController +import com.google.firebase.analytics.FirebaseAnalytics import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.app_bar_main.* +import org.koin.android.ext.android.inject import org.koin.android.viewmodel.ext.android.viewModel import ro.code4.monitorizarevot.R +import ro.code4.monitorizarevot.analytics.Event import ro.code4.monitorizarevot.helper.callSupportCenter import ro.code4.monitorizarevot.helper.changePollingStation import ro.code4.monitorizarevot.helper.startActivityWithoutTrace @@ -25,6 +28,7 @@ class MainActivity : BaseActivity() { override val layout: Int get() = R.layout.activity_main + private val firebaseAnalytics: FirebaseAnalytics by inject() override val viewModel: MainViewModel by viewModel() private lateinit var navController: NavController private lateinit var appBarConfiguration: AppBarConfiguration @@ -61,16 +65,17 @@ class MainActivity : BaseActivity() { } else { when (item.itemId) { R.id.nav_change_polling_station -> { + firebaseAnalytics.logEvent(Event.TAP_CHANGE_STATION.title, null) viewModel.notifyChangeRequested() changePollingStation() true } R.id.nav_call -> { + firebaseAnalytics.logEvent(Event.TAP_CALL.title, null) callSupportCenter() true } else -> false - } } } From 9e580b001780691d1904f964f635a24b2b9d820d Mon Sep 17 00:00:00 2001 From: Sebastian Gansca Date: Mon, 30 Dec 2019 10:42:43 +0200 Subject: [PATCH 09/29] Update manual sync event closes #170 --- app/src/main/java/ro/code4/monitorizarevot/analytics/Event.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/ro/code4/monitorizarevot/analytics/Event.kt b/app/src/main/java/ro/code4/monitorizarevot/analytics/Event.kt index 99e0a6fc..93ceb61a 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/analytics/Event.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/analytics/Event.kt @@ -3,7 +3,7 @@ package ro.code4.monitorizarevot.analytics enum class Event(val title: String) { SCREEN_OPEN("screen_open"), BUTTON_CLICK("button_click"), - MANUAL_SYNC("manual_sync"), + MANUAL_SYNC("tap_manual_sync"), LOGIN_FAILED("login_failed"), TAP_CALL("tap_call"), TAP_CHANGE_STATION("tap_change_station") From cdcc5bbd54258797cb87ab63a4d18fef63210425 Mon Sep 17 00:00:00 2001 From: Irina Borozan Date: Thu, 2 Jan 2020 16:58:03 +0200 Subject: [PATCH 10/29] added more info in readme --- Readme.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Readme.md b/Readme.md index a7e63f78..9503889c 100644 --- a/Readme.md +++ b/Readme.md @@ -32,6 +32,12 @@ Uses [Fastlane](https://fastlane.tools/) for automating builds & releases. Uses the MVVM architectural pattern. +Relies on Firebase's RemoteConfig for remote settings. + +The app is localized, meaning it's easier for any interested party to fork the project and use it in other countries, simply localizing the messages. Please see the [steps for app localization](https://github.com/code4romania/mon-vot-android-kotlin/wiki/Steps-for-app-localisation) in the wiki. + +More info on redeploying and reusing the app can be found in the wiki: [Redeploy steps](https://github.com/code4romania/mon-vot-android-kotlin/wiki/Steps-for-redeploying---reusing-the-app) & [Google Play Deploy Steps](https://github.com/code4romania/mon-vot-android-kotlin/wiki/Google-Play-Deploy-Steps) + Swagger docs for the API are available [here](http://mv-mobile-test.azurewebsites.net/swagger/index.html). ## Repos and projects From 206fdfbbdaa3d808b61eca418f14cbafbf67a3f9 Mon Sep 17 00:00:00 2001 From: Irina Borozan Date: Thu, 2 Jan 2020 17:07:35 +0200 Subject: [PATCH 11/29] updated github files --- .github/CONTRIBUTING.MD | 3 ++- .github/WORKFLOW.MD | 14 ++++++++++++++ Readme.md | 2 ++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.MD b/.github/CONTRIBUTING.MD index abe0b61c..a5d15597 100644 --- a/.github/CONTRIBUTING.MD +++ b/.github/CONTRIBUTING.MD @@ -40,7 +40,8 @@ To send us a suggestion, just [open an issue](https://github.com/code4romania/mo :computer: We'd love for you to get your hands dirty and code for the project. -If you are unsure where to begin contributing to the project, you can start by looking through these issues: +If you are unsure where to begin contributing to the project, you can start by looking through these issues: +• [How to contribute](WORKFLOW.MD) * [Good first issues](https://github.com/code4romania/mon-vot-android-kotlin/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) * [Help wanted](https://github.com/code4romania/mon-vot-android-kotlin/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) diff --git a/.github/WORKFLOW.MD b/.github/WORKFLOW.MD index 8bbfca43..041ce282 100644 --- a/.github/WORKFLOW.MD +++ b/.github/WORKFLOW.MD @@ -2,6 +2,20 @@ Whether you're trying to give back to the open source community or collaborating In an attempt to collate this information for myself and others, this short tutorial is what I've found to be fairly standard procedure for creating a fork, doing your work, issuing a pull request, and merging that pull request back into the original project. +## TL;DR + +Steps (details below): +1. Create issue if it doesn't exist +2. Pull the latest `upstream/develop` into your fork +3. Create feature branch in your fork +4. Code it up +5. Commit the changes (can put multiple commits since you'll be basing the PR off the branch) +6. Push the changes to `origin/develop` (your fork) +7. Create PR on Code4Romania repo basing `develop` <- `your fork/your feature branch` +8. Enjoy success + +Detailed instructions below. + ## Creating a Fork Just head over to the GitHub page and click the "Fork" button. It's just that simple. Once you've done that, you can use your favorite git client to clone your repo or just head straight to the command line: diff --git a/Readme.md b/Readme.md index 9503889c..e51a0ace 100644 --- a/Readme.md +++ b/Readme.md @@ -18,6 +18,8 @@ The app also has a web version, available for every citizen who wants to report This project is built by amazing volunteers and you can be one of them! Here's a list of ways in [which you can contribute to this project](.github/CONTRIBUTING.MD). +__IMPORTANT:__ Please follow the Code4Romania [WORKFLOW](.github/WORKFLOW.MD) + ## Built With * Android Studio 3.6 From 33788679fe233552c727c57e0b5c0372e8066513 Mon Sep 17 00:00:00 2001 From: lukstbit Date: Tue, 7 Jan 2020 18:17:23 +0200 Subject: [PATCH 12/29] Add code for language selection from about screen --- .../ui/onboarding/OnboardingViewModel.kt | 11 +- .../ui/settings/AboutFragment.kt | 3 +- .../settings/AboutLanguageSelectorFragment.kt | 109 ++++++++++++++++++ app/src/main/res/layout/fragment_about.xml | 10 +- .../fragment_about_language_selector.xml | 37 ++++++ 5 files changed, 160 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/ro/code4/monitorizarevot/ui/settings/AboutLanguageSelectorFragment.kt create mode 100644 app/src/main/res/layout/fragment_about_language_selector.xml diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/onboarding/OnboardingViewModel.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/onboarding/OnboardingViewModel.kt index 30fd1d60..f33fb301 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/onboarding/OnboardingViewModel.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/onboarding/OnboardingViewModel.kt @@ -54,8 +54,6 @@ class OnboardingViewModel : BaseViewModel() { postValue(screens) } - private fun getLocales(codes: Array): List = codes.map { it.getLocale() } - fun languageChanged(): LiveData = languageChangedLiveData fun onboarding(): LiveData> = onboardingLiveData fun onboardingCompleted() { @@ -64,8 +62,11 @@ class OnboardingViewModel : BaseViewModel() { fun getSelectedLocale(): Locale = preferences.getLocaleCode().getLocale() fun changeLanguage(locale: Locale) { - val code = "${locale.language}_${locale.country}" - preferences.setLocaleCode(code) + preferences.setLocaleCode(locale.encode()) languageChangedLiveData.call() } -} \ No newline at end of file +} + +internal fun getLocales(codes: Array): List = codes.map { it.getLocale() } + +internal fun Locale.encode() = "${this.language}_${this.country}" \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/settings/AboutFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/settings/AboutFragment.kt index cc959c5f..a0299885 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/settings/AboutFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/settings/AboutFragment.kt @@ -37,7 +37,8 @@ class AboutFragment : Fragment() { content.movementMethod = LinkMovementMethod.getInstance() optionChangeLanguage.setOnClickListener { - //TODO + val languageSelector = AboutLanguageSelectorFragment() + languageSelector.show(requireFragmentManager(), AboutLanguageSelectorFragment.TAG) } optionContact.setOnClickListener { onContactClicked() } diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/settings/AboutLanguageSelectorFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/settings/AboutLanguageSelectorFragment.kt new file mode 100644 index 00000000..ccd2589e --- /dev/null +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/settings/AboutLanguageSelectorFragment.kt @@ -0,0 +1,109 @@ +package ro.code4.monitorizarevot.ui.settings + +import android.content.Context +import android.content.SharedPreferences +import android.os.Bundle +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.Spinner +import androidx.appcompat.widget.AppCompatSpinner +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import org.koin.core.KoinComponent +import org.koin.core.inject +import ro.code4.monitorizarevot.R +import ro.code4.monitorizarevot.adapters.LanguageAdapter +import ro.code4.monitorizarevot.adapters.OnboardingAdapter +import ro.code4.monitorizarevot.helper.getLocale +import ro.code4.monitorizarevot.helper.getLocaleCode +import ro.code4.monitorizarevot.helper.setLocaleCode +import ro.code4.monitorizarevot.ui.onboarding.encode +import ro.code4.monitorizarevot.ui.onboarding.getLocales +import java.util.* + +class AboutLanguageSelectorFragment : BottomSheetDialogFragment(), KoinComponent, + OnboardingAdapter.OnLanguageChangedListener { + + companion object { + val TAG = AboutLanguageSelectorFragment::class.java.simpleName + } + + private val preferences: SharedPreferences by inject() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val view = inflater.inflate(R.layout.fragment_about_language_selector, container, false) + val languages = getLocales(requireActivity().resources.getStringArray(R.array.languages)) + val languagesAdapter = LanguageAdapter(requireContext(), languages) + val selectedLocale = preferences.getLocaleCode().getLocale() + view.findViewById(R.id.languagesSpinner).apply { + adapter = languagesAdapter + setSelection(languages.indexOf(selectedLocale)) + onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + // when it is initialized, the Spinner automatically invokes this listener, + // we use this flag to avoid this, otherwise the dialog wouldn't be shown(this initial + // selection would trigger the else branch and dismiss() the dialog!) + var firstTime = true + + override fun onNothingSelected(parent: AdapterView<*>?) = Unit + + override fun onItemSelected( + parent: AdapterView<*>?, view: View?, position: Int, id: Long + ) { + if (firstTime) { + firstTime = false + return + } + val locale = languagesAdapter.getItem(position) + if (locale != null && locale != selectedLocale) { + onLanguageChanged(locale) + } else { + dismiss() + } + } + } + } + return view + } + + override fun onLanguageChanged(locale: Locale) { + dismiss() + preferences.setLocaleCode(locale.encode()) + requireActivity().recreate() + } +} + +/** + * By default, a [Spinner] widget only triggers its [AdapterView.OnItemSelectedListener] only if the selected + * element is different than the current selection. Receiving the selection of the same entry is needed so + * we can cancel the language selection dialog, event when the user selects the same language. + */ +class SameSelectionAllowedSpinner @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null +) : AppCompatSpinner(context, attrs) { + + private var lastSelectedPosition = -1 + + override fun setSelection(position: Int) { + super.setSelection(position) + triggerListenerIfPossible(position) + } + + override fun setSelection(position: Int, animate: Boolean) { + super.setSelection(position, animate) + triggerListenerIfPossible(position) + } + + private fun triggerListenerIfPossible(newSelection: Int) { + if (lastSelectedPosition == newSelection) { + onItemSelectedListener?.onItemSelected(this, null, newSelection, -1) + } + lastSelectedPosition = newSelection + } +} diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml index 27d4676a..4f0b02f5 100644 --- a/app/src/main/res/layout/fragment_about.xml +++ b/app/src/main/res/layout/fragment_about.xml @@ -42,8 +42,10 @@ style="@style/Text" android:layout_width="match_parent" android:layout_height="wrap_content" - android:visibility="gone" - android:layout_margin="@dimen/margin" + android:layout_marginStart="@dimen/margin" + android:layout_marginTop="@dimen/margin" + android:layout_marginEnd="@dimen/margin" + android:layout_marginBottom="@dimen/xsmall_margin" android:background="@drawable/selector_bg_btn" android:drawableEnd="@drawable/ic_arrow_right" android:gravity="center_vertical" @@ -59,9 +61,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/margin" - android:layout_marginTop="@dimen/margin" - android:layout_marginBottom="@dimen/xsmall_margin" + android:layout_marginTop="@dimen/xsmall_margin" android:layout_marginEnd="@dimen/margin" + android:layout_marginBottom="@dimen/xsmall_margin" android:background="@drawable/selector_bg_btn" android:drawableEnd="@drawable/ic_arrow_right" android:gravity="center_vertical" diff --git a/app/src/main/res/layout/fragment_about_language_selector.xml b/app/src/main/res/layout/fragment_about_language_selector.xml new file mode 100644 index 00000000..f5e42251 --- /dev/null +++ b/app/src/main/res/layout/fragment_about_language_selector.xml @@ -0,0 +1,37 @@ + + + + + + + + \ No newline at end of file From 92ffb13e78d623518b687ec8def0048d7694419e Mon Sep 17 00:00:00 2001 From: Irina Borozan Date: Wed, 15 Jan 2020 21:40:52 +0200 Subject: [PATCH 13/29] code cleanup extracted method --- .../ro/code4/monitorizarevot/ui/settings/AboutFragment.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/settings/AboutFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/settings/AboutFragment.kt index 95e190b7..4e0fe36b 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/settings/AboutFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/settings/AboutFragment.kt @@ -42,10 +42,7 @@ class AboutFragment : BaseAnalyticsFragment() { content.text = getString(R.string.about_content).toHtml() content.movementMethod = LinkMovementMethod.getInstance() - optionChangeLanguage.setOnClickListener { - val languageSelector = AboutLanguageSelectorFragment() - languageSelector.show(requireFragmentManager(), AboutLanguageSelectorFragment.TAG) - } + optionChangeLanguage.setOnClickListener { onChangeLanguageClicked(view) } optionContact.setOnClickListener { onContactClicked(view) } @@ -54,7 +51,8 @@ class AboutFragment : BaseAnalyticsFragment() { private fun onChangeLanguageClicked(view: View) { logClickEvent(view) - // TODO: Implement + val languageSelector = AboutLanguageSelectorFragment() + languageSelector.show(requireFragmentManager(), AboutLanguageSelectorFragment.TAG) } private fun onContactClicked(view: View) { From bc419c0711f307e27468b9dda141dc426f7ca68a Mon Sep 17 00:00:00 2001 From: Irina Borozan Date: Sat, 1 Feb 2020 15:52:35 +0200 Subject: [PATCH 14/29] Create CODEOWNERS (#192) --- .github/CODEOWNERS | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..3aff1aae --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,8 @@ +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence, +# they will be requested for review when someone +# opens a pull request. +* @aniri @AlexandraDarabanQ + +# More details on creating a codeowners file: +# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners From 7035ee528282c07c2ea6158fae5260f42d1e86ee Mon Sep 17 00:00:00 2001 From: lukstbit Date: Sun, 2 Feb 2020 14:38:08 +0200 Subject: [PATCH 15/29] Add toggle icon for password visibility --- app/src/main/res/layout/activity_login.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 381c3468..aba56a33 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -94,6 +94,8 @@ android:layout_height="wrap_content" android:layout_marginTop="@dimen/margin" android:textColorHint="@color/textPrimary" + app:endIconMode="password_toggle" + app:endIconTint="@color/colorAccent" app:layout_constraintBottom_toTopOf="@id/loginButton" app:layout_constraintTop_toBottomOf="@id/phoneLayout"> @@ -113,14 +115,13 @@ From bd81fbc97bf531562083d99e89349a8454104c8c Mon Sep 17 00:00:00 2001 From: lukstbit Date: Mon, 3 Feb 2020 15:59:11 +0200 Subject: [PATCH 16/29] Fix analytics bug in AboutFragment --- .../ro/code4/monitorizarevot/ui/settings/AboutFragment.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/settings/AboutFragment.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/settings/AboutFragment.kt index 4e0fe36b..eafd7c40 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/settings/AboutFragment.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/settings/AboutFragment.kt @@ -42,11 +42,11 @@ class AboutFragment : BaseAnalyticsFragment() { content.text = getString(R.string.about_content).toHtml() content.movementMethod = LinkMovementMethod.getInstance() - optionChangeLanguage.setOnClickListener { onChangeLanguageClicked(view) } + optionChangeLanguage.setOnClickListener { onChangeLanguageClicked(it) } - optionContact.setOnClickListener { onContactClicked(view) } + optionContact.setOnClickListener { onContactClicked(it) } - optionViewPolicy.setOnClickListener { onViewPolicyClicked(view) } + optionViewPolicy.setOnClickListener { onViewPolicyClicked(it) } } private fun onChangeLanguageClicked(view: View) { From 414dd032b7af14cff11c5fb2ab67a1aa836fa232 Mon Sep 17 00:00:00 2001 From: lukstbit Date: Mon, 9 Mar 2020 12:12:04 +0200 Subject: [PATCH 17/29] Use order as forms sorting field --- .../java/ro/code4/monitorizarevot/repositories/Repository.kt | 4 ++-- .../java/ro/code4/monitorizarevot/ui/forms/FormsViewModel.kt | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt b/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt index 789985be..1e5c8340 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt @@ -177,7 +177,6 @@ class Repository : KoinComponent { return } val apiFormDetails = response.formVersions - apiFormDetails.forEachIndexed { index, formDetails -> formDetails.order = index } if (dbFormDetails == null || dbFormDetails.isEmpty()) { saveFormDetails(apiFormDetails) return @@ -191,7 +190,8 @@ class Repository : KoinComponent { } apiFormDetails.forEach { apiForm -> val dbForm = dbFormDetails.find { it.id == apiForm.id } - if (dbForm != null && apiForm.formVersion != dbForm.formVersion) { + if (dbForm != null && (apiForm.formVersion != dbForm.formVersion || + apiForm.order != dbForm.order)) { deleteFormDetails(dbForm) saveFormDetails(apiForm) } diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsViewModel.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsViewModel.kt index 231d4d59..e3c6d8f4 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsViewModel.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/forms/FormsViewModel.kt @@ -107,8 +107,6 @@ class FormsViewModel : BaseFormViewModel() { formWithSections.noAnsweredQuestions = answers.count { it.answeredQuestion.formId == formWithSections.form.id } } - - val filterDiasporaForms = try { FirebaseRemoteConfig.getInstance().getBoolean(REMOTE_CONFIG_FILTER_DIASPORA_FORMS) } catch (e: Exception) { @@ -120,7 +118,7 @@ class FormsViewModel : BaseFormViewModel() { } else -> forms.filter { it.form.diaspora == false }.map { FormListItem(it) } } - + formsList.sortedBy { it.formWithSections.form.order } items.addAll(formsList) items.add(AddNoteListItem()) formsLiveData.postValue(items) From e87e8113d6627d3b9749da6129b92189a4676ac4 Mon Sep 17 00:00:00 2001 From: lukstbit Date: Tue, 10 Mar 2020 16:08:03 +0200 Subject: [PATCH 18/29] Add order as sorting field for counties --- .../3.json | 643 ++++++++++++++++++ .../code4/monitorizarevot/data/AppDatabase.kt | 2 +- .../code4/monitorizarevot/data/Migrations.kt | 8 +- .../monitorizarevot/data/dao/CountyDao.kt | 3 + .../monitorizarevot/data/model/County.kt | 25 +- .../repositories/Repository.kt | 16 +- .../monitorizarevot/services/ApiInterface.kt | 2 +- .../PollingStationSelectionViewModel.kt | 2 +- 8 files changed, 682 insertions(+), 19 deletions(-) create mode 100644 app/schemas/ro.code4.monitorizarevot.data.AppDatabase/3.json diff --git a/app/schemas/ro.code4.monitorizarevot.data.AppDatabase/3.json b/app/schemas/ro.code4.monitorizarevot.data.AppDatabase/3.json new file mode 100644 index 00000000..496f1a69 --- /dev/null +++ b/app/schemas/ro.code4.monitorizarevot.data.AppDatabase/3.json @@ -0,0 +1,643 @@ +{ + "formatVersion": 1, + "database": { + "version": 3, + "identityHash": "0a098e610a2bdede0c5613b5840d8193", + "entities": [ + { + "tableName": "county", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `code` TEXT NOT NULL, `name` TEXT NOT NULL, `limit` INTEGER NOT NULL, `diaspora` INTEGER, `order` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "limit", + "columnName": "limit", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaspora", + "columnName": "diaspora", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_county_code", + "unique": true, + "columnNames": [ + "code" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_county_code` ON `${TABLE_NAME}` (`code`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "polling_station", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `countyCode` TEXT NOT NULL, `idPollingStation` INTEGER NOT NULL, `urbanArea` INTEGER NOT NULL, `isPollingStationPresidentFemale` INTEGER NOT NULL, `observerArrivalTime` TEXT, `observerLeaveTime` TEXT, `synced` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`countyCode`) REFERENCES `county`(`code`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "countyCode", + "columnName": "countyCode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "idPollingStation", + "columnName": "idPollingStation", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "urbanArea", + "columnName": "urbanArea", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPollingStationPresidentFemale", + "columnName": "isPollingStationPresidentFemale", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "observerArrivalTime", + "columnName": "observerArrivalTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "observerLeaveTime", + "columnName": "observerLeaveTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "synced", + "columnName": "synced", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_polling_station_countyCode_idPollingStation", + "unique": true, + "columnNames": [ + "countyCode", + "idPollingStation" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_polling_station_countyCode_idPollingStation` ON `${TABLE_NAME}` (`countyCode`, `idPollingStation`)" + } + ], + "foreignKeys": [ + { + "table": "county", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "countyCode" + ], + "referencedColumns": [ + "code" + ] + } + ] + }, + { + "tableName": "form_details", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `code` TEXT NOT NULL, `description` TEXT NOT NULL, `formVersion` INTEGER NOT NULL, `diaspora` INTEGER, `order` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "formVersion", + "columnName": "formVersion", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaspora", + "columnName": "diaspora", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "section", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uniqueId` TEXT NOT NULL, `code` TEXT, `description` TEXT, `formId` INTEGER NOT NULL, PRIMARY KEY(`uniqueId`), FOREIGN KEY(`formId`) REFERENCES `form_details`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "uniqueId", + "columnName": "uniqueId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "formId", + "columnName": "formId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "uniqueId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [ + { + "table": "form_details", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "formId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "question", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `text` TEXT NOT NULL, `code` TEXT NOT NULL, `questionType` INTEGER NOT NULL, `sectionId` TEXT NOT NULL, `hasNotes` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`sectionId`) REFERENCES `section`(`uniqueId`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "questionType", + "columnName": "questionType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sectionId", + "columnName": "sectionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasNotes", + "columnName": "hasNotes", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [ + { + "table": "section", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "sectionId" + ], + "referencedColumns": [ + "uniqueId" + ] + } + ] + }, + { + "tableName": "answer", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`idOption` INTEGER NOT NULL, `text` TEXT NOT NULL, `isFreeText` INTEGER NOT NULL, `questionId` INTEGER NOT NULL, PRIMARY KEY(`idOption`), FOREIGN KEY(`questionId`) REFERENCES `question`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "idOption", + "columnName": "idOption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isFreeText", + "columnName": "isFreeText", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "questionId", + "columnName": "questionId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "idOption" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [ + { + "table": "question", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "questionId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "answered_question", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `formId` INTEGER NOT NULL, `questionId` INTEGER NOT NULL, `countyCode` TEXT NOT NULL, `pollingStationNumber` INTEGER NOT NULL, `savedLocally` INTEGER NOT NULL, `synced` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`formId`) REFERENCES `form_details`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`questionId`) REFERENCES `question`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`countyCode`, `pollingStationNumber`) REFERENCES `polling_station`(`countyCode`, `idPollingStation`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "formId", + "columnName": "formId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "questionId", + "columnName": "questionId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "countyCode", + "columnName": "countyCode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pollingStationNumber", + "columnName": "pollingStationNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "savedLocally", + "columnName": "savedLocally", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "synced", + "columnName": "synced", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_answered_question_countyCode_pollingStationNumber_id", + "unique": true, + "columnNames": [ + "countyCode", + "pollingStationNumber", + "id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_answered_question_countyCode_pollingStationNumber_id` ON `${TABLE_NAME}` (`countyCode`, `pollingStationNumber`, `id`)" + } + ], + "foreignKeys": [ + { + "table": "form_details", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "formId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "question", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "questionId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "polling_station", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "countyCode", + "pollingStationNumber" + ], + "referencedColumns": [ + "countyCode", + "idPollingStation" + ] + } + ] + }, + { + "tableName": "selected_answer", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`optionId` INTEGER NOT NULL, `value` TEXT, `countyCode` TEXT NOT NULL, `pollingStationNumber` INTEGER NOT NULL, `questionId` TEXT NOT NULL, PRIMARY KEY(`optionId`, `countyCode`, `pollingStationNumber`), FOREIGN KEY(`optionId`) REFERENCES `answer`(`idOption`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`countyCode`, `pollingStationNumber`, `questionId`) REFERENCES `answered_question`(`countyCode`, `pollingStationNumber`, `id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "optionId", + "columnName": "optionId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "countyCode", + "columnName": "countyCode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pollingStationNumber", + "columnName": "pollingStationNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "questionId", + "columnName": "questionId", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "optionId", + "countyCode", + "pollingStationNumber" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [ + { + "table": "answer", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "optionId" + ], + "referencedColumns": [ + "idOption" + ] + }, + { + "table": "answered_question", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "countyCode", + "pollingStationNumber", + "questionId" + ], + "referencedColumns": [ + "countyCode", + "pollingStationNumber", + "id" + ] + } + ] + }, + { + "tableName": "note", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uriPath` TEXT, `description` TEXT NOT NULL, `questionId` INTEGER, `date` INTEGER NOT NULL, `countyCode` TEXT NOT NULL, `pollingStationNumber` INTEGER NOT NULL, `synced` INTEGER NOT NULL, FOREIGN KEY(`questionId`) REFERENCES `question`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`countyCode`, `pollingStationNumber`) REFERENCES `polling_station`(`countyCode`, `idPollingStation`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uriPath", + "columnName": "uriPath", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "questionId", + "columnName": "questionId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "countyCode", + "columnName": "countyCode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pollingStationNumber", + "columnName": "pollingStationNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "synced", + "columnName": "synced", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_note_countyCode_pollingStationNumber_questionId", + "unique": false, + "columnNames": [ + "countyCode", + "pollingStationNumber", + "questionId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_note_countyCode_pollingStationNumber_questionId` ON `${TABLE_NAME}` (`countyCode`, `pollingStationNumber`, `questionId`)" + } + ], + "foreignKeys": [ + { + "table": "question", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "questionId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "polling_station", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "countyCode", + "pollingStationNumber" + ], + "referencedColumns": [ + "countyCode", + "idPollingStation" + ] + } + ] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0a098e610a2bdede0c5613b5840d8193')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/data/AppDatabase.kt b/app/src/main/java/ro/code4/monitorizarevot/data/AppDatabase.kt index 46ce76b4..398fd819 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/data/AppDatabase.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/data/AppDatabase.kt @@ -16,7 +16,7 @@ import ro.code4.monitorizarevot.data.model.answers.SelectedAnswer @Database( entities = [County::class, PollingStation::class, FormDetails::class, Section::class, Question::class, Answer::class, AnsweredQuestion::class, SelectedAnswer::class, Note::class], - version = 2 + version = 3 ) @TypeConverters(DateConverter::class) abstract class AppDatabase : RoomDatabase() { diff --git a/app/src/main/java/ro/code4/monitorizarevot/data/Migrations.kt b/app/src/main/java/ro/code4/monitorizarevot/data/Migrations.kt index 3397077c..92500b63 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/data/Migrations.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/data/Migrations.kt @@ -18,5 +18,11 @@ object Migrations { } } - val ALL: Array = arrayOf(MIGRATION_1_2) + val MIGRATION_2_3 = object : Migration(2, 3) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE county ADD COLUMN `order` INTEGER NOT NULL DEFAULT 0") + } + } + + val ALL: Array = arrayOf(MIGRATION_1_2, MIGRATION_2_3) } \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/data/dao/CountyDao.kt b/app/src/main/java/ro/code4/monitorizarevot/data/dao/CountyDao.kt index 6e6ca084..a80a0fef 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/data/dao/CountyDao.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/data/dao/CountyDao.kt @@ -1,6 +1,7 @@ package ro.code4.monitorizarevot.data.dao import androidx.room.Dao +import androidx.room.Delete import androidx.room.Insert import androidx.room.OnConflictStrategy.REPLACE import androidx.room.Query @@ -19,4 +20,6 @@ interface CountyDao { @Query("SELECT * FROM county where code=:countyCode") fun get(countyCode: String): Maybe + @Query("DELETE FROM county") + fun deleteAll() } \ No newline at end of file diff --git a/app/src/main/java/ro/code4/monitorizarevot/data/model/County.kt b/app/src/main/java/ro/code4/monitorizarevot/data/model/County.kt index 92e1e6ce..2c706a94 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/data/model/County.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/data/model/County.kt @@ -4,6 +4,7 @@ import androidx.room.Entity import androidx.room.Index import androidx.room.PrimaryKey import com.google.gson.annotations.Expose +import com.google.gson.annotations.SerializedName import org.parceler.Parcel @Entity(tableName = "county", indices = [Index(value = ["code"], unique = true)]) @@ -21,17 +22,33 @@ class County { lateinit var name: String @Expose + @SerializedName("numberOfPollingStations") var limit: Int = 0 @Expose var diaspora: Boolean? = null + @Expose + var order: Int = 0 + override fun toString(): String = name override fun equals(other: Any?): Boolean { - if (other !is County) { - return false - } - return code == other.code + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as County + if (code != other.code) return false + if (limit != other.limit) return false + if (diaspora != other.diaspora) return false + if (order != other.order) return false + return true + } + + override fun hashCode(): Int { + var result = code.hashCode() + result = 31 * result + limit + result = 31 * result + (diaspora?.hashCode() ?: 0) + result = 31 * result + order + return result } } diff --git a/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt b/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt index 789985be..c15cc122 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt @@ -55,27 +55,21 @@ class Repository : KoinComponent { loginInterface.registerForNotification(token) fun getCounties(): Single> { - val observableApi = apiInterface.getCounties() val observableDb = db.countyDao().getAll().take(1).single(emptyList()) - return Single.zip( observableDb, observableApi.onErrorReturnItem(emptyList()), BiFunction, List, List> { dbCounties, apiCounties -> - //todo side effects are recommended in "do" methods, check: https://github.com/Froussios/Intro-To-RxJava/blob/master/Part%203%20-%20Taming%20the%20sequence/1.%20Side%20effects.md - val all = - apiCounties.all { apiCounty -> dbCounties.find { it.id == apiCounty.id } == apiCounty } - apiCounties.forEach { - it.name = it.name.toLowerCase(Locale.getDefault()).capitalize() - } + val areAllApiCountiesInDb = apiCounties.all(dbCounties::contains) + apiCounties.forEach { it.name = it.name.toLowerCase(Locale.getDefault()).capitalize() } return@BiFunction when { - apiCounties.isNotEmpty() && !all -> { - // TODO deleteCounties() + apiCounties.isNotEmpty() && !areAllApiCountiesInDb -> { + db.countyDao().deleteAll() db.countyDao().save(*apiCounties.map { it }.toTypedArray()) apiCounties } - apiCounties.isNotEmpty() && all -> apiCounties + apiCounties.isNotEmpty() && areAllApiCountiesInDb -> apiCounties else -> dbCounties } } diff --git a/app/src/main/java/ro/code4/monitorizarevot/services/ApiInterface.kt b/app/src/main/java/ro/code4/monitorizarevot/services/ApiInterface.kt index 620daad9..21d36782 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/services/ApiInterface.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/services/ApiInterface.kt @@ -15,7 +15,7 @@ interface ApiInterface { @GET("/api/v1/form") fun getForms(@Query("diaspora") diaspora: Boolean? = null): Observable - @GET("/api/v1/polling-station") + @GET("/api/v1/county") fun getCounties(): Single> @GET("/api/v1/form/{formId}") diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionViewModel.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionViewModel.kt index 2bd386fc..bc253a84 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionViewModel.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/section/selection/PollingStationSelectionViewModel.kt @@ -51,7 +51,7 @@ class PollingStationSelectionViewModel : BaseViewModel() { private fun updateCounties() { val countyCode = sharedPreferences.getCountyCode() val pollingStationNumber = sharedPreferences.getPollingStationNumber() - val countyNames = counties.map { it.name } + val countyNames = counties.sortedBy { county -> county.order }.map { it.name } if (countyCode.isNullOrBlank()) { countiesLiveData.postValue(Result.Success(listOf(app.getString(R.string.polling_station_spinner_choose)) + countyNames)) From daf6719333be7559db8c92a17ab68882a7187e1a Mon Sep 17 00:00:00 2001 From: lukstbit Date: Tue, 10 Mar 2020 16:08:42 +0200 Subject: [PATCH 19/29] Add tests for database migrations --- .../ro/code4/monitorizarevot/MigrationTest.kt | 76 ++++++++++++++----- 1 file changed, 58 insertions(+), 18 deletions(-) diff --git a/app/src/androidTest/java/ro/code4/monitorizarevot/MigrationTest.kt b/app/src/androidTest/java/ro/code4/monitorizarevot/MigrationTest.kt index a9e8bea7..7a697200 100644 --- a/app/src/androidTest/java/ro/code4/monitorizarevot/MigrationTest.kt +++ b/app/src/androidTest/java/ro/code4/monitorizarevot/MigrationTest.kt @@ -1,6 +1,7 @@ package ro.code4.monitorizarevot import android.content.ContentValues +import android.database.Cursor.FIELD_TYPE_NULL import android.database.sqlite.SQLiteDatabase import androidx.room.Room import androidx.room.testing.MigrationTestHelper @@ -26,11 +27,10 @@ class MigrationTest { @Test @Throws(IOException::class) fun migrate1To2() { - var rowId = -1L helper.createDatabase(TEST_DB, 1).use { - rowId = it.insert( + val rowId = it.insert( "county", - SQLiteDatabase.CONFLICT_REPLACE, + SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply { put("id", 1) put("code", "AA") @@ -38,20 +38,63 @@ class MigrationTest { put("`limit`", 12) } ) + assertTrue(rowId > 0) } - helper.runMigrationsAndValidate(TEST_DB, 2, true, Migrations.MIGRATION_1_2).use { db -> - db.query("SELECT * FROM county WHERE rowid = ?", arrayOf(rowId)).use { c -> - assertNotNull(c) - assertTrue(c.moveToFirst()) - // Check new column - assertEquals(null, c.getInt(c.getColumnIndex("diaspora"))) - // Check some old columns - assertEquals(1, c.getInt(c.getColumnIndex("id"))) - assertEquals("AA", c.getString(c.getColumnIndex("code"))) + val foundDataCursor = db.query("SELECT * FROM county") + assertNotNull(foundDataCursor) + foundDataCursor.use { + assertNotNull(it) + // we have a single row, previously inserted + assertEquals(1, it.count) + assertTrue(it.moveToFirst()) + // at this moment we expect 5 columns for the county table + assertEquals(5, it.columnCount) + // check for the diaspora column and that it has null as value(immediately after the + // migration), simply using getInt() doesn't work due to the undefined way the method + // behaves when having null in an INTEGER field + assertEquals(FIELD_TYPE_NULL, it.getType(it.getColumnIndex("diaspora"))) + // check for older columns + assertEquals(1, it.getInt(it.getColumnIndex("id"))) + assertEquals("AA", it.getString(it.getColumnIndex("code"))) + assertEquals("TEST", it.getString(it.getColumnIndex("name"))) + assertEquals(12, it.getInt(it.getColumnIndex("limit"))) } } + } + @Test + fun migrate2To3() { + helper.createDatabase(TEST_DB, 2).use { + val values = ContentValues().apply { + put("id", 1) + put("code", "AA") + put("name", "TEST") + put("`limit`", 12) + put("diaspora", false) + } + val rowId = it.insert("county", SQLiteDatabase.CONFLICT_FAIL, values) + assertTrue(rowId > 0) + } + val db = helper.runMigrationsAndValidate(TEST_DB, 3, true, Migrations.MIGRATION_2_3) + val foundDataCursor = db.query("SELECT * FROM county") + assertNotNull(foundDataCursor) + foundDataCursor.use { + assertNotNull(it) + // we have a single row, previously inserted + assertEquals(1, it.count) + assertTrue(it.moveToFirst()) + // at this point we expect to have exactly 6 columns for the county table + assertEquals(6, it.columnCount) + // check for the new column "order" and that it has the default value of 0 + assertEquals(0, it.getInt(it.getColumnIndex("order"))) + // check for older columns + assertEquals(1, it.getInt(it.getColumnIndex("id"))) + assertEquals("AA", it.getString(it.getColumnIndex("code"))) + assertEquals("TEST", it.getString(it.getColumnIndex("name"))) + val diasporaColumnValue = it.getInt(it.getColumnIndex("diaspora")) != 0 + assertEquals(false, diasporaColumnValue) + } } @Test @@ -61,20 +104,17 @@ class MigrationTest { helper.createDatabase(TEST_DB, 1).apply { close() } - - // Open latest version of the database. Room will validate the schema + // Open latest version of the database in memory. Room will validate the schema // once all migrations execute. - Room.databaseBuilder( + Room.inMemoryDatabaseBuilder( InstrumentationRegistry.getInstrumentation().targetContext, - AppDatabase::class.java, - TEST_DB + AppDatabase::class.java ).addMigrations(*Migrations.ALL).build().apply { openHelper.writableDatabase close() } } - companion object { private const val TEST_DB = "test-db" } From b922d9b62fa92ebaf1a2bc291b5ed6392c88df40 Mon Sep 17 00:00:00 2001 From: lukstbit Date: Mon, 8 Jun 2020 08:12:40 +0300 Subject: [PATCH 20/29] Add github workflow for sonar --- .github/workflows/sonar.yml | 26 ++++++++++++++++++++++++++ app/build.gradle | 10 ++++++++++ build.gradle | 2 ++ 3 files changed, 38 insertions(+) create mode 100644 .github/workflows/sonar.yml diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml new file mode 100644 index 00000000..e3ed5378 --- /dev/null +++ b/.github/workflows/sonar.yml @@ -0,0 +1,26 @@ +name: SonarCloud + +on: + push: + branches: + - master + - develop + +jobs: + sonar: + name: SonarCloud Scan + runs-on: ubuntu-latest + steps: + - name: Checkout project + uses: actions/checkout@v2 + - name: Validate gradle wrapper + uses: gradle/wrapper-validation-action@v1 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Analyze with SonarCloud + run: ./gradlew clean build sonar:sonar -Dsonar.login=$SONAR_TOKEN + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/app/build.gradle b/app/build.gradle index 03d24501..85694b75 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,6 +2,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-android-extensions' +apply plugin: 'org.sonarqube' // TODO: Uncomment this to enable FirebaseAnalytics and Crashlytics //apply plugin: 'io.fabric' @@ -165,5 +166,14 @@ dependencies { androidTestImplementation "androidx.room:room-testing:$roomVersion" } +sonarqube { + properties { + property 'sonar.projectName', 'Vote Monitor Android' + property 'sonar.projectKey', 'code4romania_vote-monitor-android' + property 'sonar.host.url', 'https://sonarcloud.io' + property 'sonar.organization', 'code4romania' + } +} + // TODO: Uncomment this to enable FirebaseAnalytics and Crashlytics //apply plugin: 'com.google.gms.google-services' diff --git a/build.gradle b/build.gradle index b2db9f34..c82bb406 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,7 @@ buildscript { google() jcenter() maven { url 'https://maven.fabric.io/public' } + maven { url "https://plugins.gradle.org/m2/" } } dependencies { classpath 'com.android.tools.build:gradle:3.6.0-beta04' @@ -13,6 +14,7 @@ buildscript { classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.1.0' classpath 'com.google.gms:google-services:4.3.3' classpath 'io.fabric.tools:gradle:1.31.2' + classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.0" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } From 877388a5d7bf0a6c288e5c5112d1d0d79b8e12de Mon Sep 17 00:00:00 2001 From: lukstbit Date: Sat, 29 Feb 2020 14:49:51 +0200 Subject: [PATCH 21/29] Update dependencies to latest versions --- app/build.gradle | 88 +++++++++---------- .../monitorizarevot/ui/base/BaseActivity.kt | 2 +- build.gradle | 7 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 4 files changed, 47 insertions(+), 52 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 03d24501..709ffd5a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,6 +17,7 @@ androidExtensions { android { compileSdkVersion 29 + buildToolsVersion = '29.0.3' defaultConfig { applicationId "ro.code4.monitorizarevot" @@ -26,7 +27,6 @@ android { versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - javaCompileOptions { annotationProcessorOptions { arguments = [ @@ -49,7 +49,6 @@ android { keyPassword getKey("keystore", "keyPassword") storePassword getKey("keystore", "storePassword") } - } buildTypes { @@ -69,14 +68,14 @@ android { debug { minifyEnabled false - manifestPlaceholders = [enableCrashReporting:"false"] + manifestPlaceholders = [enableCrashReporting: "false"] } release { minifyEnabled false signingConfig signingConfigs.fastlane proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - manifestPlaceholders = [enableCrashReporting:"true"] + manifestPlaceholders = [enableCrashReporting: "true"] } } @@ -90,7 +89,6 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } - buildToolsVersion = '29.0.2' kotlinOptions { jvmTarget = "1.8" @@ -98,10 +96,14 @@ android { } ext { - retrofitLibraryVersion = '2.5.0' + retrofitVersion = '2.9.0' + navigationVersion = "2.2.2" koinVersion = '2.0.1' - roomVersion = '2.2.0' - firebaseVersion = '17.2.1' + parcelerVersion = "1.1.13" + roomVersion = '2.2.5' + firebaseAnalyticsVersion = '17.4.2' + firebaseMessagingVersion = "20.2.0" + firebaseConfigVersion = "19.1.4" crashlyticsVersion = '2.10.1' adapterDelegatesVersion = "4.2.0" } @@ -110,56 +112,50 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.1.0' - implementation "androidx.preference:preference-ktx:1.1.0" - implementation 'androidx.core:core-ktx:1.1.0' + implementation 'androidx.annotation:annotation:1.1.0' + implementation "androidx.preference:preference-ktx:1.1.1" + implementation 'androidx.core:core-ktx:1.3.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'com.google.android.material:material:1.2.0-alpha01' + implementation 'com.google.android.material:material:1.3.0-alpha01' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'androidx.navigation:navigation-fragment:2.1.0' - implementation 'androidx.navigation:navigation-ui:2.1.0' - implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0' - implementation 'androidx.navigation:navigation-fragment-ktx:2.1.0' - implementation 'androidx.navigation:navigation-ui-ktx:2.1.0' - implementation 'androidx.annotation:annotation:1.1.0' - + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' - - implementation "com.squareup.retrofit2:retrofit:$retrofitLibraryVersion" - implementation "com.squareup.retrofit2:converter-gson:$retrofitLibraryVersion" - implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitLibraryVersion" - implementation "com.squareup.retrofit2:converter-scalars:$retrofitLibraryVersion" - implementation 'com.squareup.okhttp3:logging-interceptor:4.1.1' - + implementation 'com.sylversky.fontreplacer:fontreplacer:1.0' + implementation 'com.yqritc:recyclerview-flexibledivider:1.4.0' + implementation 'me.relex:circleindicator:2.1.4' + implementation "com.hannesdorfmann:adapterdelegates4:$adapterDelegatesVersion" + implementation "androidx.viewpager2:viewpager2:1.0.0" + // Navigation + implementation "androidx.navigation:navigation-fragment:$navigationVersion" + implementation "androidx.navigation:navigation-ui:$navigationVersion" + implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion" + implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion" + // Retrofit + implementation "com.squareup.retrofit2:retrofit:$retrofitVersion" + implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion" + implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion" + implementation "com.squareup.retrofit2:converter-scalars:$retrofitVersion" + implementation 'com.squareup.okhttp3:logging-interceptor:4.7.2' // Firebase - implementation "com.google.firebase:firebase-analytics:$firebaseVersion" - implementation 'com.google.firebase:firebase-messaging:20.0.1' - implementation 'com.google.firebase:firebase-config:19.0.3' + implementation "com.google.firebase:firebase-analytics:$firebaseAnalyticsVersion" + implementation "com.google.firebase:firebase-messaging:$firebaseMessagingVersion" + implementation "com.google.firebase:firebase-config:$firebaseConfigVersion" implementation "com.crashlytics.sdk.android:crashlytics:$crashlyticsVersion" - - //koin injection + // Koin injection implementation "org.koin:koin-android:$koinVersion" implementation "org.koin:koin-android-viewmodel:$koinVersion" - - implementation 'org.parceler:parceler-api:1.1.12' - kapt 'org.parceler:parceler:1.1.12' - - //Room + // Parceler + implementation "org.parceler:parceler-api:$parcelerVersion" + kapt "org.parceler:parceler:$parcelerVersion" + // Room implementation "androidx.room:room-runtime:$roomVersion" kapt "androidx.room:room-compiler:$roomVersion" implementation "androidx.room:room-ktx:$roomVersion" implementation "androidx.room:room-rxjava2:$roomVersion" - implementation 'com.sylversky.fontreplacer:fontreplacer:1.0' - implementation 'com.yqritc:recyclerview-flexibledivider:1.4.0' - - implementation 'me.relex:circleindicator:2.1.4' - - implementation "com.hannesdorfmann:adapterdelegates4:$adapterDelegatesVersion" - - implementation "androidx.viewpager2:viewpager2:1.0.0-rc01" - - // Testing - testImplementation 'junit:junit:4.12' + // Unit tests + testImplementation 'junit:junit:4.13' + // Instrumented tests androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' androidTestImplementation "androidx.room:room-testing:$roomVersion" diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseActivity.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseActivity.kt index bee1ab20..8163c8fe 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseActivity.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseActivity.kt @@ -77,7 +77,7 @@ abstract class BaseActivity : AppCompatActivity(), Layout when (exception) { is HttpException -> { if (exception.code() == 400) { - val apiError400 = exception.response().errorBody()?.string()?.fromJson(Gson(), APIError400::class.java) + val apiError400 = exception.response()?.errorBody()?.string()?.fromJson(Gson(), APIError400::class.java) apiError400?.let { if (it.error == null) { diff --git a/build.gradle b/build.gradle index b2db9f34..9224fb60 100644 --- a/build.gradle +++ b/build.gradle @@ -1,16 +1,15 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. - buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.3.72' repositories { google() jcenter() maven { url 'https://maven.fabric.io/public' } } dependencies { - classpath 'com.android.tools.build:gradle:3.6.0-beta04' + classpath 'com.android.tools.build:gradle:4.0.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.1.0' + classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.2.2' classpath 'com.google.gms:google-services:4.3.3' classpath 'io.fabric.tools:gradle:1.31.2' // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 163b82a9..fe745ce5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-all.zip From 5035d1643be4958b334f86ba3849713fa02d5837 Mon Sep 17 00:00:00 2001 From: lukstbit Date: Tue, 9 Jun 2020 08:05:21 +0300 Subject: [PATCH 22/29] Fix sonar workflow --- .github/workflows/sonar.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index e3ed5378..a3410524 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -20,7 +20,7 @@ jobs: with: java-version: 1.8 - name: Analyze with SonarCloud - run: ./gradlew clean build sonar:sonar -Dsonar.login=$SONAR_TOKEN + run: ./gradlew clean assembleDebug sonar -Dsonar.login=$SONAR_TOKEN env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} From 31e49c300ea61e2002f8ef56636bd36ef3df8c54 Mon Sep 17 00:00:00 2001 From: lukstbit Date: Sat, 4 Jul 2020 12:49:01 +0300 Subject: [PATCH 23/29] Update app to use Firebase Crashlytics --- app/build.gradle | 10 +++++----- build.gradle | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index eaf77612..69b5ca4f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,8 +3,8 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-android-extensions' apply plugin: 'org.sonarqube' -// TODO: Uncomment this to enable FirebaseAnalytics and Crashlytics -//apply plugin: 'io.fabric' +// TODO: Uncomment this to enable Firebase Crashlytics +// apply plugin: 'com.google.firebase.crashlytics' static def getKey(String env, String key) { Properties props = new Properties() @@ -102,10 +102,10 @@ ext { koinVersion = '2.0.1' parcelerVersion = "1.1.13" roomVersion = '2.2.5' - firebaseAnalyticsVersion = '17.4.2' + firebaseAnalyticsVersion = '17.4.3' + firebaseCrashlyticsVersion = '17.1.0' firebaseMessagingVersion = "20.2.0" firebaseConfigVersion = "19.1.4" - crashlyticsVersion = '2.10.1' adapterDelegatesVersion = "4.2.0" } @@ -141,7 +141,7 @@ dependencies { implementation "com.google.firebase:firebase-analytics:$firebaseAnalyticsVersion" implementation "com.google.firebase:firebase-messaging:$firebaseMessagingVersion" implementation "com.google.firebase:firebase-config:$firebaseConfigVersion" - implementation "com.crashlytics.sdk.android:crashlytics:$crashlyticsVersion" + implementation "com.google.firebase:firebase-crashlytics:$firebaseCrashlyticsVersion" // Koin injection implementation "org.koin:koin-android:$koinVersion" implementation "org.koin:koin-android-viewmodel:$koinVersion" diff --git a/build.gradle b/build.gradle index 8e4fdce4..cbc38d87 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,6 @@ buildscript { repositories { google() jcenter() - maven { url 'https://maven.fabric.io/public' } maven { url "https://plugins.gradle.org/m2/" } } dependencies { @@ -12,7 +11,7 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.2.2' classpath 'com.google.gms:google-services:4.3.3' - classpath 'io.fabric.tools:gradle:1.31.2' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.0' classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.0" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From 98794ff5161937af597fd13153ba1203125833c4 Mon Sep 17 00:00:00 2001 From: Irina Borozan Date: Sat, 25 Jul 2020 19:59:26 +0300 Subject: [PATCH 24/29] updated backend url --- Readme.md | 2 +- debug.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index e51a0ace..67e2775a 100644 --- a/Readme.md +++ b/Readme.md @@ -40,7 +40,7 @@ The app is localized, meaning it's easier for any interested party to fork the p More info on redeploying and reusing the app can be found in the wiki: [Redeploy steps](https://github.com/code4romania/mon-vot-android-kotlin/wiki/Steps-for-redeploying---reusing-the-app) & [Google Play Deploy Steps](https://github.com/code4romania/mon-vot-android-kotlin/wiki/Google-Play-Deploy-Steps) -Swagger docs for the API are available [here](http://mv-mobile-test.azurewebsites.net/swagger/index.html). +Swagger docs for the API are available [here](https://app-vmon-api-dev.azurewebsites.net/swagger/index.html). ## Repos and projects diff --git a/debug.properties b/debug.properties index 210749ee..4be6ff59 100644 --- a/debug.properties +++ b/debug.properties @@ -1,4 +1,4 @@ -apiUrl=https://mv-mobile-test.azurewebsites.net/api/v1/ +apiUrl=https://app-vmon-api-dev.azurewebsites.net/api/v1/ guideUrl=https://fiecarevot.ro/wp-content/uploads/2019/11/Manual-prezidentiale.pdf organisationWebUrl=https://code4.ro/ serviceCenterPhoneNumber=0800080200 From cbe568888216be8093cd75c29b79d72569e845ff Mon Sep 17 00:00:00 2001 From: Irina Borozan Date: Sat, 25 Jul 2020 22:23:33 +0300 Subject: [PATCH 25/29] updated test account --- app/src/main/res/values/nontranslatable.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/nontranslatable.xml b/app/src/main/res/values/nontranslatable.xml index 56f801df..13825662 100644 --- a/app/src/main/res/values/nontranslatable.xml +++ b/app/src/main/res/values/nontranslatable.xml @@ -1,5 +1,5 @@ 0722222222 - 123456 + 1234 From 1392fa8bb2c16d85625a93e445803fa93e3b7c54 Mon Sep 17 00:00:00 2001 From: Irina Borozan Date: Sat, 29 Aug 2020 23:19:19 +0300 Subject: [PATCH 26/29] closes #206 add about icon in menu --- app/src/main/res/drawable/ic_menu_about.xml | 8 ++++++++ app/src/main/res/menu/main_menu.xml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/ic_menu_about.xml diff --git a/app/src/main/res/drawable/ic_menu_about.xml b/app/src/main/res/drawable/ic_menu_about.xml new file mode 100644 index 00000000..04e6bb5e --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_about.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml index 3b570ba5..c5b9daa9 100644 --- a/app/src/main/res/menu/main_menu.xml +++ b/app/src/main/res/menu/main_menu.xml @@ -31,7 +31,7 @@ From 241e401739d051088156f3e18d06940692f39a13 Mon Sep 17 00:00:00 2001 From: Irina Borozan Date: Mon, 21 Sep 2020 21:54:40 +0300 Subject: [PATCH 27/29] update forms sync and backend link --- .../java/ro/code4/monitorizarevot/repositories/Repository.kt | 3 +++ debug.properties | 2 +- release.properties | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt b/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt index e8a45912..7852f7dd 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/repositories/Repository.kt @@ -189,6 +189,9 @@ class Repository : KoinComponent { deleteFormDetails(dbForm) saveFormDetails(apiForm) } + if (dbForm == null) { + saveFormDetails(apiForm) + } } } diff --git a/debug.properties b/debug.properties index 4be6ff59..3d81daa7 100644 --- a/debug.properties +++ b/debug.properties @@ -1,4 +1,4 @@ -apiUrl=https://app-vmon-api-dev.azurewebsites.net/api/v1/ +apiUrl=https://api.votemonitor.org/api/v1/ guideUrl=https://fiecarevot.ro/wp-content/uploads/2019/11/Manual-prezidentiale.pdf organisationWebUrl=https://code4.ro/ serviceCenterPhoneNumber=0800080200 diff --git a/release.properties b/release.properties index acaf00a2..52d27928 100644 --- a/release.properties +++ b/release.properties @@ -1,7 +1,7 @@ -apiUrl=http://blah.com +apiUrl=https://api.votemonitor.org/api/v1/ guideUrl=https://fiecarevot.ro/wp-content/uploads/2019/11/Manual-prezidentiale.pdf organisationWebUrl=https://code4.ro/ serviceCenterPhoneNumber=0800080200 preferredLocale=ro_RO privacyWebUrl = https://docs.google.com/document/u/1/d/e/2PACX-1vRwbFULgMu6VqKNYGFSN5migRPd8cI2YstFRWsega0cGLphaGBb6QjFVgO6th6owDv3WiRQwxe5wKSI/pub -supportEmail = contact@code4.ro \ No newline at end of file +supportEmail = contact@code4.ro From 8df3a1776bbd04597bd0f7520326f73d38e31075 Mon Sep 17 00:00:00 2001 From: Irina Borozan Date: Fri, 25 Sep 2020 00:47:33 +0300 Subject: [PATCH 28/29] added new link in menu --- app/build.gradle | 1 + .../ro/code4/monitorizarevot/ui/main/MainActivity.kt | 12 +++++++++--- app/src/main/res/menu/main_menu.xml | 4 ++++ app/src/main/res/values-ro-rRO/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + debug.properties | 5 +++-- release.properties | 3 ++- 7 files changed, 21 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 69b5ca4f..5ca17334 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -56,6 +56,7 @@ android { applicationVariants.all { variant -> variant.buildConfigField "String", "API_URL", "\"" + getKey(variant.buildType.name, "apiUrl") + "\"" variant.buildConfigField "String", "GUIDE_URL", "\"" + getKey(variant.buildType.name, "guideUrl") + "\"" + variant.buildConfigField "String", "SAFETY_URL", "\"" + getKey(variant.buildType.name, "safetyUrl") + "\"" variant.buildConfigField "String", "ORGANISATION_WEB_URL", "\"" + getKey(variant.buildType.name, "organisationWebUrl") + "\"" variant.buildConfigField "String", "SERVICE_CENTER_PHONE_NUMBER", "\"" + getKey(variant.buildType.name, "serviceCenterPhoneNumber") + "\"" variant.buildConfigField "String", "PREFERRED_LOCALE", "\"" + getKey(variant.buildType.name, "preferredLocale") + "\"" diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/main/MainActivity.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/main/MainActivity.kt index 82aa9ebe..e995e975 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/main/MainActivity.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/main/MainActivity.kt @@ -15,11 +15,10 @@ import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.app_bar_main.* import org.koin.android.ext.android.inject import org.koin.android.viewmodel.ext.android.viewModel +import ro.code4.monitorizarevot.BuildConfig import ro.code4.monitorizarevot.R import ro.code4.monitorizarevot.analytics.Event -import ro.code4.monitorizarevot.helper.callSupportCenter -import ro.code4.monitorizarevot.helper.changePollingStation -import ro.code4.monitorizarevot.helper.startActivityWithoutTrace +import ro.code4.monitorizarevot.helper.* import ro.code4.monitorizarevot.ui.base.BaseActivity import ro.code4.monitorizarevot.ui.login.LoginActivity @@ -75,6 +74,13 @@ class MainActivity : BaseActivity() { callSupportCenter() true } + R.id.nav_safety -> { + val result = applicationContext?.browse(BuildConfig.SAFETY_URL, true) + if (!result!!) { + logW("No app to view " + BuildConfig.SAFETY_URL) + } + true + } else -> false } } diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml index c5b9daa9..b9f565f2 100644 --- a/app/src/main/res/menu/main_menu.xml +++ b/app/src/main/res/menu/main_menu.xml @@ -18,6 +18,10 @@ android:id="@+id/nav_guide" android:icon="@drawable/ic_menu_guide" android:title="@string/menu_guide" /> + Formulare Schimbă secţia Ghidul observatorului + Siguranța la vot Apelează TelVerde Despre Delogare diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c758a6f5..3b39e68b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -46,6 +46,7 @@ Forms Change the polling station Observer\'s guide + Voting safety Call the Hotline About Logout diff --git a/debug.properties b/debug.properties index 3d81daa7..50b473c6 100644 --- a/debug.properties +++ b/debug.properties @@ -1,7 +1,8 @@ apiUrl=https://api.votemonitor.org/api/v1/ -guideUrl=https://fiecarevot.ro/wp-content/uploads/2019/11/Manual-prezidentiale.pdf +guideUrl=https://votcorect.ro/wp-content/uploads/2020/09/Manualul-observatorului-locale-2020-public.pdf +safetyUrl=https://votromania.ro/siguranta-la-vot organisationWebUrl=https://code4.ro/ serviceCenterPhoneNumber=0800080200 preferredLocale=ro_RO privacyWebUrl = https://docs.google.com/document/u/1/d/e/2PACX-1vRwbFULgMu6VqKNYGFSN5migRPd8cI2YstFRWsega0cGLphaGBb6QjFVgO6th6owDv3WiRQwxe5wKSI/pub -supportEmail = contact@code4.ro \ No newline at end of file +supportEmail = contact@code4.ro diff --git a/release.properties b/release.properties index 52d27928..50b473c6 100644 --- a/release.properties +++ b/release.properties @@ -1,5 +1,6 @@ apiUrl=https://api.votemonitor.org/api/v1/ -guideUrl=https://fiecarevot.ro/wp-content/uploads/2019/11/Manual-prezidentiale.pdf +guideUrl=https://votcorect.ro/wp-content/uploads/2020/09/Manualul-observatorului-locale-2020-public.pdf +safetyUrl=https://votromania.ro/siguranta-la-vot organisationWebUrl=https://code4.ro/ serviceCenterPhoneNumber=0800080200 preferredLocale=ro_RO From 512aa43dec44aa62cca909c0516ba6f6d0071c1e Mon Sep 17 00:00:00 2001 From: Irina Borozan Date: Fri, 25 Sep 2020 10:50:59 +0300 Subject: [PATCH 29/29] fixed null checks --- .../java/ro/code4/monitorizarevot/ui/main/MainActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/ro/code4/monitorizarevot/ui/main/MainActivity.kt b/app/src/main/java/ro/code4/monitorizarevot/ui/main/MainActivity.kt index e995e975..c21f6ae2 100644 --- a/app/src/main/java/ro/code4/monitorizarevot/ui/main/MainActivity.kt +++ b/app/src/main/java/ro/code4/monitorizarevot/ui/main/MainActivity.kt @@ -75,8 +75,8 @@ class MainActivity : BaseActivity() { true } R.id.nav_safety -> { - val result = applicationContext?.browse(BuildConfig.SAFETY_URL, true) - if (!result!!) { + val result = applicationContext.browse(BuildConfig.SAFETY_URL, true) + if (!result) { logW("No app to view " + BuildConfig.SAFETY_URL) } true