Skip to content

Commit

Permalink
Merge pull request #175 from code4romania/develop
Browse files Browse the repository at this point in the history
 version 2.0.5 to master
  • Loading branch information
aniri authored Nov 26, 2019
2 parents 64b9a1f + b58a900 commit efb6bce
Show file tree
Hide file tree
Showing 38 changed files with 439 additions and 118 deletions.
8 changes: 1 addition & 7 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![GitHub contributors](https://img.shields.io/github/contributors/code4romania/mon-vot-android-kotlin.svg?style=for-the-badge)](https://github.com/code4romania/mon-vot-android-kotlin/graphs/contributors) [![GitHub last commit](https://img.shields.io/github/last-commit/code4romania/mon-vot-android-kotlin.svg?style=for-the-badge)](https://github.com/code4romania/mon-vot-android-kotlin/commits/master) [![License: MPL 2.0](https://img.shields.io/badge/license-MPL%202.0-brightgreen.svg?style=for-the-badge)](https://opensource.org/licenses/MPL-2.0)

[See the project live](http://monitorizarevot.ro/)
[See the project live](https://votemonitor.org/)

Monitorizare Vot is a mobile app for monitoring elections by authorized observers. They can use the app in order to offer a real-time snapshot on what is going on at polling stations and they can report on any noticeable irregularities.

Expand Down Expand Up @@ -42,13 +42,7 @@ Swagger docs for the API are available [here](http://mv-mobile-test.azurewebsite
- repo for the iOS app - https://github.com/code4romania/monitorizare-vot-ios

Other related projects:

- https://github.com/code4romania/monitorizare-vot-votanti-client/
- https://github.com/code4romania/monitorizare-vot-votanti-api/
- https://github.com/code4romania/monitorizare-vot-votanti-admin
- https://github.com/code4romania/monitorizare-vot-admin
- https://github.com/code4romania/monitorizare-vot-ong
- https://github.com/code4romania/monitorizare-vot-docs

## Feedback

Expand Down
4 changes: 3 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ android {
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") + "\""
variant.buildConfigField "String", "PRIVACY_WEB_URL", "\"" + getKey(variant.buildType.name, "privacyWebUrl") + "\""
variant.buildConfigField "String", "SUPPORT_EMAIL", "\"" + getKey(variant.buildType.name, "supportEmail") + "\""

def productFlavor = variant.productFlavors[0] != null ? "${variant.productFlavors[0].name.capitalize()}" : ""
def buildType = "${variant.buildType.name.capitalize()}"
Expand Down Expand Up @@ -130,7 +132,7 @@ dependencies {

// Firebase
implementation "com.google.firebase:firebase-analytics:$firebaseVersion"
implementation 'com.google.firebase:firebase-messaging:20.0.0'
implementation 'com.google.firebase:firebase-messaging:20.0.1'
implementation 'com.google.firebase:firebase-config:19.0.3'
implementation "com.crashlytics.sdk.android:crashlytics:$crashlyticsVersion"

Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
<activity
android:name=".ui.section.PollingStationActivity"
android:screenOrientation="portrait"
android:launchMode="singleTop"
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".ui.onboarding.OnboardingActivity"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package ro.code4.monitorizarevot.data.dao

import androidx.lifecycle.LiveData
import androidx.room.*
import io.reactivex.Observable
import io.reactivex.Maybe
import ro.code4.monitorizarevot.data.model.Note

@Dao
Expand All @@ -24,7 +24,7 @@ interface NoteDao {
fun getNotes(countyCode: String, pollingStationNumber: Int): LiveData<List<Note>>

@Query("SELECT * FROM note WHERE synced=:synced")
fun getNotSyncedNotes(synced: Boolean = false): Observable<List<Note>>
fun getNotSyncedNotes(synced: Boolean = false): Maybe<List<Note>>

@Query("SELECT COUNT(*) FROM note WHERE synced =:synced")
fun getCountOfNotSyncedNotes(synced: Boolean = false): LiveData<Int>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package ro.code4.monitorizarevot.data.dao
import androidx.lifecycle.LiveData
import androidx.room.*
import io.reactivex.Maybe
import io.reactivex.Observable
import ro.code4.monitorizarevot.data.model.PollingStation
import ro.code4.monitorizarevot.data.pojo.PollingStationInfo

Expand All @@ -25,7 +24,7 @@ interface PollingStationDao {
): Maybe<PollingStationInfo>

@Query("SELECT * FROM polling_station WHERE synced=:synced")
fun getNotSyncedPollingStations(synced: Boolean = false): Observable<List<PollingStation>>
fun getNotSyncedPollingStations(synced: Boolean = false): Maybe<List<PollingStation>>

@Query("SELECT COUNT(*) FROM polling_station WHERE synced =:synced")
fun getCountOfNotSyncedPollingStations(synced: Boolean = false): LiveData<Int>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@ object Constants {
const val TYPE_MULTI_CHOICE_DETAILS = 3

const val REMOTE_CONFIG_FILTER_DIASPORA_FORMS = "filter_diaspora_forms"

const val PUSH_NOTIFICATION_TITLE = "title"
const val PUSH_NOTIFICATION_BODY = "body"
}
37 changes: 36 additions & 1 deletion app/src/main/java/ro/code4/monitorizarevot/helper/Utils.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package ro.code4.monitorizarevot.helper

import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.graphics.Typeface
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.Uri
import android.os.Build
import android.os.Bundle
Expand Down Expand Up @@ -406,4 +409,36 @@ fun String.getLocale(): Locale {

fun <T> String.fromJson(gson: Gson, clazz: Class<T>): T {
return gson.fromJson(this, clazz)
}
}

fun Context.isOnline(): Boolean {
val cm = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val n = cm.activeNetwork
if (n != null) {
val nc = cm.getNetworkCapabilities(n)
//It will check for both wifi and cellular network
return nc!!.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || nc.hasTransport(
NetworkCapabilities.TRANSPORT_WIFI)
}
return false
} else {
val netInfo = cm.activeNetworkInfo
return netInfo != null && netInfo.isConnectedOrConnecting
}
}

fun Context.browse(url: String, newTask: Boolean = false): Boolean {
try {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(url)
if (newTask) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
startActivity(intent)
return true
} catch (e: ActivityNotFoundException) {
return false
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package ro.code4.monitorizarevot.helper

import android.graphics.Bitmap
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import ro.code4.monitorizarevot.BuildConfig

class WebClient(private val listener: WebLoaderListener) : WebViewClient() {
override fun onPageFinished(view: WebView, url: String?) {
Expand All @@ -19,6 +21,11 @@ class WebClient(private val listener: WebLoaderListener) : WebViewClient() {
super.onPageStarted(view, url, favicon)
}

override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
return request?.url?.path?.contains(BuildConfig.GUIDE_URL) == false

}

interface WebLoaderListener {
fun onPageFinished()
fun onLoading()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class Repository : KoinComponent {
retrofit.create(ApiInterface::class.java)
}

private var syncInProgress = false
fun login(user: User): Observable<LoginResponse> = loginInterface.login(user)
fun registerForNotification(token: String): Observable<ResponseBody> =
loginInterface.registerForNotification(token)
Expand Down Expand Up @@ -269,26 +270,6 @@ class Repository : KoinComponent {
return apiInterface.postQuestionAnswer(responseAnswerContainer)
}

@SuppressLint("CheckResult")
fun syncAnswers() {
lateinit var answers: List<AnsweredQuestionPOJO>
db.formDetailsDao().getNotSyncedQuestions()
.toObservable()
.subscribeOn(Schedulers.io()).flatMap {
answers = it
syncAnswers(it)
}.observeOn(AndroidSchedulers.mainThread()).subscribe({
answers.forEach { item -> item.answeredQuestion.synced = true }
Observable.create<Unit> {
db.formDetailsDao()
.updateAnsweredQuestion(*answers.map { it.answeredQuestion }.toTypedArray())
}.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe()
}, {
Log.i(TAG, it.message ?: "Error on synchronizing data")
})
}

fun getNotSyncedQuestions(): LiveData<Int> = db.formDetailsDao().getCountOfNotSyncedQuestions()

fun getNotes(
Expand Down Expand Up @@ -346,34 +327,64 @@ class Repository : KoinComponent {
}

@SuppressLint("CheckResult")
fun syncNotes() {
db.noteDao().getNotSyncedNotes()
.flatMap { Observable.fromIterable(it) }.flatMap {
postNote(it)
}.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe({

}, {
Log.i(TAG, it.localizedMessage.orEmpty())
})
fun syncData() {
if (!syncInProgress) {
syncInProgress = true
Observable.merge(
mutableListOf(
syncAnswersObservable(),
syncPollingStationObservable(),
syncNotesObservable()
)
).observeOn(AndroidSchedulers.mainThread())
.doAfterTerminate {
syncInProgress = false
}
.subscribe({
}, {
Log.i(TAG, it.localizedMessage.orEmpty())
})
}
}

@SuppressLint("CheckResult")
private fun syncPollingStation() {
db.pollingStationDao().getNotSyncedPollingStations().flatMap { Observable.fromIterable(it) }
fun syncAnswersObservable(): Observable<Unit> {
lateinit var answers: List<AnsweredQuestionPOJO>
return db.formDetailsDao().getNotSyncedQuestions()
.toObservable()
.flatMap {
answers = it
syncAnswers(it)
}
.flatMap {
postPollingStationDetails(it)
}.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe({
answers.forEach { item -> item.answeredQuestion.synced = true }
Observable.create<Unit> { emitter ->
db.formDetailsDao()
.updateAnsweredQuestion(*answers.map { it.answeredQuestion }.toTypedArray())
emitter.onComplete()

}, {
Log.i(TAG, it.localizedMessage.orEmpty())
})
}
}
.subscribeOn(Schedulers.io())
}

fun syncData() {
syncAnswers()
syncNotes()
syncPollingStation()
@SuppressLint("CheckResult")
private fun syncNotesObservable(): Observable<ResponseBody> {

return db.noteDao().getNotSyncedNotes()
.toObservable()
.flatMap { Observable.fromIterable(it) }
.flatMap { postNote(it) }
.subscribeOn(Schedulers.io())
}

@SuppressLint("CheckResult")
private fun syncPollingStationObservable(): Observable<ResponseBody> {
return db.pollingStationDao().getNotSyncedPollingStations()
.toObservable()
.flatMap { Observable.fromIterable(it) }
.flatMap { postPollingStationDetails(it) }
.subscribeOn(Schedulers.io())
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class NotificationService : FirebaseMessagingService() {
private fun showNotification(message: RemoteMessage) {
message.notification?.let {
App.instance.currentActivity?.runOnUiThread {
App.instance.currentActivity?.showPushNotification(it)
App.instance.currentActivity?.showPushNotification(it.title.orEmpty(), it.body.orEmpty())
}
}
}
Expand Down
16 changes: 11 additions & 5 deletions app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,21 @@ abstract class BaseActivity<out T : BaseViewModel> : AppCompatActivity(), Layout

}

fun showPushNotification(notification: RemoteMessage.Notification) {
fun showPushNotification(title: String, body: String, okListener: (() -> Unit)? = null, dismissListener: (() -> Unit)? = null) {
if (!isFinishing && dialog == null) {
dialog = AlertDialog.Builder(this, R.style.AlertDialog)
.setTitle(notification.title)
.setMessage(notification.body)
.setTitle(title)
.setMessage(body)
.setPositiveButton(R.string.push_notification_ok)
{ p0, _ -> p0.dismiss() }
{ p0, _ ->
okListener?.invoke()
p0.dismiss()
}
.setCancelable(false)
.setOnDismissListener { dialog = null }
.setOnDismissListener {
dismissListener?.invoke()
dialog = null
}
.show()
}
}
Expand Down
17 changes: 0 additions & 17 deletions app/src/main/java/ro/code4/monitorizarevot/ui/base/BaseFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,4 @@ abstract class BaseFragment<out T : BaseViewModel> : Fragment(), Layout,
): View? {
return inflater.inflate(layout, container, false)
}

fun isOnline(context: Context): Boolean {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val n = cm.activeNetwork
if (n != null) {
val nc = cm.getNetworkCapabilities(n)
//It will check for both wifi and cellular network
return nc!!.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
}
return false
} else {
val netInfo = cm.activeNetworkInfo
return netInfo != null && netInfo.isConnectedOrConnecting
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class FormsFragment : BaseFragment<FormsViewModel>() {
bundleOf(
Pair(QUESTION, Parcels.wrap(it))
),
QuestionsDetailsFragment.TAG
NoteFragment.TAG
)
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ 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.helper.isOnline
import ro.code4.monitorizarevot.ui.base.BaseAnalyticsFragment


Expand Down Expand Up @@ -56,7 +57,7 @@ class FormsListFragment : BaseAnalyticsFragment<FormsViewModel>() {
// TODO send number of unsynced items
logSyncManuallyEvent(0)

if (!isOnline(context!!)) {
if (!mContext.isOnline()) {
Snackbar.make(syncButton, getString(R.string.form_sync_no_internet), Snackbar.LENGTH_SHORT)
.show()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class FormsViewModel : BaseFormViewModel() {
val notSyncedPollingStationsCount = repository.getNotSyncedPollingStationsCount()
fun update() {
syncVisibilityLiveData.value =
if (notSyncedQuestionsCount.value ?: 0 + (notSyncedNotesCount.value
if ((notSyncedQuestionsCount.value ?: 0) + (notSyncedNotesCount.value
?: 0) + (notSyncedPollingStationsCount.value ?: 0) > 0
) View.VISIBLE else View.GONE
}
Expand Down
Loading

0 comments on commit efb6bce

Please sign in to comment.