Repo created
16
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
.idea
|
||||||
|
/.idea
|
||||||
|
*.iml
|
||||||
|
.gradle
|
||||||
|
/local.properties
|
||||||
|
/.idea/workspace.xml
|
||||||
|
/.idea/libraries
|
||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
||||||
|
.externalNativeBuild
|
||||||
|
*.jks
|
||||||
|
/app/owncloud/
|
||||||
|
*.aab
|
||||||
|
*.apk
|
||||||
|
/app/release/
|
||||||
87
.gitlab-ci.yml
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
# To contribute improvements to CI/CD templates, please follow the Development guide at:
|
||||||
|
# https://docs.gitlab.com/ee/development/cicd/templates.html
|
||||||
|
# This specific template is located at:
|
||||||
|
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Android.gitlab-ci.yml
|
||||||
|
|
||||||
|
# Read more about this script on this blog post https://about.gitlab.com/2018/10/24/setting-up-gitlab-ci-for-android-projects/, by Jason Lenny
|
||||||
|
# If you are interested in using Android with FastLane for publishing take a look at the Android-Fastlane template.
|
||||||
|
|
||||||
|
image: openjdk:11-jdk
|
||||||
|
|
||||||
|
variables:
|
||||||
|
|
||||||
|
# ANDROID_COMPILE_SDK is the version of Android you're compiling with.
|
||||||
|
# It should match compileSdkVersion.
|
||||||
|
ANDROID_COMPILE_SDK: "30"
|
||||||
|
|
||||||
|
# ANDROID_BUILD_TOOLS is the version of the Android build tools you are using.
|
||||||
|
# It should match buildToolsVersion.
|
||||||
|
ANDROID_BUILD_TOOLS: "30.0.3"
|
||||||
|
|
||||||
|
# It's what version of the command line tools we're going to download from the official site.
|
||||||
|
# Official Site-> https://developer.android.com/studio/index.html
|
||||||
|
# There, look down below at the cli tools only, sdk tools package is of format:
|
||||||
|
# commandlinetools-os_type-ANDROID_SDK_TOOLS_latest.zip
|
||||||
|
# when the script was last modified for latest compileSdkVersion, it was which is written down below
|
||||||
|
ANDROID_SDK_TOOLS: "7583922"
|
||||||
|
|
||||||
|
# Packages installation before running script
|
||||||
|
before_script:
|
||||||
|
- apt-get --quiet update --yes
|
||||||
|
- apt-get --quiet install --yes wget tar unzip lib32stdc++6 lib32z1
|
||||||
|
|
||||||
|
# Setup path as ANDROID_HOME for moving/exporting the downloaded sdk into it
|
||||||
|
- export ANDROID_HOME="${PWD}/android-home"
|
||||||
|
# Create a new directory at specified location
|
||||||
|
- install -d $ANDROID_HOME
|
||||||
|
# Here we are installing androidSDK tools from official source,
|
||||||
|
# (the key thing here is the url from where you are downloading these sdk tool for command line, so please do note this url pattern there and here as well)
|
||||||
|
# after that unzipping those tools and
|
||||||
|
# then running a series of SDK manager commands to install necessary android SDK packages that'll allow the app to build
|
||||||
|
- wget --output-document=$ANDROID_HOME/cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_TOOLS}_latest.zip
|
||||||
|
# move to the archive at ANDROID_HOME
|
||||||
|
- pushd $ANDROID_HOME
|
||||||
|
- unzip -d cmdline-tools cmdline-tools.zip
|
||||||
|
- pushd cmdline-tools
|
||||||
|
# since commandline tools version 7583922 the root folder is named "cmdline-tools" so we rename it if necessary
|
||||||
|
- mv cmdline-tools tools || true
|
||||||
|
- popd
|
||||||
|
- popd
|
||||||
|
- export PATH=$PATH:${ANDROID_HOME}/cmdline-tools/tools/bin/
|
||||||
|
|
||||||
|
# Nothing fancy here, just checking sdkManager version
|
||||||
|
- sdkmanager --version
|
||||||
|
|
||||||
|
# use yes to accept all licenses
|
||||||
|
- yes | sdkmanager --licenses || true
|
||||||
|
- sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}"
|
||||||
|
- sdkmanager "platform-tools"
|
||||||
|
- sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}"
|
||||||
|
|
||||||
|
# Not necessary, but just for surity
|
||||||
|
- chmod +x ./gradlew
|
||||||
|
|
||||||
|
# Basic android and gradle stuff
|
||||||
|
# Check linting
|
||||||
|
lintDebug:
|
||||||
|
interruptible: true
|
||||||
|
stage: build
|
||||||
|
script:
|
||||||
|
- ./gradlew -Pci --console=plain :app:lintDebug -PbuildDir=lint
|
||||||
|
|
||||||
|
# Make Project
|
||||||
|
assembleDebug:
|
||||||
|
interruptible: true
|
||||||
|
stage: build
|
||||||
|
script:
|
||||||
|
- ./gradlew assembleDebug
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- app/build/outputs/
|
||||||
|
|
||||||
|
# Run all tests, if any fails, interrupt the pipeline(fail it)
|
||||||
|
debugTests:
|
||||||
|
interruptible: true
|
||||||
|
stage: test
|
||||||
|
script:
|
||||||
|
- ./gradlew -Pci --console=plain :app:testDebug
|
||||||
100
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
# [v1.18](https://gitlab.com/bisada/OCBookmarks/tags/v1.18)
|
||||||
|
This release is major release. Thanks to [newhinton](https://gitlab.com/newhinton). The following feature are released:
|
||||||
|
- One click Quick Sharing.
|
||||||
|
- Folder structure.
|
||||||
|
- CI FIX.
|
||||||
|
- Multiple functionality improvements and remove unnecessary methods.
|
||||||
|
- Switching between multiple accounts.
|
||||||
|
- The following Issues has been addressed in the following release:
|
||||||
|
- [Save in folders](https://gitlab.com/bisada/OCBookmarks/-/issues/25)
|
||||||
|
- [Switching between multiple SSO accounts](https://gitlab.com/bisada/OCBookmarks/-/issues/79)
|
||||||
|
- [widget and tags improvement](https://gitlab.com/bisada/OCBookmarks/-/issues/7)
|
||||||
|
- [[feature request] save in folders](https://gitlab.com/bisada/OCBookmarks/-/issues/25)
|
||||||
|
- [return](https://gitlab.com/bisada/OCBookmarks/-/issues/71)
|
||||||
|
- [Configurable default view](https://gitlab.com/bisada/OCBookmarks/-/issues/1)
|
||||||
|
- [Folder support?](https://gitlab.com/bisada/OCBookmarks/-/issues/61)
|
||||||
|
- [Where are the bookmarks folders?](https://gitlab.com/bisada/OCBookmarks/-/issues/68)
|
||||||
|
- [CI pipeline is broken](https://gitlab.com/bisada/OCBookmarks/-/issues/78)
|
||||||
|
- [Drawer home icon missing](https://gitlab.com/bisada/OCBookmarks/-/issues/52)
|
||||||
|
|
||||||
|
# [v1.17](https://gitlab.com/bisada/OCBookmarks/tags/v1.17)
|
||||||
|
- Introducing SSO again.
|
||||||
|
- minor improvements in the app.
|
||||||
|
- The following issues are closed:
|
||||||
|
- [[Feature request] Single sign-on](https://gitlab.com/bisada/OCBookmarks/-/issues/27)
|
||||||
|
- [unable to login](https://gitlab.com/bisada/OCBookmarks/-/issues/60)
|
||||||
|
- [Nextcloud Bookmarks issue with TLSv1.3](https://gitlab.com/bisada/OCBookmarks/-/issues/42)
|
||||||
|
- [support http and https connection](https://gitlab.com/bisada/OCBookmarks/-/issues/30)
|
||||||
|
|
||||||
|
# [v1.16](https://gitlab.com/bisada/OCBookmarks/tags/v1.16)
|
||||||
|
|
||||||
|
- SSO Removed as requested #67
|
||||||
|
|
||||||
|
# [v1.15](https://gitlab.com/bisada/OCBookmarks/tags/v1.15)
|
||||||
|
|
||||||
|
- Bookmark description & Title depend on API.
|
||||||
|
- Bookmarks can now exported and saved to phone local.
|
||||||
|
|
||||||
|
# [v1.14](https://gitlab.com/bisada/OCBookmarks/tags/v1.14)
|
||||||
|
|
||||||
|
- Refactor of README.md
|
||||||
|
|
||||||
|
# [v1.13](https://gitlab.com/bisada/OCBookmarks/tags/v1.13)
|
||||||
|
|
||||||
|
- SSO initial release.
|
||||||
|
|
||||||
|
# [v1.12](https://gitlab.com/bisada/OCBookmarks/tags/v1.12)
|
||||||
|
|
||||||
|
- New Login screen design. Show/Hide password in login screen.
|
||||||
|
|
||||||
|
# [v1.11](https://gitlab.com/bisada/OCBookmarks/tags/v1.11)
|
||||||
|
|
||||||
|
- Added initial Drawer
|
||||||
|
|
||||||
|
# [v1.10](https://gitlab.com/bisada/OCBookmarks/tags/v1.10)
|
||||||
|
|
||||||
|
- Much waited V3 Bookmark API version fixed.
|
||||||
|
|
||||||
|
# [v1.9](https://gitlab.com/bisada/OCBookmarks/tags/v1.9)
|
||||||
|
|
||||||
|
- Can not Clear description fix!
|
||||||
|
|
||||||
|
# [v1.8](https://gitlab.com/bisada/OCBookmarks/tags/v1.8)
|
||||||
|
|
||||||
|
- V3 API Fix!
|
||||||
|
- This is an intermediate release!!
|
||||||
|
- Known issues are documented here : <https://github.com/nextcloud/bookmarks/issues/1012>
|
||||||
|
|
||||||
|
# [v1.6](https://gitlab.com/bisada/OCBookmarks/tags/v1.6)
|
||||||
|
|
||||||
|
- Latest target version update
|
||||||
|
- Fix icons
|
||||||
|
- Gradle update.
|
||||||
|
|
||||||
|
# [v1.5](https://gitlab.com/bisada/OCBookmarks/tags/v1.5)
|
||||||
|
|
||||||
|
- Fix several translation
|
||||||
|
- Fix icons
|
||||||
|
|
||||||
|
|
||||||
|
# [v1.2](https://gitlab.com/bisada/OCBookmarks/tags/v1.2)
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
- Fixed icons in menu
|
||||||
|
- fixed following / problem in log in
|
||||||
|
|
||||||
|
|
||||||
|
# [v1.1](https://gitlab.com/bisada/OCBookmarks/tags/v1.1)
|
||||||
|
|
||||||
|
### New
|
||||||
|
- Add support for owncloud (just the cooperate identity).
|
||||||
|
- add proguard (minify)
|
||||||
|
- Russian translation
|
||||||
|
|
||||||
|
### fixes
|
||||||
|
- spelling fixes
|
||||||
|
- allow to update tags
|
||||||
|
|
||||||
|
|
||||||
|
# [v1.0](https://gitlab.com/bisada/OCBookmarks/tags/v1.0)
|
||||||
|
Initial release providing general functionality.
|
||||||
7
LICENSE.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
Copyright 2017 Christian Schabesberger <chris.schabesberger@mailbox.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
45
LOGINGUIDE.md
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
# :link: Nextcloud Bookmarks Android App testing Guide
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
* You should have nextcloud instances access
|
||||||
|
* NextCloud Bookmarks version [3.2.1](https://github.com/nextcloud/bookmarks/releases/tag/v3.2.1) should be installed.
|
||||||
|
|
||||||
|
### Login to Bookmark App
|
||||||
|
|
||||||
|
* Open the Android App "Nextcloud Bookmarks"
|
||||||
|
* **Step 1:** Click on **Manual login**. SSO integration is having some [known issues](https://gitlab.com/bisada/OCBookmarks/-/issues/27)
|
||||||
|
* **Step 2:** Enter Credentials:
|
||||||
|
* Enter the "server address" in the field. Eg: https://us.cloudamo.com/
|
||||||
|
* Enter the Username in the **user name** field. eg. email id(biswajitxxxxxxxx@nextcloud.com)
|
||||||
|
* Enter the credentials **Password** field.
|
||||||
|
* Finally click on **SIGN IN** button.
|
||||||
|
* **Step 3:** It will open the **BOOKMARKS** screen
|
||||||
|
* **Step 4:** Click on the **TAGS** tab to open TAGS screen.
|
||||||
|
|
||||||
|
|
||||||
|
| Step 1 Manual Login | Step 2 Credentials | Step 3 Bookmarks screen | Step 4 Tags Screen |
|
||||||
|
| :--: | :--: | :--: | :--: |
|
||||||
|
|  |  |  |  |
|
||||||
|
|
||||||
|
### ADD New BookMark
|
||||||
|
|
||||||
|
* **Add Bookmarks:** To add New **bookmark** / **Tag** Click on the **+** (plus sign)
|
||||||
|
* Add the intended url in **URL** field. Eg: https://www.youtube.com/user/Computerap
|
||||||
|
* Add some meaningfull Title or Description.
|
||||||
|
* Click on **+** (plus button) to add Tags to it. Eg. **youtube** . You can add multiple tags if you want.
|
||||||
|
* Hit the **SAVE** button to add the **Bookmarks**.
|
||||||
|
|
||||||
|
| Add Bookmarks screen |
|
||||||
|
| :--: |
|
||||||
|
|  |
|
||||||
|
|
||||||
|
### EDIT/Delete Bookmark/Tags
|
||||||
|
|
||||||
|
* **Edit/Delete:** To edit or delete Bookmarks please long press on the **Bookmarks** or **Tags** this will open EDIT/DELETE/SHARE window.
|
||||||
|
|
||||||
|
| Edit/Delete screen |
|
||||||
|
| :--: |
|
||||||
|
|  |
|
||||||
|
|
||||||
|
Thank you.
|
||||||
113
README.md
|
|
@ -1,3 +1,112 @@
|
||||||
# c2c-bookmarks
|
## [<img src="assets/nx/icon.png" height="20">](/) Nextcloud Bookmarks Android App
|
||||||
|
|
||||||
Nextcloud Bookmarks Android App
|
[](https://gitlab.com/bisada/OCBookmarks/activity)
|
||||||
|
[](https://gitlab.com/bisada/OCBookmarks/-/issues)
|
||||||
|
[](https://gitlab.com/bisada/OCBookmarks/-/pipelines)
|
||||||
|
[](https://f-droid.org/en/packages/org.schabi.nxbookmarks/)
|
||||||
|
[](https://t.me/nextcloudbookmarks)
|
||||||
|
[](https://gitter.im/nextcloud-bookmarks/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
An Android front end for the Nextcloud [Bookmark App](https://github.com/nextcloud/bookmarks/)
|
||||||
|
based on the new [REST API](https://github.com/nextcloud/bookmarks/#rest-api) that was introduced
|
||||||
|
by NextCloudBookmarks version [3.2.1](https://github.com/nextcloud/bookmarks/releases/tag/v3.2.1)
|
||||||
|
|
||||||
|
From v1.18 below features are introduced:
|
||||||
|
|
||||||
|
#### ☑ Now login with nextcloud single sign-on 🔊
|
||||||
|
#### ☑ now showing bookmarks under Folder.📁
|
||||||
|
#### ☑ App support Multiple accounts 👩👩👧👧
|
||||||
|
|
||||||
|
## :arrow_forward: Access
|
||||||
|
|
||||||
|
[<img src="https://raw.githubusercontent.com/stefan-niedermann/paypal-donate-button/master/paypal-donate-button.png"
|
||||||
|
alt="Donate with PayPal"
|
||||||
|
height="80">](https://www.paypal.me/biswajitbangalore)
|
||||||
|
[<img src="https://raw.githubusercontent.com/stefan-niedermann/DonateButtons/master/LiberaPay.png"
|
||||||
|
alt="Donate using Liberapay"
|
||||||
|
height="80">](https://liberapay.com/bisasda/donate)
|
||||||
|
[<img src="./assets/fdroid_badge.png"
|
||||||
|
alt="F-Droid"
|
||||||
|
height="80">](https://f-droid.org/packages/org.schabi.nxbookmarks/)
|
||||||
|
[<img src="https://images-na.ssl-images-amazon.com/images/G/01/mobile-apps/devportal2/res/images/amazon-appstore-badge-english-white.png"
|
||||||
|
alt="Amazon"
|
||||||
|
height="80">](https://www.amazon.com/dp/B08L5RKHMM/ref=apps_sf_sta)
|
||||||
|
|
||||||
|
|
||||||
|
## :rocket: Features
|
||||||
|
|
||||||
|
* SSO : Nextcloud Single Sign On 🔊 🔥. Need [nextcloud files](https://github.com/nextcloud/android) app as dependency.
|
||||||
|
* Folder Structure 📁🔥
|
||||||
|
* Works offline 🔌 (WIP)
|
||||||
|
* Mark bookmarks as favorite Organize your bookmarks with labels 🔖
|
||||||
|
* Manage tags 🏷
|
||||||
|
* Translated in many languages 🌎
|
||||||
|
* Multiple accounts 👩👩👧👧
|
||||||
|
|
||||||
|
|
||||||
|
## :eyes: Screenshots
|
||||||
|
|
||||||
|
| Multiple Accounts | SSO | Tags | Bookmarks |
|
||||||
|
| :--: | :--: | :--: | :--: |
|
||||||
|
|  |  |  |  |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## :checkered_flag: Planned features
|
||||||
|
|
||||||
|
* [Folder Structure](https://gitlab.com/bisada/OCBookmarks/issues/17)
|
||||||
|
|
||||||
|
|
||||||
|
## :link: Issues
|
||||||
|
* Please note we have identified Some issues. Please look at [Issue board](https://gitlab.com/bisada/OCBookmarks/issues) before review.
|
||||||
|
* Feel free to send us a pull request.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## :link: How to compile the App
|
||||||
|
|
||||||
|
### :label: Requirements:
|
||||||
|
|
||||||
|
* [Android Studio](https://developer.android.com/studio/) instance running.
|
||||||
|
* [Nextcloud](https://nextcloud.com/) instance running.
|
||||||
|
* [Nextcloud Android](https://github.com/nextcloud/android) app installed (> 3.9.0)
|
||||||
|
* [Nextcloud Bookmark](https://github.com/nextcloud/bookmarks) app enabled on Instances
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
Download and install:
|
||||||
|
|
||||||
|
1. Open cmd/terminal
|
||||||
|
2. Navigate to your workspace
|
||||||
|
3. Then type in: `git clone https://gitlab.com/bisada/OCBookmarks.git`
|
||||||
|
4. Import the Project in Android Studio and start coding!
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## :link: Maintainer
|
||||||
|
* [Biswajit Das](https://gitlab.com/bisasda): @bisasda
|
||||||
|
|
||||||
|
## :link: Contributors
|
||||||
|
* [Biswajit Das](https://gitlab.com/bisasda): @bisasda
|
||||||
|
* [Christian Schabesberger](https://gitlab.com/derSchabi): @derSchabi
|
||||||
|
* [Stefan Niedermann](https://github.com/stefan-niedermann): @stefan-niedermann
|
||||||
|
* [Felix Nüsse](https://github.com/newhinton): @newhinton
|
||||||
|
|
||||||
|
## :family: Join the team
|
||||||
|
|
||||||
|
* Test the app with different devices: [Testing Guide](./TESTING.md)
|
||||||
|
* Report issues in the [issue tracker](https://gitlab.com/bisada/OCBookmarks/issues)
|
||||||
|
* [Pick an issue](https://gitlab.com/bisada/OCBookmarks/-/issues?label_name%5B%5D=help+wanted) :notebook:
|
||||||
|
* Create a [Pull Request](https://opensource.guide/how-to-contribute/#opening-a-pull-request)
|
||||||
|
* Buy this app on [Amazon App Store](https://www.amazon.com/dp/B08L5RKHMM/ref=apps_sf_sta)
|
||||||
|
* Send me a bottle of your favorite beer :beers: :wink:
|
||||||
|
* [](https://t.me/nextcloudbookmarks)
|
||||||
|
|
||||||
|
|
||||||
|
## :link: Contributions
|
||||||
|
* All pull requests are welcome.
|
||||||
|
|
||||||
|
[](https://gitlab.com/bisada/)
|
||||||
45
TESTING.md
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
# :link: Testing: Nextcloud Bookmarks Android App testing Guide
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
* You should have nextcloud instances access
|
||||||
|
* NextCloud Bookmarks version [3.2.1](https://github.com/nextcloud/bookmarks/releases/tag/v3.2.1) should be installed.
|
||||||
|
|
||||||
|
### Login to Bookmark App
|
||||||
|
|
||||||
|
* Open the Android App "Nextcloud Bookmarks"
|
||||||
|
* **Step 1:** Click on **Nextcloud Singn on (SSO)**.
|
||||||
|
* **Step 2:** Register the Nextcoud app for sso:
|
||||||
|
* Enter the "server address" in the field. Eg: https://us.cloudamo.com/
|
||||||
|
* Enter the Username in the **user name** field. eg. email id(biswajitxxxxxxxx@nextcloud.com)
|
||||||
|
* Enter the credentials **Password** field.
|
||||||
|
* Finally click on **SIGN IN** button.
|
||||||
|
* Once added select the account to continue.
|
||||||
|
* **Step 3:** It will open the **BOOKMARKS** screen
|
||||||
|
* **Step 4:** Click on the **TAGS** tab to open TAGS screen.
|
||||||
|
|
||||||
|
|
||||||
|
| Step 1 SSO Login | Step 2 Select account | Step 3 Bookmarks screen | Step 4 Tags Screen |
|
||||||
|
| :--: | :--: | :--: | :--: |
|
||||||
|
|  |  |  |  |
|
||||||
|
|
||||||
|
### ADD New BookMark
|
||||||
|
|
||||||
|
* **Add Bookmarks:** To add New **bookmark** / **Tag** Click on the **+** (plus sign)
|
||||||
|
* Add the intended url in **URL** field. Eg: https://www.youtube.com/user/Computerap
|
||||||
|
* Add some meaningfull Title or Description.
|
||||||
|
* Click on **+** (plus button) to add Tags to it. Eg. **youtube** . You can add multiple tags if you want.
|
||||||
|
* Hit the **SAVE** button to add the **Bookmarks**.
|
||||||
|
|
||||||
|
| Add Bookmarks screen |
|
||||||
|
| :--: |
|
||||||
|
|  |
|
||||||
|
|
||||||
|
### EDIT/Delete Bookmark/Tags
|
||||||
|
|
||||||
|
* **Edit/Delete:** To edit or delete Bookmarks please long press on the **Bookmarks** or **Tags** this will open EDIT/DELETE/SHARE window.
|
||||||
|
|
||||||
|
| Edit/Delete screen |
|
||||||
|
| :--: |
|
||||||
|
|  |
|
||||||
|
|
||||||
1
app/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
||||||
86
app/build.gradle
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: "com.android.application"
|
||||||
|
apply plugin: "kotlin-android"
|
||||||
|
apply plugin: "androidx.navigation.safeargs.kotlin"
|
||||||
|
apply plugin: "kotlin-kapt"
|
||||||
|
apply plugin: 'kotlinx-serialization'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 32
|
||||||
|
buildToolsVersion '33.0.0'
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 21
|
||||||
|
targetSdkVersion 32
|
||||||
|
versionCode 19
|
||||||
|
versionName "1.19"
|
||||||
|
applicationId "org.bisw.nxbookmarks"
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
flavorDimensions "default"
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
// Do not minify: https://github.com/nextcloud/Android-SingleSignOn/issues/488
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
|
||||||
|
owncloud {
|
||||||
|
// Do not minify: https://github.com/nextcloud/Android-SingleSignOn/issues/488
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
applicationIdSuffix ".owncloud"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
productFlavors {
|
||||||
|
fdroid {
|
||||||
|
applicationId "org.schabi.nxbookmarks"
|
||||||
|
}
|
||||||
|
amazon{
|
||||||
|
applicationId "org.bisw.nxbookmarks"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||||
|
useIR = true
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding true
|
||||||
|
dataBinding true
|
||||||
|
}
|
||||||
|
lint {
|
||||||
|
disable 'MissingTranslation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
//implementation 'com.github.desperateCoder:Android-SingleSignOn:273-multiple-params-for-key-SNAPSHOT'
|
||||||
|
implementation "com.github.nextcloud:Android-SingleSignOn:0.6.0"
|
||||||
|
androidTestImplementation('androidx.test.espresso:espresso-core:3.5.0', {
|
||||||
|
exclude group: 'com.android.support', module: 'support-annotations'
|
||||||
|
})
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.5.1'
|
||||||
|
implementation 'com.google.android.material:material:1.7.0'
|
||||||
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
implementation 'org.eclipse.birt.runtime.3_7_1:org.apache.commons.codec:1.3.0'
|
||||||
|
implementation 'org.jsoup:jsoup:1.15.3'
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||||
|
}
|
||||||
28
app/proguard-rules.pro
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in /home/the-scrabi/bin/Android/Sdk/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the proguardFiles
|
||||||
|
# directive in build.gradle.
|
||||||
|
#
|
||||||
|
# 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 *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
|
|
||||||
|
-dontobfuscate
|
||||||
|
-keep class org.jsoup.**
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.schabi.ocbookmarks;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumentation test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
public void useAppContext() throws Exception {
|
||||||
|
// Context of the app under test.
|
||||||
|
Context appContext = InstrumentationRegistry.getTargetContext();
|
||||||
|
|
||||||
|
assertEquals("org.schabi.ocbookmarks", appContext.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
||||||
54
app/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="org.schabi.ocbookmarks">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
|
android:theme="@style/AppTheme">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:label="@string/app_name_short"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@style/AppTheme.NoActionBar">
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".LoginAcitivty"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@style/LoginStyle">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity android:name=".AddBookmarkActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/share_target_normal"
|
||||||
|
android:theme="@style/Transparent">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="text/plain"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity android:name=".QuickaddBookmarkActivity"
|
||||||
|
android:label="@string/share_target_quick"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@style/Transparent">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="text/plain"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
BIN
app/src/main/ic_launcher-web.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
|
|
@ -0,0 +1,103 @@
|
||||||
|
package org.schabi.ocbookmarks;
|
||||||
|
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import com.nextcloud.android.sso.BuildConfig;
|
||||||
|
import com.nextcloud.android.sso.api.NextcloudAPI;
|
||||||
|
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
|
||||||
|
import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
|
||||||
|
import com.nextcloud.android.sso.helper.SingleAccountHelper;
|
||||||
|
import com.nextcloud.android.sso.model.SingleSignOnAccount;
|
||||||
|
|
||||||
|
import org.schabi.ocbookmarks.REST.OCBookmarksRestConnector;
|
||||||
|
import org.schabi.ocbookmarks.REST.model.Bookmark;
|
||||||
|
import org.schabi.ocbookmarks.api.LoginData;
|
||||||
|
import org.schabi.ocbookmarks.api.SSOUtil;
|
||||||
|
import org.schabi.ocbookmarks.listener.BookmarkListener;
|
||||||
|
|
||||||
|
public class AddBookmarkActivity extends AppCompatActivity implements BookmarkListener {
|
||||||
|
|
||||||
|
LoginData loginData = new LoginData();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.add_bookmark_activity);
|
||||||
|
setTitle("");
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
String title = intent.getStringExtra(Intent.EXTRA_SUBJECT);
|
||||||
|
String url = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||||
|
|
||||||
|
EditBookmarkDialog bookmarkDialog = new EditBookmarkDialog();
|
||||||
|
bookmarkDialog.newBookmark(title, url);
|
||||||
|
AlertDialog dialog = bookmarkDialog.getDialog(this, null, this);
|
||||||
|
dialog.show();
|
||||||
|
|
||||||
|
dialog.setOnDismissListener(dialog1 -> AddBookmarkActivity.this.finish());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void update(Bookmark bookmark) {
|
||||||
|
AsyncTask<Void, Void, String> updateTask = new AsyncTask<Void, Void, String>() {
|
||||||
|
@Override
|
||||||
|
protected String doInBackground(Void... params) {
|
||||||
|
NextcloudAPI nextcloudAPI = null;
|
||||||
|
try {
|
||||||
|
SingleSignOnAccount ssoa = SingleAccountHelper.getCurrentSingleSignOnAccount(AddBookmarkActivity.this.getApplicationContext());
|
||||||
|
nextcloudAPI = SSOUtil.getNextcloudAPI(AddBookmarkActivity.this, ssoa);
|
||||||
|
} catch (NextcloudFilesAppAccountNotFoundException e) {
|
||||||
|
Toast.makeText(AddBookmarkActivity.this,
|
||||||
|
R.string.nextcloud_files_app_account_not_found_message,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
} catch ( NoCurrentAccountSelectedException e) {
|
||||||
|
Toast.makeText(AddBookmarkActivity.this,
|
||||||
|
R.string.no_current_account_selected_exception_message,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
OCBookmarksRestConnector connector = new OCBookmarksRestConnector(nextcloudAPI);
|
||||||
|
try {
|
||||||
|
connector.addBookmark(bookmark);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if(BuildConfig.DEBUG) e.printStackTrace();
|
||||||
|
return getString(R.string.could_not_add_bookmark);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(String result) {
|
||||||
|
if(result != null) {
|
||||||
|
Toast.makeText(AddBookmarkActivity.this,
|
||||||
|
result,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(AddBookmarkActivity.this,
|
||||||
|
R.string.bookmark_saved,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bookmarkChanged(Bookmark bookmark) {
|
||||||
|
update(bookmark);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteBookmark(Bookmark bookmark) {}
|
||||||
|
}
|
||||||
208
app/src/main/java/org/schabi/ocbookmarks/BookmarkFragment.java
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
package org.schabi.ocbookmarks;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.schabi.ocbookmarks.REST.model.Bookmark;
|
||||||
|
import org.schabi.ocbookmarks.REST.model.BookmarkListElement;
|
||||||
|
import org.schabi.ocbookmarks.REST.model.Folder;
|
||||||
|
import org.schabi.ocbookmarks.listener.BookmarkListener;
|
||||||
|
import org.schabi.ocbookmarks.listener.FolderListener;
|
||||||
|
import org.schabi.ocbookmarks.listener.OnRequestReloadListener;
|
||||||
|
import org.schabi.ocbookmarks.ui.BookmarksRecyclerViewAdapter;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by the-scrabi on 15.05.17.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class BookmarkFragment extends Fragment implements FolderListener {
|
||||||
|
|
||||||
|
private ArrayList<Bookmark> mBookmarkList = new ArrayList<>();
|
||||||
|
private ArrayList<BookmarkListElement> mFilteredBookmarks = new ArrayList<>();
|
||||||
|
private BookmarksRecyclerViewAdapter mAdapter;
|
||||||
|
private SwipeRefreshLayout refreshLayout;
|
||||||
|
|
||||||
|
private Folder mRootFolder;
|
||||||
|
private Folder mCurrentFolder;
|
||||||
|
|
||||||
|
private String mTagFilter = "";
|
||||||
|
private String mSearchTerm = "";
|
||||||
|
|
||||||
|
|
||||||
|
private OnRequestReloadListener onRequestReloadListener = null;
|
||||||
|
private BookmarkListener bookmarkListener = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changeFolderCallback(@NonNull Folder f) {
|
||||||
|
if(f.getId() == Folder.UP_ID) {
|
||||||
|
f = getFolderFromID(mCurrentFolder.getParentFolderId());
|
||||||
|
}
|
||||||
|
buildCurrentView(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setBookmarkListener(BookmarkListener listener) {
|
||||||
|
bookmarkListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnRequestReloadListener(OnRequestReloadListener listener) {
|
||||||
|
onRequestReloadListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onBackHandled() {
|
||||||
|
if(mCurrentFolder == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(mRootFolder.getId() == mCurrentFolder.getId()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Folder f = getFolderFromID(mCurrentFolder.getParentFolderId());
|
||||||
|
buildCurrentView(f);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Folder getFolderFromID(int id) {
|
||||||
|
if(id == mRootFolder.getId()) {
|
||||||
|
return mRootFolder;
|
||||||
|
}
|
||||||
|
return getFolderFromID(id, mRootFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Folder getFolderFromID(int id, Folder level) {
|
||||||
|
for(Folder f: level.getChildren()){
|
||||||
|
if(f.getId() == id) {
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
Folder m = getFolderFromID(id, f);
|
||||||
|
if(m != null) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View rootView = inflater.inflate(R.layout.fagment_bookmarks, container, false);
|
||||||
|
|
||||||
|
refreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swiperefresh_bookmarks);
|
||||||
|
|
||||||
|
RecyclerView recyclerView = (RecyclerView) rootView.findViewById(R.id.rv);
|
||||||
|
mAdapter = new BookmarksRecyclerViewAdapter(mFilteredBookmarks, getContext());
|
||||||
|
mAdapter.setBookmarkListener(bookmarkListener);
|
||||||
|
mAdapter.setBookmarkFolderListener(this);
|
||||||
|
recyclerView.setAdapter(mAdapter);
|
||||||
|
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||||
|
|
||||||
|
|
||||||
|
refreshLayout.setOnRefreshListener(() -> {
|
||||||
|
if(onRequestReloadListener != null) {
|
||||||
|
onRequestReloadListener.requestReload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return rootView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void buildCurrentView(Folder currentFolder) {
|
||||||
|
mCurrentFolder = currentFolder;
|
||||||
|
mFilteredBookmarks = new ArrayList<>();
|
||||||
|
|
||||||
|
if(!isCurrentFolderRoot()) {
|
||||||
|
Folder up = new Folder();
|
||||||
|
up.setTitle("..");
|
||||||
|
up.setId(Folder.UP_ID);
|
||||||
|
mFilteredBookmarks.add(new BookmarkListElement(up));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Folder f: currentFolder.getChildren()) {
|
||||||
|
mFilteredBookmarks.add(new BookmarkListElement(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Bookmark b : mBookmarkList) {
|
||||||
|
boolean shouldAdd = true;
|
||||||
|
|
||||||
|
if(!mSearchTerm.equals("") &&
|
||||||
|
!b.getTitle().contains(mSearchTerm) &&
|
||||||
|
!b.getDescription().contains(mSearchTerm) &&
|
||||||
|
!b.getUrl().contains(mSearchTerm)
|
||||||
|
) {
|
||||||
|
shouldAdd = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!mTagFilter.equals("") && !b.getTags().contains(mTagFilter)) {
|
||||||
|
shouldAdd = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b.getFolders().contains(currentFolder.getId()) && shouldAdd){
|
||||||
|
mFilteredBookmarks.add(new BookmarkListElement(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mAdapter.updateBookmarklist(mFilteredBookmarks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showByTag(String tag) {
|
||||||
|
mTagFilter = tag;
|
||||||
|
buildCurrentView(mCurrentFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void releaseTag() {
|
||||||
|
mTagFilter = "";
|
||||||
|
buildCurrentView(mCurrentFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void search(String term) {
|
||||||
|
mSearchTerm = term;
|
||||||
|
buildCurrentView(mCurrentFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearSearch() {
|
||||||
|
mSearchTerm = "";
|
||||||
|
buildCurrentView(mCurrentFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateData(Folder hierarchy, Bookmark[] bookmarks) {
|
||||||
|
mRootFolder = hierarchy;
|
||||||
|
mBookmarkList.clear();
|
||||||
|
mFilteredBookmarks.clear();
|
||||||
|
mSearchTerm = "";
|
||||||
|
mTagFilter = "";
|
||||||
|
mBookmarkList.addAll(Arrays.asList(bookmarks));
|
||||||
|
buildCurrentView(mRootFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCurrentFolderRoot() {
|
||||||
|
return mRootFolder.equals(mCurrentFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRefreshing(boolean refresh) {
|
||||||
|
refreshLayout.setRefreshing(refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Folder getCurrentFolder() {
|
||||||
|
if(mCurrentFolder == null) {
|
||||||
|
Log.e("TAG", "gcf" + mRootFolder.getTitle());
|
||||||
|
return mRootFolder;
|
||||||
|
}
|
||||||
|
Log.e("TAG", "gcf" + mCurrentFolder.getTitle());
|
||||||
|
return mCurrentFolder;
|
||||||
|
}
|
||||||
|
}
|
||||||
162
app/src/main/java/org/schabi/ocbookmarks/EditBookmarkDialog.java
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
package org.schabi.ocbookmarks;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import org.schabi.ocbookmarks.REST.model.Bookmark;
|
||||||
|
import org.schabi.ocbookmarks.listener.BookmarkListener;
|
||||||
|
import org.schabi.ocbookmarks.ui.TagsRecyclerViewAdapter;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
|
||||||
|
public class EditBookmarkDialog {
|
||||||
|
ArrayList<String> tagList = new ArrayList<>();
|
||||||
|
private static final String logTAG = "ocbookmarks";
|
||||||
|
Bookmark bookmark;
|
||||||
|
String title = "";
|
||||||
|
String url = "";
|
||||||
|
|
||||||
|
private BookmarkListener onBookmarkChangedListener;
|
||||||
|
|
||||||
|
public void newBookmark(final String title, final String url) {
|
||||||
|
this.title = title;
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlertDialog getDialog(final Activity context, Bookmark b, BookmarkListener listener) {
|
||||||
|
onBookmarkChangedListener = listener;
|
||||||
|
|
||||||
|
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
View view = inflater.inflate(R.layout.edit_bookmark_dialog, null);
|
||||||
|
final Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar);
|
||||||
|
final EditText urlInput = (EditText) view.findViewById(R.id.urlInput);
|
||||||
|
final EditText titleInput = (EditText) view.findViewById(R.id.titleInput);
|
||||||
|
final EditText descriptionInput = (EditText) view.findViewById(R.id.descriptionInput);
|
||||||
|
|
||||||
|
String dialogTitle = null;
|
||||||
|
|
||||||
|
if(b == null) {
|
||||||
|
dialogTitle = "Add bookmark";
|
||||||
|
bookmark = Bookmark.emptyInstance();
|
||||||
|
bookmark.setTitle(title);
|
||||||
|
bookmark.setUrl(url);
|
||||||
|
} else {
|
||||||
|
dialogTitle = "Edit bookmark";
|
||||||
|
bookmark = b;
|
||||||
|
}
|
||||||
|
urlInput.setText(bookmark.getUrl());
|
||||||
|
titleInput.setText(bookmark.getTitle());
|
||||||
|
descriptionInput.setText(bookmark.getDescription());
|
||||||
|
|
||||||
|
for(String tag : bookmark.getTags()) {
|
||||||
|
tagList.add(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
toolbar.setTitle(dialogTitle);
|
||||||
|
toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white_24dp);
|
||||||
|
toolbar.inflateMenu(R.menu.edit_bookmark_menu);
|
||||||
|
|
||||||
|
final AlertDialog dialog = new AlertDialog.Builder(context)
|
||||||
|
.setCancelable(true)
|
||||||
|
.setView(view)
|
||||||
|
.create();
|
||||||
|
|
||||||
|
|
||||||
|
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onMenuItemClick(MenuItem item) {
|
||||||
|
//Here Actually we are saving Data to NextCloud
|
||||||
|
if(item.getItemId() == R.id.save_menu) {
|
||||||
|
bookmark.setUrl(urlInput.getText().toString());
|
||||||
|
bookmark.setTitle(titleInput.getText().toString());
|
||||||
|
bookmark.setDescription(descriptionInput.getText().toString());
|
||||||
|
|
||||||
|
if(bookmark.getUrl().isEmpty()){
|
||||||
|
Toast.makeText(context, R.string.no_url_entered, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bookmark.setTags(tagList);
|
||||||
|
if (onBookmarkChangedListener != null) {
|
||||||
|
onBookmarkChangedListener.bookmarkChanged(bookmark);
|
||||||
|
}
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// setup recycler view
|
||||||
|
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.tag_recycler_view);
|
||||||
|
final TagsRecyclerViewAdapter adapter = new TagsRecyclerViewAdapter(context, true, tagList);
|
||||||
|
adapter.setOnTagDeletedListener(new TagsRecyclerViewAdapter.OnTagDeletedListener() {
|
||||||
|
@Override
|
||||||
|
public void onTagDeleted(String tag) {
|
||||||
|
tagList.remove(tag);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
adapter.setOnTagEditedListener(new TagsRecyclerViewAdapter.OnTagEditedListener() {
|
||||||
|
@Override
|
||||||
|
public void onTagEdited(String oldTag, String newTag) {
|
||||||
|
if(newTag.isEmpty()) {
|
||||||
|
tagList.remove(oldTag);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newTag != oldTag) {
|
||||||
|
int oldTagPos = tagList.indexOf(oldTag);
|
||||||
|
if (oldTagPos >= 0) {
|
||||||
|
tagList.set(tagList.indexOf(oldTag), newTag);
|
||||||
|
}
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
recyclerView.setAdapter(adapter);
|
||||||
|
recyclerView.setLayoutManager(new GridLayoutManager(context, 2));
|
||||||
|
|
||||||
|
fixTitlebarColor(toolbar, context);
|
||||||
|
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fixTitlebarColor(Toolbar toolbar, Context context) {
|
||||||
|
int textColor = 0;
|
||||||
|
if(SDK_INT <= 23) {
|
||||||
|
textColor = Color.parseColor("#ffffffff");
|
||||||
|
} else {
|
||||||
|
textColor = context.getColor(R.color.editTitlebarTextColor);
|
||||||
|
}
|
||||||
|
toolbar.setTitleTextColor(textColor);
|
||||||
|
TextView saveItem = (TextView) toolbar.findViewById(R.id.save_menu);
|
||||||
|
saveItem.setTextColor(textColor);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
193
app/src/main/java/org/schabi/ocbookmarks/LoginAcitivty.java
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
package org.schabi.ocbookmarks;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import com.nextcloud.android.sso.AccountImporter;
|
||||||
|
import com.nextcloud.android.sso.BuildConfig;
|
||||||
|
import com.nextcloud.android.sso.api.NextcloudAPI;
|
||||||
|
import com.nextcloud.android.sso.exceptions.AccountImportCancelledException;
|
||||||
|
import com.nextcloud.android.sso.exceptions.AndroidGetAccountsPermissionNotGranted;
|
||||||
|
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
|
||||||
|
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotInstalledException;
|
||||||
|
import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
|
||||||
|
import com.nextcloud.android.sso.helper.SingleAccountHelper;
|
||||||
|
import com.nextcloud.android.sso.ui.UiExceptionManager;
|
||||||
|
|
||||||
|
import org.schabi.ocbookmarks.REST.OCBookmarksRestConnector;
|
||||||
|
import org.schabi.ocbookmarks.REST.RequestException;
|
||||||
|
import org.schabi.ocbookmarks.api.LoginData;
|
||||||
|
import org.schabi.ocbookmarks.api.SSOUtil;
|
||||||
|
|
||||||
|
|
||||||
|
public class LoginAcitivty extends AppCompatActivity {
|
||||||
|
|
||||||
|
// reply info
|
||||||
|
private static final int OK = 0;
|
||||||
|
private static final int CONNECTION_FAIL = 1;
|
||||||
|
private static final int HOST_NOT_FOUND = 2;
|
||||||
|
private static final int FILE_NOT_FOUND = 3;
|
||||||
|
private static final int TIME_OUT = 4;
|
||||||
|
private static final int SSO_FAILED = 5;
|
||||||
|
private static final int BOOKMARK_NOT_INSTALLED = 6;
|
||||||
|
|
||||||
|
LoginData loginData = new LoginData();
|
||||||
|
|
||||||
|
Button ssoButton;
|
||||||
|
TextView errorView;
|
||||||
|
View errorContainer;
|
||||||
|
|
||||||
|
SharedPreferences sharedPrefs;
|
||||||
|
String TAG = this.getClass().toString();
|
||||||
|
Context mContext;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_login_acitivty);
|
||||||
|
|
||||||
|
mContext = this;
|
||||||
|
ssoButton = findViewById(R.id.ssoButton);
|
||||||
|
errorView = findViewById(R.id.loginErrorView);
|
||||||
|
errorContainer = findViewById(R.id.errorContainer);
|
||||||
|
|
||||||
|
sharedPrefs = getSharedPreferences(getPackageName(), Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
ssoButton.setOnClickListener(v -> {
|
||||||
|
try {
|
||||||
|
AccountImporter.pickNewAccount(LoginAcitivty.this);
|
||||||
|
} catch (NextcloudFilesAppNotInstalledException | AndroidGetAccountsPermissionNotGranted e) {
|
||||||
|
UiExceptionManager.showDialogForException(LoginAcitivty.this, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
checkIfSSOIsDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
checkIfSSOIsDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
|
||||||
|
try {
|
||||||
|
AccountImporter.onActivityResult(requestCode, resultCode, data, this, (account) -> {
|
||||||
|
|
||||||
|
Log.d(TAG, "Login Attempt: "+account.name);
|
||||||
|
SingleAccountHelper.setCurrentAccount(this.getApplicationContext(), account.name);
|
||||||
|
loginData.ssologin = true;
|
||||||
|
checkIfSSOIsDone();
|
||||||
|
});
|
||||||
|
} catch (AccountImportCancelledException e) {
|
||||||
|
Log.i("log", "Account import has been canceled.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void checkIfSSOIsDone() {
|
||||||
|
try {
|
||||||
|
SingleAccountHelper.getCurrentSingleSignOnAccount(this.getApplicationContext());
|
||||||
|
// If we pass here, we do have an account set and can continue.
|
||||||
|
|
||||||
|
TestLoginTask testLoginTask = new TestLoginTask();
|
||||||
|
testLoginTask.execute(loginData);
|
||||||
|
} catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
|
||||||
|
Log.i(TAG, "No Account available. Please log in.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressLint("ResourceType")
|
||||||
|
private void storeLogin(LoginData loginData) {
|
||||||
|
SharedPreferences.Editor editor = sharedPrefs.edit();
|
||||||
|
editor.putBoolean(getString(R.string.ssologin), loginData.ssologin);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestLoginTask extends AsyncTask<LoginData, Void, Integer> {
|
||||||
|
protected Integer doInBackground(LoginData... loginDatas) {
|
||||||
|
NextcloudAPI nextcloudAPI = null;
|
||||||
|
if (loginData.ssologin) {
|
||||||
|
try {
|
||||||
|
nextcloudAPI = SSOUtil.getNextcloudAPI(LoginAcitivty.this, SingleAccountHelper.getCurrentSingleSignOnAccount(LoginAcitivty.this));
|
||||||
|
} catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return SSO_FAILED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OCBookmarksRestConnector connector = new OCBookmarksRestConnector(nextcloudAPI);
|
||||||
|
try {
|
||||||
|
connector.testAPI();
|
||||||
|
return OK;
|
||||||
|
} catch (RequestException re) {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
re.printStackTrace();
|
||||||
|
}
|
||||||
|
if(re.getError() == RequestException.ERROR.BOOKMARK_NOT_INSTALLED) {
|
||||||
|
return BOOKMARK_NOT_INSTALLED;
|
||||||
|
}
|
||||||
|
if (re.getMessage().contains("FileNotFound")) {
|
||||||
|
return FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
if (re.getMessage().contains("UnknownHost")) {
|
||||||
|
return HOST_NOT_FOUND;
|
||||||
|
}
|
||||||
|
if (re.getMessage().contains("SocketTimeout")) {
|
||||||
|
return TIME_OUT;
|
||||||
|
}
|
||||||
|
return CONNECTION_FAIL;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return CONNECTION_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onPostExecute(Integer result) {
|
||||||
|
if (result == OK) {
|
||||||
|
storeLogin(loginData);
|
||||||
|
Intent intent = new Intent(mContext, MainActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
} else {
|
||||||
|
errorContainer.setVisibility(View.VISIBLE);
|
||||||
|
SSOUtil.invalidateAPICache();
|
||||||
|
SingleAccountHelper.setCurrentAccount(LoginAcitivty.this, null);
|
||||||
|
switch (result) {
|
||||||
|
case CONNECTION_FAIL:
|
||||||
|
errorView.setText(getString(R.string.connection_failed_login));
|
||||||
|
break;
|
||||||
|
case HOST_NOT_FOUND:
|
||||||
|
errorView.setText(getString(R.string.login_host_not_found));
|
||||||
|
break;
|
||||||
|
case FILE_NOT_FOUND:
|
||||||
|
errorView.setText(getString(R.string.login_failed));
|
||||||
|
break;
|
||||||
|
case TIME_OUT:
|
||||||
|
errorView.setText(getString(R.string.login_timeout));
|
||||||
|
break;
|
||||||
|
case SSO_FAILED:
|
||||||
|
errorView.setText(getString(R.string.sso_failed));
|
||||||
|
break;
|
||||||
|
case BOOKMARK_NOT_INSTALLED:
|
||||||
|
errorView.setText(R.string.login_error_no_bookmarks_api);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
513
app/src/main/java/org/schabi/ocbookmarks/MainActivity.java
Normal file
|
|
@ -0,0 +1,513 @@
|
||||||
|
package org.schabi.ocbookmarks;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.SubMenu;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.drawerlayout.widget.DrawerLayout;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
import com.google.android.material.navigation.NavigationView;
|
||||||
|
import com.google.android.material.navigation.NavigationView.OnNavigationItemSelectedListener;
|
||||||
|
import com.nextcloud.android.sso.AccountImporter;
|
||||||
|
import com.nextcloud.android.sso.BuildConfig;
|
||||||
|
import com.nextcloud.android.sso.api.NextcloudAPI;
|
||||||
|
import com.nextcloud.android.sso.exceptions.AndroidGetAccountsPermissionNotGranted;
|
||||||
|
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
|
||||||
|
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotInstalledException;
|
||||||
|
import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
|
||||||
|
import com.nextcloud.android.sso.exceptions.SSOException;
|
||||||
|
import com.nextcloud.android.sso.helper.SingleAccountHelper;
|
||||||
|
import com.nextcloud.android.sso.model.SingleSignOnAccount;
|
||||||
|
import com.nextcloud.android.sso.ui.UiExceptionManager;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.schabi.ocbookmarks.REST.model.Bookmark;
|
||||||
|
import org.schabi.ocbookmarks.REST.OCBookmarksRestConnector;
|
||||||
|
import org.schabi.ocbookmarks.REST.model.Folder;
|
||||||
|
import org.schabi.ocbookmarks.api.LoginData;
|
||||||
|
import org.schabi.ocbookmarks.api.SSOUtil;
|
||||||
|
import org.schabi.ocbookmarks.listener.BookmarkListener;
|
||||||
|
import org.schabi.ocbookmarks.listener.OnRequestReloadListener;
|
||||||
|
import org.schabi.ocbookmarks.ui.IconHandler;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
|
||||||
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private static final String DATA_FILE_NAME = "data.json";
|
||||||
|
private static final String DATA_BACKUP_FILE_NAME = "data-backup.json";
|
||||||
|
private static final int TAGLIST_MIN_ID = 10;
|
||||||
|
|
||||||
|
|
||||||
|
private Toolbar mToolbar;
|
||||||
|
|
||||||
|
private NextcloudAPI mNextcloudAPI = null;
|
||||||
|
|
||||||
|
private static final String BOOKMARK_FRAGMENT = "bookmark_fragment";
|
||||||
|
private BookmarkFragment mBookmarkFragment = null;
|
||||||
|
private ProgressBar mainProgressBar;
|
||||||
|
|
||||||
|
private NavigationView navigationview;
|
||||||
|
private DrawerLayout drawerLayout;
|
||||||
|
|
||||||
|
private static final String TAG = MainActivity.class.toString();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
|
//Get Navigationview and do the action
|
||||||
|
drawerLayout = findViewById(R.id.drawer_layout);
|
||||||
|
navigationview = findViewById(R.id.nvView);
|
||||||
|
navigationview.setNavigationItemSelectedListener(item -> {
|
||||||
|
int id = item.getItemId();
|
||||||
|
|
||||||
|
if(id >= TAGLIST_MIN_ID) {
|
||||||
|
String tag = item.getTitle().toString();
|
||||||
|
mBookmarkFragment.showByTag(tag);
|
||||||
|
} else {
|
||||||
|
mBookmarkFragment.releaseTag();
|
||||||
|
}
|
||||||
|
drawerLayout.closeDrawer(this.navigationview);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
mToolbar = findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(mToolbar);
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
getSupportActionBar().setHomeButtonEnabled(true);
|
||||||
|
|
||||||
|
ActionBarDrawerToggle drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.nav_open, R.string.nav_close);
|
||||||
|
drawerLayout.addDrawerListener(drawerToggle);
|
||||||
|
drawerToggle.syncState();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
FloatingActionButton fab = findViewById(R.id.fab);
|
||||||
|
fab.setOnClickListener(view -> {
|
||||||
|
EditBookmarkDialog bookmarkDialog = new EditBookmarkDialog();
|
||||||
|
AlertDialog dialog = bookmarkDialog.getDialog(MainActivity.this, null, new BookmarkListener() {
|
||||||
|
@Override
|
||||||
|
public void bookmarkChanged(Bookmark bookmark) {
|
||||||
|
bookmark.setFolders(Arrays.asList(mBookmarkFragment.getCurrentFolder().getId()));
|
||||||
|
addEditBookmark(bookmark);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteBookmark(Bookmark bookmark) {}
|
||||||
|
});
|
||||||
|
dialog.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
mainProgressBar = findViewById(R.id.mainProgressBar);
|
||||||
|
|
||||||
|
|
||||||
|
if(savedInstanceState == null) {
|
||||||
|
mBookmarkFragment = new BookmarkFragment();
|
||||||
|
setupBookmarkFragmentListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareSSO();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRestoreInstanceState(Bundle inState) {
|
||||||
|
super.onRestoreInstanceState(inState);
|
||||||
|
FragmentManager fm = getSupportFragmentManager();
|
||||||
|
mBookmarkFragment = (BookmarkFragment) fm.getFragment(inState, BOOKMARK_FRAGMENT);
|
||||||
|
setupBookmarkFragmentListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
FragmentManager fm = getSupportFragmentManager();
|
||||||
|
fm.putFragment(outState, BOOKMARK_FRAGMENT, mBookmarkFragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupBookmarkFragmentListener() {
|
||||||
|
|
||||||
|
FragmentManager fm = getSupportFragmentManager();
|
||||||
|
fm.beginTransaction()
|
||||||
|
.add(R.id.container, mBookmarkFragment)
|
||||||
|
.commit();
|
||||||
|
|
||||||
|
mBookmarkFragment.setOnRequestReloadListener(new OnRequestReloadListener() {
|
||||||
|
@Override
|
||||||
|
public void requestReload() {
|
||||||
|
reloadData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mBookmarkFragment.setBookmarkListener(new BookmarkListener() {
|
||||||
|
@Override
|
||||||
|
public void bookmarkChanged(Bookmark bookmark) {
|
||||||
|
addEditBookmark(bookmark);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteBookmark(final Bookmark bookmark) {
|
||||||
|
setRefreshing(true);
|
||||||
|
new AsyncTask<Void, Void, String>() {
|
||||||
|
@Override
|
||||||
|
protected String doInBackground(Void... params) {
|
||||||
|
OCBookmarksRestConnector connector = new OCBookmarksRestConnector(mNextcloudAPI);
|
||||||
|
try {
|
||||||
|
connector.deleteBookmark(bookmark);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return getString(R.string.could_not_delete_bookmark);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(String result) {
|
||||||
|
if(result != null) {
|
||||||
|
Toast.makeText(MainActivity.this, result, Toast.LENGTH_LONG);
|
||||||
|
}
|
||||||
|
reloadData();
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addEditBookmark(final Bookmark bookmark) {
|
||||||
|
setRefreshing(true);
|
||||||
|
AsyncTask<Void, Void, String> updateTask = new AsyncTask<Void, Void, String>() {
|
||||||
|
@Override
|
||||||
|
protected String doInBackground(Void... params) {
|
||||||
|
OCBookmarksRestConnector connector = new OCBookmarksRestConnector(mNextcloudAPI);
|
||||||
|
// loginData.token,
|
||||||
|
// loginData.ssologin);
|
||||||
|
if(bookmark.getId() < 0) {
|
||||||
|
// add new bookmark
|
||||||
|
try {
|
||||||
|
connector.addBookmark(bookmark);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if(BuildConfig.DEBUG) e.printStackTrace();
|
||||||
|
return getString(R.string.could_not_add_bookmark);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
connector.editBookmark(bookmark);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if(BuildConfig.DEBUG) e.printStackTrace();
|
||||||
|
return getString(R.string.could_not_change_bookmark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(String result) {
|
||||||
|
if(result != null) {
|
||||||
|
Toast.makeText(MainActivity.this, result, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
reloadData();
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
prepareSSO();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
mBookmarkFragment.onBackHandled();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void prepareSSO() {
|
||||||
|
if(mNextcloudAPI != null) {
|
||||||
|
Log.e(TAG, "API is already set up, we can continue...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Log.e(TAG, "Prepare the API");
|
||||||
|
SingleSignOnAccount ssoa = SingleAccountHelper.getCurrentSingleSignOnAccount(this.getApplicationContext());
|
||||||
|
Log.e(TAG, "Found user: "+ssoa.name);
|
||||||
|
mNextcloudAPI = SSOUtil.getNextcloudAPI(this, ssoa);
|
||||||
|
Log.e(TAG, "Done!");
|
||||||
|
|
||||||
|
View headerView = navigationview.getHeaderView(0);
|
||||||
|
TextView userTextView= (TextView)headerView.findViewById(R.id.userTextView);
|
||||||
|
TextView urlTextView= (TextView)headerView.findViewById(R.id.urlTextView);
|
||||||
|
urlTextView.setText(ssoa.url);
|
||||||
|
userTextView.setText(ssoa.name);
|
||||||
|
reloadData();
|
||||||
|
|
||||||
|
} catch (NextcloudFilesAppAccountNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
SSOUtil.invalidateAPICache();
|
||||||
|
} catch (NoCurrentAccountSelectedException e) {
|
||||||
|
Log.e(TAG, "Exception: No Account set up, log in again!");
|
||||||
|
Log.e(TAG, e.toString());
|
||||||
|
Intent intent = new Intent(this, LoginAcitivty.class);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
// Inflate the menu; this adds items to the action bar if it is present.
|
||||||
|
getMenuInflater().inflate(R.menu.menu_main, menu);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
|
MenuItem backupDataItem = menu.findItem(R.id.action_backup_data);
|
||||||
|
if (backupDataItem != null) {
|
||||||
|
backupDataItem.setVisible(getDataFileIfExists() != null);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
// Handle action bar item clicks here. The action bar will
|
||||||
|
// automatically handle clicks on the Home/Up button, so long
|
||||||
|
// as you specify a parent activity in AndroidManifest.xml.
|
||||||
|
int id = item.getItemId();
|
||||||
|
|
||||||
|
//noinspection SimplifiableIfStatement
|
||||||
|
switch (id) {
|
||||||
|
case R.id.action_change_login:
|
||||||
|
try {
|
||||||
|
SSOUtil.invalidateAPICache();
|
||||||
|
SingleAccountHelper.setCurrentAccount(this, null);
|
||||||
|
SingleAccountHelper.reauthenticateCurrentAccount(this);
|
||||||
|
} catch (SSOException e) {
|
||||||
|
UiExceptionManager.showDialogForException(this, e);
|
||||||
|
}
|
||||||
|
Intent intent = new Intent(this, LoginAcitivty.class);
|
||||||
|
startActivity(intent);
|
||||||
|
return true;
|
||||||
|
case R.id.action_reload_icons:
|
||||||
|
IconHandler iconHandler = new IconHandler(MainActivity.this);
|
||||||
|
iconHandler.deleteAll();
|
||||||
|
reloadData();
|
||||||
|
return true;
|
||||||
|
case R.id.action_backup_data:
|
||||||
|
new BackupDataTask(this).execute();
|
||||||
|
return true;
|
||||||
|
case android.R.id.home:
|
||||||
|
if (drawerLayout.isDrawerOpen(this.navigationview)) {
|
||||||
|
drawerLayout.closeDrawer(this.navigationview);
|
||||||
|
} else {
|
||||||
|
drawerLayout.openDrawer(this.navigationview);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onBackPressed();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reloadData() {
|
||||||
|
RelodDataTask relodDataTask = new RelodDataTask();
|
||||||
|
relodDataTask.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setRefreshing(boolean refresh) {
|
||||||
|
mBookmarkFragment.setRefreshing(refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RelodDataTask extends AsyncTask<Void, Void, Bookmark[]> {
|
||||||
|
Folder root = null;
|
||||||
|
protected Bookmark[] doInBackground(Void... bla) {
|
||||||
|
try {
|
||||||
|
prepareSSO();
|
||||||
|
OCBookmarksRestConnector connector =
|
||||||
|
new OCBookmarksRestConnector(mNextcloudAPI);
|
||||||
|
//new OCBookmarksRestConnector(loginData.url, loginData.user, loginData.password,loginData.token,loginData.ssologin);
|
||||||
|
root = connector.getFolders();
|
||||||
|
|
||||||
|
JSONArray data = connector.getRawBookmarks();
|
||||||
|
storeToFile(data);
|
||||||
|
return connector.getFromRawJson(data);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if(BuildConfig.DEBUG) e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onPostExecute(Bookmark[] bookmarks) {
|
||||||
|
if(bookmarks == null) {
|
||||||
|
Toast.makeText(MainActivity.this, R.string.connectino_failed, Toast.LENGTH_SHORT)
|
||||||
|
.show();
|
||||||
|
} else {
|
||||||
|
mainProgressBar.setVisibility(View.GONE);
|
||||||
|
mBookmarkFragment.updateData(root, bookmarks);
|
||||||
|
|
||||||
|
|
||||||
|
Menu menu = navigationview.getMenu();
|
||||||
|
menu.removeGroup(R.id.tag_group);
|
||||||
|
SubMenu subMenu = menu.addSubMenu(R.id.tag_group, 1, Menu.NONE, R.string.nav_drawer_tags_header);
|
||||||
|
|
||||||
|
int i = TAGLIST_MIN_ID;
|
||||||
|
for (String tag: Bookmark.getTagsFromBookmarks(bookmarks)) {
|
||||||
|
MenuItem menuItem = subMenu.add(i, i++, Menu.NONE, tag);
|
||||||
|
menuItem.setIcon(R.drawable.ic_tag);
|
||||||
|
}
|
||||||
|
setRefreshing(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class BackupDataTask extends AsyncTask<Void, Void, String> {
|
||||||
|
private WeakReference<MainActivity> activityReference;
|
||||||
|
|
||||||
|
BackupDataTask(MainActivity mainActivity) {
|
||||||
|
this.activityReference = new WeakReference<>(mainActivity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String doInBackground(Void... voids) {
|
||||||
|
final MainActivity mainActivity = activityReference.get();
|
||||||
|
if (mainActivity == null || mainActivity.isFinishing()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final File dataFile = mainActivity.getDataFileIfExists();
|
||||||
|
if (dataFile == null) {
|
||||||
|
Log.e(this.getClass().getName(), DATA_FILE_NAME + " does not exist");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final File backupDir = mainActivity.getExternalFilesDir(null);
|
||||||
|
if (backupDir == null) {
|
||||||
|
Log.e(this.getClass().getName(), "External storage not available");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final File backupFile = new File(backupDir, DATA_BACKUP_FILE_NAME);
|
||||||
|
if (backupFile.exists() && !backupFile.delete()) {
|
||||||
|
Log.e(this.getClass().getName(), "Existing backup file could not be deleted");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
doCopy(dataFile, backupFile);
|
||||||
|
return backupFile.getAbsolutePath();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(this.getClass().getName(), "Error creating backup of " + dataFile, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(String backupFilePath) {
|
||||||
|
final MainActivity mainActivity = activityReference.get();
|
||||||
|
if (mainActivity == null || mainActivity.isFinishing()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (backupFilePath != null) {
|
||||||
|
mainActivity.mainProgressBar.setVisibility(View.GONE);
|
||||||
|
mainActivity.setRefreshing(false);
|
||||||
|
Toast.makeText(
|
||||||
|
mainActivity,
|
||||||
|
mainActivity.getApplicationContext().getString(
|
||||||
|
R.string.backup_successful,
|
||||||
|
backupFilePath),
|
||||||
|
Toast.LENGTH_LONG)
|
||||||
|
.show();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
mainActivity,
|
||||||
|
R.string.backup_failed,
|
||||||
|
Toast.LENGTH_SHORT)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doCopy(final File dataFile, final File backupFile) throws Exception {
|
||||||
|
try (final InputStream fis = new FileInputStream(dataFile);
|
||||||
|
final OutputStream fos = new FileOutputStream(backupFile)) {
|
||||||
|
final byte[] buffer = new byte[1024];
|
||||||
|
|
||||||
|
int length;
|
||||||
|
while ((length = fis.read(buffer)) > 0) {
|
||||||
|
fos.write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
fos.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getDataFileIfExists() {
|
||||||
|
final File dataFile = new File(getFilesDir() + File.pathSeparator + DATA_FILE_NAME);
|
||||||
|
return dataFile.exists() ? dataFile : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadFromFile() {
|
||||||
|
File jsonFile = getDataFileIfExists();
|
||||||
|
if (jsonFile != null) {
|
||||||
|
StringBuilder text = new StringBuilder();
|
||||||
|
mainProgressBar.setVisibility(View.GONE);
|
||||||
|
try {
|
||||||
|
BufferedReader br = new BufferedReader(new FileReader(jsonFile));
|
||||||
|
String line;
|
||||||
|
while ((line = br.readLine()) != null) {
|
||||||
|
text.append(line);
|
||||||
|
text.append("\n");
|
||||||
|
}
|
||||||
|
br.close();
|
||||||
|
OCBookmarksRestConnector connector = new OCBookmarksRestConnector(mNextcloudAPI);
|
||||||
|
Bookmark[] bookmarks = connector.getFromRawJson(new JSONArray(text.toString()));
|
||||||
|
mBookmarkFragment.updateData(connector.getFolders(), bookmarks);
|
||||||
|
} catch (JSONException je) {
|
||||||
|
if (BuildConfig.DEBUG) je.printStackTrace();
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (BuildConfig.DEBUG) e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void storeToFile(JSONArray data) {
|
||||||
|
try {
|
||||||
|
FileOutputStream jsonFile =
|
||||||
|
new FileOutputStream(getFilesDir() + File.pathSeparator + DATA_FILE_NAME);
|
||||||
|
jsonFile.write(data.toString().getBytes());
|
||||||
|
jsonFile.flush();
|
||||||
|
jsonFile.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (BuildConfig.DEBUG) e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
package org.schabi.ocbookmarks;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import com.nextcloud.android.sso.BuildConfig;
|
||||||
|
import com.nextcloud.android.sso.api.NextcloudAPI;
|
||||||
|
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
|
||||||
|
import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
|
||||||
|
import com.nextcloud.android.sso.helper.SingleAccountHelper;
|
||||||
|
import com.nextcloud.android.sso.model.SingleSignOnAccount;
|
||||||
|
|
||||||
|
import org.schabi.ocbookmarks.REST.model.Bookmark;
|
||||||
|
import org.schabi.ocbookmarks.REST.OCBookmarksRestConnector;
|
||||||
|
import org.schabi.ocbookmarks.api.SSOUtil;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
public class QuickaddBookmarkActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.add_bookmark_activity);
|
||||||
|
setTitle("");
|
||||||
|
|
||||||
|
|
||||||
|
TextView stateView = findViewById(R.id.adding_text_view);
|
||||||
|
ProgressBar progressView = findViewById(R.id.progressView);
|
||||||
|
ImageView successView = findViewById(R.id.successView);
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
String title = intent.getStringExtra(Intent.EXTRA_SUBJECT);
|
||||||
|
String url = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||||
|
|
||||||
|
Bookmark bookmark = Bookmark.emptyInstance();
|
||||||
|
bookmark.setTitle(title);
|
||||||
|
bookmark.setUrl(url);
|
||||||
|
// bookmark.setTags(new String[] {this.getString(R.string.share_target_quick)});
|
||||||
|
ArrayList<String> list;
|
||||||
|
list = new ArrayList<String>();
|
||||||
|
list.add(String.valueOf(R.string.share_target_quick));
|
||||||
|
bookmark.setTags(list);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
Handler handler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
|
executor.execute(() -> {
|
||||||
|
|
||||||
|
String result = getString(R.string.bookmark_saved);
|
||||||
|
NextcloudAPI nextcloudAPI = null;
|
||||||
|
try {
|
||||||
|
SingleSignOnAccount ssoa = SingleAccountHelper.getCurrentSingleSignOnAccount(QuickaddBookmarkActivity.this.getApplicationContext());
|
||||||
|
nextcloudAPI = SSOUtil.getNextcloudAPI(QuickaddBookmarkActivity.this, ssoa);
|
||||||
|
} catch (NextcloudFilesAppAccountNotFoundException e) {
|
||||||
|
Toast.makeText(QuickaddBookmarkActivity.this,
|
||||||
|
R.string.nextcloud_files_app_account_not_found_message,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch ( NoCurrentAccountSelectedException e) {
|
||||||
|
Toast.makeText(QuickaddBookmarkActivity.this,
|
||||||
|
R.string.no_current_account_selected_exception_message,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
OCBookmarksRestConnector connector = new OCBookmarksRestConnector(nextcloudAPI);
|
||||||
|
try {
|
||||||
|
connector.addBookmark(bookmark);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if(BuildConfig.DEBUG) e.printStackTrace();
|
||||||
|
result = getString(R.string.could_not_add_bookmark);
|
||||||
|
}
|
||||||
|
String finalResult = result;
|
||||||
|
handler.post(() -> {
|
||||||
|
progressView.setVisibility(View.GONE);
|
||||||
|
successView.setVisibility(View.VISIBLE);
|
||||||
|
stateView.setText(finalResult);
|
||||||
|
|
||||||
|
Toast.makeText(QuickaddBookmarkActivity.this,
|
||||||
|
finalResult,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,426 @@
|
||||||
|
package org.schabi.ocbookmarks.REST;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.nextcloud.android.sso.QueryParam;
|
||||||
|
import com.nextcloud.android.sso.aidl.NextcloudRequest;
|
||||||
|
import com.nextcloud.android.sso.api.NextcloudAPI;
|
||||||
|
import com.nextcloud.android.sso.api.Response;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.schabi.ocbookmarks.REST.model.Bookmark;
|
||||||
|
import org.schabi.ocbookmarks.REST.model.Folder;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by the-scrabi on 14.05.17.
|
||||||
|
* Modified by @dasbiswajit on 14.04.2019
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
public class OCBookmarksRestConnector {
|
||||||
|
private String apiRootUrl = "";
|
||||||
|
private final NextcloudAPI nextcloudAPI;
|
||||||
|
String TAG = this.getClass().toString();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param nextcloudAPI will be used if not null instead of traditional user / password authentication
|
||||||
|
*/
|
||||||
|
public OCBookmarksRestConnector(NextcloudAPI nextcloudAPI) {
|
||||||
|
this.nextcloudAPI = nextcloudAPI;
|
||||||
|
// host is defined by SingleSignOnAccount
|
||||||
|
|
||||||
|
apiRootUrl = "/index.php/apps/bookmarks/public/rest/v2";
|
||||||
|
Log.e(TAG,"API Root-Url: "+apiRootUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sending SSO fancy way
|
||||||
|
*/
|
||||||
|
public JSONObject sendWithSSO(@NonNull String methode, @NonNull String relativeUrl, @NonNull Collection<QueryParam> parameter) throws RequestException {
|
||||||
|
if (this.nextcloudAPI == null) {
|
||||||
|
Log.e(TAG,"API not set up.");
|
||||||
|
throw new RequestException("Trying to send request via SSO, but API is null.", RequestException.ERROR.API_NOT_SET_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i(TAG,"API is already set up");
|
||||||
|
NextcloudRequest request = new NextcloudRequest
|
||||||
|
.Builder()
|
||||||
|
.setMethod(methode)
|
||||||
|
.setUrl(apiRootUrl + relativeUrl)
|
||||||
|
.setParameter(parameter)
|
||||||
|
.build();
|
||||||
|
try {
|
||||||
|
Response response = nextcloudAPI.performNetworkRequestV2(request);
|
||||||
|
|
||||||
|
Log.e(TAG, response.getPlainHeaders().toString());
|
||||||
|
|
||||||
|
final StringBuilder result = new StringBuilder();
|
||||||
|
final BufferedReader rd = new BufferedReader(new InputStreamReader(response.getBody()));
|
||||||
|
String line;
|
||||||
|
while ((line = rd.readLine()) != null) {
|
||||||
|
result.append(line);
|
||||||
|
}
|
||||||
|
response.getBody().close();
|
||||||
|
|
||||||
|
return parseJson(methode, apiRootUrl + relativeUrl, result.toString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
if(e.getMessage().contains("status-code: 302")) {
|
||||||
|
throw new RequestException(RequestException.ERROR.BOOKMARK_NOT_INSTALLED);
|
||||||
|
}
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new RequestException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSONObject parseJson(String methode, String url, String response) throws RequestException {
|
||||||
|
|
||||||
|
JSONObject data = null;
|
||||||
|
if("GET".equals(methode) && url.endsWith("/folder")) {
|
||||||
|
JSONObject array = null;
|
||||||
|
try {
|
||||||
|
data = new JSONObject(response);
|
||||||
|
// data = new JSONObject();
|
||||||
|
// data.put("data", array);
|
||||||
|
} catch (JSONException je) {
|
||||||
|
throw new RequestException("Parsing error, maybe owncloud does not support bookmark api", je);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
if ("GET".equals(methode) && url.endsWith("/tag")) {
|
||||||
|
// we have to handle GET /tag different:
|
||||||
|
// https://github.com/nextcloud/bookmarks#list-all-tags
|
||||||
|
JSONArray array = null;
|
||||||
|
try {
|
||||||
|
array = new JSONArray(response);
|
||||||
|
data = new JSONObject();
|
||||||
|
data.put("data", array);
|
||||||
|
} catch (JSONException je) {
|
||||||
|
throw new RequestException("Parsing error, maybe owncloud does not support bookmark api", je);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
} else if ("PUT".equals(methode)) {
|
||||||
|
try {
|
||||||
|
data = new JSONObject(response);
|
||||||
|
return data.getJSONObject("item");
|
||||||
|
} catch (JSONException je) {
|
||||||
|
throw new RequestException("Parsing error, maybe owncloud does not support bookmark api", je);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = new JSONObject(response);
|
||||||
|
} catch (JSONException je) {
|
||||||
|
throw new RequestException("Parsing error, maybe owncloud does not support bookmark api", je);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!data.getString("status").equals("success")) {
|
||||||
|
throw new RequestException("Error bad request: " + url);
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new RequestException("Error bad request: " + url, e);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// +++++++++++++++++
|
||||||
|
// + bookmarks +
|
||||||
|
// +++++++++++++++++
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a function to get the first bookmarks.
|
||||||
|
* This assures us that the bookmark app is avaliable on the server
|
||||||
|
* while not gathering every bookmark. Only one query is made.
|
||||||
|
* @return
|
||||||
|
* @throws RequestException
|
||||||
|
*/
|
||||||
|
public JSONArray testAPI() throws RequestException {
|
||||||
|
try {
|
||||||
|
JSONArray bookmarks = new JSONArray();
|
||||||
|
|
||||||
|
Collection<QueryParam> parameter = new ArrayList<>();
|
||||||
|
parameter.add(new QueryParam("page", "1"));
|
||||||
|
parameter.add(new QueryParam("limit", "10"));
|
||||||
|
JSONObject now = sendWithSSO("GET", "/bookmark", parameter);
|
||||||
|
JSONArray data = now.getJSONArray("data");
|
||||||
|
for (int i = 0; i < data.length(); i++) {
|
||||||
|
JSONObject bm = (JSONObject) data.get(i);
|
||||||
|
bookmarks.put(bm);
|
||||||
|
}
|
||||||
|
return bookmarks;
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new RequestException("Could not parse data", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public JSONArray getRawBookmarks() throws RequestException {
|
||||||
|
try {
|
||||||
|
JSONArray bookmarks = new JSONArray();
|
||||||
|
int pageSize = 300;
|
||||||
|
int resultLength = pageSize;
|
||||||
|
int page = 0;
|
||||||
|
|
||||||
|
while (resultLength == pageSize) {
|
||||||
|
Collection<QueryParam> parameter = new ArrayList<>();
|
||||||
|
parameter.add(new QueryParam("page", String.valueOf(page++)));
|
||||||
|
parameter.add(new QueryParam("limit", "300"));
|
||||||
|
JSONObject now = sendWithSSO("GET", "/bookmark", parameter);
|
||||||
|
JSONArray data = now.getJSONArray("data");
|
||||||
|
for (int i = 0; i < data.length(); i++) {
|
||||||
|
JSONObject bm = (JSONObject) data.get(i);
|
||||||
|
bookmarks.put(bm);
|
||||||
|
}
|
||||||
|
resultLength = bookmarks.length();
|
||||||
|
}
|
||||||
|
return bookmarks;
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new RequestException("Could not parse data", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bookmark[] getFromRawJson(JSONArray data) throws RequestException {
|
||||||
|
try {
|
||||||
|
Bookmark[] bookmarks = new Bookmark[data.length()];
|
||||||
|
for (int i = 0; i < data.length(); i++) {
|
||||||
|
JSONObject bookmark = data.getJSONObject(i);
|
||||||
|
bookmarks[i] = getBookmarkFromJsonO(bookmark);
|
||||||
|
}
|
||||||
|
return bookmarks;
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new RequestException("Could not parse data", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bookmark[] getBookmarks() throws RequestException {
|
||||||
|
JSONArray data = getRawBookmarks();
|
||||||
|
return getFromRawJson(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Bookmark getBookmarkFromJsonO(JSONObject jBookmark) throws RequestException {
|
||||||
|
|
||||||
|
ArrayList<String> tags;
|
||||||
|
try {
|
||||||
|
JSONArray jTags = jBookmark.getJSONArray("tags");
|
||||||
|
tags = new ArrayList<>();
|
||||||
|
for (int j = 0; j < jTags.length(); j++) {
|
||||||
|
tags.add(jTags.getString(j));
|
||||||
|
}
|
||||||
|
} catch (JSONException je) {
|
||||||
|
throw new RequestException("Could not parse array", je);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Integer> folders;
|
||||||
|
try {
|
||||||
|
JSONArray jfolders = jBookmark.getJSONArray("folders");
|
||||||
|
folders = new ArrayList<>(jfolders.length());
|
||||||
|
for (int j = 0; j < jfolders.length(); j++) {
|
||||||
|
folders.add(jfolders.getInt(j));
|
||||||
|
}
|
||||||
|
} catch (JSONException je) {
|
||||||
|
throw new RequestException("Could not parse folder array", je);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Todo: another api error we need to fix
|
||||||
|
if (tags.size() == 1 && tags.get(0).isEmpty()) {
|
||||||
|
tags = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Bookmark.emptyInstance()
|
||||||
|
.setId(jBookmark.getInt("id"))
|
||||||
|
.setUrl(jBookmark.getString("url"))
|
||||||
|
.setTitle(jBookmark.getString("title"))
|
||||||
|
.setUserId(jBookmark.getString("userId"))
|
||||||
|
.setDescription(jBookmark.getString("description"))
|
||||||
|
//.setPublic(false) //dummy to false for version 2 to 3 upgrade.
|
||||||
|
// .setAdded(new Date(jBookmark.getLong("added") * 1000))
|
||||||
|
.setLastModified(new Date(jBookmark.getLong("lastmodified") * 1000))
|
||||||
|
.setClickcount(jBookmark.getInt("clickcount"))
|
||||||
|
.setTags(tags)
|
||||||
|
.setFolders(folders);
|
||||||
|
} catch (JSONException je) {
|
||||||
|
throw new RequestException("Could not gather all data", je);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
private String createBookmarkParameterString(Bookmark bookmark) {
|
||||||
|
if (!bookmark.getTitle().isEmpty() && !bookmark.getUrl().startsWith("http")) {
|
||||||
|
//tittle can only be set if the sheme is given
|
||||||
|
//this is a bug we need to fix
|
||||||
|
bookmark.setUrl("http://" + bookmark.getUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = "?url=" + URLEncoder.encode(bookmark.getUrl());
|
||||||
|
|
||||||
|
if (!bookmark.getTitle().isEmpty()) {
|
||||||
|
url += "&title=" + URLEncoder.encode(bookmark.getTitle());
|
||||||
|
}
|
||||||
|
if (!bookmark.getDescription().isEmpty()) {
|
||||||
|
url += "&description=" + URLEncoder.encode(bookmark.getDescription());
|
||||||
|
}
|
||||||
|
// if(bookmark.isPublic()) {
|
||||||
|
// url += "&is_public=1";
|
||||||
|
// }
|
||||||
|
|
||||||
|
for (String tag : bookmark.getTags()) {
|
||||||
|
url += "&" + URLEncoder.encode("tags[]") + "=" + URLEncoder.encode(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<QueryParam> createBookmarkParameter(Bookmark bookmark) {
|
||||||
|
final Collection<QueryParam> parameter = new ArrayList<>(3 + bookmark.getTags().size());
|
||||||
|
if (!bookmark.getTitle().isEmpty() && !bookmark.getUrl().startsWith("http")) {
|
||||||
|
// Title can only be set if the sheme is given
|
||||||
|
// This is a bug we need to fix
|
||||||
|
bookmark.setUrl("http://" + bookmark.getUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
parameter.add(new QueryParam("url", bookmark.getUrl()));
|
||||||
|
parameter.add(new QueryParam("title", bookmark.getTitle()));
|
||||||
|
parameter.add(new QueryParam("description", bookmark.getDescription()));
|
||||||
|
|
||||||
|
for (String tag : bookmark.getTags()) {
|
||||||
|
parameter.add(new QueryParam("tags[]", tag));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Integer folder : bookmark.getFolders()) {
|
||||||
|
parameter.add(new QueryParam("folders[]", folder.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bookmark addBookmark(Bookmark bookmark) throws RequestException {
|
||||||
|
try {
|
||||||
|
if (bookmark.getId() == -1) {
|
||||||
|
JSONObject reply;
|
||||||
|
reply = sendWithSSO("POST", "/bookmark", createBookmarkParameter(bookmark));
|
||||||
|
|
||||||
|
Log.e(TAG, "Bookmark Creation Reply: "+reply);
|
||||||
|
return getBookmarkFromJsonO(reply.getJSONObject("item"));
|
||||||
|
} else {
|
||||||
|
throw new RequestException("Bookmark id is set. Maybe this bookmark already exist: id=" + bookmark.getId());
|
||||||
|
}
|
||||||
|
} catch (JSONException je) {
|
||||||
|
throw new RequestException("Could not parse reply", je);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteBookmark(Bookmark bookmark) throws RequestException {
|
||||||
|
if (bookmark.getId() < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendWithSSO("DELETE", "/bookmark/" + bookmark.getId(), Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bookmark editBookmark(Bookmark bookmark) throws RequestException {
|
||||||
|
return editBookmark(bookmark, bookmark.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bookmark editBookmark(Bookmark bookmark, int newRecordId) throws RequestException {
|
||||||
|
if (bookmark.getId() < 0) {
|
||||||
|
throw new RequestException("Bookmark has no valid id. Maybe you want to add a bookmark? id="
|
||||||
|
+ bookmark.getId());
|
||||||
|
}
|
||||||
|
if (bookmark.getUrl().isEmpty()) {
|
||||||
|
throw new RequestException("Bookmark has no url. Maybe you want to add a bookmark?");
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection<QueryParam> parameter = createBookmarkParameter(bookmark);
|
||||||
|
parameter.add(new QueryParam("record_id", Integer.toString(newRecordId)));
|
||||||
|
return getBookmarkFromJsonO(sendWithSSO("PUT", "/bookmark/" + bookmark.getId(), parameter));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ++++++++++++++++++
|
||||||
|
// + folders +
|
||||||
|
// ++++++++++++++++++
|
||||||
|
|
||||||
|
public Folder getFolders() throws RequestException {
|
||||||
|
try {
|
||||||
|
JSONArray data = sendWithSSO("GET", "/folder", Collections.emptyList()).getJSONArray("data");
|
||||||
|
Folder root = Folder.createEmptyRootFolder();
|
||||||
|
fillChildren(root, data);
|
||||||
|
return root;
|
||||||
|
} catch (JSONException je) {
|
||||||
|
throw new RequestException("Could not get all folders", je);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillChildren(Folder rootFolder, JSONArray children) {
|
||||||
|
if (children == null || children.length() < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<Folder> childFolderList = new ArrayList<>();
|
||||||
|
for (int i = 0; i < children.length(); i++) {
|
||||||
|
try {
|
||||||
|
JSONObject folderJson = children.getJSONObject(i);
|
||||||
|
Folder folder = new Folder();
|
||||||
|
folder.setId(folderJson.getInt("id"));
|
||||||
|
folder.setParentFolderId(folderJson.getInt("parent_folder"));
|
||||||
|
folder.setTitle(folderJson.getString("title"));
|
||||||
|
if (folderJson.has("children")) {
|
||||||
|
fillChildren(folder, folderJson.getJSONArray("children"));
|
||||||
|
}
|
||||||
|
childFolderList.add(folder);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rootFolder.setChildren(childFolderList);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ++++++++++++++++++
|
||||||
|
// + tags +
|
||||||
|
// ++++++++++++++++++
|
||||||
|
|
||||||
|
public String[] getTags() throws RequestException {
|
||||||
|
try {
|
||||||
|
JSONArray data;
|
||||||
|
data = sendWithSSO("GET", "/tag", Collections.emptyList()).getJSONArray("data");
|
||||||
|
|
||||||
|
String[] tags = new String[data.length()];
|
||||||
|
for (int i = 0; i < tags.length; i++) {
|
||||||
|
tags[i] = data.getString(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tags;
|
||||||
|
} catch (JSONException je) {
|
||||||
|
throw new RequestException("Could not get all tags", je);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteTag(String tag) throws RequestException {
|
||||||
|
sendWithSSO("DELETE", "/tag", Collections.singletonList(new QueryParam("old_name", tag)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void renameTag(String oldName, String newName) throws RequestException {
|
||||||
|
final Collection<QueryParam> parameter = new ArrayList<>(2);
|
||||||
|
parameter.add(new QueryParam("old_name", oldName));
|
||||||
|
parameter.add(new QueryParam("new_name", newName));
|
||||||
|
sendWithSSO("POST", "/tag", parameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.schabi.ocbookmarks.REST;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by the-scrabi on 14.05.17.
|
||||||
|
*/
|
||||||
|
public class PermissionException extends RequestException {
|
||||||
|
PermissionException(Exception e) {
|
||||||
|
super(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package org.schabi.ocbookmarks.REST;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Todo: Phase out the String message and try to use enum.
|
||||||
|
* Reason: I think it makes handling errors more easy because we dont rely on string parsing.
|
||||||
|
* Created by the-scrabi on 14.05.17.
|
||||||
|
*/
|
||||||
|
public class RequestException extends IOException {
|
||||||
|
|
||||||
|
|
||||||
|
public enum ERROR {
|
||||||
|
UNKNOWN,
|
||||||
|
API_NOT_SET_UP,
|
||||||
|
FILE_NOT_FOUND,
|
||||||
|
HOST_NOT_FOUND,
|
||||||
|
TIME_OUT,
|
||||||
|
BOOKMARK_NOT_INSTALLED
|
||||||
|
}
|
||||||
|
|
||||||
|
private ERROR mError = ERROR.UNKNOWN;
|
||||||
|
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
RequestException(String message, Exception e) {
|
||||||
|
super(message, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestException(Exception e) {
|
||||||
|
super(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
RequestException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
RequestException(String message, ERROR error) {
|
||||||
|
super(message);
|
||||||
|
mError = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestException(ERROR error) {
|
||||||
|
super(error.name());
|
||||||
|
mError = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ERROR getError() {
|
||||||
|
return mError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,201 @@
|
||||||
|
package org.schabi.ocbookmarks.REST.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by the-scrabi on 14.05.17.
|
||||||
|
* modified by bisasda
|
||||||
|
*/
|
||||||
|
public class Bookmark {
|
||||||
|
private int id = -1;
|
||||||
|
private String url = "";
|
||||||
|
private String title = "";
|
||||||
|
private String userId = "";
|
||||||
|
private String description = "";
|
||||||
|
// private Date added = null;
|
||||||
|
private Date lastModified = null;
|
||||||
|
private int clickcount = -1;
|
||||||
|
// private boolean isPublic = false;
|
||||||
|
private ArrayList<String> tags = new ArrayList<>();
|
||||||
|
private List<Integer> folders = new ArrayList<>();
|
||||||
|
|
||||||
|
private boolean isFolder = false;
|
||||||
|
|
||||||
|
public static Bookmark emptyInstance() {
|
||||||
|
return new Bookmark();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bookmark() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ++++++++++++++++++++
|
||||||
|
// + factory setter +
|
||||||
|
// ++++++++++++++++++++
|
||||||
|
|
||||||
|
public Bookmark setId(int id) {
|
||||||
|
this.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bookmark setUrl(String url) {
|
||||||
|
this.url = url;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bookmark setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bookmark setUserId(String userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bookmark setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// public Bookmark setAdded(Date added) {
|
||||||
|
// this.added = added;
|
||||||
|
// return this;
|
||||||
|
// }
|
||||||
|
|
||||||
|
public Bookmark setLastModified(Date lastModified) {
|
||||||
|
this.lastModified = lastModified;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bookmark setClickcount(int clickcount) {
|
||||||
|
this.clickcount = clickcount;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bookmark setTags(ArrayList<String> tags) {
|
||||||
|
this.tags = tags;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bookmark setFolders(List<Integer> folders) {
|
||||||
|
this.folders = folders;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
// public Bookmark setPublic(boolean aPublic) {
|
||||||
|
// isPublic = aPublic;
|
||||||
|
// return this;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// +++++++++++++++++++++++++
|
||||||
|
// + getter functions +
|
||||||
|
// +++++++++++++++++++++++++
|
||||||
|
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
public String getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
// public Date getAdded() {
|
||||||
|
// return added;
|
||||||
|
// }
|
||||||
|
public Date getLastModified() {
|
||||||
|
return lastModified;
|
||||||
|
}
|
||||||
|
public int getClickcount() {
|
||||||
|
return clickcount;
|
||||||
|
}
|
||||||
|
public ArrayList<String> getTags() {
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
public List<Integer> getFolders(){
|
||||||
|
return folders;
|
||||||
|
}
|
||||||
|
// public boolean isPublic() {
|
||||||
|
// return isPublic;
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String tagsString = "[";
|
||||||
|
for(String tag : tags) {
|
||||||
|
tagsString += tag + ",";
|
||||||
|
}
|
||||||
|
tagsString += "]";
|
||||||
|
|
||||||
|
String foldersString = "[";
|
||||||
|
for(int folder : folders) {
|
||||||
|
foldersString += folder + ",";
|
||||||
|
}
|
||||||
|
foldersString += "]";
|
||||||
|
|
||||||
|
return "id:" + Integer.toString(id) + "\n" +
|
||||||
|
"url:" + url + "\n" +
|
||||||
|
"title:" + title + "\n" +
|
||||||
|
"userId:" + userId + "\n" +
|
||||||
|
"description:" + description + "\n" +
|
||||||
|
// "added:" + added.toString() + "\n" +
|
||||||
|
"lastModified:" + lastModified.toString() + "\n" +
|
||||||
|
"clickount:" + clickcount + "\n" +
|
||||||
|
"tags:" + tagsString+ "\n" +
|
||||||
|
"folders:"+foldersString;
|
||||||
|
// "isPublic:" + Boolean.toString(isPublic);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String[] getTagsFromBookmarks(Bookmark[] bookmarks) {
|
||||||
|
Vector<String> tagList = new Vector<>();
|
||||||
|
for(Bookmark b : bookmarks) {
|
||||||
|
for(String tag : b.getTags()) {
|
||||||
|
if(!tagList.contains(tag)) {
|
||||||
|
tagList.add(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] returnTagList = new String[tagList.size()];
|
||||||
|
for(int i = 0; i < returnTagList.length; i++) {
|
||||||
|
returnTagList[i] = tagList.get(i);
|
||||||
|
}
|
||||||
|
return returnTagList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int[] getFoldersFromBookmarks(Bookmark[] bookmarks) {
|
||||||
|
Vector<Integer> folderList = new Vector<>();
|
||||||
|
for(Bookmark b : bookmarks) {
|
||||||
|
for(int folder : b.getFolders()) {
|
||||||
|
if(!folderList.contains(folder)) {
|
||||||
|
folderList.add(folder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] returnFolderList = new int[folderList.size()];
|
||||||
|
for(int i = 0; i < returnFolderList.length; i++) {
|
||||||
|
returnFolderList[i] = folderList.get(i);
|
||||||
|
}
|
||||||
|
return returnFolderList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFolder() {
|
||||||
|
return isFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFolder(boolean folder) {
|
||||||
|
isFolder = folder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package org.schabi.ocbookmarks.REST.model;
|
||||||
|
|
||||||
|
public class BookmarkListElement {
|
||||||
|
|
||||||
|
|
||||||
|
private Folder mFolder;
|
||||||
|
private Bookmark mBookmark;
|
||||||
|
|
||||||
|
private boolean isFolder = false;
|
||||||
|
|
||||||
|
|
||||||
|
public BookmarkListElement(Folder folder) {
|
||||||
|
isFolder = true;
|
||||||
|
mFolder = folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BookmarkListElement(Bookmark bookmark) {
|
||||||
|
isFolder = false;
|
||||||
|
mBookmark = bookmark;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isFolder() {
|
||||||
|
return isFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Folder getFolder() {
|
||||||
|
return mFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bookmark getBookmark() {
|
||||||
|
return mBookmark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
package org.schabi.ocbookmarks.REST.model;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Folder implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
// https://nextcloud-bookmarks.readthedocs.io/en/latest/folder.html#folder-model
|
||||||
|
// This is how it looks like in JSON:
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"status": "success", "data": [
|
||||||
|
{"id": 1, "title": "work", "parent_folder": -1},
|
||||||
|
{"id": 2, "title": "personal", "parent_folder": -1, "children": [
|
||||||
|
{"id": 3, "title": "garden", "parent_folder": 2},
|
||||||
|
{"id": 4, "title": "music", "parent_folder": 2}
|
||||||
|
]},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
public static final int ROOT_ID = -1;
|
||||||
|
public static final int UP_ID = -2;
|
||||||
|
|
||||||
|
|
||||||
|
private int id;
|
||||||
|
private String title;
|
||||||
|
private int parentFolderId;
|
||||||
|
private List<Folder> children = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
|
public static Folder createEmptyRootFolder() {
|
||||||
|
Folder root = new Folder();
|
||||||
|
root.setId(ROOT_ID);
|
||||||
|
root.setTitle("All Bookmarks");
|
||||||
|
root.setParentFolderId(ROOT_ID);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getParentFolderId() {
|
||||||
|
return parentFolderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParentFolderId(int parentFolderId) {
|
||||||
|
this.parentFolderId = parentFolderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Folder> getChildren() {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChildren(List<Folder> children) {
|
||||||
|
this.children = children;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
Folder folder = (Folder) o;
|
||||||
|
|
||||||
|
if (id != folder.id) return false;
|
||||||
|
if (parentFolderId != folder.parentFolderId) return false;
|
||||||
|
if (title != null ? !title.equals(folder.title) : folder.title != null) return false;
|
||||||
|
return children != null ? children.equals(folder.children) : folder.children == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = id;
|
||||||
|
result = 31 * result + (title != null ? title.hashCode() : 0);
|
||||||
|
result = 31 * result + parentFolderId;
|
||||||
|
result = 31 * result + (children != null ? children.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package org.schabi.ocbookmarks.api;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by the-scrabi on 04.06.17.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class LoginData {
|
||||||
|
public boolean ssologin;
|
||||||
|
}
|
||||||
55
app/src/main/java/org/schabi/ocbookmarks/api/SSOUtil.java
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
package org.schabi.ocbookmarks.api;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.nextcloud.android.sso.api.NextcloudAPI;
|
||||||
|
import com.nextcloud.android.sso.exceptions.TokenMismatchException;
|
||||||
|
import com.nextcloud.android.sso.model.SingleSignOnAccount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class keeps {@link NextcloudAPI} instance
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
public class SSOUtil {
|
||||||
|
|
||||||
|
private static final String TAG = SSOUtil.class.getSimpleName();
|
||||||
|
|
||||||
|
private static NextcloudAPI mNextcloudAPI;
|
||||||
|
|
||||||
|
public static NextcloudAPI getNextcloudAPI(@NonNull Context appContext, @NonNull SingleSignOnAccount ssoAccount) {
|
||||||
|
if (mNextcloudAPI != null) {
|
||||||
|
return mNextcloudAPI;
|
||||||
|
}
|
||||||
|
Log.v(TAG, "NextcloudRequest account: " + ssoAccount.name);
|
||||||
|
final NextcloudAPI nextcloudAPI = new NextcloudAPI(appContext, ssoAccount, new GsonBuilder().create(), new NextcloudAPI.ApiConnectedListener() {
|
||||||
|
@Override
|
||||||
|
public void onConnected() {
|
||||||
|
Log.i(TAG, "SSO API connected for " + ssoAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mNextcloudAPI = nextcloudAPI;
|
||||||
|
return nextcloudAPI;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates the cached {@link NextcloudAPI}
|
||||||
|
* Should be called in case a {@link TokenMismatchException} occurs.
|
||||||
|
*/
|
||||||
|
public static void invalidateAPICache() {
|
||||||
|
Log.v(TAG, "Invalidating API cache");
|
||||||
|
if (mNextcloudAPI != null) {
|
||||||
|
mNextcloudAPI.stop();
|
||||||
|
}
|
||||||
|
mNextcloudAPI = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package org.schabi.ocbookmarks.listener;
|
||||||
|
|
||||||
|
import org.schabi.ocbookmarks.REST.model.Bookmark;
|
||||||
|
|
||||||
|
public interface BookmarkListener {
|
||||||
|
void bookmarkChanged(Bookmark bookmark);
|
||||||
|
void deleteBookmark(Bookmark bookmark);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.schabi.ocbookmarks.listener
|
||||||
|
|
||||||
|
import org.schabi.ocbookmarks.REST.model.Folder
|
||||||
|
|
||||||
|
interface FolderListener {
|
||||||
|
fun changeFolderCallback(f: Folder)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package org.schabi.ocbookmarks.listener;
|
||||||
|
|
||||||
|
public interface OnRequestReloadListener {
|
||||||
|
void requestReload();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,245 @@
|
||||||
|
package org.schabi.ocbookmarks.ui;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.appcompat.widget.PopupMenu;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import org.schabi.ocbookmarks.EditBookmarkDialog;
|
||||||
|
import org.schabi.ocbookmarks.R;
|
||||||
|
import org.schabi.ocbookmarks.REST.model.Bookmark;
|
||||||
|
import org.schabi.ocbookmarks.REST.model.BookmarkListElement;
|
||||||
|
import org.schabi.ocbookmarks.REST.model.Folder;
|
||||||
|
import org.schabi.ocbookmarks.listener.BookmarkListener;
|
||||||
|
import org.schabi.ocbookmarks.listener.FolderListener;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class BookmarksRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||||
|
|
||||||
|
private final ArrayList<BookmarkListElement> mListElements;
|
||||||
|
Context mContext;
|
||||||
|
LayoutInflater mInflater;
|
||||||
|
FolderListener mFolderCallback;
|
||||||
|
BookmarkListener mBookmarkCallback;
|
||||||
|
|
||||||
|
private static final int FOLDER_TYPE = 0;
|
||||||
|
private static final int BOOKMARK_TYPE = 1;
|
||||||
|
|
||||||
|
public BookmarksRecyclerViewAdapter(ArrayList<BookmarkListElement> listElements, Context context) {
|
||||||
|
this.mListElements = listElements;
|
||||||
|
this.mContext = context;
|
||||||
|
this.mInflater = LayoutInflater.from(mContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void updateBookmarklist(ArrayList<BookmarkListElement> listElements) {
|
||||||
|
this.mListElements.clear();
|
||||||
|
this.mListElements.addAll(listElements);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBookmarkListener(BookmarkListener listener) {
|
||||||
|
this.mBookmarkCallback = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
switch (viewType) {
|
||||||
|
case BOOKMARK_TYPE:
|
||||||
|
return new BookmarkHolder(mInflater.inflate(R.layout.bookmark_list_item, parent, false));
|
||||||
|
case FOLDER_TYPE:
|
||||||
|
return new FolderViewHolder(mInflater.inflate(R.layout.bookmark_list_item_folder, parent, false));
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
|
||||||
|
if (holder instanceof BookmarkHolder) {
|
||||||
|
BookmarkHolder bookmarkView = (BookmarkHolder) holder;
|
||||||
|
bookmarkView.relatedBookmarkId = holder.getAdapterPosition();
|
||||||
|
Bookmark b = mListElements.get(holder.getAdapterPosition()).getBookmark();
|
||||||
|
bookmarkView.titleView.setText(b.getTitle());
|
||||||
|
if (!b.getDescription().isEmpty()) {
|
||||||
|
bookmarkView.urlDescriptionView.setText(b.getDescription());
|
||||||
|
} else {
|
||||||
|
bookmarkView.urlDescriptionView.setText(b.getUrl());
|
||||||
|
}
|
||||||
|
IconHandler ih = new IconHandler(mContext);
|
||||||
|
ih.loadIcon(bookmarkView.iconView, b);
|
||||||
|
|
||||||
|
|
||||||
|
} else if (holder instanceof FolderViewHolder) {
|
||||||
|
FolderViewHolder folderView = (FolderViewHolder) holder;
|
||||||
|
folderView.relatedBookmarkId = holder.getAdapterPosition();
|
||||||
|
Folder f = mListElements.get(holder.getAdapterPosition()).getFolder();
|
||||||
|
folderView.folderTitle.setText(f.getTitle());
|
||||||
|
folderView.setUpFolder(f.getId() == Folder.UP_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBookmarkFolderListener(FolderListener fl){
|
||||||
|
mFolderCallback = fl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
if (mListElements.get(position).isFolder()) {
|
||||||
|
return FOLDER_TYPE;
|
||||||
|
} else {
|
||||||
|
return BOOKMARK_TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return mListElements.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BookmarkHolder extends RecyclerView.ViewHolder
|
||||||
|
implements View.OnClickListener, View.OnLongClickListener {
|
||||||
|
|
||||||
|
final PopupMenu popup;
|
||||||
|
final TextView titleView;
|
||||||
|
final TextView urlDescriptionView;
|
||||||
|
final ImageView iconView;
|
||||||
|
int relatedBookmarkId;
|
||||||
|
|
||||||
|
public BookmarkHolder(View view) {
|
||||||
|
super(view);
|
||||||
|
view.setOnClickListener(this);
|
||||||
|
view.setOnLongClickListener(this);
|
||||||
|
titleView = (TextView) view.findViewById(R.id.bookmark_title);
|
||||||
|
urlDescriptionView = (TextView) view.findViewById(R.id.bookmark_url_description);
|
||||||
|
iconView = (ImageView) view.findViewById(R.id.site_icon);
|
||||||
|
|
||||||
|
popup = new PopupMenu(mContext, view);
|
||||||
|
MenuInflater inflater = popup.getMenuInflater();
|
||||||
|
inflater.inflate(R.menu.edit_bookmark_item_menu, popup.getMenu());
|
||||||
|
|
||||||
|
|
||||||
|
// try setting force show icons via reflections
|
||||||
|
Object menuHelper;
|
||||||
|
Class[] argTypes;
|
||||||
|
try {
|
||||||
|
Field fMenuHelper = PopupMenu.class.getDeclaredField("mPopup");
|
||||||
|
fMenuHelper.setAccessible(true);
|
||||||
|
menuHelper = fMenuHelper.get(popup);
|
||||||
|
argTypes = new Class[]{boolean.class};
|
||||||
|
menuHelper.getClass().getDeclaredMethod("setForceShowIcon", argTypes).invoke(menuHelper, true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
popup.setOnMenuItemClickListener(item -> {
|
||||||
|
int id = item.getItemId();
|
||||||
|
Bookmark bookmark = mListElements.get(relatedBookmarkId).getBookmark();
|
||||||
|
|
||||||
|
switch (id) {
|
||||||
|
case R.id.share:
|
||||||
|
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||||
|
intent.setType("text/plain");
|
||||||
|
intent.putExtra(Intent.EXTRA_SUBJECT, bookmark.getTitle());
|
||||||
|
intent.putExtra(Intent.EXTRA_TEXT, bookmark.getUrl());
|
||||||
|
mContext.startActivity(intent);
|
||||||
|
return true;
|
||||||
|
case R.id.edit_menu:
|
||||||
|
EditBookmarkDialog bookmarkDialog = new EditBookmarkDialog();
|
||||||
|
bookmarkDialog.getDialog((Activity) mContext,
|
||||||
|
bookmark,
|
||||||
|
mBookmarkCallback
|
||||||
|
).show();
|
||||||
|
return true;
|
||||||
|
case R.id.delete_menu:
|
||||||
|
showDeleteDialog();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showDeleteDialog() {
|
||||||
|
AlertDialog dialog = new AlertDialog.Builder(mContext)
|
||||||
|
.setTitle(R.string.sure_to_delete_bookmark)
|
||||||
|
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
if(mBookmarkCallback != null) {
|
||||||
|
mBookmarkCallback.deleteBookmark(mListElements.get(relatedBookmarkId).getBookmark());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
Bookmark b = mListElements.get(relatedBookmarkId).getBookmark();
|
||||||
|
intent.setData(Uri.parse(b.getUrl()));
|
||||||
|
mContext.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onLongClick(View view) {
|
||||||
|
popup.show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FolderViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
final TextView folderTitle;
|
||||||
|
int relatedBookmarkId;
|
||||||
|
|
||||||
|
private final ImageView upImage;
|
||||||
|
private final ImageView folderImage;
|
||||||
|
|
||||||
|
FolderViewHolder(View view) {
|
||||||
|
super(view);
|
||||||
|
folderTitle = view.findViewById(R.id.folder_title);
|
||||||
|
upImage = view.findViewById(R.id.icon_back);
|
||||||
|
folderImage = view.findViewById(R.id.icon);
|
||||||
|
|
||||||
|
((RelativeLayout) view.findViewById(R.id.layout)).setOnClickListener(view1 -> {
|
||||||
|
Folder f = mListElements.get(relatedBookmarkId).getFolder();
|
||||||
|
mFolderCallback.changeFolderCallback(f);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpFolder(boolean isUp) {
|
||||||
|
if(isUp) {
|
||||||
|
upImage.setVisibility(View.VISIBLE);
|
||||||
|
folderImage.setVisibility(View.INVISIBLE);
|
||||||
|
} else {
|
||||||
|
upImage.setVisibility(View.INVISIBLE);
|
||||||
|
folderImage.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
180
app/src/main/java/org/schabi/ocbookmarks/ui/IconHandler.java
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
package org.schabi.ocbookmarks.ui;
|
||||||
|
|
||||||
|
import static org.schabi.ocbookmarks.R.drawable.*;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.schabi.ocbookmarks.BuildConfig;
|
||||||
|
import org.schabi.ocbookmarks.R;
|
||||||
|
import org.schabi.ocbookmarks.REST.model.Bookmark;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by the-scrabi on 16.06.17.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class IconHandler {
|
||||||
|
|
||||||
|
private Context context;
|
||||||
|
public IconHandler(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void loadIcon(final ImageView imageView, final Bookmark bookmark) {
|
||||||
|
|
||||||
|
if(siteHasNoIcon(bookmark)) {
|
||||||
|
Bitmap icon = BitmapFactory.decodeResource(context.getResources(),
|
||||||
|
ic_globe);
|
||||||
|
imageView.setImageBitmap(icon);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitmap icon = loadIcon(bookmark);
|
||||||
|
if(icon != null) {
|
||||||
|
|
||||||
|
imageView.setImageBitmap(icon);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncTask<Void, Void, Bitmap> loadTask = new AsyncTask<Void, Void, Bitmap>() {
|
||||||
|
@Override
|
||||||
|
protected Bitmap doInBackground(Void... params) {
|
||||||
|
HttpURLConnection connection = null;
|
||||||
|
BufferedReader in = null;
|
||||||
|
try {
|
||||||
|
|
||||||
|
// get icon url
|
||||||
|
URL url = new URL(bookmark.getUrl());
|
||||||
|
connection = (HttpURLConnection) url.openConnection();
|
||||||
|
connection.setRequestMethod("GET");
|
||||||
|
in = new BufferedReader(
|
||||||
|
new InputStreamReader(connection.getInputStream()));
|
||||||
|
|
||||||
|
StringBuilder res = new StringBuilder();
|
||||||
|
String inputLine;
|
||||||
|
while ((inputLine = in.readLine()) != null) {
|
||||||
|
res.append(inputLine);
|
||||||
|
}
|
||||||
|
in.close();
|
||||||
|
|
||||||
|
Document doc = Jsoup.parse(res.toString(), bookmark.getUrl());
|
||||||
|
Element link = null;
|
||||||
|
// try to get highres firs
|
||||||
|
link = doc.select("link[rel*=\"apple-touch-icon\"]").first();
|
||||||
|
if (link == null) {
|
||||||
|
link = doc.select("link[rel*=\"icon\"]").first();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (link != null) {
|
||||||
|
|
||||||
|
// get icon
|
||||||
|
String iconUrl = link.attr("abs:href");
|
||||||
|
|
||||||
|
// fix icon url for certain sites
|
||||||
|
// ---------------------------------
|
||||||
|
iconUrl = iconUrl.replace("google.com", "www.google.com");
|
||||||
|
// ---------------------------------
|
||||||
|
|
||||||
|
URL iUrl = new URL(iconUrl);
|
||||||
|
connection = (HttpURLConnection) iUrl.openConnection();
|
||||||
|
connection.setRequestMethod("GET");
|
||||||
|
Bitmap icon = BitmapFactory.decodeStream(connection.getInputStream());
|
||||||
|
return icon;
|
||||||
|
} else {
|
||||||
|
Log.d("IconHandler", "Nothing found for: " + bookmark.getUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
if(BuildConfig.DEBUG) e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (in != null) {
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
if(BuildConfig.DEBUG) e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Bitmap result) {
|
||||||
|
if(result == null) {
|
||||||
|
setSiteHasNoIcon(bookmark);
|
||||||
|
} else {
|
||||||
|
storeIcon(bookmark, result);
|
||||||
|
}
|
||||||
|
imageView.setImageBitmap(result);
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap loadIcon(Bookmark bookmark) {
|
||||||
|
int id = bookmark.getId();
|
||||||
|
File homeDir = context.getFilesDir();
|
||||||
|
File iconFile = new File(homeDir.toString() + "/" + id + ".png");
|
||||||
|
if(iconFile.exists()) {
|
||||||
|
return BitmapFactory.decodeFile(iconFile.toString());
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean siteHasNoIcon(Bookmark bookmark) {
|
||||||
|
int id = bookmark.getId();
|
||||||
|
File homeDir = context.getFilesDir();
|
||||||
|
File iconFile = new File(homeDir.toString() + "/" + id + ".noicon");
|
||||||
|
return iconFile.exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSiteHasNoIcon(Bookmark bookmark) {
|
||||||
|
int id = bookmark.getId();
|
||||||
|
File homeDir = context.getFilesDir();
|
||||||
|
File iconFile = new File(homeDir.toString() + "/" + id + ".noicon");
|
||||||
|
try {
|
||||||
|
iconFile.createNewFile();
|
||||||
|
} catch (Exception e) {
|
||||||
|
if(BuildConfig.DEBUG) e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void storeIcon(Bookmark bookmark, Bitmap icon) {
|
||||||
|
int id = bookmark.getId();
|
||||||
|
File homeDir = context.getFilesDir();
|
||||||
|
File iconFile = new File(homeDir.toString() + "/" + id + ".png");
|
||||||
|
FileOutputStream out = null;
|
||||||
|
try {
|
||||||
|
out = new FileOutputStream(iconFile.toString());
|
||||||
|
icon.compress(Bitmap.CompressFormat.PNG, 100, out);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if(BuildConfig.DEBUG) e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteAll() {
|
||||||
|
File homeDir = context.getFilesDir();
|
||||||
|
for(File file : homeDir.listFiles()) {
|
||||||
|
if(file.toString().endsWith(".png") || file.toString().endsWith(".noicon")) {
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,283 @@
|
||||||
|
package org.schabi.ocbookmarks.ui;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import androidx.cardview.widget.CardView;
|
||||||
|
import androidx.appcompat.widget.PopupMenu;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.app.Activity;
|
||||||
|
|
||||||
|
import org.schabi.ocbookmarks.R;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by the-scrabi on 25.05.17.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class TagsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||||
|
ArrayList<String> tagList = new ArrayList<>();
|
||||||
|
Activity context;
|
||||||
|
LayoutInflater inflater;
|
||||||
|
boolean addTagMode = false;
|
||||||
|
|
||||||
|
public interface OnTagTapedListener {
|
||||||
|
void onTagTaped(String tag);
|
||||||
|
}
|
||||||
|
private OnTagTapedListener onTagTapedListener = null;
|
||||||
|
public void setOnTagTapedListener(OnTagTapedListener listener) {
|
||||||
|
onTagTapedListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnTagEditedListener {
|
||||||
|
void onTagEdited(String oldTag, String newTag);
|
||||||
|
}
|
||||||
|
private OnTagEditedListener onTagEditedListener = null;
|
||||||
|
public void setOnTagEditedListener(OnTagEditedListener listener) {
|
||||||
|
onTagEditedListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnTagDeletedListener {
|
||||||
|
void onTagDeleted(String tag);
|
||||||
|
}
|
||||||
|
private OnTagDeletedListener onTagDeletedListener = null;
|
||||||
|
public void setOnTagDeletedListener(OnTagDeletedListener listener) {
|
||||||
|
onTagDeletedListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagsRecyclerViewAdapter(Activity acitivty, boolean addTagMode, ArrayList<String> list) {
|
||||||
|
this.addTagMode = addTagMode;
|
||||||
|
this.context = acitivty;
|
||||||
|
inflater = LayoutInflater.from(context);
|
||||||
|
tagList = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
switch (viewType) {
|
||||||
|
case 0:
|
||||||
|
return new TagHolder(inflater.inflate(R.layout.tag_list_item, parent, false));
|
||||||
|
case 1:
|
||||||
|
return new AddTagHolder(inflater.inflate(R.layout.add_tag_list_item, parent, false));
|
||||||
|
case 2:
|
||||||
|
return new FooderTagHolder(inflater.inflate(R.layout.fooder_tag_list_item, parent, false));
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
||||||
|
if(position < tagList.size()) {
|
||||||
|
TagHolder tagHolder = (TagHolder) holder;
|
||||||
|
tagHolder.setTag(position, tagList.get(position));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
if(position < tagList.size()) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
if(addTagMode) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return tagList.size() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTag(String tagName) {
|
||||||
|
tagList.add(tagName);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
class TagHolder extends RecyclerView.ViewHolder
|
||||||
|
implements View.OnClickListener, View.OnLongClickListener{
|
||||||
|
private final TextView textView;
|
||||||
|
private final PopupMenu popup;
|
||||||
|
private final CardView cardView;
|
||||||
|
private String tagName;
|
||||||
|
private int tagId;
|
||||||
|
|
||||||
|
public TagHolder(View view) {
|
||||||
|
super(view);
|
||||||
|
|
||||||
|
textView = (TextView) view.findViewById(R.id.tag_text);
|
||||||
|
cardView = (CardView) view.findViewById(R.id.card_view);
|
||||||
|
|
||||||
|
cardView.setOnClickListener(this);
|
||||||
|
cardView.setOnLongClickListener(this);
|
||||||
|
|
||||||
|
popup = new PopupMenu(context, view);
|
||||||
|
MenuInflater inflater = popup.getMenuInflater();
|
||||||
|
inflater.inflate(R.menu.edit_tag_item_menu, popup.getMenu());
|
||||||
|
|
||||||
|
// try setting force show icons via reflections (android is a peace of shit)
|
||||||
|
Object menuHelper;
|
||||||
|
Class[] argTypes;
|
||||||
|
try {
|
||||||
|
Field fMenuHelper = PopupMenu.class.getDeclaredField("mPopup");
|
||||||
|
fMenuHelper.setAccessible(true);
|
||||||
|
menuHelper = fMenuHelper.get(popup);
|
||||||
|
argTypes = new Class[]{boolean.class};
|
||||||
|
menuHelper.getClass().getDeclaredMethod("setForceShowIcon", argTypes).invoke(menuHelper, true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onMenuItemClick(MenuItem item) {
|
||||||
|
int id = item.getItemId();
|
||||||
|
switch (id) {
|
||||||
|
case R.id.edit_menu:
|
||||||
|
showEditDialog();
|
||||||
|
return true;
|
||||||
|
case R.id.delete_menu:
|
||||||
|
if(!addTagMode) {
|
||||||
|
showDeleteDialog();
|
||||||
|
} else {
|
||||||
|
notifyDataSetChanged();
|
||||||
|
onTagDeletedListener.onTagDeleted(tagName);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTag(int id, String tag) {
|
||||||
|
tagName = tag;
|
||||||
|
tagId = id;
|
||||||
|
textView.setText(tagName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
if(addTagMode) {
|
||||||
|
showEditDialog();
|
||||||
|
} else {
|
||||||
|
if (onTagTapedListener != null) {
|
||||||
|
onTagTapedListener.onTagTaped(tagName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onLongClick(View view) {
|
||||||
|
popup.show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showDeleteDialog() {
|
||||||
|
AlertDialog dialog = new AlertDialog.Builder(context)
|
||||||
|
.setTitle(R.string.sure_to_delete_tag)
|
||||||
|
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
if(onTagDeletedListener != null) {
|
||||||
|
for(int i = 0; i < tagList.size(); i++) {
|
||||||
|
if(tagList.get(i).equals(tagName)) {
|
||||||
|
tagList.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notifyDataSetChanged();
|
||||||
|
onTagDeletedListener.onTagDeleted(tagName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showEditDialog() {
|
||||||
|
final EditText editText = new EditText(context);
|
||||||
|
editText.setText(tagName);
|
||||||
|
AlertDialog dialog = new AlertDialog.Builder(context)
|
||||||
|
.setTitle(R.string.edit_tag)
|
||||||
|
.setView(editText)
|
||||||
|
.setPositiveButton(R.string.save, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
|
||||||
|
//setTagName(editText.getText().toString());
|
||||||
|
// tagList.set(tagId, editText.getText().toString());
|
||||||
|
|
||||||
|
if(onTagEditedListener != null) {
|
||||||
|
onTagEditedListener.onTagEdited(tagName, editText.getText().toString());
|
||||||
|
}
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}).create();
|
||||||
|
dialog.getWindow().setSoftInputMode(
|
||||||
|
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddTagHolder extends RecyclerView.ViewHolder
|
||||||
|
implements View.OnClickListener {
|
||||||
|
|
||||||
|
public AddTagHolder(View view) {
|
||||||
|
super(view);
|
||||||
|
CardView cardView = (CardView) view.findViewById(R.id.card_view);
|
||||||
|
cardView.setOnClickListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
final EditText editText = new EditText(context);
|
||||||
|
AlertDialog dialog = new AlertDialog.Builder(context)
|
||||||
|
.setTitle(R.string.new_tag)
|
||||||
|
.setView(editText)
|
||||||
|
.setPositiveButton(R.string.save, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
addTag(editText.getText().toString());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}).show();
|
||||||
|
dialog.getWindow().setSoftInputMode(
|
||||||
|
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FooderTagHolder extends RecyclerView.ViewHolder {
|
||||||
|
public FooderTagHolder(View view) {
|
||||||
|
super(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
app/src/main/res/drawable-hdpi/ic_action_visibility.png
Normal file
|
After Width: | Height: | Size: 504 B |
BIN
app/src/main/res/drawable-hdpi/ic_arrow_back_white_24dp.png
Normal file
|
After Width: | Height: | Size: 148 B |
BIN
app/src/main/res/drawable-hdpi/ic_delete_black_24dp.png
Normal file
|
After Width: | Height: | Size: 155 B |
BIN
app/src/main/res/drawable-hdpi/ic_folder_light_blue_700_24dp.png
Normal file
|
After Width: | Height: | Size: 168 B |
BIN
app/src/main/res/drawable-hdpi/ic_globe.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 192 B |
|
After Width: | Height: | Size: 156 B |
BIN
app/src/main/res/drawable-hdpi/ic_mode_edit_black_24dp.png
Normal file
|
After Width: | Height: | Size: 202 B |
BIN
app/src/main/res/drawable-hdpi/ic_share_black_24dp.png
Normal file
|
After Width: | Height: | Size: 398 B |
BIN
app/src/main/res/drawable-hdpi/nextcloud.png
Normal file
|
After Width: | Height: | Size: 292 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_action_visibility.png
Normal file
|
After Width: | Height: | Size: 335 B |
BIN
app/src/main/res/drawable-mdpi/ic_arrow_back_white_24dp.png
Normal file
|
After Width: | Height: | Size: 115 B |
BIN
app/src/main/res/drawable-mdpi/ic_delete_black_24dp.png
Normal file
|
After Width: | Height: | Size: 111 B |
BIN
app/src/main/res/drawable-mdpi/ic_folder_light_blue_700_24dp.png
Normal file
|
After Width: | Height: | Size: 142 B |
|
After Width: | Height: | Size: 155 B |
|
After Width: | Height: | Size: 128 B |
BIN
app/src/main/res/drawable-mdpi/ic_mode_edit_black_24dp.png
Normal file
|
After Width: | Height: | Size: 160 B |
BIN
app/src/main/res/drawable-mdpi/ic_share_black_24dp.png
Normal file
|
After Width: | Height: | Size: 262 B |
BIN
app/src/main/res/drawable-mdpi/nextcloud.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_action_visibility.png
Normal file
|
After Width: | Height: | Size: 643 B |
BIN
app/src/main/res/drawable-xhdpi/ic_arrow_back_white_24dp.png
Normal file
|
After Width: | Height: | Size: 131 B |
BIN
app/src/main/res/drawable-xhdpi/ic_delete_black_24dp.png
Normal file
|
After Width: | Height: | Size: 148 B |
|
After Width: | Height: | Size: 257 B |
|
After Width: | Height: | Size: 168 B |
BIN
app/src/main/res/drawable-xhdpi/ic_mode_edit_black_24dp.png
Normal file
|
After Width: | Height: | Size: 222 B |
BIN
app/src/main/res/drawable-xhdpi/ic_share_black_24dp.png
Normal file
|
After Width: | Height: | Size: 483 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_action_visibility.png
Normal file
|
After Width: | Height: | Size: 934 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_arrow_back_white_24dp.png
Normal file
|
After Width: | Height: | Size: 191 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_delete_black_24dp.png
Normal file
|
After Width: | Height: | Size: 191 B |
|
After Width: | Height: | Size: 334 B |
|
After Width: | Height: | Size: 366 B |
|
After Width: | Height: | Size: 199 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_menu_delete.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_mode_edit_black_24dp.png
Normal file
|
After Width: | Height: | Size: 269 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_share_black_24dp.png
Normal file
|
After Width: | Height: | Size: 675 B |
BIN
app/src/main/res/drawable-xxhdpi/nextcloud.png
Normal file
|
After Width: | Height: | Size: 697 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_arrow_back_white_24dp.png
Normal file
|
After Width: | Height: | Size: 194 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_delete_black_24dp.png
Normal file
|
After Width: | Height: | Size: 237 B |
|
After Width: | Height: | Size: 449 B |
|
After Width: | Height: | Size: 476 B |
|
After Width: | Height: | Size: 258 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_mode_edit_black_24dp.png
Normal file
|
After Width: | Height: | Size: 319 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.png
Normal file
|
After Width: | Height: | Size: 888 B |
5
app/src/main/res/drawable/add_bookmark_background.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#D2575757" />
|
||||||
|
<corners android:radius="24dp" />
|
||||||
|
</shape>
|
||||||
BIN
app/src/main/res/drawable/connect.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
5
app/src/main/res/drawable/ic_add.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M18,13h-5v5c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1v-5H6c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1h5V6c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v5h5c0.55,0 1,0.45 1,1s-0.45,1 -1,1z"/>
|
||||||
|
</vector>
|
||||||
BIN
app/src/main/res/drawable/ic_add_white.png
Normal file
|
After Width: | Height: | Size: 102 B |
28
app/src/main/res/drawable/ic_launcher_background.xml
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:autoMirrored="true"
|
||||||
|
android:viewportWidth="1344"
|
||||||
|
android:viewportHeight="1344">
|
||||||
|
<path
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:pathData="M0,0h1344v1344h-1344z"
|
||||||
|
android:strokeLineJoin="round">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="1344"
|
||||||
|
android:endY="1.2959057E-4"
|
||||||
|
android:startX="163.34073"
|
||||||
|
android:startY="1344"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#FF0082C9"
|
||||||
|
android:offset="0" />
|
||||||
|
<item
|
||||||
|
android:color="#FF1CAFFF"
|
||||||
|
android:offset="1" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
</vector>
|
||||||
12
app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="64"
|
||||||
|
android:viewportHeight="64">
|
||||||
|
<group android:translateX="16"
|
||||||
|
android:translateY="16">
|
||||||
|
<path
|
||||||
|
android:pathData="m16,2c0.9449,0 3.9911,7.9919 4.7555,8.5752 0.7644,0.5833 8.9427,1.1565 9.2346,2.1003 0.292,0.9438 -6.0036,6.4562 -6.2956,7.4 -0.292,0.9438 2.3984,9.2899 1.6339,9.8732 -0.764,0.583 -8.383,-4.002 -9.328,-4.002 -0.9449,0 -8.5641,4.585 -9.3285,4.0017 -0.7644,-0.584 1.9259,-8.9297 1.6339,-9.8735s-6.5875,-6.4562 -6.2956,-7.4c0.292,-0.9438 8.4702,-1.517 9.2342,-2.1003 0.765,-0.5833 3.811,-8.5752 4.756,-8.5752z"
|
||||||
|
android:fillColor="#fff"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
||||||
BIN
app/src/main/res/drawable/ic_menu_email.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
5
app/src/main/res/drawable/ic_settings.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/>
|
||||||
|
</vector>
|
||||||
5
app/src/main/res/drawable/ic_tag.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M20,10L20,8h-4L16,4h-2v4h-4L10,4L8,4v4L4,8v2h4v4L4,14v2h4v4h2v-4h4v4h2v-4h4v-2h-4v-4h4zM14,14h-4v-4h4v4z"/>
|
||||||
|
</vector>
|
||||||
9
app/src/main/res/drawable/shadow.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- res/drawable/myrect.xml -->
|
||||||
|
<shape
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
|
||||||
|
<solid android:color="#31454545" />
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
</shape>
|
||||||
5
app/src/main/res/drawable/shape.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="@color/colorPrimaryDark" />
|
||||||
|
<corners android:radius="32dp" />
|
||||||
|
</shape>
|
||||||
BIN
app/src/main/res/drawable/star.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
app/src/main/res/drawable/star_new.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
73
app/src/main/res/layout/activity_login_acitivty.xml
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context="org.schabi.ocbookmarks.LoginAcitivty">
|
||||||
|
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/starImageView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="64dp"
|
||||||
|
android:focusable="true"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:srcCompat="@drawable/star_new" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- todo: Fix this blue on blue color -->
|
||||||
|
<Button
|
||||||
|
android:id="@+id/ssoButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="48dp"
|
||||||
|
android:layout_marginTop="48dp"
|
||||||
|
android:layout_marginEnd="48dp"
|
||||||
|
android:background="@drawable/shape"
|
||||||
|
android:backgroundTint="@color/colorPrimaryDark"
|
||||||
|
android:elevation="8dp"
|
||||||
|
android:enabled="true"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:text="@string/connect_sso"
|
||||||
|
android:textColor="@color/login_error_color"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/starImageView" />
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/errorContainer"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="48dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="invisible"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/ssoButton">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:srcCompat="@android:drawable/stat_notify_error" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/loginErrorView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="96dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="96dp"
|
||||||
|
android:text="Login Failed"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:visibility="visible" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
27
app/src/main/res/layout/activity_main.xml
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.drawerlayout.widget.DrawerLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/drawer_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
tools:openDrawer="start">
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/app_bar_main"
|
||||||
|
layout="@layout/app_bar_main"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<com.google.android.material.navigation.NavigationView
|
||||||
|
android:id="@+id/nvView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
app:headerLayout="@layout/fragment_drawer"
|
||||||
|
app:menu="@menu/navigation_menu" />
|
||||||
|
|
||||||
|
</androidx.drawerlayout.widget.DrawerLayout>
|
||||||
47
app/src/main/res/layout/add_bookmark_activity.xml
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical" android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="176dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="64dp"
|
||||||
|
android:background="@drawable/add_bookmark_background"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/successView"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
app:srcCompat="@drawable/star" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressView"
|
||||||
|
style="?android:attr/progressBarStyle"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_gravity="center" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/adding_text_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="Adding..." />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
34
app/src/main/res/layout/add_tag_list_item.xml
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:id="@+id/add_tag_list_item_layout">
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/card_view"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
card_view:cardBackgroundColor="@android:color/white"
|
||||||
|
card_view:cardElevation="4dp"
|
||||||
|
card_view:cardUseCompatPadding="true"
|
||||||
|
android:clickable="true"
|
||||||
|
android:foreground="?attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
card_view:srcCompat="@drawable/ic_add" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
23
app/src/main/res/layout/app_bar_main.xml
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" >
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="?attr/colorPrimary" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<include layout="@layout/content_main" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
48
app/src/main/res/layout/bookmark_list_item.xml
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clickable="true"
|
||||||
|
android:longClickable="true"
|
||||||
|
android:background="?attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/site_icon"
|
||||||
|
android:layout_width="72dp"
|
||||||
|
android:layout_height="72dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:scaleType="fitXY"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="15dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/item_name_and_size"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_toRightOf="@id/site_icon"
|
||||||
|
android:layout_toEndOf="@id/site_icon"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bookmark_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="6dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:text="Example"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bookmark_url_description"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="6dp"
|
||||||
|
android:text="https://example.org"
|
||||||
|
android:textSize="12sp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
53
app/src/main/res/layout/bookmark_list_item_folder.xml
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:longClickable="true">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="72dp"
|
||||||
|
android:layout_height="72dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="15dp"
|
||||||
|
android:scaleType="fitXY"
|
||||||
|
app:srcCompat="@drawable/ic_folder_light_blue_700_24dp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon_back"
|
||||||
|
android:layout_width="72dp"
|
||||||
|
android:layout_height="72dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="15dp"
|
||||||
|
android:scaleType="fitXY"
|
||||||
|
android:visibility="invisible"
|
||||||
|
app:srcCompat="@drawable/ic_arrow_back_white_24dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_toEndOf="@id/icon"
|
||||||
|
android:layout_toRightOf="@id/icon"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/folder_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:padding="6dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:text="Folder"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
44
app/src/main/res/layout/content_main.xml
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||||
|
tools:showIn="@layout/app_bar_main">
|
||||||
|
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
android:id="@+id/main_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/container"
|
||||||
|
android:name="org.schabi.ocbookmarks.BookmarkFragment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:layout="@layout/fagment_bookmarks" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/mainProgressBar"
|
||||||
|
style="?android:attr/progressBarStyle"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/fab"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:layout_marginEnd="@dimen/fab_margin"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
app:backgroundTint="@color/colorAccent"
|
||||||
|
app:srcCompat="@drawable/ic_add" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
82
app/src/main/res/layout/edit_bookmark_dialog.xml
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
tools:context="org.schabi.ocbookmarks.EditBookmarkDialog">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="@color/colorPrimary"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="?attr/actionBarSize"
|
||||||
|
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
|
||||||
|
app:popupTheme="@style/ThemeOverlay.AppCompat.ActionBar">
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.Toolbar>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="5dp">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/urlInput"
|
||||||
|
android:inputType="textUri"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/bookmark_url_hint" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/titleInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/bookmark_title_hint" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/descriptionInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/bookmark_description_hint" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/add_tags_text_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/add_tags" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/tag_recycler_view"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:listitem="@layout/tag_list_item"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
27
app/src/main/res/layout/fagment_bookmarks.xml
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context="org.schabi.ocbookmarks.MainActivity">
|
||||||
|
|
||||||
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/swiperefresh_bookmarks"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<!-- <androidx.recyclerview.widget.RecyclerView-->
|
||||||
|
<!-- android:id="@+id/bookmark_recycler_view"-->
|
||||||
|
<!-- android:scrollbars="vertical"-->
|
||||||
|
<!-- android:layout_width="match_parent"-->
|
||||||
|
<!-- android:layout_height="match_parent"-->
|
||||||
|
<!-- tools:listitem="@layout/bookmark_list_item"/>-->
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"/>
|
||||||
|
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
8
app/src/main/res/layout/fooder_tag_list_item.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:id="@+id/fooder_tag_list_item_layout">
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||