Commit 9ae5c446 authored by Rahadi Jalu's avatar Rahadi Jalu

Current Changes

parent 2ee4e7a9
# ODK Collect
![Platform](https://img.shields.io/badge/platform-Android-blue.svg)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![Build status](https://circleci.com/gh/opendatakit/collect.svg?style=shield&circle-token=:circle-token)](https://circleci.com/gh/opendatakit/collect)
[![Slack status](http://slack.opendatakit.org/badge.svg)](http://slack.opendatakit.org)
## Belum Implementasi
Sudah ada pada ODK tetapi belum sempat untuk diimplementasikan
* Merubah pengaturan melalui share QR Code
* Range widget
* Redesign draw activity
* load setting melalui JSON, selain dari collect.settings
ODK Collect is an Android app for filling out forms. It is designed to be used in resource-constrained environments with challenges such as unreliable connectivity or power infrastructure. ODK Collect is part of Open Data Kit (ODK), a free and open-source set of tools which help organizations author, field, and manage mobile data collection solutions. Learn more about the Open Data Kit project and its history [here](https://opendatakit.org/about/) and read about example ODK deployments [here](https://opendatakit.org/about/deployments/).
ODK Collect renders forms that are compliant with the [ODK XForms standard](http://opendatakit.github.io/xforms-spec/), a subset of the [XForms 1.1 standard](https://www.w3.org/TR/xforms/) with some extensions. The form parsing is done by the [JavaRosa library](https://github.com/opendatakit/javarosa) which Collect includes as a jar.
* ODK website: [https://opendatakit.org](https://opendatakit.org)
* ODK Collect usage instructions: [https://opendatakit.org/use/collect](https://opendatakit.org/use/collect/)
* ODK community mailing list: [http://groups.google.com/group/opendatakit](http://groups.google.com/group/opendatakit)
* ODK developer mailing list: [http://groups.google.com/group/opendatakit-developers](http://groups.google.com/group/opendatakit-developers)
* ODK developer Slack chat: [http://slack.opendatakit.org](http://slack.opendatakit.org)
* ODK developer Slack archive: [http://opendatakit.slackarchive.io](http://opendatakit.slackarchive.io)
* ODK developer wiki: [https://github.com/opendatakit/opendatakit/wiki](https://github.com/opendatakit/opendatakit/wiki)
## Release cycle
New versions of ODK Collect are released on the last Sunday of each month. We freeze commits to the master branch on the preceding Wednesday (except for bug fixes).
## Testing a form locally
1. [Make](https://xlsform.org) or get ([example forms](https://github.com/XLSForm/example-forms), [test forms](https://github.com/XLSForm/test-forms)) an XLSForm.
1. Convert the XLSForm (xlsx) to XForm (xml). Use the [ODK website](http://opendatakit.org/xiframe/) or [XLSForm Offline](https://gumroad.com/l/xlsform-offline) or [pyxform](https://github.com/XLSForm/pyxform).
1. Once you have the XForm, use [adb](https://developer.android.com/studio/command-line/adb.html) to push the form to your device (after [enabling USB debugging](https://www.kingoapp.com/root-tutorials/how-to-enable-usb-debugging-mode-on-android.htm)) or emulator.
```
adb push my_form.xml /sdcard/odk/forms/
```
1. Launch ODK Collect and tap `Fill Blank Form`. The new form will be there.
## Setting up your development environment
1. Download and install [Git](https://git-scm.com/downloads) and add it to your PATH
1. Download and install [Android Studio](https://developer.android.com/studio/index.html)
1. Fork the collect project ([why and how to fork](https://help.github.com/articles/fork-a-repo/))
1. Clone your fork of the project locally. At the command line:
git clone https://github.com/YOUR-GITHUB-USERNAME/collect
If you prefer not to use the command line, you can use Android Studio to create a new project from version control using `https://github.com/YOUR-GITHUB-USERNAME/collect`.
1. Open the project in the folder of your clone from Android Studio. To run the project, click on the green arrow at the top of the screen. The emulator is very slow so we generally recommend using a physical device when possible.
## Using APIs for local development
To run functionality that makes API calls from your debug-signed builds, you may need to get an API key or otherwise authorize your app.
**Google Drive and Sheets APIs** - Follow the instructions in the "Generate the signing certificate fingerprint and register your application" section from [here](https://developers.google.com/drive/android/auth). Enable the Google Drive API [here](https://console.developers.google.com/apis/api/drive/). Enable the Google Sheets API [here](https://console.developers.google.com/apis/api/sheets.googleapis.com).
**Google Maps API** - Follow the instructions [here](https://developers.google.com/maps/documentation/android-api/signup). Please be sure not to commit your personal API key to a branch that you will submit a pull request for.
## Contributing code
Any and all contributions to the project are welcome. ODK Collect is used across the world primarily by organizations with a social purpose so you can have real impact!
Issues tagged as [quick win](https://github.com/opendatakit/collect/labels/quick%20win) should be a good place to start. There are also currently many issues tagged as [needs reproduction](https://github.com/opendatakit/collect/labels/needs%20reproduction) which need someone to try to reproduce them with the current version of ODK Collect and comment on the issue with their findings.
If you're ready to contribute code, see [the contribution guide](CONTRIBUTING.md).
## Contributing in other ways
If you know a language other than English, consider contributing translations through [Transifex](https://www.transifex.com/opendatakit/odk-collect/).
You can also help by improving this documentation.
## Downloading builds
Per-commit debug builds can be found on [CircleCI](https://circleci.com/gh/opendatakit/collect). Login with your GitHub account, click the build you'd like, then find the APK in the Artifacts tab.
Current and previous production builds can be found on the [ODK website](https://opendatakit.org/downloads/download-info/odk-collect-apk).
## Troubleshooting
#### Error when running Robolectric tests from Android Studio on macOS: `build/intermediates/bundles/debug/AndroidManifest.xml (No such file or directory)`
> Configure the default JUnit test runner configuration in order to work around a bug where IntelliJ / Android Studio does not set the working directory to the module being tested. This can be accomplished by editing the run configurations, Defaults -> JUnit and changing the working directory value to $MODULE_DIR$.
> Source: [Robolectric Wiki](https://github.com/robolectric/robolectric/wiki/Running-tests-in-Android-Studio#notes-for-mac).
#### Android Studio Error: `SDK location not found. Define location with sdk.dir in the local.properties file or with an ANDROID_HOME environment variable.`
When cloning the project from Android Studio, click "No" when prompted to open the `build.gradle` file and then open project.
......@@ -53,18 +53,6 @@ Secara umum, pengembangan aplikasi lebih lanjut dapat dilakukan dengan langkah-l
7. Untuk melakukan run project, tekan tombol panah hijau di bagian atas dari Android Studio.
> Note: Penggunaan emulator mungkin dapat mengakibatkan aplikasi berjalan lambat. Oleh karena itu, disarankan untuk menggunakan device selama memungkinkan.
## Panduan & Dokumentasi
Berikut adalah *table of content* dari panduan yang tersedia:
1. Instalasi aplikasi
- Menggunakan APK
- Run project pada Android Studio
2. Penggunaan aplikasi
- Konfigurasi Server
- User Interface pemutakhiran
- Keterkaitan Kuesioner
## Credits
Banyak pihak yang telah dilibatkan dalam melakukan pengembangan aplikasi ini. Oleh karena itu, pengembang mengucapkan terima kasih kepada:
......
......@@ -313,7 +313,7 @@ public class CapiInstanceActivity extends AppCompatActivity
HashMap<String, String> informations = new HashMap<>();
File file = new File(instance.getInstanceFilePath());
InstanceValues values = XmlUtils.getInstanceValues(instance.getInstanceUuid(), file, labels);
InstanceValues values = XmlUtils.getInstanceValues(instance.getInstanceUuid(), file, labels, InstanceValues.STATE_NEUTRAL);
Set<String> parsedXpaths = values.getXPaths();
for(String parsed : parsedXpaths) {
if(parsed.equals(titleSelected)) {
......
package id.ac.stis.capi.lessthink.activities;
import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import id.ac.stis.capi.lessthink.models.InstanceValues;
/**
* Author : Rahadi Jalu
* Email : 14.8325@stis.ac.id
* Company: Politeknik Statistika STIS
*/
public class GroupActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
private Map<String, List<InstanceValues>> getUniqueInstanceValuesByGroup(List<InstanceValues> instanceValues, String groupBy) {
Map<String, List<InstanceValues>> result = new HashMap<>();
for (InstanceValues values : instanceValues) {
String groupValue = values.getInstanceValue(groupBy);
if (groupValue == null) {
return null;
}
if(groupValue.isEmpty() || groupBy.trim().length() == 0) {
groupValue = "<Other>";
}
List<InstanceValues> group = result.get(groupValue);
if (group == null) {
group = new ArrayList<>();
result.put(groupValue, group);
}
group.add(values);
}
return new TreeMap<>(result);
}
}
package id.ac.stis.capi.lessthink.activities;
import android.content.ContentUris;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import org.apache.commons.lang3.ArrayUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import id.ac.stis.capi.R;
import id.ac.stis.capi.collectiva.preferences.Constants;
import id.ac.stis.capi.lessthink.binders.NodeViewFactory;
import id.ac.stis.capi.lessthink.fragments.FormEntryDialogFragment;
import id.ac.stis.capi.lessthink.fragments.InstancePreferenceFragment;
import id.ac.stis.capi.lessthink.listeners.InstanceDownloadListener;
import id.ac.stis.capi.lessthink.listeners.InstanceListDownloadListener;
import id.ac.stis.capi.lessthink.listeners.InstanceValueListener;
import id.ac.stis.capi.lessthink.listeners.OnEntrySavedListener;
import id.ac.stis.capi.lessthink.listeners.OnTableAddButtonClickListener;
import id.ac.stis.capi.lessthink.models.FormLabels;
import id.ac.stis.capi.lessthink.models.InstanceValues;
import id.ac.stis.capi.lessthink.models.ModeTreeNode;
import id.ac.stis.capi.lessthink.tasks.InstanceDownloadTask;
import id.ac.stis.capi.lessthink.tasks.InstanceListDownloadTask;
import id.ac.stis.capi.lessthink.tasks.InstanceValueTask;
import id.ac.stis.capi.lessthink.utils.XmlUtils;
import id.ac.stis.capi.lessthink.views.TableFooterView;
import id.ac.stis.capi.lessthink.views.TableHeaderView;
......@@ -43,15 +53,16 @@ import id.ac.stis.capi.odk.dto.Form;
import id.ac.stis.capi.odk.dto.Instance;
import id.ac.stis.capi.odk.listeners.DeleteInstancesListener;
import id.ac.stis.capi.odk.listeners.DiskSyncListener;
import id.ac.stis.capi.odk.provider.FormsProviderAPI;
import id.ac.stis.capi.odk.provider.InstanceProviderAPI;
import id.ac.stis.capi.odk.tasks.DeleteInstancesTask;
import id.ac.stis.capi.odk.tasks.InstanceSyncTask;
import id.ac.stis.capi.odk.utilities.ApplicationConstants;
import me.texy.treeview.TreeNode;
import me.texy.treeview.TreeView;
import timber.log.Timber;
public class InstanceActivity extends AppCompatActivity implements OnEntrySavedListener, SearchView.OnQueryTextListener,
DiskSyncListener, DeleteInstancesListener{
public class InstanceActivity extends AppCompatActivity implements OnEntrySavedListener,
SearchView.OnQueryTextListener, DiskSyncListener, DeleteInstancesListener,
InstanceListDownloadListener, InstanceDownloadListener, InstanceValueListener {
private static final String FORM_ID_KEY = "formid";
private static final String FORMNAME = "formname";
......@@ -65,6 +76,12 @@ public class InstanceActivity extends AppCompatActivity implements OnEntrySavedL
private InstanceSyncTask instanceSyncTask;
private DeleteInstancesTask deleteInstancesTask;
private LinearLayout progressBarHolder;
private FrameLayout downloadButton;
private InstanceListDownloadTask listDownloadTask;
private TextView initDataCountView;
private InstanceDownloadTask downloadTask;
private InstanceValueTask instanceValueTask;
private ProgressDialog dialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
......@@ -77,6 +94,8 @@ public class InstanceActivity extends AppCompatActivity implements OnEntrySavedL
tableRowView = findViewById(R.id.row_view);
tableFooterView = findViewById(R.id.footer_view);
progressBarHolder = findViewById(R.id.loading_container);
downloadButton = findViewById(R.id.init_data_download);
initDataCountView = findViewById(R.id.init_data_count);
formId = getIntent().getStringExtra(FORM_ID_KEY);
formName = getIntent().getStringExtra(FORMNAME);
......@@ -86,9 +105,44 @@ public class InstanceActivity extends AppCompatActivity implements OnEntrySavedL
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
instanceSyncTask = new InstanceSyncTask();
instanceSyncTask.setDiskSyncListener(this);
instanceSyncTask.execute();
FormsDao formsDao = new FormsDao();
List<Form> forms = formsDao.getFormsFromCursor(formsDao.getFormsCursorForFormId(formId));
File file = new File(forms.get(0).getFormFilePath());
List<String> xPaths = XmlUtils.getInstanceXPathList(file, false);
labels = XmlUtils.getFormLabels(formId, file, xPaths);
tableHeaderView.setColumns(labels);
tableRowView.initView();
tableRowView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(InstanceActivity.this, "Click : " + view.getTag(), Toast.LENGTH_LONG).show();
String instanceUuid = view.getTag().toString();
// entryDialogFragment = FormEntryDialogFragment.newInstance(instanceUuid,
// FormEntryDialogFragment.MODE_EDIT, false);
entryDialogFragment = FormEntryDialogFragment.newInstance(instanceUuid, formId);
entryDialogFragment.show(getSupportFragmentManager(), instanceUuid);
entryDialogFragment.setOnEntrySavedListener(InstanceActivity.this);
//
// Intent i =new Intent(InstanceActivity.this, ListingFormEntryActivity.class);
// i.putExtra("instanceUuid", view.getTag().toString());
// startActivity(i);
}
});
tableFooterView.setOnTableAddButtonClickListener(new OnTableAddButtonClickListener() {
@Override
public void onAddButtonClick(View v) {
fillBlankForm();
}
});
listDownloadTask = new InstanceListDownloadTask(formId);
listDownloadTask.setDownloaderListener(this);
listDownloadTask.execute();
onStartLoading();
}
......@@ -107,19 +161,24 @@ public class InstanceActivity extends AppCompatActivity implements OnEntrySavedL
@Override
protected void onResume() {
if (instanceSyncTask != null) {
instanceSyncTask.setDiskSyncListener(this);
onStartLoading();
}
super.onResume();
if (instanceSyncTask.getStatus() == AsyncTask.Status.FINISHED) {
// if (instanceSyncTask != null) {
// instanceSyncTask.setDiskSyncListener(this);
// onStartLoading();
// }
if (instanceSyncTask != null && instanceSyncTask.getStatus() == AsyncTask.Status.FINISHED) {
syncComplete(instanceSyncTask.getStatusMessage());
}
if (deleteInstancesTask != null
&& deleteInstancesTask.getStatus() == AsyncTask.Status.FINISHED) {
if (deleteInstancesTask != null && deleteInstancesTask.getStatus() == AsyncTask.Status.FINISHED) {
deleteComplete(deleteInstancesTask.getDeleteCount());
}
if (listDownloadTask != null && listDownloadTask.getStatus() == AsyncTask.Status.FINISHED) {
instanceListDownloadingComplete(listDownloadTask.getDownloadedUri());
}
}
@Override
......@@ -132,6 +191,10 @@ public class InstanceActivity extends AppCompatActivity implements OnEntrySavedL
deleteInstancesTask.setDeleteListener(null);
}
if (listDownloadTask != null) {
listDownloadTask.setDownloaderListener(null);
}
super.onPause();
}
......@@ -171,29 +234,10 @@ public class InstanceActivity extends AppCompatActivity implements OnEntrySavedL
}
private void refreshItem(boolean init) {
List<InstanceValues> instanceValues = new LinkedList<>();
String selection = InstanceProviderAPI.InstanceColumns.LATEST + " =? AND " +
InstanceProviderAPI.InstanceColumns.JR_FORM_ID + " =?";
String[] selectionArgs = new String[]{InstanceProviderAPI.LATEST_VERSION, formId};
InstancesDao instancesDao = new InstancesDao();
// TODO: 30/06/2018 Sorting order
Cursor data = instancesDao.getInstancesCursor(null, selection, selectionArgs, null);
List<Instance> instanceList = instancesDao.getInstancesFromCursor(data);
for (Instance instance : instanceList) {
File f = new File(instance.getInstanceFilePath());
InstanceValues values = XmlUtils.getInstanceValues(instance.getInstanceUuid(), f, labels);
instanceValues.add(values);
}
if (init) {
tableRowView.initView(instanceValues);
} else {
tableRowView.setRows(instanceValues);
}
private void refreshItem() {
instanceValueTask = new InstanceValueTask(labels);
instanceValueTask.setInstanceValueListener(this);
instanceValueTask.execute();
}
@Override
......@@ -212,7 +256,7 @@ public class InstanceActivity extends AppCompatActivity implements OnEntrySavedL
@Override
public void onEntrySaved() {
refreshItem(false);
refreshItem();
}
@Override
......@@ -226,44 +270,38 @@ public class InstanceActivity extends AppCompatActivity implements OnEntrySavedL
}
@Override
public void syncComplete(String result) {
onDoneLoading();
FormsDao formsDao = new FormsDao();
List<Form> forms = formsDao.getFormsFromCursor(formsDao.getFormsCursorForFormId(formId));
File file = new File(forms.get(0).getFormFilePath());
List<String> xPaths = XmlUtils.getInstanceXPathList(file);
labels = XmlUtils.getFormLabels(formId, file, xPaths);
tableHeaderView.setColumns(labels);
refreshItem(true);
tableRowView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Toast.makeText(InstanceActivity.this, "Click : " + view.getTag(), Toast.LENGTH_LONG).show();
public void instanceListDownloadingComplete(final Map<String, String> instances) {
if (instances.size() > 0) {
initDataCountView.setText("(" + instances.size() + ")");
downloadButton.setVisibility(View.VISIBLE);
downloadButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog = new ProgressDialog(InstanceActivity.this);
dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
dialog.setCancelable(false);
dialog.setMessage(getString(R.string.please_wait));
dialog.show();
downloadTask = new InstanceDownloadTask(formId, instances);
downloadTask.setDownloaderListener(InstanceActivity.this);
downloadTask.execute();
}
});
} else {
downloadButton.setVisibility(View.GONE);
}
String instanceUuid = view.getTag().toString();
// entryDialogFragment = FormEntryDialogFragment.newInstance(instanceUuid,
// FormEntryDialogFragment.MODE_EDIT, false);
entryDialogFragment = FormEntryDialogFragment.newInstance(instanceUuid, formId);
entryDialogFragment.show(getSupportFragmentManager(), instanceUuid);
entryDialogFragment.setOnEntrySavedListener(InstanceActivity.this);
//
// Intent i =new Intent(InstanceActivity.this, ListingFormEntryActivity.class);
// i.putExtra("instanceUuid", view.getTag().toString());
// startActivity(i);
}
});
instanceSyncTask = new InstanceSyncTask();
instanceSyncTask.setDiskSyncListener(this);
instanceSyncTask.execute();
}
tableFooterView.setOnTableAddButtonClickListener(new OnTableAddButtonClickListener() {
@Override
public void onAddButtonClick(View v) {
fillBlankForm();
}
});
@Override
public void syncComplete(String result) {
refreshItem();
}
@Override
......@@ -273,9 +311,9 @@ public class InstanceActivity extends AppCompatActivity implements OnEntrySavedL
private void onStartLoading() {
progressBarHolder.setVisibility(View.VISIBLE);
tableHeaderView.setVisibility(View.GONE);
tableRowView.setVisibility(View.GONE);
tableFooterView.setVisibility(View.GONE);
// tableHeaderView.setVisibility(View.INVISIBLE);
tableRowView.setVisibility(View.INVISIBLE);
tableFooterView.setVisibility(View.INVISIBLE);
}
private void onErrorLoading(String message) {
......@@ -284,8 +322,61 @@ public class InstanceActivity extends AppCompatActivity implements OnEntrySavedL
private void onDoneLoading() {
progressBarHolder.setVisibility(View.GONE);
tableHeaderView.setVisibility(View.VISIBLE);
// tableHeaderView.setVisibility(View.VISIBLE);
tableRowView.setVisibility(View.VISIBLE);
tableFooterView.setVisibility(View.VISIBLE);
}
@Override
public void onInstanceDownloadProgressUpdate(int progress, int total, String current) {
dialog.setMessage(getString(R.string.fetching_file, current, "" + progress, "" + total));
}
@Override
public void onInstanceDownloadCompleted(HashMap<String, String> result) {
dialog.dismiss();
TreeNode rootTreeNode = TreeNode.root();
Set<String> keySet = result.keySet();
for(String key : keySet) {
String status = result.get(key);
TreeNode childNode;
if(status.equals(InstanceDownloadTask.STATUS_SUCCESS)) {
childNode = new ModeTreeNode(key, "Success", ModeTreeNode.DOWNLOADED);
} else {
childNode = new ModeTreeNode(key, "Failed", ModeTreeNode.FAILED);
}
childNode.setExpanded(true);
childNode.setItemClickEnable(false);
rootTreeNode.addChild(childNode);
}
View treeView = new TreeView(rootTreeNode, this, new NodeViewFactory()).getView();
AlertDialog dialog = new AlertDialog.Builder(this)
.setTitle("Downloads Details")
.setView(treeView)
.setCancelable(false)
.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
instanceSyncTask = new InstanceSyncTask();
instanceSyncTask.setDiskSyncListener(InstanceActivity.this);
instanceSyncTask.execute();
}
})
.create();
dialog.show();
}
@Override
public void onValueParsed(List<InstanceValues> values) {
tableRowView.setRows(values);
onDoneLoading();
}
}
......@@ -40,26 +40,12 @@ public class HierarchyAdapter extends RecyclerView.Adapter<HierarchyAdapter.View
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
Timber.d("Creating with type %s", viewType);
// BaseHierarchy hierarchy = hierarchies.get(viewType);
// if (hierarchy instanceof PromptHierarchy) {
// ((PromptHierarchy) hierarchy).setOnChangeListener(onChangeListener);
// return new PromptViewHolder(hierarchy.getView());
// } else if (hierarchy instanceof CaptionHierarchy) {
// return new CaptionViewHolder(hierarchy.getView());