Skip to content

Commit

Permalink
Merge pull request getodk#4529 from grzesiek2010/COLLECT-4423
Browse files Browse the repository at this point in the history
Display project icon in the main menu
  • Loading branch information
seadowg authored Apr 28, 2021
2 parents 9ea1492 + 42618f7 commit bcfdc76
Show file tree
Hide file tree
Showing 26 changed files with 554 additions and 143 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package org.odk.collect.androidshared.livedata;

import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.Observer;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import kotlin.Triple;

Expand All @@ -11,6 +16,26 @@ private LiveDataUtils() {

}

public static <T> T getOrAwaitValue(final LiveData<T> liveData) throws InterruptedException {
final Object[] data = new Object[1];
final CountDownLatch latch = new CountDownLatch(1);
Observer<T> observer = new Observer<T>() {
@Override
public void onChanged(@Nullable T o) {
data[0] = o;
latch.countDown();
liveData.removeObserver(this);
}
};
liveData.observeForever(observer);
// Don't wait indefinitely if the LiveData is not set.
if (!latch.await(2, TimeUnit.SECONDS)) {
throw new RuntimeException("LiveData value was never set.");
}
//noinspection unchecked
return (T) data[0];
}

public static <T, U, V> LiveData<Triple<T, U, V>> zip3(LiveData<T> one, LiveData<U> two, LiveData<V> three) {
return new Zipped3LiveData<>(one, two, three);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,33 @@ class SwitchProjectTest {

@Test
fun switchProjectTest() {
// Add project Turtle nesting
rule.mainMenu()
.assertProjectIcon("D", "#3e9fcc")
.openProjectSettingsDialog()
.clickAddProject()
.inputProjectName("Turtle nesting")
.inputProjectIcon("T")
.inputProjectColor("#0000FF")
.addProject()

// Switch to Turtle nesting
rule.mainMenu()
.openProjectSettingsDialog()
.clickAddProject()
.inputProjectName("Polio - Banadir")
.inputProjectIcon("P")
.inputProjectColor("#0000FF")
.addProject()

.openProjectSettingsDialog()
.clickOnText("Polio - Banadir")
.checkIsToastWithMessageDisplayed(R.string.switched_project, "Polio - Banadir")
.assertCurrentProject("Demo project")
.assertInactiveProject("Turtle nesting")
.clickOnText("Turtle nesting")
rule.mainMenu()
.checkIsToastWithMessageDisplayed(R.string.switched_project, "Turtle nesting")
.assertProjectIcon("T", "#0000FF")

// Switch to Demo project
rule.mainMenu()
.openProjectSettingsDialog()
.assertCurrentProject("Polio - Banadir")
.assertInactiveProject("Turtle nesting")
.assertCurrentProject("Turtle nesting")
.assertInactiveProject("Demo project")
.clickOnText("Demo project")
rule.mainMenu()
.assertProjectIcon("D", "#3e9fcc")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ public void evaluate() throws Throwable {
// Reinitialize any application state with new deps/state
((Collect) context.getApplicationContext()).getComponent().applicationInitializer().initialize();

importDemoProject();

base.evaluate();
}
}
Expand Down Expand Up @@ -93,4 +95,8 @@ private void clearPrefs() {
settingsProvider.getAdminSettings().setDefaultForAllSettingsWithoutValues();
settingsProvider.getMetaSettings().clear();
}

private void importDemoProject() {
DaggerUtils.getComponent(InstrumentationRegistry.getInstrumentation().getTargetContext()).projectImporter().importDemoProject();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;

import androidx.test.espresso.Espresso;
import androidx.test.rule.ActivityTestRule;
Expand All @@ -20,12 +22,16 @@
import static androidx.test.espresso.intent.Intents.intending;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static androidx.test.espresso.matcher.CursorMatchers.withRowString;
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
import static androidx.test.espresso.matcher.ViewMatchers.isClickable;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.AllOf.allOf;

public class MainMenuPage extends Page<MainMenuPage> {

Expand Down Expand Up @@ -212,5 +218,12 @@ public DeleteSavedFormPage clickDeleteSavedForm() {
onView(withText(getTranslatedString(R.string.manage_files))).perform(scrollTo(), click());
return new DeleteSavedFormPage(rule).assertOnPage();
}

public MainMenuPage assertProjectIcon(String projectIcon, String expectedBackgroundColor) {
onView(allOf(hasDescendant(withText(projectIcon)), withId(R.id.projects))).check(matches(isDisplayed()));
int backgroundColor = ((GradientDrawable) rule.getActivity().findViewById(R.id.project_icon_text).getBackground()).getColor().getDefaultColor();
assertThat(backgroundColor, is(Color.parseColor(expectedBackgroundColor)));
return this;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import androidx.lifecycle.ViewModelProvider;

import org.odk.collect.android.R;
import org.odk.collect.android.activities.viewmodels.CurrentProjectViewModel;
import org.odk.collect.android.activities.viewmodels.MainMenuViewModel;
import org.odk.collect.android.configure.qr.QRCodeTabsActivity;
import org.odk.collect.android.gdrive.GoogleDriveActivity;
Expand All @@ -35,6 +36,7 @@
import org.odk.collect.android.preferences.dialogs.AdminPasswordDialogFragment.Action;
import org.odk.collect.android.preferences.keys.GeneralKeys;
import org.odk.collect.android.preferences.screens.AdminPreferencesActivity;
import org.odk.collect.android.projects.ProjectIconView;
import org.odk.collect.android.projects.ProjectSettingsDialog;
import org.odk.collect.android.utilities.ApplicationConstants;
import org.odk.collect.android.utilities.MultiClickGuard;
Expand Down Expand Up @@ -62,17 +64,28 @@ public class MainMenuActivity extends CollectAbstractActivity implements AdminPa

@Inject
MainMenuViewModel.Factory viewModelFactory;
private MainMenuViewModel viewModel;

@Inject
CurrentProjectViewModel.Factory currentProjectViewModelFactory;

private MainMenuViewModel mainMenuViewModel;

private CurrentProjectViewModel currentProjectViewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerUtils.getComponent(this).inject(this);
setContentView(R.layout.main_menu);

viewModel = new ViewModelProvider(this, viewModelFactory).get(MainMenuViewModel.class);
mainMenuViewModel = new ViewModelProvider(this, viewModelFactory).get(MainMenuViewModel.class);
currentProjectViewModel = new ViewModelProvider(this, currentProjectViewModelFactory).get(CurrentProjectViewModel.class);
currentProjectViewModel.getCurrentProject().observe(this, project -> {
invalidateOptionsMenu();
});

initToolbar();

// enter data button. expects a result.
Button enterDataButton = findViewById(R.id.enter_data);
enterDataButton.setText(getString(R.string.enter_data_button));
Expand Down Expand Up @@ -159,14 +172,14 @@ public void onClick(View v) {
});

TextView versionSHAView = findViewById(R.id.version_sha);
String versionSHA = viewModel.getVersionCommitDescription();
String versionSHA = mainMenuViewModel.getVersionCommitDescription();
if (versionSHA != null) {
versionSHAView.setText(versionSHA);
} else {
versionSHAView.setVisibility(View.GONE);
}

viewModel.getFinalizedFormsCount().observe(this, finalized -> {
mainMenuViewModel.getFinalizedFormsCount().observe(this, finalized -> {
if (finalized > 0) {
sendDataButton.setText(getString(R.string.send_data_button, String.valueOf(finalized)));
} else {
Expand All @@ -175,7 +188,7 @@ public void onClick(View v) {
});


viewModel.getUnsentFormsCount().observe(this, unsent -> {
mainMenuViewModel.getUnsentFormsCount().observe(this, unsent -> {
if (unsent > 0) {
reviewDataButton.setText(getString(R.string.review_data_button, String.valueOf(unsent)));
} else {
Expand All @@ -184,7 +197,7 @@ public void onClick(View v) {
});


viewModel.getSentFormsCount().observe(this, sent -> {
mainMenuViewModel.getSentFormsCount().observe(this, sent -> {
if (sent > 0) {
viewSentFormsButton.setText(getString(R.string.view_sent_forms_button, String.valueOf(sent)));
} else {
Expand All @@ -196,18 +209,29 @@ public void onClick(View v) {
@Override
protected void onResume() {
super.onResume();
viewModel.resume();
mainMenuViewModel.resume();

setButtonsVisibility();
invalidateOptionsMenu();
}

private void setButtonsVisibility() {
reviewDataButton.setVisibility(viewModel.shouldEditSavedFormButtonBeVisible() ? View.VISIBLE : View.GONE);
sendDataButton.setVisibility(viewModel.shouldSendFinalizedFormButtonBeVisible() ? View.VISIBLE : View.GONE);
viewSentFormsButton.setVisibility(viewModel.shouldViewSentFormButtonBeVisible() ? View.VISIBLE : View.GONE);
getFormsButton.setVisibility(viewModel.shouldGetBlankFormButtonBeVisible() ? View.VISIBLE : View.GONE);
manageFilesButton.setVisibility(viewModel.shouldDeleteSavedFormButtonBeVisible() ? View.VISIBLE : View.GONE);
reviewDataButton.setVisibility(mainMenuViewModel.shouldEditSavedFormButtonBeVisible() ? View.VISIBLE : View.GONE);
sendDataButton.setVisibility(mainMenuViewModel.shouldSendFinalizedFormButtonBeVisible() ? View.VISIBLE : View.GONE);
viewSentFormsButton.setVisibility(mainMenuViewModel.shouldViewSentFormButtonBeVisible() ? View.VISIBLE : View.GONE);
getFormsButton.setVisibility(mainMenuViewModel.shouldGetBlankFormButtonBeVisible() ? View.VISIBLE : View.GONE);
manageFilesButton.setVisibility(mainMenuViewModel.shouldDeleteSavedFormButtonBeVisible() ? View.VISIBLE : View.GONE);
}

@Override
public boolean onPrepareOptionsMenu(Menu menu) {
final MenuItem projectsMenuItem = menu.findItem(R.id.projects);

ProjectIconView projectIconView = (ProjectIconView) projectsMenuItem.getActionView();
projectIconView.setProject(currentProjectViewModel.getCurrentProject().getValue());
projectIconView.setOnClickListener(v -> onOptionsItemSelected(projectsMenuItem));

return super.onPrepareOptionsMenu(menu);
}

@Override
Expand All @@ -231,7 +255,7 @@ public boolean onOptionsItemSelected(MenuItem item) {

private void initToolbar() {
Toolbar toolbar = findViewById(R.id.toolbar);
setTitle(String.format("%s %s", getString(R.string.app_name), viewModel.getVersion()));
setTitle(String.format("%s %s", getString(R.string.app_name), mainMenuViewModel.getVersion()));
setSupportActionBar(toolbar);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.odk.collect.android.activities.viewmodels

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import org.odk.collect.android.projects.CurrentProjectProvider
import org.odk.collect.projects.Project

class CurrentProjectViewModel(currentProjectProvider: CurrentProjectProvider) : ViewModel() {

private val _currentProject = MutableLiveData(currentProjectProvider.getCurrentProject()!!)
val currentProject: LiveData<Project> = _currentProject

fun setCurrentProject(project: Project) {
_currentProject.postValue(project)
}

open class Factory constructor(private val currentProjectProvider: CurrentProjectProvider) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return CurrentProjectViewModel(currentProjectProvider) as T
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public class MainMenuViewModel extends ViewModel {
private final InstancesCountRepository instancesCountRepository;
private final Application application;

public MainMenuViewModel(Application application, VersionInformation versionInformation, SettingsProvider settingsProvider, InstancesCountRepository instancesCountRepository) {
public MainMenuViewModel(Application application, VersionInformation versionInformation,
SettingsProvider settingsProvider, InstancesCountRepository instancesCountRepository) {
this.application = application;
this.version = versionInformation;
this.generalSettings = settingsProvider.getGeneralSettings();
Expand Down Expand Up @@ -121,7 +122,8 @@ public static class Factory implements ViewModelProvider.Factory {
private final InstancesCountRepository instancesCountRepository;

@Inject
public Factory(VersionInformation versionInformation, Application application, SettingsProvider settingsProvider, InstancesCountRepository instancesCountRepository) {
public Factory(VersionInformation versionInformation, Application application,
SettingsProvider settingsProvider, InstancesCountRepository instancesCountRepository) {
this.versionInformation = versionInformation;
this.application = application;
this.settingsProvider = settingsProvider;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
import org.odk.collect.android.preferences.source.SettingsProvider;
import org.odk.collect.android.projects.AddProjectDialog;
import org.odk.collect.android.projects.CurrentProjectProvider;
import org.odk.collect.android.projects.ProjectImporter;
import org.odk.collect.android.projects.ProjectSettingsDialog;
import org.odk.collect.projects.ProjectsRepository;
import org.odk.collect.android.provider.FormsProvider;
Expand Down Expand Up @@ -283,4 +284,6 @@ interface Builder {
ProjectsRepository projectsRepository();

CurrentProjectProvider currentProjectProvider();

ProjectImporter projectImporter();
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import org.odk.collect.analytics.NoopAnalytics;
import org.odk.collect.android.BuildConfig;
import org.odk.collect.android.R;
import org.odk.collect.android.activities.viewmodels.CurrentProjectViewModel;
import org.odk.collect.android.activities.viewmodels.MainMenuViewModel;
import org.odk.collect.android.activities.viewmodels.SplashScreenViewModel;
import org.odk.collect.android.application.CollectSettingsChangeHandler;
import org.odk.collect.android.application.initialization.ApplicationInitializer;
Expand Down Expand Up @@ -572,4 +574,15 @@ public ProjectImporter providesProjectImporter(ProjectsRepository projectsReposi
public AppStateProvider providesAppStateProvider(Context context) {
return new AppStateProvider(context);
}

@Provides
public MainMenuViewModel.Factory providesMainMenuViewModel(VersionInformation versionInformation, Application application,
SettingsProvider settingsProvider, InstancesCountRepository instancesCountRepository) {
return new MainMenuViewModel.Factory(versionInformation, application, settingsProvider, instancesCountRepository);
}

@Provides
public CurrentProjectViewModel.Factory providesCurrentProjectViewModel(CurrentProjectProvider currentProjectProvider) {
return new CurrentProjectViewModel.Factory(currentProjectProvider);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ class CurrentProjectProvider(private val settingsProvider: SettingsProvider, pri
}

fun getCurrentProject(): Project? {
// This should be removed. It was added just to fix tests because ResetStateRule which should be responsible for it is not always called first
if (projectsRepository.getAll().isEmpty()) {
ProjectImporter(projectsRepository, settingsProvider.getMetaSettings()).importDemoProject()
}

return projectsRepository.get(getCurrentProjectId())
}

Expand Down
Loading

0 comments on commit bcfdc76

Please sign in to comment.