Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08e4f6ddae | ||
|
|
86cae53789 | ||
|
|
5cbdb5d87d | ||
|
|
f8d889bb1f | ||
|
|
4625f9e42a | ||
|
|
6add34bb33 | ||
|
|
ea4e3fd8f2 | ||
|
|
b2a38cea62 | ||
|
|
0228d43442 | ||
|
|
060e5e8e24 | ||
|
|
566c563786 | ||
|
|
8597eac9f9 | ||
|
|
b7f920f864 | ||
|
|
1e3282eff3 | ||
|
|
451d913442 | ||
|
|
e3962c4e3f | ||
|
|
25136bc146 | ||
|
|
c727ac3a56 | ||
|
|
52387d93ac | ||
|
|
072dd7b280 | ||
|
|
42320dc9b9 | ||
|
|
ff994ce63b | ||
|
|
82ba0b5761 | ||
|
|
fc1bb22d8d | ||
|
|
6ff639baac | ||
|
|
4c24e7921a | ||
|
|
f7f5f93a9e | ||
|
|
f1eb3795d9 | ||
|
|
1f092f7d93 | ||
|
|
9b4b13a721 | ||
|
|
a1af1f369f | ||
|
|
c283607063 | ||
|
|
b8061a6a1e | ||
|
|
1c4161981b | ||
|
|
5e3093d0d3 | ||
|
|
0d4643cc93 | ||
|
|
80a2e0d055 | ||
|
|
06e97824df | ||
|
|
3461804399 | ||
|
|
bfc70baefb | ||
|
|
aa4b73b730 | ||
|
|
07247854ac | ||
|
|
9b5780bbb0 | ||
|
|
bad42d96f3 | ||
|
|
6ff1570b3c | ||
|
|
24345bc176 | ||
|
|
75cde7e9d6 | ||
|
|
192c2030d3 | ||
|
|
18cedaef2c | ||
|
|
d0c259ead2 | ||
|
|
2347483676 | ||
|
|
6c976087de | ||
|
|
c36014b46f | ||
|
|
5cf0532db7 | ||
|
|
6edae27d26 | ||
|
|
9ae8303b18 | ||
|
|
a0356845b1 | ||
|
|
6fa0b8494e | ||
|
|
c9210c39c4 | ||
|
|
790453047d | ||
|
|
5befef2992 | ||
|
|
dd59172a19 | ||
|
|
17e5c65d04 | ||
|
|
a762ce4715 | ||
|
|
89d66eca4a | ||
|
|
323b95ad7a | ||
|
|
b42b195245 | ||
|
|
a7987386e1 | ||
|
|
a181eac9a5 | ||
|
|
5662c080d6 | ||
|
|
745766a2c3 | ||
|
|
f44d30d890 | ||
|
|
ea0c7982ac | ||
|
|
2abf0c6eab | ||
|
|
13e8b828ac | ||
|
|
551c10e7a3 | ||
|
|
e17abfe411 | ||
|
|
3330acfc75 | ||
|
|
2837b21a86 | ||
|
|
5666d9b8b5 | ||
|
|
824e37b8ea | ||
|
|
1d08508e51 | ||
|
|
a84748f075 | ||
|
|
a6c123ad03 | ||
|
|
407564f28c | ||
|
|
6a9a166670 | ||
|
|
1773998ca0 | ||
|
|
c610364ef7 | ||
|
|
bc719b3165 | ||
|
|
92b2219bed | ||
|
|
8c5c54125f | ||
|
|
2ce5749226 | ||
|
|
76d195a344 | ||
|
|
04752cab0c | ||
|
|
ffa7d796b5 | ||
|
|
5119d32714 | ||
|
|
c0a963e845 | ||
|
|
20c2b0460b | ||
|
|
2ee979c012 | ||
|
|
bc3683990f | ||
|
|
c06e2971bc |
1
.gitignore
vendored
@@ -4,6 +4,7 @@
|
|||||||
/*/bin
|
/*/bin
|
||||||
/*/gen
|
/*/gen
|
||||||
/*/target
|
/*/target
|
||||||
|
/*/build
|
||||||
/*/*.iml
|
/*/*.iml
|
||||||
/out
|
/out
|
||||||
/.idea
|
/.idea
|
||||||
|
|||||||
10
Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
FROM sismics/debian-java7-jetty9
|
||||||
|
MAINTAINER benjamin.gam@gmail.com
|
||||||
|
|
||||||
|
RUN apt-get -y -q install tesseract-ocr tesseract-ocr-fra tesseract-ocr-jpn
|
||||||
|
|
||||||
|
ENV TESSDATA_PREFIX /usr/share/tesseract-ocr
|
||||||
|
ENV LC_NUMERIC C
|
||||||
|
|
||||||
|
ADD docs-web/target/docs-web-*.war /opt/jetty/webapps/docs.war
|
||||||
|
ADD docs.xml /opt/jetty/webapps/docs.xml
|
||||||
29
README.md
@@ -1,7 +1,13 @@
|
|||||||
Sismics Docs
|
Sismics Docs
|
||||||
============
|
============
|
||||||
|
|
||||||

|
_Web interface_
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
_Android application_
|
||||||
|
|
||||||
|
  
|
||||||
|
|
||||||
What is Docs?
|
What is Docs?
|
||||||
---------------
|
---------------
|
||||||
@@ -20,20 +26,21 @@ Features
|
|||||||
- Full text search in image and PDF
|
- Full text search in image and PDF
|
||||||
- SHA-256 encryption
|
- SHA-256 encryption
|
||||||
- Tag system
|
- Tag system
|
||||||
- Multi-users
|
- Multi-users ACL system
|
||||||
- Document sharing
|
- Document sharing by URL
|
||||||
- RESTful Web API
|
- RESTful Web API
|
||||||
|
- Modern Android client
|
||||||
|
|
||||||
License
|
Download
|
||||||
-------
|
--------
|
||||||
|
|
||||||
Docs is released under the terms of the GPL license. See `COPYING` for more
|
The latest release is downloadable here: <https://github.com/sismics/docs/releases> in WAR format.
|
||||||
information or see <http://opensource.org/licenses/GPL-2.0>.
|
You will need a Java webapp server to run it, like [Jetty](http://eclipse.org/jetty/) or [Tomcat](http://tomcat.apache.org/)
|
||||||
|
|
||||||
How to build Docs from the sources
|
How to build Docs from the sources
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
Prerequisites: JDK 7, Maven 3, Tesseract 3.02
|
Prerequisites: JDK 7 with JCE, Maven 3, Tesseract 3.02
|
||||||
|
|
||||||
Docs is organized in several Maven modules:
|
Docs is organized in several Maven modules:
|
||||||
|
|
||||||
@@ -65,3 +72,9 @@ From the `docs-web` directory:
|
|||||||
mvn -Pprod -DskipTests clean install
|
mvn -Pprod -DskipTests clean install
|
||||||
|
|
||||||
You will get your deployable WAR in the `target` directory.
|
You will get your deployable WAR in the `target` directory.
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
Docs is released under the terms of the GPL license. See `COPYING` for more
|
||||||
|
information or see <http://opensource.org/licenses/GPL-2.0>.
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" type="JAVA_MODULE" version="4">
|
<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="docs-android" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
|
||||||
<component name="FacetManager">
|
<component name="FacetManager">
|
||||||
|
<facet type="android-gradle" name="Android-Gradle">
|
||||||
|
<configuration>
|
||||||
|
<option name="GRADLE_PROJECT_PATH" value=":app" />
|
||||||
|
</configuration>
|
||||||
|
</facet>
|
||||||
<facet type="android" name="Android">
|
<facet type="android" name="Android">
|
||||||
<configuration>
|
<configuration>
|
||||||
<option name="SELECTED_BUILD_VARIANT" value="debug" />
|
<option name="SELECTED_BUILD_VARIANT" value="debug" />
|
||||||
|
<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="compileDebugJava" />
|
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
|
||||||
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugTest" />
|
|
||||||
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
|
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
|
||||||
|
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
|
||||||
|
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" />
|
||||||
|
<option name="TEST_SOURCE_GEN_TASK_NAME" value="generateDebugAndroidTestSources" />
|
||||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
||||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
||||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
|
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
|
||||||
@@ -15,62 +23,99 @@
|
|||||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
|
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
|
||||||
</configuration>
|
</configuration>
|
||||||
</facet>
|
</facet>
|
||||||
<facet type="android-gradle" name="Android-Gradle">
|
|
||||||
<configuration>
|
|
||||||
<option name="GRADLE_PROJECT_PATH" value=":app" />
|
|
||||||
</configuration>
|
|
||||||
</facet>
|
|
||||||
</component>
|
</component>
|
||||||
<component name="NewModuleRootManager" inherit-compiler-output="false">
|
<component name="NewModuleRootManager" inherit-compiler-output="false">
|
||||||
<output url="file://$MODULE_DIR$/build/classes/debug" />
|
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
|
||||||
|
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/debug" />
|
||||||
<exclude-output />
|
<exclude-output />
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/source/r/debug" isTestSource="false" generated="true" />
|
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/source/aidl/debug" isTestSource="false" generated="true" />
|
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/source/buildConfig/debug" isTestSource="false" generated="true" />
|
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/source/rs/debug" isTestSource="false" generated="true" />
|
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/res/rs/debug" type="java-resource" />
|
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/source/r/test/debug" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/debug" type="java-resource" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/source/aidl/test/debug" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/source/buildConfig/test/debug" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/source/rs/test/debug" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/res/rs/test/debug" type="java-test-resource" />
|
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/androidTest/debug" type="java-test-resource" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" isTestSource="false" />
|
|
||||||
<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/debug/res" type="java-resource" />
|
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/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/aidl" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
|
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/aidl" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/assets" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/java" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/jni" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/rs" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/res" type="java-test-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/resources" type="java-test-resource" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/apk" />
|
<excludeFolder url="file://$MODULE_DIR$/build/apk" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/assets" />
|
<excludeFolder url="file://$MODULE_DIR$/build/assets" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/bundles" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/classes" />
|
<excludeFolder url="file://$MODULE_DIR$/build/classes" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/dependency-cache" />
|
<excludeFolder url="file://$MODULE_DIR$/build/dependency-cache" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build/dex" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/incremental" />
|
<excludeFolder url="file://$MODULE_DIR$/build/incremental" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/22.1.1/jars" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/recyclerview-v7/22.0.0/jars" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/22.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/jacoco" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
|
||||||
|
<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/proguard" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/libs" />
|
<excludeFolder url="file://$MODULE_DIR$/build/libs" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/manifests" />
|
<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/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/symbols" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
|
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="Android API 19 Platform" jdkType="Android SDK" />
|
<orderEntry type="jdk" jdkName="Android API 22 Platform" jdkType="Android SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" exported="" name="support-v4-18.0.0" level="project" />
|
<orderEntry type="library" exported="" name="appcompat-v7-22.1.1" level="project" />
|
||||||
|
<orderEntry type="library" exported="" name="fab-0.0.6" 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="eventbus-2.4.0" 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-v4-22.1.1" level="project" />
|
||||||
|
<orderEntry type="library" exported="" name="support-annotations-22.1.1" level="project" />
|
||||||
|
<orderEntry type="library" exported="" name="recyclerview-v7-22.0.0" level="project" />
|
||||||
|
<orderEntry type="library" exported="" name="android-async-http-1.4.6" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
|
|
||||||
|
|||||||
@@ -3,22 +3,22 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:0.7.+'
|
classpath 'com.android.tools.build:gradle:1.2.3'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
apply plugin: 'android'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 19
|
compileSdkVersion 22
|
||||||
buildToolsVersion "19.0.0"
|
buildToolsVersion "22.0.1"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
targetSdkVersion 19
|
targetSdkVersion 22
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
}
|
}
|
||||||
@@ -27,14 +27,33 @@ android {
|
|||||||
sourceCompatibility JavaVersion.VERSION_1_7
|
sourceCompatibility JavaVersion.VERSION_1_7
|
||||||
targetCompatibility JavaVersion.VERSION_1_7
|
targetCompatibility JavaVersion.VERSION_1_7
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
release {
|
||||||
|
storeFile file(System.getenv("TRACKINO_STORE_PATH"))
|
||||||
|
storePassword System.getenv("TRACKINO_STORE_PASS")
|
||||||
|
keyAlias System.getenv("TRACKINO_STORE_ALIAS")
|
||||||
|
keyPassword System.getenv("TRACKINO_STORE_KEYPASS")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
runProguard false
|
signingConfig signingConfigs.release
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lintOptions {
|
||||||
|
abortOnError false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'com.android.support:support-v4:18.0.0'
|
compile fileTree(dir: 'libs', include: '*.jar')
|
||||||
|
compile 'com.android.support:appcompat-v7:22.1.1'
|
||||||
|
compile 'com.android.support:recyclerview-v7:22.0.0'
|
||||||
|
compile 'com.loopj.android:android-async-http:1.4.6'
|
||||||
|
compile 'it.sephiroth.android.library.imagezoom:imagezoom:1.0.5'
|
||||||
|
compile 'de.greenrobot:eventbus:2.4.0'
|
||||||
|
compile 'com.shamanland:fab:0.0.6'
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
docs-android/app/libs/android-query.0.26.8.jar
Normal file
BIN
docs-android/app/libs/tokenautocomplete-1.2.1.jar
Normal file
@@ -2,28 +2,66 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.sismics.docs" >
|
package="com.sismics.docs" >
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".MainApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/AppTheme" >
|
android:theme="@style/AppTheme" >
|
||||||
<activity
|
<activity
|
||||||
android:name="com.sismics.docs.DocListActivity"
|
android:name=".activity.LoginActivity"
|
||||||
android:label="@string/app_name" >
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/AppThemeDark">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name="com.sismics.docs.DocDetailActivity"
|
android:name=".activity.MainActivity"
|
||||||
android:label="@string/title_doc_detail"
|
android:label="@string/app_name"
|
||||||
android:parentActivityName="com.sismics.docs.DocListActivity" >
|
android:launchMode="singleTop"
|
||||||
<meta-data
|
android:windowSoftInputMode="adjustNothing">
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
<intent-filter>
|
||||||
android:value="com.sismics.docs.DocListActivity" />
|
<action android:name="android.intent.action.SEARCH" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.GET_CONTENT" />
|
||||||
|
<category android:name="android.intent.category.OPENABLE" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="*/*" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data android:name="android.app.searchable" android:resource="@xml/searchable" />
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".activity.DocumentViewActivity"
|
||||||
|
android:label="">
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".activity.DocumentEditActivity"
|
||||||
|
android:label="@string/new_document">
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".activity.SettingsActivity"
|
||||||
|
android:label="@string/settings">
|
||||||
|
</activity>
|
||||||
|
<provider android:name=".provider.RecentSuggestionsProvider"
|
||||||
|
android:exported="false"
|
||||||
|
android:authorities="com.sismics.docs.provider.RecentSuggestionsProvider" />
|
||||||
|
<service
|
||||||
|
android:name=".service.FileUploadService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false" >
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.sismics.docs.file.upload"/>
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
package com.sismics.docs;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v4.app.FragmentActivity;
|
|
||||||
import android.support.v4.app.NavUtils;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An activity representing a single Doc detail screen. This
|
|
||||||
* activity is only used on handset devices. On tablet-size devices,
|
|
||||||
* item details are presented side-by-side with a list of items
|
|
||||||
* in a {@link DocListActivity}.
|
|
||||||
* <p>
|
|
||||||
* This activity is mostly just a 'shell' activity containing nothing
|
|
||||||
* more than a {@link DocDetailFragment}.
|
|
||||||
*/
|
|
||||||
public class DocDetailActivity extends FragmentActivity {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_doc_detail);
|
|
||||||
|
|
||||||
// Show the Up button in the action bar.
|
|
||||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
|
|
||||||
// savedInstanceState is non-null when there is fragment state
|
|
||||||
// saved from previous configurations of this activity
|
|
||||||
// (e.g. when rotating the screen from portrait to landscape).
|
|
||||||
// In this case, the fragment will automatically be re-added
|
|
||||||
// to its container so we don't need to manually add it.
|
|
||||||
// For more information, see the Fragments API guide at:
|
|
||||||
//
|
|
||||||
// http://developer.android.com/guide/components/fragments.html
|
|
||||||
//
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
// Create the detail fragment and add it to the activity
|
|
||||||
// using a fragment transaction.
|
|
||||||
Bundle arguments = new Bundle();
|
|
||||||
arguments.putString(DocDetailFragment.ARG_ITEM_ID,
|
|
||||||
getIntent().getStringExtra(DocDetailFragment.ARG_ITEM_ID));
|
|
||||||
DocDetailFragment fragment = new DocDetailFragment();
|
|
||||||
fragment.setArguments(arguments);
|
|
||||||
getSupportFragmentManager().beginTransaction()
|
|
||||||
.add(R.id.doc_detail_container, fragment)
|
|
||||||
.commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
int id = item.getItemId();
|
|
||||||
if (id == android.R.id.home) {
|
|
||||||
// This ID represents the Home or Up button. In the case of this
|
|
||||||
// activity, the Up button is shown. Use NavUtils to allow users
|
|
||||||
// to navigate up one level in the application structure. For
|
|
||||||
// more details, see the Navigation pattern on Android Design:
|
|
||||||
//
|
|
||||||
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
|
|
||||||
//
|
|
||||||
NavUtils.navigateUpTo(this, new Intent(this, DocListActivity.class));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package com.sismics.docs;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v4.app.Fragment;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.sismics.docs.dummy.DummyContent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A fragment representing a single Doc detail screen.
|
|
||||||
* This fragment is either contained in a {@link DocListActivity}
|
|
||||||
* in two-pane mode (on tablets) or a {@link DocDetailActivity}
|
|
||||||
* on handsets.
|
|
||||||
*/
|
|
||||||
public class DocDetailFragment extends Fragment {
|
|
||||||
/**
|
|
||||||
* The fragment argument representing the item ID that this fragment
|
|
||||||
* represents.
|
|
||||||
*/
|
|
||||||
public static final String ARG_ITEM_ID = "item_id";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The dummy content this fragment is presenting.
|
|
||||||
*/
|
|
||||||
private DummyContent.DummyItem mItem;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mandatory empty constructor for the fragment manager to instantiate the
|
|
||||||
* fragment (e.g. upon screen orientation changes).
|
|
||||||
*/
|
|
||||||
public DocDetailFragment() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
if (getArguments().containsKey(ARG_ITEM_ID)) {
|
|
||||||
// Load the dummy content specified by the fragment
|
|
||||||
// arguments. In a real-world scenario, use a Loader
|
|
||||||
// to load content from a content provider.
|
|
||||||
mItem = DummyContent.ITEM_MAP.get(getArguments().getString(ARG_ITEM_ID));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
|
||||||
Bundle savedInstanceState) {
|
|
||||||
View rootView = inflater.inflate(R.layout.fragment_doc_detail, container, false);
|
|
||||||
|
|
||||||
// Show the dummy content as text in a TextView.
|
|
||||||
if (mItem != null) {
|
|
||||||
((TextView) rootView.findViewById(R.id.doc_detail)).setText(mItem.content);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rootView;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package com.sismics.docs;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v4.app.FragmentActivity;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An activity representing a list of Docs. This activity
|
|
||||||
* has different presentations for handset and tablet-size devices. On
|
|
||||||
* handsets, the activity presents a list of items, which when touched,
|
|
||||||
* lead to a {@link DocDetailActivity} representing
|
|
||||||
* item details. On tablets, the activity presents the list of items and
|
|
||||||
* item details side-by-side using two vertical panes.
|
|
||||||
* <p>
|
|
||||||
* The activity makes heavy use of fragments. The list of items is a
|
|
||||||
* {@link DocListFragment} and the item details
|
|
||||||
* (if present) is a {@link DocDetailFragment}.
|
|
||||||
* <p>
|
|
||||||
* This activity also implements the required
|
|
||||||
* {@link DocListFragment.Callbacks} interface
|
|
||||||
* to listen for item selections.
|
|
||||||
*/
|
|
||||||
public class DocListActivity extends FragmentActivity
|
|
||||||
implements DocListFragment.Callbacks {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
|
|
||||||
* device.
|
|
||||||
*/
|
|
||||||
private boolean mTwoPane;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_doc_list);
|
|
||||||
|
|
||||||
if (findViewById(R.id.doc_detail_container) != null) {
|
|
||||||
// The detail container view will be present only in the
|
|
||||||
// large-screen layouts (res/values-large and
|
|
||||||
// res/values-sw600dp). If this view is present, then the
|
|
||||||
// activity should be in two-pane mode.
|
|
||||||
mTwoPane = true;
|
|
||||||
|
|
||||||
// In two-pane mode, list items should be given the
|
|
||||||
// 'activated' state when touched.
|
|
||||||
((DocListFragment) getSupportFragmentManager()
|
|
||||||
.findFragmentById(R.id.doc_list))
|
|
||||||
.setActivateOnItemClick(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: If exposing deep links into your app, handle intents here.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback method from {@link DocListFragment.Callbacks}
|
|
||||||
* indicating that the item with the given ID was selected.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onItemSelected(String id) {
|
|
||||||
if (mTwoPane) {
|
|
||||||
// In two-pane mode, show the detail view in this activity by
|
|
||||||
// adding or replacing the detail fragment using a
|
|
||||||
// fragment transaction.
|
|
||||||
Bundle arguments = new Bundle();
|
|
||||||
arguments.putString(DocDetailFragment.ARG_ITEM_ID, id);
|
|
||||||
DocDetailFragment fragment = new DocDetailFragment();
|
|
||||||
fragment.setArguments(arguments);
|
|
||||||
getSupportFragmentManager().beginTransaction()
|
|
||||||
.replace(R.id.doc_detail_container, fragment)
|
|
||||||
.commit();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// In single-pane mode, simply start the detail activity
|
|
||||||
// for the selected item ID.
|
|
||||||
Intent detailIntent = new Intent(this, DocDetailActivity.class);
|
|
||||||
detailIntent.putExtra(DocDetailFragment.ARG_ITEM_ID, id);
|
|
||||||
startActivity(detailIntent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
package com.sismics.docs;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v4.app.ListFragment;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.ListView;
|
|
||||||
|
|
||||||
import com.sismics.docs.dummy.DummyContent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list fragment representing a list of Docs. This fragment
|
|
||||||
* also supports tablet devices by allowing list items to be given an
|
|
||||||
* 'activated' state upon selection. This helps indicate which item is
|
|
||||||
* currently being viewed in a {@link DocDetailFragment}.
|
|
||||||
* <p>
|
|
||||||
* Activities containing this fragment MUST implement the {@link Callbacks}
|
|
||||||
* interface.
|
|
||||||
*/
|
|
||||||
public class DocListFragment extends ListFragment {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The serialization (saved instance state) Bundle key representing the
|
|
||||||
* activated item position. Only used on tablets.
|
|
||||||
*/
|
|
||||||
private static final String STATE_ACTIVATED_POSITION = "activated_position";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The fragment's current callback object, which is notified of list item
|
|
||||||
* clicks.
|
|
||||||
*/
|
|
||||||
private Callbacks mCallbacks = sDummyCallbacks;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current activated item position. Only used on tablets.
|
|
||||||
*/
|
|
||||||
private int mActivatedPosition = ListView.INVALID_POSITION;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A callback interface that all activities containing this fragment must
|
|
||||||
* implement. This mechanism allows activities to be notified of item
|
|
||||||
* selections.
|
|
||||||
*/
|
|
||||||
public interface Callbacks {
|
|
||||||
/**
|
|
||||||
* Callback for when an item has been selected.
|
|
||||||
*/
|
|
||||||
public void onItemSelected(String id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A dummy implementation of the {@link Callbacks} interface that does
|
|
||||||
* nothing. Used only when this fragment is not attached to an activity.
|
|
||||||
*/
|
|
||||||
private static Callbacks sDummyCallbacks = new Callbacks() {
|
|
||||||
@Override
|
|
||||||
public void onItemSelected(String id) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mandatory empty constructor for the fragment manager to instantiate the
|
|
||||||
* fragment (e.g. upon screen orientation changes).
|
|
||||||
*/
|
|
||||||
public DocListFragment() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
// TODO: replace with a real list adapter.
|
|
||||||
setListAdapter(new ArrayAdapter<DummyContent.DummyItem>(
|
|
||||||
getActivity(),
|
|
||||||
android.R.layout.simple_list_item_activated_1,
|
|
||||||
android.R.id.text1,
|
|
||||||
DummyContent.ITEMS));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
|
|
||||||
// Restore the previously serialized activated item position.
|
|
||||||
if (savedInstanceState != null
|
|
||||||
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
|
|
||||||
setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Activity activity) {
|
|
||||||
super.onAttach(activity);
|
|
||||||
|
|
||||||
// Activities containing this fragment must implement its callbacks.
|
|
||||||
if (!(activity instanceof Callbacks)) {
|
|
||||||
throw new IllegalStateException("Activity must implement fragment's callbacks.");
|
|
||||||
}
|
|
||||||
|
|
||||||
mCallbacks = (Callbacks) activity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDetach() {
|
|
||||||
super.onDetach();
|
|
||||||
|
|
||||||
// Reset the active callbacks interface to the dummy implementation.
|
|
||||||
mCallbacks = sDummyCallbacks;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onListItemClick(ListView listView, View view, int position, long id) {
|
|
||||||
super.onListItemClick(listView, view, position, id);
|
|
||||||
|
|
||||||
// Notify the active callbacks interface (the activity, if the
|
|
||||||
// fragment is attached to one) that an item has been selected.
|
|
||||||
mCallbacks.onItemSelected(DummyContent.ITEMS.get(position).id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
if (mActivatedPosition != ListView.INVALID_POSITION) {
|
|
||||||
// Serialize and persist the activated item position.
|
|
||||||
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Turns on activate-on-click mode. When this mode is on, list items will be
|
|
||||||
* given the 'activated' state when touched.
|
|
||||||
*/
|
|
||||||
public void setActivateOnItemClick(boolean activateOnItemClick) {
|
|
||||||
// When setting CHOICE_MODE_SINGLE, ListView will automatically
|
|
||||||
// give items the 'activated' state when touched.
|
|
||||||
getListView().setChoiceMode(activateOnItemClick
|
|
||||||
? ListView.CHOICE_MODE_SINGLE
|
|
||||||
: ListView.CHOICE_MODE_NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setActivatedPosition(int position) {
|
|
||||||
if (position == ListView.INVALID_POSITION) {
|
|
||||||
getListView().setItemChecked(mActivatedPosition, false);
|
|
||||||
} else {
|
|
||||||
getListView().setItemChecked(position, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
mActivatedPosition = position;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main application.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class MainApplication extends Application {
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
// Fetching GET /user from cache
|
||||||
|
JSONObject json = PreferenceUtil.getCachedJson(getApplicationContext(), PreferenceUtil.PREF_CACHED_USER_INFO_JSON);
|
||||||
|
ApplicationContext.getInstance().setUserInfo(getApplicationContext(), json);
|
||||||
|
|
||||||
|
// TODO google docs app: right drawer with all actions, with acls, with deep metadatas
|
||||||
|
// TODO Provide documents to intent action get content
|
||||||
|
|
||||||
|
super.onCreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLowMemory() {
|
||||||
|
BitmapAjaxCallback.clearCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
package com.sismics.docs.activity;
|
||||||
|
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.Spinner;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
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.resource.DocumentResource;
|
||||||
|
import com.sismics.docs.ui.form.Validator;
|
||||||
|
import com.sismics.docs.ui.form.validator.Required;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import de.greenrobot.event.EventBus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Document edition activity.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class DocumentEditActivity extends AppCompatActivity {
|
||||||
|
/**
|
||||||
|
* Document edited.
|
||||||
|
*/
|
||||||
|
private JSONObject document;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form validator.
|
||||||
|
*/
|
||||||
|
private Validator validator;
|
||||||
|
|
||||||
|
// View cache
|
||||||
|
private EditText titleEditText;
|
||||||
|
private EditText descriptionEditText;
|
||||||
|
private TagsCompleteTextView tagsEditText;
|
||||||
|
private Spinner languageSpinner;
|
||||||
|
private DatePickerView datePickerView;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle args) {
|
||||||
|
super.onCreate(args);
|
||||||
|
|
||||||
|
// Handle activity context
|
||||||
|
if (getIntent() == null) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse input document
|
||||||
|
String documentJson = getIntent().getStringExtra("document");
|
||||||
|
if (documentJson != null) {
|
||||||
|
try {
|
||||||
|
document = new JSONObject(documentJson);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the activity
|
||||||
|
setContentView(R.layout.document_edit_activity);
|
||||||
|
if (getSupportActionBar() != null) {
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
getSupportActionBar().setHomeButtonEnabled(true);
|
||||||
|
}
|
||||||
|
languageSpinner = (Spinner) findViewById(R.id.languageSpinner);
|
||||||
|
tagsEditText = (TagsCompleteTextView) findViewById(R.id.tagsEditText);
|
||||||
|
datePickerView = (DatePickerView) findViewById(R.id.dateEditText);
|
||||||
|
titleEditText = (EditText) findViewById(R.id.titleEditText);
|
||||||
|
descriptionEditText = (EditText) findViewById(R.id.descriptionEditText);
|
||||||
|
|
||||||
|
// Language spinner
|
||||||
|
LanguageAdapter languageAdapter = new LanguageAdapter(this, false);
|
||||||
|
languageSpinner.setAdapter(languageAdapter);
|
||||||
|
|
||||||
|
// Tags auto-complete
|
||||||
|
JSONObject tags = PreferenceUtil.getCachedJson(this, PreferenceUtil.PREF_CACHED_TAGS_JSON);
|
||||||
|
if (tags == null) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JSONArray tagArray = tags.optJSONArray("stats");
|
||||||
|
|
||||||
|
List<JSONObject> tagList = new ArrayList<>();
|
||||||
|
for (int i = 0; i < tagArray.length(); i++) {
|
||||||
|
tagList.add(tagArray.optJSONObject(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
tagsEditText.allowDuplicates(false);
|
||||||
|
tagsEditText.setAdapter(new TagAutoCompleteAdapter(this, 0, tagList));
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
validator = new Validator(this, true);
|
||||||
|
validator.addValidable(titleEditText, new Required());
|
||||||
|
|
||||||
|
// Fill the activity
|
||||||
|
if (document == null) {
|
||||||
|
datePickerView.setDate(new Date());
|
||||||
|
} else {
|
||||||
|
setTitle(R.string.edit_document);
|
||||||
|
titleEditText.setText(document.optString("title"));
|
||||||
|
descriptionEditText.setText(document.optString("description"));
|
||||||
|
datePickerView.setDate(new Date(document.optLong("create_date")));
|
||||||
|
languageSpinner.setSelection(languageAdapter.getItemPosition(document.optString("language")));
|
||||||
|
JSONArray documentTags = document.optJSONArray("tags");
|
||||||
|
for (int i = 0; i < documentTags.length(); i++) {
|
||||||
|
tagsEditText.addObject(documentTags.optJSONObject(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
MenuInflater inflater = getMenuInflater();
|
||||||
|
inflater.inflate(R.menu.document_edit_activity, menu);
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.save:
|
||||||
|
validator.validate();
|
||||||
|
if (!validator.isValidated()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
final String title = titleEditText.getText().toString();
|
||||||
|
final String description = descriptionEditText.getText().toString();
|
||||||
|
LanguageAdapter.Language language = (LanguageAdapter.Language) languageSpinner.getSelectedItem();
|
||||||
|
final String langId = language.getId();
|
||||||
|
final long createDate = datePickerView.getDate().getTime();
|
||||||
|
Set<String> tagIdList = new HashSet<>();
|
||||||
|
for (Object object : tagsEditText.getObjects()) {
|
||||||
|
JSONObject tag = (JSONObject) object;
|
||||||
|
tagIdList.add(tag.optString("id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancellable progress dialog
|
||||||
|
final ProgressDialog progressDialog = ProgressDialog.show(this,
|
||||||
|
getString(R.string.please_wait),
|
||||||
|
getString(R.string.document_editing_message), true, true,
|
||||||
|
new DialogInterface.OnCancelListener() {
|
||||||
|
@Override
|
||||||
|
public void onCancel(DialogInterface dialog) {
|
||||||
|
DocumentResource.cancel(DocumentEditActivity.this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Server callback
|
||||||
|
JsonHttpResponseHandler callback = new JsonHttpResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
|
||||||
|
// Build a fake document JSON to update the UI
|
||||||
|
final JSONObject outputDoc = new JSONObject();
|
||||||
|
try {
|
||||||
|
if (document == null) {
|
||||||
|
outputDoc.putOpt("id", response.optString("id"));
|
||||||
|
outputDoc.putOpt("shared", false);
|
||||||
|
} else {
|
||||||
|
outputDoc.putOpt("id", document.optString("id"));
|
||||||
|
outputDoc.putOpt("shared", document.optBoolean("shared"));
|
||||||
|
}
|
||||||
|
outputDoc.putOpt("title", title);
|
||||||
|
outputDoc.putOpt("description", description);
|
||||||
|
outputDoc.putOpt("language", langId);
|
||||||
|
outputDoc.putOpt("create_date", createDate);
|
||||||
|
JSONArray tags = new JSONArray();
|
||||||
|
for (Object object : tagsEditText.getObjects()) {
|
||||||
|
tags.put(object);
|
||||||
|
}
|
||||||
|
outputDoc.putOpt("tags", tags);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.e(DocumentEditActivity.class.getSimpleName(), "Error building JSON for document", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire the right event
|
||||||
|
if (document == null) {
|
||||||
|
EventBus.getDefault().post(new DocumentAddEvent(outputDoc));
|
||||||
|
} else {
|
||||||
|
EventBus.getDefault().post(new DocumentEditEvent(outputDoc));
|
||||||
|
}
|
||||||
|
setResult(RESULT_OK);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
|
||||||
|
Toast.makeText(DocumentEditActivity.this, R.string.error_editing_document, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinish() {
|
||||||
|
progressDialog.dismiss();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Actual server call
|
||||||
|
if (document == null) {
|
||||||
|
DocumentResource.add(this, title, description, tagIdList, langId, createDate, callback);
|
||||||
|
} else {
|
||||||
|
DocumentResource.edit(this, document.optString("id"), title, description, tagIdList, langId, createDate, callback);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case android.R.id.home:
|
||||||
|
finish();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,614 @@
|
|||||||
|
package com.sismics.docs.activity;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.DownloadManager;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.ClipData;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.support.v4.app.DialogFragment;
|
||||||
|
import android.support.v4.view.GravityCompat;
|
||||||
|
import android.support.v4.view.ViewPager;
|
||||||
|
import android.support.v4.widget.DrawerLayout;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.text.format.DateFormat;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
import com.sismics.docs.adapter.AclListAdapter;
|
||||||
|
import com.sismics.docs.adapter.FilePagerAdapter;
|
||||||
|
import com.sismics.docs.event.DocumentDeleteEvent;
|
||||||
|
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.DocShareFragment;
|
||||||
|
import com.sismics.docs.listener.JsonHttpResponseHandler;
|
||||||
|
import com.sismics.docs.model.application.ApplicationContext;
|
||||||
|
import com.sismics.docs.resource.DocumentResource;
|
||||||
|
import com.sismics.docs.resource.FileResource;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.greenrobot.event.EventBus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Document activity.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class DocumentViewActivity extends AppCompatActivity {
|
||||||
|
/**
|
||||||
|
* Request code of adding file.
|
||||||
|
*/
|
||||||
|
public static final int REQUEST_CODE_ADD_FILE = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request code of editing document.
|
||||||
|
*/
|
||||||
|
public static final int REQUEST_CODE_EDIT_DOCUMENT = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File view pager.
|
||||||
|
*/
|
||||||
|
private ViewPager fileViewPager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File pager adapter.
|
||||||
|
*/
|
||||||
|
private FilePagerAdapter filePagerAdapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Document displayed.
|
||||||
|
*/
|
||||||
|
private JSONObject document;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Menu.
|
||||||
|
*/
|
||||||
|
private Menu menu;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(final Bundle args) {
|
||||||
|
super.onCreate(args);
|
||||||
|
|
||||||
|
// Check if logged in
|
||||||
|
if (!ApplicationContext.getInstance().isLoggedIn()) {
|
||||||
|
startActivity(new Intent(this, LoginActivity.class));
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle activity context
|
||||||
|
if (getIntent() == null) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse input document
|
||||||
|
String documentJson = getIntent().getStringExtra("document");
|
||||||
|
if (documentJson == null) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
document = new JSONObject(documentJson);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the activity
|
||||||
|
setContentView(R.layout.document_view_activity);
|
||||||
|
if (getSupportActionBar() != null) {
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
getSupportActionBar().setHomeButtonEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill the view
|
||||||
|
refreshDocument(document);
|
||||||
|
|
||||||
|
EventBus.getDefault().register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the displayed document.
|
||||||
|
*
|
||||||
|
* @param document Document in JSON format
|
||||||
|
*/
|
||||||
|
private void refreshDocument(JSONObject document) {
|
||||||
|
this.document = document;
|
||||||
|
|
||||||
|
String title = document.optString("title");
|
||||||
|
String date = DateFormat.getDateFormat(this).format(new Date(document.optLong("create_date")));
|
||||||
|
String description = document.optString("description");
|
||||||
|
boolean shared = document.optBoolean("shared");
|
||||||
|
String language = document.optString("language");
|
||||||
|
JSONArray tags = document.optJSONArray("tags");
|
||||||
|
|
||||||
|
// Setup the title
|
||||||
|
setTitle(title);
|
||||||
|
Toolbar toolbar = (Toolbar) findViewById(R.id.action_bar);
|
||||||
|
TextView titleTextView = (TextView) toolbar.getChildAt(1);
|
||||||
|
if (titleTextView != null) {
|
||||||
|
titleTextView.setEllipsize(TextUtils.TruncateAt.MARQUEE);
|
||||||
|
titleTextView.setMarqueeRepeatLimit(-1);
|
||||||
|
titleTextView.setFocusable(true);
|
||||||
|
titleTextView.setFocusableInTouchMode(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill the layout
|
||||||
|
TextView createdDateTextView = (TextView) findViewById(R.id.createdDateTextView);
|
||||||
|
createdDateTextView.setText(date);
|
||||||
|
|
||||||
|
TextView descriptionTextView = (TextView) findViewById(R.id.descriptionTextView);
|
||||||
|
if (description == null || description.isEmpty()) {
|
||||||
|
descriptionTextView.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
descriptionTextView.setVisibility(View.VISIBLE);
|
||||||
|
descriptionTextView.setText(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextView tagTextView = (TextView) findViewById(R.id.tagTextView);
|
||||||
|
if (tags.length() == 0) {
|
||||||
|
tagTextView.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
tagTextView.setVisibility(View.VISIBLE);
|
||||||
|
tagTextView.setText(TagUtil.buildSpannable(tags));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageView languageImageView = (ImageView) findViewById(R.id.languageImageView);
|
||||||
|
languageImageView.setImageResource(getResources().getIdentifier(language, "drawable", getPackageName()));
|
||||||
|
|
||||||
|
ImageView sharedImageView = (ImageView) findViewById(R.id.sharedImageView);
|
||||||
|
sharedImageView.setVisibility(shared ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
// Action edit document
|
||||||
|
Button button = (Button) findViewById(R.id.actionEditDocument);
|
||||||
|
button.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
Intent intent = new Intent(DocumentViewActivity.this, DocumentEditActivity.class);
|
||||||
|
intent.putExtra("document", DocumentViewActivity.this.document.toString());
|
||||||
|
startActivityForResult(intent, REQUEST_CODE_EDIT_DOCUMENT);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Action upload file
|
||||||
|
button = (Button) findViewById(R.id.actionUploadFile);
|
||||||
|
button.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT)
|
||||||
|
.setType("*/*")
|
||||||
|
.putExtra("android.intent.extra.ALLOW_MULTIPLE", true)
|
||||||
|
.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
startActivityForResult(Intent.createChooser(intent, getText(R.string.upload_from)), REQUEST_CODE_ADD_FILE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Action download document
|
||||||
|
button = (Button) findViewById(R.id.actionDownload);
|
||||||
|
button.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
downloadZip();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Action delete document
|
||||||
|
button = (Button) findViewById(R.id.actionDelete);
|
||||||
|
button.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
deleteDocument();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Action share
|
||||||
|
button = (Button) findViewById(R.id.actionSharing);
|
||||||
|
button.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
DialogFragment dialog = DocShareFragment.newInstance(DocumentViewActivity.this.document.optString("id"));
|
||||||
|
dialog.show(getSupportFragmentManager(), "DocShareFragment");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Grab the attached files
|
||||||
|
updateFiles();
|
||||||
|
|
||||||
|
// Grab the full document (used for ACLs and writable status)
|
||||||
|
updateDocument();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||||
|
MenuInflater inflater = getMenuInflater();
|
||||||
|
inflater.inflate(R.menu.document_view_activity, menu);
|
||||||
|
this.menu = menu;
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.info:
|
||||||
|
DrawerLayout drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
|
||||||
|
if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
|
||||||
|
drawerLayout.closeDrawer(GravityCompat.END);
|
||||||
|
} else {
|
||||||
|
drawerLayout.openDrawer(GravityCompat.END);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case R.id.download_file:
|
||||||
|
downloadCurrentFile();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case R.id.delete_file:
|
||||||
|
deleteCurrentFile();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case android.R.id.home:
|
||||||
|
finish();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download the current displayed file.
|
||||||
|
*/
|
||||||
|
private void downloadCurrentFile() {
|
||||||
|
if (fileViewPager == null || filePagerAdapter == null) return;
|
||||||
|
|
||||||
|
JSONObject file = filePagerAdapter.getObjectAt(fileViewPager.getCurrentItem());
|
||||||
|
if (file == null) return;
|
||||||
|
|
||||||
|
// Build the destination filename
|
||||||
|
String mimeType = file.optString("mimetype");
|
||||||
|
int position = fileViewPager.getCurrentItem();
|
||||||
|
if (mimeType == null || !mimeType.contains("/")) return;
|
||||||
|
String ext = mimeType.split("/")[1];
|
||||||
|
String fileName = getTitle() + "-" + position + "." + ext;
|
||||||
|
|
||||||
|
// Download the file
|
||||||
|
String fileUrl = PreferenceUtil.getServerUrl(this) + "/api/file/" + file.optString("id") + "/data";
|
||||||
|
downloadFile(fileUrl, fileName, getTitle().toString(), getString(R.string.downloading_file, position + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteCurrentFile() {
|
||||||
|
if (fileViewPager == null || filePagerAdapter == null) return;
|
||||||
|
|
||||||
|
final JSONObject file = filePagerAdapter.getObjectAt(fileViewPager.getCurrentItem());
|
||||||
|
if (file == null) return;
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
|
||||||
|
builder.setTitle(R.string.delete_file_title)
|
||||||
|
.setMessage(R.string.delete_file_message)
|
||||||
|
.setCancelable(true)
|
||||||
|
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
// Dismiss the confirmation dialog
|
||||||
|
dialog.dismiss();
|
||||||
|
|
||||||
|
// Show a progress dialog while deleting
|
||||||
|
final ProgressDialog progressDialog = ProgressDialog.show(DocumentViewActivity.this,
|
||||||
|
getString(R.string.please_wait),
|
||||||
|
getString(R.string.file_deleting_message), true, true,
|
||||||
|
new DialogInterface.OnCancelListener() {
|
||||||
|
@Override
|
||||||
|
public void onCancel(DialogInterface dialog) {
|
||||||
|
FileResource.cancel(DocumentViewActivity.this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Actual delete server call
|
||||||
|
final String fileId = file.optString("id");
|
||||||
|
FileResource.delete(DocumentViewActivity.this, fileId, new JsonHttpResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
|
||||||
|
EventBus.getDefault().post(new FileDeleteEvent(fileId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
|
||||||
|
Toast.makeText(DocumentViewActivity.this, R.string.file_delete_failure, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinish() {
|
||||||
|
progressDialog.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}).create().show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download the document (all files zipped).
|
||||||
|
*/
|
||||||
|
private void downloadZip() {
|
||||||
|
if (document == null) return;
|
||||||
|
String url = PreferenceUtil.getServerUrl(this) + "/api/file/zip?id=" + document.optString("id");
|
||||||
|
String fileName = getTitle() + ".zip";
|
||||||
|
downloadFile(url, fileName, getTitle().toString(), getString(R.string.downloading_document));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download a file using Android download manager.
|
||||||
|
*
|
||||||
|
* @param url URL to download
|
||||||
|
* @param fileName Destination file name
|
||||||
|
* @param title Notification title
|
||||||
|
* @param description Notification description
|
||||||
|
*/
|
||||||
|
private void downloadFile(String url, String fileName, String title, String description) {
|
||||||
|
String authToken = PreferenceUtil.getAuthToken(this);
|
||||||
|
DownloadManager downloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
|
||||||
|
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
|
||||||
|
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
|
||||||
|
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
|
||||||
|
request.addRequestHeader("Cookie", "auth_token=" + authToken);
|
||||||
|
request.setTitle(title);
|
||||||
|
request.setDescription(description);
|
||||||
|
downloadManager.enqueue(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the current document.
|
||||||
|
*/
|
||||||
|
private void deleteDocument() {
|
||||||
|
if (document == null) return;
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
|
||||||
|
builder.setTitle(R.string.delete_document_title)
|
||||||
|
.setMessage(R.string.delete_document_message)
|
||||||
|
.setCancelable(true)
|
||||||
|
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
// Dismiss the confirmation dialog
|
||||||
|
dialog.dismiss();
|
||||||
|
|
||||||
|
// Show a progress dialog while deleting
|
||||||
|
final ProgressDialog progressDialog = ProgressDialog.show(DocumentViewActivity.this,
|
||||||
|
getString(R.string.please_wait),
|
||||||
|
getString(R.string.document_deleting_message), true, true,
|
||||||
|
new DialogInterface.OnCancelListener() {
|
||||||
|
@Override
|
||||||
|
public void onCancel(DialogInterface dialog) {
|
||||||
|
DocumentResource.cancel(DocumentViewActivity.this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Actual delete server call
|
||||||
|
final String documentId = document.optString("id");
|
||||||
|
DocumentResource.delete(DocumentViewActivity.this, documentId, new JsonHttpResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
|
||||||
|
EventBus.getDefault().post(new DocumentDeleteEvent(documentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
|
||||||
|
Toast.makeText(DocumentViewActivity.this, R.string.document_delete_failure, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinish() {
|
||||||
|
progressDialog.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}).create().show();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A document fullscreen event has been fired.
|
||||||
|
*
|
||||||
|
* @param event Document fullscreen event
|
||||||
|
*/
|
||||||
|
public void onEventMainThread(DocumentFullscreenEvent event) {
|
||||||
|
findViewById(R.id.detailLayout).setVisibility(event.isFullscreen() ? View.GONE : View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A document edit event has been fired.
|
||||||
|
*
|
||||||
|
* @param event Document edit event
|
||||||
|
*/
|
||||||
|
public void onEventMainThread(DocumentEditEvent event) {
|
||||||
|
if (document == null) return;
|
||||||
|
if (event.getDocument().optString("id").equals(document.optString("id"))) {
|
||||||
|
// The current document has been modified, refresh it
|
||||||
|
refreshDocument(event.getDocument());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A document delete event has been fired.
|
||||||
|
*
|
||||||
|
* @param event Document delete event
|
||||||
|
*/
|
||||||
|
public void onEventMainThread(DocumentDeleteEvent event) {
|
||||||
|
if (document == null) return;
|
||||||
|
if (event.getDocumentId().equals(document.optString("id"))) {
|
||||||
|
// The current document has been deleted, close this activity
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A file delete event has been fired.
|
||||||
|
*
|
||||||
|
* @param event File delete event
|
||||||
|
*/
|
||||||
|
public void onEventMainThread(FileDeleteEvent event) {
|
||||||
|
if (filePagerAdapter == null) return;
|
||||||
|
filePagerAdapter.remove(event.getFileId());
|
||||||
|
final TextView filesEmptyView = (TextView) findViewById(R.id.filesEmptyView);
|
||||||
|
if (filePagerAdapter.getCount() == 0) filesEmptyView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A file add event has been fired.
|
||||||
|
*
|
||||||
|
* @param event File add event
|
||||||
|
*/
|
||||||
|
public void onEventMainThread(FileAddEvent event) {
|
||||||
|
if (document == null) return;
|
||||||
|
if (document.optString("id").equals(event.getDocumentId())) {
|
||||||
|
updateFiles();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
if (document == null) return;
|
||||||
|
|
||||||
|
if (requestCode == REQUEST_CODE_ADD_FILE && resultCode == RESULT_OK) {
|
||||||
|
List<Uri> uriList = new ArrayList<>();
|
||||||
|
// Single file upload
|
||||||
|
if (data.getData() != null) {
|
||||||
|
uriList.add(data.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle multiple file upload
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||||
|
ClipData clipData = data.getClipData();
|
||||||
|
if (clipData != null) {
|
||||||
|
for (int i = 0; i < clipData.getItemCount(); ++i) {
|
||||||
|
Uri uri = clipData.getItemAt(i).getUri();
|
||||||
|
if (uri != null) {
|
||||||
|
uriList.add(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload all files
|
||||||
|
for (Uri uri : uriList) {
|
||||||
|
Intent intent = new Intent(this, FileUploadService.class)
|
||||||
|
.putExtra(FileUploadService.PARAM_URI, uri)
|
||||||
|
.putExtra(FileUploadService.PARAM_DOCUMENT_ID, document.optString("id"));
|
||||||
|
startService(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the document model.
|
||||||
|
*/
|
||||||
|
private void updateDocument() {
|
||||||
|
if (document == null) return;
|
||||||
|
|
||||||
|
// 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() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
|
||||||
|
document = response;
|
||||||
|
boolean writable = document.optBoolean("writable");
|
||||||
|
|
||||||
|
if (menu != null) {
|
||||||
|
menu.findItem(R.id.delete_file).setVisible(writable);
|
||||||
|
}
|
||||||
|
|
||||||
|
findViewById(R.id.actionEditDocument).setVisibility(writable ? View.VISIBLE : View.INVISIBLE);
|
||||||
|
findViewById(R.id.actionUploadFile).setVisibility(writable ? View.VISIBLE : View.INVISIBLE);
|
||||||
|
findViewById(R.id.actionSharing).setVisibility(writable ? View.VISIBLE : View.INVISIBLE);
|
||||||
|
findViewById(R.id.actionDelete).setVisibility(writable ? View.VISIBLE : View.INVISIBLE);
|
||||||
|
|
||||||
|
// ACLs
|
||||||
|
ListView aclListView = (ListView) findViewById(R.id.aclListView);
|
||||||
|
aclListView.setAdapter(new AclListAdapter(document.optJSONArray("acls")));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh files list.
|
||||||
|
*/
|
||||||
|
private void updateFiles() {
|
||||||
|
if (document == null) return;
|
||||||
|
|
||||||
|
final View progressBar = findViewById(R.id.progressBar);
|
||||||
|
final TextView filesEmptyView = (TextView) findViewById(R.id.filesEmptyView);
|
||||||
|
fileViewPager = (ViewPager) findViewById(R.id.fileViewPager);
|
||||||
|
fileViewPager.setOffscreenPageLimit(1);
|
||||||
|
fileViewPager.setAdapter(null);
|
||||||
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
filesEmptyView.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
FileResource.list(this, document.optString("id"), new JsonHttpResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
|
||||||
|
JSONArray files = response.optJSONArray("files");
|
||||||
|
filePagerAdapter = new FilePagerAdapter(DocumentViewActivity.this, files);
|
||||||
|
fileViewPager.setAdapter(filePagerAdapter);
|
||||||
|
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
if (files.length() == 0) filesEmptyView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
|
||||||
|
filesEmptyView.setText(R.string.error_loading_files);
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
filesEmptyView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
EventBus.getDefault().unregister(this);
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
package com.sismics.docs.activity;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.text.Html;
|
||||||
|
import android.text.method.LinkMovementMethod;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
|
||||||
|
import com.androidquery.AQuery;
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
import com.sismics.docs.listener.CallbackListener;
|
||||||
|
import com.sismics.docs.listener.JsonHttpResponseHandler;
|
||||||
|
import com.sismics.docs.model.application.ApplicationContext;
|
||||||
|
import com.sismics.docs.resource.UserResource;
|
||||||
|
import com.sismics.docs.ui.form.Validator;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login activity.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class LoginActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User interface.
|
||||||
|
*/
|
||||||
|
private View loginForm;
|
||||||
|
private View progressBar;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
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());
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
|
||||||
|
|
||||||
|
loginForm.setVisibility(View.GONE);
|
||||||
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
// Form validation
|
||||||
|
final Validator validator = new Validator(this, false);
|
||||||
|
validator.addValidable(txtServer, new Required());
|
||||||
|
validator.addValidable(txtUsername, new Required());
|
||||||
|
validator.addValidable(txtPassword, new Required());
|
||||||
|
validator.setOnValidationChanged(new CallbackListener() {
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
btnConnect.setEnabled(validator.isValidated());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Preset saved server URL
|
||||||
|
String serverUrl = PreferenceUtil.getStringPreference(this, PreferenceUtil.PREF_SERVER_URL);
|
||||||
|
if (serverUrl != null) {
|
||||||
|
txtServer.setText(serverUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
tryConnect();
|
||||||
|
|
||||||
|
// Login button
|
||||||
|
btnConnect.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
loginForm.setVisibility(View.GONE);
|
||||||
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
PreferenceUtil.setServerUrl(LoginActivity.this, txtServer.getText().toString());
|
||||||
|
|
||||||
|
try {
|
||||||
|
UserResource.login(getApplicationContext(), txtUsername.getText().toString(), txtPassword.getText().toString(), new JsonHttpResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(int statusCode, Header[] headers, JSONObject json) {
|
||||||
|
// Empty previous user caches
|
||||||
|
PreferenceUtil.resetUserCache(getApplicationContext());
|
||||||
|
|
||||||
|
// Getting user info and redirecting to main activity
|
||||||
|
ApplicationContext.getInstance().fetchUserInfo(LoginActivity.this, new CallbackListener() {
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
|
||||||
|
loginForm.setVisibility(View.VISIBLE);
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
if (responseBytes != null && new String(responseBytes).contains("\"ForbiddenError\"")) {
|
||||||
|
DialogUtil.showOkDialog(LoginActivity.this, R.string.login_fail_title, R.string.login_fail);
|
||||||
|
} else {
|
||||||
|
DialogUtil.showOkDialog(LoginActivity.this, R.string.network_error_title, R.string.network_error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// Given URL is not valid
|
||||||
|
loginForm.setVisibility(View.VISIBLE);
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
PreferenceUtil.setServerUrl(LoginActivity.this, null);
|
||||||
|
DialogUtil.showOkDialog(LoginActivity.this, R.string.invalid_url_title, R.string.invalid_url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to get a "session".
|
||||||
|
*/
|
||||||
|
private void tryConnect() {
|
||||||
|
String serverUrl = PreferenceUtil.getStringPreference(this, PreferenceUtil.PREF_SERVER_URL);
|
||||||
|
if (serverUrl == null) {
|
||||||
|
// Server URL is empty
|
||||||
|
loginForm.setVisibility(View.VISIBLE);
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ApplicationContext.getInstance().isLoggedIn()) {
|
||||||
|
// If we are already connected (from cache data)
|
||||||
|
// redirecting to main activity
|
||||||
|
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
} else {
|
||||||
|
// Trying to get user data
|
||||||
|
UserResource.info(getApplicationContext(), new JsonHttpResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(int statusCode, Header[] headers, final JSONObject json) {
|
||||||
|
if (json.optBoolean("anonymous", true)) {
|
||||||
|
loginForm.setVisibility(View.VISIBLE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save user data in application context
|
||||||
|
ApplicationContext.getInstance().setUserInfo(getApplicationContext(), json);
|
||||||
|
|
||||||
|
// Redirecting to main activity
|
||||||
|
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
|
||||||
|
DialogUtil.showOkDialog(LoginActivity.this, R.string.network_error_title, R.string.network_error);
|
||||||
|
loginForm.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinish() {
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,283 @@
|
|||||||
|
package com.sismics.docs.activity;
|
||||||
|
|
||||||
|
import android.app.SearchManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.provider.SearchRecentSuggestions;
|
||||||
|
import android.support.v4.widget.DrawerLayout;
|
||||||
|
import android.support.v7.app.ActionBarDrawerToggle;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
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;
|
||||||
|
import com.sismics.docs.event.SearchEvent;
|
||||||
|
import com.sismics.docs.fragment.SearchFragment;
|
||||||
|
import com.sismics.docs.listener.JsonHttpResponseHandler;
|
||||||
|
import com.sismics.docs.model.application.ApplicationContext;
|
||||||
|
import com.sismics.docs.provider.RecentSuggestionsProvider;
|
||||||
|
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 de.greenrobot.event.EventBus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main activity.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private ActionBarDrawerToggle drawerToggle;
|
||||||
|
private MenuItem searchItem;
|
||||||
|
private DrawerLayout drawerLayout;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(final Bundle args) {
|
||||||
|
super.onCreate(args);
|
||||||
|
|
||||||
|
// Check if logged in
|
||||||
|
if (!ApplicationContext.getInstance().isLoggedIn()) {
|
||||||
|
startActivity(new Intent(this, LoginActivity.class));
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the activity
|
||||||
|
setContentView(R.layout.main_activity);
|
||||||
|
|
||||||
|
// Enable ActionBar app icon to behave as action to toggle nav drawer
|
||||||
|
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
|
||||||
|
if (getSupportActionBar() != null) {
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
getSupportActionBar().setHomeButtonEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionBarDrawerToggle ties together the the proper interactions
|
||||||
|
// between the sliding drawer and the action bar app icon
|
||||||
|
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout,
|
||||||
|
R.string.drawer_open, R.string.drawer_close);
|
||||||
|
drawerLayout.setDrawerListener(drawerToggle);
|
||||||
|
|
||||||
|
// Fill the drawer user info
|
||||||
|
JSONObject userInfo = ApplicationContext.getInstance().getUserInfo();
|
||||||
|
TextView usernameTextView = (TextView) findViewById(R.id.usernameTextView);
|
||||||
|
usernameTextView.setText(userInfo.optString("username"));
|
||||||
|
TextView emailTextView = (TextView) findViewById(R.id.emailTextView);
|
||||||
|
emailTextView.setText(userInfo.optString("email"));
|
||||||
|
|
||||||
|
// Get tag list to fill the drawer
|
||||||
|
final ListView tagListView = (ListView) findViewById(R.id.tagListView);
|
||||||
|
final View tagProgressView = findViewById(R.id.tagProgressView);
|
||||||
|
final TextView tagEmptyView = (TextView) findViewById(R.id.tagEmptyView);
|
||||||
|
tagListView.setEmptyView(tagProgressView);
|
||||||
|
JSONObject cacheTags = PreferenceUtil.getCachedJson(this, PreferenceUtil.PREF_CACHED_TAGS_JSON);
|
||||||
|
if (cacheTags != null) {
|
||||||
|
tagListView.setAdapter(new TagListAdapter(cacheTags.optJSONArray("stats")));
|
||||||
|
}
|
||||||
|
TagResource.stats(this, new JsonHttpResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
|
||||||
|
PreferenceUtil.setCachedJson(MainActivity.this, PreferenceUtil.PREF_CACHED_TAGS_JSON, response);
|
||||||
|
tagListView.setAdapter(new TagListAdapter(response.optJSONArray("stats")));
|
||||||
|
tagProgressView.setVisibility(View.GONE);
|
||||||
|
tagListView.setEmptyView(tagEmptyView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
|
||||||
|
tagEmptyView.setText(R.string.error_loading_tags);
|
||||||
|
tagProgressView.setVisibility(View.GONE);
|
||||||
|
tagListView.setEmptyView(tagEmptyView);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Click on a tag
|
||||||
|
tagListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
TagListAdapter adapter = (TagListAdapter) tagListView.getAdapter();
|
||||||
|
if (adapter == null) return;
|
||||||
|
JSONObject tag = adapter.getItem(position);
|
||||||
|
if (tag == null) return;
|
||||||
|
searchQuery("tag:" + tag.optString("name"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Click on All documents
|
||||||
|
View allDocumentsLayout = findViewById(R.id.allDocumentsLayout);
|
||||||
|
allDocumentsLayout.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
searchQuery(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Click on Shared documents
|
||||||
|
View sharedDocumentsLayout = findViewById(R.id.sharedDocumentsLayout);
|
||||||
|
sharedDocumentsLayout.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
searchQuery("shared:yes");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
handleIntent(getIntent());
|
||||||
|
|
||||||
|
EventBus.getDefault().register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.logout:
|
||||||
|
UserResource.logout(getApplicationContext(), new JsonHttpResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onFinish() {
|
||||||
|
// Force logout in all cases, so the user is not stuck in case of network error
|
||||||
|
ApplicationContext.getInstance().setUserInfo(getApplicationContext(), null);
|
||||||
|
startActivity(new Intent(MainActivity.this, LoginActivity.class));
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case R.id.advanced_search:
|
||||||
|
SearchFragment dialog = SearchFragment.newInstance();
|
||||||
|
dialog.show(getSupportFragmentManager(), "SearchFragment");
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case R.id.settings:
|
||||||
|
startActivity(new Intent(MainActivity.this, SettingsActivity.class));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case android.R.id.home:
|
||||||
|
// The action bar home/up action should open or close the drawer.
|
||||||
|
// ActionBarDrawerToggle will take care of this.
|
||||||
|
if (drawerToggle.onOptionsItemSelected(item)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostCreate(Bundle savedInstanceState) {
|
||||||
|
super.onPostCreate(savedInstanceState);
|
||||||
|
// Sync the toggle state after onRestoreInstanceState has occurred.
|
||||||
|
drawerToggle.syncState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigurationChanged(Configuration newConfig) {
|
||||||
|
super.onConfigurationChanged(newConfig);
|
||||||
|
// Pass any configuration change to the drawer toggle
|
||||||
|
drawerToggle.onConfigurationChanged(newConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
MenuInflater inflater = getMenuInflater();
|
||||||
|
inflater.inflate(R.menu.main_activity, menu);
|
||||||
|
|
||||||
|
// Get the SearchView and set the searchable configuration
|
||||||
|
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
|
||||||
|
searchItem = menu.findItem(R.id.action_search);
|
||||||
|
SearchView searchView = (SearchView) searchItem.getActionView();
|
||||||
|
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
|
||||||
|
|
||||||
|
searchView.setOnCloseListener(new SearchView.OnCloseListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onClose() {
|
||||||
|
EventBus.getDefault().post(new SearchEvent(null));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onNewIntent(Intent intent) {
|
||||||
|
setIntent(intent);
|
||||||
|
handleIntent(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the incoming intent.
|
||||||
|
*
|
||||||
|
* @param intent Intent
|
||||||
|
*/
|
||||||
|
private void handleIntent(Intent intent) {
|
||||||
|
// Intent is consumed
|
||||||
|
setIntent(null);
|
||||||
|
|
||||||
|
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||||
|
// Perform a search query
|
||||||
|
String query = intent.getStringExtra(SearchManager.QUERY);
|
||||||
|
|
||||||
|
// Collapse the SearchView
|
||||||
|
if (searchItem != null) {
|
||||||
|
searchItem.collapseActionView();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the query
|
||||||
|
SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this, RecentSuggestionsProvider.AUTHORITY, RecentSuggestionsProvider.MODE);
|
||||||
|
suggestions.saveRecentQuery(query, null);
|
||||||
|
|
||||||
|
EventBus.getDefault().post(new SearchEvent(query));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a search query.
|
||||||
|
*
|
||||||
|
* @param query Query
|
||||||
|
*/
|
||||||
|
private void searchQuery(String query) {
|
||||||
|
SearchView searchView = (SearchView) searchItem.getActionView();
|
||||||
|
searchView.setQuery(query, true);
|
||||||
|
searchView.setIconified(query == null);
|
||||||
|
searchView.clearFocus();
|
||||||
|
drawerLayout.closeDrawers();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An advanced search event has been fired.
|
||||||
|
*
|
||||||
|
* @param event Advanced search event
|
||||||
|
*/
|
||||||
|
public void onEventMainThread(AdvancedSearchEvent event) {
|
||||||
|
searchQuery(event.getQuery());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.sismics.docs.activity;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
|
||||||
|
import com.sismics.docs.fragment.SettingsFragment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Settings activity.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class SettingsActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
if (getSupportActionBar() != null) {
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
getSupportActionBar().setHomeButtonEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display the fragment as the main content.
|
||||||
|
getFragmentManager().beginTransaction()
|
||||||
|
.replace(android.R.id.content, new SettingsFragment())
|
||||||
|
.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.home:
|
||||||
|
finish();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package com.sismics.docs.adapter;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.BaseAdapter;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ACL list adapter.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class AclListAdapter extends BaseAdapter {
|
||||||
|
/**
|
||||||
|
* Shares.
|
||||||
|
*/
|
||||||
|
private List<JSONObject> acls;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ACL list adapter.
|
||||||
|
*
|
||||||
|
* @param acls ACLs
|
||||||
|
*/
|
||||||
|
public AclListAdapter(JSONArray acls) {
|
||||||
|
this.acls = new ArrayList<>();
|
||||||
|
|
||||||
|
// Extract only share ACLs
|
||||||
|
for (int i = 0; i < acls.length(); i++) {
|
||||||
|
JSONObject acl = acls.optJSONObject(i);
|
||||||
|
this.acls.add(acl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return acls.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONObject getItem(int position) {
|
||||||
|
return acls.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return getItem(position).optString("id").hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View view, final ViewGroup parent) {
|
||||||
|
if (view == null) {
|
||||||
|
LayoutInflater vi = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
view = vi.inflate(R.layout.acl_list_item, parent, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill the view
|
||||||
|
final JSONObject acl = getItem(position);
|
||||||
|
TextView typeTextView = (TextView) view.findViewById(R.id.typeTextView);
|
||||||
|
typeTextView.setText(acl.optString("type"));
|
||||||
|
TextView nameTextView = (TextView) view.findViewById(R.id.nameTextView);
|
||||||
|
nameTextView.setText(acl.optString("name"));
|
||||||
|
TextView permTextView = (TextView) view.findViewById(R.id.permTextView);
|
||||||
|
permTextView.setText(acl.optString("perm"));
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
package com.sismics.docs.adapter;
|
||||||
|
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.text.format.DateFormat;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
import com.sismics.docs.util.TagUtil;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter of documents.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class DocListAdapter extends RecyclerView.Adapter<DocListAdapter.ViewHolder> {
|
||||||
|
/**
|
||||||
|
* Displayed documents.
|
||||||
|
*/
|
||||||
|
private List<JSONObject> documents;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewHolder.
|
||||||
|
*/
|
||||||
|
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
public TextView titleTextView;
|
||||||
|
public TextView subtitleTextView;
|
||||||
|
public TextView dateTextView;
|
||||||
|
public ImageView sharedImageView;
|
||||||
|
|
||||||
|
public ViewHolder(View v) {
|
||||||
|
super(v);
|
||||||
|
titleTextView = (TextView) v.findViewById(R.id.titleTextView);
|
||||||
|
subtitleTextView = (TextView) v.findViewById(R.id.subtitleTextView);
|
||||||
|
dateTextView = (TextView) v.findViewById(R.id.dateTextView);
|
||||||
|
sharedImageView = (ImageView) v.findViewById(R.id.sharedImageView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor.
|
||||||
|
*/
|
||||||
|
public DocListAdapter() {
|
||||||
|
// Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DocListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
View v = LayoutInflater.from(parent.getContext()).
|
||||||
|
inflate(R.layout.doc_list_item, parent, false);
|
||||||
|
|
||||||
|
return new ViewHolder(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||||
|
JSONObject document = documents.get(position);
|
||||||
|
|
||||||
|
holder.titleTextView.setText(document.optString("title"));
|
||||||
|
|
||||||
|
JSONArray tags = document.optJSONArray("tags");
|
||||||
|
holder.subtitleTextView.setText(TagUtil.buildSpannable(tags));
|
||||||
|
|
||||||
|
String date = DateFormat.getDateFormat(holder.dateTextView.getContext()).format(new Date(document.optLong("create_date")));
|
||||||
|
holder.dateTextView.setText(date);
|
||||||
|
|
||||||
|
holder.sharedImageView.setVisibility(document.optBoolean("shared") ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
if (documents == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return documents.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an item at a given position.
|
||||||
|
*
|
||||||
|
* @param position Item position
|
||||||
|
* @return Item
|
||||||
|
*/
|
||||||
|
public JSONObject getItemAt(int position) {
|
||||||
|
if (documents == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return documents.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the documents.
|
||||||
|
*/
|
||||||
|
public void clearDocuments() {
|
||||||
|
documents = new ArrayList<>();
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add documents to display.
|
||||||
|
*
|
||||||
|
* @param documents Documents
|
||||||
|
*/
|
||||||
|
public void addDocuments(JSONArray documents) {
|
||||||
|
if (this.documents == null) {
|
||||||
|
this.documents = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < documents.length(); i++) {
|
||||||
|
this.documents.add(documents.optJSONObject(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a document.
|
||||||
|
*
|
||||||
|
* @param document Document
|
||||||
|
*/
|
||||||
|
public void updateDocument(JSONObject document) {
|
||||||
|
for (int i = 0; i < documents.size(); i++) {
|
||||||
|
JSONObject currentDoc = documents.get(i);
|
||||||
|
if (currentDoc.optString("id").equals(document.optString("id"))) {
|
||||||
|
// This document has been modified
|
||||||
|
documents.set(i, document);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a document.
|
||||||
|
*
|
||||||
|
* @param documentId Document ID
|
||||||
|
*/
|
||||||
|
public void deleteDocument(String documentId) {
|
||||||
|
for (int i = 0; i < documents.size(); i++) {
|
||||||
|
JSONObject currentDoc = documents.get(i);
|
||||||
|
if (currentDoc.optString("id").equals(documentId)) {
|
||||||
|
// This document has been deleted
|
||||||
|
documents.remove(i);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package com.sismics.docs.adapter;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.v4.view.PagerAdapter;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
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.PreferenceUtil;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import it.sephiroth.android.library.imagezoom.ImageViewTouch;
|
||||||
|
import it.sephiroth.android.library.imagezoom.ImageViewTouchBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class FilePagerAdapter extends PagerAdapter {
|
||||||
|
/**
|
||||||
|
* Files list.
|
||||||
|
*/
|
||||||
|
private List<JSONObject> files;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AQuery.
|
||||||
|
*/
|
||||||
|
private AQuery aq;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context.
|
||||||
|
*/
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auth token used to download files.
|
||||||
|
*/
|
||||||
|
private String authToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File pager adapter.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param filesArray Files
|
||||||
|
*/
|
||||||
|
public FilePagerAdapter(Context context, JSONArray filesArray) {
|
||||||
|
this.files = new ArrayList<>();
|
||||||
|
for (int i = 0; i < filesArray.length(); i++) {
|
||||||
|
files.add(filesArray.optJSONObject(i));
|
||||||
|
}
|
||||||
|
this.context = context;
|
||||||
|
this.authToken = PreferenceUtil.getAuthToken(context);
|
||||||
|
aq = new AQuery(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object instantiateItem(ViewGroup container, int position) {
|
||||||
|
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);
|
||||||
|
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));
|
||||||
|
|
||||||
|
fileImageView.setDisplayType(ImageViewTouchBase.DisplayType.FIT_TO_SCREEN);
|
||||||
|
|
||||||
|
container.addView(view, 0);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroyItem(ViewGroup container, int position, Object object) {
|
||||||
|
container.removeView((View) object);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
if (files == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return files.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isViewFromObject(View view, Object object) {
|
||||||
|
return view == object;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the object at a given position.
|
||||||
|
*
|
||||||
|
* @param position Position
|
||||||
|
* @return Object
|
||||||
|
*/
|
||||||
|
public JSONObject getObjectAt(int position) {
|
||||||
|
if (files == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return files.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a file.
|
||||||
|
*
|
||||||
|
* @param fileId File ID
|
||||||
|
*/
|
||||||
|
public void remove(String fileId) {
|
||||||
|
if (files == null || fileId == null) return;
|
||||||
|
|
||||||
|
for (JSONObject file : files) {
|
||||||
|
if (fileId.equals(file.optString("id"))) {
|
||||||
|
files.remove(file);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemPosition(Object object) {
|
||||||
|
return POSITION_NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package com.sismics.docs.adapter;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.BaseAdapter;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Languages adapter.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class LanguageAdapter extends BaseAdapter {
|
||||||
|
/**
|
||||||
|
* Context.
|
||||||
|
*/
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
private List<Language> languageList;
|
||||||
|
|
||||||
|
public LanguageAdapter(Context context, boolean noValue) {
|
||||||
|
this.context = context;
|
||||||
|
this.languageList = new ArrayList<>();
|
||||||
|
if (noValue) {
|
||||||
|
languageList.add(new Language("", R.string.all_languages, 0));
|
||||||
|
}
|
||||||
|
languageList.add(new Language("fra", R.string.language_french, R.drawable.fra));
|
||||||
|
languageList.add(new Language("eng", R.string.language_english, R.drawable.eng));
|
||||||
|
languageList.add(new Language("jpn", R.string.language_japanese, R.drawable.jpn));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return languageList.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Language getItem(int position) {
|
||||||
|
if (position >= languageList.size()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return languageList.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return getItem(position).id.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View view, ViewGroup parent) {
|
||||||
|
if (view == null) {
|
||||||
|
LayoutInflater vi = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
view = vi.inflate(R.layout.language_list_item, parent, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill the view
|
||||||
|
Language language = getItem(position);
|
||||||
|
TextView languageTextView = (TextView) view.findViewById(R.id.languageTextView);
|
||||||
|
languageTextView.setText(context.getText(language.name));
|
||||||
|
languageTextView.setCompoundDrawablesWithIntrinsicBounds(language.drawable, 0, 0, 0);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the position of a language.
|
||||||
|
* 0 if it doesn't exists.
|
||||||
|
*
|
||||||
|
* @param languageId Language ID
|
||||||
|
* @return Position
|
||||||
|
*/
|
||||||
|
public int getItemPosition(String languageId) {
|
||||||
|
for (Language language : languageList) {
|
||||||
|
if (language.id.equals(languageId)) {
|
||||||
|
return languageList.indexOf(language);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A language.
|
||||||
|
*/
|
||||||
|
public static class Language {
|
||||||
|
private String id;
|
||||||
|
private int name;
|
||||||
|
private int drawable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A language.
|
||||||
|
*
|
||||||
|
* @param id Language ID
|
||||||
|
* @param name Language name
|
||||||
|
* @param drawable Language drawable
|
||||||
|
*/
|
||||||
|
public Language(String id, int name, int drawable) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.drawable = drawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package com.sismics.docs.adapter;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.BaseAdapter;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
import com.sismics.docs.event.ShareDeleteEvent;
|
||||||
|
import com.sismics.docs.event.ShareSendEvent;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.greenrobot.event.EventBus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Share list adapter.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class ShareListAdapter extends BaseAdapter {
|
||||||
|
/**
|
||||||
|
* Shares.
|
||||||
|
*/
|
||||||
|
private List<JSONObject> acls;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Share list adapter.
|
||||||
|
*
|
||||||
|
* @param acls ACLs
|
||||||
|
*/
|
||||||
|
public ShareListAdapter(JSONArray acls) {
|
||||||
|
this.acls = new ArrayList<>();
|
||||||
|
|
||||||
|
// Extract only share ACLs
|
||||||
|
for (int i = 0; i < acls.length(); i++) {
|
||||||
|
JSONObject acl = acls.optJSONObject(i);
|
||||||
|
if (acl.optString("type").equals("SHARE")) {
|
||||||
|
this.acls.add(acl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return acls.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONObject getItem(int position) {
|
||||||
|
return acls.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return getItem(position).optString("id").hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View view, final ViewGroup parent) {
|
||||||
|
if (view == null) {
|
||||||
|
LayoutInflater vi = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
view = vi.inflate(R.layout.share_list_item, parent, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill the view
|
||||||
|
final JSONObject acl = getItem(position);
|
||||||
|
String name = acl.optString("name");
|
||||||
|
TextView shareTextView = (TextView) view.findViewById(R.id.shareTextView);
|
||||||
|
shareTextView.setText(name.isEmpty() ? parent.getContext().getString(R.string.share_default_name) : name);
|
||||||
|
|
||||||
|
// Delete a share
|
||||||
|
ImageButton shareDeleteButton = (ImageButton) view.findViewById(R.id.shareDeleteButton);
|
||||||
|
shareDeleteButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
EventBus.getDefault().post(new ShareDeleteEvent(acl.optString("id")));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send the link
|
||||||
|
ImageButton shareSendButton = (ImageButton) view.findViewById(R.id.shareSendButton);
|
||||||
|
shareSendButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
EventBus.getDefault().post(new ShareSendEvent(acl));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.sismics.docs.adapter;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
import com.tokenautocomplete.FilteredArrayAdapter;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag auto-complete adapter.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class TagAutoCompleteAdapter extends FilteredArrayAdapter<JSONObject> {
|
||||||
|
public TagAutoCompleteAdapter(Context context, int resource, List<JSONObject> objects) {
|
||||||
|
super(context, resource, objects);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View view, ViewGroup parent) {
|
||||||
|
if (view == null) {
|
||||||
|
LayoutInflater vi = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
view = vi.inflate(R.layout.tag_autocomplete_item, parent, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill the view
|
||||||
|
JSONObject tag = getItem(position);
|
||||||
|
TextView textView = (TextView) view;
|
||||||
|
textView.setText(tag.optString("name"));
|
||||||
|
|
||||||
|
Drawable drawable = textView.getCompoundDrawables()[0].mutate();
|
||||||
|
drawable.setColorFilter(Color.parseColor(tag.optString("color")), PorterDuff.Mode.MULTIPLY);
|
||||||
|
textView.setCompoundDrawables(drawable, null, null, null);
|
||||||
|
textView.invalidate();
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean keepObject(JSONObject tag, String s) {
|
||||||
|
return tag.optString("name").toLowerCase().startsWith(s.toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package com.sismics.docs.adapter;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.BaseAdapter;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag list adapter.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class TagListAdapter extends BaseAdapter {
|
||||||
|
/**
|
||||||
|
* Tags.
|
||||||
|
*/
|
||||||
|
private List<JSONObject> tags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag list adapter.
|
||||||
|
*
|
||||||
|
* @param tagsArray Tags
|
||||||
|
*/
|
||||||
|
public TagListAdapter(JSONArray tagsArray) {
|
||||||
|
this.tags = new ArrayList<>();
|
||||||
|
for (int i = 0; i < tagsArray.length(); i++) {
|
||||||
|
tags.add(tagsArray.optJSONObject(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort tags by count desc
|
||||||
|
Collections.sort(tags, new Comparator<JSONObject>() {
|
||||||
|
@Override
|
||||||
|
public int compare(JSONObject lhs, JSONObject rhs) {
|
||||||
|
return lhs.optInt("count") < rhs.optInt("count") ? 1 : -1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return tags.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONObject getItem(int position) {
|
||||||
|
return tags.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return getItem(position).optString("id").hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View view, ViewGroup parent) {
|
||||||
|
if (view == null) {
|
||||||
|
LayoutInflater vi = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
view = vi.inflate(R.layout.tag_list_item, parent, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill the view
|
||||||
|
JSONObject tag = getItem(position);
|
||||||
|
TextView tagTextView = (TextView) view.findViewById(R.id.tagTextView);
|
||||||
|
tagTextView.setText(tag.optString("name"));
|
||||||
|
TextView tagCountTextView = (TextView) view.findViewById(R.id.tagCountTextView);
|
||||||
|
tagCountTextView.setText(tag.optString("count"));
|
||||||
|
|
||||||
|
// Label color filtering
|
||||||
|
ImageView labelImageView = (ImageView) view.findViewById(R.id.labelImageView);
|
||||||
|
Drawable labelDrawable = labelImageView.getDrawable().mutate();
|
||||||
|
labelDrawable.setColorFilter(Color.parseColor(tag.optString("color")), PorterDuff.Mode.MULTIPLY);
|
||||||
|
labelImageView.setImageDrawable(labelDrawable);
|
||||||
|
labelImageView.invalidate();
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package com.sismics.docs.dummy;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class for providing sample content for user interfaces created by
|
|
||||||
* Android template wizards.
|
|
||||||
* <p>
|
|
||||||
* TODO: Replace all uses of this class before publishing your app.
|
|
||||||
*/
|
|
||||||
public class DummyContent {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An array of sample (dummy) items.
|
|
||||||
*/
|
|
||||||
public static List<DummyItem> ITEMS = new ArrayList<DummyItem>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A map of sample (dummy) items, by ID.
|
|
||||||
*/
|
|
||||||
public static Map<String, DummyItem> ITEM_MAP = new HashMap<String, DummyItem>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
// Add 3 sample items.
|
|
||||||
addItem(new DummyItem("1", "Item 1"));
|
|
||||||
addItem(new DummyItem("2", "Item 2"));
|
|
||||||
addItem(new DummyItem("3", "Item 3"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void addItem(DummyItem item) {
|
|
||||||
ITEMS.add(item);
|
|
||||||
ITEM_MAP.put(item.id, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A dummy item representing a piece of content.
|
|
||||||
*/
|
|
||||||
public static class DummyItem {
|
|
||||||
public String id;
|
|
||||||
public String content;
|
|
||||||
|
|
||||||
public DummyItem(String id, String content) {
|
|
||||||
this.id = id;
|
|
||||||
this.content = content;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.sismics.docs.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advanced search event.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class AdvancedSearchEvent {
|
||||||
|
/**
|
||||||
|
* Search query.
|
||||||
|
*/
|
||||||
|
private String query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an advanced search event.
|
||||||
|
*
|
||||||
|
* @param query Query
|
||||||
|
*/
|
||||||
|
public AdvancedSearchEvent(String query) {
|
||||||
|
this.query = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of query.
|
||||||
|
*
|
||||||
|
* @return query
|
||||||
|
*/
|
||||||
|
public String getQuery() {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.sismics.docs.event;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Document add event.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class DocumentAddEvent {
|
||||||
|
/**
|
||||||
|
* Document.
|
||||||
|
*/
|
||||||
|
private JSONObject document;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a document add event.
|
||||||
|
*
|
||||||
|
* @param document Document
|
||||||
|
*/
|
||||||
|
public DocumentAddEvent(JSONObject document) {
|
||||||
|
this.document = document;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of document.
|
||||||
|
*
|
||||||
|
* @return document
|
||||||
|
*/
|
||||||
|
public JSONObject getDocument() {
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.sismics.docs.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Document delete event.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class DocumentDeleteEvent {
|
||||||
|
/**
|
||||||
|
* Document ID.
|
||||||
|
*/
|
||||||
|
private String documentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a document delete event.
|
||||||
|
*
|
||||||
|
* @param documentId Document ID
|
||||||
|
*/
|
||||||
|
public DocumentDeleteEvent(String documentId) {
|
||||||
|
this.documentId = documentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of documentId.
|
||||||
|
*
|
||||||
|
* @return documentId
|
||||||
|
*/
|
||||||
|
public String getDocumentId() {
|
||||||
|
return documentId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.sismics.docs.event;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Document edit event.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class DocumentEditEvent {
|
||||||
|
/**
|
||||||
|
* Document.
|
||||||
|
*/
|
||||||
|
private JSONObject document;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a document edit event.
|
||||||
|
*
|
||||||
|
* @param document Document
|
||||||
|
*/
|
||||||
|
public DocumentEditEvent(JSONObject document) {
|
||||||
|
this.document = document;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of document.
|
||||||
|
*
|
||||||
|
* @return document
|
||||||
|
*/
|
||||||
|
public JSONObject getDocument() {
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.sismics.docs.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class DocumentFullscreenEvent {
|
||||||
|
|
||||||
|
private boolean fullscreen;
|
||||||
|
|
||||||
|
public DocumentFullscreenEvent(boolean fullscreen) {
|
||||||
|
this.fullscreen = fullscreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFullscreen() {
|
||||||
|
return fullscreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.sismics.docs.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File add event.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class FileAddEvent {
|
||||||
|
/**
|
||||||
|
* Document ID.
|
||||||
|
*/
|
||||||
|
private String documentId;
|
||||||
|
/**
|
||||||
|
* File ID.
|
||||||
|
*/
|
||||||
|
private String fileId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a file add event.
|
||||||
|
*
|
||||||
|
* @param fileId File ID
|
||||||
|
*/
|
||||||
|
public FileAddEvent(String documentId, String fileId) {
|
||||||
|
this.documentId = documentId;
|
||||||
|
this.fileId = fileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of fileId.
|
||||||
|
*
|
||||||
|
* @return fileId
|
||||||
|
*/
|
||||||
|
public String getFileId() {
|
||||||
|
return fileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of documentId.
|
||||||
|
*
|
||||||
|
* @return documentId
|
||||||
|
*/
|
||||||
|
public String getDocumentId() {
|
||||||
|
return documentId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.sismics.docs.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File delete event.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class FileDeleteEvent {
|
||||||
|
/**
|
||||||
|
* File ID.
|
||||||
|
*/
|
||||||
|
private String fileId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a document delete event.
|
||||||
|
*
|
||||||
|
* @param fileId File ID
|
||||||
|
*/
|
||||||
|
public FileDeleteEvent(String fileId) {
|
||||||
|
this.fileId = fileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of fileId.
|
||||||
|
*
|
||||||
|
* @return fileId
|
||||||
|
*/
|
||||||
|
public String getFileId() {
|
||||||
|
return fileId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.sismics.docs.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search event.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class SearchEvent {
|
||||||
|
/**
|
||||||
|
* Search query.
|
||||||
|
*/
|
||||||
|
private String query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a search event.
|
||||||
|
*
|
||||||
|
* @param query Query
|
||||||
|
*/
|
||||||
|
public SearchEvent(String query) {
|
||||||
|
this.query = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of query.
|
||||||
|
*
|
||||||
|
* @return query
|
||||||
|
*/
|
||||||
|
public String getQuery() {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.sismics.docs.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Share delete event.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class ShareDeleteEvent {
|
||||||
|
/**
|
||||||
|
* Share ID
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a share delete event.
|
||||||
|
*
|
||||||
|
* @param id Share ID
|
||||||
|
*/
|
||||||
|
public ShareDeleteEvent(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.sismics.docs.event;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Share send event.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class ShareSendEvent {
|
||||||
|
/**
|
||||||
|
* ACL data.
|
||||||
|
*/
|
||||||
|
private JSONObject acl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a share send event.
|
||||||
|
*
|
||||||
|
* @param acl ACL data
|
||||||
|
*/
|
||||||
|
public ShareSendEvent(JSONObject acl) {
|
||||||
|
this.acl = acl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSONObject getAcl() {
|
||||||
|
return acl;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
package com.sismics.docs.fragment;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.widget.SwipeRefreshLayout;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
import com.sismics.docs.activity.DocumentEditActivity;
|
||||||
|
import com.sismics.docs.activity.DocumentViewActivity;
|
||||||
|
import com.sismics.docs.adapter.DocListAdapter;
|
||||||
|
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.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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class DocListFragment extends Fragment {
|
||||||
|
/**
|
||||||
|
* Documents adapter.
|
||||||
|
*/
|
||||||
|
private DocListAdapter adapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search query.
|
||||||
|
*/
|
||||||
|
private String query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request code of adding document.
|
||||||
|
*/
|
||||||
|
private static final int REQUEST_CODE_ADD_DOCUMENT = 1;
|
||||||
|
|
||||||
|
// View cache
|
||||||
|
private EmptyRecyclerView recyclerView;
|
||||||
|
private SwipeRefreshLayout swipeRefreshLayout;
|
||||||
|
|
||||||
|
// Infinite scrolling things
|
||||||
|
private boolean loading = true;
|
||||||
|
private int previousTotal = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
final View view = inflater.inflate(R.layout.doc_list_fragment, container, false);
|
||||||
|
|
||||||
|
// Configure the RecyclerView
|
||||||
|
recyclerView = (EmptyRecyclerView) view.findViewById(R.id.docList);
|
||||||
|
adapter = new DocListAdapter();
|
||||||
|
recyclerView.setAdapter(adapter);
|
||||||
|
recyclerView.setHasFixedSize(true);
|
||||||
|
recyclerView.setLongClickable(true);
|
||||||
|
recyclerView.addItemDecoration(new DividerItemDecoration(getResources().getDrawable(R.drawable.abc_list_divider_mtrl_alpha)));
|
||||||
|
|
||||||
|
// Configure the LayoutManager
|
||||||
|
final LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
|
||||||
|
recyclerView.setLayoutManager(layoutManager);
|
||||||
|
|
||||||
|
// Configure the swipe refresh layout
|
||||||
|
swipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipeRefreshLayout);
|
||||||
|
swipeRefreshLayout.setColorSchemeResources(android.R.color.holo_blue_bright,
|
||||||
|
android.R.color.holo_green_light,
|
||||||
|
android.R.color.holo_orange_light,
|
||||||
|
android.R.color.holo_red_light);
|
||||||
|
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
|
||||||
|
@Override
|
||||||
|
public void onRefresh() {
|
||||||
|
loadDocuments(view, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Document opening
|
||||||
|
recyclerView.addOnItemTouchListener(new RecyclerItemClickListener(getActivity(), new RecyclerItemClickListener.OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemClick(View view, int position) {
|
||||||
|
JSONObject document = adapter.getItemAt(position);
|
||||||
|
if (document != null) {
|
||||||
|
openDocument(document);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Infinite scrolling
|
||||||
|
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy);
|
||||||
|
|
||||||
|
int visibleItemCount = recyclerView.getChildCount();
|
||||||
|
int totalItemCount = layoutManager.getItemCount();
|
||||||
|
int firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
if (totalItemCount > previousTotal) {
|
||||||
|
loading = false;
|
||||||
|
previousTotal = totalItemCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!loading && totalItemCount - visibleItemCount <= firstVisibleItem + 3) {
|
||||||
|
loadDocuments(getView(), false);
|
||||||
|
loading = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add document button
|
||||||
|
ImageButton addDocumentButton = (ImageButton) view.findViewById(R.id.addDocumentButton);
|
||||||
|
addDocumentButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
Intent intent = new Intent(getActivity(), DocumentEditActivity.class);
|
||||||
|
startActivityForResult(intent, REQUEST_CODE_ADD_DOCUMENT);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Grab the documents
|
||||||
|
loadDocuments(view, true);
|
||||||
|
|
||||||
|
EventBus.getDefault().register(this);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
EventBus.getDefault().unregister(this);
|
||||||
|
super.onDestroyView();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A search event has been fired.
|
||||||
|
*
|
||||||
|
* @param event Search event
|
||||||
|
*/
|
||||||
|
public void onEventMainThread(SearchEvent event) {
|
||||||
|
query = event.getQuery();
|
||||||
|
loadDocuments(getView(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A document edit event has been fired.
|
||||||
|
*
|
||||||
|
* @param event Document edit event
|
||||||
|
*/
|
||||||
|
public void onEventMainThread(DocumentEditEvent event) {
|
||||||
|
adapter.updateDocument(event.getDocument());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A document delete event has been fired.
|
||||||
|
*
|
||||||
|
* @param event Document delete event
|
||||||
|
*/
|
||||||
|
public void onEventMainThread(DocumentDeleteEvent event) {
|
||||||
|
adapter.deleteDocument(event.getDocumentId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A document add event has been fired.
|
||||||
|
*
|
||||||
|
* @param event Document add event
|
||||||
|
*/
|
||||||
|
public void onEventMainThread(DocumentAddEvent event) {
|
||||||
|
// Refresh the list, maybe the new document fit in it
|
||||||
|
loadDocuments(getView(), true);
|
||||||
|
|
||||||
|
// Open the newly created document
|
||||||
|
openDocument(event.getDocument());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a document.
|
||||||
|
*
|
||||||
|
* @param document Document to open
|
||||||
|
*/
|
||||||
|
private void openDocument(JSONObject document) {
|
||||||
|
Intent intent = new Intent(getActivity(), DocumentViewActivity.class);
|
||||||
|
intent.putExtra("document", document.toString());
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the document list.
|
||||||
|
*
|
||||||
|
* @param view View
|
||||||
|
* @param reset If true, reload the documents
|
||||||
|
*/
|
||||||
|
private void loadDocuments(final View view, final boolean reset) {
|
||||||
|
if (view == null) return;
|
||||||
|
final View progressBar = view.findViewById(R.id.progressBar);
|
||||||
|
final TextView documentsEmptyView = (TextView) view.findViewById(R.id.documentsEmptyView);
|
||||||
|
|
||||||
|
if (reset) {
|
||||||
|
loading = true;
|
||||||
|
previousTotal = 0;
|
||||||
|
adapter.clearDocuments();
|
||||||
|
} else {
|
||||||
|
swipeRefreshLayout.setRefreshing(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
recyclerView.setEmptyView(progressBar);
|
||||||
|
|
||||||
|
DocumentResource.list(getActivity(), reset ? 0 : adapter.getItemCount(), query, new JsonHttpResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(int statusCode, Header[] headers, 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) {
|
||||||
|
documentsEmptyView.setText(R.string.error_loading_documents);
|
||||||
|
recyclerView.setEmptyView(documentsEmptyView);
|
||||||
|
|
||||||
|
if (!reset) {
|
||||||
|
// We are loading a new page, so the empty view won't be visible, pop a toast
|
||||||
|
Toast.makeText(getActivity(), R.string.error_loading_documents, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinish() {
|
||||||
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
package com.sismics.docs.fragment;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v4.app.DialogFragment;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
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.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 de.greenrobot.event.EventBus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Document sharing dialog fragment.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class DocShareFragment extends DialogFragment {
|
||||||
|
/**
|
||||||
|
* Document data.
|
||||||
|
*/
|
||||||
|
private JSONObject document;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Document sharing dialog fragment
|
||||||
|
* @param id Document ID
|
||||||
|
*/
|
||||||
|
public static DocShareFragment newInstance(String id) {
|
||||||
|
DocShareFragment fragment = new DocShareFragment();
|
||||||
|
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_share_dialog, null);
|
||||||
|
final Button shareAddButton = (Button) view.findViewById(R.id.shareAddButton);
|
||||||
|
final EditText shareNameEditText = (EditText) view.findViewById(R.id.shareNameEditText);
|
||||||
|
|
||||||
|
// Add a share
|
||||||
|
shareAddButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
shareNameEditText.setEnabled(false);
|
||||||
|
shareAddButton.setEnabled(false);
|
||||||
|
|
||||||
|
ShareResource.add(getActivity(), getArguments().getString("id"), shareNameEditText.getText().toString(),
|
||||||
|
new JsonHttpResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
|
||||||
|
shareNameEditText.setText("");
|
||||||
|
loadShares(getDialog().getWindow().getDecorView());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
|
||||||
|
Toast.makeText(getActivity(), R.string.error_adding_share, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinish() {
|
||||||
|
shareNameEditText.setEnabled(true);
|
||||||
|
shareAddButton.setEnabled(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the shares
|
||||||
|
loadShares(view);
|
||||||
|
|
||||||
|
// Build the dialog
|
||||||
|
builder.setView(view)
|
||||||
|
.setNegativeButton(R.string.close, new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
getDialog().cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return builder.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the shares.
|
||||||
|
*
|
||||||
|
* @param view View
|
||||||
|
*/
|
||||||
|
private void loadShares(View view) {
|
||||||
|
if (isDetached()) return;
|
||||||
|
|
||||||
|
final ListView shareListView = (ListView) view.findViewById(R.id.shareListView);
|
||||||
|
final TextView shareEmptyView = (TextView) view.findViewById(R.id.shareEmptyView);
|
||||||
|
final ProgressBar shareProgressBar = (ProgressBar) view.findViewById(R.id.shareProgressBar);
|
||||||
|
|
||||||
|
shareListView.setEmptyView(shareProgressBar);
|
||||||
|
DocumentResource.get(getActivity(), getArguments().getString("id"), new JsonHttpResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
|
||||||
|
document = response;
|
||||||
|
JSONArray acls = response.optJSONArray("acls");
|
||||||
|
shareProgressBar.setVisibility(View.GONE);
|
||||||
|
shareListView.setEmptyView(shareEmptyView);
|
||||||
|
shareListView.setAdapter(new ShareListAdapter(acls));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
|
||||||
|
getDialog().cancel();
|
||||||
|
Toast.makeText(getActivity(), R.string.error_loading_shares, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onEventMainThread(ShareDeleteEvent event) {
|
||||||
|
ShareResource.delete(getActivity(), event.getId(), new JsonHttpResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
|
||||||
|
loadShares(getDialog().getWindow().getDecorView());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
|
||||||
|
Toast.makeText(getActivity(), R.string.error_deleting_share, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onEventMainThread(ShareSendEvent event) {
|
||||||
|
if (document == null) return;
|
||||||
|
|
||||||
|
// Build the share link
|
||||||
|
String serverUrl = PreferenceUtil.getServerUrl(getActivity());
|
||||||
|
String link = serverUrl + "/share.html#/share/" + document.optString("id") + "/" + event.getAcl().optString("id");
|
||||||
|
|
||||||
|
// Build the intent
|
||||||
|
Context context = getActivity();
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setAction(Intent.ACTION_SEND);
|
||||||
|
intent.putExtra(Intent.EXTRA_SUBJECT, document.optString("title"));
|
||||||
|
intent.putExtra(Intent.EXTRA_TEXT, link);
|
||||||
|
intent.setType("text/plain");
|
||||||
|
|
||||||
|
// Open the target chooser
|
||||||
|
context.startActivity(Intent.createChooser(intent, context.getText(R.string.send_share_to)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
EventBus.getDefault().register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
EventBus.getDefault().unregister(this);
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
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 android.view.WindowManager;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.Spinner;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
import com.sismics.docs.adapter.LanguageAdapter;
|
||||||
|
import com.sismics.docs.adapter.TagAutoCompleteAdapter;
|
||||||
|
import com.sismics.docs.event.AdvancedSearchEvent;
|
||||||
|
import com.sismics.docs.ui.view.DatePickerView;
|
||||||
|
import com.sismics.docs.ui.view.TagsCompleteTextView;
|
||||||
|
import com.sismics.docs.util.PreferenceUtil;
|
||||||
|
import com.sismics.docs.util.SearchQueryBuilder;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.greenrobot.event.EventBus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advanced search fragment.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class SearchFragment extends DialogFragment {
|
||||||
|
/**
|
||||||
|
* Document sharing dialog fragment
|
||||||
|
*/
|
||||||
|
public static SearchFragment newInstance() {
|
||||||
|
SearchFragment fragment = new SearchFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
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.search_dialog, null);
|
||||||
|
final EditText searchEditText = (EditText) view.findViewById(R.id.searchEditText);
|
||||||
|
final EditText fulltextEditText = (EditText) view.findViewById(R.id.fulltextEditText);
|
||||||
|
final CheckBox sharedCheckbox = (CheckBox) view.findViewById(R.id.sharedCheckbox);
|
||||||
|
final Spinner languageSpinner = (Spinner) view.findViewById(R.id.languageSpinner);
|
||||||
|
final DatePickerView beforeDatePicker = (DatePickerView) view.findViewById(R.id.beforeDatePicker);
|
||||||
|
final DatePickerView afterDatePicker = (DatePickerView) view.findViewById(R.id.afterDatePicker);
|
||||||
|
final TagsCompleteTextView tagsEditText = (TagsCompleteTextView) view.findViewById(R.id.tagsEditText);
|
||||||
|
|
||||||
|
// Language spinner
|
||||||
|
LanguageAdapter languageAdapter = new LanguageAdapter(getActivity(), true);
|
||||||
|
languageSpinner.setAdapter(languageAdapter);
|
||||||
|
|
||||||
|
// Tags auto-complete
|
||||||
|
JSONObject tags = PreferenceUtil.getCachedJson(getActivity(), PreferenceUtil.PREF_CACHED_TAGS_JSON);
|
||||||
|
if (tags == null) {
|
||||||
|
Dialog dialog = builder.create();
|
||||||
|
dialog.cancel();
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
JSONArray tagArray = tags.optJSONArray("stats");
|
||||||
|
|
||||||
|
List<JSONObject> tagList = new ArrayList<>();
|
||||||
|
for (int i = 0; i < tagArray.length(); i++) {
|
||||||
|
tagList.add(tagArray.optJSONObject(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
tagsEditText.allowDuplicates(false);
|
||||||
|
tagsEditText.setAdapter(new TagAutoCompleteAdapter(getActivity(), 0, tagList));
|
||||||
|
|
||||||
|
// Build the dialog
|
||||||
|
builder.setView(view)
|
||||||
|
.setPositiveButton(R.string.search, new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
// Build the simple criterias
|
||||||
|
SearchQueryBuilder queryBuilder = new SearchQueryBuilder()
|
||||||
|
.simpleSearch(searchEditText.getText().toString())
|
||||||
|
.shared(sharedCheckbox.isChecked())
|
||||||
|
.language(((LanguageAdapter.Language) languageSpinner.getSelectedItem()).getId())
|
||||||
|
.before(beforeDatePicker.getDate())
|
||||||
|
.after(afterDatePicker.getDate());
|
||||||
|
|
||||||
|
// Fulltext criteria
|
||||||
|
String fulltextCriteria = fulltextEditText.getText().toString();
|
||||||
|
if (!fulltextCriteria.trim().isEmpty()) {
|
||||||
|
String[] criterias = fulltextCriteria.split(" ");
|
||||||
|
for (String criteria : criterias) {
|
||||||
|
queryBuilder.fulltextSearch(criteria);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tags criteria
|
||||||
|
for (Object object : tagsEditText.getObjects()) {
|
||||||
|
JSONObject tag = (JSONObject) object;
|
||||||
|
queryBuilder.tag(tag.optString("name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the advanced search event
|
||||||
|
EventBus.getDefault().post(new AdvancedSearchEvent(queryBuilder.build()));
|
||||||
|
|
||||||
|
getDialog().cancel();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
getDialog().cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Dialog dialog = builder.create();
|
||||||
|
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package com.sismics.docs.fragment;
|
||||||
|
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.preference.ListPreference;
|
||||||
|
import android.preference.Preference;
|
||||||
|
import android.preference.PreferenceFragment;
|
||||||
|
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.PreferenceUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Settings fragment.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
// Load the preferences from an XML resource
|
||||||
|
addPreferencesFromResource(R.xml.preferences);
|
||||||
|
|
||||||
|
// Initialize summaries
|
||||||
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||||
|
onSharedPreferenceChanged(sharedPreferences, PreferenceUtil.PREF_CACHE_SIZE);
|
||||||
|
|
||||||
|
|
||||||
|
// Handle clearing the recent search history
|
||||||
|
Preference clearHistoryPref = findPreference("pref_clearHistory");
|
||||||
|
clearHistoryPref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
|
SearchRecentSuggestions suggestions = new SearchRecentSuggestions(getActivity(),
|
||||||
|
RecentSuggestionsProvider.AUTHORITY, RecentSuggestionsProvider.MODE);
|
||||||
|
suggestions.clearHistory();
|
||||||
|
Toast.makeText(getActivity(), R.string.pref_clear_history_success, Toast.LENGTH_LONG).show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle clearing the cache
|
||||||
|
Preference clearCachePref = findPreference("pref_clearCache");
|
||||||
|
clearCachePref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
|
AQUtility.cleanCacheAsync(getActivity());
|
||||||
|
Toast.makeText(getActivity(), R.string.pref_clear_cache_success, Toast.LENGTH_LONG).show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize static text preferences
|
||||||
|
Preference versionPref = findPreference("pref_version");
|
||||||
|
versionPref.setSummary(getString(R.string.version) + " " + ApplicationUtil.getVersionName(getActivity())
|
||||||
|
+ " | " + getString(R.string.build) + " " + ApplicationUtil.getVersionCode(getActivity()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
|
Preference pref = findPreference(key);
|
||||||
|
if (pref instanceof ListPreference) {
|
||||||
|
ListPreference listPref = (ListPreference) pref;
|
||||||
|
pref.setSummary(listPref.getEntry());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.sismics.docs.listener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple listener.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public interface CallbackListener {
|
||||||
|
public void onComplete();
|
||||||
|
}
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
/*
|
||||||
|
Android Asynchronous Http Client
|
||||||
|
Copyright (c) 2011 James Smith <james@loopj.com>
|
||||||
|
http://loopj.com
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sismics.docs.listener;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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> </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> </p>
|
||||||
|
* Additionally, you can override the other event methods from the parent class.
|
||||||
|
*/
|
||||||
|
public class JsonHttpResponseHandler extends TextHttpResponseHandler {
|
||||||
|
|
||||||
|
private static final String LOG_TAG = "JsonHttpResponseHandler";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new JsonHttpResponseHandler, with JSON String encoding UTF-8
|
||||||
|
*/
|
||||||
|
public JsonHttpResponseHandler() {
|
||||||
|
super(DEFAULT_CHARSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new JsonHttpRespnseHandler with given JSON String encoding
|
||||||
|
*
|
||||||
|
* @param encoding String encoding to be used when parsing JSON
|
||||||
|
*/
|
||||||
|
public JsonHttpResponseHandler(String encoding) {
|
||||||
|
super(encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns when request succeeds
|
||||||
|
*
|
||||||
|
* @param statusCode http response status line
|
||||||
|
* @param headers response headers if any
|
||||||
|
* @param response parsed response if any
|
||||||
|
*/
|
||||||
|
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
|
||||||
|
Log.w(LOG_TAG, "onSuccess(int, Header[], JSONObject) was not overriden, but callback was received");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns when request succeeds
|
||||||
|
*
|
||||||
|
* @param statusCode http response status line
|
||||||
|
* @param headers response headers if any
|
||||||
|
* @param response parsed response if any
|
||||||
|
*/
|
||||||
|
public void onSuccess(int statusCode, Header[] headers, JSONArray response) {
|
||||||
|
Log.w(LOG_TAG, "onSuccess(int, Header[], JSONArray) was not overriden, but callback was received");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns when request failed
|
||||||
|
*
|
||||||
|
* @param statusCode http response status line
|
||||||
|
* @param headers response headers if any
|
||||||
|
* @param throwable throwable describing the way request failed
|
||||||
|
* @param errorResponse parsed response if any
|
||||||
|
*/
|
||||||
|
public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) {
|
||||||
|
Log.w(LOG_TAG, "onFailure(int, Header[], Throwable, JSONObject) was not overriden, but callback was received", throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns when request failed
|
||||||
|
*
|
||||||
|
* @param statusCode http response status line
|
||||||
|
* @param headers response headers if any
|
||||||
|
* @param throwable throwable describing the way request failed
|
||||||
|
* @param errorResponse parsed response if any
|
||||||
|
*/
|
||||||
|
public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONArray errorResponse) {
|
||||||
|
Log.w(LOG_TAG, "onFailure(int, Header[], Throwable, JSONArray) was not overriden, but callback was received", throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) {
|
||||||
|
Log.w(LOG_TAG, "onFailure(int, Header[], String, Throwable) was not overriden, but callback was received", throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(int statusCode, Header[] headers, String responseString) {
|
||||||
|
Log.w(LOG_TAG, "onSuccess(int, Header[], String) was not overriden, but callback was received");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void onSuccess(final int statusCode, final Header[] headers, final byte[] responseBytes) {
|
||||||
|
if (statusCode != HttpStatus.SC_NO_CONTENT) {
|
||||||
|
Runnable parser = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
final Object jsonResponse = parseResponse(responseBytes);
|
||||||
|
postRunnable(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (jsonResponse instanceof JSONObject) {
|
||||||
|
onSuccess(statusCode, headers, (JSONObject) jsonResponse);
|
||||||
|
} else if (jsonResponse instanceof JSONArray) {
|
||||||
|
onSuccess(statusCode, headers, (JSONArray) jsonResponse);
|
||||||
|
} else if (jsonResponse instanceof String) {
|
||||||
|
onFailure(statusCode, headers, (String) jsonResponse, new JSONException("Response cannot be parsed as JSON data"));
|
||||||
|
} else {
|
||||||
|
onFailure(statusCode, headers, new JSONException("Unexpected response type " + jsonResponse.getClass().getName()), (JSONObject) null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (final JSONException ex) {
|
||||||
|
postRunnable(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
onFailure(statusCode, headers, ex, (JSONObject) null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (!getUseSynchronousMode()) {
|
||||||
|
new Thread(parser).start();
|
||||||
|
} else {
|
||||||
|
// In synchronous mode everything should be run on one thread
|
||||||
|
parser.run();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onSuccess(statusCode, headers, new JSONObject());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void onFailure(final int statusCode, final Header[] headers, final byte[] responseBytes, final Throwable throwable) {
|
||||||
|
if (responseBytes != null) {
|
||||||
|
Runnable parser = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
final Object jsonResponse = parseResponse(responseBytes);
|
||||||
|
postRunnable(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (jsonResponse instanceof JSONObject) {
|
||||||
|
onFailure(statusCode, headers, throwable, (JSONObject) jsonResponse);
|
||||||
|
} else if (jsonResponse instanceof JSONArray) {
|
||||||
|
onFailure(statusCode, headers, throwable, (JSONArray) jsonResponse);
|
||||||
|
} else if (jsonResponse instanceof String) {
|
||||||
|
onFailure(statusCode, headers, (String) jsonResponse, throwable);
|
||||||
|
} else {
|
||||||
|
onFailure(statusCode, headers, new JSONException("Unexpected response type " + jsonResponse.getClass().getName()), (JSONObject) null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (final JSONException ex) {
|
||||||
|
postRunnable(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
onFailure(statusCode, headers, ex, (JSONObject) null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (!getUseSynchronousMode()) {
|
||||||
|
new Thread(parser).start();
|
||||||
|
} else {
|
||||||
|
// In synchronous mode everything should be run on one thread
|
||||||
|
parser.run();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.v(LOG_TAG, "response body is null, calling onFailure(Throwable, JSONObject)");
|
||||||
|
onFailure(statusCode, headers, throwable, (JSONObject) null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In all cases, call the default failure listener
|
||||||
|
onAllFailure(statusCode, headers, responseBytes, throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
|
||||||
|
// All failures go there
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Object of type {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, Long,
|
||||||
|
* Double or {@link JSONObject#NULL}, see {@link org.json.JSONTokener#nextValue()}
|
||||||
|
*
|
||||||
|
* @param responseBody response bytes to be assembled in String and parsed as JSON
|
||||||
|
* @return Object parsedResponse
|
||||||
|
* @throws org.json.JSONException exception if thrown while parsing JSON
|
||||||
|
*/
|
||||||
|
protected Object parseResponse(byte[] responseBody) throws JSONException {
|
||||||
|
if (null == responseBody)
|
||||||
|
return null;
|
||||||
|
Object result = null;
|
||||||
|
//trim the string to prevent start with blank, and test if the string is valid JSON, because the parser don't do this :(. If JSON is not valid this will return null
|
||||||
|
String jsonString = getResponseString(responseBody, getCharset());
|
||||||
|
if (jsonString != null) {
|
||||||
|
jsonString = jsonString.trim();
|
||||||
|
if (jsonString.startsWith(UTF8_BOM)) {
|
||||||
|
jsonString = jsonString.substring(1);
|
||||||
|
}
|
||||||
|
if (jsonString.startsWith("{") || jsonString.startsWith("[")) {
|
||||||
|
result = new JSONTokener(jsonString).nextValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
result = jsonString;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.sismics.docs.listener;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.GestureDetector;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
|
||||||
|
public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
|
||||||
|
private OnItemClickListener mListener;
|
||||||
|
|
||||||
|
public interface OnItemClickListener {
|
||||||
|
public void onItemClick(View view, int position);
|
||||||
|
}
|
||||||
|
|
||||||
|
GestureDetector mGestureDetector;
|
||||||
|
|
||||||
|
public RecyclerItemClickListener(Context context, OnItemClickListener listener) {
|
||||||
|
mListener = listener;
|
||||||
|
mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
|
||||||
|
@Override public boolean onSingleTapUp(MotionEvent e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
|
||||||
|
View childView = view.findChildViewUnder(e.getX(), e.getY());
|
||||||
|
if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) {
|
||||||
|
mListener.onItemClick(childView, view.getChildPosition(childView));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) { }
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package com.sismics.docs.model.application;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.sismics.docs.listener.CallbackListener;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global context of the application.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class ApplicationContext {
|
||||||
|
/**
|
||||||
|
* Singleton's instance.
|
||||||
|
*/
|
||||||
|
private static ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response of /user/info
|
||||||
|
*/
|
||||||
|
private JSONObject userInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private constructor.
|
||||||
|
*/
|
||||||
|
private ApplicationContext() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a singleton of ApplicationContext.
|
||||||
|
*
|
||||||
|
* @return Singleton of ApplicationContext
|
||||||
|
*/
|
||||||
|
public static ApplicationContext getInstance() {
|
||||||
|
if (applicationContext == null) {
|
||||||
|
applicationContext = new ApplicationContext();
|
||||||
|
}
|
||||||
|
return applicationContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if current user is logged in.
|
||||||
|
*
|
||||||
|
* @return True if the current user is logged in
|
||||||
|
*/
|
||||||
|
public boolean isLoggedIn() {
|
||||||
|
return userInfo != null && !userInfo.optBoolean("anonymous");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of userInfo
|
||||||
|
*
|
||||||
|
* @return userInfo
|
||||||
|
*/
|
||||||
|
public JSONObject getUserInfo() {
|
||||||
|
return userInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter of userInfo
|
||||||
|
*
|
||||||
|
* @param json userInfo
|
||||||
|
*/
|
||||||
|
public void setUserInfo(Context context, JSONObject json) {
|
||||||
|
this.userInfo = json;
|
||||||
|
PreferenceUtil.setCachedJson(context, PreferenceUtil.PREF_CACHED_USER_INFO_JSON, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronously get user info.
|
||||||
|
*
|
||||||
|
* @param activity Activity
|
||||||
|
* @param callbackListener CallbackListener
|
||||||
|
*/
|
||||||
|
public void fetchUserInfo(final Activity activity, final CallbackListener callbackListener) {
|
||||||
|
UserResource.info(activity.getApplicationContext(), new JsonHttpResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(int statusCode, Header[] headers, final JSONObject json) {
|
||||||
|
// Save data in application context
|
||||||
|
if (!json.optBoolean("anonymous", true)) {
|
||||||
|
setUserInfo(activity.getApplicationContext(), json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinish() {
|
||||||
|
if (callbackListener != null) {
|
||||||
|
callbackListener.onComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.sismics.docs.provider;
|
||||||
|
|
||||||
|
import android.content.SearchRecentSuggestionsProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search recent suggestions provider.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class RecentSuggestionsProvider extends SearchRecentSuggestionsProvider {
|
||||||
|
public final static String AUTHORITY = "com.sismics.docs.provider.RecentSuggestionsProvider";
|
||||||
|
public final static int MODE = DATABASE_MODE_QUERIES;
|
||||||
|
|
||||||
|
public RecentSuggestionsProvider() {
|
||||||
|
setupSuggestions(AUTHORITY, MODE);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
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;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for API access.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class BaseResource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User-Agent to use.
|
||||||
|
*/
|
||||||
|
protected static String USER_AGENT = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept-Language header.
|
||||||
|
*/
|
||||||
|
protected static String ACCEPT_LANGUAGE = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP client.
|
||||||
|
*/
|
||||||
|
protected static AsyncHttpClient client = new AsyncHttpClient();
|
||||||
|
|
||||||
|
static {
|
||||||
|
// 20sec default timeout
|
||||||
|
client.setTimeout(60000);
|
||||||
|
try {
|
||||||
|
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
|
trustStore.load(null, null);
|
||||||
|
MySSLSocketFactory sf = new MySSLSocketFactory(trustStore);
|
||||||
|
sf.setHostnameVerifier(MySSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
|
||||||
|
client.setSSLSocketFactory(sf);
|
||||||
|
AbstractAjaxCallback.setSSF(sf);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resource initialization.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
*/
|
||||||
|
protected static void init(Context context) {
|
||||||
|
client.setCookieStore(new PersistentCookieStore(context));
|
||||||
|
|
||||||
|
if (USER_AGENT == null) {
|
||||||
|
USER_AGENT = "Sismics Docs Android " + ApplicationUtil.getVersionName(context) + "/Android " + Build.VERSION.RELEASE + "/" + Build.MODEL;
|
||||||
|
client.setUserAgent(USER_AGENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ACCEPT_LANGUAGE == null) {
|
||||||
|
Locale locale = Locale.getDefault();
|
||||||
|
ACCEPT_LANGUAGE = locale.getLanguage() + "_" + locale.getCountry();
|
||||||
|
client.addHeader("Accept-Language", ACCEPT_LANGUAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Socket factory to allow self-signed certificates.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public static class MySSLSocketFactory extends SSLSocketFactory {
|
||||||
|
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||||
|
|
||||||
|
public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
|
||||||
|
super(truststore);
|
||||||
|
|
||||||
|
TrustManager tm = new X509TrustManager() {
|
||||||
|
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate[] getAcceptedIssuers() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sslContext.init(null, new TrustManager[] { tm }, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
|
||||||
|
return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createSocket() throws IOException {
|
||||||
|
return sslContext.getSocketFactory().createSocket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns cleaned API URL.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @return Cleaned API URL
|
||||||
|
*/
|
||||||
|
protected static String getApiUrl(Context context) {
|
||||||
|
String serverUrl = PreferenceUtil.getServerUrl(context);
|
||||||
|
|
||||||
|
if (serverUrl == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverUrl + "/api";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
package com.sismics.docs.resource;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.loopj.android.http.RequestParams;
|
||||||
|
import com.sismics.docs.listener.JsonHttpResponseHandler;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access to /document API.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class DocumentResource extends BaseResource {
|
||||||
|
/**
|
||||||
|
* GET /document/list.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param offset Offset
|
||||||
|
* @param query Search query
|
||||||
|
* @param responseHandler 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /document/id.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param id ID
|
||||||
|
* @param responseHandler Callback
|
||||||
|
*/
|
||||||
|
public static void get(Context context, String id, JsonHttpResponseHandler responseHandler) {
|
||||||
|
init(context);
|
||||||
|
|
||||||
|
client.get(getApiUrl(context) + "/document/" + id, responseHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE /document/id.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param id ID
|
||||||
|
* @param responseHandler Callback
|
||||||
|
*/
|
||||||
|
public static void delete(Context context, String id, JsonHttpResponseHandler responseHandler) {
|
||||||
|
init(context);
|
||||||
|
|
||||||
|
client.delete(getApiUrl(context) + "/document/" + id, responseHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PUT /document.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param title Title
|
||||||
|
* @param description Description
|
||||||
|
* @param tagIdList Tags ID list
|
||||||
|
* @param language Language
|
||||||
|
* @param createDate Create date
|
||||||
|
* @param responseHandler Callback
|
||||||
|
*/
|
||||||
|
public static void add(Context context, String title, String description,
|
||||||
|
Set<String> tagIdList, String language, long createDate, JsonHttpResponseHandler responseHandler) {
|
||||||
|
init(context);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /document/id.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param id ID
|
||||||
|
* @param title Title
|
||||||
|
* @param description Description
|
||||||
|
* @param tagIdList Tags ID list
|
||||||
|
* @param language Language
|
||||||
|
* @param createDate Create date
|
||||||
|
* @param responseHandler Callback
|
||||||
|
*/
|
||||||
|
public static void edit(Context context, String id, String title, String description,
|
||||||
|
Set<String> tagIdList, String language, long createDate, JsonHttpResponseHandler responseHandler) {
|
||||||
|
init(context);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel pending requests.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
*/
|
||||||
|
public static void cancel(Context context) {
|
||||||
|
client.cancelRequests(context, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package com.sismics.docs.resource;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.loopj.android.http.PersistentCookieStore;
|
||||||
|
import com.loopj.android.http.RequestParams;
|
||||||
|
import com.loopj.android.http.SyncHttpClient;
|
||||||
|
import com.sismics.docs.listener.JsonHttpResponseHandler;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access to /file API.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class FileResource extends BaseResource {
|
||||||
|
/**
|
||||||
|
* GET /file/list.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param documentId Document ID
|
||||||
|
* @param responseHandler Callback
|
||||||
|
*/
|
||||||
|
public static void list(Context context, String documentId, JsonHttpResponseHandler responseHandler) {
|
||||||
|
init(context);
|
||||||
|
|
||||||
|
client.get(getApiUrl(context) + "/file/list?id=" + documentId, responseHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE /file/id.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param id ID
|
||||||
|
* @param responseHandler Callback
|
||||||
|
*/
|
||||||
|
public static void delete(Context context, String id, JsonHttpResponseHandler responseHandler) {
|
||||||
|
init(context);
|
||||||
|
|
||||||
|
client.delete(getApiUrl(context) + "/file/" + id, responseHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PUT /file.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param documentId Document ID
|
||||||
|
* @param is Input stream
|
||||||
|
* @param responseHandler Callback
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static void addSync(Context context, String documentId, InputStream is, JsonHttpResponseHandler responseHandler) throws Exception {
|
||||||
|
init(context);
|
||||||
|
|
||||||
|
SyncHttpClient client = new SyncHttpClient();
|
||||||
|
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
|
trustStore.load(null, null);
|
||||||
|
MySSLSocketFactory sf = new MySSLSocketFactory(trustStore);
|
||||||
|
sf.setHostnameVerifier(MySSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
|
||||||
|
client.setSSLSocketFactory(sf);
|
||||||
|
client.setCookieStore(new PersistentCookieStore(context));
|
||||||
|
client.setUserAgent(USER_AGENT);
|
||||||
|
client.addHeader("Accept-Language", ACCEPT_LANGUAGE);
|
||||||
|
|
||||||
|
RequestParams params = new RequestParams();
|
||||||
|
params.put("id", documentId);
|
||||||
|
params.put("file", is, "file", "application/octet-stream", true);
|
||||||
|
client.put(getApiUrl(context) + "/file", params, responseHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel pending requests.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
*/
|
||||||
|
public static void cancel(Context context) {
|
||||||
|
client.cancelRequests(context, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.sismics.docs.resource;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.loopj.android.http.RequestParams;
|
||||||
|
import com.sismics.docs.listener.JsonHttpResponseHandler;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access to /tag API.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class ShareResource extends BaseResource {
|
||||||
|
/**
|
||||||
|
* PUT /share.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param documentId Document ID
|
||||||
|
* @param name Name
|
||||||
|
* @param responseHandler Callback
|
||||||
|
*/
|
||||||
|
public static void add(Context context, String documentId, String name, JsonHttpResponseHandler responseHandler) {
|
||||||
|
init(context);
|
||||||
|
|
||||||
|
RequestParams params = new RequestParams();
|
||||||
|
params.put("id", documentId);
|
||||||
|
params.put("name", name);
|
||||||
|
client.put(getApiUrl(context) + "/share", params, responseHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE /share.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param id ID
|
||||||
|
* @param responseHandler Callback
|
||||||
|
*/
|
||||||
|
public static void delete(Context context, String id, JsonHttpResponseHandler responseHandler) {
|
||||||
|
init(context);
|
||||||
|
|
||||||
|
client.delete(getApiUrl(context) + "/share/" + id, responseHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.sismics.docs.resource;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.sismics.docs.listener.JsonHttpResponseHandler;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access to /tag API.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class TagResource extends BaseResource {
|
||||||
|
/**
|
||||||
|
* GET /tag/stats.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param responseHandler Callback
|
||||||
|
*/
|
||||||
|
public static void stats(Context context, JsonHttpResponseHandler responseHandler) {
|
||||||
|
init(context);
|
||||||
|
|
||||||
|
client.get(getApiUrl(context) + "/tag/stats", responseHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.sismics.docs.resource;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.loopj.android.http.RequestParams;
|
||||||
|
import com.sismics.docs.listener.JsonHttpResponseHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access to /user API.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class UserResource extends BaseResource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /user/login.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param username Username
|
||||||
|
* @param password Password
|
||||||
|
* @param responseHandler Callback
|
||||||
|
*/
|
||||||
|
public static void login(Context context, String username, String password, JsonHttpResponseHandler responseHandler) {
|
||||||
|
init(context);
|
||||||
|
|
||||||
|
RequestParams params = new RequestParams();
|
||||||
|
params.put("username", username);
|
||||||
|
params.put("password", password);
|
||||||
|
params.put("remember", "true");
|
||||||
|
client.post(getApiUrl(context) + "/user/login", params, responseHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /user.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param responseHandler Callback
|
||||||
|
*/
|
||||||
|
public static void info(Context context, JsonHttpResponseHandler responseHandler) {
|
||||||
|
init(context);
|
||||||
|
|
||||||
|
RequestParams params = new RequestParams();
|
||||||
|
client.get(getApiUrl(context) + "/user", params, responseHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /user/logout.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param responseHandler Callback
|
||||||
|
*/
|
||||||
|
public static void logout(Context context, JsonHttpResponseHandler responseHandler) {
|
||||||
|
init(context);
|
||||||
|
|
||||||
|
RequestParams params = new RequestParams();
|
||||||
|
client.post(getApiUrl(context) + "/user/logout", params, responseHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package com.sismics.docs.service;
|
||||||
|
|
||||||
|
import android.app.IntentService;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.PowerManager;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
import android.support.v4.app.NotificationCompat.Builder;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
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 de.greenrobot.event.EventBus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to upload a file to a document in the background.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class FileUploadService extends IntentService {
|
||||||
|
private static final String TAG = "FileUploadService";
|
||||||
|
|
||||||
|
private static final int UPLOAD_NOTIFICATION_ID = 1;
|
||||||
|
private static final int UPLOAD_NOTIFICATION_ID_DONE = 2;
|
||||||
|
public static final String PARAM_URI = "uri";
|
||||||
|
public static final String PARAM_DOCUMENT_ID = "documentId";
|
||||||
|
|
||||||
|
private NotificationManager notificationManager;
|
||||||
|
private Builder notification;
|
||||||
|
private PowerManager.WakeLock wakeLock;
|
||||||
|
|
||||||
|
public FileUploadService() {
|
||||||
|
super(FileUploadService.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
|
||||||
|
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||||
|
notification = new NotificationCompat.Builder(this);
|
||||||
|
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
|
||||||
|
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onHandleIntent(Intent intent) {
|
||||||
|
if (intent == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wakeLock.acquire();
|
||||||
|
try {
|
||||||
|
onStart();
|
||||||
|
handleFileUpload(intent.getStringExtra(PARAM_DOCUMENT_ID), (Uri) intent.getParcelableExtra(PARAM_URI));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error uploading the file", e);
|
||||||
|
onError();
|
||||||
|
} finally {
|
||||||
|
wakeLock.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actually uploading the file.
|
||||||
|
*
|
||||||
|
* @param documentId Document ID
|
||||||
|
* @param uri Data URI
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private void handleFileUpload(final String documentId, final Uri uri) throws Exception {
|
||||||
|
final InputStream is = getContentResolver().openInputStream(uri);
|
||||||
|
FileResource.addSync(this, documentId, is, new JsonHttpResponseHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
|
||||||
|
EventBus.getDefault().post(new FileAddEvent(documentId, response.optString("id")));
|
||||||
|
FileUploadService.this.onComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAllFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) {
|
||||||
|
FileUploadService.this.onError();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On upload start.
|
||||||
|
*/
|
||||||
|
private void onStart() {
|
||||||
|
notification.setContentTitle(getString(R.string.upload_notification_title))
|
||||||
|
.setContentText(getString(R.string.upload_notification_message))
|
||||||
|
.setContentIntent(PendingIntent.getBroadcast(this, 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT))
|
||||||
|
.setSmallIcon(R.drawable.ic_file_upload_white_24dp)
|
||||||
|
.setProgress(100, 0, true)
|
||||||
|
.setOngoing(true);
|
||||||
|
|
||||||
|
startForeground(UPLOAD_NOTIFICATION_ID, notification.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On upload complete.
|
||||||
|
*/
|
||||||
|
private void onComplete() {
|
||||||
|
stopForeground(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On upload error.
|
||||||
|
*/
|
||||||
|
private void onError() {
|
||||||
|
stopForeground(false);
|
||||||
|
|
||||||
|
notification.setContentTitle(getString(R.string.upload_notification_title))
|
||||||
|
.setContentText(getString(R.string.upload_notification_error))
|
||||||
|
.setSmallIcon(R.drawable.ic_file_upload_white_24dp)
|
||||||
|
.setProgress(0, 0, false)
|
||||||
|
.setOngoing(false);
|
||||||
|
|
||||||
|
notificationManager.notify(UPLOAD_NOTIFICATION_ID_DONE, notification.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package com.sismics.docs.ui.form;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.sismics.docs.ui.form.validator.ValidatorType;
|
||||||
|
|
||||||
|
public class Validable {
|
||||||
|
|
||||||
|
private final ValidatorType[] validatorTypes;
|
||||||
|
|
||||||
|
private View view;
|
||||||
|
|
||||||
|
private boolean isValidated = false;
|
||||||
|
|
||||||
|
public Validable(ValidatorType... validatorTypes) {
|
||||||
|
this.validatorTypes = validatorTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of view.
|
||||||
|
*
|
||||||
|
* @return view
|
||||||
|
*/
|
||||||
|
public View getView() {
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter of view.
|
||||||
|
*
|
||||||
|
* @param view view
|
||||||
|
*/
|
||||||
|
public void setView(View view) {
|
||||||
|
this.view = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of isValidated.
|
||||||
|
*
|
||||||
|
* @return isValidated
|
||||||
|
*/
|
||||||
|
public boolean isValidated() {
|
||||||
|
return isValidated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter of isValidated.
|
||||||
|
*
|
||||||
|
* @param isValidated isValidated
|
||||||
|
*/
|
||||||
|
public void setValidated(boolean isValidated) {
|
||||||
|
this.isValidated = isValidated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of validatorTypes.
|
||||||
|
*
|
||||||
|
* @return validatorTypes
|
||||||
|
*/
|
||||||
|
public ValidatorType[] getValidatorTypes() {
|
||||||
|
return validatorTypes;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
package com.sismics.docs.ui.form;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.EditText;
|
||||||
|
|
||||||
|
import com.sismics.docs.listener.CallbackListener;
|
||||||
|
import com.sismics.docs.ui.form.validator.ValidatorType;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility for form validation.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class Validator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of validable elements.
|
||||||
|
*/
|
||||||
|
private Map<EditText, Validable> validables = new HashMap<EditText, Validable>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback when the validation of one element has changed.
|
||||||
|
*/
|
||||||
|
private CallbackListener onValidationChanged;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the validator show validation errors.
|
||||||
|
*/
|
||||||
|
private boolean showErrors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context.
|
||||||
|
*/
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param showErrors True to display validation errors
|
||||||
|
*/
|
||||||
|
public Validator(Context context, boolean showErrors) {
|
||||||
|
this.context = context;
|
||||||
|
this.showErrors = showErrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter of onValidationChanged.
|
||||||
|
* @param onValidationChanged onValidationChanged
|
||||||
|
*/
|
||||||
|
public void setOnValidationChanged(CallbackListener onValidationChanged) {
|
||||||
|
this.onValidationChanged = onValidationChanged;
|
||||||
|
onValidationChanged.onComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a validable element.
|
||||||
|
*
|
||||||
|
* @param editText Edit text
|
||||||
|
* @param validatorTypes Validators
|
||||||
|
*/
|
||||||
|
public void addValidable(final EditText editText, final ValidatorType... validatorTypes) {
|
||||||
|
final Validable validable = new Validable(validatorTypes);
|
||||||
|
validables.put(editText, validable);
|
||||||
|
|
||||||
|
editText.addTextChangedListener(new TextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s) {
|
||||||
|
validate(editText, validable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the element is validated.
|
||||||
|
*
|
||||||
|
* @param view View
|
||||||
|
* @return True if the element is validated
|
||||||
|
*/
|
||||||
|
public boolean isValidated(View view) {
|
||||||
|
return validables.get(view).isValidated();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a specific EditText.
|
||||||
|
*
|
||||||
|
* @param editText EditText
|
||||||
|
* @param validable Validable
|
||||||
|
*/
|
||||||
|
private void validate(EditText editText, Validable validable) {
|
||||||
|
validable.setValidated(true);
|
||||||
|
for (ValidatorType validatorType : validable.getValidatorTypes()) {
|
||||||
|
if (!validatorType.validate(editText.getEditableText().toString())) {
|
||||||
|
if (showErrors) {
|
||||||
|
editText.setError(validatorType.getErrorMessage(context));
|
||||||
|
}
|
||||||
|
validable.setValidated(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validable.isValidated()) {
|
||||||
|
editText.setError(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onValidationChanged != null) {
|
||||||
|
onValidationChanged.onComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate everything now.
|
||||||
|
*/
|
||||||
|
public void validate() {
|
||||||
|
for (Map.Entry<EditText, Validable> entry : validables.entrySet()) {
|
||||||
|
validate(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if all elements are validated.
|
||||||
|
*
|
||||||
|
* @return True if all elements are validated
|
||||||
|
*/
|
||||||
|
public boolean isValidated() {
|
||||||
|
for (Validable validable : validables.values()) {
|
||||||
|
if (!validable.isValidated()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.sismics.docs.ui.form.validator;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alphanumeric validator.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class Alphanumeric implements ValidatorType {
|
||||||
|
|
||||||
|
private static Pattern ALPHANUMERIC_PATTERN = Pattern.compile("[a-zA-Z0-9_]+");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validate(String text) {
|
||||||
|
return ALPHANUMERIC_PATTERN.matcher(text).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getErrorMessage(Context context) {
|
||||||
|
return context.getString(R.string.validate_error_alphanumeric);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.sismics.docs.ui.form.validator;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Email validator.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class Email implements ValidatorType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pattern de validation.
|
||||||
|
*/
|
||||||
|
private static Pattern EMAIL_PATTERN = Pattern.compile(".+@.+\\..+");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validate(String text) {
|
||||||
|
return EMAIL_PATTERN.matcher(text).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getErrorMessage(Context context) {
|
||||||
|
return context.getResources().getString(R.string.validate_error_email);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.sismics.docs.ui.form.validator;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text length validator.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class Length implements ValidatorType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum length.
|
||||||
|
*/
|
||||||
|
private int minLength = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum length.
|
||||||
|
*/
|
||||||
|
private int maxLength = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the last validation error was about a string too short.
|
||||||
|
*/
|
||||||
|
private boolean tooShort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
* @param minLength Minimum length
|
||||||
|
* @param maxLength Maximum length
|
||||||
|
*/
|
||||||
|
public Length(int minLength, int maxLength) {
|
||||||
|
this.minLength = minLength;
|
||||||
|
this.maxLength = maxLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validate(String text) {
|
||||||
|
tooShort = text.trim().length() < minLength;
|
||||||
|
return text.trim().length() >= minLength && text.trim().length() <= maxLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getErrorMessage(Context context) {
|
||||||
|
if (tooShort) {
|
||||||
|
return context.getResources().getString(R.string.validate_error_length_min, minLength);
|
||||||
|
}
|
||||||
|
return context.getResources().getString(R.string.validate_error_length_max, maxLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.sismics.docs.ui.form.validator;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text presence validator.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class Required implements ValidatorType {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validate(String text) {
|
||||||
|
return text.trim().length() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getErrorMessage(Context context) {
|
||||||
|
return context.getString(R.string.validate_error_required);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.sismics.docs.ui.form.validator;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for validation types.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public interface ValidatorType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the validator is validated.
|
||||||
|
* @param text
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean validate(String text);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an error message.
|
||||||
|
* @param context
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getErrorMessage(Context context);
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package com.sismics.docs.ui.view;
|
||||||
|
|
||||||
|
import android.app.DatePickerDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.text.format.DateFormat;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.DatePicker;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date picker widget.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class DatePickerView extends TextView implements DatePickerDialog.OnDateSetListener {
|
||||||
|
|
||||||
|
private Date date;
|
||||||
|
|
||||||
|
public DatePickerView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DatePickerView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
setAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DatePickerView(Context context) {
|
||||||
|
super(context);
|
||||||
|
setAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setAttributes() {
|
||||||
|
setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
final Calendar calendar = Calendar.getInstance();
|
||||||
|
if (date != null) {
|
||||||
|
calendar.setTime(date);
|
||||||
|
}
|
||||||
|
new DatePickerDialog(
|
||||||
|
DatePickerView.this.getContext(), DatePickerView.this,
|
||||||
|
calendar.get(Calendar.YEAR),
|
||||||
|
calendar.get(Calendar.MONTH),
|
||||||
|
calendar.get(Calendar.DAY_OF_MONTH)).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
|
||||||
|
Date date = new GregorianCalendar(year, monthOfYear, dayOfMonth).getTime();
|
||||||
|
setDate(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDate(Date date) {
|
||||||
|
this.date = date;
|
||||||
|
String formattedDate = DateFormat.getDateFormat(getContext()).format(date);
|
||||||
|
setText(formattedDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getDate() {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.sismics.docs.ui.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Divider item decoration for recycler view.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
|
||||||
|
|
||||||
|
private Drawable mDivider;
|
||||||
|
|
||||||
|
public DividerItemDecoration(Context context, AttributeSet attrs) {
|
||||||
|
final TypedArray a = context.obtainStyledAttributes(attrs, new int [] { android.R.attr.listDivider });
|
||||||
|
mDivider = a.getDrawable(0);
|
||||||
|
a.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DividerItemDecoration(Drawable divider) { mDivider = divider; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getItemOffsets (Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
|
||||||
|
super.getItemOffsets(outRect, view, parent, state);
|
||||||
|
if (mDivider == null) return;
|
||||||
|
if (parent.getChildPosition(view) < 1) return;
|
||||||
|
|
||||||
|
if (getOrientation(parent) == LinearLayoutManager.VERTICAL) outRect.top = mDivider.getIntrinsicHeight();
|
||||||
|
else outRect.left = mDivider.getIntrinsicWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrawOver(Canvas c, RecyclerView parent) {
|
||||||
|
if (mDivider == null) { super.onDrawOver(c, parent); return; }
|
||||||
|
|
||||||
|
if (getOrientation(parent) == LinearLayoutManager.VERTICAL) {
|
||||||
|
final int left = parent.getPaddingLeft();
|
||||||
|
final int right = parent.getWidth() - parent.getPaddingRight();
|
||||||
|
final int childCount = parent.getChildCount();
|
||||||
|
|
||||||
|
for (int i=1; i < childCount; i++) {
|
||||||
|
final View child = parent.getChildAt(i);
|
||||||
|
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
|
||||||
|
final int size = mDivider.getIntrinsicHeight();
|
||||||
|
final int top = child.getTop() - params.topMargin;
|
||||||
|
final int bottom = top + size;
|
||||||
|
mDivider.setBounds(left, top, right, bottom);
|
||||||
|
mDivider.draw(c);
|
||||||
|
}
|
||||||
|
} else { //horizontal
|
||||||
|
final int top = parent.getPaddingTop();
|
||||||
|
final int bottom = parent.getHeight() - parent.getPaddingBottom();
|
||||||
|
final int childCount = parent.getChildCount();
|
||||||
|
|
||||||
|
for (int i=1; i < childCount; i++) {
|
||||||
|
final View child = parent.getChildAt(i);
|
||||||
|
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
|
||||||
|
final int size = mDivider.getIntrinsicWidth();
|
||||||
|
final int left = child.getLeft() - params.leftMargin;
|
||||||
|
final int right = left + size;
|
||||||
|
mDivider.setBounds(left, top, right, bottom);
|
||||||
|
mDivider.draw(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getOrientation(RecyclerView parent) {
|
||||||
|
if (parent.getLayoutManager() instanceof LinearLayoutManager) {
|
||||||
|
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
|
||||||
|
return layoutManager.getOrientation();
|
||||||
|
} else throw new IllegalStateException("DividerItemDecoration can only be used with a LinearLayoutManager.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.sismics.docs.ui.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RecyclerView with empty view support.
|
||||||
|
* Thanks to https://gist.github.com/adelnizamutdinov/31c8f054d1af4588dc5c
|
||||||
|
*
|
||||||
|
* @author Nizamutdinov Adel
|
||||||
|
*/
|
||||||
|
public class EmptyRecyclerView extends RecyclerView {
|
||||||
|
private View emptyView;
|
||||||
|
|
||||||
|
public EmptyRecyclerView(Context context) { super(context); }
|
||||||
|
|
||||||
|
public EmptyRecyclerView(Context context, AttributeSet attrs) { super(context, attrs); }
|
||||||
|
|
||||||
|
public EmptyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkIfEmpty() {
|
||||||
|
if (emptyView != null) {
|
||||||
|
emptyView.setVisibility(getAdapter().getItemCount() > 0 ? GONE : VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final AdapterDataObserver observer = new AdapterDataObserver() {
|
||||||
|
@Override public void onChanged() {
|
||||||
|
super.onChanged();
|
||||||
|
checkIfEmpty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override public void setAdapter(Adapter adapter) {
|
||||||
|
final Adapter oldAdapter = getAdapter();
|
||||||
|
if (oldAdapter != null) {
|
||||||
|
oldAdapter.unregisterAdapterDataObserver(observer);
|
||||||
|
}
|
||||||
|
super.setAdapter(adapter);
|
||||||
|
if (adapter != null) {
|
||||||
|
adapter.registerAdapterDataObserver(observer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmptyView(View emptyView) {
|
||||||
|
// Hide the current empty view
|
||||||
|
if (this.emptyView != null) {
|
||||||
|
this.emptyView.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
this.emptyView = emptyView;
|
||||||
|
checkIfEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.sismics.docs.ui.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.v4.view.ViewPager;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import it.sephiroth.android.library.imagezoom.ImageViewTouch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewPager for files.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class FileViewPager extends ViewPager {
|
||||||
|
|
||||||
|
public FileViewPager(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileViewPager(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
|
||||||
|
if (v instanceof ImageViewTouch) {
|
||||||
|
return ((ImageViewTouch) v).canScroll(dx);
|
||||||
|
} else {
|
||||||
|
return super.canScroll(v, checkV, dx, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.sismics.docs.ui.view;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.text.InputFilter;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
import com.tokenautocomplete.TokenCompleteTextView;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-complete text view displaying tags.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class TagsCompleteTextView extends TokenCompleteTextView {
|
||||||
|
public TagsCompleteTextView(Context context) {
|
||||||
|
super(context);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagsCompleteTextView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagsCompleteTextView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
setFilters(new InputFilter[] {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected View getViewForObject(Object object) {
|
||||||
|
JSONObject tag = (JSONObject) object;
|
||||||
|
|
||||||
|
LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
|
||||||
|
View view = inflater.inflate(R.layout.tag_autocomplete_token, (ViewGroup) getParent(), false);
|
||||||
|
TextView textView = (TextView) view.findViewById(R.id.tagTextView);
|
||||||
|
textView.setText(tag.optString("name"));
|
||||||
|
|
||||||
|
Drawable drawable = textView.getCompoundDrawables()[0].mutate();
|
||||||
|
drawable.setColorFilter(Color.parseColor(tag.optString("color")), PorterDuff.Mode.MULTIPLY);
|
||||||
|
textView.setCompoundDrawables(drawable, null, null, null);
|
||||||
|
textView.invalidate();
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object defaultObject(String completionText) {
|
||||||
|
return completionText;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.sismics.docs.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class on general application data.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class ApplicationUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns version name.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @return Nom de la version
|
||||||
|
*/
|
||||||
|
public static String getVersionName(Context context) {
|
||||||
|
try {
|
||||||
|
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
||||||
|
return packageInfo.versionName;
|
||||||
|
} catch (NameNotFoundException e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns version number.
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @return Numéro de version
|
||||||
|
*/
|
||||||
|
public static int getVersionCode(Context context) {
|
||||||
|
try {
|
||||||
|
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
||||||
|
return packageInfo.versionCode;
|
||||||
|
} catch (NameNotFoundException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.sismics.docs.util;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
|
||||||
|
import com.sismics.docs.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for dialogs.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class DialogUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a dialog with an OK button.
|
||||||
|
*
|
||||||
|
* @param activity Context activity
|
||||||
|
* @param title Dialog title
|
||||||
|
* @param message Dialog message
|
||||||
|
*/
|
||||||
|
public static void showOkDialog(Activity activity, int title, int message) {
|
||||||
|
if (activity == null || activity.isFinishing()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||||
|
|
||||||
|
builder.setTitle(title)
|
||||||
|
.setMessage(message)
|
||||||
|
.setCancelable(true)
|
||||||
|
.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}).create().show();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
package com.sismics.docs.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.SharedPreferences.Editor;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import com.loopj.android.http.PersistentCookieStore;
|
||||||
|
|
||||||
|
import org.apache.http.cookie.Cookie;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class on preferences.
|
||||||
|
*
|
||||||
|
* @author bgamard
|
||||||
|
*/
|
||||||
|
public class PreferenceUtil {
|
||||||
|
|
||||||
|
public static final String PREF_CACHED_USER_INFO_JSON = "pref_cachedUserInfoJson";
|
||||||
|
public static final String PREF_CACHED_TAGS_JSON = "pref_cachedTagsJson";
|
||||||
|
public static final String PREF_SERVER_URL = "pref_ServerUrl";
|
||||||
|
public static final String PREF_CACHE_SIZE = "pref_cacheSize";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a preference of boolean type.
|
||||||
|
* @param context Context
|
||||||
|
* @param key Shared preference key
|
||||||
|
* @return Shared preference value
|
||||||
|
*/
|
||||||
|
public static boolean getBooleanPreference(Context context, String key, boolean defaultValue) {
|
||||||
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
return sharedPreferences.getBoolean(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a preference of string type.
|
||||||
|
* @param context Context
|
||||||
|
* @param key Shared preference key
|
||||||
|
* @return Shared preference value
|
||||||
|
*/
|
||||||
|
public static String getStringPreference(Context context, String key) {
|
||||||
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
return sharedPreferences.getString(key, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a preference of integer type.
|
||||||
|
* @param context Context
|
||||||
|
* @param key Shared preference key
|
||||||
|
* @return Shared preference value
|
||||||
|
*/
|
||||||
|
public static int getIntegerPreference(Context context, String key, int defaultValue) {
|
||||||
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
try {
|
||||||
|
String pref = sharedPreferences.getString(key, "");
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(pref);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
return sharedPreferences.getInt(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update JSON cache.
|
||||||
|
* @param context Context
|
||||||
|
* @param key Shared preference key
|
||||||
|
* @param json JSON data
|
||||||
|
*/
|
||||||
|
public static void setCachedJson(Context context, String key, JSONObject json) {
|
||||||
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
sharedPreferences.edit().putString(key, json != null ? json.toString() : null).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a JSON cache.
|
||||||
|
* @param context Context
|
||||||
|
* @param key Shared preference key
|
||||||
|
* @return JSON data
|
||||||
|
*/
|
||||||
|
public static JSONObject getCachedJson(Context context, String key) {
|
||||||
|
try {
|
||||||
|
return new JSONObject(getStringPreference(context, key));
|
||||||
|
} catch (Exception e) {
|
||||||
|
// The cache is not parsable, clean this up
|
||||||
|
setCachedJson(context, key, null);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update server URL.
|
||||||
|
* @param context Context
|
||||||
|
*/
|
||||||
|
public static void setServerUrl(Context context, String serverUrl) {
|
||||||
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
sharedPreferences.edit().putString(PREF_SERVER_URL, serverUrl).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty user caches.
|
||||||
|
* @param context Context
|
||||||
|
*/
|
||||||
|
public static void resetUserCache(Context context) {
|
||||||
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
Editor editor = sharedPreferences.edit();
|
||||||
|
editor
|
||||||
|
.putString(PREF_CACHED_USER_INFO_JSON, null)
|
||||||
|
.putString(PREF_CACHED_TAGS_JSON, null)
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns auth token cookie from shared preferences.
|
||||||
|
* @return Auth token
|
||||||
|
*/
|
||||||
|
public static String getAuthToken(Context context) {
|
||||||
|
PersistentCookieStore cookieStore = new PersistentCookieStore(context);
|
||||||
|
List<Cookie> cookieList = cookieStore.getCookies();
|
||||||
|
for (Cookie cookie : cookieList) {
|
||||||
|
if (cookie.getName().equals("auth_token")) {
|
||||||
|
return cookie.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns cleaned server URL.
|
||||||
|
* @param context Context
|
||||||
|
* @return Server URL
|
||||||
|
*/
|
||||||
|
public static String getServerUrl(Context context) {
|
||||||
|
String serverUrl = getStringPreference(context, PREF_SERVER_URL);
|
||||||
|
if (serverUrl == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim
|
||||||
|
serverUrl = serverUrl.trim();
|
||||||
|
|
||||||
|
if (!serverUrl.startsWith("http")) {
|
||||||
|
// Try to add http
|
||||||
|
serverUrl = "http://" + serverUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serverUrl.endsWith("/")) {
|
||||||
|
// Delete last /
|
||||||
|
serverUrl = serverUrl.substring(0, serverUrl.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove /api
|
||||||
|
if (serverUrl.endsWith("/api")) {
|
||||||
|
serverUrl = serverUrl.substring(0, serverUrl.length() - 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
package com.sismics.docs.util;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search query builder.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class SearchQueryBuilder {
|
||||||
|
/**
|
||||||
|
* The query.
|
||||||
|
*/
|
||||||
|
private StringBuilder query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search separator.
|
||||||
|
*/
|
||||||
|
private static String SEARCH_SEPARATOR = " ";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search date format.
|
||||||
|
*/
|
||||||
|
private SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a query.
|
||||||
|
*/
|
||||||
|
public SearchQueryBuilder() {
|
||||||
|
query = new StringBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a simple search criteria.
|
||||||
|
*
|
||||||
|
* @param simpleSearch Simple search criteria
|
||||||
|
* @return The builder
|
||||||
|
*/
|
||||||
|
public SearchQueryBuilder simpleSearch(String simpleSearch) {
|
||||||
|
if (isValid(simpleSearch)) {
|
||||||
|
query.append(SEARCH_SEPARATOR).append(simpleSearch);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a fulltext search criteria.
|
||||||
|
*
|
||||||
|
* @param fulltextSearch Fulltext search criteria
|
||||||
|
* @return The builder
|
||||||
|
*/
|
||||||
|
public SearchQueryBuilder fulltextSearch(String fulltextSearch) {
|
||||||
|
if (isValid(fulltextSearch)) {
|
||||||
|
query.append(SEARCH_SEPARATOR)
|
||||||
|
.append("full:")
|
||||||
|
.append(fulltextSearch);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a language criteria.
|
||||||
|
*
|
||||||
|
* @param language Language criteria
|
||||||
|
* @return The builder
|
||||||
|
*/
|
||||||
|
public SearchQueryBuilder language(String language) {
|
||||||
|
if (isValid(language)) {
|
||||||
|
query.append(SEARCH_SEPARATOR)
|
||||||
|
.append("lang:")
|
||||||
|
.append(language);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a shared criteria.
|
||||||
|
*
|
||||||
|
* @param shared Shared criteria
|
||||||
|
* @return The builder
|
||||||
|
*/
|
||||||
|
public SearchQueryBuilder shared(boolean shared) {
|
||||||
|
if (shared) {
|
||||||
|
query.append(SEARCH_SEPARATOR).append("shared:yes");
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a tag criteria.
|
||||||
|
*
|
||||||
|
* @param tag Tag criteria
|
||||||
|
* @return The builder
|
||||||
|
*/
|
||||||
|
public SearchQueryBuilder tag(String tag) {
|
||||||
|
query.append(SEARCH_SEPARATOR)
|
||||||
|
.append("tag:")
|
||||||
|
.append(tag);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a before date criteria.
|
||||||
|
*
|
||||||
|
* @param before Before date criteria
|
||||||
|
* @return The builder
|
||||||
|
*/
|
||||||
|
public SearchQueryBuilder before(Date before) {
|
||||||
|
if (before != null) {
|
||||||
|
query.append(SEARCH_SEPARATOR)
|
||||||
|
.append("before:")
|
||||||
|
.append(DATE_FORMAT.format(before));
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an after date criteria.
|
||||||
|
*
|
||||||
|
* @param after After date criteria
|
||||||
|
* @return The builder
|
||||||
|
*/
|
||||||
|
public SearchQueryBuilder after(Date after) {
|
||||||
|
if (after != null) {
|
||||||
|
query.append(SEARCH_SEPARATOR)
|
||||||
|
.append("after:")
|
||||||
|
.append(DATE_FORMAT.format(after));
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the query.
|
||||||
|
*
|
||||||
|
* @return The query
|
||||||
|
*/
|
||||||
|
public String build() {
|
||||||
|
return query.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the search criteria is valid.
|
||||||
|
*
|
||||||
|
* @param criteria Search criteria
|
||||||
|
* @return True if the search criteria is valid
|
||||||
|
*/
|
||||||
|
private boolean isValid(String criteria) {
|
||||||
|
return criteria != null && !criteria.trim().isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.sismics.docs.util;
|
||||||
|
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.text.Spannable;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.text.style.BackgroundColorSpan;
|
||||||
|
import android.text.style.ForegroundColorSpan;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for tags.
|
||||||
|
*
|
||||||
|
* @author bgamard.
|
||||||
|
*/
|
||||||
|
public class TagUtil {
|
||||||
|
/**
|
||||||
|
* Create a colored spannable from tags.
|
||||||
|
*
|
||||||
|
* @param tags Tags
|
||||||
|
* @return Colored spannable
|
||||||
|
*/
|
||||||
|
public static Spannable buildSpannable(JSONArray tags) {
|
||||||
|
SpannableStringBuilder builder = new SpannableStringBuilder();
|
||||||
|
|
||||||
|
for (int i = 0; i < tags.length(); i++) {
|
||||||
|
JSONObject tag = tags.optJSONObject(i);
|
||||||
|
int start = builder.length();
|
||||||
|
builder.append(" ").append(tag.optString("name")).append(" ");
|
||||||
|
builder.setSpan(new ForegroundColorSpan(Color.WHITE), start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
builder.setSpan(new BackgroundColorSpan(Color.parseColor(tag.optString("color"))), start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
builder.append(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
BIN
docs-android/app/src/main/res/drawable-xhdpi/eng.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
docs-android/app/src/main/res/drawable-xhdpi/fra.png
Normal file
|
After Width: | Height: | Size: 352 B |
|
After Width: | Height: | Size: 198 B |
|
After Width: | Height: | Size: 379 B |
|
After Width: | Height: | Size: 271 B |
|
After Width: | Height: | Size: 363 B |
|
After Width: | Height: | Size: 283 B |
|
After Width: | Height: | Size: 275 B |
|
After Width: | Height: | Size: 271 B |
|
After Width: | Height: | Size: 543 B |
|
After Width: | Height: | Size: 410 B |
|
After Width: | Height: | Size: 530 B |
|
After Width: | Height: | Size: 374 B |
|
Before Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 503 B |
|
After Width: | Height: | Size: 476 B |
|
After Width: | Height: | Size: 629 B |
|
After Width: | Height: | Size: 306 B |
BIN
docs-android/app/src/main/res/drawable-xhdpi/jpn.png
Normal file
|
After Width: | Height: | Size: 558 B |
BIN
docs-android/app/src/main/res/drawable-xxhdpi/eng.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
docs-android/app/src/main/res/drawable-xxhdpi/fra.png
Normal file
|
After Width: | Height: | Size: 505 B |
|
After Width: | Height: | Size: 222 B |
|
After Width: | Height: | Size: 493 B |
|
After Width: | Height: | Size: 341 B |
|
After Width: | Height: | Size: 476 B |
|
After Width: | Height: | Size: 353 B |
|
After Width: | Height: | Size: 339 B |
|
After Width: | Height: | Size: 325 B |