Compare commits
No commits in common. "master" and "v1.0" have entirely different histories.
3
.github/FUNDING.yml
vendored
@ -1,3 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [jendib]
|
84
.github/workflows/build-deploy.yml
vendored
@ -1,84 +0,0 @@
|
||||
name: Maven CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
tags: [v*]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build_and_publish:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: "11"
|
||||
distribution: "temurin"
|
||||
cache: maven
|
||||
- name: Install test dependencies
|
||||
run: sudo apt-get update && sudo apt-get -y -q --no-install-recommends install ffmpeg mediainfo tesseract-ocr tesseract-ocr-deu
|
||||
- name: Build with Maven
|
||||
run: mvn --batch-mode -Pprod clean install
|
||||
- name: Upload war artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: docs-web-ci.war
|
||||
path: docs-web/target/docs*.war
|
||||
|
||||
build_docker_image:
|
||||
name: Publish to Docker Hub
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_and_publish]
|
||||
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Download war artifact
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: docs-web-ci.war
|
||||
path: docs-web/target
|
||||
-
|
||||
name: Setup up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
-
|
||||
name: Populate Docker metadata
|
||||
id: metadata
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: sismics/docs
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=ref,event=tag
|
||||
type=raw,value=latest,enable=${{ github.ref_type != 'tag' }}
|
||||
labels: |
|
||||
org.opencontainers.image.title = Teedy
|
||||
org.opencontainers.image.description = Teedy is an open source, lightweight document management system for individuals and businesses.
|
||||
org.opencontainers.image.created = ${{ github.event_created_at }}
|
||||
org.opencontainers.image.author = Sismics
|
||||
org.opencontainers.image.url = https://teedy.io/
|
||||
org.opencontainers.image.vendor = Sismics
|
||||
org.opencontainers.image.license = GPLv2
|
||||
org.opencontainers.image.version = ${{ github.event_head_commit.id }}
|
||||
-
|
||||
name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.metadata.outputs.tags }}
|
||||
labels: ${{ steps.metadata.outputs.labels }}
|
14
.gitignore
vendored
@ -4,18 +4,6 @@
|
||||
/*/bin
|
||||
/*/gen
|
||||
/*/target
|
||||
/*/build
|
||||
/*/*.iml
|
||||
/out
|
||||
/.idea
|
||||
/.project
|
||||
*.iml
|
||||
node_modules
|
||||
import_test
|
||||
teedy-importer-linux
|
||||
teedy-importer-macos
|
||||
teedy-importer-win.exe
|
||||
docs/*
|
||||
!docs/.gitkeep
|
||||
|
||||
#macos
|
||||
.DS_Store
|
||||
|
@ -1,46 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@sismicsdocs.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
75
Dockerfile
@ -1,75 +0,0 @@
|
||||
FROM ubuntu:22.04
|
||||
LABEL maintainer="b.gamard@sismics.com"
|
||||
|
||||
# Run Debian in non interactive mode
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
# Configure env
|
||||
ENV LANG C.UTF-8
|
||||
ENV LC_ALL C.UTF-8
|
||||
ENV JAVA_HOME /usr/lib/jvm/java-11-openjdk-amd64/
|
||||
ENV JAVA_OPTIONS -Dfile.encoding=UTF-8 -Xmx1g
|
||||
ENV JETTY_VERSION 11.0.20
|
||||
ENV JETTY_HOME /opt/jetty
|
||||
|
||||
# Install packages
|
||||
RUN apt-get update && \
|
||||
apt-get -y -q --no-install-recommends install \
|
||||
vim less procps unzip wget tzdata openjdk-11-jdk \
|
||||
ffmpeg \
|
||||
mediainfo \
|
||||
tesseract-ocr \
|
||||
tesseract-ocr-ara \
|
||||
tesseract-ocr-ces \
|
||||
tesseract-ocr-chi-sim \
|
||||
tesseract-ocr-chi-tra \
|
||||
tesseract-ocr-dan \
|
||||
tesseract-ocr-deu \
|
||||
tesseract-ocr-fin \
|
||||
tesseract-ocr-fra \
|
||||
tesseract-ocr-heb \
|
||||
tesseract-ocr-hin \
|
||||
tesseract-ocr-hun \
|
||||
tesseract-ocr-ita \
|
||||
tesseract-ocr-jpn \
|
||||
tesseract-ocr-kor \
|
||||
tesseract-ocr-lav \
|
||||
tesseract-ocr-nld \
|
||||
tesseract-ocr-nor \
|
||||
tesseract-ocr-pol \
|
||||
tesseract-ocr-por \
|
||||
tesseract-ocr-rus \
|
||||
tesseract-ocr-spa \
|
||||
tesseract-ocr-swe \
|
||||
tesseract-ocr-tha \
|
||||
tesseract-ocr-tur \
|
||||
tesseract-ocr-ukr \
|
||||
tesseract-ocr-vie \
|
||||
tesseract-ocr-sqi \
|
||||
&& apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
RUN dpkg-reconfigure -f noninteractive tzdata
|
||||
|
||||
# Install Jetty
|
||||
RUN wget -nv -O /tmp/jetty.tar.gz \
|
||||
"https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-home/${JETTY_VERSION}/jetty-home-${JETTY_VERSION}.tar.gz" \
|
||||
&& tar xzf /tmp/jetty.tar.gz -C /opt \
|
||||
&& mv /opt/jetty* /opt/jetty \
|
||||
&& useradd jetty -U -s /bin/false \
|
||||
&& chown -R jetty:jetty /opt/jetty \
|
||||
&& mkdir /opt/jetty/webapps \
|
||||
&& chmod +x /opt/jetty/bin/jetty.sh
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
# Install app
|
||||
RUN mkdir /app && \
|
||||
cd /app && \
|
||||
java -jar /opt/jetty/start.jar --add-modules=server,http,webapp,deploy
|
||||
|
||||
ADD docs.xml /app/webapps/docs.xml
|
||||
ADD docs-web/target/docs-web-*.war /app/webapps/docs.war
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
CMD ["java", "-jar", "/opt/jetty/start.jar"]
|
241
README.md
@ -1,206 +1,42 @@
|
||||
<h3 align="center">
|
||||
<img src="https://teedy.io/img/github-title.png" alt="Teedy" width=500 />
|
||||
</h3>
|
||||
Sismics Docs
|
||||
============
|
||||
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](https://github.com/sismics/docs/actions/workflows/build-deploy.yml)
|
||||

|
||||
|
||||
Teedy is an open source, lightweight document management system for individuals and businesses.
|
||||
What is Docs?
|
||||
---------------
|
||||
|
||||
<hr />
|
||||
<h2 align="center">
|
||||
✨ <a href="https://github.com/users/jendib/sponsorship">Sponsor this project if you use and appreciate it!</a> ✨
|
||||
</h2>
|
||||
<hr />
|
||||
Docs is an open source, lightweight document management system.
|
||||
|
||||

|
||||
Docs is written in Java, and may be run on any operating system with Java support.
|
||||
|
||||
# Demo
|
||||
|
||||
A demo is available at [demo.teedy.io](https://demo.teedy.io)
|
||||
|
||||
- Guest login is enabled with read access on all documents
|
||||
- "admin" login with "admin" password
|
||||
- "demo" login with "password" password
|
||||
|
||||
# Features
|
||||
Features
|
||||
--------
|
||||
|
||||
- Responsive user interface
|
||||
- Optical character recognition
|
||||
- LDAP authentication 
|
||||
- Support image, PDF, ODT, DOCX, PPTX files
|
||||
- Video file support
|
||||
- Flexible search engine with suggestions and highlighting
|
||||
- Full text search in all supported files
|
||||
- All [Dublin Core](http://dublincore.org/) metadata
|
||||
- Custom user-defined metadata 
|
||||
- Workflow system 
|
||||
- 256-bit AES encryption of stored files
|
||||
- File versioning 
|
||||
- Tag system with nesting
|
||||
- Import document from email (EML format)
|
||||
- Automatic inbox scanning and importing
|
||||
- User/group permission system
|
||||
- 2-factor authentication
|
||||
- Hierarchical groups
|
||||
- Audit log
|
||||
- Comments
|
||||
- Storage quota per user
|
||||
- Document sharing by URL
|
||||
- Optical characted recognition
|
||||
- Support image and PDF files
|
||||
- Flexible search engine
|
||||
- Full text search in image and PDF
|
||||
- Tag system
|
||||
- Multi-users
|
||||
- Document sharing
|
||||
- RESTful Web API
|
||||
- Webhooks to trigger external service
|
||||
- Fully featured Android client
|
||||
- [Bulk files importer](https://github.com/sismics/docs/tree/master/docs-importer) (single or scan mode)
|
||||
- Tested to one million documents
|
||||
|
||||
# Install with Docker
|
||||
License
|
||||
-------
|
||||
|
||||
A preconfigured Docker image is available, including OCR and media conversion tools, listening on port 8080. If no PostgreSQL config is provided, the database is an embedded H2 database. The H2 embedded database should only be used for testing. For production usage use the provided PostgreSQL configuration (check the Docker Compose example)
|
||||
Reader is released under the terms of the GPL license. See `COPYING` for more
|
||||
information or see <http://opensource.org/licenses/GPL-2.0>.
|
||||
|
||||
**The default admin password is "admin". Don't forget to change it before going to production.**
|
||||
How to build Docs from the sources
|
||||
------------------------------------
|
||||
|
||||
- Master branch, can be unstable. Not recommended for production use: `sismics/docs:latest`
|
||||
- Latest stable version: `sismics/docs:v1.11`
|
||||
Prerequisites: JDK 7, Maven 3
|
||||
|
||||
The data directory is `/data`. Don't forget to mount a volume on it.
|
||||
|
||||
To build external URL, the server is expecting a `DOCS_BASE_URL` environment variable (for example https://teedy.mycompany.com)
|
||||
|
||||
## Available environment variables
|
||||
|
||||
- General
|
||||
- `DOCS_BASE_URL`: The base url used by the application. Generated url's will be using this as base.
|
||||
- `DOCS_GLOBAL_QUOTA`: Defines the default quota applying to all users.
|
||||
- `DOCS_BCRYPT_WORK`: Defines the work factor which is used for password hashing. The default is `10`. This value may be `4...31` including `4` and `31`. The specified value will be used for all new users and users changing their password. Be aware that setting this factor to high can heavily impact login and user creation performance.
|
||||
|
||||
- Admin
|
||||
- `DOCS_ADMIN_EMAIL_INIT`: Defines the e-mail-address the admin user should have upon initialization.
|
||||
- `DOCS_ADMIN_PASSWORD_INIT`: Defines the password the admin user should have upon initialization. Needs to be a bcrypt hash. **Be aware that `$` within the hash have to be escaped with a second `$`.**
|
||||
|
||||
- Database
|
||||
- `DATABASE_URL`: The jdbc connection string to be used by `hibernate`.
|
||||
- `DATABASE_USER`: The user which should be used for the database connection.
|
||||
- `DATABASE_PASSWORD`: The password to be used for the database connection.
|
||||
- `DATABASE_POOL_SIZE`: The pool size to be used for the database connection.
|
||||
|
||||
- Language
|
||||
- `DOCS_DEFAULT_LANGUAGE`: The language which will be used as default. Currently supported values are:
|
||||
- `eng`, `fra`, `ita`, `deu`, `spa`, `por`, `pol`, `rus`, `ukr`, `ara`, `hin`, `chi_sim`, `chi_tra`, `jpn`, `tha`, `kor`, `nld`, `tur`, `heb`, `hun`, `fin`, `swe`, `lav`, `dan`
|
||||
|
||||
- E-Mail
|
||||
- `DOCS_SMTP_HOSTNAME`: Hostname of the SMTP-Server to be used by Teedy.
|
||||
- `DOCS_SMTP_PORT`: The port which should be used.
|
||||
- `DOCS_SMTP_USERNAME`: The username to be used.
|
||||
- `DOCS_SMTP_PASSWORD`: The password to be used.
|
||||
|
||||
## Examples
|
||||
|
||||
In the following examples some passwords are exposed in cleartext. This was done in order to keep the examples simple. We strongly encourage you to use variables with an `.env` file or other means to securely store your passwords.
|
||||
|
||||
|
||||
### Default, using PostgreSQL
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
services:
|
||||
# Teedy Application
|
||||
teedy-server:
|
||||
image: sismics/docs:v1.11
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
# Map internal port to host
|
||||
- 8080:8080
|
||||
environment:
|
||||
# Base url to be used
|
||||
DOCS_BASE_URL: "https://docs.example.com"
|
||||
# Set the admin email
|
||||
DOCS_ADMIN_EMAIL_INIT: "admin@example.com"
|
||||
# Set the admin password (in this example: "superSecure")
|
||||
DOCS_ADMIN_PASSWORD_INIT: "$$2a$$05$$PcMNUbJvsk7QHFSfEIDaIOjk1VI9/E7IPjTKx.jkjPxkx2EOKSoPS"
|
||||
# Setup the database connection. "teedy-db" is the hostname
|
||||
# and "teedy" is the name of the database the application
|
||||
# will connect to.
|
||||
DATABASE_URL: "jdbc:postgresql://teedy-db:5432/teedy"
|
||||
DATABASE_USER: "teedy_db_user"
|
||||
DATABASE_PASSWORD: "teedy_db_password"
|
||||
DATABASE_POOL_SIZE: "10"
|
||||
volumes:
|
||||
- ./docs/data:/data
|
||||
networks:
|
||||
- docker-internal
|
||||
- internet
|
||||
depends_on:
|
||||
- teedy-db
|
||||
|
||||
# DB for Teedy
|
||||
teedy-db:
|
||||
image: postgres:13.1-alpine
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- 5432
|
||||
environment:
|
||||
POSTGRES_USER: "teedy_db_user"
|
||||
POSTGRES_PASSWORD: "teedy_db_password"
|
||||
POSTGRES_DB: "teedy"
|
||||
volumes:
|
||||
- ./docs/db:/var/lib/postgresql/data
|
||||
networks:
|
||||
- docker-internal
|
||||
|
||||
networks:
|
||||
# Network without internet access. The db does not need
|
||||
# access to the host network.
|
||||
docker-internal:
|
||||
driver: bridge
|
||||
internal: true
|
||||
internet:
|
||||
driver: bridge
|
||||
```
|
||||
|
||||
### Using the internal database (only for testing)
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
services:
|
||||
# Teedy Application
|
||||
teedy-server:
|
||||
image: sismics/docs:v1.11
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
# Map internal port to host
|
||||
- 8080:8080
|
||||
environment:
|
||||
# Base url to be used
|
||||
DOCS_BASE_URL: "https://docs.example.com"
|
||||
# Set the admin email
|
||||
DOCS_ADMIN_EMAIL_INIT: "admin@example.com"
|
||||
# Set the admin password (in this example: "superSecure")
|
||||
DOCS_ADMIN_PASSWORD_INIT: "$$2a$$05$$PcMNUbJvsk7QHFSfEIDaIOjk1VI9/E7IPjTKx.jkjPxkx2EOKSoPS"
|
||||
volumes:
|
||||
- ./docs/data:/data
|
||||
```
|
||||
|
||||
# Manual installation
|
||||
|
||||
## Requirements
|
||||
|
||||
- Java 11
|
||||
- Tesseract 4 for OCR
|
||||
- ffmpeg for video thumbnails
|
||||
- mediainfo for video metadata extraction
|
||||
- A webapp server like [Jetty](http://eclipse.org/jetty/) or [Tomcat](http://tomcat.apache.org/)
|
||||
|
||||
## Download
|
||||
|
||||
The latest release is downloadable here: <https://github.com/sismics/docs/releases> in WAR format.
|
||||
**The default admin password is "admin". Don't forget to change it before going to production.**
|
||||
|
||||
## How to build Teedy from the sources
|
||||
|
||||
Prerequisites: JDK 11, Maven 3, NPM, Grunt, Tesseract 4
|
||||
|
||||
Teedy is organized in several Maven modules:
|
||||
Docs is organized in several Maven modules:
|
||||
|
||||
- docs-parent
|
||||
- docs-core
|
||||
- docs-web
|
||||
- docs-web-common
|
||||
@ -208,39 +44,22 @@ Teedy is organized in several Maven modules:
|
||||
First off, clone the repository: `git clone git://github.com/sismics/docs.git`
|
||||
or download the sources from GitHub.
|
||||
|
||||
### Launch the build
|
||||
#### Launch the build
|
||||
|
||||
From the root directory:
|
||||
From the `docs-parent` directory:
|
||||
|
||||
```console
|
||||
mvn clean -DskipTests install
|
||||
```
|
||||
|
||||
### Run a stand-alone version
|
||||
#### Run a stand-alone version
|
||||
|
||||
From the `docs-web` directory:
|
||||
|
||||
```console
|
||||
mvn jetty:run
|
||||
```
|
||||
|
||||
### Build a .war to deploy to your servlet container
|
||||
#### Build a .war to deploy to your servlet container
|
||||
|
||||
From the `docs-web` directory:
|
||||
|
||||
```console
|
||||
mvn -Pprod -DskipTests clean install
|
||||
```
|
||||
|
||||
You will get your deployable WAR in the `docs-web/target` directory.
|
||||
|
||||
# Contributing
|
||||
|
||||
All contributions are more than welcomed. Contributions may close an issue, fix a bug (reported or not reported), improve the existing code, add new feature, and so on.
|
||||
|
||||
The `master` branch is the default and base branch for the project. It is used for development and all Pull Requests should go there.
|
||||
|
||||
# License
|
||||
|
||||
Teedy is released under the terms of the GPL license. See `COPYING` for more
|
||||
information or see <http://opensource.org/licenses/GPL-2.0>.
|
||||
You will get your deployable WAR in the `target` directory.
|
||||
|
@ -1,18 +0,0 @@
|
||||
version: '3'
|
||||
services:
|
||||
# Teedy Application
|
||||
teedy-server:
|
||||
image: sismics/docs:v1.10
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
# Map internal port to host
|
||||
- 8080:8080
|
||||
environment:
|
||||
# Base url to be used
|
||||
DOCS_BASE_URL: "https://docs.example.com"
|
||||
# Set the admin email
|
||||
DOCS_ADMIN_EMAIL_INIT: "admin@example.com"
|
||||
# Set the admin password (in this example: "superSecure")
|
||||
DOCS_ADMIN_PASSWORD_INIT: "$$2a$$05$$PcMNUbJvsk7QHFSfEIDaIOjk1VI9/E7IPjTKx.jkjPxkx2EOKSoPS"
|
||||
volumes:
|
||||
- ./docs/data:/data
|
4
docs-android/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea
|
||||
.DS_Store
|
1
docs-android/app/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/build
|
@ -1,43 +0,0 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.4.0'
|
||||
}
|
||||
}
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
versionName '1.0'
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: '*.jar')
|
||||
implementation 'com.android.support:appcompat-v7:28.0.0'
|
||||
implementation 'com.android.support:recyclerview-v7:28.0.0'
|
||||
implementation 'com.android.support:design:28.0.0'
|
||||
implementation 'it.sephiroth.android.library.imagezoom:imagezoom:1.0.5'
|
||||
implementation 'org.greenrobot:eventbus:3.1.1'
|
||||
implementation 'com.squareup.picasso:picasso:2.5.2'
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.10.0'
|
||||
implementation 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /opt/android-studio/sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the ProGuard
|
||||
# include property in project.properties.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
@ -1,83 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
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" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
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=".activity.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
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="">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
</intent-filter>
|
||||
</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,29 +0,0 @@
|
||||
package com.sismics.docs;
|
||||
|
||||
import android.app.Application;
|
||||
import android.support.v7.app.AppCompatDelegate;
|
||||
|
||||
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();
|
||||
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
|
||||
}
|
||||
}
|
@ -1,121 +0,0 @@
|
||||
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 = 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 = 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 = findViewById(R.id.swipeRefreshLayout);
|
||||
final ProgressBar progressBar = findViewById(R.id.progressBar);
|
||||
final ListView auditLogListView = 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(AuditLogActivity.this, 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);
|
||||
}
|
||||
}
|
@ -1,231 +0,0 @@
|
||||
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("tags");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,853 +0,0 @@
|
||||
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.text.method.LinkMovementMethod;
|
||||
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.SpannableUtil;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
// Create date
|
||||
TextView createdDateTextView = (TextView) findViewById(R.id.createdDateTextView);
|
||||
createdDateTextView.setText(date);
|
||||
|
||||
// Description
|
||||
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);
|
||||
}
|
||||
|
||||
// Tags
|
||||
TextView tagTextView = (TextView) findViewById(R.id.tagTextView);
|
||||
if (tags.length() == 0) {
|
||||
tagTextView.setVisibility(View.GONE);
|
||||
} else {
|
||||
tagTextView.setVisibility(View.VISIBLE);
|
||||
tagTextView.setText(SpannableUtil.buildSpannableTags(tags));
|
||||
}
|
||||
|
||||
// Language
|
||||
ImageView languageImageView = (ImageView) findViewById(R.id.languageImageView);
|
||||
languageImageView.setImageResource(getResources().getIdentifier(language, "drawable", getPackageName()));
|
||||
|
||||
// Shared status
|
||||
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());
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
// 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.GONE);
|
||||
findViewById(R.id.actionUploadFile).setVisibility(writable ? View.VISIBLE : View.GONE);
|
||||
findViewById(R.id.actionSharing).setVisibility(writable ? View.VISIBLE : View.GONE);
|
||||
findViewById(R.id.actionDelete).setVisibility(writable ? View.VISIBLE : View.GONE);
|
||||
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
|
||||
// Contributors
|
||||
TextView contributorsTextView = (TextView) findViewById(R.id.contributorsTextView);
|
||||
contributorsTextView.setText(SpannableUtil.buildSpannableContributors(document.optJSONArray("contributors")));
|
||||
|
||||
// Relations
|
||||
JSONArray relations = document.optJSONArray("relations");
|
||||
if (relations.length() > 0) {
|
||||
TextView relationsTextView = (TextView) findViewById(R.id.relationsTextView);
|
||||
relationsTextView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
relationsTextView.setText(SpannableUtil.buildSpannableRelations(relations));
|
||||
} else {
|
||||
findViewById(R.id.relationsLayout).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Additional dublincore metadata
|
||||
displayDublincoreMetadata(R.id.subjectTextView, R.id.subjectLayout, "subject");
|
||||
displayDublincoreMetadata(R.id.identifierTextView, R.id.identifierLayout, "identifier");
|
||||
displayDublincoreMetadata(R.id.publisherTextView, R.id.publisherLayout, "publisher");
|
||||
displayDublincoreMetadata(R.id.formatTextView, R.id.formatLayout, "format");
|
||||
displayDublincoreMetadata(R.id.sourceTextView, R.id.sourceLayout, "source");
|
||||
displayDublincoreMetadata(R.id.typeTextView, R.id.typeLayout, "type");
|
||||
displayDublincoreMetadata(R.id.coverageTextView, R.id.coverageLayout, "coverage");
|
||||
displayDublincoreMetadata(R.id.rightsTextView, R.id.rightsLayout, "rights");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a dublincore metadata.
|
||||
*
|
||||
* @param textViewId TextView ID
|
||||
* @param blockViewId View ID
|
||||
* @param name Name
|
||||
*/
|
||||
private void displayDublincoreMetadata(int textViewId, int blockViewId, String name) {
|
||||
if (document == null) return;
|
||||
String value = document.optString(name);
|
||||
if (document.isNull(name) || value.isEmpty()) {
|
||||
findViewById(blockViewId).setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
findViewById(blockViewId).setVisibility(View.VISIBLE);
|
||||
TextView textView = (TextView) findViewById(textViewId);
|
||||
textView.setText(value);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,187 +0,0 @@
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,302 +0,0 @@
|
||||
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.support.v7.widget.Toolbar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ImageButton;
|
||||
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 = findViewById(R.id.drawer_layout);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
toolbar.setTitleTextColor(getResources().getColor(android.R.color.white));
|
||||
setSupportActionBar(toolbar);
|
||||
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 = findViewById(R.id.usernameTextView);
|
||||
usernameTextView.setText(userInfo.optString("username"));
|
||||
TextView emailTextView = findViewById(R.id.emailTextView);
|
||||
emailTextView.setText(userInfo.optString("email"));
|
||||
|
||||
// Get tag list to fill the drawer
|
||||
final ListView tagListView = findViewById(R.id.tagListView);
|
||||
final View tagProgressView = findViewById(R.id.tagProgressView);
|
||||
final TextView tagEmptyView = 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("tags")));
|
||||
}
|
||||
TagResource.list(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("tags")));
|
||||
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));
|
||||
}
|
||||
});
|
||||
|
||||
// Add document button
|
||||
ImageButton addDocumentButton = findViewById(R.id.addDocumentButton);
|
||||
addDocumentButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
startActivity(new Intent(MainActivity.this, DocumentEditActivity.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
|
||||
PreferenceUtil.clearAuthToken(MainActivity.this);
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* Context.
|
||||
*/
|
||||
private Context context;
|
||||
|
||||
/**
|
||||
* Audit log list adapter.
|
||||
*
|
||||
* @param context Context
|
||||
* @param logs Logs
|
||||
*/
|
||||
public AuditLogListAdapter(Context context, JSONArray logs) {
|
||||
this.context = context;
|
||||
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();
|
||||
|
||||
// Translate entity name
|
||||
int stringId = context.getResources().getIdentifier("auditlog_" + log.optString("class"), "string", context.getPackageName());
|
||||
if (stringId == 0) {
|
||||
message.append(log.optString("class"));
|
||||
} else {
|
||||
message.append(context.getResources().getString(stringId));
|
||||
}
|
||||
message.append(" ");
|
||||
|
||||
switch (log.optString("type")) {
|
||||
case "CREATE": message.append(context.getResources().getString(R.string.auditlog_created)); break;
|
||||
case "UPDATE": message.append(context.getResources().getString(R.string.auditlog_updated)); break;
|
||||
case "DELETE": message.append(context.getResources().getString(R.string.auditlog_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 = view.findViewById(R.id.usernameTextView);
|
||||
TextView messageTextView = view.findViewById(R.id.messageTextView);
|
||||
TextView dateTextView = 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;
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,158 +0,0 @@
|
||||
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.SpannableUtil;
|
||||
|
||||
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(SpannableUtil.buildSpannableTags(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
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("deu", R.string.language_german, R.drawable.deu));
|
||||
languageList.add(new Language("pol", R.string.language_polish, R.drawable.pol));
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
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, "", 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 (parentId.equals(tagParentId)) {
|
||||
TagItem tagItem = new TagItem();
|
||||
tagItem.id = tag.optString("id");
|
||||
tagItem.name = tag.optString("name");
|
||||
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);
|
||||
|
||||
// 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 String color;
|
||||
private int depth;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,233 +0,0 @@
|
||||
package com.sismics.docs.fragment;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
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.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.sismics.docs.R;
|
||||
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;
|
||||
|
||||
// View cache
|
||||
private EmptyRecyclerView recyclerView;
|
||||
private SwipeRefreshLayout swipeRefreshLayout;
|
||||
|
||||
// Infinite scrolling things
|
||||
private boolean loading = true;
|
||||
private int previousTotal = 0;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
final View view = inflater.inflate(R.layout.doc_list_fragment, container, false);
|
||||
|
||||
// Configure the RecyclerView
|
||||
recyclerView = view.findViewById(R.id.docList);
|
||||
adapter = new DocListAdapter();
|
||||
recyclerView.setAdapter(adapter);
|
||||
recyclerView.setHasFixedSize(true);
|
||||
recyclerView.setLongClickable(true);
|
||||
|
||||
// Configure the LayoutManager
|
||||
final LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
// Configure the swipe refresh layout
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 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 = 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,199 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
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("tags");
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package com.sismics.docs.listener;
|
||||
|
||||
/**
|
||||
* Simple listener.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public interface CallbackListener {
|
||||
public void onComplete();
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
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) { }
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
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";
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
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/list.
|
||||
*
|
||||
* @param context Context
|
||||
* @param callback Callback
|
||||
*/
|
||||
public static void list(Context context, HttpCallback callback) {
|
||||
Request request = new Request.Builder()
|
||||
.url(HttpUrl.parse(getApiUrl(context) + "/tag/list"))
|
||||
.get()
|
||||
.build();
|
||||
OkHttpUtil.buildClient(context)
|
||||
.newCall(request)
|
||||
.enqueue(HttpCallback.buildOkHttpCallback(callback));
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
@ -1,229 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
package com.sismics.docs.service;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
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 = "sismicsdocs:fileupload";
|
||||
private static final String CHANNEL_ID = "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);
|
||||
initChannels();
|
||||
notification = new NotificationCompat.Builder(this, CHANNEL_ID);
|
||||
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
|
||||
}
|
||||
|
||||
private void initChannels() {
|
||||
if (Build.VERSION.SDK_INT < 26) {
|
||||
return;
|
||||
}
|
||||
|
||||
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
|
||||
"File Upload", NotificationManager.IMPORTANCE_HIGH);
|
||||
channel.setDescription("Used to show file upload progress");
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
if (intent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
wakeLock.acquire(60_000 * 30); // 30 minutes upload time maximum
|
||||
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 e
|
||||
*/
|
||||
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());
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
package com.sismics.docs.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.AppBarLayout;
|
||||
import android.support.design.widget.CoordinatorLayout;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import com.sismics.docs.R;
|
||||
|
||||
public class ScrollingFABBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
|
||||
private int toolbarHeight;
|
||||
|
||||
public ScrollingFABBehavior(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
this.toolbarHeight = getToolbarHeight(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull FloatingActionButton fab, @NonNull View dependency) {
|
||||
return dependency instanceof AppBarLayout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull FloatingActionButton fab, @NonNull View dependency) {
|
||||
if (dependency instanceof AppBarLayout) {
|
||||
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
|
||||
int fabBottomMargin = lp.bottomMargin;
|
||||
int distanceToScroll = fab.getHeight() + fabBottomMargin;
|
||||
float ratio = dependency.getY() /(float) toolbarHeight;
|
||||
fab.setTranslationY(- distanceToScroll * ratio);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private int getToolbarHeight(Context context) {
|
||||
final TypedArray styledAttributes = context.getTheme().obtainStyledAttributes(
|
||||
new int[] { R.attr.actionBarSize });
|
||||
int toolbarHeight = (int) styledAttributes.getDimension(0, 0);
|
||||
styledAttributes.recycle();
|
||||
|
||||
return toolbarHeight;
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,148 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
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);
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
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.");
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package com.sismics.docs.ui.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ListView;
|
||||
|
||||
/**
|
||||
* Non-scrollable ListView.
|
||||
* All items are visible from the start.
|
||||
*
|
||||
* @author http://stackoverflow.com/questions/18813296/non-scrollable-listview-inside-scrollview/24629341#24629341
|
||||
*/
|
||||
public class NonScrollListView extends ListView {
|
||||
|
||||
public NonScrollListView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
public NonScrollListView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
public NonScrollListView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
@Override
|
||||
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int heightMeasureSpec_custom = MeasureSpec.makeMeasureSpec(
|
||||
Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec_custom);
|
||||
ViewGroup.LayoutParams params = getLayoutParams();
|
||||
params.height = getMeasuredHeight();
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
package com.sismics.docs.util;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.DownloadManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
|
||||
/**
|
||||
* 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(Activity activity, String url, String fileName, String title, String description) {
|
||||
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(activity, new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
String authToken = PreferenceUtil.getAuthToken(activity);
|
||||
DownloadManager downloadManager = (DownloadManager) activity.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);
|
||||
}
|
||||
}
|
@ -1,187 +0,0 @@
|
||||
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 = "Teedy 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();
|
||||
}
|
||||
}
|
@ -1,186 +0,0 @@
|
||||
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.
|
||||
*
|
||||
* @param context Context
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all auth tokens.
|
||||
*
|
||||
* @param context Context
|
||||
*/
|
||||
public static void clearAuthToken(Context context) {
|
||||
PersistentCookieStore cookieStore = new PersistentCookieStore(context);
|
||||
cookieStore.removeAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
@ -1,168 +0,0 @@
|
||||
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("simple:")
|
||||
.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();
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
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 spannable.
|
||||
*
|
||||
* @author bgamard.
|
||||
*/
|
||||
public class SpannableUtil {
|
||||
/**
|
||||
* Create a colored spannable from tags.
|
||||
*
|
||||
* @param tags Tags
|
||||
* @return Colored spannable
|
||||
*/
|
||||
public static Spannable buildSpannableTags(JSONArray tags) {
|
||||
return buildSpannable(tags, "name", "color");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a spannable for contributors.
|
||||
*
|
||||
* @param contributors Contributors
|
||||
* @return Spannable
|
||||
*/
|
||||
public static Spannable buildSpannableContributors(JSONArray contributors) {
|
||||
return buildSpannable(contributors, "username", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a spannable for relations.
|
||||
*
|
||||
* @param relations Relations
|
||||
* @return Spannable
|
||||
*/
|
||||
public static Spannable buildSpannableRelations(JSONArray relations) {
|
||||
return buildSpannable(relations, "title", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a spannable from a JSONArray.
|
||||
*
|
||||
* @param array JSONArray
|
||||
* @param valueName Name of the value part
|
||||
* @param colorName Name of the color part (optional)
|
||||
* @return Spannable
|
||||
*/
|
||||
private static Spannable buildSpannable(JSONArray array, String valueName, String colorName) {
|
||||
SpannableStringBuilder builder = new SpannableStringBuilder();
|
||||
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
final JSONObject tag = array.optJSONObject(i);
|
||||
int start = builder.length();
|
||||
builder.append(" ").append(tag.optString(valueName)).append(" ");
|
||||
builder.setSpan(new ForegroundColorSpan(Color.WHITE), start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
builder.setSpan(new BackgroundColorSpan(Color.parseColor(tag.optString(colorName, "#5bc0de"))), start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
/*
|
||||
TODO : Make tags, relations and contributors clickable
|
||||
builder.setSpan(new ClickableSpan() {
|
||||
@Override
|
||||
public void onClick(View widget) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDrawState(TextPaint ds) {
|
||||
super.updateDrawState(ds);
|
||||
ds.setColor(Color.WHITE);
|
||||
ds.setUnderlineText(false);
|
||||
}
|
||||
}, start, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);*/
|
||||
builder.append(" ");
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 352 B |
Before Width: | Height: | Size: 198 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 379 B |
Before Width: | Height: | Size: 271 B |
Before Width: | Height: | Size: 353 B |
Before Width: | Height: | Size: 363 B |
Before Width: | Height: | Size: 283 B |
Before Width: | Height: | Size: 275 B |
Before Width: | Height: | Size: 271 B |
Before Width: | Height: | Size: 543 B |