Skip to content

Commit

Permalink
Reworked ReadyToSendBanner to use viewmodel
Browse files Browse the repository at this point in the history
  • Loading branch information
grzesiek2010 committed Aug 31, 2023
1 parent 7bf40a6 commit d9a2217
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ class SendFinalizedFormTest {
@Test
fun whenThereAreSentAndReadyToSendForms_displayTheBanner() {
rule.withProject(testDependencies.server.url)
.copyForm("one-question.xml", projectName = testDependencies.server.hostName)
.copyForm("one-question.xml", testDependencies.server.hostName)
.startBlankForm("One Question")
.fillOutAndFinalize(QuestionAndAnswer("what is your age", "123"))
.startBlankForm("One Question")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import org.odk.collect.android.instancemanagement.autosend.AutoSendSettingsProvider;
import org.odk.collect.android.instancemanagement.autosend.InstanceAutoSendFetcher;
import org.odk.collect.android.instancemanagement.autosend.InstanceAutoSender;
import org.odk.collect.android.instancemanagement.send.ReadyToSendViewModel;
import org.odk.collect.android.itemsets.FastExternalItemsetsRepository;
import org.odk.collect.android.mainmenu.MainMenuViewModelFactory;
import org.odk.collect.android.notifications.NotificationManagerNotifier;
Expand Down Expand Up @@ -485,6 +486,11 @@ public ProjectPreferencesViewModel.Factory providesProjectPreferencesViewModel(A
return new ProjectPreferencesViewModel.Factory(adminPasswordProvider);
}

@Provides
public ReadyToSendViewModel.Factory providesReadyToSendViewModel(InstancesRepositoryProvider instancesRepositoryProvider, Scheduler scheduler) {
return new ReadyToSendViewModel.Factory(instancesRepositoryProvider.get(), scheduler, System::currentTimeMillis);
}

@Provides
public MainMenuViewModelFactory providesMainMenuViewModelFactory(VersionInformation versionInformation, Application application,
SettingsProvider settingsProvider, InstancesAppState instancesAppState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@
import org.odk.collect.android.mainmenu.MainMenuActivity;
import org.odk.collect.android.preferences.screens.ProjectPreferencesActivity;
import org.odk.collect.android.projects.CurrentProjectProvider;
import org.odk.collect.android.utilities.InstancesRepositoryProvider;
import org.odk.collect.android.utilities.PlayServicesChecker;
import org.odk.collect.androidshared.network.NetworkStateProvider;
import org.odk.collect.androidshared.ui.MultiSelectViewModel;
Expand Down Expand Up @@ -122,7 +121,7 @@ public class InstanceUploaderListActivity extends LocalizedActivity implements
SettingsProvider settingsProvider;

@Inject
InstancesRepositoryProvider instancesRepositoryProvider;
ReadyToSendViewModel.Factory factory;

private ListView listView;
private InstanceUploaderAdapter listAdapter;
Expand All @@ -132,6 +131,7 @@ public class InstanceUploaderListActivity extends LocalizedActivity implements
private String filterText;

private MultiSelectViewModel multiSelectViewModel;
private ReadyToSendViewModel readyToSendViewModel;
private boolean allSelected;

private boolean isSearchBoxShown;
Expand All @@ -158,6 +158,7 @@ public void onCreate(Bundle savedInstanceState) {

listAdapter.setSelected(ids);
});
readyToSendViewModel = new ViewModelProvider(this, factory).get(ReadyToSendViewModel.class);

// set title
setTitle(getString(org.odk.collect.strings.R.string.send_data));
Expand Down Expand Up @@ -476,7 +477,7 @@ public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor cursor) {
findViewById(R.id.buttonholder).setVisibility(View.VISIBLE);
}

binding.readyToSendBanner.init(instancesRepositoryProvider.get(), System::currentTimeMillis);
binding.readyToSendBanner.init(readyToSendViewModel, this);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.lifecycle.LifecycleOwner
import org.odk.collect.android.databinding.ReadyToSendBannerBinding
import org.odk.collect.forms.instances.Instance
import org.odk.collect.forms.instances.InstancesRepository
import org.odk.collect.strings.R
import java.util.function.Supplier

const val ONE_SECOND = 1000L
const val ONE_MINUTE = 60000L
Expand All @@ -20,32 +18,28 @@ class ReadyToSendBanner(context: Context, attrs: AttributeSet?) : ConstraintLayo

private val binding = ReadyToSendBannerBinding.inflate(LayoutInflater.from(context), this, true)

fun init(instancesRepository: InstancesRepository, clock: Supplier<Long>) {
val sentInstances: List<Instance> = instancesRepository.getAllByStatus(Instance.STATUS_SUBMITTED)
val numberOfInstancesReadyToSend = instancesRepository.getCountByStatus(
Instance.STATUS_COMPLETE,
Instance.STATUS_SUBMISSION_FAILED
)
fun init(viewModel: ReadyToSendViewModel, owner: LifecycleOwner) {
viewModel.data.observe(owner) {
if (it.numberOfSentInstances > 0 && it.numberOfInstancesReadyToSend > 0) {
if (it.lastInstanceSentTimeMillis >= ONE_DAY) {
val days: Int = (it.lastInstanceSentTimeMillis / ONE_DAY).toInt()
binding.title.text = context.resources.getQuantityString(R.plurals.last_form_sent_days_ago, days, days)
} else if (it.lastInstanceSentTimeMillis >= ONE_HOUR) {
val hours: Int = (it.lastInstanceSentTimeMillis / ONE_HOUR).toInt()
binding.title.text = context.resources.getQuantityString(R.plurals.last_form_sent_hours_ago, hours, hours)
} else if (it.lastInstanceSentTimeMillis >= ONE_MINUTE) {
val minutes: Int = (it.lastInstanceSentTimeMillis / ONE_MINUTE).toInt()
binding.title.text = context.resources.getQuantityString(R.plurals.last_form_sent_minutes_ago, minutes, minutes)
} else {
val seconds: Int = (it.lastInstanceSentTimeMillis / ONE_SECOND).toInt()
binding.title.text = context.resources.getQuantityString(R.plurals.last_form_sent_seconds_ago, seconds, seconds)
}

if (sentInstances.isNotEmpty() && numberOfInstancesReadyToSend > 0) {
val lastSentInstance = sentInstances.maxBy { instance -> instance.lastStatusChangeDate }
val millisecondsAgo = clock.get() - lastSentInstance.lastStatusChangeDate
if (millisecondsAgo >= ONE_DAY) {
val days: Int = (millisecondsAgo / ONE_DAY).toInt()
binding.title.text = context.resources.getQuantityString(R.plurals.last_form_sent_days_ago, days, days)
} else if (millisecondsAgo >= ONE_HOUR) {
val hours: Int = (millisecondsAgo / ONE_HOUR).toInt()
binding.title.text = context.resources.getQuantityString(R.plurals.last_form_sent_hours_ago, hours, hours)
} else if (millisecondsAgo >= ONE_MINUTE) {
val minutes: Int = (millisecondsAgo / ONE_MINUTE).toInt()
binding.title.text = context.resources.getQuantityString(R.plurals.last_form_sent_minutes_ago, minutes, minutes)
} else {
val seconds: Int = (millisecondsAgo / ONE_SECOND).toInt()
binding.title.text = context.resources.getQuantityString(R.plurals.last_form_sent_seconds_ago, seconds, seconds)
binding.subtext.text = context.resources.getQuantityString(R.plurals.forms_ready_to_send, it.numberOfInstancesReadyToSend, it.numberOfInstancesReadyToSend)
binding.banner.visibility = VISIBLE
}

binding.subtext.text = context.resources.getQuantityString(R.plurals.forms_ready_to_send, numberOfInstancesReadyToSend, numberOfInstancesReadyToSend)
binding.banner.visibility = VISIBLE
}

viewModel.init()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.odk.collect.android.instancemanagement.send

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import org.odk.collect.async.Scheduler
import org.odk.collect.forms.instances.Instance
import org.odk.collect.forms.instances.InstancesRepository
import java.util.function.Supplier

class ReadyToSendViewModel(
private val instancesRepository: InstancesRepository,
private val scheduler: Scheduler,
private val clock: Supplier<Long>
) : ViewModel() {
private val _data = MutableLiveData<Data>()
val data: LiveData<Data> = _data

fun init() {
scheduler.immediate(
background = {
val sentInstances = instancesRepository.getAllByStatus(Instance.STATUS_SUBMITTED)
val numberOfSentInstances = sentInstances.size
val numberOfInstancesReadyToSend = instancesRepository.getCountByStatus(
Instance.STATUS_COMPLETE,
Instance.STATUS_SUBMISSION_FAILED
)
val lastInstanceSentTimeMillis = if (sentInstances.isNotEmpty()) {
val lastSentInstance = sentInstances.maxBy { instance -> instance.lastStatusChangeDate }
clock.get() - lastSentInstance.lastStatusChangeDate
} else {
0
}
Data(numberOfInstancesReadyToSend, numberOfSentInstances, lastInstanceSentTimeMillis)
},
foreground = {
_data.value = it
}
)
}

open class Factory(
private val instancesRepository: InstancesRepository,
private val scheduler: Scheduler,
private val clock: Supplier<Long>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ReadyToSendViewModel(instancesRepository, scheduler, clock) as T
}
}

data class Data(
val numberOfInstancesReadyToSend: Int,
val numberOfSentInstances: Int,
val lastInstanceSentTimeMillis: Long
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@ import com.google.android.material.textview.MaterialTextView
import org.hamcrest.Matchers.equalTo
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.mockito.kotlin.whenever
import org.odk.collect.android.R
import org.odk.collect.androidtest.FakeLifecycleOwner
import org.odk.collect.forms.instances.Instance
import org.odk.collect.formstest.InMemInstancesRepository
import org.odk.collect.testshared.FakeScheduler
import java.util.function.Supplier

@RunWith(AndroidJUnit4::class)
class ReadyToSendBannerTest {
Expand All @@ -30,10 +35,18 @@ class ReadyToSendBannerTest {
)
}

private val scheduler = FakeScheduler()
private val clock = mock<Supplier<Long>>().apply {
whenever(this.get()).thenReturn(0)
}
private val viewModel = ReadyToSendViewModel(instancesRepository, scheduler, clock)
private val lifecycleOwner = FakeLifecycleOwner()

@Test
fun `if there are no sent instances do not display the banner`() {
val view = ReadyToSendBanner(context).also {
it.init(instancesRepository) { 0 }
it.init(viewModel, lifecycleOwner)
scheduler.runBackground()
}

assertThat(view.findViewById<ConstraintLayout>(R.id.banner).visibility, equalTo(View.GONE))
Expand All @@ -42,7 +55,8 @@ class ReadyToSendBannerTest {
@Test
fun `if there are no instances ready to send do not display the banner`() {
val view = ReadyToSendBanner(context).also {
it.init(instancesRepository) { 0 }
it.init(viewModel, lifecycleOwner)
scheduler.runBackground()
}

assertThat(view.findViewById<ConstraintLayout>(R.id.banner).visibility, equalTo(View.GONE))
Expand All @@ -58,7 +72,8 @@ class ReadyToSendBannerTest {
)

val view = ReadyToSendBanner(context).also {
it.init(instancesRepository) { 0 }
it.init(viewModel, lifecycleOwner)
scheduler.runBackground()
}

assertThat(view.findViewById<ConstraintLayout>(R.id.banner).visibility, equalTo(View.GONE))
Expand All @@ -74,7 +89,8 @@ class ReadyToSendBannerTest {
)

val view = ReadyToSendBanner(context).also {
it.init(instancesRepository) { 0 }
it.init(viewModel, lifecycleOwner)
scheduler.runBackground()
}

assertThat(view.findViewById<ConstraintLayout>(R.id.banner).visibility, equalTo(View.GONE))
Expand All @@ -90,7 +106,8 @@ class ReadyToSendBannerTest {
)

val view = ReadyToSendBanner(context).also {
it.init(instancesRepository) { 0 }
it.init(viewModel, lifecycleOwner)
scheduler.runBackground()
}

assertThat(view.findViewById<ConstraintLayout>(R.id.banner).visibility, equalTo(View.GONE))
Expand All @@ -113,7 +130,8 @@ class ReadyToSendBannerTest {
)

val view = ReadyToSendBanner(context).also {
it.init(instancesRepository) { 0 }
it.init(viewModel, lifecycleOwner)
scheduler.runBackground()
}

assertThat(
Expand All @@ -139,8 +157,10 @@ class ReadyToSendBannerTest {
.build()
)

whenever(clock.get()).thenReturn(ONE_SECOND * 5)
val view = ReadyToSendBanner(context).also {
it.init(instancesRepository) { ONE_SECOND * 5 }
it.init(viewModel, lifecycleOwner)
scheduler.runBackground()
}

assertThat(
Expand All @@ -166,8 +186,10 @@ class ReadyToSendBannerTest {
.build()
)

whenever(clock.get()).thenReturn(ONE_MINUTE)
val view = ReadyToSendBanner(context).also {
it.init(instancesRepository) { ONE_MINUTE }
it.init(viewModel, lifecycleOwner)
scheduler.runBackground()
}

assertThat(
Expand All @@ -193,8 +215,10 @@ class ReadyToSendBannerTest {
.build()
)

whenever(clock.get()).thenReturn(ONE_HOUR * 2)
val view = ReadyToSendBanner(context).also {
it.init(instancesRepository) { ONE_HOUR * 2 }
it.init(viewModel, lifecycleOwner)
scheduler.runBackground()
}

assertThat(
Expand All @@ -220,8 +244,10 @@ class ReadyToSendBannerTest {
.build()
)

whenever(clock.get()).thenReturn(ONE_DAY * 34)
val view = ReadyToSendBanner(context).also {
it.init(instancesRepository) { ONE_DAY * 34 }
it.init(viewModel, lifecycleOwner)
scheduler.runBackground()
}

assertThat(
Expand Down Expand Up @@ -263,8 +289,10 @@ class ReadyToSendBannerTest {
.build()
)

whenever(clock.get()).thenReturn(ONE_SECOND * 10)
val view = ReadyToSendBanner(context).also {
it.init(instancesRepository) { ONE_SECOND * 10 }
it.init(viewModel, lifecycleOwner)
scheduler.runBackground()
}

assertThat(
Expand Down Expand Up @@ -304,7 +332,8 @@ class ReadyToSendBannerTest {
)

val view = ReadyToSendBanner(context).also {
it.init(instancesRepository) { 0 }
it.init(viewModel, lifecycleOwner)
scheduler.runBackground()
}

assertThat(
Expand Down

0 comments on commit d9a2217

Please sign in to comment.