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:
mvn -Pinit validate -N
mvn clean -DskipTests install
#### Run a stand-alone version

View File

@ -12,10 +12,7 @@
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<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>
<task>generateDebugAndroidTestSources</task>
<task>generateDebugSources</task>
</afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
@ -28,7 +25,7 @@
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
<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 />
<content url="file://$MODULE_DIR$">
<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/jni" 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/resources" 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/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/apk" />
<excludeFolder url="file://$MODULE_DIR$/build/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/dex" />
<excludeFolder url="file://$MODULE_DIR$/build/incremental" />
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<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/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/builds" />
<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/dex" />
<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/22.2.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.android.support/appcompat-v7/23.1.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/support-v4/23.1.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/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/incremental" />
<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-runtimfe-classes" />
<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/jniLibs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
<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/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/rs" />
<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/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/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" />
</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="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="imagezoom-1.0.5" level="project" />
<orderEntry type="library" exported="" name="support-v4-22.2.1" level="project" />
<orderEntry type="library" exported="" name="recyclerview-v7-22.2.1" 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="android-async-http-1.4.9" level="project" />
<orderEntry type="library" exported="" name="picasso-2.5.2" 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>
</module>

View File

@ -3,7 +3,7 @@ buildscript {
jcenter()
}
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'
@ -13,12 +13,12 @@ repositories {
}
android {
compileSdkVersion 22
compileSdkVersion 23
buildToolsVersion '23.0.2'
defaultConfig {
minSdkVersion 14
targetSdkVersion 22
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
@ -50,10 +50,14 @@ android {
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
compile 'com.android.support:appcompat-v7:22.+'
compile 'com.android.support:recyclerview-v7:22.+'
compile 'com.loopj.android:android-async-http:1.4.6'
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:recyclerview-v7:23.1.1'
compile 'com.loopj.android:android-async-http:1.4.9'
compile 'it.sephiroth.android.library.imagezoom:imagezoom:1.0.5'
compile 'de.greenrobot:eventbus:2.4.1'
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 com.androidquery.callback.BitmapAjaxCallback;
import com.sismics.docs.model.application.ApplicationContext;
import com.sismics.docs.util.PreferenceUtil;
@ -24,10 +23,4 @@ public class MainApplication extends Application {
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.event.DocumentAddEvent;
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.ui.form.Validator;
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.util.PreferenceUtil;
import org.apache.http.Header;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@ -174,9 +173,9 @@ public class DocumentEditActivity extends AppCompatActivity {
});
// Server callback
JsonHttpResponseHandler callback = new JsonHttpResponseHandler() {
HttpCallback callback = new HttpCallback() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
public void onSuccess(JSONObject response) {
// Build a fake document JSON to update the UI
final JSONObject outputDoc = new JSONObject();
try {
@ -211,7 +210,7 @@ public class DocumentEditActivity extends AppCompatActivity {
}
@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();
}

View File

@ -44,7 +44,9 @@ import com.sismics.docs.event.DocumentEditEvent;
import com.sismics.docs.event.DocumentFullscreenEvent;
import com.sismics.docs.event.FileAddEvent;
import com.sismics.docs.event.FileDeleteEvent;
import com.sismics.docs.fragment.DocExportPdfFragment;
import com.sismics.docs.fragment.DocShareFragment;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.listener.JsonHttpResponseHandler;
import com.sismics.docs.model.application.ApplicationContext;
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.TagUtil;
import org.apache.http.Header;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@ -63,6 +64,7 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import cz.msebera.android.httpclient.Header;
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
button = (Button) findViewById(R.id.actionSharing);
button.setOnClickListener(new View.OnClickListener() {
@ -476,14 +488,14 @@ public class DocumentViewActivity extends AppCompatActivity {
// Actual delete server call
final String documentId = document.optString("id");
DocumentResource.delete(DocumentViewActivity.this, documentId, new JsonHttpResponseHandler() {
DocumentResource.delete(DocumentViewActivity.this, documentId, new HttpCallback() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
public void onSuccess(JSONObject response) {
EventBus.getDefault().post(new DocumentDeleteEvent(documentId));
}
@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();
}
@ -635,9 +647,9 @@ public class DocumentViewActivity extends AppCompatActivity {
// 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,
// 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
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
public void onSuccess(JSONObject response) {
document = response;
boolean writable = document.optBoolean("writable");
@ -720,7 +732,7 @@ public class DocumentViewActivity extends AppCompatActivity {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
JSONArray comments = response.optJSONArray("comments");
commentListAdapter = new CommentListAdapter(comments);
commentListAdapter = new CommentListAdapter(DocumentViewActivity.this, comments);
listView.setAdapter(commentListAdapter);
listView.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);

View File

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

View File

@ -18,7 +18,6 @@ import android.widget.ListView;
import android.widget.SearchView;
import android.widget.TextView;
import com.androidquery.util.AQUtility;
import com.sismics.docs.R;
import com.sismics.docs.adapter.TagListAdapter;
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.util.PreferenceUtil;
import org.apache.http.Header;
import org.json.JSONObject;
import cz.msebera.android.httpclient.Header;
import de.greenrobot.event.EventBus;
/**
@ -274,10 +273,6 @@ public class MainActivity extends AppCompatActivity {
@Override
protected void onDestroy() {
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();
}
}

View File

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

View File

@ -7,10 +7,11 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import com.androidquery.AQuery;
import com.androidquery.callback.BitmapAjaxCallback;
import com.sismics.docs.R;
import com.sismics.docs.util.OkHttpUtil;
import com.sismics.docs.util.PreferenceUtil;
import com.squareup.picasso.Callback;
import com.squareup.picasso.MemoryPolicy;
import org.json.JSONArray;
import org.json.JSONObject;
@ -30,11 +31,6 @@ public class FilePagerAdapter extends PagerAdapter {
*/
private List<JSONObject> files;
/**
* AQuery.
*/
private AQuery aq;
/**
* Context.
*/
@ -58,7 +54,6 @@ public class FilePagerAdapter extends PagerAdapter {
}
this.context = context;
this.authToken = PreferenceUtil.getAuthToken(context);
aq = new AQuery(context);
}
@Override
@ -66,15 +61,20 @@ public class FilePagerAdapter extends PagerAdapter {
View view = LayoutInflater.from(container.getContext()).inflate(R.layout.file_viewpager_item, container, false);
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);
String fileUrl = PreferenceUtil.getServerUrl(context) + "/api/file/" + file.optString("id") + "/data?size=web";
aq.id(fileImageView)
.image(new BitmapAjaxCallback()
.url(fileUrl)
.progress(progressBar)
.animation(AQuery.FADE_IN_NETWORK)
.cookie("auth_token", authToken));
// Load image
OkHttpUtil.picasso(context)
.load(fileUrl)
.memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE) // Don't memory cache the images
.into(fileImageView, new Callback.EmptyCallback() {
@Override
public void onSuccess() {
progressBar.setVisibility(View.GONE);
}
});
fileImageView.setDisplayType(ImageViewTouchBase.DisplayType.FIT_TO_SCREEN);
@ -109,7 +109,7 @@ public class FilePagerAdapter extends PagerAdapter {
* @return Object
*/
public JSONObject getObjectAt(int position) {
if (files == null) {
if (files == null || position < 0 || position >= files.size()) {
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.DocumentEditEvent;
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.resource.DocumentResource;
import com.sismics.docs.ui.view.DividerItemDecoration;
import com.sismics.docs.ui.view.EmptyRecyclerView;
import org.apache.http.Header;
import org.json.JSONObject;
import de.greenrobot.event.EventBus;
@ -218,16 +217,16 @@ public class DocListFragment extends Fragment {
recyclerView.setEmptyView(progressBar);
DocumentResource.list(getActivity(), reset ? 0 : adapter.getItemCount(), query, new JsonHttpResponseHandler() {
DocumentResource.list(getActivity(), reset ? 0 : adapter.getItemCount(), query, new HttpCallback() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
public void onSuccess(JSONObject response) {
adapter.addDocuments(response.optJSONArray("documents"));
documentsEmptyView.setText(R.string.no_documents);
recyclerView.setEmptyView(documentsEmptyView);
}
@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);
recyclerView.setEmptyView(documentsEmptyView);

View File

@ -21,15 +21,16 @@ import com.sismics.docs.R;
import com.sismics.docs.adapter.ShareListAdapter;
import com.sismics.docs.event.ShareDeleteEvent;
import com.sismics.docs.event.ShareSendEvent;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.listener.JsonHttpResponseHandler;
import com.sismics.docs.resource.DocumentResource;
import com.sismics.docs.resource.ShareResource;
import com.sismics.docs.util.PreferenceUtil;
import org.apache.http.Header;
import org.json.JSONArray;
import org.json.JSONObject;
import cz.msebera.android.httpclient.Header;
import de.greenrobot.event.EventBus;
/**
@ -44,7 +45,8 @@ public class DocShareFragment extends DialogFragment {
private JSONObject document;
/**
* Document sharing dialog fragment
* Document sharing dialog fragment.
*
* @param id Document 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);
shareListView.setEmptyView(shareProgressBar);
DocumentResource.get(getActivity(), getArguments().getString("id"), new JsonHttpResponseHandler() {
DocumentResource.get(getActivity(), getArguments().getString("id"), new HttpCallback() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
public void onSuccess(JSONObject response) {
document = response;
JSONArray acls = response.optJSONArray("acls");
shareProgressBar.setVisibility(View.GONE);
@ -132,7 +134,7 @@ public class DocShareFragment extends DialogFragment {
}
@Override
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
public void onFailure(JSONObject json, Exception e) {
getDialog().cancel();
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.widget.Toast;
import com.androidquery.util.AQUtility;
import com.sismics.docs.R;
import com.sismics.docs.provider.RecentSuggestionsProvider;
import com.sismics.docs.util.ApplicationUtil;
import com.sismics.docs.util.OkHttpUtil;
import com.sismics.docs.util.PreferenceUtil;
/**
@ -52,7 +52,7 @@ public class SettingsFragment extends PreferenceFragment implements SharedPrefer
clearCachePref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
AQUtility.cleanCacheAsync(getActivity());
OkHttpUtil.clearCache(getActivity());
Toast.makeText(getActivity(), R.string.pref_clear_cache_success, Toast.LENGTH_LONG).show();
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 org.apache.http.Header;
import org.apache.http.HttpStatus;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
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
* 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,
* org.apache.http.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.JSONArray)} or {@link #onSuccess(int,
* 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.
*/
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.util.PreferenceUtil;
import org.apache.http.Header;
import org.json.JSONObject;
import cz.msebera.android.httpclient.Header;
/**
* Global context of the application.
*

View File

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

View File

@ -2,11 +2,15 @@ package com.sismics.docs.resource;
import android.content.Context;
import com.loopj.android.http.RequestParams;
import com.sismics.docs.listener.JsonHttpResponseHandler;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.util.OkHttpUtil;
import java.util.Set;
import okhttp3.FormBody;
import okhttp3.HttpUrl;
import okhttp3.Request;
/**
* Access to /document API.
*
@ -19,18 +23,23 @@ public class DocumentResource extends BaseResource {
* @param context Context
* @param offset Offset
* @param query Search query
* @param responseHandler Callback
* @param callback Callback
*/
public static void list(Context context, int offset, String query, JsonHttpResponseHandler responseHandler) {
init(context);
RequestParams params = new RequestParams();
params.put("limit", 20);
params.put("offset", offset);
params.put("sort_column", 3);
params.put("asc", false);
params.put("search", query);
client.get(getApiUrl(context) + "/document/list", params, responseHandler);
public static void list(Context context, int offset, String query, HttpCallback callback) {
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/document/list")
.newBuilder()
.addQueryParameter("limit", "20")
.addQueryParameter("offset", Integer.toString(offset))
.addQueryParameter("sort_column", "3")
.addQueryParameter("asc", "false")
.addQueryParameter("search", query)
.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 id ID
* @param responseHandler Callback
* @param callback Callback
*/
public static void get(Context context, String id, JsonHttpResponseHandler responseHandler) {
init(context);
client.get(getApiUrl(context) + "/document/" + id, responseHandler);
public static void get(Context context, String id, HttpCallback callback) {
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/document/" + id))
.get()
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
/**
@ -51,12 +64,16 @@ public class DocumentResource extends BaseResource {
*
* @param context Context
* @param id ID
* @param responseHandler Callback
* @param callback Callback
*/
public static void delete(Context context, String id, JsonHttpResponseHandler responseHandler) {
init(context);
client.delete(getApiUrl(context) + "/document/" + id, responseHandler);
public static void delete(Context context, String id, HttpCallback callback) {
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/document/" + id))
.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 language Language
* @param createDate Create date
* @param responseHandler Callback
* @param callback Callback
*/
public static void add(Context context, String title, String description,
Set<String> tagIdList, String language, long createDate, JsonHttpResponseHandler responseHandler) {
init(context);
Set<String> tagIdList, String language, long createDate, HttpCallback callback) {
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();
params.put("title", title);
params.put("description", description);
params.put("tags", tagIdList);
params.put("language", language);
params.put("create_date", createDate);
client.put(getApiUrl(context) + "/document", params, responseHandler);
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/document"))
.put(formBuilder.build())
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
/**
@ -93,19 +118,27 @@ public class DocumentResource extends BaseResource {
* @param tagIdList Tags ID list
* @param language Language
* @param createDate Create date
* @param responseHandler Callback
* @param callback Callback
*/
public static void edit(Context context, String id, String title, String description,
Set<String> tagIdList, String language, long createDate, JsonHttpResponseHandler responseHandler) {
init(context);
Set<String> tagIdList, String language, long createDate, HttpCallback callback) {
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();
params.put("title", title);
params.put("description", description);
params.put("tags", tagIdList);
params.put("language", language);
params.put("create_date", createDate);
client.post(getApiUrl(context) + "/document/" + id, params, responseHandler);
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/document/" + id))
.post(formBuilder.build())
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.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.resource.FileResource;
import org.apache.http.Header;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import cz.msebera.android.httpclient.Header;
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 org.apache.http.cookie.Cookie;
import org.json.JSONObject;
import java.util.List;
import cz.msebera.android.httpclient.cookie.Cookie;
/**
* 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"
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
android:id="@+id/actionSharing"
android:layout_width="wrap_content"

View File

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

View File

@ -117,6 +117,8 @@
<string name="comment_delete">Delete comment</string>
<string name="deleting_comment">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>

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
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
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>
</dependency>
<dependency>
<groupId>com.levigo.jbig2</groupId>
<artifactId>levigo-jbig2-imageio</artifactId>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>org.odftoolkit.odfdom.converter.pdf</artifactId>
@ -128,15 +123,25 @@
<artifactId>org.apache.poi.xwpf.converter.pdf</artifactId>
</dependency>
<!-- OCR dependencies -->
<dependency>
<groupId>jna</groupId>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
</dependency>
<!-- ImageIO plugins -->
<dependency>
<groupId>jai</groupId>
<artifactId>imageio</artifactId>
<groupId>com.levigo.jbig2</groupId>
<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>
<!-- Test dependencies -->

View File

@ -38,9 +38,9 @@ import javax.imageio.stream.ImageOutputStream;
import org.w3c.dom.NodeList;
import com.sun.media.imageio.plugins.tiff.TIFFImageWriteParam;
import com.sun.media.imageioimpl.plugins.tiff.TIFFImageReaderSpi;
import com.sun.media.imageioimpl.plugins.tiff.TIFFImageWriterSpi;
import com.github.jaiimageio.impl.plugins.tiff.TIFFImageReaderSpi;
import com.github.jaiimageio.impl.plugins.tiff.TIFFImageWriterSpi;
import com.github.jaiimageio.plugins.tiff.TIFFImageWriteParam;
public class ImageIOHelper {
@ -51,26 +51,26 @@ public class ImageIOHelper {
* Gets pixel data of an
* <code>IIOImage</code> object.
*
* @param image an
* @param oimage an
* <code>IIOImage</code> object
* @return a byte buffer of pixel data
* @throws Exception
*/
public static ByteBuffer getImageByteBuffer(IIOImage image) throws IOException {
//Set up the writeParam
TIFFImageWriteParam tiffWriteParam = new TIFFImageWriteParam(Locale.US);
tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED);
public static ByteBuffer getImageByteBuffer(BufferedImage oimage) throws IOException {
// Get tif writer and set output to file
ImageWriter writer = new TIFFImageWriterSpi().createWriterInstance();
// Set up the writeParam
// 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();
ImageOutputStream ios = ImageIO.createImageOutputStream(outputStream);
writer.setOutput(ios);
writer.write(streamMetadata, new IIOImage(image.getRenderedImage(), null, null), tiffWriteParam);
writer.write(streamMetadata, new IIOImage(oimage, null, null), tiffWriteParam);
writer.dispose();
// Read the writed image

View File

@ -17,7 +17,6 @@ package com.sismics.tess4j;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@ -25,8 +24,6 @@ import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import javax.imageio.IIOImage;
import com.sun.jna.Pointer;
/**
@ -169,9 +166,8 @@ public class Tesseract {
* @throws TesseractException
*/
public String doOCR(BufferedImage bi, Rectangle rect) throws TesseractException {
IIOImage oimage = new IIOImage(bi, null, null);
List<IIOImage> imageList = new ArrayList<IIOImage>();
imageList.add(oimage);
List<BufferedImage> imageList = new ArrayList<BufferedImage>();
imageList.add(bi);
return doOCR(imageList, rect);
}
@ -179,23 +175,22 @@ public class Tesseract {
* Performs OCR operation.
*
* @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
* recognized. A rectangle of zero dimension or
* <code>null</code> indicates the whole image.
* @return the recognized text
* @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();
pageNum = 0;
for (IIOImage oimage : imageList) {
for (BufferedImage oimage : imageList) {
pageNum++;
try {
ByteBuffer buf = ImageIOHelper.getImageByteBuffer(oimage);
RenderedImage ri = oimage.getRenderedImage();
String pageText = doOCR(ri.getWidth(), ri.getHeight(), buf, rect, ri.getColorModel().getPixelSize());
String pageText = doOCR(oimage.getWidth(), oimage.getHeight(), buf, rect, oimage.getColorModel().getPixelSize());
sb.append(pageText);
} catch (IOException ioe) {
//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>
<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>
<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>
<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-webapp.version>9.2.13.v20150730</org.eclipse.jetty.jetty-webapp.version>
@ -69,15 +72,8 @@
<enabled>true</enabled>
</snapshots>
</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>
<build>
<plugins>
<plugin>
@ -380,79 +376,39 @@
<version>${fr.opensagres.xdocreport.version}</version>
</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>
<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>
<artifactId>levigo-jbig2-imageio</artifactId>
<version>${com.levigo.jbig2.levigo-jbig2-imageio.version}</version>
</dependency>
<!-- OCR dependencies -->
<dependency>
<groupId>jna</groupId>
<artifactId>jna</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>jai</groupId>
<artifactId>imageio</artifactId>
<version>1.0</version>
<dependency><!-- Essentially TIFF (for OCR) -->
<groupId>com.github.jai-imageio</groupId>
<artifactId>jai-imageio-core</artifactId>
<version>${com.github.jai-imageio.jai-imageio-core.version}</version>
</dependency>
</dependencies>
</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>

View File

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

View File

@ -7,6 +7,12 @@
metadata-complete="true">
<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 -->
<filter>
<filter-name>requestContextFilter</filter-name>

View File

@ -3,7 +3,7 @@
/**
* 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
Restangular.one('document', $stateParams.documentId).get({ share: $stateParams.shareId })
.then(function (data) {
@ -33,4 +33,16 @@ angular.module('share').controller('Share', function($scope, $state, $stateParam
$scope.openFile = function (file) {
$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="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">
<h1>
{{ 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>
<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>

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/controller/Main.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/FileModalView.js" type="text/javascript"></script>
<script src="app/share/filter/Newline.js" type="text/javascript"></script>