Merge pull request #60 from sismics/master

Push to production
This commit is contained in:
Benjamin Gamard 2016-01-24 16:30:50 +01:00
commit bf8e0827e2
45 changed files with 1073 additions and 323 deletions

View File

@ -60,7 +60,6 @@ or download the sources from GitHub.
From the `docs-parent` directory: From the `docs-parent` directory:
mvn -Pinit validate -N
mvn clean -DskipTests install mvn clean -DskipTests install
#### Run a stand-alone version #### Run a stand-alone version

View File

@ -12,10 +12,7 @@
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" /> <option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" /> <option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" /> <option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" />
<afterSyncTasks> <afterSyncTasks>
<task>generateDebugAndroidTestSources</task>
<task>generateDebugSources</task> <task>generateDebugSources</task>
</afterSyncTasks> </afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" /> <option name="ALLOW_USER_CONFIGURATION" value="false" />
@ -28,7 +25,7 @@
</component> </component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false"> <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" /> <output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/debug" /> <output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
@ -50,6 +47,13 @@
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
@ -64,65 +68,58 @@
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/apk" /> <sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
<excludeFolder url="file://$MODULE_DIR$/build/assets" /> <sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<excludeFolder url="file://$MODULE_DIR$/build/classes" /> <sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
<excludeFolder url="file://$MODULE_DIR$/build/dependency-cache" /> <sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/dex" /> <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/incremental" /> <sourceFolder url="file://$MODULE_DIR$/src/test/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/builds" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/debug" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.1.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/22.2.1/jars" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/recyclerview-v7/23.1.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/recyclerview-v7/22.2.1/jars" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/23.1.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/22.2.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.shamanland/fab/0.0.6/jars" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.shamanland/fab/0.0.6/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/it.sephiroth.android.library.easing/android-easing/1.0.3/jars" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/it.sephiroth.android.library.easing/android-easing/1.0.3/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/it.sephiroth.android.library.imagezoom/imagezoom/1.0.5/jars" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/it.sephiroth.android.library.imagezoom/imagezoom/1.0.5/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-classes" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-runtime-classes" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-runtime-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-runtimfe-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-verifier" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-verifier" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-support" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-support" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/reload-dex" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/reload-dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/resources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/restart-dex" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/restart-dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/tmp" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
<excludeFolder url="file://$MODULE_DIR$/build/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/ndk" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" /> <excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/res" />
<excludeFolder url="file://$MODULE_DIR$/build/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/source" />
<excludeFolder url="file://$MODULE_DIR$/build/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" /> <excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content> </content>
<orderEntry type="jdk" jdkName="Android API 22 Platform" jdkType="Android SDK" /> <orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="fab-0.0.6" level="project" /> <orderEntry type="library" exported="" name="support-annotations-23.1.1" level="project" />
<orderEntry type="library" exported="" name="android-easing-1.0.3" level="project" /> <orderEntry type="library" exported="" name="android-easing-1.0.3" level="project" />
<orderEntry type="library" exported="" name="imagezoom-1.0.5" level="project" /> <orderEntry type="library" exported="" name="imagezoom-1.0.5" level="project" />
<orderEntry type="library" exported="" name="support-v4-22.2.1" level="project" /> <orderEntry type="library" exported="" name="android-async-http-1.4.9" level="project" />
<orderEntry type="library" exported="" name="recyclerview-v7-22.2.1" level="project" /> <orderEntry type="library" exported="" name="picasso-2.5.2" level="project" />
<orderEntry type="library" exported="" name="android-query.0.26.8" level="project" />
<orderEntry type="library" exported="" name="tokenautocomplete-1.2.1" level="project" />
<orderEntry type="library" exported="" name="support-annotations-22.2.1" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-22.2.1" level="project" />
<orderEntry type="library" exported="" name="android-async-http-1.4.6" level="project" />
<orderEntry type="library" exported="" name="eventbus-2.4.1" level="project" /> <orderEntry type="library" exported="" name="eventbus-2.4.1" level="project" />
<orderEntry type="library" exported="" name="recyclerview-v7-23.1.1" level="project" />
<orderEntry type="library" exported="" name="okhttp-urlconnection-3.0.1" level="project" />
<orderEntry type="library" exported="" name="support-v4-23.1.1" level="project" />
<orderEntry type="library" exported="" name="fab-0.0.6" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-23.1.1" level="project" />
<orderEntry type="library" exported="" name="httpclient-4.3.6" level="project" />
<orderEntry type="library" exported="" name="okio-1.6.0" level="project" />
<orderEntry type="library" exported="" name="picasso2-okhttp3-downloader-1.0.2" level="project" />
<orderEntry type="library" exported="" name="tokenautocomplete-1.2.1" level="project" />
<orderEntry type="library" exported="" name="okhttp-3.0.1" level="project" />
</component> </component>
</module> </module>

View File

@ -3,7 +3,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.0.0-alpha3' classpath 'com.android.tools.build:gradle:2.0.0-alpha7'
} }
} }
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
@ -13,12 +13,12 @@ repositories {
} }
android { android {
compileSdkVersion 22 compileSdkVersion 23
buildToolsVersion '23.0.2' buildToolsVersion '23.0.2'
defaultConfig { defaultConfig {
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 22 targetSdkVersion 23
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
} }
@ -50,10 +50,14 @@ android {
dependencies { dependencies {
compile fileTree(dir: 'libs', include: '*.jar') compile fileTree(dir: 'libs', include: '*.jar')
compile 'com.android.support:appcompat-v7:22.+' compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:recyclerview-v7:22.+' compile 'com.android.support:recyclerview-v7:23.1.1'
compile 'com.loopj.android:android-async-http:1.4.6' compile 'com.loopj.android:android-async-http:1.4.9'
compile 'it.sephiroth.android.library.imagezoom:imagezoom:1.0.5' compile 'it.sephiroth.android.library.imagezoom:imagezoom:1.0.5'
compile 'de.greenrobot:eventbus:2.4.1' compile 'de.greenrobot:eventbus:2.4.1'
compile 'com.shamanland:fab:0.0.6' compile 'com.shamanland:fab:0.0.6'
compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.squareup.okhttp3:okhttp:3.0.1'
compile "com.squareup.okhttp3:okhttp-urlconnection:3.0.1"
compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.0.2'
} }

View File

@ -2,7 +2,6 @@ package com.sismics.docs;
import android.app.Application; import android.app.Application;
import com.androidquery.callback.BitmapAjaxCallback;
import com.sismics.docs.model.application.ApplicationContext; import com.sismics.docs.model.application.ApplicationContext;
import com.sismics.docs.util.PreferenceUtil; import com.sismics.docs.util.PreferenceUtil;
@ -24,10 +23,4 @@ public class MainApplication extends Application {
super.onCreate(); super.onCreate();
} }
@Override
public void onLowMemory() {
super.onLowMemory();
BitmapAjaxCallback.clearCache();
}
} }

View File

