Compare commits
315 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c695572b28 | ||
|
|
79141edf70 | ||
|
|
b1e58396d1 | ||
|
|
b9cd113dc0 | ||
|
|
4a512af178 | ||
|
|
9506e9b8b4 | ||
|
|
3ff41d2002 | ||
|
|
f41dafe76d | ||
|
|
6f89a50fe5 | ||
|
|
e234440ce6 | ||
|
|
26685334a1 | ||
|
|
4d79dd7076 | ||
|
|
f5394534f7 | ||
|
|
faa66e01b6 | ||
|
|
bf4cb02de5 | ||
|
|
642b9a63d3 | ||
|
|
1ed7422171 | ||
|
|
3dd8a52f7d | ||
|
|
a55c55bbdb | ||
|
|
b851fd0ecc | ||
|
|
c8f7fe15ef | ||
|
|
73133f5ba5 | ||
|
|
eaf2e816b4 | ||
|
|
62020864ef | ||
|
|
f12e3ec663 | ||
|
|
5226df53a2 | ||
|
|
a59c67d774 | ||
|
|
1b1d5e9b4c | ||
|
|
37fc2d09bb | ||
|
|
bc94466cf7 | ||
|
|
6af7b6fce9 | ||
|
|
f2ae899938 | ||
|
|
c398a3c4f5 | ||
|
|
27027ec412 | ||
|
|
ddf9e83a9b | ||
|
|
0f661e5a34 | ||
|
|
09a53d5c4e | ||
|
|
542ab737a2 | ||
|
|
6e1276293f | ||
|
|
4e768e9103 | ||
|
|
55cdca0c7d | ||
|
|
9b52395786 | ||
|
|
50b02c800c | ||
|
|
2bdae5ea5c | ||
|
|
c49827ce25 | ||
|
|
64db701498 | ||
|
|
e16ce4b4f1 | ||
|
|
77d1e87fdb | ||
|
|
7d7adeeca0 | ||
|
|
8ad9c529b6 | ||
|
|
274512a58e | ||
|
|
ef16561272 | ||
|
|
98350860eb | ||
|
|
1343948d33 | ||
|
|
e616add75a | ||
|
|
b33b7115ef | ||
|
|
fb0bb62eaf | ||
|
|
5f84da61c8 | ||
|
|
6e6babd2e3 | ||
|
|
b28e08e2c7 | ||
|
|
718728a672 | ||
|
|
5de77e35dc | ||
|
|
5a41e9555e | ||
|
|
6598b585a2 | ||
|
|
a81474b40a | ||
|
|
ee159f5b36 | ||
|
|
ced64a5d1f | ||
|
|
689a4e6aae | ||
|
|
21b3ba2bf6 | ||
|
|
7be2e1b9e5 | ||
|
|
c1c2228937 | ||
|
|
3b9a66d1d8 | ||
|
|
a5ce5bf9ec | ||
|
|
43a1575187 | ||
|
|
eb5f207cc1 | ||
|
|
de3f055323 | ||
|
|
6012cdd9a5 | ||
|
|
0fab8ff935 | ||
|
|
00ee2d3bf6 | ||
|
|
c2a2e9f585 | ||
|
|
31fff7e021 | ||
|
|
0dda01269f | ||
|
|
d58b0e8f74 | ||
|
|
1bbb21c7c6 | ||
|
|
24713f54e2 | ||
|
|
5e2bd76e10 | ||
|
|
78d4b5797b | ||
|
|
ff91521a67 | ||
|
|
0525754337 | ||
|
|
ca8c525de0 | ||
|
|
1e7d2fcfd9 | ||
|
|
7e983bebb9 | ||
|
|
f927193ae9 | ||
|
|
a102bf04f4 | ||
|
|
919948489d | ||
|
|
12efd5c11f | ||
|
|
25a2144b31 | ||
|
|
59682b5ba6 | ||
|
|
7deaeca7b5 | ||
|
|
a7a6adfa34 | ||
|
|
7f19f8c112 | ||
|
|
943465a390 | ||
|
|
2824878065 | ||
|
|
508a1230e9 | ||
|
|
0ad7ef43d5 | ||
|
|
67171e05b9 | ||
|
|
adebb7ff6d | ||
|
|
6fbcd46a76 | ||
|
|
ef3a592807 | ||
|
|
d8d01b077d | ||
|
|
831e2e60ed | ||
|
|
2d858e6e11 | ||
|
|
f9c3715d8d | ||
|
|
359f5b5f49 | ||
|
|
ed51b77b0e | ||
|
|
47082ceee9 | ||
|
|
98497f2a37 | ||
|
|
d3a74ed361 | ||
|
|
7f2f480b25 | ||
|
|
34d1422868 | ||
|
|
509ab82745 | ||
|
|
7f325e3eb5 | ||
|
|
e23ca4b8c1 | ||
|
|
a0f309c957 | ||
|
|
0db4f1643d | ||
|
|
cfa5888be9 | ||
|
|
3172a5f216 | ||
|
|
456fc5b991 | ||
|
|
d9509474b0 | ||
|
|
e7a289ffb5 | ||
|
|
b9a4f0f1e0 | ||
|
|
0f4e5a8f6d | ||
|
|
83e1191a8a | ||
|
|
2c791f5123 | ||
|
|
25a17ae2da | ||
|
|
0591f8a39f | ||
|
|
eb61b06784 | ||
|
|
0d1a4ec7ea | ||
|
|
5f82752416 | ||
|
|
332de409b8 | ||
|
|
24d8784e1b | ||
|
|
7708f61343 | ||
|
|
1a37d97a61 | ||
|
|
046984a447 | ||
|
|
5f516047bd | ||
|
|
e930ce4d47 | ||
|
|
3dbdf88124 | ||
|
|
d428ce162b | ||
|
|
bc323e9945 | ||
|
|
ef69feeae7 | ||
|
|
e36143b61c | ||
|
|
aa97253ec7 | ||
|
|
0fab0e4fc0 | ||
|
|
90a4949d76 | ||
|
|
1466fb4d6c | ||
|
|
d41172abb6 | ||
|
|
24ca81e91c | ||
|
|
1cae964c09 | ||
|
|
dd671795e6 | ||
|
|
2948c0c860 | ||
|
|
978fbf2cf9 | ||
|
|
60ee000b6c | ||
|
|
634ab7ec38 | ||
|
|
7e5aa9aecf | ||
|
|
1c7381376c | ||
|
|
c7ce42fb3f | ||
|
|
fc3a8bb4ae | ||
|
|
82b39586f0 | ||
|
|
c365c6f6e0 | ||
|
|
9afd52108b | ||
|
|
97252bb5da | ||
|
|
7eeaeb01a0 | ||
|
|
b3e44b84d2 | ||
|
|
af23cd4948 | ||
|
|
dc05ca0484 | ||
|
|
f94e069792 | ||
|
|
cd32f452e9 | ||
|
|
66cb7333c1 | ||
|
|
08633a993d | ||
|
|
2c782a23d8 | ||
|
|
c7b7527183 | ||
|
|
80bd11b44e | ||
|
|
99a596b2e1 | ||
|
|
cfde218d32 | ||
|
|
d8cefddebd | ||
|
|
50c7066f88 | ||
|
|
a95dcf488d | ||
|
|
0fe51d355c | ||
|
|
97694d5d59 | ||
|
|
e72fe3683c | ||
|
|
44c10b60cd | ||
|
|
467d14bacb | ||
|
|
6d73554967 | ||
|
|
36b5bf3bb2 | ||
|
|
d14db1d3fb | ||
|
|
9c97ab14f8 | ||
|
|
6558ff7e05 | ||
|
|
86473d5639 | ||
|
|
374310d13c | ||
|
|
4396ef83a3 | ||
|
|
6b9ef4ab31 | ||
|
|
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 | ||
|
|
b3aeac8bf4 | ||
|
|
34e3ac5478 | ||
|
|
ae566018d6 | ||
|
|
12c3c4750f | ||
|
|
42f23ed0d7 | ||
|
|
d76b8e32c8 | ||
|
|
6aaecb473f | ||
|
|
03976160bf | ||
|
|
438a38985a | ||
|
|
9802beaf7b | ||
|
|
6963fd9770 | ||
|
|
85aa16afba | ||
|
|
0e99f06310 |
4
.gitignore
vendored
@@ -4,6 +4,8 @@
|
||||
/*/bin
|
||||
/*/gen
|
||||
/*/target
|
||||
/*/*.iml
|
||||
/*/build
|
||||
/out
|
||||
/.idea
|
||||
/.project
|
||||
*.iml
|
||||
|
||||
11
.travis.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
language: java
|
||||
before_install:
|
||||
- sudo apt-get -qq update
|
||||
- sudo apt-get -y -q install tesseract-ocr tesseract-ocr-fra tesseract-ocr-jpn
|
||||
- sudo apt-get -y -q install haveged && sudo service haveged start
|
||||
env:
|
||||
global:
|
||||
- TESSDATA_PREFIX=/usr/share/tesseract-ocr
|
||||
- LC_NUMERIC=C
|
||||
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
|
||||
51
README.md
@@ -1,7 +1,13 @@
|
||||
Sismics Docs
|
||||
Sismics Docs [](http://travis-ci.org/sismics/docs)
|
||||
============
|
||||
|
||||

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

|
||||
|
||||
_Android application_
|
||||
|
||||
   
|
||||
|
||||
What is Docs?
|
||||
---------------
|
||||
@@ -15,29 +21,35 @@ Features
|
||||
|
||||
- Responsive user interface
|
||||
- Optical character recognition
|
||||
- Support image and PDF files
|
||||
- Support image, PDF, ODT and DOCX files
|
||||
- Flexible search engine
|
||||
- Full text search in image and PDF
|
||||
- SHA-256 encryption
|
||||
- Tag system
|
||||
- Multi-users
|
||||
- Document sharing
|
||||
- Full text search in all supported files
|
||||
- All [Dublin Core](http://dublincore.org/) metadata
|
||||
- 256-bit AES encryption of stored files
|
||||
- Tag system with nesting
|
||||
- User/group permission system
|
||||
- Hierarchical groups
|
||||
- Audit log
|
||||
- Comments
|
||||
- Storage quota per user
|
||||
- Document sharing by URL
|
||||
- RESTful Web API
|
||||
- Fully featured Android client
|
||||
- Tested to 100k documents
|
||||
|
||||
License
|
||||
-------
|
||||
Download
|
||||
--------
|
||||
|
||||
Docs is released under the terms of the GPL license. See `COPYING` for more
|
||||
information or see <http://opensource.org/licenses/GPL-2.0>.
|
||||
The latest release is downloadable here: <https://github.com/sismics/docs/releases> in WAR format.
|
||||
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
|
||||
----------------------------------
|
||||
|
||||
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-parent
|
||||
- docs-core
|
||||
- docs-web
|
||||
- docs-web-common
|
||||
@@ -47,9 +59,8 @@ or download the sources from GitHub.
|
||||
|
||||
#### Launch the build
|
||||
|
||||
From the `docs-parent` directory:
|
||||
From the root directory:
|
||||
|
||||
mvn -Pinit validate -N
|
||||
mvn clean -DskipTests install
|
||||
|
||||
#### Run a stand-alone version
|
||||
@@ -64,4 +75,10 @@ From the `docs-web` directory:
|
||||
|
||||
mvn -Pprod -DskipTests clean install
|
||||
|
||||
You will get your deployable WAR in the `target` directory.
|
||||
You will get your deployable WAR in the `docs-web/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,76 +0,0 @@
|
||||
<?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">
|
||||
<component name="FacetManager">
|
||||
<facet type="android" name="Android">
|
||||
<configuration>
|
||||
<option name="SELECTED_BUILD_VARIANT" value="debug" />
|
||||
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
|
||||
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugJava" />
|
||||
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugTest" />
|
||||
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
|
||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
|
||||
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
|
||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
|
||||
</configuration>
|
||||
</facet>
|
||||
<facet type="android-gradle" name="Android-Gradle">
|
||||
<configuration>
|
||||
<option name="GRADLE_PROJECT_PATH" value=":app" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="false">
|
||||
<output url="file://$MODULE_DIR$/build/classes/debug" />
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/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/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/res/rs/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/r/test/debug" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/aidl/test/debug" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/buildConfig/test/debug" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/rs/test/debug" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/res/rs/test/debug" type="java-test-resource" />
|
||||
<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/jni" 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/debug/resources" type="java-resource" />
|
||||
<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/jni" 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/main/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/assets" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/jni" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/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/assets" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/bundles" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/classes" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/dependency-cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/incremental" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/libs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/manifests" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/res" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/symbols" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Android API 19 Platform" jdkType="Android SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" exported="" name="support-v4-18.0.0" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.7.+'
|
||||
classpath 'com.android.tools.build:gradle:2.1.0-beta1'
|
||||
}
|
||||
}
|
||||
apply plugin: 'android'
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 19
|
||||
buildToolsVersion "19.0.0"
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion '23.0.3'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 19
|
||||
targetSdkVersion 23
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
@@ -27,14 +27,36 @@ android {
|
||||
sourceCompatibility 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 {
|
||||
release {
|
||||
runProguard false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.android.support:support-v4:18.0.0'
|
||||
compile fileTree(dir: 'libs', include: '*.jar')
|
||||
compile 'com.android.support:appcompat-v7:23.3.0'
|
||||
compile 'com.android.support:recyclerview-v7:23.3.0'
|
||||
compile 'com.android.support:design:23.3.0'
|
||||
compile 'it.sephiroth.android.library.imagezoom:imagezoom:1.0.5'
|
||||
compile 'org.greenrobot:eventbus:3.0.0'
|
||||
compile 'com.squareup.picasso:picasso:2.5.2'
|
||||
compile 'com.squareup.okhttp3:okhttp:3.1.1'
|
||||
compile "com.squareup.okhttp3:okhttp-urlconnection:3.1.1"
|
||||
compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.0.2'
|
||||
}
|
||||
|
||||
BIN
docs-android/app/libs/tokenautocomplete-1.2.1.jar
Normal file
@@ -1,29 +1,78 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.sismics.docs" >
|
||||
package="com.sismics.docs"
|
||||
android:installLocation="auto">
|
||||
|
||||
<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
|
||||
android:name=".MainApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme" >
|
||||
<activity
|
||||
android:name="com.sismics.docs.DocListActivity"
|
||||
android:label="@string/app_name" >
|
||||
android:name=".activity.LoginActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppThemeDark">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.sismics.docs.DocDetailActivity"
|
||||
android:label="@string/title_doc_detail"
|
||||
android:parentActivityName="com.sismics.docs.DocListActivity" >
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.sismics.docs.DocListActivity" />
|
||||
android:name=".activity.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
android:windowSoftInputMode="adjustNothing">
|
||||
<intent-filter>
|
||||
<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
|
||||
android:name=".activity.DocumentViewActivity"
|
||||
android:label="">
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.DocumentEditActivity"
|
||||
android:label="@string/new_document">
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.AuditLogActivity"
|
||||
android:label="@string/latest_activity">
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.UserProfileActivity">
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.GroupProfileActivity">
|
||||
</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>
|
||||
|
||||
</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,26 @@
|
||||
package com.sismics.docs;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
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 Provide documents to intent action get content
|
||||
|
||||
super.onCreate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.sismics.docs.activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import com.sismics.docs.R;
|
||||
import com.sismics.docs.adapter.AuditLogListAdapter;
|
||||
import com.sismics.docs.listener.HttpCallback;
|
||||
import com.sismics.docs.model.application.ApplicationContext;
|
||||
import com.sismics.docs.resource.AuditLogResource;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Audit log activity.
|
||||
*
|
||||
* @author bgamard.
|
||||
*/
|
||||
public class AuditLogActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Check if logged in
|
||||
if (!ApplicationContext.getInstance().isLoggedIn()) {
|
||||
startActivity(new Intent(this, LoginActivity.class));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle activity context
|
||||
if (getIntent() == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Input document ID (optional)
|
||||
final String documentId = getIntent().getStringExtra("documentId");
|
||||
|
||||
// Setup the activity
|
||||
setContentView(R.layout.auditlog_activity);
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
}
|
||||
|
||||
// Configure the swipe refresh layout
|
||||
SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) 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() {
|
||||
refreshView(documentId);
|
||||
}
|
||||
});
|
||||
|
||||
// Navigate to user profile on click
|
||||
final ListView auditLogListView = (ListView) findViewById(R.id.auditLogListView);
|
||||
auditLogListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (auditLogListView.getAdapter() == null) {
|
||||
return;
|
||||
}
|
||||
AuditLogListAdapter adapter = (AuditLogListAdapter) auditLogListView.getAdapter();
|
||||
String username = adapter.getItem(position).optString("username");
|
||||
Intent intent = new Intent(AuditLogActivity.this, UserProfileActivity.class);
|
||||
intent.putExtra("username", username);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
// Get audit log list
|
||||
refreshView(documentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the view.
|
||||
*/
|
||||
private void refreshView(String documentId) {
|
||||
final SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
|
||||
final ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
|
||||
final ListView auditLogListView = (ListView) findViewById(R.id.auditLogListView);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
auditLogListView.setVisibility(View.GONE);
|
||||
AuditLogResource.list(this, documentId, new HttpCallback() {
|
||||
@Override
|
||||
public void onSuccess(JSONObject response) {
|
||||
auditLogListView.setAdapter(new AuditLogListAdapter(response.optJSONArray("logs")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
auditLogListView.setVisibility(View.VISIBLE);
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
package com.sismics.docs.activity;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
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.HttpCallback;
|
||||
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.greenrobot.eventbus.EventBus;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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.isNull("description") ? "" : 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);
|
||||
|
||||
// Server callback
|
||||
HttpCallback callback = new HttpCallback() {
|
||||
@Override
|
||||
public void onSuccess(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 onFailure(JSONObject json, Exception e) {
|
||||
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,808 @@
|
||||
package com.sismics.docs.activity;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ClipData;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
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.ContextMenu;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
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.CommentListAdapter;
|
||||
import com.sismics.docs.adapter.FilePagerAdapter;
|
||||
import com.sismics.docs.event.CommentAddEvent;
|
||||
import com.sismics.docs.event.CommentDeleteEvent;
|
||||
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.DocExportPdfFragment;
|
||||
import com.sismics.docs.fragment.DocShareFragment;
|
||||
import com.sismics.docs.listener.HttpCallback;
|
||||
import com.sismics.docs.model.application.ApplicationContext;
|
||||
import com.sismics.docs.resource.CommentResource;
|
||||
import com.sismics.docs.resource.DocumentResource;
|
||||
import com.sismics.docs.resource.FileResource;
|
||||
import com.sismics.docs.service.FileUploadService;
|
||||
import com.sismics.docs.util.NetworkUtil;
|
||||
import com.sismics.docs.util.PreferenceUtil;
|
||||
import com.sismics.docs.util.TagUtil;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Comment list adapter.
|
||||
*/
|
||||
private CommentListAdapter commentListAdapter;
|
||||
|
||||
/**
|
||||
* 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(final 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.isEmpty() || document.isNull("description")) {
|
||||
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 export PDF
|
||||
button = (Button) findViewById(R.id.actionExportPdf);
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
DialogFragment dialog = DocExportPdfFragment.newInstance(
|
||||
document.optString("id"), document.optString("title"));
|
||||
dialog.show(getSupportFragmentManager(), "DocExportPdfFragment");
|
||||
}
|
||||
});
|
||||
|
||||
// Action share
|
||||
button = (Button) findViewById(R.id.actionSharing);
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
DialogFragment dialog = DocShareFragment.newInstance(document.optString("id"));
|
||||
dialog.show(getSupportFragmentManager(), "DocShareFragment");
|
||||
}
|
||||
});
|
||||
|
||||
// Action audit log
|
||||
button = (Button) findViewById(R.id.actionAuditLog);
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent intent = new Intent(DocumentViewActivity.this, AuditLogActivity.class);
|
||||
intent.putExtra("documentId", document.optString("id"));
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
// Button add a comment
|
||||
ImageButton imageButton = (ImageButton) findViewById(R.id.addCommentBtn);
|
||||
imageButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
final EditText commentEditText = (EditText) findViewById(R.id.commentEditText);
|
||||
if (commentEditText.getText().length() == 0) {
|
||||
// No content for the new comment
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(DocumentViewActivity.this, R.string.adding_comment, Toast.LENGTH_LONG).show();
|
||||
|
||||
CommentResource.add(DocumentViewActivity.this,
|
||||
DocumentViewActivity.this.document.optString("id"),
|
||||
commentEditText.getText().toString(),
|
||||
new HttpCallback() {
|
||||
public void onSuccess(JSONObject response) {
|
||||
EventBus.getDefault().post(new CommentAddEvent(response));
|
||||
commentEditText.setText("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(JSONObject json, Exception e) {
|
||||
Toast.makeText(DocumentViewActivity.this, R.string.comment_add_failure, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Grab the comments
|
||||
updateComments();
|
||||
|
||||
// Grab the attached files
|
||||
updateFiles();
|
||||
|
||||
// Grab the full document (used for ACLs, remaining metadata 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.comments:
|
||||
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
|
||||
if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
|
||||
drawerLayout.closeDrawer(GravityCompat.START);
|
||||
} else {
|
||||
drawerLayout.openDrawer(GravityCompat.START);
|
||||
}
|
||||
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 = document.optString("title") + "-" + position + "." + ext;
|
||||
|
||||
// Download the file
|
||||
String fileUrl = PreferenceUtil.getServerUrl(this) + "/api/file/" + file.optString("id") + "/data";
|
||||
NetworkUtil.downloadFile(this, fileUrl, fileName, document.optString("title"), getString(R.string.download_file_title));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Actual delete server call
|
||||
final String fileId = file.optString("id");
|
||||
FileResource.delete(DocumentViewActivity.this, fileId, new HttpCallback() {
|
||||
@Override
|
||||
public void onSuccess(JSONObject response) {
|
||||
EventBus.getDefault().post(new FileDeleteEvent(fileId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(JSONObject json, Exception e) {
|
||||
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 = document.optString("title") + ".zip";
|
||||
NetworkUtil.downloadFile(this, url, fileName, document.optString("title"), getString(R.string.download_document_title));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
// Actual delete server call
|
||||
final String documentId = document.optString("id");
|
||||
DocumentResource.delete(DocumentViewActivity.this, documentId, new HttpCallback() {
|
||||
@Override
|
||||
public void onSuccess(JSONObject response) {
|
||||
EventBus.getDefault().post(new DocumentDeleteEvent(documentId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(JSONObject json, Exception e) {
|
||||
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
|
||||
*/
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
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
|
||||
*/
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
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
|
||||
*/
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
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
|
||||
*/
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
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
|
||||
*/
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(FileAddEvent event) {
|
||||
if (document == null) return;
|
||||
if (document.optString("id").equals(event.getDocumentId())) {
|
||||
updateFiles();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A comment add event has been fired.
|
||||
*
|
||||
* @param event Comment add event
|
||||
*/
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(CommentAddEvent event) {
|
||||
if (commentListAdapter == null) return;
|
||||
TextView emptyView = (TextView) findViewById(R.id.commentEmptyView);
|
||||
ListView listView = (ListView) findViewById(R.id.commentListView);
|
||||
emptyView.setVisibility(View.GONE);
|
||||
listView.setVisibility(View.VISIBLE);
|
||||
commentListAdapter.add(event.getComment());
|
||||
}
|
||||
|
||||
/**
|
||||
* A comment delete event has been fired.
|
||||
*
|
||||
* @param event Comment add event
|
||||
*/
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(CommentDeleteEvent event) {
|
||||
if (commentListAdapter == null) return;
|
||||
TextView emptyView = (TextView) findViewById(R.id.commentEmptyView);
|
||||
ListView listView = (ListView) findViewById(R.id.commentListView);
|
||||
commentListAdapter.remove(event.getCommentId());
|
||||
if (commentListAdapter.getCount() == 0) {
|
||||
emptyView.setVisibility(View.VISIBLE);
|
||||
listView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@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 HttpCallback() {
|
||||
@Override
|
||||
public void onSuccess(JSONObject response) {
|
||||
document = response;
|
||||
boolean writable = document.optBoolean("writable");
|
||||
|
||||
if (menu != null) {
|
||||
menu.findItem(R.id.delete_file).setVisible(writable);
|
||||
}
|
||||
|
||||
// Action only available if the document is 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);
|
||||
final AclListAdapter aclListAdapter = new AclListAdapter(document.optJSONArray("acls"));
|
||||
aclListView.setAdapter(aclListAdapter);
|
||||
aclListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
AclListAdapter.AclItem acl = aclListAdapter.getItem(position);
|
||||
if (acl.getType().equals("USER")) {
|
||||
Intent intent = new Intent(DocumentViewActivity.this, UserProfileActivity.class);
|
||||
intent.putExtra("username", acl.getName());
|
||||
startActivity(intent);
|
||||
} else if (acl.getType().equals("GROUP")) {
|
||||
Intent intent = new Intent(DocumentViewActivity.this, GroupProfileActivity.class);
|
||||
intent.putExtra("name", acl.getName());
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Remaining metadata
|
||||
TextView creatorTextView = (TextView) findViewById(R.id.creatorTextView);
|
||||
final String creator = document.optString("creator");
|
||||
creatorTextView.setText(creator);
|
||||
creatorTextView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(DocumentViewActivity.this, UserProfileActivity.class);
|
||||
intent.putExtra("username", creator);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
|
||||
switch (view.getId()) {
|
||||
case R.id.commentListView:
|
||||
if (commentListAdapter == null || document == null) return;
|
||||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
|
||||
JSONObject comment = commentListAdapter.getItem(info.position);
|
||||
boolean writable = document.optBoolean("writable");
|
||||
String creator = comment.optString("creator");
|
||||
String username = ApplicationContext.getInstance().getUserInfo().optString("username");
|
||||
if (writable || creator.equals(username)) {
|
||||
menu.add(Menu.NONE, 0, 0, getString(R.string.comment_delete));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
// Use real ids if more than one item someday
|
||||
if (item.getItemId() == 0) {
|
||||
// Delete a comment
|
||||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
|
||||
if (commentListAdapter == null) return false;
|
||||
JSONObject comment = commentListAdapter.getItem(info.position);
|
||||
final String commentId = comment.optString("id");
|
||||
Toast.makeText(DocumentViewActivity.this, R.string.deleting_comment, Toast.LENGTH_LONG).show();
|
||||
|
||||
CommentResource.remove(DocumentViewActivity.this, commentId, new HttpCallback() {
|
||||
@Override
|
||||
public void onSuccess(JSONObject response) {
|
||||
EventBus.getDefault().post(new CommentDeleteEvent(commentId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(JSONObject json, Exception e) {
|
||||
Toast.makeText(DocumentViewActivity.this, R.string.error_deleting_comment, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh comments list.
|
||||
*/
|
||||
private void updateComments() {
|
||||
if (document == null) return;
|
||||
|
||||
final View progressBar = findViewById(R.id.commentProgressView);
|
||||
final TextView emptyView = (TextView) findViewById(R.id.commentEmptyView);
|
||||
final ListView listView = (ListView) findViewById(R.id.commentListView);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
emptyView.setVisibility(View.GONE);
|
||||
listView.setVisibility(View.GONE);
|
||||
registerForContextMenu(listView);
|
||||
|
||||
CommentResource.list(this, document.optString("id"), new HttpCallback() {
|
||||
@Override
|
||||
public void onSuccess(JSONObject response) {
|
||||
JSONArray comments = response.optJSONArray("comments");
|
||||
commentListAdapter = new CommentListAdapter(DocumentViewActivity.this, comments);
|
||||
listView.setAdapter(commentListAdapter);
|
||||
listView.setVisibility(View.VISIBLE);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
if (comments.length() == 0) {
|
||||
listView.setVisibility(View.GONE);
|
||||
emptyView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(JSONObject json, Exception e) {
|
||||
emptyView.setText(R.string.error_loading_comments);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
listView.setVisibility(View.GONE);
|
||||
emptyView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 HttpCallback() {
|
||||
@Override
|
||||
public void onSuccess(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 onFailure(JSONObject json, Exception e) {
|
||||
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,92 @@
|
||||
package com.sismics.docs.activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.sismics.docs.R;
|
||||
import com.sismics.docs.listener.HttpCallback;
|
||||
import com.sismics.docs.model.application.ApplicationContext;
|
||||
import com.sismics.docs.resource.UserResource;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Group profile activity.
|
||||
*
|
||||
* @author bgamard.
|
||||
*/
|
||||
public class GroupProfileActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Check if logged in
|
||||
if (!ApplicationContext.getInstance().isLoggedIn()) {
|
||||
startActivity(new Intent(this, LoginActivity.class));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle activity context
|
||||
if (getIntent() == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Input name
|
||||
final String name = getIntent().getStringExtra("name");
|
||||
if (name == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup the activity
|
||||
setTitle(name);
|
||||
setContentView(R.layout.groupprofile_activity);
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
}
|
||||
|
||||
// Get the group and populate the view
|
||||
final ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
|
||||
final View layoutView = findViewById(R.id.layout);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
layoutView.setVisibility(View.GONE);
|
||||
UserResource.get(this, name, new HttpCallback() {
|
||||
@Override
|
||||
public void onSuccess(JSONObject json) {
|
||||
TextView membersTextView = (TextView) findViewById(R.id.membersTextView);
|
||||
JSONArray members = json.optJSONArray("members");
|
||||
String output = "";
|
||||
for (int i = 0; i < members.length(); i++) {
|
||||
output += members.optString(i) + "; ";
|
||||
}
|
||||
membersTextView.setText(output);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
layoutView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
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 android.widget.TextView;
|
||||
|
||||
import com.sismics.docs.R;
|
||||
import com.sismics.docs.listener.CallbackListener;
|
||||
import com.sismics.docs.listener.HttpCallback;
|
||||
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.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);
|
||||
|
||||
TextView loginExplainTextView = (TextView) findViewById(R.id.loginExplain);
|
||||
loginExplainTextView.setText(Html.fromHtml(getString(R.string.login_explain)));
|
||||
loginExplainTextView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
final EditText txtServer = (EditText) findViewById(R.id.txtServer);
|
||||
final EditText txtUsername = (EditText) findViewById(R.id.txtUsername);
|
||||
final EditText txtPassword = (EditText) findViewById(R.id.txtPassword);
|
||||
final EditText txtValidationCode = (EditText) findViewById(R.id.txtValidationCode);
|
||||
final Button btnConnect = (Button) findViewById(R.id.btnConnect);
|
||||
loginForm = findViewById(R.id.loginForm);
|
||||
progressBar = findViewById(R.id.progressBar);
|
||||
|
||||
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
|
||||
|
||||
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(), txtValidationCode.getText().toString(),
|
||||
new HttpCallback() {
|
||||
@Override
|
||||
public void onSuccess(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 onFailure(JSONObject json, Exception e) {
|
||||
loginForm.setVisibility(View.VISIBLE);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
|
||||
if (json != null && json.optString("type").equals("ForbiddenError")) {
|
||||
DialogUtil.showOkDialog(LoginActivity.this, R.string.login_fail_title, R.string.login_fail);
|
||||
} else if (json != null && json.optString("type").equals("ValidationCodeRequired")) {
|
||||
txtValidationCode.setVisibility(View.VISIBLE);
|
||||
validator.addValidable(txtValidationCode, new Required());
|
||||
validator.validate();
|
||||
} 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 HttpCallback() {
|
||||
@Override
|
||||
public void onSuccess(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 onFailure(JSONObject json, Exception e) {
|
||||
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,287 @@
|
||||
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.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.HttpCallback;
|
||||
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.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* 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.addDrawerListener(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 HttpCallback() {
|
||||
@Override
|
||||
public void onSuccess(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 onFailure(JSONObject json, Exception e) {
|
||||
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;
|
||||
TagListAdapter.TagItem tagItem = adapter.getItem(position);
|
||||
if (tagItem == null) return;
|
||||
searchQuery("tag:" + tagItem.getName());
|
||||
}
|
||||
});
|
||||
|
||||
// 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");
|
||||
}
|
||||
});
|
||||
|
||||
// Click on Latest activity
|
||||
View auditLogLayout = findViewById(R.id.auditLogLayout);
|
||||
auditLogLayout.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
startActivity(new Intent(MainActivity.this, AuditLogActivity.class));
|
||||
}
|
||||
});
|
||||
|
||||
handleIntent(getIntent());
|
||||
|
||||
EventBus.getDefault().register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.logout:
|
||||
UserResource.logout(getApplicationContext(), new HttpCallback() {
|
||||
@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
|
||||
*/
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(AdvancedSearchEvent event) {
|
||||
searchQuery(event.getQuery());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
EventBus.getDefault().unregister(this);
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
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,91 @@
|
||||
package com.sismics.docs.activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.sismics.docs.R;
|
||||
import com.sismics.docs.listener.HttpCallback;
|
||||
import com.sismics.docs.model.application.ApplicationContext;
|
||||
import com.sismics.docs.resource.UserResource;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* User profile activity.
|
||||
*
|
||||
* @author bgamard.
|
||||
*/
|
||||
public class UserProfileActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Check if logged in
|
||||
if (!ApplicationContext.getInstance().isLoggedIn()) {
|
||||
startActivity(new Intent(this, LoginActivity.class));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle activity context
|
||||
if (getIntent() == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Input username
|
||||
final String username = getIntent().getStringExtra("username");
|
||||
if (username == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup the activity
|
||||
setTitle(username);
|
||||
setContentView(R.layout.userprofile_activity);
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
}
|
||||
|
||||
// Get the user and populate the view
|
||||
final ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
|
||||
final View layoutView = findViewById(R.id.layout);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
layoutView.setVisibility(View.GONE);
|
||||
UserResource.get(this, username, new HttpCallback() {
|
||||
@Override
|
||||
public void onSuccess(JSONObject json) {
|
||||
TextView emailTextView = (TextView) findViewById(R.id.emailTextView);
|
||||
emailTextView.setText(json.optString("email"));
|
||||
|
||||
TextView quotaTextView = (TextView) findViewById(R.id.quotaTextView);
|
||||
quotaTextView.setText(getString(R.string.storage_display,
|
||||
Math.round(json.optLong("storage_current") / 1000000),
|
||||
Math.round(json.optLong("storage_quota") / 1000000)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
layoutView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.sismics.docs.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
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<AclItem> aclItemList;
|
||||
|
||||
/**
|
||||
* ACL list adapter.
|
||||
*
|
||||
* @param acls ACLs
|
||||
*/
|
||||
public AclListAdapter(JSONArray acls) {
|
||||
this.aclItemList = new ArrayList<>();
|
||||
|
||||
// Group ACLs
|
||||
for (int i = 0; i < acls.length(); i++) {
|
||||
JSONObject acl = acls.optJSONObject(i);
|
||||
String type = acl.optString("type");
|
||||
String name = acl.optString("name");
|
||||
String perm = acl.optString("perm");
|
||||
|
||||
boolean found = false;
|
||||
for (AclItem aclItem : aclItemList) {
|
||||
if (aclItem.type.equals(type) && aclItem.name.equals(name)) {
|
||||
aclItem.permList.add(perm);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
AclItem aclItem = new AclItem();
|
||||
aclItem.type = type;
|
||||
aclItem.name = name;
|
||||
aclItem.permList.add(perm);
|
||||
this.aclItemList.add(aclItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return aclItemList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AclItem getItem(int position) {
|
||||
return aclItemList.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return getItem(position).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 AclItem aclItem = getItem(position);
|
||||
TextView typeTextView = (TextView) view.findViewById(R.id.typeTextView);
|
||||
typeTextView.setText(aclItem.type);
|
||||
TextView nameTextView = (TextView) view.findViewById(R.id.nameTextView);
|
||||
nameTextView.setText(aclItem.name);
|
||||
TextView permTextView = (TextView) view.findViewById(R.id.permTextView);
|
||||
permTextView.setText(TextUtils.join(" + ", aclItem.permList));
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* An ACL item in the list.
|
||||
* Permissions are grouped together.
|
||||
*/
|
||||
public static class AclItem {
|
||||
private String type;
|
||||
private String name;
|
||||
private List<String> permList = new ArrayList<>();
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (type + name).hashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.sismics.docs.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.DateFormat;
|
||||
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.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Audit log list adapter.
|
||||
*
|
||||
* @author bgamard.
|
||||
*/
|
||||
public class AuditLogListAdapter extends BaseAdapter {
|
||||
/**
|
||||
* Shares.
|
||||
*/
|
||||
private List<JSONObject> logList;
|
||||
|
||||
/**
|
||||
* Audit log list adapter.
|
||||
*
|
||||
* @param logs Logs
|
||||
*/
|
||||
public AuditLogListAdapter(JSONArray logs) {
|
||||
this.logList = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < logs.length(); i++) {
|
||||
logList.add(logs.optJSONObject(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return logList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getItem(int position) {
|
||||
return logList.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return getItem(position).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.auditlog_list_item, parent, false);
|
||||
}
|
||||
|
||||
// Build message
|
||||
final JSONObject log = getItem(position);
|
||||
StringBuilder message = new StringBuilder(log.optString("class"));
|
||||
switch (log.optString("type")) {
|
||||
case "CREATE": message.append(" created"); break;
|
||||
case "UPDATE": message.append(" updated"); break;
|
||||
case "DELETE": message.append(" deleted"); break;
|
||||
}
|
||||
switch (log.optString("class")) {
|
||||
case "Document":
|
||||
case "Acl":
|
||||
case "Tag":
|
||||
case "User":
|
||||
case "Group":
|
||||
message.append(" : ");
|
||||
message.append(log.optString("message"));
|
||||
break;
|
||||
}
|
||||
|
||||
// Fill the view
|
||||
TextView usernameTextView = (TextView) view.findViewById(R.id.usernameTextView);
|
||||
TextView messageTextView = (TextView) view.findViewById(R.id.messageTextView);
|
||||
TextView dateTextView = (TextView) view.findViewById(R.id.dateTextView);
|
||||
usernameTextView.setText(log.optString("username"));
|
||||
messageTextView.setText(message);
|
||||
String date = DateFormat.getDateFormat(parent.getContext()).format(new Date(log.optLong("create_date")));
|
||||
dateTextView.setText(date);
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.sismics.docs.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.format.DateFormat;
|
||||
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 com.sismics.docs.util.OkHttpUtil;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Comment list adapter.
|
||||
*
|
||||
* @author bgamard.
|
||||
*/
|
||||
public class CommentListAdapter extends BaseAdapter {
|
||||
/**
|
||||
* Tags.
|
||||
*/
|
||||
private List<JSONObject> commentList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Context.
|
||||
*/
|
||||
private Context context;
|
||||
|
||||
/**
|
||||
* Comment list adapter.
|
||||
*
|
||||
* @param commentsArray Comments
|
||||
*/
|
||||
public CommentListAdapter(Context context, JSONArray commentsArray) {
|
||||
this.context = context;
|
||||
for (int i = 0; i < commentsArray.length(); i++) {
|
||||
commentList.add(commentsArray.optJSONObject(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return commentList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getItem(int position) {
|
||||
return commentList.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.comment_list_item, parent, false);
|
||||
}
|
||||
|
||||
// Fill the view
|
||||
JSONObject comment = getItem(position);
|
||||
TextView creatorTextView = (TextView) view.findViewById(R.id.creatorTextView);
|
||||
TextView dateTextView = (TextView) view.findViewById(R.id.dateTextView);
|
||||
TextView contentTextView = (TextView) view.findViewById(R.id.contentTextView);
|
||||
ImageView gravatarImageView = (ImageView) view.findViewById(R.id.gravatarImageView);
|
||||
creatorTextView.setText(comment.optString("creator"));
|
||||
dateTextView.setText(DateFormat.getDateFormat(dateTextView.getContext()).format(new Date(comment.optLong("create_date"))));
|
||||
contentTextView.setText(comment.optString("content"));
|
||||
|
||||
// Gravatar image
|
||||
String gravatarUrl = "http://www.gravatar.com/avatar/" + comment.optString("creator_gravatar") + "?s=128d=identicon";
|
||||
OkHttpUtil.picasso(context)
|
||||
.load(gravatarUrl)
|
||||
.into(gravatarImageView);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new comment.
|
||||
*
|
||||
* @param comment Comment
|
||||
*/
|
||||
public void add(JSONObject comment) {
|
||||
commentList.add(comment);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a comment.
|
||||
*
|
||||
* @param commentId Comment ID
|
||||
*/
|
||||
public void remove(String commentId) {
|
||||
for (JSONObject comment : commentList) {
|
||||
if (comment.optString("id").equals(commentId)) {
|
||||
commentList.remove(comment);
|
||||
notifyDataSetChanged();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,134 @@
|
||||
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.sismics.docs.R;
|
||||
import com.sismics.docs.util.OkHttpUtil;
|
||||
import com.sismics.docs.util.PreferenceUtil;
|
||||
import com.squareup.picasso.Callback;
|
||||
import com.squareup.picasso.MemoryPolicy;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Context.
|
||||
*/
|
||||
private Context context;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
@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);
|
||||
final ProgressBar progressBar = (ProgressBar) view.findViewById(R.id.fileProgressBar);
|
||||
JSONObject file = files.get(position);
|
||||
String fileUrl = PreferenceUtil.getServerUrl(context) + "/api/file/" + file.optString("id") + "/data?size=web";
|
||||
|
||||
// Load image
|
||||
OkHttpUtil.picasso(context)
|
||||
.load(fileUrl)
|
||||
.memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE) // Don't memory cache the images
|
||||
.into(fileImageView, new Callback.EmptyCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
|
||||
fileImageView.setDisplayType(ImageViewTouchBase.DisplayType.FIT_TO_SCREEN);
|
||||
|
||||
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 || position < 0 || position >= files.size()) {
|
||||
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,98 @@
|
||||
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.greenrobot.eventbus.EventBus;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 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,136 @@
|
||||
package com.sismics.docs.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.TypedValue;
|
||||
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.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Tag list adapter.
|
||||
*
|
||||
* @author bgamard.
|
||||
*/
|
||||
public class TagListAdapter extends BaseAdapter {
|
||||
/**
|
||||
* Tags.
|
||||
*/
|
||||
private List<TagItem> tagItemList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Tag list adapter.
|
||||
*
|
||||
* @param tagsArray Tags
|
||||
*/
|
||||
public TagListAdapter(JSONArray tagsArray) {
|
||||
List<JSONObject> tags = new ArrayList<>();
|
||||
for (int i = 0; i < tagsArray.length(); i++) {
|
||||
tags.add(tagsArray.optJSONObject(i));
|
||||
}
|
||||
|
||||
// Reorder tags by parent/child relation and compute depth
|
||||
int depth = 0;
|
||||
initTags(tags, JSONObject.NULL.toString(), depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init tags model recursively.
|
||||
*
|
||||
* @param tags All tags from server
|
||||
* @param parentId Parent ID
|
||||
* @param depth Depth
|
||||
*/
|
||||
private void initTags(List<JSONObject> tags, String parentId, int depth) {
|
||||
// Get all tags with this parent
|
||||
for (JSONObject tag : tags) {
|
||||
String tagParentId = tag.optString("parent");
|
||||
if (tagParentId.equals(parentId)) {
|
||||
TagItem tagItem = new TagItem();
|
||||
tagItem.id = tag.optString("id");
|
||||
tagItem.name = tag.optString("name");
|
||||
tagItem.count = tag.optInt("count");
|
||||
tagItem.color = tag.optString("color");
|
||||
tagItem.depth = depth;
|
||||
tagItemList.add(tagItem);
|
||||
initTags(tags, tagItem.id, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return tagItemList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TagItem getItem(int position) {
|
||||
return tagItemList.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.tag_list_item, parent, false);
|
||||
}
|
||||
|
||||
// Fill the view
|
||||
TagItem tagItem = getItem(position);
|
||||
TextView tagTextView = (TextView) view.findViewById(R.id.tagTextView);
|
||||
tagTextView.setText(tagItem.name);
|
||||
TextView tagCountTextView = (TextView) view.findViewById(R.id.tagCountTextView);
|
||||
tagCountTextView.setText(String.format(Locale.ENGLISH, "%d", tagItem.count));
|
||||
|
||||
// Label color filtering
|
||||
ImageView labelImageView = (ImageView) view.findViewById(R.id.labelImageView);
|
||||
Drawable labelDrawable = labelImageView.getDrawable().mutate();
|
||||
labelDrawable.setColorFilter(Color.parseColor(tagItem.color), PorterDuff.Mode.MULTIPLY);
|
||||
labelImageView.setImageDrawable(labelDrawable);
|
||||
labelImageView.invalidate();
|
||||
|
||||
// Offset according to depth
|
||||
Resources resources = parent.getContext().getResources();
|
||||
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) labelImageView.getLayoutParams();
|
||||
layoutParams.leftMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, tagItem.depth * 12, resources.getDisplayMetrics());
|
||||
labelImageView.setLayoutParams(layoutParams);
|
||||
labelImageView.requestLayout();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* A tag item in the tags list.
|
||||
*/
|
||||
public static class TagItem {
|
||||
private String id;
|
||||
private String name;
|
||||
private int count;
|
||||
private String color;
|
||||
private int depth;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* Comment add event.
|
||||
*
|
||||
* @author bgamard.
|
||||
*/
|
||||
public class CommentAddEvent {
|
||||
/**
|
||||
* Comment.
|
||||
*/
|
||||
private JSONObject comment;
|
||||
|
||||
/**
|
||||
* Create a comment add event.
|
||||
*
|
||||
* @param comment Comment
|
||||
*/
|
||||
public CommentAddEvent(JSONObject comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of comment.
|
||||
*
|
||||
* @return comment
|
||||
*/
|
||||
public JSONObject getComment() {
|
||||
return comment;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.sismics.docs.event;
|
||||
|
||||
/**
|
||||
* Comment delete event.
|
||||
*
|
||||
* @author bgamard.
|
||||
*/
|
||||
public class CommentDeleteEvent {
|
||||
/**
|
||||
* Comment ID.
|
||||
*/
|
||||
private String commentId;
|
||||
|
||||
/**
|
||||
* Create a comment add event.
|
||||
*
|
||||
* @param commentId Comment ID
|
||||
*/
|
||||
public CommentDeleteEvent(String commentId) {
|
||||
this.commentId = commentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of commentId.
|
||||
*
|
||||
* @return commentId
|
||||
*/
|
||||
public String getCommentId() {
|
||||
return commentId;
|
||||
}
|
||||
}
|
||||
@@ -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,95 @@
|
||||
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.widget.CheckBox;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.sismics.docs.R;
|
||||
import com.sismics.docs.util.NetworkUtil;
|
||||
import com.sismics.docs.util.PreferenceUtil;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Export PDF dialog fragment.
|
||||
*
|
||||
* @author bgamard.
|
||||
*/
|
||||
public class DocExportPdfFragment extends DialogFragment {
|
||||
/**
|
||||
* Export PDF dialog fragment.
|
||||
*
|
||||
* @param id Document ID
|
||||
* @param title Document title
|
||||
*/
|
||||
public static DocExportPdfFragment newInstance(String id, String title) {
|
||||
DocExportPdfFragment fragment = new DocExportPdfFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString("id", id);
|
||||
args.putString("title", title);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
// Setup the view
|
||||
LayoutInflater inflater = getActivity().getLayoutInflater();
|
||||
View view = inflater.inflate(R.layout.document_export_pdf_dialog, null);
|
||||
final SeekBar marginSeekBar = (SeekBar) view.findViewById(R.id.marginSeekBar);
|
||||
final CheckBox exportMetadataCheckbox = (CheckBox) view.findViewById(R.id.exportMetadataCheckbox);
|
||||
final CheckBox exportCommentsCheckbox = (CheckBox) view.findViewById(R.id.exportCommentsCheckbox);
|
||||
final CheckBox fitToPageCheckbox = (CheckBox) view.findViewById(R.id.fitToPageCheckbox);
|
||||
final TextView marginValueText = (TextView) view.findViewById(R.id.marginValueText);
|
||||
|
||||
// Margin label follow seekbar value
|
||||
marginSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
marginValueText.setText(String.format(Locale.ENGLISH, "%d", progress));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// Build the dialog
|
||||
builder.setView(view)
|
||||
.setPositiveButton(R.string.download, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
// Download the PDF
|
||||
String pdfUrl = PreferenceUtil.getServerUrl(getActivity()) + "/api/document/" + getArguments().getString("id") + "/pdf?" +
|
||||
"metadata=" + exportMetadataCheckbox.isChecked() + "&comments=" + exportCommentsCheckbox.isChecked() + "&fitimagetopage=" + fitToPageCheckbox.isChecked() +
|
||||
"&margin=" + marginSeekBar.getProgress();
|
||||
String title = getArguments().getString("title");
|
||||
NetworkUtil.downloadFile(getActivity(), pdfUrl, title + ".pdf", title, getString(R.string.download_pdf_title));
|
||||
|
||||
getDialog().cancel();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
getDialog().cancel();
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
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.HttpCallback;
|
||||
import com.sismics.docs.listener.RecyclerItemClickListener;
|
||||
import com.sismics.docs.resource.DocumentResource;
|
||||
import com.sismics.docs.ui.view.DividerItemDecoration;
|
||||
import com.sismics.docs.ui.view.EmptyRecyclerView;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* @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.addOnScrollListener(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
|
||||
*/
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(SearchEvent event) {
|
||||
query = event.getQuery();
|
||||
loadDocuments(getView(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* A document edit event has been fired.
|
||||
*
|
||||
* @param event Document edit event
|
||||
*/
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(DocumentEditEvent event) {
|
||||
adapter.updateDocument(event.getDocument());
|
||||
}
|
||||
|
||||
/**
|
||||
* A document delete event has been fired.
|
||||
*
|
||||
* @param event Document delete event
|
||||
*/
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(DocumentDeleteEvent event) {
|
||||
adapter.deleteDocument(event.getDocumentId());
|
||||
}
|
||||
|
||||
/**
|
||||
* A document add event has been fired.
|
||||
*
|
||||
* @param event Document add event
|
||||
*/
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
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 HttpCallback() {
|
||||
@Override
|
||||
public void onSuccess(JSONObject response) {
|
||||
adapter.addDocuments(response.optJSONArray("documents"));
|
||||
documentsEmptyView.setText(R.string.no_documents);
|
||||
recyclerView.setEmptyView(documentsEmptyView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(JSONObject response, Exception e) {
|
||||
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,199 @@
|
||||
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.HttpCallback;
|
||||
import com.sismics.docs.resource.DocumentResource;
|
||||
import com.sismics.docs.resource.ShareResource;
|
||||
import com.sismics.docs.util.PreferenceUtil;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* 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 HttpCallback() {
|
||||
@Override
|
||||
public void onSuccess(JSONObject response) {
|
||||
shareNameEditText.setText("");
|
||||
loadShares(getDialog().getWindow().getDecorView());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(JSONObject json, Exception e) {
|
||||
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 HttpCallback() {
|
||||
@Override
|
||||
public void onSuccess(JSONObject response) {
|
||||
document = response;
|
||||
JSONArray acls = response.optJSONArray("acls");
|
||||
shareProgressBar.setVisibility(View.GONE);
|
||||
shareListView.setEmptyView(shareEmptyView);
|
||||
shareListView.setAdapter(new ShareListAdapter(acls));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(JSONObject json, Exception e) {
|
||||
getDialog().cancel();
|
||||
Toast.makeText(getActivity(), R.string.error_loading_shares, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A share delete event has been fired.
|
||||
*
|
||||
* @param event Share delete event
|
||||
*/
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(ShareDeleteEvent event) {
|
||||
ShareResource.delete(getActivity(), event.getId(), new HttpCallback() {
|
||||
@Override
|
||||
public void onSuccess(JSONObject response) {
|
||||
loadShares(getDialog().getWindow().getDecorView());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(JSONObject json, Exception e) {
|
||||
Toast.makeText(getActivity(), R.string.error_deleting_share, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A share send event has been fired.
|
||||
*
|
||||
* @param event Share send event
|
||||
*/
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
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,129 @@
|
||||
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.greenrobot.eventbus.EventBus;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 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 EditText creatorEditText = (EditText) view.findViewById(R.id.creatorEditText);
|
||||
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())
|
||||
.creator(creatorEditText.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.sismics.docs.R;
|
||||
import com.sismics.docs.provider.RecentSuggestionsProvider;
|
||||
import com.sismics.docs.util.ApplicationUtil;
|
||||
import com.sismics.docs.util.OkHttpUtil;
|
||||
import com.sismics.docs.util.PreferenceUtil;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
OkHttpUtil.clearCache(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,78 @@
|
||||
package com.sismics.docs.listener;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.Response;
|
||||
|
||||
/**
|
||||
* An HTTP callback.
|
||||
*
|
||||
* @author bgamard.
|
||||
*/
|
||||
public class HttpCallback {
|
||||
public void onSuccess(JSONObject json) {
|
||||
// Implement me
|
||||
}
|
||||
|
||||
public void onFailure(JSONObject json, Exception e) {
|
||||
// Implement me
|
||||
}
|
||||
|
||||
public void onFinish() {
|
||||
// Implement me
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an OkHttp Callback from a HttpCallback.
|
||||
*
|
||||
* @param httpCallback HttpCallback
|
||||
* @return OkHttp Callback
|
||||
*/
|
||||
public static Callback buildOkHttpCallback(final HttpCallback httpCallback) {
|
||||
return new Callback() {
|
||||
@Override
|
||||
public void onResponse(final Call call, final Response response) throws IOException {
|
||||
final String body = response.body().string();
|
||||
|
||||
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (response.isSuccessful()) {
|
||||
try {
|
||||
httpCallback.onSuccess(new JSONObject(body));
|
||||
} catch (Exception e) {
|
||||
httpCallback.onFailure(null, e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
httpCallback.onFailure(new JSONObject(body), null);
|
||||
} catch (Exception e) {
|
||||
httpCallback.onFailure(null, e);
|
||||
}
|
||||
}
|
||||
|
||||
httpCallback.onFinish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Call call, final IOException e) {
|
||||
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
httpCallback.onFailure(null, e);
|
||||
httpCallback.onFinish();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
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 {
|
||||
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.getChildAdapterPosition(childView));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) { }
|
||||
|
||||
@Override
|
||||
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { }
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
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.HttpCallback;
|
||||
import com.sismics.docs.resource.UserResource;
|
||||
import com.sismics.docs.util.PreferenceUtil;
|
||||
|
||||
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 HttpCallback() {
|
||||
@Override
|
||||
public void onSuccess(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,38 @@
|
||||
package com.sismics.docs.resource;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.sismics.docs.listener.HttpCallback;
|
||||
import com.sismics.docs.util.OkHttpUtil;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.Request;
|
||||
|
||||
/**
|
||||
* Access to /auditlog API.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class AuditLogResource extends BaseResource {
|
||||
/**
|
||||
* GET /auditlog.
|
||||
*
|
||||
* @param context Context
|
||||
* @param documentId Document ID
|
||||
* @param callback Callback
|
||||
*/
|
||||
public static void list(Context context, String documentId, HttpCallback callback) {
|
||||
HttpUrl.Builder httpUrlBuilder = HttpUrl.parse(getApiUrl(context) + "/auditlog")
|
||||
.newBuilder();
|
||||
if (documentId != null) {
|
||||
httpUrlBuilder.addQueryParameter("document", documentId);
|
||||
}
|
||||
Request request = new Request.Builder()
|
||||
.url(httpUrlBuilder.build())
|
||||
.get()
|
||||
.build();
|
||||
OkHttpUtil.buildClient(context)
|
||||
.newCall(request)
|
||||
.enqueue(HttpCallback.buildOkHttpCallback(callback));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.sismics.docs.resource;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.sismics.docs.util.PreferenceUtil;
|
||||
|
||||
/**
|
||||
* Base class for API access.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class BaseResource {
|
||||
/**
|
||||
* 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,73 @@
|
||||
package com.sismics.docs.resource;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.sismics.docs.listener.HttpCallback;
|
||||
import com.sismics.docs.util.OkHttpUtil;
|
||||
|
||||
import okhttp3.FormBody;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.Request;
|
||||
|
||||
|
||||
/**
|
||||
* Access to /comment API.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class CommentResource extends BaseResource {
|
||||
/**
|
||||
* GET /comment/id.
|
||||
*
|
||||
* @param context Context
|
||||
* @param documentId Document ID
|
||||
* @param callback Callback
|
||||
*/
|
||||
public static void list(Context context, String documentId, HttpCallback callback) {
|
||||
Request request = new Request.Builder()
|
||||
.url(HttpUrl.parse(getApiUrl(context) + "/comment/" + documentId))
|
||||
.get()
|
||||
.build();
|
||||
OkHttpUtil.buildClient(context)
|
||||
.newCall(request)
|
||||
.enqueue(HttpCallback.buildOkHttpCallback(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /comment.
|
||||
*
|
||||
* @param context Context
|
||||
* @param documentId Document ID
|
||||
* @param content Comment content
|
||||
* @param callback Callback
|
||||
*/
|
||||
public static void add(Context context, String documentId, String content, HttpCallback callback) {
|
||||
Request request = new Request.Builder()
|
||||
.url(HttpUrl.parse(getApiUrl(context) + "/comment"))
|
||||
.put(new FormBody.Builder()
|
||||
.add("id", documentId)
|
||||
.add("content", content)
|
||||
.build())
|
||||
.build();
|
||||
OkHttpUtil.buildClient(context)
|
||||
.newCall(request)
|
||||
.enqueue(HttpCallback.buildOkHttpCallback(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /comment/id.
|
||||
*
|
||||
* @param context Context
|
||||
* @param commentId Comment ID
|
||||
* @param callback Callback
|
||||
*/
|
||||
public static void remove(Context context, String commentId, HttpCallback callback) {
|
||||
Request request = new Request.Builder()
|
||||
.url(HttpUrl.parse(getApiUrl(context) + "/comment/" + commentId))
|
||||
.delete()
|
||||
.build();
|
||||
OkHttpUtil.buildClient(context)
|
||||
.newCall(request)
|
||||
.enqueue(HttpCallback.buildOkHttpCallback(callback));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package com.sismics.docs.resource;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.sismics.docs.listener.HttpCallback;
|
||||
import com.sismics.docs.util.OkHttpUtil;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import okhttp3.FormBody;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.Request;
|
||||
|
||||
/**
|
||||
* Access to /document API.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class DocumentResource extends BaseResource {
|
||||
/**
|
||||
* GET /document/list.
|
||||
*
|
||||
* @param context Context
|
||||
* @param offset Offset
|
||||
* @param query Search query
|
||||
* @param callback Callback
|
||||
*/
|
||||
public static void list(Context context, int offset, String query, HttpCallback callback) {
|
||||
Request request = new Request.Builder()
|
||||
.url(HttpUrl.parse(getApiUrl(context) + "/document/list")
|
||||
.newBuilder()
|
||||
.addQueryParameter("limit", "20")
|
||||
.addQueryParameter("offset", Integer.toString(offset))
|
||||
.addQueryParameter("sort_column", "3")
|
||||
.addQueryParameter("asc", "false")
|
||||
.addQueryParameter("search", query)
|
||||
.build())
|
||||
.get()
|
||||
.build();
|
||||
OkHttpUtil.buildClient(context)
|
||||
.newCall(request)
|
||||
.enqueue(HttpCallback.buildOkHttpCallback(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /document/id.
|
||||
*
|
||||
* @param context Context
|
||||
* @param id ID
|
||||
* @param callback Callback
|
||||
*/
|
||||
public static void get(Context context, String id, HttpCallback callback) {
|
||||
Request request = new Request.Builder()
|
||||
.url(HttpUrl.parse(getApiUrl(context) + "/document/" + id))
|
||||
.get()
|
||||
.build();
|
||||
OkHttpUtil.buildClient(context)
|
||||
.newCall(request)
|
||||
.enqueue(HttpCallback.buildOkHttpCallback(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /document/id.
|
||||
*
|
||||
* @param context Context
|
||||
* @param id ID
|
||||
* @param callback Callback
|
||||
*/
|
||||
public static void delete(Context context, String id, HttpCallback callback) {
|
||||
Request request = new Request.Builder()
|
||||
.url(HttpUrl.parse(getApiUrl(context) + "/document/" + id))
|
||||
.delete()
|
||||
.build();
|
||||
OkHttpUtil.buildClient(context)
|
||||
.newCall(request)
|
||||
.enqueue(HttpCallback.buildOkHttpCallback(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /document.
|
||||
*
|
||||
* @param context Context
|
||||
* @param title Title
|
||||
* @param description Description
|
||||
* @param tagIdList Tags ID list
|
||||
* @param language Language
|
||||
* @param createDate Create date
|
||||
* @param callback Callback
|
||||
*/
|
||||
public static void add(Context context, String title, String description,
|
||||
Set<String> tagIdList, String language, long createDate, HttpCallback callback) {
|
||||
FormBody.Builder formBuilder = new FormBody.Builder()
|
||||
.add("title", title)
|
||||
.add("description", description)
|
||||
.add("language", language)
|
||||
.add("create_date", Long.toString(createDate));
|
||||
for( String tagId : tagIdList) {
|
||||
formBuilder.add("tags", tagId);
|
||||
}
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(HttpUrl.parse(getApiUrl(context) + "/document"))
|
||||
.put(formBuilder.build())
|
||||
.build();
|
||||
OkHttpUtil.buildClient(context)
|
||||
.newCall(request)
|
||||
.enqueue(HttpCallback.buildOkHttpCallback(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 callback Callback
|
||||
*/
|
||||
public static void edit(Context context, String id, String title, String description,
|
||||
Set<String> tagIdList, String language, long createDate, HttpCallback callback) {
|
||||
FormBody.Builder formBuilder = new FormBody.Builder()
|
||||
.add("title", title)
|
||||
.add("description", description)
|
||||
.add("language", language)
|
||||
.add("create_date", Long.toString(createDate));
|
||||
for( String tagId : tagIdList) {
|
||||
formBuilder.add("tags", tagId);
|
||||
}
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(HttpUrl.parse(getApiUrl(context) + "/document/" + id))
|
||||
.post(formBuilder.build())
|
||||
.build();
|
||||
OkHttpUtil.buildClient(context)
|
||||
.newCall(request)
|
||||
.enqueue(HttpCallback.buildOkHttpCallback(callback));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.sismics.docs.resource;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.sismics.docs.listener.HttpCallback;
|
||||
import com.sismics.docs.util.OkHttpUtil;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.internal.Util;
|
||||
import okio.BufferedSink;
|
||||
import okio.Okio;
|
||||
import okio.Source;
|
||||
|
||||
|
||||
/**
|
||||
* Access to /file API.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class FileResource extends BaseResource {
|
||||
/**
|
||||
* GET /file/list.
|
||||
*
|
||||
* @param context Context
|
||||
* @param documentId Document ID
|
||||
* @param callback Callback
|
||||
*/
|
||||
public static void list(Context context, String documentId, HttpCallback callback) {
|
||||
Request request = new Request.Builder()
|
||||
.url(HttpUrl.parse(getApiUrl(context) + "/file/list")
|
||||
.newBuilder()
|
||||
.addQueryParameter("id", documentId)
|
||||
.build())
|
||||
.get()
|
||||
.build();
|
||||
OkHttpUtil.buildClient(context)
|
||||
.newCall(request)
|
||||
.enqueue(HttpCallback.buildOkHttpCallback(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /file/id.
|
||||
*
|
||||
* @param context Context
|
||||
* @param id ID
|
||||
* @param callback Callback
|
||||
*/
|
||||
public static void delete(Context context, String id, HttpCallback callback) {
|
||||
Request request = new Request.Builder()
|
||||
.url(HttpUrl.parse(getApiUrl(context) + "/file/" + id))
|
||||
.delete()
|
||||
.build();
|
||||
OkHttpUtil.buildClient(context)
|
||||
.newCall(request)
|
||||
.enqueue(HttpCallback.buildOkHttpCallback(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /file.
|
||||
*
|
||||
* @param context Context
|
||||
* @param documentId Document ID
|
||||
* @param is Input stream
|
||||
* @param callback Callback
|
||||
* @throws Exception
|
||||
*/
|
||||
public static void addSync(Context context, String documentId, final InputStream is, HttpCallback callback) throws Exception {
|
||||
Request request = new Request.Builder()
|
||||
.url(HttpUrl.parse(getApiUrl(context) + "/file"))
|
||||
.put(new MultipartBody.Builder()
|
||||
.setType(MultipartBody.FORM)
|
||||
.addFormDataPart("id", documentId)
|
||||
.addFormDataPart("file", "file", new RequestBody() {
|
||||
@Override
|
||||
public MediaType contentType() {
|
||||
return MediaType.parse("application/octet-stream");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(BufferedSink sink) throws IOException {
|
||||
Source source = Okio.source(is);
|
||||
try {
|
||||
sink.writeAll(source);
|
||||
} finally {
|
||||
Util.closeQuietly(source);
|
||||
}
|
||||
}
|
||||
})
|
||||
.build())
|
||||
.build();
|
||||
Response response = OkHttpUtil.buildClient(context)
|
||||
.newCall(request)
|
||||
.execute();
|
||||
|
||||
// Call the right callback
|
||||
final String body = response.body().string();
|
||||
if (response.isSuccessful()) {
|
||||
try {
|
||||
callback.onSuccess(new JSONObject(body));
|
||||
} catch (Exception e) {
|
||||
callback.onFailure(null, e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
callback.onFailure(new JSONObject(body), null);
|
||||
} catch (Exception e) {
|
||||
callback.onFailure(null, e);
|
||||
}
|
||||
}
|
||||
|
||||
callback.onFinish();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.sismics.docs.resource;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.sismics.docs.listener.HttpCallback;
|
||||
import com.sismics.docs.util.OkHttpUtil;
|
||||
|
||||
import okhttp3.FormBody;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.Request;
|
||||
|
||||
|
||||
/**
|
||||
* Access to /tag API.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class ShareResource extends BaseResource {
|
||||
/**
|
||||
* PUT /share.
|
||||
*
|
||||
* @param context Context
|
||||
* @param documentId Document ID
|
||||
* @param name Name
|
||||
* @param callback Callback
|
||||
*/
|
||||
public static void add(Context context, String documentId, String name, HttpCallback callback) {
|
||||
Request request = new Request.Builder()
|
||||
.url(HttpUrl.parse(getApiUrl(context) + "/share"))
|
||||
.put(new FormBody.Builder()
|
||||
.add("id", documentId)
|
||||
.add("name", name)
|
||||
.build())
|
||||
.build();
|
||||
OkHttpUtil.buildClient(context)
|
||||
.newCall(request)
|
||||
.enqueue(HttpCallback.buildOkHttpCallback(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /share.
|
||||
*
|
||||
* @param context Context
|
||||
* @param id ID
|
||||
* @param callback Callback
|
||||
*/
|
||||
public static void delete(Context context, String id, HttpCallback callback) {
|
||||
Request request = new Request.Builder()
|
||||
.url(HttpUrl.parse(getApiUrl(context) + "/share/" + id))
|
||||
.delete()
|
||||
.build();
|
||||
OkHttpUtil.buildClient(context)
|
||||
.newCall(request)
|
||||
.enqueue(HttpCallback.buildOkHttpCallback(callback));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.sismics.docs.resource;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.sismics.docs.listener.HttpCallback;
|
||||
import com.sismics.docs.util.OkHttpUtil;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.Request;
|
||||
|
||||
|
||||
/**
|
||||
* Access to /tag API.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class TagResource extends BaseResource {
|
||||
/**
|
||||
* GET /tag/stats.
|
||||
*
|
||||
* @param context Context
|
||||
* @param callback Callback
|
||||
*/
|
||||
public static void stats(Context context, HttpCallback callback) {
|
||||
Request request = new Request.Builder()
|
||||
.url(HttpUrl.parse(getApiUrl(context) + "/tag/stats"))
|
||||
.get()
|
||||
.build();
|
||||
OkHttpUtil.buildClient(context)
|
||||
.newCall(request)
|
||||
.enqueue(HttpCallback.buildOkHttpCallback(callback));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.sismics.docs.resource;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.sismics.docs.listener.HttpCallback;
|
||||
import com.sismics.docs.util.OkHttpUtil;
|
||||
|
||||
import okhttp3.FormBody;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.Request;
|
||||
|
||||
/**
|
||||
* Access to /user API.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class UserResource extends BaseResource {
|
||||
|
||||
/**
|
||||
* POST /user/login.
|
||||
*
|
||||
* @param context Context
|
||||
* @param username Username
|
||||
* @param password Password
|
||||
* @param callback Callback
|
||||
*/
|
||||
public static void login(Context context, String username, String password, String code, HttpCallback callback) {
|
||||
Request request = new Request.Builder()
|
||||
.url(HttpUrl.parse(getApiUrl(context) + "/user/login"))
|
||||
.post(new FormBody.Builder()
|
||||
.add("username", username)
|
||||
.add("password", password)
|
||||
.add("code", code)
|
||||
.add("remember", "true")
|
||||
.build())
|
||||
.build();
|
||||
OkHttpUtil.buildClient(context)
|
||||
.newCall(request)
|
||||
.enqueue(HttpCallback.buildOkHttpCallback(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /user.
|
||||
*
|
||||
* @param context Context
|
||||
* @param callback Callback
|
||||
*/
|
||||
public static void info(Context context, HttpCallback callback) {
|
||||
Request request = new Request.Builder()
|
||||
.url(HttpUrl.parse(getApiUrl(context) + "/user"))
|
||||
.get()
|
||||
.build();
|
||||
OkHttpUtil.buildClient(context)
|
||||
.newCall(request)
|
||||
.enqueue(HttpCallback.buildOkHttpCallback(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /user/username.
|
||||
*
|
||||
* @param context Context
|
||||
* param username Username
|
||||
* @param callback Callback
|
||||
*/
|
||||
public static void get(Context context, String username, HttpCallback callback) {
|
||||
Request request = new Request.Builder()
|
||||
.url(HttpUrl.parse(getApiUrl(context) + "/user/" + username))
|
||||
.get()
|
||||
.build();
|
||||
OkHttpUtil.buildClient(context)
|
||||
.newCall(request)
|
||||
.enqueue(HttpCallback.buildOkHttpCallback(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /user/logout.
|
||||
*
|
||||
* @param context Context
|
||||
* @param callback Callback
|
||||
*/
|
||||
public static void logout(Context context, HttpCallback callback) {
|
||||
Request request = new Request.Builder()
|
||||
.url(HttpUrl.parse(getApiUrl(context) + "/user/logout"))
|
||||
.post(new FormBody.Builder().build())
|
||||
.build();
|
||||
OkHttpUtil.buildClient(context)
|
||||
.newCall(request)
|
||||
.enqueue(HttpCallback.buildOkHttpCallback(callback));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
package com.sismics.docs.resource.cookie;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.net.CookieStore;
|
||||
import java.net.HttpCookie;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* A persistent cookie store which implements the Apache HttpClient CookieStore interface.
|
||||
* Cookies are stored and will persist on the user's device between application sessions since they
|
||||
* are serialized and stored in SharedPreferences.
|
||||
*/
|
||||
public class PersistentCookieStore implements CookieStore {
|
||||
|
||||
private static final String LOG_TAG = "PersistentCookieStore";
|
||||
private static final String COOKIE_PREFS = "CookiePrefsFileOkHttp";
|
||||
private static final String COOKIE_NAME_PREFIX = "cookie_okhttp_";
|
||||
|
||||
private final HashMap<String, ConcurrentHashMap<String, HttpCookie>> cookies;
|
||||
private final SharedPreferences cookiePrefs;
|
||||
|
||||
/**
|
||||
* Construct a persistent cookie store.
|
||||
*
|
||||
* @param context Context to attach cookie store to
|
||||
*/
|
||||
public PersistentCookieStore(Context context) {
|
||||
cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);
|
||||
cookies = new HashMap<>();
|
||||
|
||||
// Load any previously stored cookies into the store
|
||||
Map<String, ?> prefsMap = cookiePrefs.getAll();
|
||||
for (Map.Entry<String, ?> entry : prefsMap.entrySet()) {
|
||||
if (entry.getValue() != null && !((String) entry.getValue()).startsWith(COOKIE_NAME_PREFIX)) {
|
||||
String[] cookieNames = TextUtils.split((String) entry.getValue(), ",");
|
||||
for (String name : cookieNames) {
|
||||
String encodedCookie = cookiePrefs.getString(COOKIE_NAME_PREFIX + name, null);
|
||||
if (encodedCookie != null) {
|
||||
HttpCookie decodedCookie = decodeCookie(encodedCookie);
|
||||
if (decodedCookie != null) {
|
||||
if (!cookies.containsKey(entry.getKey()))
|
||||
cookies.put(entry.getKey(), new ConcurrentHashMap<String, HttpCookie>());
|
||||
cookies.get(entry.getKey()).put(name, decodedCookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(URI uri, HttpCookie cookie) {
|
||||
String name = getCookieToken(uri, cookie);
|
||||
|
||||
// Save cookie into local store, or remove if expired
|
||||
if (!cookie.hasExpired()) {
|
||||
if (!cookies.containsKey(uri.getHost()))
|
||||
cookies.put(uri.getHost(), new ConcurrentHashMap<String, HttpCookie>());
|
||||
cookies.get(uri.getHost()).put(name, cookie);
|
||||
} else {
|
||||
if (cookies.containsKey(uri.toString()))
|
||||
cookies.get(uri.getHost()).remove(name);
|
||||
}
|
||||
|
||||
// Save cookie into persistent store
|
||||
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
|
||||
prefsWriter.putString(uri.getHost(), TextUtils.join(",", cookies.get(uri.getHost()).keySet()));
|
||||
prefsWriter.putString(COOKIE_NAME_PREFIX + name, encodeCookie(new SerializableHttpCookie(cookie)));
|
||||
prefsWriter.apply();
|
||||
}
|
||||
|
||||
protected String getCookieToken(URI uri, HttpCookie cookie) {
|
||||
return cookie.getName() + cookie.getDomain();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HttpCookie> get(URI uri) {
|
||||
ArrayList<HttpCookie> ret = new ArrayList<>();
|
||||
if (cookies.containsKey(uri.getHost()))
|
||||
ret.addAll(cookies.get(uri.getHost()).values());
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll() {
|
||||
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
|
||||
prefsWriter.clear();
|
||||
prefsWriter.apply();
|
||||
cookies.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean remove(URI uri, HttpCookie cookie) {
|
||||
String name = getCookieToken(uri, cookie);
|
||||
|
||||
if (cookies.containsKey(uri.getHost()) && cookies.get(uri.getHost()).containsKey(name)) {
|
||||
cookies.get(uri.getHost()).remove(name);
|
||||
|
||||
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
|
||||
if (cookiePrefs.contains(COOKIE_NAME_PREFIX + name)) {
|
||||
prefsWriter.remove(COOKIE_NAME_PREFIX + name);
|
||||
}
|
||||
prefsWriter.putString(uri.getHost(), TextUtils.join(",", cookies.get(uri.getHost()).keySet()));
|
||||
prefsWriter.apply();
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HttpCookie> getCookies() {
|
||||
ArrayList<HttpCookie> ret = new ArrayList<>();
|
||||
for (String key : cookies.keySet())
|
||||
ret.addAll(cookies.get(key).values());
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<URI> getURIs() {
|
||||
ArrayList<URI> ret = new ArrayList<>();
|
||||
for (String key : cookies.keySet())
|
||||
try {
|
||||
ret.add(new URI(key));
|
||||
} catch (URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes Cookie object into String
|
||||
*
|
||||
* @param cookie cookie to be encoded, can be null
|
||||
* @return cookie encoded as String
|
||||
*/
|
||||
protected String encodeCookie(SerializableHttpCookie cookie) {
|
||||
if (cookie == null)
|
||||
return null;
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
try {
|
||||
ObjectOutputStream outputStream = new ObjectOutputStream(os);
|
||||
outputStream.writeObject(cookie);
|
||||
} catch (IOException e) {
|
||||
Log.d(LOG_TAG, "IOException in encodeCookie", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return byteArrayToHexString(os.toByteArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns cookie decoded from cookie string
|
||||
*
|
||||
* @param cookieString string of cookie as returned from http request
|
||||
* @return decoded cookie or null if exception occured
|
||||
*/
|
||||
protected HttpCookie decodeCookie(String cookieString) {
|
||||
byte[] bytes = hexStringToByteArray(cookieString);
|
||||
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
|
||||
HttpCookie cookie = null;
|
||||
try {
|
||||
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
|
||||
cookie = ((SerializableHttpCookie) objectInputStream.readObject()).getCookie();
|
||||
} catch (IOException e) {
|
||||
Log.d(LOG_TAG, "IOException in decodeCookie", e);
|
||||
} catch (ClassNotFoundException e) {
|
||||
Log.d(LOG_TAG, "ClassNotFoundException in decodeCookie", e);
|
||||
}
|
||||
|
||||
return cookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using some super basic byte array <-> hex conversions so we don't have to rely on any
|
||||
* large Base64 libraries. Can be overridden if you like!
|
||||
*
|
||||
* @param bytes byte array to be converted
|
||||
* @return string containing hex values
|
||||
*/
|
||||
protected String byteArrayToHexString(byte[] bytes) {
|
||||
StringBuilder sb = new StringBuilder(bytes.length * 2);
|
||||
for (byte element : bytes) {
|
||||
int v = element & 0xff;
|
||||
if (v < 16) {
|
||||
sb.append('0');
|
||||
}
|
||||
sb.append(Integer.toHexString(v));
|
||||
}
|
||||
return sb.toString().toUpperCase(Locale.US);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts hex values from strings to byte arra
|
||||
*
|
||||
* @param hexString string of hex-encoded values
|
||||
* @return decoded byte array
|
||||
*/
|
||||
protected byte[] hexStringToByteArray(String hexString) {
|
||||
int len = hexString.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.sismics.docs.resource.cookie;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.net.HttpCookie;
|
||||
|
||||
public class SerializableHttpCookie implements Serializable {
|
||||
private static final long serialVersionUID = 6374381323722046732L;
|
||||
|
||||
private transient final HttpCookie cookie;
|
||||
private transient HttpCookie clientCookie;
|
||||
|
||||
public SerializableHttpCookie(HttpCookie cookie) {
|
||||
this.cookie = cookie;
|
||||
}
|
||||
|
||||
public HttpCookie getCookie() {
|
||||
HttpCookie bestCookie = cookie;
|
||||
if (clientCookie != null) {
|
||||
bestCookie = clientCookie;
|
||||
}
|
||||
return bestCookie;
|
||||
}
|
||||
|
||||
private void writeObject(ObjectOutputStream out) throws IOException {
|
||||
out.writeObject(cookie.getName());
|
||||
out.writeObject(cookie.getValue());
|
||||
out.writeObject(cookie.getComment());
|
||||
out.writeObject(cookie.getCommentURL());
|
||||
out.writeObject(cookie.getDomain());
|
||||
out.writeLong(cookie.getMaxAge());
|
||||
out.writeObject(cookie.getPath());
|
||||
out.writeObject(cookie.getPortlist());
|
||||
out.writeInt(cookie.getVersion());
|
||||
out.writeBoolean(cookie.getSecure());
|
||||
out.writeBoolean(cookie.getDiscard());
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||
String name = (String) in.readObject();
|
||||
String value = (String) in.readObject();
|
||||
clientCookie = new HttpCookie(name, value);
|
||||
clientCookie.setComment((String) in.readObject());
|
||||
clientCookie.setCommentURL((String) in.readObject());
|
||||
clientCookie.setDomain((String) in.readObject());
|
||||
clientCookie.setMaxAge(in.readLong());
|
||||
clientCookie.setPath((String) in.readObject());
|
||||
clientCookie.setPortlist((String) in.readObject());
|
||||
clientCookie.setVersion(in.readInt());
|
||||
clientCookie.setSecure(in.readBoolean());
|
||||
clientCookie.setDiscard(in.readBoolean());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
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.HttpCallback;
|
||||
import com.sismics.docs.resource.FileResource;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import okhttp3.internal.Util;
|
||||
|
||||
/**
|
||||
* 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 HttpCallback() {
|
||||
@Override
|
||||
public void onSuccess(JSONObject response) {
|
||||
EventBus.getDefault().post(new FileAddEvent(documentId, response.optString("id")));
|
||||
FileUploadService.this.onComplete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(JSONObject json, Exception e) {
|
||||
FileUploadService.this.onError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
Util.closeQuietly(is);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,42 @@
|
||||
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,39 @@
|
||||
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,33 @@
|
||||
package com.sismics.docs.util;
|
||||
|
||||
import android.app.DownloadManager;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
|
||||
/**
|
||||
* Utility class for network actions.
|
||||
*
|
||||
* @author bgamard.
|
||||
*/
|
||||
public class NetworkUtil {
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static void downloadFile(Context context, String url, String fileName, String title, String description) {
|
||||
String authToken = PreferenceUtil.getAuthToken(context);
|
||||
DownloadManager downloadManager = (DownloadManager) context.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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package com.sismics.docs.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import com.jakewharton.picasso.OkHttp3Downloader;
|
||||
import com.sismics.docs.resource.cookie.PersistentCookieStore;
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.CookieManager;
|
||||
import java.net.CookiePolicy;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import okhttp3.Cache;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.JavaNetCookieJar;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
/**
|
||||
* Utilities for OkHttp.
|
||||
*
|
||||
* @author bgamard.
|
||||
*/
|
||||
public class OkHttpUtil {
|
||||
/**
|
||||
* OkHttp singleton client.
|
||||
*/
|
||||
private static OkHttpClient okHttpClient = new OkHttpClient();
|
||||
|
||||
/**
|
||||
* Singleton cache.
|
||||
*/
|
||||
private static Cache cache = null;
|
||||
|
||||
/**
|
||||
* User-Agent to use.
|
||||
*/
|
||||
protected static String userAgent = null;
|
||||
|
||||
/**
|
||||
* Accept-Language header.
|
||||
*/
|
||||
protected static String acceptLanguage = null;
|
||||
|
||||
static {
|
||||
// OkHttp configuration
|
||||
try {
|
||||
// Create a trust manager that does not validate certificate chains
|
||||
final TrustManager[] trustAllCerts = new TrustManager[] {
|
||||
new X509TrustManager() {
|
||||
@Override
|
||||
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Install the all-trusting trust manager
|
||||
final SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
|
||||
final javax.net.ssl.SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
|
||||
|
||||
// Configure OkHttpClient
|
||||
okHttpClient = okHttpClient.newBuilder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS)
|
||||
.sslSocketFactory(sslSocketFactory)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
// NOP
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a Picasso object with base config.
|
||||
*
|
||||
* @param context Context
|
||||
* @return Picasso object
|
||||
*/
|
||||
public static Picasso picasso(Context context) {
|
||||
OkHttpClient okHttpClient = buildClient(context)
|
||||
.newBuilder()
|
||||
.addInterceptor(new Interceptor() {
|
||||
@Override
|
||||
public Response intercept(Interceptor.Chain chain) throws IOException { // Override cache configuration
|
||||
final Request original = chain.request();
|
||||
return chain.proceed(original.newBuilder()
|
||||
.header("Cache-Control", "max-age=" + (3600 * 24 * 365))
|
||||
.method(original.method(), original.body())
|
||||
.build());
|
||||
}
|
||||
})
|
||||
.cache(getCache(context))
|
||||
.build();
|
||||
|
||||
Picasso picasso = new Picasso.Builder(context)
|
||||
.downloader(new OkHttp3Downloader(okHttpClient))
|
||||
.build();
|
||||
picasso.setIndicatorsEnabled(false); // Debug stuff
|
||||
return picasso;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and eventually build the singleton cache.
|
||||
*
|
||||
* @param context Context
|
||||
* @return Cache
|
||||
*/
|
||||
private static Cache getCache(Context context) {
|
||||
if (cache == null) {
|
||||
cache = new Cache(context.getCacheDir(),
|
||||
PreferenceUtil.getIntegerPreference(context, PreferenceUtil.PREF_CACHE_SIZE, 0) * 1000000);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the HTTP cache.
|
||||
*
|
||||
* @param context Context
|
||||
*/
|
||||
public static void clearCache(Context context) {
|
||||
Cache cache = getCache(context);
|
||||
try {
|
||||
cache.evictAll();
|
||||
} catch (IOException e) {
|
||||
Log.e("OKHttpUtil", "Error clearing cache", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an OkHttpClient.
|
||||
*
|
||||
* @param context Context
|
||||
* @return OkHttpClient
|
||||
*/
|
||||
public static OkHttpClient buildClient(final Context context) {
|
||||
// One-time header computation
|
||||
if (userAgent == null) {
|
||||
userAgent = "Sismics Docs Android " + ApplicationUtil.getVersionName(context) + "/Android " + Build.VERSION.RELEASE + "/" + Build.MODEL;
|
||||
}
|
||||
|
||||
if (acceptLanguage == null) {
|
||||
Locale locale = Locale.getDefault();
|
||||
acceptLanguage = locale.getLanguage() + "_" + locale.getCountry();
|
||||
}
|
||||
|
||||
// Cookie handling
|
||||
PersistentCookieStore cookieStore = new PersistentCookieStore(context);
|
||||
CookieManager cookieManager = new CookieManager(cookieStore, CookiePolicy.ACCEPT_ALL);
|
||||
|
||||
// Runtime configuration
|
||||
return okHttpClient.newBuilder()
|
||||
.cookieJar(new JavaNetCookieJar(cookieManager))
|
||||
.addNetworkInterceptor(new Interceptor() {
|
||||
@Override
|
||||
public Response intercept(Chain chain) throws IOException {
|
||||
Request original = chain.request();
|
||||
return chain.proceed(original.newBuilder()
|
||||
.header("User-Agent", userAgent)
|
||||
.header("Accept-Language", acceptLanguage)
|
||||
.method(original.method(), original.body())
|
||||
.build());
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package com.sismics.docs.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import com.sismics.docs.resource.cookie.PersistentCookieStore;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.net.HttpCookie;
|
||||
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<HttpCookie> cookieList = cookieStore.getCookies();
|
||||
for (HttpCookie 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,166 @@
|
||||
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 creator criteria.
|
||||
*
|
||||
* @param creator Creator criteria
|
||||
* @return The builder
|
||||
*/
|
||||
public SearchQueryBuilder creator(String creator) {
|
||||
if (isValid(creator)) {
|
||||
query.append(SEARCH_SEPARATOR)
|
||||
.append("by:")
|
||||
.append(creator);
|
||||
}
|
||||
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: 1.2 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 379 B |
|
After Width: | Height: | Size: 271 B |
|
After Width: | Height: | Size: 353 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 |