@ -17,7 +17,7 @@ import com.sismics.docs.adapter.LanguageAdapter;
import com.sismics.docs.adapter.TagAutoCompleteAdapter; import com.sismics.docs.adapter.TagAutoCompleteAdapter;
import com.sismics.docs.event.DocumentAddEvent; import com.sismics.docs.event.DocumentAddEvent;
import com.sismics.docs.event.DocumentEditEvent; import com.sismics.docs.event.DocumentEditEvent;
import com.sismics.docs.listener.JsonHttpResponseHandler; import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.resource.DocumentResource; import com.sismics.docs.resource.DocumentResource;
import com.sismics.docs.ui.form.Validator; import com.sismics.docs.ui.form.Validator;
import com.sismics.docs.ui.form.validator.Required; import com.sismics.docs.ui.form.validator.Required;
@ -25,7 +25,6 @@ import com.sismics.docs.ui.view.DatePickerView;
import com.sismics.docs.ui.view.TagsCompleteTextView; import com.sismics.docs.ui.view.TagsCompleteTextView;
import com.sismics.docs.util.PreferenceUtil; import com.sismics.docs.util.PreferenceUtil;
import org.apache.http.Header;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -174,9 +173,9 @@ public class DocumentEditActivity extends AppCompatActivity {
}); });
// Server callback // Server callback
JsonHttpResponseHandler callback = new JsonHttpResponseHandler() { HttpCallback callback = new HttpCallback() {
@Override @Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) { public void onSuccess(JSONObject response) {
// Build a fake document JSON to update the UI // Build a fake document JSON to update the UI
final JSONObject outputDoc = new JSONObject(); final JSONObject outputDoc = new JSONObject();
try { try {
@ -211,7 +210,7 @@ public class DocumentEditActivity extends AppCompatActivity {
} }
@Override @Override
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) { public void onFailure(JSONObject json, Exception e) {
Toast.makeText(DocumentEditActivity.this, R.string.error_editing_document, Toast.LENGTH_LONG).show(); Toast.makeText(DocumentEditActivity.this, R.string.error_editing_document, Toast.LENGTH_LONG).show();
} }

View File

@ -44,7 +44,9 @@ import com.sismics.docs.event.DocumentEditEvent;
import com.sismics.docs.event.DocumentFullscreenEvent; import com.sismics.docs.event.DocumentFullscreenEvent;
import com.sismics.docs.event.FileAddEvent; import com.sismics.docs.event.FileAddEvent;
import com.sismics.docs.event.FileDeleteEvent; import com.sismics.docs.event.FileDeleteEvent;
import com.sismics.docs.fragment.DocExportPdfFragment;
import com.sismics.docs.fragment.DocShareFragment; import com.sismics.docs.fragment.DocShareFragment;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.listener.JsonHttpResponseHandler; import com.sismics.docs.listener.JsonHttpResponseHandler;
import com.sismics.docs.model.application.ApplicationContext; import com.sismics.docs.model.application.ApplicationContext;
import com.sismics.docs.resource.CommentResource; import com.sismics.docs.resource.CommentResource;
@ -54,7 +56,6 @@ import com.sismics.docs.service.FileUploadService;
import com.sismics.docs.util.PreferenceUtil; import com.sismics.docs.util.PreferenceUtil;
import com.sismics.docs.util.TagUtil; import com.sismics.docs.util.TagUtil;
import org.apache.http.Header;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -63,6 +64,7 @@ import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import cz.msebera.android.httpclient.Header;
import de.greenrobot.event.EventBus; import de.greenrobot.event.EventBus;
/** /**
@ -244,6 +246,16 @@ public class DocumentViewActivity extends AppCompatActivity {
} }
}); });
// Action export PDF
button = (Button) findViewById(R.id.actionExportPdf);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
DialogFragment dialog = DocExportPdfFragment.newInstance(DocumentViewActivity.this.document.optString("id"));
dialog.show(getSupportFragmentManager(), "DocExportPdfFragment");
}
});
// Action share // Action share
button = (Button) findViewById(R.id.actionSharing); button = (Button) findViewById(R.id.actionSharing);
button.setOnClickListener(new View.OnClickListener() { button.setOnClickListener(new View.OnClickListener() {
@ -476,14 +488,14 @@ public class DocumentViewActivity extends AppCompatActivity {
// Actual delete server call // Actual delete server call
final String documentId = document.optString("id"); final String documentId = document.optString("id");
DocumentResource.delete(DocumentViewActivity.this, documentId, new JsonHttpResponseHandler() { DocumentResource.delete(DocumentViewActivity.this, documentId, new HttpCallback() {
@Override @Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) { public void onSuccess(JSONObject response) {
EventBus.getDefault().post(new DocumentDeleteEvent(documentId)); EventBus.getDefault().post(new DocumentDeleteEvent(documentId));
} }
@Override @Override
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) { public void onFailure(JSONObject json, Exception e) {
Toast.makeText(DocumentViewActivity.this, R.string.document_delete_failure, Toast.LENGTH_LONG).show(); Toast.makeText(DocumentViewActivity.this, R.string.document_delete_failure, Toast.LENGTH_LONG).show();
} }
@ -635,9 +647,9 @@ public class DocumentViewActivity extends AppCompatActivity {
// Silently get the document to know if it is writable by the current user // Silently get the document to know if it is writable by the current user
// If this call fails or is slow and the document is read-only, // If this call fails or is slow and the document is read-only,
// write actions will be allowed and will fail // write actions will be allowed and will fail
DocumentResource.get(this, document.optString("id"), new JsonHttpResponseHandler() { DocumentResource.get(this, document.optString("id"), new HttpCallback() {
@Override @Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) { public void onSuccess(JSONObject response) {
document = response; document = response;
boolean writable = document.optBoolean("writable"); boolean writable = document.optBoolean("writable");
@ -720,7 +732,7 @@ public class DocumentViewActivity extends AppCompatActivity {
@Override @Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) { public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
JSONArray comments = response.optJSONArray("comments"); JSONArray comments = response.optJSONArray("comments");
commentListAdapter = new CommentListAdapter(comments); commentListAdapter = new CommentListAdapter(DocumentViewActivity.this, comments);
listView.setAdapter(commentListAdapter); listView.setAdapter(commentListAdapter);
listView.setVisibility(View.VISIBLE); listView.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);

View File

@ -10,8 +10,8 @@ import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.TextView;
import com.androidquery.AQuery;
import com.sismics.docs.R; import com.sismics.docs.R;
import com.sismics.docs.listener.CallbackListener; import com.sismics.docs.listener.CallbackListener;
import com.sismics.docs.listener.JsonHttpResponseHandler; import com.sismics.docs.listener.JsonHttpResponseHandler;
@ -22,9 +22,10 @@ import com.sismics.docs.ui.form.validator.Required;
import com.sismics.docs.util.DialogUtil; import com.sismics.docs.util.DialogUtil;
import com.sismics.docs.util.PreferenceUtil; import com.sismics.docs.util.PreferenceUtil;
import org.apache.http.Header;
import org.json.JSONObject; import org.json.JSONObject;
import cz.msebera.android.httpclient.Header;
/** /**
* Login activity. * Login activity.
* *
@ -43,18 +44,16 @@ public class LoginActivity extends AppCompatActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.login_activity); setContentView(R.layout.login_activity);
AQuery aq = new AQuery(this); TextView loginExplainTextView = (TextView) findViewById(R.id.loginExplain);
aq.id(R.id.loginExplain) loginExplainTextView.setText(Html.fromHtml(getString(R.string.login_explain)));
.text(Html.fromHtml(getString(R.string.login_explain))) loginExplainTextView.setMovementMethod(LinkMovementMethod.getInstance());
.getTextView()
.setMovementMethod(LinkMovementMethod.getInstance());
final EditText txtServer = aq.id(R.id.txtServer).getEditText(); final EditText txtServer = (EditText) findViewById(R.id.txtServer);
final EditText txtUsername = aq.id(R.id.txtUsername).getEditText(); final EditText txtUsername = (EditText) findViewById(R.id.txtUsername);
final EditText txtPassword = aq.id(R.id.txtPassword).getEditText(); final EditText txtPassword = (EditText) findViewById(R.id.txtPassword);
final Button btnConnect = aq.id(R.id.btnConnect).getButton(); final Button btnConnect = (Button) findViewById(R.id.btnConnect);
loginForm = aq.id(R.id.loginForm).getView(); loginForm = findViewById(R.id.loginForm);
progressBar = aq.id(R.id.progressBar).getView(); progressBar = findViewById(R.id.progressBar);
PreferenceManager.setDefaultValues(this, R.xml.preferences, false); PreferenceManager.setDefaultValues(this, R.xml.preferences, false);

View File

@ -18,7 +18,6 @@ import android.widget.ListView;
import android.widget.SearchView; import android.widget.SearchView;
import android.widget.TextView; import android.widget.TextView;
import com.androidquery.util.AQUtility;
import com.sismics.docs.R; import com.sismics.docs.R;
import com.sismics.docs.adapter.TagListAdapter; import com.sismics.docs.adapter.TagListAdapter;
import com.sismics.docs.event.AdvancedSearchEvent; import com.sismics.docs.event.AdvancedSearchEvent;
@ -31,9 +30,9 @@ import com.sismics.docs.resource.TagResource;
import com.sismics.docs.resource.UserResource; import com.sismics.docs.resource.UserResource;
import com.sismics.docs.util.PreferenceUtil; import com.sismics.docs.util.PreferenceUtil;
import org.apache.http.Header;
import org.json.JSONObject; import org.json.JSONObject;
import cz.msebera.android.httpclient.Header;
import de.greenrobot.event.EventBus; import de.greenrobot.event.EventBus;
/** /**
@ -274,10 +273,6 @@ public class MainActivity extends AppCompatActivity {
@Override @Override
protected void onDestroy() { protected void onDestroy() {
EventBus.getDefault().unregister(this); EventBus.getDefault().unregister(this);
if(isTaskRoot()) {
int cacheSizeMb = PreferenceUtil.getIntegerPreference(this, PreferenceUtil.PREF_CACHE_SIZE, 10);
AQUtility.cleanCacheAsync(this, cacheSizeMb * 1000000, cacheSizeMb * 1000000);
}
super.onDestroy(); super.onDestroy();
} }
} }

View File

@ -1,7 +1,6 @@
package com.sismics.docs.adapter; package com.sismics.docs.adapter;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap;
import android.text.format.DateFormat; import android.text.format.DateFormat;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -10,9 +9,8 @@ import android.widget.BaseAdapter;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.androidquery.AQuery;
import com.androidquery.callback.BitmapAjaxCallback;
import com.sismics.docs.R; import com.sismics.docs.R;
import com.sismics.docs.util.OkHttpUtil;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
@ -27,22 +25,23 @@ import java.util.List;
* @author bgamard. * @author bgamard.
*/ */
public class CommentListAdapter extends BaseAdapter { public class CommentListAdapter extends BaseAdapter {
/**
* AQuery.
*/
private AQuery aq;
/** /**
* Tags. * Tags.
*/ */
private List<JSONObject> commentList = new ArrayList<>(); private List<JSONObject> commentList = new ArrayList<>();
/**
* Context.
*/
private Context context;
/** /**
* Comment list adapter. * Comment list adapter.
* *
* @param commentsArray Comments * @param commentsArray Comments
*/ */
public CommentListAdapter(JSONArray commentsArray) { public CommentListAdapter(Context context, JSONArray commentsArray) {
this.context = context;
for (int i = 0; i < commentsArray.length(); i++) { for (int i = 0; i < commentsArray.length(); i++) {
commentList.add(commentsArray.optJSONObject(i)); commentList.add(commentsArray.optJSONObject(i));
} }
@ -70,12 +69,6 @@ public class CommentListAdapter extends BaseAdapter {
view = vi.inflate(R.layout.comment_list_item, parent, false); view = vi.inflate(R.layout.comment_list_item, parent, false);
} }
if (aq == null) {
aq = new AQuery(view);
} else {
aq.recycle(view);
}
// Fill the view // Fill the view
JSONObject comment = getItem(position); JSONObject comment = getItem(position);
TextView creatorTextView = (TextView) view.findViewById(R.id.creatorTextView); TextView creatorTextView = (TextView) view.findViewById(R.id.creatorTextView);
@ -88,14 +81,9 @@ public class CommentListAdapter extends BaseAdapter {
// Gravatar image // Gravatar image
String gravatarUrl = "http://www.gravatar.com/avatar/" + comment.optString("creator_gravatar") + "?s=128d=identicon"; String gravatarUrl = "http://www.gravatar.com/avatar/" + comment.optString("creator_gravatar") + "?s=128d=identicon";
if (aq.shouldDelay(position, view, parent, gravatarUrl)) { OkHttpUtil.picasso(context)
aq.id(gravatarImageView).image((Bitmap) null); .load(gravatarUrl)
} else { .into(gravatarImageView);
aq.id(gravatarImageView).image(new BitmapAjaxCallback()
.url(gravatarUrl)
.animation(AQuery.FADE_IN_NETWORK)
);
}
return view; return view;
} }

View File

@ -7,10 +7,11 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import com.androidquery.AQuery;
import com.androidquery.callback.BitmapAjaxCallback;
import com.sismics.docs.R; import com.sismics.docs.R;
import com.sismics.docs.util.OkHttpUtil;
import com.sismics.docs.util.PreferenceUtil; import com.sismics.docs.util.PreferenceUtil;
import com.squareup.picasso.Callback;
import com.squareup.picasso.MemoryPolicy;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
@ -30,11 +31,6 @@ public class FilePagerAdapter extends PagerAdapter {
*/ */
private List<JSONObject> files; private List<JSONObject> files;
/**
* AQuery.
*/
private AQuery aq;
/** /**
* Context. * Context.
*/ */
@ -58,7 +54,6 @@ public class FilePagerAdapter extends PagerAdapter {
} }
this.context = context; this.context = context;
this.authToken = PreferenceUtil.getAuthToken(context); this.authToken = PreferenceUtil.getAuthToken(context);
aq = new AQuery(context);
} }
@Override @Override
@ -66,15 +61,20 @@ public class FilePagerAdapter extends PagerAdapter {
View view = LayoutInflater.from(container.getContext()).inflate(R.layout.file_viewpager_item, container, false); View view = LayoutInflater.from(container.getContext()).inflate(R.layout.file_viewpager_item, container, false);
ImageViewTouch fileImageView = (ImageViewTouch) view.findViewById(R.id.fileImageView); ImageViewTouch fileImageView = (ImageViewTouch) view.findViewById(R.id.fileImageView);
ProgressBar progressBar = (ProgressBar) view.findViewById(R.id.fileProgressBar); final ProgressBar progressBar = (ProgressBar) view.findViewById(R.id.fileProgressBar);
JSONObject file = files.get(position); JSONObject file = files.get(position);
String fileUrl = PreferenceUtil.getServerUrl(context) + "/api/file/" + file.optString("id") + "/data?size=web"; String fileUrl = PreferenceUtil.getServerUrl(context) + "/api/file/" + file.optString("id") + "/data?size=web";
aq.id(fileImageView)
.image(new BitmapAjaxCallback() // Load image
.url(fileUrl) OkHttpUtil.picasso(context)
.progress(progressBar) .load(fileUrl)
.animation(AQuery.FADE_IN_NETWORK) .memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE) // Don't memory cache the images
.cookie("auth_token", authToken)); .into(fileImageView, new Callback.EmptyCallback() {
@Override
public void onSuccess() {
progressBar.setVisibility(View.GONE);
}
});
fileImageView.setDisplayType(ImageViewTouchBase.DisplayType.FIT_TO_SCREEN); fileImageView.setDisplayType(ImageViewTouchBase.DisplayType.FIT_TO_SCREEN);
@ -109,7 +109,7 @@ public class FilePagerAdapter extends PagerAdapter {
* @return Object * @return Object
*/ */
public JSONObject getObjectAt(int position) { public JSONObject getObjectAt(int position) {
if (files == null) { if (files == null || position < 0 || position >= files.size()) {
return null; return null;
} }

View File

@ -0,0 +1,56 @@
package com.sismics.docs.fragment;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import com.sismics.docs.R;
/**
* Export PDF dialog fragment.
*
* @author bgamard.
*/
public class DocExportPdfFragment extends DialogFragment {
/**
* Export PDF dialog fragment.
*
* @param id Document ID
*/
public static DocExportPdfFragment newInstance(String id) {
DocExportPdfFragment fragment = new DocExportPdfFragment();
Bundle args = new Bundle();
args.putString("id", id);
fragment.setArguments(args);
return fragment;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
// Setup the view
LayoutInflater inflater = getActivity().getLayoutInflater();
View view = inflater.inflate(R.layout.document_export_pdf_dialog, null);
// Build the dialog
builder.setView(view)
.setPositiveButton(R.string.download, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
getDialog().cancel();
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
getDialog().cancel();
}
});
return builder.create();
}
}

View File

@ -21,13 +21,12 @@ import com.sismics.docs.event.DocumentAddEvent;
import com.sismics.docs.event.DocumentDeleteEvent; import com.sismics.docs.event.DocumentDeleteEvent;
import com.sismics.docs.event.DocumentEditEvent; import com.sismics.docs.event.DocumentEditEvent;
import com.sismics.docs.event.SearchEvent; import com.sismics.docs.event.SearchEvent;
import com.sismics.docs.listener.JsonHttpResponseHandler; import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.listener.RecyclerItemClickListener; import com.sismics.docs.listener.RecyclerItemClickListener;
import com.sismics.docs.resource.DocumentResource; import com.sismics.docs.resource.DocumentResource;
import com.sismics.docs.ui.view.DividerItemDecoration; import com.sismics.docs.ui.view.DividerItemDecoration;
import com.sismics.docs.ui.view.EmptyRecyclerView; import com.sismics.docs.ui.view.EmptyRecyclerView;
import org.apache.http.Header;
import org.json.JSONObject; import org.json.JSONObject;
import de.greenrobot.event.EventBus; import de.greenrobot.event.EventBus;
@ -218,16 +217,16 @@ public class DocListFragment extends Fragment {
recyclerView.setEmptyView(progressBar); recyclerView.setEmptyView(progressBar);
DocumentResource.list(getActivity(), reset ? 0 : adapter.getItemCount(), query, new JsonHttpResponseHandler() { DocumentResource.list(getActivity(), reset ? 0 : adapter.getItemCount(), query, new HttpCallback() {
@Override @Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) { public void onSuccess(JSONObject response) {
adapter.addDocuments(response.optJSONArray("documents")); adapter.addDocuments(response.optJSONArray("documents"));
documentsEmptyView.setText(R.string.no_documents); documentsEmptyView.setText(R.string.no_documents);
recyclerView.setEmptyView(documentsEmptyView); recyclerView.setEmptyView(documentsEmptyView);
} }
@Override @Override
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) { public void onFailure(JSONObject response, Exception e) {
documentsEmptyView.setText(R.string.error_loading_documents); documentsEmptyView.setText(R.string.error_loading_documents);
recyclerView.setEmptyView(documentsEmptyView); recyclerView.setEmptyView(documentsEmptyView);

View File

@ -21,15 +21,16 @@ import com.sismics.docs.R;
import com.sismics.docs.adapter.ShareListAdapter; import com.sismics.docs.adapter.ShareListAdapter;
import com.sismics.docs.event.ShareDeleteEvent; import com.sismics.docs.event.ShareDeleteEvent;
import com.sismics.docs.event.ShareSendEvent; import com.sismics.docs.event.ShareSendEvent;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.listener.JsonHttpResponseHandler; import com.sismics.docs.listener.JsonHttpResponseHandler;
import com.sismics.docs.resource.DocumentResource; import com.sismics.docs.resource.DocumentResource;
import com.sismics.docs.resource.ShareResource; import com.sismics.docs.resource.ShareResource;
import com.sismics.docs.util.PreferenceUtil; import com.sismics.docs.util.PreferenceUtil;
import org.apache.http.Header;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import cz.msebera.android.httpclient.Header;
import de.greenrobot.event.EventBus; import de.greenrobot.event.EventBus;
/** /**
@ -44,7 +45,8 @@ public class DocShareFragment extends DialogFragment {
private JSONObject document; private JSONObject document;
/** /**
* Document sharing dialog fragment * Document sharing dialog fragment.
*
* @param id Document ID * @param id Document ID
*/ */
public static DocShareFragment newInstance(String id) { public static DocShareFragment newInstance(String id) {
@ -121,9 +123,9 @@ public class DocShareFragment extends DialogFragment {
final ProgressBar shareProgressBar = (ProgressBar) view.findViewById(R.id.shareProgressBar); final ProgressBar shareProgressBar = (ProgressBar) view.findViewById(R.id.shareProgressBar);
shareListView.setEmptyView(shareProgressBar); shareListView.setEmptyView(shareProgressBar);
DocumentResource.get(getActivity(), getArguments().getString("id"), new JsonHttpResponseHandler() { DocumentResource.get(getActivity(), getArguments().getString("id"), new HttpCallback() {
@Override @Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) { public void onSuccess(JSONObject response) {
document = response; document = response;
JSONArray acls = response.optJSONArray("acls"); JSONArray acls = response.optJSONArray("acls");
shareProgressBar.setVisibility(View.GONE); shareProgressBar.setVisibility(View.GONE);
@ -132,7 +134,7 @@ public class DocShareFragment extends DialogFragment {
} }
@Override @Override
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) { public void onFailure(JSONObject json, Exception e) {
getDialog().cancel(); getDialog().cancel();
Toast.makeText(getActivity(), R.string.error_loading_shares, Toast.LENGTH_SHORT).show(); Toast.makeText(getActivity(), R.string.error_loading_shares, Toast.LENGTH_SHORT).show();
} }

View File

@ -9,10 +9,10 @@ import android.preference.PreferenceManager;
import android.provider.SearchRecentSuggestions; import android.provider.SearchRecentSuggestions;
import android.widget.Toast; import android.widget.Toast;
import com.androidquery.util.AQUtility;
import com.sismics.docs.R; import com.sismics.docs.R;
import com.sismics.docs.provider.RecentSuggestionsProvider; import com.sismics.docs.provider.RecentSuggestionsProvider;
import com.sismics.docs.util.ApplicationUtil; import com.sismics.docs.util.ApplicationUtil;
import com.sismics.docs.util.OkHttpUtil;
import com.sismics.docs.util.PreferenceUtil; import com.sismics.docs.util.PreferenceUtil;
/** /**
@ -52,7 +52,7 @@ public class SettingsFragment extends PreferenceFragment implements SharedPrefer
clearCachePref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { clearCachePref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
AQUtility.cleanCacheAsync(getActivity()); OkHttpUtil.clearCache(getActivity());
Toast.makeText(getActivity(), R.string.pref_clear_cache_success, Toast.LENGTH_LONG).show(); Toast.makeText(getActivity(), R.string.pref_clear_cache_success, Toast.LENGTH_LONG).show();
return true; return true;
} }

View File

@ -0,0 +1,78 @@
package com.sismics.docs.listener;
import android.os.Handler;
import android.os.Looper;
import org.json.JSONObject;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
/**
* An HTTP callback.
*
* @author bgamard.
*/
public class HttpCallback {
public void onSuccess(JSONObject json) {
// Implement me
}
public void onFailure(JSONObject json, Exception e) {
// Implement me
}
public void onFinish() {
// Implement me
}
/**
* Build an OkHttp Callback from a HttpCallback.
*
* @param httpCallback HttpCallback
* @return OkHttp Callback
*/
public static Callback buildOkHttpCallback(final HttpCallback httpCallback) {
return new Callback() {
@Override
public void onResponse(final Call call, final Response response) throws IOException {
final String body = response.body().string();
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
if (response.isSuccessful()) {
try {
httpCallback.onSuccess(new JSONObject(body));
} catch (Exception e) {
httpCallback.onFailure(null, e);
}
} else {
try {
httpCallback.onFailure(new JSONObject(body), null);
} catch (Exception e) {
httpCallback.onFailure(null, e);
}
}
httpCallback.onFinish();
}
});
}
@Override
public void onFailure(final Call call, final IOException e) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
httpCallback.onFailure(null, e);
httpCallback.onFinish();
}
});
}
};
}
}

View File

@ -22,19 +22,20 @@ import android.util.Log;
import com.loopj.android.http.TextHttpResponseHandler; import com.loopj.android.http.TextHttpResponseHandler;
import org.apache.http.Header;
import org.apache.http.HttpStatus;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.json.JSONTokener; import org.json.JSONTokener;
import cz.msebera.android.httpclient.Header;
import cz.msebera.android.httpclient.HttpStatus;
/** /**
* Used to intercept and handle the responses from requests made using {@link com.loopj.android.http.AsyncHttpClient}, with * Used to intercept and handle the responses from requests made using {@link com.loopj.android.http.AsyncHttpClient}, with
* automatic parsing into a {@link JSONObject} or {@link JSONArray}. <p>&nbsp;</p> This class is * automatic parsing into a {@link JSONObject} or {@link JSONArray}. <p>&nbsp;</p> This class is
* designed to be passed to get, post, put and delete requests with the {@link #onSuccess(int, * designed to be passed to get, post, put and delete requests with the {@link #onSuccess(int,
* org.apache.http.Header[], org.json.JSONArray)} or {@link #onSuccess(int, * cz.msebera.android.httpclient.Header[], org.json.JSONArray)} or {@link #onSuccess(int,
* org.apache.http.Header[], org.json.JSONObject)} methods anonymously overridden. <p>&nbsp;</p> * cz.msebera.android.httpclient.Header[], org.json.JSONObject)} methods anonymously overridden. <p>&nbsp;</p>
* Additionally, you can override the other event methods from the parent class. * Additionally, you can override the other event methods from the parent class.
*/ */
public class JsonHttpResponseHandler extends TextHttpResponseHandler { public class JsonHttpResponseHandler extends TextHttpResponseHandler {

View File

@ -8,9 +8,10 @@ import com.sismics.docs.listener.JsonHttpResponseHandler;
import com.sismics.docs.resource.UserResource; import com.sismics.docs.resource.UserResource;
import com.sismics.docs.util.PreferenceUtil; import com.sismics.docs.util.PreferenceUtil;
import org.apache.http.Header;
import org.json.JSONObject; import org.json.JSONObject;
import cz.msebera.android.httpclient.Header;
/** /**
* Global context of the application. * Global context of the application.
* *

View File

@ -3,14 +3,11 @@ package com.sismics.docs.resource;
import android.content.Context; import android.content.Context;
import android.os.Build; import android.os.Build;
import com.androidquery.callback.AbstractAjaxCallback;
import com.loopj.android.http.AsyncHttpClient; import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.PersistentCookieStore; import com.loopj.android.http.PersistentCookieStore;
import com.sismics.docs.util.ApplicationUtil; import com.sismics.docs.util.ApplicationUtil;
import com.sismics.docs.util.PreferenceUtil; import com.sismics.docs.util.PreferenceUtil;
import org.apache.http.conn.ssl.SSLSocketFactory;
import java.io.IOException; import java.io.IOException;
import java.net.Socket; import java.net.Socket;
import java.security.KeyManagementException; import java.security.KeyManagementException;
@ -26,13 +23,14 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import cz.msebera.android.httpclient.conn.ssl.SSLSocketFactory;
/** /**
* Base class for API access. * Base class for API access.
* *
* @author bgamard * @author bgamard
*/ */
public class BaseResource { public class BaseResource {
/** /**
* User-Agent to use. * User-Agent to use.
*/ */
@ -44,7 +42,7 @@ public class BaseResource {
protected static String ACCEPT_LANGUAGE = null; protected static String ACCEPT_LANGUAGE = null;
/** /**
* HTTP client. * Async HTTP client.
*/ */
protected static AsyncHttpClient client = new AsyncHttpClient(); protected static AsyncHttpClient client = new AsyncHttpClient();
@ -54,10 +52,11 @@ public class BaseResource {
try { try {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null); trustStore.load(null, null);
// Async HTTP Client uses another HTTP libary
MySSLSocketFactory sf = new MySSLSocketFactory(trustStore); MySSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(MySSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); sf.setHostnameVerifier(MySSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
client.setSSLSocketFactory(sf); client.setSSLSocketFactory(sf);
AbstractAjaxCallback.setSSF(sf);
} catch (Exception e) { } catch (Exception e) {
// NOP // NOP
} }
@ -84,12 +83,12 @@ public class BaseResource {
} }
/** /**
* Socket factory to allow self-signed certificates. * Socket factory to allow self-signed certificates for Async HTTP Client.
* *
* @author bgamard * @author bgamard
*/ */
public static class MySSLSocketFactory extends SSLSocketFactory { public static class MySSLSocketFactory extends cz.msebera.android.httpclient.conn.ssl.SSLSocketFactory {
SSLContext sslContext = SSLContext.getInstance("TLS"); SSLContext sslContext = SSLContext.getInstance(SSLSocketFactory.TLS);
public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(truststore); super(truststore);
@ -106,7 +105,7 @@ public class BaseResource {
} }
}; };
sslContext.init(null, new TrustManager[] { tm }, null); sslContext.init(null, new TrustManager[]{tm}, null);
} }
@Override @Override

View File

@ -2,11 +2,15 @@ package com.sismics.docs.resource;
import android.content.Context; import android.content.Context;
import com.loopj.android.http.RequestParams; import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.listener.JsonHttpResponseHandler; import com.sismics.docs.util.OkHttpUtil;
import java.util.Set; import java.util.Set;
import okhttp3.FormBody;
import okhttp3.HttpUrl;
import okhttp3.Request;
/** /**
* Access to /document API. * Access to /document API.
* *
@ -19,18 +23,23 @@ public class DocumentResource extends BaseResource {
* @param context Context * @param context Context
* @param offset Offset * @param offset Offset
* @param query Search query * @param query Search query
* @param responseHandler Callback * @param callback Callback
*/ */
public static void list(Context context, int offset, String query, JsonHttpResponseHandler responseHandler) { public static void list(Context context, int offset, String query, HttpCallback callback) {
init(context); Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/document/list")
RequestParams params = new RequestParams(); .newBuilder()
params.put("limit", 20); .addQueryParameter("limit", "20")
params.put("offset", offset); .addQueryParameter("offset", Integer.toString(offset))
params.put("sort_column", 3); .addQueryParameter("sort_column", "3")
params.put("asc", false); .addQueryParameter("asc", "false")
params.put("search", query); .addQueryParameter("search", query)
client.get(getApiUrl(context) + "/document/list", params, responseHandler); .build())
.get()
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
} }
/** /**
@ -38,12 +47,16 @@ public class DocumentResource extends BaseResource {
* *
* @param context Context * @param context Context
* @param id ID * @param id ID
* @param responseHandler Callback * @param callback Callback
*/ */
public static void get(Context context, String id, JsonHttpResponseHandler responseHandler) { public static void get(Context context, String id, HttpCallback callback) {
init(context); Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/document/" + id))
client.get(getApiUrl(context) + "/document/" + id, responseHandler); .get()
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
} }
/** /**
@ -51,12 +64,16 @@ public class DocumentResource extends BaseResource {
* *
* @param context Context * @param context Context
* @param id ID * @param id ID
* @param responseHandler Callback * @param callback Callback
*/ */
public static void delete(Context context, String id, JsonHttpResponseHandler responseHandler) { public static void delete(Context context, String id, HttpCallback callback) {
init(context); Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/document/" + id))
client.delete(getApiUrl(context) + "/document/" + id, responseHandler); .delete()
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
} }
/** /**
@ -68,19 +85,27 @@ public class DocumentResource extends BaseResource {
* @param tagIdList Tags ID list * @param tagIdList Tags ID list
* @param language Language * @param language Language
* @param createDate Create date * @param createDate Create date
* @param responseHandler Callback * @param callback Callback
*/ */
public static void add(Context context, String title, String description, public static void add(Context context, String title, String description,
Set<String> tagIdList, String language, long createDate, JsonHttpResponseHandler responseHandler) { Set<String> tagIdList, String language, long createDate, HttpCallback callback) {
init(context); FormBody.Builder formBuilder = new FormBody.Builder()
.add("title", title)
.add("description", description)
.add("language", language)
.add("create_date", Long.toString(createDate));
String[] tagIdArray = tagIdList.toArray(new String[tagIdList.size()]);
for (int i = 0; i < tagIdArray.length; i++) {
formBuilder.add("tags", tagIdArray[i]);
}
RequestParams params = new RequestParams(); Request request = new Request.Builder()
params.put("title", title); .url(HttpUrl.parse(getApiUrl(context) + "/document"))
params.put("description", description); .put(formBuilder.build())
params.put("tags", tagIdList); .build();
params.put("language", language); OkHttpUtil.buildClient(context)
params.put("create_date", createDate); .newCall(request)
client.put(getApiUrl(context) + "/document", params, responseHandler); .enqueue(HttpCallback.buildOkHttpCallback(callback));
} }
/** /**
@ -93,19 +118,27 @@ public class DocumentResource extends BaseResource {
* @param tagIdList Tags ID list * @param tagIdList Tags ID list
* @param language Language * @param language Language
* @param createDate Create date * @param createDate Create date
* @param responseHandler Callback * @param callback Callback
*/ */
public static void edit(Context context, String id, String title, String description, public static void edit(Context context, String id, String title, String description,
Set<String> tagIdList, String language, long createDate, JsonHttpResponseHandler responseHandler) { Set<String> tagIdList, String language, long createDate, HttpCallback callback) {
init(context); FormBody.Builder formBuilder = new FormBody.Builder()
.add("title", title)
.add("description", description)
.add("language", language)
.add("create_date", Long.toString(createDate));
String[] tagIdArray = tagIdList.toArray(new String[tagIdList.size()]);
for (int i = 0; i < tagIdArray.length; i++) {
formBuilder.add("tags", tagIdArray[i]);
}
RequestParams params = new RequestParams(); Request request = new Request.Builder()
params.put("title", title); .url(HttpUrl.parse(getApiUrl(context) + "/document/" + id))
params.put("description", description); .post(formBuilder.build())
params.put("tags", tagIdList); .build();
params.put("language", language); OkHttpUtil.buildClient(context)
params.put("create_date", createDate); .newCall(request)
client.post(getApiUrl(context) + "/document/" + id, params, responseHandler); .enqueue(HttpCallback.buildOkHttpCallback(callback));
} }
/** /**

View File

@ -0,0 +1,229 @@
package com.sismics.docs.resource.cookie;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.CookieStore;
import java.net.HttpCookie;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* A persistent cookie store which implements the Apache HttpClient CookieStore interface.
* Cookies are stored and will persist on the user's device between application sessions since they
* are serialized and stored in SharedPreferences.
*/
public class PersistentCookieStore implements CookieStore {
private static final String LOG_TAG = "PersistentCookieStore";
private static final String COOKIE_PREFS = "CookiePrefsFileOkHttp";
private static final String COOKIE_NAME_PREFIX = "cookie_okhttp_";
private final HashMap<String, ConcurrentHashMap<String, HttpCookie>> cookies;
private final SharedPreferences cookiePrefs;
/**
* Construct a persistent cookie store.
*
* @param context Context to attach cookie store to
*/
public PersistentCookieStore(Context context) {
cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);
cookies = new HashMap<>();
// Load any previously stored cookies into the store
Map<String, ?> prefsMap = cookiePrefs.getAll();
for (Map.Entry<String, ?> entry : prefsMap.entrySet()) {
if (entry.getValue() != null && !((String) entry.getValue()).startsWith(COOKIE_NAME_PREFIX)) {
String[] cookieNames = TextUtils.split((String) entry.getValue(), ",");
for (String name : cookieNames) {
String encodedCookie = cookiePrefs.getString(COOKIE_NAME_PREFIX + name, null);
if (encodedCookie != null) {
HttpCookie decodedCookie = decodeCookie(encodedCookie);
if (decodedCookie != null) {
if (!cookies.containsKey(entry.getKey()))
cookies.put(entry.getKey(), new ConcurrentHashMap<String, HttpCookie>());
cookies.get(entry.getKey()).put(name, decodedCookie);
}
}
}
}
}
}
@Override
public void add(URI uri, HttpCookie cookie) {
String name = getCookieToken(uri, cookie);
// Save cookie into local store, or remove if expired
if (!cookie.hasExpired()) {
if (!cookies.containsKey(uri.getHost()))
cookies.put(uri.getHost(), new ConcurrentHashMap<String, HttpCookie>());
cookies.get(uri.getHost()).put(name, cookie);
} else {
if (cookies.containsKey(uri.toString()))
cookies.get(uri.getHost()).remove(name);
}
// Save cookie into persistent store
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
prefsWriter.putString(uri.getHost(), TextUtils.join(",", cookies.get(uri.getHost()).keySet()));
prefsWriter.putString(COOKIE_NAME_PREFIX + name, encodeCookie(new SerializableHttpCookie(cookie)));
prefsWriter.apply();
}
protected String getCookieToken(URI uri, HttpCookie cookie) {
return cookie.getName() + cookie.getDomain();
}
@Override
public List<HttpCookie> get(URI uri) {
ArrayList<HttpCookie> ret = new ArrayList<>();
if (cookies.containsKey(uri.getHost()))
ret.addAll(cookies.get(uri.getHost()).values());
return ret;
}
@Override
public boolean removeAll() {
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
prefsWriter.clear();
prefsWriter.apply();
cookies.clear();
return true;
}
@Override
public boolean remove(URI uri, HttpCookie cookie) {
String name = getCookieToken(uri, cookie);
if (cookies.containsKey(uri.getHost()) && cookies.get(uri.getHost()).containsKey(name)) {
cookies.get(uri.getHost()).remove(name);
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
if (cookiePrefs.contains(COOKIE_NAME_PREFIX + name)) {
prefsWriter.remove(COOKIE_NAME_PREFIX + name);
}
prefsWriter.putString(uri.getHost(), TextUtils.join(",", cookies.get(uri.getHost()).keySet()));
prefsWriter.apply();
return true;
} else {
return false;
}
}
@Override
public List<HttpCookie> getCookies() {
ArrayList<HttpCookie> ret = new ArrayList<>();
for (String key : cookies.keySet())
ret.addAll(cookies.get(key).values());
return ret;
}
@Override
public List<URI> getURIs() {
ArrayList<URI> ret = new ArrayList<>();
for (String key : cookies.keySet())
try {
ret.add(new URI(key));
} catch (URISyntaxException e) {
e.printStackTrace();
}
return ret;
}
/**
* Serializes Cookie object into String
*
* @param cookie cookie to be encoded, can be null
* @return cookie encoded as String
*/
protected String encodeCookie(SerializableHttpCookie cookie) {
if (cookie == null)
return null;
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
ObjectOutputStream outputStream = new ObjectOutputStream(os);
outputStream.writeObject(cookie);
} catch (IOException e) {
Log.d(LOG_TAG, "IOException in encodeCookie", e);
return null;
}
return byteArrayToHexString(os.toByteArray());
}
/**
* Returns cookie decoded from cookie string
*
* @param cookieString string of cookie as returned from http request
* @return decoded cookie or null if exception occured
*/
protected HttpCookie decodeCookie(String cookieString) {
byte[] bytes = hexStringToByteArray(cookieString);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
HttpCookie cookie = null;
try {
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
cookie = ((SerializableHttpCookie) objectInputStream.readObject()).getCookie();
} catch (IOException e) {
Log.d(LOG_TAG, "IOException in decodeCookie", e);
} catch (ClassNotFoundException e) {
Log.d(LOG_TAG, "ClassNotFoundException in decodeCookie", e);
}
return cookie;
}
/**
* Using some super basic byte array &lt;-&gt; hex conversions so we don't have to rely on any
* large Base64 libraries. Can be overridden if you like!
*
* @param bytes byte array to be converted
* @return string containing hex values
*/
protected String byteArrayToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte element : bytes) {
int v = element & 0xff;
if (v < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(v));
}
return sb.toString().toUpperCase(Locale.US);
}
/**
* Converts hex values from strings to byte arra
*
* @param hexString string of hex-encoded values
* @return decoded byte array
*/
protected byte[] hexStringToByteArray(String hexString) {
int len = hexString.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16));
}
return data;
}
}

View File

@ -0,0 +1,55 @@
package com.sismics.docs.resource.cookie;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.HttpCookie;
public class SerializableHttpCookie implements Serializable {
private static final long serialVersionUID = 6374381323722046732L;
private transient final HttpCookie cookie;
private transient HttpCookie clientCookie;
public SerializableHttpCookie(HttpCookie cookie) {
this.cookie = cookie;
}
public HttpCookie getCookie() {
HttpCookie bestCookie = cookie;
if (clientCookie != null) {
bestCookie = clientCookie;
}
return bestCookie;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeObject(cookie.getName());
out.writeObject(cookie.getValue());
out.writeObject(cookie.getComment());
out.writeObject(cookie.getCommentURL());
out.writeObject(cookie.getDomain());
out.writeLong(cookie.getMaxAge());
out.writeObject(cookie.getPath());
out.writeObject(cookie.getPortlist());
out.writeInt(cookie.getVersion());
out.writeBoolean(cookie.getSecure());
out.writeBoolean(cookie.getDiscard());
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
String name = (String) in.readObject();
String value = (String) in.readObject();
clientCookie = new HttpCookie(name, value);
clientCookie.setComment((String) in.readObject());
clientCookie.setCommentURL((String) in.readObject());
clientCookie.setDomain((String) in.readObject());
clientCookie.setMaxAge(in.readLong());
clientCookie.setPath((String) in.readObject());
clientCookie.setPortlist((String) in.readObject());
clientCookie.setVersion(in.readInt());
clientCookie.setSecure(in.readBoolean());
clientCookie.setDiscard(in.readBoolean());
}
}

View File

@ -15,12 +15,12 @@ import com.sismics.docs.event.FileAddEvent;
import com.sismics.docs.listener.JsonHttpResponseHandler; import com.sismics.docs.listener.JsonHttpResponseHandler;
import com.sismics.docs.resource.FileResource; import com.sismics.docs.resource.FileResource;
import org.apache.http.Header;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import cz.msebera.android.httpclient.Header;
import de.greenrobot.event.EventBus; import de.greenrobot.event.EventBus;
/** /**

View File

@ -0,0 +1,191 @@
package com.sismics.docs.util;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import com.jakewharton.picasso.OkHttp3Downloader;
import com.sismics.docs.resource.cookie.PersistentCookieStore;
import com.squareup.picasso.Picasso;
import java.io.IOException;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.HttpCookie;
import java.net.URI;
import java.security.cert.CertificateException;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import okhttp3.Cache;
import okhttp3.Interceptor;
import okhttp3.JavaNetCookieJar;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
/**
* Utilities for OkHttp.
*
* @author bgamard.
*/
public class OkHttpUtil {
/**
* OkHttp singleton client.
*/
private static OkHttpClient okHttpClient = new OkHttpClient();
/**
* Singleton cache.
*/
private static Cache cache = null;
/**
* User-Agent to use.
*/
protected static String userAgent = null;
/**
* Accept-Language header.
*/
protected static String acceptLanguage = null;
static {
// OkHttp configuration
try {
// Create a trust manager that does not validate certificate chains
final TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
}
};
// Install the all-trusting trust manager
final SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
final javax.net.ssl.SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
// Configure OkHttpClient
okHttpClient = okHttpClient.newBuilder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.sslSocketFactory(sslSocketFactory)
.build();
} catch (Exception e) {
// NOP
}
}
/**
* Build a Picasso object with base config.
*
* @param context Context
* @return Picasso object
*/
public static Picasso picasso(Context context) {
OkHttpClient okHttpClient = buildClient(context)
.newBuilder()
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Interceptor.Chain chain) throws IOException { // Override cache configuration
final Request original = chain.request();
final Request.Builder requestBuilder = original.newBuilder()
.header("Cache-Control", "max-age=" + (3600 * 24 * 365))
.method(original.method(), original.body());
return chain.proceed(requestBuilder.build());
}
})
.cache(getCache(context))
.build();
Picasso picasso = new Picasso.Builder(context)
.downloader(new OkHttp3Downloader(okHttpClient))
.build();
picasso.setIndicatorsEnabled(false); // Debug stuff
return picasso;
}
/**
* Get and eventually build the singleton cache.
*
* @param context Context
* @return Cache
*/
private static Cache getCache(Context context) {
if (cache == null) {
cache = new Cache(context.getCacheDir(),
PreferenceUtil.getIntegerPreference(context, PreferenceUtil.PREF_CACHE_SIZE, 0) * 1000000);
}
return cache;
}
/**
* Clear the HTTP cache.
*
* @param context Context
*/
public static void clearCache(Context context) {
Cache cache = getCache(context);
try {
cache.evictAll();
} catch (IOException e) {
Log.e("OKHttpUtil", "Error clearing cache", e);
}
}
/**
* Build an OkHttpClient.
*
* @param context Context
* @return OkHttpClient
*/
public static OkHttpClient buildClient(final Context context) {
// One-time header computation
if (userAgent == null) {
userAgent = "Sismics Docs Android " + ApplicationUtil.getVersionName(context) + "/Android " + Build.VERSION.RELEASE + "/" + Build.MODEL;
}
if (acceptLanguage == null) {
Locale locale = Locale.getDefault();
acceptLanguage = locale.getLanguage() + "_" + locale.getCountry();
}
// Cookie handling
PersistentCookieStore cookieStore = new PersistentCookieStore(context);
CookieManager cookieManager = new CookieManager(cookieStore, CookiePolicy.ACCEPT_ALL);
cookieStore.add(URI.create(PreferenceUtil.getServerUrl(context)),
new HttpCookie("auth_token", PreferenceUtil.getAuthToken(context))); // TODO Remove me when async http is ditched
// Runtime configuration
return okHttpClient.newBuilder()
.cookieJar(new JavaNetCookieJar(cookieManager))
.addNetworkInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
return chain.proceed(originalRequest.newBuilder()
.header("User-Agent", userAgent)
.header("Accept-Language", acceptLanguage)
// TODO necessary?? .method(originalRequest.method(), originalRequest.body())
.build());
}
})
.build();
}
}

View File

@ -7,11 +7,12 @@ import android.preference.PreferenceManager;
import com.loopj.android.http.PersistentCookieStore; import com.loopj.android.http.PersistentCookieStore;
import org.apache.http.cookie.Cookie;
import org.json.JSONObject; import org.json.JSONObject;
import java.util.List; import java.util.List;
import cz.msebera.android.httpclient.cookie.Cookie;
/** /**
* Utility class on preferences. * Utility class on preferences.
* *

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="12dp">
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Export metadata"
android:id="@+id/checkBox" />
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Export comments"
android:id="@+id/checkBox2" />
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fit image to page"
android:id="@+id/checkBox3" />
<SeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/seekBar" />
</LinearLayout>

View File

@ -207,6 +207,17 @@
android:orientation="horizontal" android:orientation="horizontal"
style="?android:buttonBarStyle"> style="?android:buttonBarStyle">
<Button
android:id="@+id/actionExportPdf"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="@drawable/ic_description_grey600_24dp"
style="?android:buttonBarButtonStyle"
android:text="@string/export_pdf"
android:textColor="@color/button_material_dark"
android:textAllCaps="false"
android:layout_margin="8dp"/>
<Button <Button
android:id="@+id/actionSharing" android:id="@+id/actionSharing"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -9,7 +9,7 @@
android:layout_width="200dp" android:layout_width="200dp"
android:layout_height="15dip" android:layout_height="15dip"
android:id="@+id/fileProgressBar" android:id="@+id/fileProgressBar"
android:indeterminate="false" android:indeterminate="true"
android:layout_centerInParent="true"/> android:layout_centerInParent="true"/>
<it.sephiroth.android.library.imagezoom.ImageViewTouch <it.sephiroth.android.library.imagezoom.ImageViewTouch

View File

@ -117,6 +117,8 @@
<string name="comment_delete">Delete comment</string> <string name="comment_delete">Delete comment</string>
<string name="deleting_comment">Deleting comment</string> <string name="deleting_comment">Deleting comment</string>
<string name="error_deleting_comment">Error deleting comment</string> <string name="error_deleting_comment">Error deleting comment</string>
<string name="export_pdf">Export PDF</string>
<string name="download">Download</string>
</resources> </resources>

View File

@ -1,6 +1,6 @@
#Mon Nov 23 20:12:30 CET 2015 #Sat Jan 16 19:15:13 CET 2016
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip

View File

@ -113,11 +113,6 @@
<artifactId>bcprov-jdk15on</artifactId> <artifactId>bcprov-jdk15on</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.levigo.jbig2</groupId>
<artifactId>levigo-jbig2-imageio</artifactId>
</dependency>
<dependency> <dependency>
<groupId>fr.opensagres.xdocreport</groupId> <groupId>fr.opensagres.xdocreport</groupId>
<artifactId>org.odftoolkit.odfdom.converter.pdf</artifactId> <artifactId>org.odftoolkit.odfdom.converter.pdf</artifactId>
@ -128,15 +123,25 @@
<artifactId>org.apache.poi.xwpf.converter.pdf</artifactId> <artifactId>org.apache.poi.xwpf.converter.pdf</artifactId>
</dependency> </dependency>
<!-- OCR dependencies -->
<dependency> <dependency>
<groupId>jna</groupId> <groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId> <artifactId>jna</artifactId>
</dependency> </dependency>
<!-- ImageIO plugins -->
<dependency> <dependency>
<groupId>jai</groupId> <groupId>com.levigo.jbig2</groupId>
<artifactId>imageio</artifactId> <artifactId>levigo-jbig2-imageio</artifactId>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId>
</dependency>
<dependency>
<groupId>com.github.jai-imageio</groupId>
<artifactId>jai-imageio-core</artifactId>
</dependency> </dependency>
<!-- Test dependencies --> <!-- Test dependencies -->

View File

@ -38,9 +38,9 @@ import javax.imageio.stream.ImageOutputStream;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import com.sun.media.imageio.plugins.tiff.TIFFImageWriteParam; import com.github.jaiimageio.impl.plugins.tiff.TIFFImageReaderSpi;
import com.sun.media.imageioimpl.plugins.tiff.TIFFImageReaderSpi; import com.github.jaiimageio.impl.plugins.tiff.TIFFImageWriterSpi;
import com.sun.media.imageioimpl.plugins.tiff.TIFFImageWriterSpi; import com.github.jaiimageio.plugins.tiff.TIFFImageWriteParam;
public class ImageIOHelper { public class ImageIOHelper {
@ -51,26 +51,26 @@ public class ImageIOHelper {
* Gets pixel data of an * Gets pixel data of an
* <code>IIOImage</code> object. * <code>IIOImage</code> object.
* *
* @param image an * @param oimage an
* <code>IIOImage</code> object * <code>IIOImage</code> object
* @return a byte buffer of pixel data * @return a byte buffer of pixel data
* @throws Exception * @throws Exception
*/ */
public static ByteBuffer getImageByteBuffer(IIOImage image) throws IOException { public static ByteBuffer getImageByteBuffer(BufferedImage oimage) throws IOException {
//Set up the writeParam // Get tif writer and set output to file
TIFFImageWriteParam tiffWriteParam = new TIFFImageWriteParam(Locale.US);
tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED);
//Get tif writer and set output to file
ImageWriter writer = new TIFFImageWriterSpi().createWriterInstance(); ImageWriter writer = new TIFFImageWriterSpi().createWriterInstance();
//Get the stream metadata // Set up the writeParam
IIOMetadata streamMetadata = writer.getDefaultStreamMetadata(tiffWriteParam); // We are using the old JAI ImageIO plugin, because for some reason, OCR don't work with TwelveMonkeys' plugin
ImageWriteParam tiffWriteParam = new TIFFImageWriteParam(Locale.US);
tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED);
// Get the stream metadata
IIOMetadata streamMetadata = writer.getDefaultStreamMetadata(tiffWriteParam);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageOutputStream ios = ImageIO.createImageOutputStream(outputStream); ImageOutputStream ios = ImageIO.createImageOutputStream(outputStream);
writer.setOutput(ios); writer.setOutput(ios);
writer.write(streamMetadata, new IIOImage(image.getRenderedImage(), null, null), tiffWriteParam); writer.write(streamMetadata, new IIOImage(oimage, null, null), tiffWriteParam);
writer.dispose(); writer.dispose();
// Read the writed image // Read the writed image

View File

@ -17,7 +17,6 @@ package com.sismics.tess4j;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
@ -25,8 +24,6 @@ import java.util.Enumeration;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
import javax.imageio.IIOImage;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
/** /**
@ -169,9 +166,8 @@ public class Tesseract {
* @throws TesseractException * @throws TesseractException
*/ */
public String doOCR(BufferedImage bi, Rectangle rect) throws TesseractException { public String doOCR(BufferedImage bi, Rectangle rect) throws TesseractException {
IIOImage oimage = new IIOImage(bi, null, null); List<BufferedImage> imageList = new ArrayList<BufferedImage>();
List<IIOImage> imageList = new ArrayList<IIOImage>(); imageList.add(bi);
imageList.add(oimage);
return doOCR(imageList, rect); return doOCR(imageList, rect);
} }
@ -179,23 +175,22 @@ public class Tesseract {
* Performs OCR operation. * Performs OCR operation.
* *
* @param imageList a list of * @param imageList a list of
* <code>IIOImage</code> objects * <code>BufferedImage</code> objects
* @param rect the bounding rectangle defines the region of the image to be * @param rect the bounding rectangle defines the region of the image to be
* recognized. A rectangle of zero dimension or * recognized. A rectangle of zero dimension or
* <code>null</code> indicates the whole image. * <code>null</code> indicates the whole image.
* @return the recognized text * @return the recognized text
* @throws TesseractException * @throws TesseractException
*/ */
public String doOCR(List<IIOImage> imageList, Rectangle rect) throws TesseractException { public String doOCR(List<BufferedImage> imageList, Rectangle rect) throws TesseractException {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
pageNum = 0; pageNum = 0;
for (IIOImage oimage : imageList) { for (BufferedImage oimage : imageList) {
pageNum++; pageNum++;
try { try {
ByteBuffer buf = ImageIOHelper.getImageByteBuffer(oimage); ByteBuffer buf = ImageIOHelper.getImageByteBuffer(oimage);
RenderedImage ri = oimage.getRenderedImage(); String pageText = doOCR(oimage.getWidth(), oimage.getHeight(), buf, rect, oimage.getColorModel().getPixelSize());
String pageText = doOCR(ri.getWidth(), ri.getHeight(), buf, rect, ri.getColorModel().getPixelSize());
sb.append(pageText); sb.append(pageText);
} catch (IOException ioe) { } catch (IOException ioe) {
//skip the problematic image //skip the problematic image

Binary file not shown.

Binary file not shown.

View File

@ -35,8 +35,11 @@
<joda-time.joda-time.version>2.9.1</joda-time.joda-time.version> <joda-time.joda-time.version>2.9.1</joda-time.joda-time.version>
<org.hibernate.hibernate.version>4.1.0.Final</org.hibernate.hibernate.version> <org.hibernate.hibernate.version>4.1.0.Final</org.hibernate.hibernate.version>
<javax.servlet.javax.servlet-api.version>3.1.0</javax.servlet.javax.servlet-api.version> <javax.servlet.javax.servlet-api.version>3.1.0</javax.servlet.javax.servlet-api.version>
<com.levigo.jbig2.levigo-jbig2-imageio.version>1.6.3</com.levigo.jbig2.levigo-jbig2-imageio.version>
<fr.opensagres.xdocreport.version>1.0.5</fr.opensagres.xdocreport.version> <fr.opensagres.xdocreport.version>1.0.5</fr.opensagres.xdocreport.version>
<net.java.dev.jna.jna.version>4.2.1</net.java.dev.jna.jna.version>
<com.twelvemonkeys.imageio.version>3.2.1</com.twelvemonkeys.imageio.version>
<com.levigo.jbig2.levigo-jbig2-imageio.version>1.6.5</com.levigo.jbig2.levigo-jbig2-imageio.version>
<com.github.jai-imageio.jai-imageio-core.version>1.3.1</com.github.jai-imageio.jai-imageio-core.version>
<org.eclipse.jetty.jetty-server.version>9.2.13.v20150730</org.eclipse.jetty.jetty-server.version> <org.eclipse.jetty.jetty-server.version>9.2.13.v20150730</org.eclipse.jetty.jetty-server.version>
<org.eclipse.jetty.jetty-webapp.version>9.2.13.v20150730</org.eclipse.jetty.jetty-webapp.version> <org.eclipse.jetty.jetty-webapp.version>9.2.13.v20150730</org.eclipse.jetty.jetty-webapp.version>
@ -69,15 +72,8 @@
<enabled>true</enabled> <enabled>true</enabled>
</snapshots> </snapshots>
</repository> </repository>
<repository>
<id>jbig2.googlecode</id>
<name>JBIG2 ImageIO-Plugin repository at googlecode.com</name>
<url>http://jbig2-imageio.googlecode.com/svn/maven-repository</url>
</repository>
</repositories> </repositories>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
@ -380,79 +376,39 @@
<version>${fr.opensagres.xdocreport.version}</version> <version>${fr.opensagres.xdocreport.version}</version>
</dependency> </dependency>
<!-- Used to read JBIG2 images. See https://github.com/sismics/docs/issues/38 --> <dependency> <!-- Servlet listener to register SPI ImageIO plugins -->
<groupId>com.twelvemonkeys.servlet</groupId>
<artifactId>servlet</artifactId>
<version>${com.twelvemonkeys.imageio.version}</version>
</dependency>
<!-- JNA for Tesseract -->
<dependency> <dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>${net.java.dev.jna.jna.version}</version>
</dependency>
<!-- ImageIO plugins -->
<dependency> <!-- Permissive JPEG plugin -->
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId>
<version>${com.twelvemonkeys.imageio.version}</version>
</dependency>
<dependency><!-- Only JBIG2 -->
<groupId>com.levigo.jbig2</groupId> <groupId>com.levigo.jbig2</groupId>
<artifactId>levigo-jbig2-imageio</artifactId> <artifactId>levigo-jbig2-imageio</artifactId>
<version>${com.levigo.jbig2.levigo-jbig2-imageio.version}</version> <version>${com.levigo.jbig2.levigo-jbig2-imageio.version}</version>
</dependency> </dependency>
<!-- OCR dependencies --> <dependency><!-- Essentially TIFF (for OCR) -->
<dependency> <groupId>com.github.jai-imageio</groupId>
<groupId>jna</groupId> <artifactId>jai-imageio-core</artifactId>
<artifactId>jna</artifactId> <version>${com.github.jai-imageio.jai-imageio-core.version}</version>
<version>1.0</version>
</dependency>
<dependency>
<groupId>jai</groupId>
<artifactId>imageio</artifactId>
<version>1.0</version>
</dependency> </dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
<profiles>
<profile>
<id>init</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>2.3.1</version>
<executions>
<execution>
<id>install-jna</id>
<phase>validate</phase>
<configuration>
<file>${project.basedir}/lib/jna.jar</file>
<repositoryLayout>default</repositoryLayout>
<groupId>jna</groupId>
<artifactId>jna</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<generatePom>true</generatePom>
</configuration>
<goals>
<goal>install-file</goal>
</goals>
</execution>
<execution>
<id>install-jai-imageio</id>
<phase>validate</phase>
<configuration>
<file>${project.basedir}/lib/jai_imageio.jar</file>
<repositoryLayout>default</repositoryLayout>
<groupId>jai</groupId>
<artifactId>imageio</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<generatePom>true</generatePom>
</configuration>
<goals>
<goal>install-file</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project> </project>

View File

@ -84,6 +84,11 @@
<artifactId>h2</artifactId> <artifactId>h2</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.twelvemonkeys.servlet</groupId>
<artifactId>servlet</artifactId>
</dependency>
<!-- Test dependencies --> <!-- Test dependencies -->
<dependency> <dependency>
<groupId>com.sismics.docs</groupId> <groupId>com.sismics.docs</groupId>

View File

@ -7,6 +7,12 @@
metadata-complete="true"> metadata-complete="true">
<display-name>Docs</display-name> <display-name>Docs</display-name>
<!-- Proper loader/unloader of ImageIO plugins -->
<listener>
<display-name>ImageIO service provider loader/unloader</display-name>
<listener-class>com.twelvemonkeys.servlet.image.IIOProviderContextListener</listener-class>
</listener>
<!-- This filter is used to process a couple things in the request context --> <!-- This filter is used to process a couple things in the request context -->
<filter> <filter>
<filter-name>requestContextFilter</filter-name> <filter-name>requestContextFilter</filter-name>

View File

@ -3,7 +3,7 @@
/** /**
* Share controller. * Share controller.
*/ */
angular.module('share').controller('Share', function($scope, $state, $stateParams, Restangular) { angular.module('share').controller('Share', function($scope, $state, $stateParams, Restangular, $modal) {
// Load document // Load document
Restangular.one('document', $stateParams.documentId).get({ share: $stateParams.shareId }) Restangular.one('document', $stateParams.documentId).get({ share: $stateParams.shareId })
.then(function (data) { .then(function (data) {
@ -33,4 +33,16 @@ angular.module('share').controller('Share', function($scope, $state, $stateParam
$scope.openFile = function (file) { $scope.openFile = function (file) {
$state.go('share.file', { documentId: $stateParams.documentId, shareId: $stateParams.shareId, fileId: file.id }) $state.go('share.file', { documentId: $stateParams.documentId, shareId: $stateParams.shareId, fileId: file.id })
}; };
/**
* Export the current document to PDF.
*/
$scope.exportPdf = function() {
$modal.open({
templateUrl: 'partial/share/share.pdf.html',
controller: 'ShareModalPdf'
});
return false;
};
}); });

View File

@ -0,0 +1,30 @@
'use strict';
/**
* Document modal PDF controller.
*/
angular.module('share').controller('ShareModalPdf', function ($scope, $window, $stateParams, $modalInstance) {
$scope.export = {
metadata: false,
comments: false,
fitimagetopage: true,
margin: 10
};
// Export to PDF
$scope.exportPdf = function() {
$window.open('../api/document/' + $stateParams.documentId
+ '/pdf?metadata=' + $scope.export.metadata
+ '&comments=' + $scope.export.comments
+ '&fitimagetopage=' + $scope.export.fitimagetopage
+ '&margin=' + $scope.export.margin
+ '&share=' + $stateParams.shareId);
$modalInstance.close();
};
// Close the modal
$scope.close = function () {
$modalInstance.close();
}
});

View File

@ -1,11 +1,32 @@
<div class="row"> <div class="row">
<div class="col-md-10"> <div class="col-md-10">
<div class="text-right">
<div class="btn-group dropdown" dropdown>
<button class="btn btn-default" dropdown-toggle>
<span class="glyphicon glyphicon-export"></span>
Export
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li>
<a ng-href="../api/file/zip?id={{ document.id }}&share={{ $stateParams.shareId }}" title="Download all files">
<span class="glyphicon glyphicon glyphicon-compressed"></span>
Download files
</a>
</li>
<li>
<a ng-click="exportPdf()" title="Export document to PDF" class="pointer">
<span class="glyphicon glyphicon glyphicon-save-file"></span>
Export to PDF
</a>
</li>
</ul>
</div>
</div>
<div class="page-header"> <div class="page-header">
<h1> <h1>
{{ document.title }} <small>{{ document.create_date | date: 'yyyy-MM-dd' }}</small> {{ document.title }} <small>{{ document.create_date | date: 'yyyy-MM-dd' }}</small>
<a ng-href="../api/file/zip?id={{ document.id }}&share={{ $stateParams.shareId }}" class="btn btn-default" title="Download all files">
<span class="glyphicon glyphicon-download-alt"></span>
</a>
</h1> </h1>
<ul class="list-inline"> <ul class="list-inline">
<li ng-repeat="tag in document.tags"><span class="label label-info" ng-style="{ 'background': tag.color }">{{ tag.name }}</span></li> <li ng-repeat="tag in document.tags"><span class="label label-info" ng-style="{ 'background': tag.color }">{{ tag.name }}</span></li>

View File

@ -0,0 +1,45 @@
<div class="modal-header">
<h3>Export to PDF</h3>
</div>
<div class="modal-body">
<form class="form-horizontal">
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="export.metadata" /> Export metadata
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="export.comments" /> Export comments
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="export.fitimagetopage" /> Fit image to page
</label>
</div>
</div>
</div>
<label for="inputMargin" class="col-sm-2 control-label">Margin</label>
<div class="input-group col-sm-5">
<input type="number" class="form-control" id="inputMargin" ng-model="export.margin" min="0" max="100" step="1">
<div class="input-group-addon">mm</div>
</div>
</form>
</div>
<div class="modal-footer">
<button ng-click="exportPdf()" class="btn btn-primary">
<span class="glyphicon glyphicon-save-file"></span> Export
</button>
<button ng-click="close()" class="btn btn-default">Cancel</button>
</div>

View File

@ -34,6 +34,7 @@
<script src="app/share/app.js" type="text/javascript"></script> <script src="app/share/app.js" type="text/javascript"></script>
<script src="app/share/controller/Main.js" type="text/javascript"></script> <script src="app/share/controller/Main.js" type="text/javascript"></script>
<script src="app/share/controller/Share.js" type="text/javascript"></script> <script src="app/share/controller/Share.js" type="text/javascript"></script>
<script src="app/share/controller/ShareModalPdf.js" type="text/javascript"></script>
<script src="app/share/controller/FileView.js" type="text/javascript"></script> <script src="app/share/controller/FileView.js" type="text/javascript"></script>
<script src="app/share/controller/FileModalView.js" type="text/javascript"></script> <script src="app/share/controller/FileModalView.js" type="text/javascript"></script>
<script src="app/share/filter/Newline.js" type="text/javascript"></script> <script src="app/share/filter/Newline.js" type="text/javascript"></script>