1
0
Fork 0
mirror of https://github.com/gsantner/dandelion synced 2025-09-09 10:19:42 +02:00

Compare commits

...

119 commits

Author SHA1 Message Date
Gregor Santner
774c5bec59 Merge dandelion news and FAQ content all into markdown files in the dandelion repository 2023-02-11 03:07:41 +01:00
Gregor Santner
c65aab3840 Update translations, project info 2023-01-28 17:21:07 +01:00
Gregor Santner
c2bea7bec0 Crowdin add Estonian, Update translations (PR #297) 2022-12-08 14:16:52 +01:00
Gregor Santner
36bd1af0b9 Translations: Add bulgarian 2022-07-13 01:51:31 +02:00
Gregor Santner
67b0bbbfd2
Update translations (PR #294) 2022-05-28 19:03:18 +00:00
Gregor Santner
b80cc3ab04 Add croatian & thai language to translation system 2022-05-28 20:55:42 +02:00
Gregor Santner
37f586912f
Change link to domain podupti.me -> diaspora.fediverse.observer, closes #295, by @gsantner 2021-10-09 20:17:11 +02:00
Gregor Santner
de378b8adc Add language Odia/Oriya (India), Update translations (#293) 2021-10-02 13:04:29 +02:00
Gregor Santner
8161353de7
New Crowdin updates (#292) 2021-09-09 22:46:37 +02:00
Gregor Santner
c42b4995b3 Update crowdin config 2021-09-09 22:43:58 +02:00
Gregor Santner
5a5d1d66b3 Update CHANGELOG 2021-08-01 14:31:44 +02:00
Gregor Santner
ceb0002546 Update translations (PR #286) 2021-08-01 14:12:24 +02:00
James Addison
0cf003ea38
Update README screenshot references (PR #291) 2021-08-01 13:38:59 +02:00
Gregor Santner
904f2af20a
Update gs/opoc utilities 2021-08-01 13:30:21 +02:00
Gregor Santner
d923630b42
Update README badges, remove IRC 2021-05-19 20:48:50 +02:00
Gregor Santner
a76a463cd4
Add language Esperanto, by @gsantner 2021-03-13 14:25:09 +01:00
Gregor Santner
c9c10a6fd4
Add language Sinhala (Sri Lanka) 2021-02-28 22:09:03 +01:00
Gregor Santner
e5866ffb2b
Update opoc 2021-01-18 21:32:50 +01:00
Gregor Santner
97d9bf434e
Various additions & improvements to opoc utils 2021-01-12 20:23:10 +01:00
Gregor Santner
f1464b5f8f
ci add comment for cleanup as github has no delete-all button 2020-12-23 14:45:38 +01:00
Gregor Santner
369abf14a5
CI/CD: Remove special junit/lint jobs 2020-12-22 03:39:33 +01:00
Gregor Santner
ceb98c4258
gh actions pull_request action 2020-12-21 23:50:58 +01:00
Gregor Santner
4ebd393250
Update v1.3.5 (translations/libs only) 2020-12-09 12:08:05 +01:00
Gregor Santner
f3b3c6a160
disable AndroidSupportMeWrapper 2020-12-09 11:59:36 +01:00
Gregor Santner
ff62aa5a07 Add Makefile; CI: Remove Circle/Travis, add GitHub Actions 2020-12-09 01:07:24 +01:00
Gregor Santner
6fbd399a4b
Update gsantner/opoc libs/utils 2020-12-09 00:57:31 +01:00
Gregor Santner
fa7e37ccba
Remove Travis CI - Open Source love is gone 2020-12-08 08:41:38 +01:00
Gregor Santner
20d75acd39
New Crowdin updates (#276) 2020-12-05 22:08:40 +01:00
Gregor Santner
7b1897b2e0 crowdin: Add macedonian 2020-11-13 21:56:27 +01:00
Gregor Santner
93aadae64b
[ci skip] Crowdin translation bot: New translations for Swedish (#272) 2020-09-24 23:18:12 +02:00
Gregor Santner
a7dd4a6166
New Crowdin translations (#267) 2020-05-29 21:27:09 +00:00
Gregor Santner
cf1f0100ba
Update crowdin bot commit message template 2020-05-29 23:22:35 +02:00
Gregor Santner
875f613cd9
Update supportme link to https 2020-04-20 02:01:40 +02:00
Gregor Santner
ea1ee6bc40 Update opoc 2020-03-29 19:53:32 +02:00
Gregor Santner
a66d7bf3b2 New Crowdin translations (#264) 2020-03-29 19:33:19 +02:00
Gregor Santner
c5849d7d6a Release v1.3.4 2020-02-09 13:12:59 +01:00
Gregor Santner
cc16b84011 Add seconds to 'save picture' date format, closes #262 2020-02-09 13:12:07 +01:00
Gregor Santner
0f6b1c3ec3
Update opoc 2020-02-09 12:49:26 +01:00
Gregor Santner
a686fea141 Bump junit from 4.12 to 4.13 (#261); New Crowdin translations (#260, #263) 2020-02-09 12:46:29 +01:00
Gregor Santner
f9951f3b16 Release v1.3.3 2019-12-23 21:21:10 +01:00
Gregor Santner
073a688db9
New Crowdin translations (#259) 2019-12-23 20:18:54 +00:00
Gregor Santner
1f3cde9326
Add albanian / Tamil 2019-12-23 21:11:39 +01:00
Gregor Santner
8326c2196c
Update tools 2019-12-06 22:02:24 +01:00
Gregor Santner
7361d4bc3f
Update opoc
Update shared helper utilities of my projects to latest state
2019-11-20 00:34:10 +01:00
Gregor Santner
1948c28cff
Crowdin: Update language mapping 2019-11-04 14:18:40 +01:00
Gregor Santner
d1be7f5215 Bump gradle from 3.5.0 to 3.5.1 (#254) 2019-10-04 01:38:43 +02:00
Gregor Santner
8d019b6c3b
New Crowdin translations (#253) 2019-09-22 13:55:33 +00:00
Gregor Santner
0708fcb02b
Update list of languages for crowdin 2019-09-22 13:35:33 +02:00
Gregor Santner
c5e3a42005
Update opoc 2019-09-07 21:54:08 +02:00
Gregor Santner
8c3c7da2ab
Release v1.3.2 2019-08-25 14:19:19 +02:00
Gregor Santner
3bc6afad03
Fix getDateOfDaysAgo 2019-08-25 14:07:07 +02:00
Gregor Santner
194fbf6927 Rename test flavor to atest (so it's default dev flavor when starting android studio) 2019-08-24 14:54:25 +02:00
dependabot-preview[bot]
6d45620181 Bump gradle from 3.4.2 to 3.5.0 (#250) 2019-08-21 12:22:56 +00:00
Gregor Santner
c9d3693515
New Crowdin translations (#245) 2019-07-26 01:28:14 +00:00
Gregor Santner
a618da97d8
Update opoc 2019-07-26 03:19:28 +02:00
imgbot[bot]
3ec8ab89c6 [ImgBot] Optimize images (#247)
*Total -- 1,011.84kb -> 914.41kb (9.63%)

/app/src/flavorTest/res/drawable-ldpi/ic_launcher.png -- 1.50kb -> 1.24kb (17.35%)
/app/src/main/res/drawable-ldpi/ic_launcher.png -- 1.67kb -> 1.40kb (16.03%)
/metadata/en-US/promoGraphic.png -- 3.58kb -> 3.04kb (15.17%)
/app/src/flavorDandelior/ic_launcher-web.png -- 29.94kb -> 26.33kb (12.05%)
/metadata/en-US/featureGraphic.png -- 861.38kb -> 773.90kb (10.16%)
/app/src/flavorDandelior/res/drawable-xhdpi/ic_launcher.png -- 9.28kb -> 8.74kb (5.83%)
/app/src/flavorDandelior/res/drawable-xhdpi/ic_launcher_round.png -- 9.28kb -> 8.74kb (5.83%)
/app/src/flavorDandelior/res/drawable-hdpi/ic_launcher_round.png -- 8.01kb -> 7.58kb (5.36%)
/app/src/flavorDandelior/res/drawable-hdpi/ic_launcher.png -- 8.01kb -> 7.58kb (5.36%)
/app/src/flavorDandelior/res/drawable-xxhdpi/ic_launcher_round.png -- 11.90kb -> 11.28kb (5.19%)
/app/src/flavorDandelior/res/drawable-xxhdpi/ic_launcher.png -- 11.90kb -> 11.28kb (5.19%)
/app/src/flavorDandelior/res/drawable-xxxhdpi/ic_launcher_round.png -- 14.72kb -> 14.08kb (4.31%)
/app/src/flavorDandelior/res/drawable-xxxhdpi/ic_launcher.png -- 14.72kb -> 14.08kb (4.31%)
/app/src/flavorDandelior/res/drawable-mdpi/ic_launcher_round.png -- 6.80kb -> 6.58kb (3.33%)
/app/src/flavorDandelior/res/drawable-mdpi/ic_launcher.png -- 6.80kb -> 6.58kb (3.33%)
/app/src/flavorDandelior/res/drawable-ldpi/ic_launcher_round.png -- 6.17kb -> 5.99kb (2.96%)
/app/src/flavorDandelior/res/drawable-ldpi/ic_launcher.png -- 6.17kb -> 5.99kb (2.96%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
2019-07-26 00:35:39 +00:00
Gregor Santner
938a54fc74
Release v1.3.1 2019-04-26 00:55:53 +02:00
Gregor Santner
0732fe1b91
New Crowdin translations (#243) 2019-04-25 22:53:54 +00:00
Gregor Santner
a7054d3ff8 Update metadata 2019-04-21 15:57:49 +02:00
25020c1692
Merge pull request #240 from vanitasvitae/de_metadata
German translation of metadata
2019-03-11 15:51:39 +01:00
b1fb5d646a
German translation of metadata 2019-03-11 14:10:35 +01:00
Gregor Santner
51c3f68f22 Release v1.3.0 2019-03-08 14:49:29 +01:00
Gregor Santner
a80f6ebb45 New Crowdin translations (#238) 2019-03-08 14:45:02 +01:00
Gregor Santner
49c6a97ce7 Add AndroidSupportMeWrapper 2019-03-08 12:32:10 +01:00
Gregor Santner
4f44d1acd3
New Crowdin translations (#231) 2019-02-07 09:38:41 +00:00
Gregor Santner
24571f6921
Auto reformat code 2019-02-07 10:31:18 +01:00
massimilianoLe
fb8eb60974 pullRefresh as option + Shortcut to settings @themesettings (#226)
* pull to refresh page

* Make youtube links open external

* corrected a bit but still not able to check if app is installed

* corrected triggersync

* Added option For user to choose how to open youtube links

* removed unecessary comment

* update with dandelion

* pull refresh option+shortcut to themes

* Changes request applyed

* added name in contributors list
2019-02-07 09:25:06 +00:00
Gregor Santner
b833311353 Release v1.2.5 2019-01-11 02:43:53 +01:00
Gregor Santner
6f310985d6 Update gradle tooling & gsantner-opoc 2019-01-11 02:38:30 +01:00
Gregor Santner
6d62c70359
New Crowdin translations (#224) 2019-01-11 00:16:10 +00:00
Gregor Santner
d34cbe1ed1
Remove/move serbian 2019-01-11 01:11:05 +01:00
Gregor Santner
66cbf9a47d update crowdin yaml 2019-01-11 00:56:13 +01:00
Gregor Santner
b1d5e980c2
New Crowdin translations (#221) 2019-01-10 23:31:30 +00:00
massimilianoLe
f693418d64 Make youtube links open external, by @massimilianoLe (#220) 2018-12-01 17:12:04 +01:00
Gregor Santner
04e89e516c
New Crowdin translations (#218) 2018-11-23 10:31:00 +01:00
massimilianoLe
5854075080 pull to refresh page (#219) 2018-11-23 10:28:37 +01:00
Gregor Santner
1f8babd708 Release v1.2.3 2018-11-12 01:33:36 +01:00
Gregor Santner
2618bdf5ad
New Crowdin translations (#214) 2018-11-12 01:25:36 +01:00
Gregor Santner
19884c33c1
New Crowdin translations (#212) 2018-10-09 20:57:57 +02:00
Gregor Santner
2289c04f0f Update opoc and it's license headers 2018-10-01 21:12:17 +02:00
Gregor Santner
1129f1848a
New Crowdin translations (#210) 2018-09-27 11:17:17 +02:00
Gregor Santner
c77fd1db01
add more language remappings for crowdin 2018-09-27 11:13:25 +02:00
Gregor Santner
b072111b8b
add more language remappings for crowdin 2018-09-27 10:58:04 +02:00
Gregor Santner
351b2d6447
Mark only strings.xml as translatable on crowdin 2018-09-27 10:49:31 +02:00
Gregor Santner
ddc2549c66 Update Crowdin configuration file 2018-09-25 17:55:04 +02:00
Gregor Santner
c1d34fc5db
Merge pull request #204 from muhaaliss/stringlate-tr-9470
Updated tr (Turkish) translation, by @muhaaliss
2018-09-16 09:32:10 +02:00
Muha Aliss
33db3656fa Updated tr (Turkish) translation
Commit made via Stringlate
2018-09-16 03:17:33 +03:00
Gregor Santner
e194d2159e
Add @vanitasvitae 's unfinished image-viewer patch (deleted all test/demo branches for cleanup 2018-09-05 17:00:04 +02:00
Gregor Santner
e66c72d255 release v1.2.2 2018-09-04 20:00:55 +02:00
Gregor Santner
4cd88400ee Rework string ids to tell content 2018-09-04 19:16:51 +02:00
Gregor Santner
48a00b099e
. 2018-09-04 17:02:34 +02:00
Gregor Santner
e6b2fa606e
Merge pull request #201 from asereze/patch-1
Updated sc (Sardinian) translation xml file
2018-08-07 23:59:30 +02:00
asereze
0b66ef801f
Updated sc (Sardinian) translation xml file
I hope it's ok now.
2018-08-07 23:43:07 +02:00
Gregor Santner
e1ea19f02a Release v1.2.1 2018-08-06 23:36:04 +02:00
Gregor Santner
af9e3ad8a3
Update metadata 2018-07-30 21:40:30 +02:00
Gregor Santner
d76fe4b82b
Update metadata 2018-07-30 13:32:34 +02:00
Gregor Santner
dc07beafc3
Merge pull request #199 from xmgz/master
galician (gl) strings.xml updated, by @xmgz (#199)
2018-07-29 11:55:29 +02:00
Xosé M
423970ce39 faltaban 3 cadeas 2018-07-29 10:30:51 +02:00
Xosé M
215f67308e eliminar string preferences e about 2018-07-29 09:54:34 +02:00
Xosé M
376449486b minor gl fixes 2018-07-29 09:50:16 +02:00
Gregor Santner
1e15374229 Release v1.2.0 2018-07-25 03:30:28 +02:00
Gregor Santner
dbbd3cef35 Use searchable dialog for tag and aspect/contact list
* Remove seperate fragments for both cases
* Make use of opoc/SearchOrCustomTextDialog
* Basically also removes all fragment swaps
  * Only two fragments are stream and podselection
  * Pod logout restarts app
  * Thus long known Android Supportlib bug can't occur

* Fixes #198
2018-07-25 01:59:25 +02:00
Gregor Santner
918adcf358 Add flavor dandelior*, move flavor specific to folder instead gradle generated 2018-07-22 14:03:29 +02:00
Gregor Santner
957573ea83 Update opoc 2018-05-21 14:57:41 +02:00
Gregor Santner
65ba10712f Update paths 2018-05-13 12:08:27 +02:00
Gregor Santner
099eb9ca78
Update supportlib 2018-05-09 03:25:23 +02:00
Gregor Santner
ad3966dd85
Merge string resources to one file, propagte change to all translations 2018-05-09 03:23:12 +02:00
Gregor Santner
9a6798f9f4
Update french translation, by @arnaud-jacquemin, (closes #196) 2018-05-09 03:00:33 +02:00
XoseM
d8d74de22b actualizando o galego 2018-02-21 12:36:28 +01:00
XoseM
8df13fe283 Merge branch 'master' of https://github.com/xmgz/dandelion
pideumo o git
2018-02-21 12:32:21 +01:00
XoseM
724a0dbc9c actualizando a gl-translation 2018-02-21 12:31:45 +01:00
XoseM
1f873e2057 fixed strings from gl-tranlation branch 2018-02-21 12:14:48 +01:00
x m l p
ea66b57979
Update .gitignore 2018-02-21 12:02:03 +01:00
XoseM
cbb98f7c60 editando desde gl-translation 2018-02-21 11:51:25 +01:00
XoseM
ed511bec01 update using git help from gsantner 2018-02-21 11:45:02 +01:00
Xosé M
204429b8d5 engadido cómo traducir 2018-02-21 08:31:04 +01:00
Xosé M
120c48f702 actualizando segundo contexto 2018-02-21 08:02:47 +01:00
Xosé M. Lamas
15240fca49 pequenos erros tipográficos 2017-11-30 08:37:16 +01:00
XoseM
b1c2219b4d 1ª modificación en github 2017-11-28 09:47:44 +01:00
246 changed files with 10586 additions and 5151 deletions

View file

@ -21,7 +21,4 @@
Description: Description:
What this is about, what happens and what is expected to happen. What needs to be done for it to happen. What this is about, what happens and what is expected to happen. What needs to be done for it to happen.
If a crash is happening a log is needed. Screenshots or demonstration videos are always helpful too. If a crash is happening a log is needed. Screenshots or demonstration videos are always helpful too.
About logging:
https://gsantner.net/android-contribution-guide/?packageid=com.github.dfa.diaspora_android&name=dandelion&web=https://github.com/diaspora-for-android/dandelion#logcat
--> -->

View file

@ -0,0 +1,68 @@
##############################################################################################################################
# # Cleanup:
#const sleep = ms => () => new Promise((resolve, reject) => window.setTimeout(resolve, ms));
#Promise.resolve()
#.then(() => { document.getElementsByClassName("details-overlay details-reset position-relative d-inline-block ")[3].children[0].click(); })
#.then(sleep(500))
#.then(() => { document.getElementsByClassName("dropdown-item btn-link menu-item-danger")[0].click(); })
#.then(sleep(1000))
#.then(() => { document.getElementsByClassName("btn btn-block btn-danger")[0].click();});
#
# while [ 1 ] ; do sleep 4; xdotool key Up; sleep 0.1; xdotool key Return; done
##############################################################################################################################
name: "CI"
on: [push, pull_request]
jobs:
build:
if: "!contains(github.event.head_commit.message, 'ci skip') && (!contains(github.event_name, 'pull_request') || (contains(github.event_name, 'pull_request') && github.event.pull_request.head.repo.full_name != github.repository))"
runs-on: ubuntu-latest
steps:
- name: "Checkout: Code"
uses: actions/checkout@v2
- name: "Checkout: Code (PR)"
uses: actions/checkout@v2
if: "contains(github.event_name, 'pull_request')"
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: "Setup: Java"
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: "Cache: Gradle"
uses: actions/cache@v2
with:
path: |
~/.gradle
.gradle
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('gradle/wrapper/gradle-wrapper.*') }}
- name: "Build: Project with make"
run: make clean all
- name: "Build: List dist files"
if: always()
run: find dist -type f -maxdepth 2
- name: "Artifacts: All"
if: always()
uses: actions/upload-artifact@v2.2.1
with:
name: "all"
path: dist
retention-days: 5
- name: "Artifacts: Android APK"
uses: actions/upload-artifact@v2.2.1
with:
name: "android-apk"
path: |
dist/*.apk

1
.gitignore vendored
View file

@ -36,6 +36,7 @@ tmp/
### Gradle ### ### Gradle ###
.gradle .gradle
build/ build/
dist/
gradle-app.setting gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)

View file

@ -1,48 +0,0 @@
language: android
jdk: oraclejdk8
before_cache:
# Do not cache a few Gradle files/directories (see https://docs.travis-ci.com/user/languages/java/#Caching)
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
# Android SDK
- $HOME/android-sdk-dl
- $HOME/android-sdk
# Gradle dependencies
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
# Android build cache (see http://tools.android.com/tech-docs/build-cache)
- $HOME/.android/build-cache
install:
# Download and unzip the Android SDK tools (if not already there thanks to the cache mechanism)
# Latest version available here: https://developer.android.com/studio/index.html#downloads
- if test ! -e $HOME/android-sdk-dl/sdk-tools.zip ; then curl https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip > $HOME/android-sdk-dl/sdk-tools.zip ; fi
- unzip -qq -n $HOME/android-sdk-dl/sdk-tools.zip -d $HOME/android-sdk
# Install or update Android SDK components (will not do anything if already up to date thanks to the cache mechanism)
- echo y | $HOME/android-sdk/tools/bin/sdkmanager 'tools' > /dev/null
- echo y | $HOME/android-sdk/tools/bin/sdkmanager 'platform-tools' > /dev/null
- echo y | $HOME/android-sdk/tools/bin/sdkmanager 'build-tools;26.0.2' > /dev/null
- echo y | $HOME/android-sdk/tools/bin/sdkmanager 'platforms;android-27' > /dev/null
- echo y | $HOME/android-sdk/tools/bin/sdkmanager 'extras;google;m2repository' > /dev/null
branches:
except:
- gh-pages
- l10n_master
- crowdin
env:
global:
- ANDROID_HOME=$HOME/android-sdk
matrix:
- TASK="clean lintFlavorDefaultDebug --stacktrace"
- TASK="clean build check -x lint --stacktrace"
script: "./gradlew --no-daemon --parallel $TASK"

View file

@ -1,3 +1,36 @@
### Recent changes
- See [Discussions](https://github.com/gsantner/dandelion/discussions), [Issues](https://github.com/gsantner/dandelion/issues) and [Project page](https://github.com/gsantner/dandelion#readme) to see what is going on.
### v1.4.0
- Add seconds to 'save picture' date format
- Updated translations
- Added german F-Droid description translation
- Update to Android SDK 29
### v1.3.0
- Add option to open youtube links external/in YouTube app (optional)
- Pull to refresh
### v1.2.3
**Improved:**
- More supported languages, more complete translations!
### v1.2.1
**App release: dandelior**
- Added an (rebranded) flavor of dandelion: dandelior
- Only differenties in use are other (black) icon and AMOLED colors by default enabled
- Already available on F-Droid
**New features:**
- All new Aspects and Tags, using a searchable dialog
**Fixed:**
- Sometimes the Stream went white, which is due an still (>3 years) unfixed Android Support library bug. It should not occur very often anymore due less use of fragments.
**Improved:**
- Various small tweaks
- Updated translation files
### v1.1.3 ### v1.1.3
- Improve sharing *a lot*, add support for multiple filetypes - Improve sharing *a lot*, add support for multiple filetypes
- Support for downloading GIFs ;) - Support for downloading GIFs ;)

View file

@ -1,6 +1,5 @@
<!-- <!--
This file contains references to people who contributed to the app. This file contains references to people who contributed to the app.
You can also send a mail to [gsantner AT mailbox DOT org](http://gsantner.net#contact) to get included.
Schema: **[Name](Reference)**<br/>~° Text Schema: **[Name](Reference)**<br/>~° Text
@ -9,11 +8,11 @@ Where:
* Reference: E-Mail, Webpage * Reference: E-Mail, Webpage
* Text: Information about / kind of contribution * Text: Information about / kind of contribution
## LIST OF CONTRIBUTORS ## LIST OF CONTRIBUTORS
--> -->
* **[Gregor Santner](http://gsantner.net)**<br/>~° Current developer of dandelion * **[Gregor Santner](http://github.com/gsantner)**<br/>~° Development of dandelion
* **[Paul Schaub](https://github.com/vanitasvitae)**<br/>~° Development of dandelion * **[Paul Schaub](https://github.com/vanitasvitae)**<br/>~° Development of dandelion
* **[Martín Vukovic](martinvukovic AT protonmail DOT com)**<br/>~° Diaspora Native WebApp * **[Martín Vukovic](martinvukovic AT protonmail DOT com)**<br/>~° Diaspora Native WebApp
* **[Gaukler Faun](https://github.com/scoute-dich)**<br/>~° Diaspora Native WebApp additions * **[Gaukler Faun](https://github.com/scoute-dich)**<br/>~° Diaspora Native WebApp additions
@ -30,3 +29,4 @@ Where:
* **[Jean Lucas](jean AT 4ray DOT co)**<br/>~° Spanish translation * **[Jean Lucas](jean AT 4ray DOT co)**<br/>~° Spanish translation
* **[asereze](https://github.com/asereze)**<br/>~° Sardinian translation * **[asereze](https://github.com/asereze)**<br/>~° Sardinian translation
* **[Xosé M. Lamas](http://xmgz.eu)**<br />~° Galician translation * **[Xosé M. Lamas](http://xmgz.eu)**<br />~° Galician translation
* **[massimiliano](https://framagit.org/massimiliano)**<br />~° Contributor

99
Makefile Normal file
View file

@ -0,0 +1,99 @@
# License of Makefile: Public Domain / CC0
.PHONY: $(shell sed -n -e '/^$$/ { n ; /^[^ .\#][^ ]*:/ { s/:.*$$// ; p ; } ; }' $(MAKEFILE_LIST))
.NOTPARALLEL: clean
.DEFAULT_GOAL := all
env-%:
@: $(if ${${*}},,$(error Environment variable $* not set))
####################################################################################
DIST_DIR = dist
MOVE = mv
all: $(DIST_DIR) spellcheck lint deptree test build aapt_dump_badging
####################################################################################
$(DIST_DIR):
mkdir -p ${DIST_DIR}
ANDROID_BUILD_TOOLS := $(shell test -n "$ANDROID_SDK_ROOT" && find "${ANDROID_SDK_ROOT}/build-tools" -iname "aapt" | sort -r | head -n1 | xargs dirname)
TOOL_SPELLCHECKING_ISPELL := $(shell command -v ispell 2> /dev/null)
FLAVOR := $(or ${FLAVOR},${FLAVOR},Atest)
.NOTPARALLEL: gradle gradle-analyze-log
gradle: env-ANDROID_SDK_ROOT
mkdir -p $(DIST_DIR)/log/
chmod +x gradlew
./gradlew --no-daemon --parallel --stacktrace $A 2>&1 | tee "$(DIST_DIR)/log/gradle.log"
@echo "-----------------------------------------------------------------------------------"
gradle-analyze-log:
mv "$(DIST_DIR)/log/gradle.log" "$(DIST_DIR)/log/gradle$A.log"
cat "$(DIST_DIR)/log/gradle$A.log" | grep "BUILD " | tail -n1 | grep -q "BUILD SUCCESSFUL in"
adb: env-ANDROID_SDK_ROOT
"${ANDROID_SDK_ROOT}/platform-tools/adb" $A 2>&1 | tee "$(DIST_DIR)/log/adb-$L.log"
aapt: env-ANDROID_SDK_ROOT
"${ANDROID_BUILD_TOOLS}/aapt" $A 2>&1 | grep -v 'application-label-' | tee "$(DIST_DIR)/log/aapt$L.log"
build:
rm -f $(DIST_DIR)/*.apk
$(MAKE) A="clean assembleFlavor$(FLAVOR) -x lint" gradle
find app -type f -newermt '-300 seconds' -iname '*.apk' -not -iname '*unsigned.apk' | xargs cp -R -t $(DIST_DIR)/
$(MAKE) A="-build" gradle-analyze-log
lint:
rm -Rf $(DIST_DIR)/lint
mkdir -p $(DIST_DIR)/lint/
$(MAKE) A="lintFlavorDefaultDebug" gradle
find app -type f -iname 'lint-results-*' | grep -v 'intermediates' | xargs cp -R -t $(DIST_DIR)/lint
$(MAKE) A="-lint" gradle-analyze-log
test:
rm -Rf $(DIST_DIR)/tests
$(MAKE) A="testFlavorDefaultDebugUnitTest -x lint" gradle
mkdir -p app/build/test-results/testFlavorDefaultDebugUnitTest && echo 'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHRlc3RzdWl0ZSBuYW1lPSJkdW1teSIgdGVzdHM9IjEiIHNraXBwZWQ9IjAiIGZhaWx1cmVzPSIwIiBlcnJvcnM9IjAiIHRpbWVzdGFtcD0iMjAyMC0xMi0wOFQwMDowMDowMCIgaG9zdG5hbWU9ImxvY2FsaG9zdCIgdGltZT0iMC4wMSI+CiAgPHByb3BlcnRpZXMvPgogIDx0ZXN0Y2FzZSBuYW1lPSJkdW1teSIgY2xhc3NuYW1lPSJkdW1teSIgdGltZT0iMC4wMSIvPgogIDxzeXN0ZW0tb3V0PjwhW0NEQVRBW11dPjwvc3lzdGVtLW91dD4KICA8c3lzdGVtLWVycj48IVtDREFUQVtdXT48L3N5c3RlbS1lcnI+CjwvdGVzdHN1aXRlPgo=' | base64 -d > 'app/build/test-results/testFlavorDefaultDebugUnitTest/TEST-dummy.xml'
find app -type d -iname 'testFlavorDefaultDebugUnitTest' | xargs cp -R -t $(DIST_DIR)/
mv ${DIST_DIR}/testFlavorDefaultDebugUnitTest $(DIST_DIR)/tests
$(MAKE) A="-test" gradle-analyze-log
deptree:
$(MAKE) A="app:dependencies --configuration flavor$(FLAVOR)DebugRuntimeClasspath" gradle
$(MAKE) A="-dependency-tree" gradle-analyze-log
clean:
$(MAKE) A="clean" gradle
rm -Rf $(DIST_DIR) app/build app/flavor* .idea dist
find . -type f -iname "*.iml" -delete
$(MAKE) $(DIST_DIR)
@echo "-----------------------------------------------------------------------------------"
install:
$(MAKE) A="install -r $(DIST_DIR)/*.apk" L="install" adb
run:
$(MAKE) A="shell monkey -p $$(aapt dump badging $(DIST_DIR)/*.apk | grep package: | sed 's@.* name=@@' | sed 's@ .*@@' | xargs | head -n1) -c android.intent.category.LAUNCHER 1" L="run" adb
aapt_dump_badging:
$(MAKE) A="dump badging $(DIST_DIR)/*.apk" aapt
@echo "-----------------------------------------------------------------------------------"
spellcheck:
mkdir -p "$(DIST_DIR)/lint/"
ifndef TOOL_SPELLCHECKING_ISPELL
@echo "Tool ispell (spellcheck) not found in PATH. Spellcheck skipped." > "$(DIST_DIR)/lint/stringsxml-spellcheck.txt"
else
@echo "Use ispell for spellchecking the original values/strings.xml"
find . -iname "strings.xml" -path "*/main*/values/*" | head -n1 | xargs cat \
| grep "<string name=" | sed 's@.*">@@' | sed 's@</string>@@' | sed 's@\\n@ @g' | sed 's@\\@@g' \
| ispell -W3 -a | grep ^\& | sed 's@[0-9]@@g' | sort | uniq | cut -d, -f1-4 \
| sed 's@^..@- @' | column -t -s: \
> "$(DIST_DIR)/lint/stringsxml-spellcheck.txt"
@echo "\nPotential words with bad spelling:"
endif
@cat "$(DIST_DIR)/lint/stringsxml-spellcheck.txt"
@echo "-----------------------------------------------------------------------------------"

96
NEWS.md Normal file
View file

@ -0,0 +1,96 @@
# dandelion - News
## General
### Installation
You can install and update from [F-Droid](https://f-droid.org/repository/browse/?fdid=com.github.dfa.diaspora_android) or [GitHub](https://github.com/gsantner/dandelion/releases/latest).
F-Droid is a store for free & open source apps.
The *.apk's available for download are signed by the F-Droid team and guaranteed to correspond to the (open source) source code of dandelion.
Generally this is the recommended way to install dandelion & keep it updated.
### Get informed
* Check the [project readme](https://github.com/gsantner/dandelion/tree/news#readme) for general project information.
* Check the [project news](https://github.com/gsantner/dandelion/blob/master/NEWS.md#readme) for more details on what is going on.
* Check the [project git history](https://github.com/gsantner/dandelion/commits/master) for most recent code changes.
### The right place to ask
If you have questions or found an issue please head to the [dandelion project](https://github.com/gsantner/dandelion/issues/new/choose) and ask there.
[Search](https://github.com/gsantner/dandelion/issues?q=#js-issues-search) for same/similar and related issues/questions before, it might be already answered or resolved.
### Navigation
* [dandelion v1.2 - Add dandelior - Searchable Tags and Aspects](#dandelion-v12---add-dandelior---searchable-tags-and-aspects)
* [dandelion v0.1.2 - Aspekte, Pod wechseln](#dandelion-v012---aspekte-pod-wechseln)
------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------
# dandelion\* v1.2 - Add dandelior\* - Searchable Tags and Aspects
_12. August 2018_
## dandelior\* is a rebranded version of dandelion\*
dandelior\* is based 100% on the same code and resources as dandelion\*. Its from the same code repository, just a different build flavor.
The main purpose of dandelior\* is the most requested feature till date - to support multiple accounts / another account at dandelion\*.
- Added an (rebranded) flavor of dandelion: dandelior
- Only differenties in use are other (black) icon and AMOLED colors by default enabled
- Already available on F-Droid
**New features:**
- All new Aspects and Tags, using a searchable dialog
**Fixed:**
- Sometimes the Stream went white, which is due an still (3+ years) unfixed Android Support library bug. It should not occur very often anymore due less use of fragments.
**Improved:**
- Various small tweaks
- Updated translation files
------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------
# dandelion v0.1.2 - Aspekte, Pod wechseln
_05. Juni 2016_
In den letzten Tagen hat @gsantner viel Zeit in die inoffizielle diaspora\* Android App ([dandelion\*](https://github.com/gsantner/dandelion)) investiert.
Dabei wurden unter anderem folgende Änderungen beigesteuert:
- Allgemeines zur Usability
- Animationen für den Activity-Wechsel und Startup, WebView-Scroll-Top
- Podliste caching
- Aspekt-Liste und Aspekte hinzugefügt
- Verbessertes Sharing aus der App
- Material Progressbar
- Suche verbessert
- Collapsing top menu
- toolbar/actions/menu geändert, fab entfernt
- Refactoring layout & menu files, dialogs
- Überarbeitete Main,Splash,PodSelectionActivity
- Pod wechseln

View file

@ -1,9 +1,9 @@
[![GitHub release](https://img.shields.io/github/tag/diaspora-for-android/dandelion.svg)](https://github.com/diaspora-for-android/dandelion/releases) [![GitHub releases](https://img.shields.io/github/tag/gsantner/dandelion.svg)](https://github.com/gsantner/dandelion/releases)
[![Build Status](https://travis-ci.org/Diaspora-for-Android/dandelion.svg?branch=master)](https://travis-ci.org/Diaspora-for-Android/dandelion) [![GitHub downloads](https://img.shields.io/github/downloads/gsantner/dandelion/total.svg?logo=github&logoColor=lime)](https://github.com/gsantner/dandelion/releases)
[![Translate - with Stringlate](https://img.shields.io/badge/stringlate-translate-green.svg)](https://lonamiwebs.github.io/stringlate/translate?git=https%3A%2F%2Fgithub.com%2Fdiaspora-for-android%2Fdandelion.git&mail=gro.xobliam@@rentnasg) [![Translate on Crowdin](https://img.shields.io/badge/translate-crowdin-green.svg)](https://crowdin.com/project/diaspora-for-android/invite)
[![Chat - Matrix](https://img.shields.io/badge/chat-on%20matrix-blue.svg)](https://matrix.to/#/#dandelion:matrix.org) [![Chat - FreeNode IRC](https://img.shields.io/badge/chat-on%20irc-blue.svg)](https://kiwiirc.com/client/irc.freenode.net/?nick=dandelion-anon|?##dandelion) [![Chat on Matrix](https://img.shields.io/badge/chat-matrix-blue.svg)](https://matrix.to/#/#dandelion:matrix.org)
[![Donate Bitcoin](https://img.shields.io/badge/donate-bitcoin-orange.svg)](https://gsantner.net/supportme/?project=dandelion&source=readme) [![GitHub CI](https://github.com/gsantner/dandelion/workflows/CI/badge.svg)](https://github.com/gsantner/dandelion/actions)
[![Donate LiberaPay](https://img.shields.io/badge/donate-liberapay-orange.svg)](https://liberapay.com/gsantner/donate) [![Codacy code quality](https://img.shields.io/codacy/grade/aff869c440bc48b7bd64680e97cbc453)](https://www.codacy.com/app/gsantner/dandelion)
# dandelion\* # dandelion\*
<img src="/app/src/main/ic_launcher-web.png" align="left" width="100" hspace="10" vspace="10"> <img src="/app/src/main/ic_launcher-web.png" align="left" width="100" hspace="10" vspace="10">
@ -41,44 +41,41 @@ dandelion\* requires access to the Internet and to external storage to be able t
The project is always open for contributions and accepts pull requests. The project is always open for contributions and accepts pull requests.
The project uses [AOSP Java Code Style](https://source.android.com/source/code-style#follow-field-naming-conventions), with one exception: private members are `_camelCase` instead of `mBigCamel`. You may use Android Studios _auto reformat feature_ before sending a PR. The project uses [AOSP Java Code Style](https://source.android.com/source/code-style#follow-field-naming-conventions), with one exception: private members are `_camelCase` instead of `mBigCamel`. You may use Android Studios _auto reformat feature_ before sending a PR.
Translations can be contributed on GitHub or via [E-Mail](http://gsantner.net/#contact). You can use Stringlate ([![Translate - with Stringlate](https://img.shields.io/badge/stringlate-translate-green.svg)](https://lonamiwebs.github.io/stringlate/translate?git=https%3A%2F%2Fgithub.com%2Fdiaspora-for-android%2Fdandelion.git)) to translate the project directly on your Android phone. It allows you to export as E-Mail attachement and to post on GitHub. Translations can be contributed on GitHub. You can use Stringlate ([![Translate - with Stringlate](https://img.shields.io/badge/stringlate-translate-green.svg)](https://lonamiwebs.github.io/stringlate/translate?git=https%3A%2F%2Fgithub.com%2Fgsantner%2Fdandelion.git)) to translate the project directly on your Android phone. It allows you to export as E-Mail attachement and to post on GitHub.
Join our IRC or Matrix channel (bridged) and say hello! Don't be afraid to start talking. [![Chat - Matrix](https://img.shields.io/badge/chat-on%20matrix-blue.svg)](https://matrix.to/#/#dandelion:matrix.org) [![Chat - FreeNode IRC](https://img.shields.io/badge/chat-on%20irc-blue.svg)](https://kiwiirc.com/client/irc.freenode.net/?nick=dandelion-anon|?##dandelion)
Join our Matrix channel and say hello! Don't be afraid to start talking. [![Chat - Matrix](https://img.shields.io/badge/chat-on%20matrix-blue.svg)](https://matrix.to/#/#dandelion:matrix.org)
Note that the main project members are working on this project for free during leisure time, are mostly busy with their job/university/school, and may not react or start coding immediately. Note that the main project members are working on this project for free during leisure time, are mostly busy with their job/university/school, and may not react or start coding immediately.
#### Resources #### Resources
* Project: [Changelog](/CHANGELOG.md) | [Issues level/beginner](https://github.com/diaspora-for-android/dandelion/issues?q=is%3Aissue+is%3Aopen+label%3Alevel%2Fbeginner) | [License](/LICENSE.txt) | [CoC](/CODE_OF_CONDUCT.md) * Project: [Changelog](/CHANGELOG.md) | [Issues level/beginner](https://github.com/gsantner/dandelion/issues?q=is%3Aissue+is%3Aopen+label%3Alevel%2Fbeginner) | [License](/LICENSE.txt) | [CoC](/CODE_OF_CONDUCT.md)
* Project diaspora\* account: [dandelion00@diasp.org](https://diasp.org/people/48b78420923501341ef3782bcb452bd5) * Project diaspora\* account: [dandelion00@diasp.org](https://diasp.org/people/48b78420923501341ef3782bcb452bd5)
* diaspora\*: [GitHub](https://github.com/diaspora/diaspora) | [Web](https://diasporafoundation.org) | [d\* HQ account](https://pod.diaspora.software/people/7bca7c80311b01332d046c626dd55703) * diaspora\*: [GitHub](https://github.com/diaspora/diaspora) | [Web](https://diasporafoundation.org) | [d\* HQ account](https://pod.diaspora.software/people/7bca7c80311b01332d046c626dd55703)
* App on F-Droid: [Metadata](https://gitlab.com/fdroid/fdroiddata/blob/master/metadata/com.github.dfa.diaspora_android.txt) | [Page](https://f-droid.org/packages/com.github.dfa.diaspora_android/) | [Wiki](https://f-droid.org/wiki/page/com.github.dfa.diaspora_android) | [Build log](https://f-droid.org/wiki/page/com.github.dfa.diaspora_android/lastbuild) * App on F-Droid: [Metadata](https://gitlab.com/fdroid/fdroiddata/blob/master/metadata/com.github.dfa.diaspora_android.txt) | [Page](https://f-droid.org/packages/com.github.dfa.diaspora_android/) | [Wiki](https://f-droid.org/wiki/page/com.github.dfa.diaspora_android) | [Build log](https://f-droid.org/wiki/page/com.github.dfa.diaspora_android/lastbuild)
## Licensing ## Licensing
dandelion\* is released under GNU GENERAL PUBLIC LICENSE (see [LICENCE](https://github.com/Diaspora-for-Android/dandelion/blob/master/LICENSE.md)). dandelion\* is released under GNU GENERAL PUBLIC LICENSE (see [LICENCE](https://github.com/gsantner/dandelion/blob/master/LICENSE.md)).
The app is licensed GPL v3. Localization files and resources (strings\*.xml) are licensed CC0 1.0. The app is licensed GPL v3. Localization files and resources (strings\*.xml) are licensed CC0 1.0.
For more licensing informations, see [`3rd party licenses`](/app/src/main/res/raw/licenses_3rd_party.md). For more licensing informations, see [`3rd party licenses`](/app/src/main/res/raw/licenses_3rd_party.md).
## Screenshots ## Screenshots
<div style="display:flex;" > <div style="display:flex;" >
<img src="https://raw.githubusercontent.com/diaspora-for-android/dandelion-metadata-latest/master/en-US/phoneScreenshots/01.png" width="19%" > <img src="https://raw.githubusercontent.com/gsantner/dandelion/master/metadata/en-US/phoneScreenshots/01.png" width="19%" >
<img src="https://raw.githubusercontent.com/diaspora-for-android/dandelion-metadata-latest/master/en-US/phoneScreenshots/02.png" width="19%" style="margin-left:10px;" > <img src="https://raw.githubusercontent.com/gsantner/dandelion/master/metadata/en-US/phoneScreenshots/02.png" width="19%" style="margin-left:10px;" >
<img src="https://raw.githubusercontent.com/diaspora-for-android/dandelion-metadata-latest/master/en-US/phoneScreenshots/03.png" width="19%" style="margin-left:10px;" > <img src="https://raw.githubusercontent.com/gsantner/dandelion/master/metadata/en-US/phoneScreenshots/03.png" width="19%" style="margin-left:10px;" >
<img src="https://raw.githubusercontent.com/diaspora-for-android/dandelion-metadata-latest/master/en-US/phoneScreenshots/04.png" width="19%" style="margin-left:10px;" > <img src="https://raw.githubusercontent.com/gsantner/dandelion/master/metadata/en-US/phoneScreenshots/04.png" width="19%" style="margin-left:10px;" >
<img src="https://raw.githubusercontent.com/diaspora-for-android/dandelion-metadata-latest/master/en-US/phoneScreenshots/05.png" width="19%" style="margin-left:10px;" > <img src="https://raw.githubusercontent.com/gsantner/dandelion/master/metadata/en-US/phoneScreenshots/05.png" width="19%" style="margin-left:10px;" >
</div> </div>
<div style="display:flex;" > <div style="display:flex;" >
<img src="https://raw.githubusercontent.com/diaspora-for-android/dandelion-metadata-latest/master/en-US/phoneScreenshots/06.png" width="19%" > <img src="https://raw.githubusercontent.com/gsantner/dandelion/master/metadata/en-US/phoneScreenshots/06.png" width="19%" >
<img src="https://raw.githubusercontent.com/diaspora-for-android/dandelion-metadata-latest/master/en-US/phoneScreenshots/07.png" width="19%" style="margin-left:10px;" > <img src="https://raw.githubusercontent.com/gsantner/dandelion/master/metadata/en-US/phoneScreenshots/07.png" width="19%" style="margin-left:10px;" >
<img src="https://raw.githubusercontent.com/diaspora-for-android/dandelion-metadata-latest/master/en-US/phoneScreenshots/08.png" width="19%" style="margin-left:10px;" > <img src="https://raw.githubusercontent.com/gsantner/dandelion/master/metadata/en-US/phoneScreenshots/08.png" width="19%" style="margin-left:10px;" >
<img src="https://raw.githubusercontent.com/diaspora-for-android/dandelion-metadata-latest/master/en-US/phoneScreenshots/09.png" width="19%" style="margin-left:10px;" > <img src="https://raw.githubusercontent.com/gsantner/dandelion/master/metadata/en-US/phoneScreenshots/09.png" width="19%" style="margin-left:10px;" >
</div> </div>
### Notice ### Notice
#### Maintainers #### Maintainers
- gsantner ([GitHub](https://github.com/gsantner), [Web](https://gsantner.net), [diaspora*](https://pod.geraspora.de/people/d1cbdd70095301341e834860008dbc6c)) - gsantner ([GitHub](https://github.com/gsantner), [diaspora*](https://pod.geraspora.de/people/d1cbdd70095301341e834860008dbc6c))
- Bitcoin: [1B9ZyYdQoY9BxMe9dRUEKaZbJWsbQqfXU5](http://gsantner.net/donate/#donate)
- vanitasvitae ([GitHub](https://github.com/vanitasvitae), [diaspora*](https://pod.geraspora.de/people/bbd7af90fbec013213e34860008dbc6c)) - vanitasvitae ([GitHub](https://github.com/vanitasvitae), [diaspora*](https://pod.geraspora.de/people/bbd7af90fbec013213e34860008dbc6c))
- Bitcoin: 1Ao3W6NaQv3xKppviB7RSFKjHo6PGd8RTy

View file

@ -1,16 +0,0 @@
## Screenshots
<div style="display:flex;" >
<img src="https://raw.githubusercontent.com/diaspora-for-android/dandelion-metadata-latest/master/en-US/phoneScreenshots/01.png" width="19%" >
<img src="https://raw.githubusercontent.com/diaspora-for-android/dandelion-metadata-latest/master/en-US/phoneScreenshots/02.png" width="19%" style="margin-left:10px;" >
<img src="https://raw.githubusercontent.com/diaspora-for-android/dandelion-metadata-latest/master/en-US/phoneScreenshots/03.png" width="19%" style="margin-left:10px;" >
<img src="https://raw.githubusercontent.com/diaspora-for-android/dandelion-metadata-latest/master/en-US/phoneScreenshots/04.png" width="19%" style="margin-left:10px;" >
<img src="https://raw.githubusercontent.com/diaspora-for-android/dandelion-metadata-latest/master/en-US/phoneScreenshots/05.png" width="19%" style="margin-left:10px;" >
</div>
<div style="display:flex;" >
<img src="https://raw.githubusercontent.com/diaspora-for-android/dandelion-metadata-latest/master/en-US/phoneScreenshots/06.png" width="19%" >
<img src="https://raw.githubusercontent.com/diaspora-for-android/dandelion-metadata-latest/master/en-US/phoneScreenshots/07.png" width="19%" style="margin-left:10px;" >
<img src="https://raw.githubusercontent.com/diaspora-for-android/dandelion-metadata-latest/master/en-US/phoneScreenshots/08.png" width="19%" style="margin-left:10px;" >
<img src="https://raw.githubusercontent.com/diaspora-for-android/dandelion-metadata-latest/master/en-US/phoneScreenshots/09.png" width="19%" style="margin-left:10px;" >
</div>

View file

@ -6,46 +6,27 @@ if (enable_plugin_kotlin) {
} }
android { android {
compileSdkVersion version_setup_compileSdk buildToolsVersion rootProject.ext.version_buildTools
flavorDimensions "default" compileSdkVersion rootProject.ext.version_compileSdk
defaultConfig { defaultConfig {
minSdkVersion version_setup_minSdk resValue "string", "manifest_package_id", "com.github.dfa.diaspora_android"
targetSdkVersion version_setup_targetSdk applicationId "com.github.dfa.diaspora_android"
versionName "1.3.5"
versionCode 46
vectorDrawables.useSupportLibrary = true
minSdkVersion rootProject.ext.version_minSdk
targetSdkVersion rootProject.ext.version_compileSdk
buildConfigField "boolean", "IS_TEST_BUILD", "false" buildConfigField "boolean", "IS_TEST_BUILD", "false"
buildConfigField "boolean", "IS_GPLAY_BUILD", "false" buildConfigField "boolean", "IS_GPLAY_BUILD", "false"
buildConfigField "String[]", "DETECTED_ANDROID_LOCALES", "${findUsedAndroidLocales()}" buildConfigField "String[]", "DETECTED_ANDROID_LOCALES", "${findUsedAndroidLocales()}"
buildConfigField "String", "BUILD_DATE", "\"${getBuildDate()}\""
buildConfigField "String", "GITHASH", "\"${getGitHash()}\"" buildConfigField "String", "GITHASH", "\"${getGitHash()}\""
resValue "string", "manifest_package_id", "com.github.dfa.diaspora_android" setProperty("archivesBaseName", applicationId + "-v" + versionCode + "-" + versionName)
applicationId "com.github.dfa.diaspora_android"
versionName "1.1.3"
versionCode 32
vectorDrawables.useSupportLibrary = true
resValue 'string', 'app_name', "dandelion*"
manifestPlaceholders = [appIcon: "@drawable/ic_launcher"]
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
if (enable_plugin_kotlin) {
main.java.srcDirs += 'src/main/kotlin'
}
} }
flavorDimensions "default"
productFlavors { productFlavors {
flavorDefault { flavorDefault {
} }
@ -55,17 +36,62 @@ android {
buildConfigField "boolean", "IS_GPLAY_BUILD", "true" buildConfigField "boolean", "IS_GPLAY_BUILD", "true"
}*/ }*/
flavorTest { flavorDandelior {
applicationId "com.github.dfa.secondlion" applicationId "net.gsantner.dandelior"
resValue 'string', 'app_name', "secondlion*" }
manifestPlaceholders = [appIcon: "@drawable/ic_launcher_test"]
flavorAtest {
applicationId "net.gsantner.secondlion"
versionCode = Integer.parseInt(new Date().format('yyMMdd')) versionCode = Integer.parseInt(new Date().format('yyMMdd'))
versionName = new Date().format('yyMMdd') versionName = new Date().format('yyMMdd')
buildConfigField "boolean", "IS_TEST_BUILD", "true" buildConfigField "boolean", "IS_TEST_BUILD", "true"
} }
} }
sourceSets {
main { assets.srcDirs = ['src/main/assets'] }
if (enable_plugin_kotlin) {
main.java.srcDirs += 'src/main/kotlin'
}
main.java.srcDirs += 'thirdparty/java'
main.res.srcDirs += 'thirdparty/res'
main.assets.srcDirs += 'thirdparty/assets'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
configurations.all {
resolutionStrategy {
eachDependency { details ->
if (details.requested.group == 'com.android.support') {
if (details.requested.name != 'multidex' && details.requested.name != 'multidex-instrumentation') {
details.useVersion "${rootProject.ext.version_library_appcompat}"
}
}
}
}
}
packagingOptions {
exclude 'META-INF/LICENSE-LGPL-2.1.txt'
exclude 'META-INF/LICENSE-LGPL-3.txt'
exclude 'META-INF/LICENSE-W3C-TEST'
}
compileOptions {
encoding = 'UTF-8'
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions { lintOptions {
disable 'MissingTranslation' disable 'MissingTranslation', 'InvalidPackage', 'ObsoleteLintCustomCheck', 'DefaultLocale', 'UnusedAttribute', 'VectorRaster', 'InflateParams', 'IconLocation', 'UnusedResources', 'TypographyEllipsis'
abortOnError false
} }
} }
@ -75,7 +101,7 @@ dependencies {
// Jars // Jars
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.13'
// Android standard libs // Android standard libs
implementation "com.android.support:appcompat-v7:${version_library_appcompat}" implementation "com.android.support:appcompat-v7:${version_library_appcompat}"
@ -83,16 +109,19 @@ dependencies {
implementation "com.android.support:support-v4:${version_library_appcompat}" implementation "com.android.support:support-v4:${version_library_appcompat}"
implementation "com.android.support:customtabs:${version_library_appcompat}" implementation "com.android.support:customtabs:${version_library_appcompat}"
implementation "com.android.support:cardview-v7:${version_library_appcompat}" implementation "com.android.support:cardview-v7:${version_library_appcompat}"
implementation "com.android.support:preference-v7:${version_library_appcompat}"
// UI libraries // UI libraries
implementation "com.github.DASAR:ShiftColorPicker:v0.5" implementation "com.github.DASAR:ShiftColorPicker:v0.5"
// Tool libraries // Tool libraries
implementation 'commons-io:commons-io:2.6'
implementation "info.guardianproject.netcipher:netcipher:${version_library_netcipher}" implementation "info.guardianproject.netcipher:netcipher:${version_library_netcipher}"
implementation "info.guardianproject.netcipher:netcipher-webkit:${version_library_netcipher}" implementation "info.guardianproject.netcipher:netcipher-webkit:${version_library_netcipher}"
//noinspection AnnotationProcessorOnCompilePath
implementation "com.jakewharton:butterknife:${version_library_butterknife}" implementation "com.jakewharton:butterknife:${version_library_butterknife}"
if (enable_plugin_kotlin) { if (enable_plugin_kotlin) {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$version_plugin_kotlin" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${version_plugin_kotlin}"
} }
// Processors // Processors

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before After
Before After

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">secondlion*</string>
</resources>

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:pathData="M0.025,-0.07h19.95v20.14H0.025z"
android:fillColor="#000000"/>
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="36.363636"
android:viewportHeight="36.363636">
<group android:translateX="8.181818"
android:translateY="8.181818">
<path
android:pathData="M11.337,14.123l-0.963,-1.345c-0.257,-0.36 -0.466,-0.64 -0.477,-0.64 -0.012,0 -0.416,0.544 -0.958,1.287a83.9,83.9 0,0 1,-0.947 1.287c-0.015,0 -1.86,-1.3 -1.865,-1.313 -0.002,-0.007 0.415,-0.62 0.927,-1.361 0.512,-0.742 0.931,-1.36 0.931,-1.375 0,-0.023 -0.166,-0.081 -1.468,-0.515l-1.485,-0.496c-0.013,-0.005 0.063,-0.263 0.327,-1.094 0.19,-0.599 0.349,-1.093 0.354,-1.099 0.005,-0.006 0.707,0.219 1.56,0.5 0.852,0.28 1.556,0.509 1.565,0.509 0.008,0 0.018,-0.013 0.022,-0.03 0.003,-0.015 0.01,-0.74 0.016,-1.612 0.006,-0.87 0.015,-1.59 0.02,-1.6 0.009,-0.012 0.248,-0.015 1.127,-0.015 0.614,0 1.123,0.004 1.13,0.01 0.01,0.006 0.027,0.485 0.056,1.56 0.046,1.766 0.047,1.79 0.075,1.79 0.01,0 0.686,-0.226 1.501,-0.503a50.795,50.795 0,0 1,1.49 -0.492c0.016,0.019 0.685,2.194 0.676,2.202 -0.004,0.005 -0.684,0.237 -1.51,0.517 -1.137,0.386 -1.504,0.515 -1.507,0.531 -0.003,0.012 0.388,0.597 0.886,1.324 0.49,0.716 0.888,1.308 0.886,1.314a96.945,96.945 0,0 1,-1.852 1.364c-0.006,0 -0.239,-0.317 -0.517,-0.705z"
android:fillColor="#fafafa"/>
</group>
</vector>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">dandelior*</string>
</resources>

View file

@ -13,8 +13,9 @@
<application <application
android:name="com.github.dfa.diaspora_android.App" android:name="com.github.dfa.diaspora_android.App"
android:allowBackup="false" android:allowBackup="false"
android:icon="${appIcon}" android:icon="@drawable/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:theme="@style/DiasporaLight"> android:theme="@style/DiasporaLight">
<provider <provider
@ -47,19 +48,20 @@
<activity <activity
android:name="com.github.dfa.diaspora_android.activity.AboutActivity" android:name="com.github.dfa.diaspora_android.activity.AboutActivity"
android:configChanges="locale" android:configChanges="locale"
android:label="@string/about_activity__title_about_app" android:label="@string/about"
android:theme="@style/DiasporaLight.NoActionBar" /> android:theme="@style/DiasporaLight.NoActionBar" />
<activity <activity
android:name="com.github.dfa.diaspora_android.activity.MainActivity" android:name="com.github.dfa.diaspora_android.activity.MainActivity"
android:configChanges="keyboardHidden|locale|orientation|screenSize" android:configChanges="keyboardHidden|locale|orientation|screenSize"
android:icon="${appIcon}" android:icon="@drawable/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/DiasporaLight.NoActionBar" android:theme="@style/DiasporaLight.NoActionBar"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<meta-data android:name="android.app.shortcuts" <meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" /> android:resource="@xml/shortcuts" />
<intent-filter> <intent-filter>
@ -68,6 +70,7 @@
<action android:name="sc_aspects" /> <action android:name="sc_aspects" />
<action android:name="sc_activities" /> <action android:name="sc_activities" />
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View file

@ -35,6 +35,8 @@ import com.github.dfa.diaspora_android.util.AppSettings;
import com.github.dfa.diaspora_android.util.DiasporaUrlHelper; import com.github.dfa.diaspora_android.util.DiasporaUrlHelper;
import net.gsantner.opoc.util.AdBlock; import net.gsantner.opoc.util.AdBlock;
import net.gsantner.opoc.util.ContextUtils;
import net.gsantner.opoc.util.ShareUtil;
public class App extends Application { public class App extends Application {
private volatile static App app; private volatile static App app;
@ -50,10 +52,18 @@ public class App extends Application {
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
ShareUtil.setFileProviderAuthority(BuildConfig.APPLICATION_ID);
app = this; app = this;
final Context c = getApplicationContext(); final Context c = getApplicationContext();
appSettings = AppSettings.get(); appSettings = AppSettings.get();
String a = new ContextUtils(this).bcstr("FLAVOR", "");
a += "__";
if (appSettings.isAppFirstStart() && "flavorDandelior".equals(new ContextUtils(this).bcstr("FLAVOR", ""))) {
appSettings.setAmoledColorMode(true);
}
// Init app log // Init app log
AppLog.setLoggingEnabled(appSettings.isLoggingEnabled()); AppLog.setLoggingEnabled(appSettings.isLoggingEnabled());
AppLog.setLoggingSpamEnabled(appSettings.isLoggingSpamEnabled()); AppLog.setLoggingSpamEnabled(appSettings.isLoggingSpamEnabled());

View file

@ -169,7 +169,7 @@ public class AboutActivity extends ThemedActivity
if (isAdded()) { if (isAdded()) {
try { try {
PackageInfo pInfo = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0); PackageInfo pInfo = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0);
appVersion.setText(getString(R.string.fragment_debug__app_version, pInfo.versionName + " (" + pInfo.versionCode + ")")); appVersion.setText(getString(R.string.app_version_with_arg, pInfo.versionName + " (" + pInfo.versionCode + ")"));
} catch (PackageManager.NameNotFoundException e) { } catch (PackageManager.NameNotFoundException e) {
e.printStackTrace(); e.printStackTrace();
@ -213,8 +213,8 @@ public class AboutActivity extends ThemedActivity
Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
sharingIntent.setType("text/plain"); sharingIntent.setType("text/plain");
sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, getString(R.string.app_name)); sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, getString(R.string.app_name));
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, getString(R.string.fragment_about__spread_the_word_share_text, getString(R.string.fragment_about__fdroid_link))); sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, getString(R.string.hey_checkout_dandelion_tag__appspecific, getString(R.string.fragment_about__fdroid_link)));
startActivity(Intent.createChooser(sharingIntent, getResources().getString(R.string.action_share_dotdotdot))); startActivity(Intent.createChooser(sharingIntent, getResources().getString(R.string.share_dotdotdot)));
break; break;
} }
} }
@ -258,9 +258,9 @@ public class AboutActivity extends ThemedActivity
ButterKnife.bind(this, view); ButterKnife.bind(this, view);
accentColor = ContextUtils.get().colorToHexString(ThemeHelper.getAccentColor()); accentColor = ContextUtils.get().colorToHexString(ThemeHelper.getAccentColor());
maintainers.setTextFormatted(getString(R.string.fragment_license__maintainers_text, maintainers.setTextFormatted(getString(R.string.this_app_is_currently_developed_and_maintained_by_witharg,
ContextUtils.get().loadMarkdownForTextViewFromRaw(R.raw.maintainers, ""))); ContextUtils.get().loadMarkdownForTextViewFromRaw(R.raw.maintainers, "")));
contributors.setTextFormatted(getString(R.string.fragment_license__contributors_thank_you, contributors.setTextFormatted(getString(R.string.thank_you_witharg,
ContextUtils.get().loadMarkdownForTextViewFromRaw(R.raw.contributors, ""))); ContextUtils.get().loadMarkdownForTextViewFromRaw(R.raw.contributors, "")));
thirdPartyLibs.setTextFormatted( thirdPartyLibs.setTextFormatted(
ContextUtils.get().loadMarkdownForTextViewFromRaw(R.raw.licenses_3rd_party, "")); ContextUtils.get().loadMarkdownForTextViewFromRaw(R.raw.licenses_3rd_party, ""));
@ -341,7 +341,7 @@ public class AboutActivity extends ThemedActivity
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(CLIPBOARD_SERVICE); ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("DEBUG_LOG", AppLog.Log.getLogBuffer()); ClipData clip = ClipData.newPlainText("DEBUG_LOG", AppLog.Log.getLogBuffer());
clipboard.setPrimaryClip(clip); clipboard.setPrimaryClip(clip);
Toast.makeText(DebugFragment.this.getActivity(), R.string.fragment_debug__toast_log_copied, Toast.LENGTH_SHORT).show(); Toast.makeText(DebugFragment.this.getActivity(), R.string.debug_log_copied_to_clipboard, Toast.LENGTH_SHORT).show();
} else { } else {
AppLog.d(this, "Not Added!"); AppLog.d(this, "Not Added!");
} }
@ -356,13 +356,13 @@ public class AboutActivity extends ThemedActivity
PackageInfo pInfo = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0); PackageInfo pInfo = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0);
AppSettings appSettings = ((App) getActivity().getApplication()).getSettings(); AppSettings appSettings = ((App) getActivity().getApplication()).getSettings();
packageName.setText(pInfo.packageName); packageName.setText(pInfo.packageName);
appVersion.setText(getString(R.string.fragment_debug__app_version, pInfo.versionName + " (" + pInfo.versionCode + ")")); appVersion.setText(getString(R.string.app_version_with_arg, pInfo.versionName + " (" + pInfo.versionCode + ")"));
osVersion.setText(getString(R.string.fragment_debug__android_version, Build.VERSION.RELEASE)); osVersion.setText(getString(R.string.android_version_witharg, Build.VERSION.RELEASE));
deviceName.setText(getString(R.string.fragment_debug__device_name, Build.MANUFACTURER + " " + Build.MODEL)); deviceName.setText(getString(R.string.device_name_witharg, Build.MANUFACTURER + " " + Build.MODEL));
if (app.getSettings().getPod() != null) { if (app.getSettings().getPod() != null) {
podDomain.setText(getString(R.string.fragment_debug__pod_profile_url, app.getSettings().getPod().getPodUrl())); podDomain.setText(getString(R.string.pod_domain_witharg__appspecific, app.getSettings().getPod().getPodUrl()));
podName.setText(getString(R.string.fragment_debug__pod_profile_name, app.getSettings().getPod().getName())); podName.setText(getString(R.string.pod_profile_name_witharg__appspecific, app.getSettings().getPod().getName()));
} }
} catch (PackageManager.NameNotFoundException e) { } catch (PackageManager.NameNotFoundException e) {
@ -420,11 +420,11 @@ public class AboutActivity extends ThemedActivity
public CharSequence getPageTitle(int position) { public CharSequence getPageTitle(int position) {
switch (position) { switch (position) {
case 0: case 0:
return getString(R.string.about_activity__title_about_app); return getString(R.string.about);
case 1: case 1:
return getString(R.string.about_activity__title_about_license); return getString(R.string.license);
case 2: case 2:
return getString(R.string.about_activity__title_debug_info); return getString(R.string.debugging);
} }
return null; return null;
} }

View file

@ -1,208 +0,0 @@
/*
This file is part of the dandelion*.
dandelion* is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dandelion* is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with the dandelion*.
If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.dfa.diaspora_android.activity;
import android.content.Context;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.widget.AppCompatImageView;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.github.dfa.diaspora_android.App;
import com.github.dfa.diaspora_android.R;
import com.github.dfa.diaspora_android.data.DiasporaAspect;
import com.github.dfa.diaspora_android.listener.OnSomethingClickListener;
import com.github.dfa.diaspora_android.ui.theme.ThemedFragment;
import com.github.dfa.diaspora_android.util.AppSettings;
import com.github.dfa.diaspora_android.util.ContextUtils;
import com.github.dfa.diaspora_android.util.DiasporaUrlHelper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
/**
* Fragment that shows a list of the Aspects
*/
public class AspectListFragment extends ThemedFragment implements OnSomethingClickListener<Object> {
public static final String TAG = "com.github.dfa.diaspora_android.AspectListFragment";
@BindView(R.id.fragment_list__recycler_view)
public RecyclerView aspectsRecyclerView;
@BindView(R.id.fragment_list__spacer)
public View space;
@BindView(R.id.fragment_list__root)
public RelativeLayout rootView;
protected App app;
protected DiasporaUrlHelper urls;
@Override
protected int getLayoutResId() {
return R.layout.recycler_list__fragment;
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ButterKnife.bind(this, view);
app = (App) getActivity().getApplication();
AppSettings appSettings = app.getSettings();
urls = new DiasporaUrlHelper(appSettings);
aspectsRecyclerView.setHasFixedSize(true);
aspectsRecyclerView.setNestedScrollingEnabled(false);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext());
aspectsRecyclerView.setLayoutManager(layoutManager);
final AspectAdapter adapter = new AspectAdapter(appSettings, this);
aspectsRecyclerView.setAdapter(adapter);
//Set window title
getActivity().setTitle(R.string.nav_aspects);
}
@Override
public String getFragmentTag() {
return TAG;
}
@Override
public boolean onBackPressed() {
return false;
}
@Override
public void onSomethingClicked(Object null1, Integer null2, String aspectId) {
((MainActivity) getActivity()).openDiasporaUrl(urls.getAspectUrl(aspectId));
}
@Override
protected void applyColorToViews() {
aspectsRecyclerView.invalidate();
if (getAppSettings().isAmoledColorMode()) {
rootView.setBackgroundColor(Color.BLACK);
space.setBackgroundColor(Color.BLACK);
}
}
public static class AspectAdapter extends RecyclerView.Adapter<AspectAdapter.ViewHolder> {
private boolean isAmoledColorMode;
private final AppSettings appSettings;
private final DiasporaAspect[] aspectList;
private final List<String> aspectFavsList;
private final OnSomethingClickListener<Object> aspectClickedListener;
static class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.recycler_view__list_item__text)
public TextView title;
@BindView(R.id.recycler_view__list_item__favourite)
AppCompatImageView favouriteImage;
@BindView(R.id.recycler_view__list_item__root)
RelativeLayout root;
ViewHolder(View v) {
super(v);
ButterKnife.bind(this, v);
}
}
AspectAdapter(AppSettings appSettings, OnSomethingClickListener<Object> aspectClickedListener) {
this.appSettings = appSettings;
this.aspectList = appSettings.getAspects();
this.aspectFavsList = new ArrayList<>(Arrays.asList(appSettings.getAspectFavs()));
this.aspectClickedListener = aspectClickedListener;
this.isAmoledColorMode = appSettings.isAmoledColorMode();
}
@Override
public int getItemCount() {
return aspectList.length;
}
@Override
public AspectAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recycler_list__list_item_with_fav, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
// Alternating colors
final Context c = holder.root.getContext();
final DiasporaAspect aspect = aspectList[position];
holder.title.setText(aspect.name);
if (position % 2 == 1) {
holder.root.setBackgroundColor(isAmoledColorMode ? Color.BLACK : ContextUtils.get().rcolor(R.color.alternate_row_color));
holder.title.setTextColor(isAmoledColorMode ? Color.GRAY : Color.BLACK);
} else {
holder.root.setBackgroundColor(isAmoledColorMode ? Color.BLACK : Color.WHITE);
holder.title.setTextColor(isAmoledColorMode ? Color.GRAY : Color.BLACK);
}
// Favourite (Star) Image
applyFavouriteImage(holder.favouriteImage, isAspectFaved(aspect.name));
// Click on fav button
holder.favouriteImage.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (isAspectFaved(aspect.name)) {
aspectFavsList.remove(aspectFavsList.indexOf(aspect.name));
} else {
aspectFavsList.add(aspect.name);
}
appSettings.setAspectFavs(aspectFavsList);
applyFavouriteImage(holder.favouriteImage, isAspectFaved(aspect.name));
}
});
holder.root.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
aspectClickedListener.onSomethingClicked(null, null, aspect.id + "");
}
});
}
private boolean isAspectFaved(String tag) {
return aspectFavsList.contains(tag);
}
private void applyFavouriteImage(AppCompatImageView imageView, boolean isFaved) {
imageView.setImageResource(isFaved ? R.drawable.ic_star_filled_48px : R.drawable.ic_star_border_black_48px);
imageView.setColorFilter(isFaved ? appSettings.getAccentColor() : (isAmoledColorMode ? Color.GRAY : 0), PorterDuff.Mode.SRC_ATOP);
}
}
}

View file

@ -128,7 +128,7 @@ public class DiasporaStreamFragment extends BrowserFragment {
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
AppLog.d(this, "StreamFragment.onOptionsItemSelected()"); AppLog.d(this, "StreamFragment.onOptionsItemSelected()");
ShareUtil shu = new ShareUtil(getContext()).setFileProviderAuthority(BuildConfig.APPLICATION_ID); ShareUtil shu = new ShareUtil(getContext());
PermissionChecker permc = new PermissionChecker(getActivity()); PermissionChecker permc = new PermissionChecker(getActivity());
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.action_reload: { case R.id.action_reload: {
@ -152,7 +152,7 @@ public class DiasporaStreamFragment extends BrowserFragment {
sharingIntent.setType("text/plain"); sharingIntent.setType("text/plain");
sharingIntent.putExtra(Intent.EXTRA_SUBJECT, webView.getTitle()); sharingIntent.putExtra(Intent.EXTRA_SUBJECT, webView.getTitle());
sharingIntent.putExtra(Intent.EXTRA_TEXT, webView.getUrl()); sharingIntent.putExtra(Intent.EXTRA_TEXT, webView.getUrl());
startActivity(Intent.createChooser(sharingIntent, getResources().getString(R.string.action_share_dotdotdot))); startActivity(Intent.createChooser(sharingIntent, getResources().getString(R.string.share_dotdotdot)));
return true; return true;
} }
@ -165,7 +165,7 @@ public class DiasporaStreamFragment extends BrowserFragment {
case R.id.action_share_link_to_clipboard: { case R.id.action_share_link_to_clipboard: {
shu.setClipboard(webView.getUrl()); shu.setClipboard(webView.getUrl());
Toast.makeText(getContext(), R.string.share__toast_link_address_copied, Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), R.string.link_adress_copied, Toast.LENGTH_SHORT).show();
return true; return true;
} }
@ -180,13 +180,13 @@ public class DiasporaStreamFragment extends BrowserFragment {
} }
case R.id.action_take_screenshot: { case R.id.action_take_screenshot: {
if (permc.doIfExtStoragePermissionGranted(getString(R.string.permissions_screenshot))) { if (permc.doIfExtStoragePermissionGranted(getString(R.string.screenshot_permission__appspecific))) {
File fileSaveDirectory = appSettings.getAppSaveDirectory(); File fileSaveDirectory = appSettings.getAppSaveDirectory();
if (permc.mkdirIfStoragePermissionGranted(fileSaveDirectory)) { if (permc.mkdirIfStoragePermissionGranted(fileSaveDirectory)) {
Bitmap bmp = ShareUtil.getBitmapFromWebView(webView); Bitmap bmp = ShareUtil.getBitmapFromWebView(webView);
String filename = "dandelion-" + ShareUtil.SDF_SHORT.format(new Date()) + ".jpg"; String filename = "dandelion-" + ShareUtil.SDF_SHORT.format(new Date()) + ".jpg";
_cu.writeImageToFileJpeg(new File(fileSaveDirectory, filename), bmp); _cu.writeImageToFile(new File(fileSaveDirectory, filename), bmp);
Snackbar.make(webView, getString(R.string.share__toast_screenshot) Snackbar.make(webView, getString(R.string.saving_screenshot_as)
+ " " + filename, Snackbar.LENGTH_LONG).show(); + " " + filename, Snackbar.LENGTH_LONG).show();
} }
} }
@ -194,8 +194,8 @@ public class DiasporaStreamFragment extends BrowserFragment {
} }
case R.id.action_share_screenshot: { case R.id.action_share_screenshot: {
if (permc.doIfExtStoragePermissionGranted(getString(R.string.permissions_screenshot))) { if (permc.doIfExtStoragePermissionGranted(getString(R.string.screenshot_permission__appspecific))) {
shu.shareImage(ShareUtil.getBitmapFromWebView(webView), Bitmap.CompressFormat.JPEG); shu.shareImage(ShareUtil.getBitmapFromWebView(webView));
} }
return true; return true;
} }
@ -293,7 +293,7 @@ public class DiasporaStreamFragment extends BrowserFragment {
if (hasWRITE_EXTERNAL_STORAGE != PackageManager.PERMISSION_GRANTED) { if (hasWRITE_EXTERNAL_STORAGE != PackageManager.PERMISSION_GRANTED) {
if (!shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { if (!shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
new ThemedAlertDialogBuilder(getContext(), appSettings) new ThemedAlertDialogBuilder(getContext(), appSettings)
.setMessage(R.string.permissions_image) .setMessage(R.string.image_permission_description__appspecific)
.setNegativeButton(android.R.string.no, null) .setNegativeButton(android.R.string.no, null)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override @Override

View file

@ -36,6 +36,7 @@ import android.support.customtabs.CustomTabsSession;
import android.support.design.widget.AppBarLayout; import android.support.design.widget.AppBarLayout;
import android.support.design.widget.NavigationView; import android.support.design.widget.NavigationView;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
@ -61,8 +62,8 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.github.dfa.diaspora_android.App; import com.github.dfa.diaspora_android.App;
import com.github.dfa.diaspora_android.BuildConfig;
import com.github.dfa.diaspora_android.R; import com.github.dfa.diaspora_android.R;
import com.github.dfa.diaspora_android.data.DiasporaAspect;
import com.github.dfa.diaspora_android.data.DiasporaPodList; import com.github.dfa.diaspora_android.data.DiasporaPodList;
import com.github.dfa.diaspora_android.data.DiasporaUserProfile; import com.github.dfa.diaspora_android.data.DiasporaUserProfile;
import com.github.dfa.diaspora_android.listener.DiasporaUserProfileChangedListener; import com.github.dfa.diaspora_android.listener.DiasporaUserProfileChangedListener;
@ -71,6 +72,7 @@ import com.github.dfa.diaspora_android.receiver.OpenExternalLinkReceiver;
import com.github.dfa.diaspora_android.receiver.UpdateTitleReceiver; import com.github.dfa.diaspora_android.receiver.UpdateTitleReceiver;
import com.github.dfa.diaspora_android.ui.BadgeDrawable; import com.github.dfa.diaspora_android.ui.BadgeDrawable;
import com.github.dfa.diaspora_android.ui.PodSelectionDialog; import com.github.dfa.diaspora_android.ui.PodSelectionDialog;
import com.github.dfa.diaspora_android.ui.SearchOrCustomTextDialogCreator;
import com.github.dfa.diaspora_android.ui.theme.ThemeHelper; import com.github.dfa.diaspora_android.ui.theme.ThemeHelper;
import com.github.dfa.diaspora_android.ui.theme.ThemedActivity; import com.github.dfa.diaspora_android.ui.theme.ThemedActivity;
import com.github.dfa.diaspora_android.ui.theme.ThemedAlertDialogBuilder; import com.github.dfa.diaspora_android.ui.theme.ThemedAlertDialogBuilder;
@ -262,7 +264,7 @@ public class MainActivity extends ThemedActivity
//Setup snackbar //Setup snackbar
snackbarExitApp = Snackbar snackbarExitApp = Snackbar
.make(fragmentContainer, R.string.confirm_exit, Snackbar.LENGTH_LONG) .make(fragmentContainer, R.string.do_you_want_to_exit, Snackbar.LENGTH_LONG)
.setAction(android.R.string.yes, new View.OnClickListener() { .setAction(android.R.string.yes, new View.OnClickListener() {
public void onClick(View view) { public void onClick(View view) {
finish(); finish();
@ -271,13 +273,13 @@ public class MainActivity extends ThemedActivity
}); });
snackbarLastVisitedTimestampInStream = snackbarLastVisitedTimestampInStream =
Snackbar.make(fragmentContainer, Snackbar.make(fragmentContainer,
R.string.jump_to_last_visited_timestamp_in_stream, Snackbar.LENGTH_LONG) R.string.jump_to_last_visited_page_in_stream__appspecific, Snackbar.LENGTH_LONG)
.setAction(android.R.string.yes, new View.OnClickListener() { .setAction(android.R.string.yes, new View.OnClickListener() {
public void onClick(View view) { public void onClick(View view) {
openDiasporaUrl(urls.getStreamWithTimestampUrl(diasporaUserProfile.getLastVisitedPositionInStream())); openDiasporaUrl(urls.getStreamWithTimestampUrl(diasporaUserProfile.getLastVisitedPositionInStream()));
} }
}); });
snackbarNoInternet = Snackbar.make(fragmentContainer, R.string.no_internet, Snackbar.LENGTH_LONG); snackbarNoInternet = Snackbar.make(fragmentContainer, R.string.sorry_need_to_be_connected_to_internet, Snackbar.LENGTH_LONG);
// Load app settings // Load app settings
setupNavigationSlider(); setupNavigationSlider();
@ -306,14 +308,6 @@ public class MainActivity extends ThemedActivity
BrowserFragment bf = new BrowserFragment(); BrowserFragment bf = new BrowserFragment();
fm.beginTransaction().add(bf, fragmentTag).commit(); fm.beginTransaction().add(bf, fragmentTag).commit();
return bf; return bf;
case TagListFragment.TAG:
TagListFragment hlf = new TagListFragment();
fm.beginTransaction().add(hlf, fragmentTag).commit();
return hlf;
case AspectListFragment.TAG:
AspectListFragment alf = new AspectListFragment();
fm.beginTransaction().add(alf, fragmentTag).commit();
return alf;
case PodSelectionFragment.TAG: case PodSelectionFragment.TAG:
PodSelectionFragment psf = new PodSelectionFragment(); PodSelectionFragment psf = new PodSelectionFragment();
fm.beginTransaction().add(psf, fragmentTag).commit(); fm.beginTransaction().add(psf, fragmentTag).commit();
@ -337,7 +331,7 @@ public class MainActivity extends ThemedActivity
// This URL seems to be called somehow, but it doesn't make sense ;) // This URL seems to be called somehow, but it doesn't make sense ;)
toolbarTop.postDelayed(() -> { toolbarTop.postDelayed(() -> {
Intent i = new Intent(ACTION_OPEN_EXTERNAL_URL); Intent i = new Intent(ACTION_OPEN_EXTERNAL_URL);
i.putExtra(EXTRA_URL, "https://github.com/Diaspora-for-Android/dandelion/blob/master/README.md"); i.putExtra(EXTRA_URL, "https://github.com/gsantner/dandelion/blob/master/README.md");
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(i); LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(i);
}, 1000); }, 1000);
return; return;
@ -370,6 +364,13 @@ public class MainActivity extends ThemedActivity
* @param fragment Fragment to show * @param fragment Fragment to show
*/ */
protected void showFragment(ThemedFragment fragment) { protected void showFragment(ThemedFragment fragment) {
if (PodSelectionFragment.TAG.equals(fragment.getTag())) {
Fragment fragment1 = fm.findFragmentByTag(DiasporaStreamFragment.TAG);
if (fragment1 != null) {
new net.gsantner.opoc.util.ContextUtils(this).restartApp(MainActivity.class);
}
}
AppLog.v(this, "showFragment()"); AppLog.v(this, "showFragment()");
ThemedFragment currentTop = (ThemedFragment) fm.findFragmentById(R.id.fragment_container); ThemedFragment currentTop = (ThemedFragment) fm.findFragmentById(R.id.fragment_container);
if (currentTop == null || !currentTop.getFragmentTag().equals(fragment.getFragmentTag())) { if (currentTop == null || !currentTop.getFragmentTag().equals(fragment.getFragmentTag())) {
@ -387,7 +388,7 @@ public class MainActivity extends ThemedActivity
*/ */
private void setupNavigationSlider() { private void setupNavigationSlider() {
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, navDrawer, toolbarTop, R.string.navigation_drawer_open, R.string.navigation_drawer_close); this, navDrawer, toolbarTop, R.string.open_navdrawer, R.string.close_navdrawer);
navDrawer.addDrawerListener(toggle); navDrawer.addDrawerListener(toggle);
toggle.syncState(); toggle.syncState();
@ -431,8 +432,6 @@ public class MainActivity extends ThemedActivity
app.getAvatarImageLoader().startImageDownload(navheaderImage, avatarUrl); app.getAvatarImageLoader().startImageDownload(navheaderImage, avatarUrl);
} }
} }
} else if (BuildConfig.IS_TEST_BUILD) {
navheaderImage.setImageResource(R.drawable.ic_launcher_test);
} }
updateNavigationViewEntryVisibilities(); updateNavigationViewEntryVisibilities();
} }
@ -459,7 +458,7 @@ public class MainActivity extends ThemedActivity
navMenu.findItem(R.id.nav_statistics).setVisible(_appSettings.isVisibleInNavStatistics()); navMenu.findItem(R.id.nav_statistics).setVisible(_appSettings.isVisibleInNavStatistics());
navMenu.findItem(R.id.nav_reports).setVisible(_appSettings.isVisibleInNavReports()); navMenu.findItem(R.id.nav_reports).setVisible(_appSettings.isVisibleInNavReports());
navMenu.findItem(R.id.nav_toggle_desktop_page).setVisible(_appSettings.isVisibleInNavToggleMobileDesktop()); navMenu.findItem(R.id.nav_toggle_desktop_page).setVisible(_appSettings.isVisibleInNavToggleMobileDesktop());
navMenu.findItem(R.id.nav_dandelion).setVisible(_appSettings.isVisibleInNavDandelionAccount()); navMenu.findItem(R.id.nav_product_support).setVisible(_appSettings.isVisibleInNavGsantnerAccount());
// Hide whole group (for logged in use) if no pod was selected // Hide whole group (for logged in use) if no pod was selected
@ -563,15 +562,15 @@ public class MainActivity extends ThemedActivity
} else if ("sc_new_post".equals(action)) { } else if ("sc_new_post".equals(action)) {
openDiasporaUrl(urls.getNewPostUrl()); openDiasporaUrl(urls.getNewPostUrl());
return; return;
} else if ("sc_nav_followed_tags".equals(action)) {
showFragment(getFragment(TagListFragment.TAG));
return;
} else if ("sc_aspects".equals(action)) {
showFragment(getFragment(AspectListFragment.TAG));
return;
} else if ("sc_activities".equals(action)) { } else if ("sc_activities".equals(action)) {
openDiasporaUrl(urls.getActivityUrl()); openDiasporaUrl(urls.getActivityUrl());
return; return;
} else if ("sc_contacts".equals(action)) {
onNavigationItemSelected(navView.getMenu().findItem(R.id.nav_aspects));
return;
} else if ("sc_tags".equals(action)) {
onNavigationItemSelected(navView.getMenu().findItem(R.id.nav_followed_tags));
return;
} }
//Catch split screen recreation //Catch split screen recreation
if (action != null && action.equals(Intent.ACTION_MAIN) && getTopFragment() != null) { if (action != null && action.equals(Intent.ACTION_MAIN) && getTopFragment() != null) {
@ -855,7 +854,7 @@ public class MainActivity extends ThemedActivity
public void onClick(DialogInterface dialogInterface, int which) { public void onClick(DialogInterface dialogInterface, int which) {
String query = input.getText().toString().trim().replaceAll((which == DialogInterface.BUTTON_NEGATIVE ? "\\*" : "\\#"), ""); String query = input.getText().toString().trim().replaceAll((which == DialogInterface.BUTTON_NEGATIVE ? "\\*" : "\\#"), "");
if (query.equals("")) { if (query.equals("")) {
Snackbar.make(fragmentContainer, R.string.search_alert_bypeople_validate_needsomedata, Snackbar.LENGTH_LONG).show(); Snackbar.make(fragmentContainer, R.string.please_add_a_name, Snackbar.LENGTH_LONG).show();
} else { } else {
openDiasporaUrl(which == DialogInterface.BUTTON_NEGATIVE ? urls.getSearchPeopleUrl(query) : urls.getSearchTagsUrl(query)); openDiasporaUrl(which == DialogInterface.BUTTON_NEGATIVE ? urls.getSearchPeopleUrl(query) : urls.getSearchTagsUrl(query));
} }
@ -867,8 +866,8 @@ public class MainActivity extends ThemedActivity
final AlertDialog dialog = new ThemedAlertDialogBuilder(this, _appSettings) final AlertDialog dialog = new ThemedAlertDialogBuilder(this, _appSettings)
.setView(layout).setTitle(R.string.search_alert_title) .setView(layout).setTitle(R.string.search_alert_title)
.setCancelable(true) .setCancelable(true)
.setPositiveButton(R.string.search_alert_tag, clickListener) .setPositiveButton(R.string.by_tags, clickListener)
.setNegativeButton(R.string.search_alert_people, clickListener) .setNegativeButton(R.string.by_people, clickListener)
.create(); .create();
input.setOnEditorActionListener(new TextView.OnEditorActionListener() { input.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@ -1064,12 +1063,39 @@ public class MainActivity extends ThemedActivity
break; break;
case R.id.nav_followed_tags: { case R.id.nav_followed_tags: {
showFragment(getFragment(TagListFragment.TAG)); SearchOrCustomTextDialogCreator.showDiasporaTagsDialog(this, arg -> {
if (arg.startsWith(SearchOrCustomTextDialogCreator.SPECIAL_PREFIX)) {
arg = arg.replace(SearchOrCustomTextDialogCreator.SPECIAL_PREFIX, "").trim();
if (arg.equals(getString(R.string.manage_hashtags))) {
openDiasporaUrl(urls.getManageTagsUrl());
} else {
openDiasporaUrl(urls.getAllFollowedTagsUrl());
}
} else {
openDiasporaUrl(urls.getSearchTagsUrl(arg));
}
});
} }
break; break;
case R.id.nav_aspects: { case R.id.nav_aspects: {
showFragment(getFragment(AspectListFragment.TAG)); SearchOrCustomTextDialogCreator.showDiasporaAspectsDialog(this, arg -> {
if (arg.startsWith(SearchOrCustomTextDialogCreator.SPECIAL_PREFIX)) {
arg = arg.replace(SearchOrCustomTextDialogCreator.SPECIAL_PREFIX, "").trim();
if (arg.equals(getString(R.string.manage_your_contact_list))) {
openDiasporaUrl(urls.getContactsUrl());
} else if (arg.equals(getString(R.string.nav_profile))) {
openDiasporaUrl(urls.getProfileUrl());
}
} else {
for (DiasporaAspect daspect : _appSettings.getAspects()) {
if (arg.equals(daspect.name)) {
openDiasporaUrl(urls.getAspectUrl(Long.toString(daspect.id)));
break;
}
}
}
});
} }
break; break;
@ -1150,8 +1176,8 @@ public class MainActivity extends ThemedActivity
} }
break; break;
case R.id.nav_dandelion: { case R.id.nav_product_support: {
openDiasporaUrl(urls.getProfileUrl("48b78420923501341ef3782bcb452bd5")); openDiasporaUrl(urls.getProfileUrl("d1cbdd70095301341e834860008dbc6c"));
} }
break; break;

View file

@ -161,7 +161,7 @@ public class PodSelectionFragment extends ThemedFragment implements SearchView.O
} catch (JSONException ignored) { } catch (JSONException ignored) {
} }
} else { } else {
Snackbar.make(listViewPod, R.string.podlist_error, Snackbar.LENGTH_SHORT).show(); Snackbar.make(listViewPod, R.string.could_not_retrieve_list_of_pods__appspecific, Snackbar.LENGTH_SHORT).show();
} }
} }
} }
@ -173,7 +173,7 @@ public class PodSelectionFragment extends ThemedFragment implements SearchView.O
rootView.setBackgroundColor(appSettings.isAmoledColorMode() ? Color.BLACK : Color.WHITE); rootView.setBackgroundColor(appSettings.isAmoledColorMode() ? Color.BLACK : Color.WHITE);
listViewPod.setDivider(new ColorDrawable(Color.GRAY)); listViewPod.setDivider(new ColorDrawable(Color.GRAY));
listViewPod.setDividerHeight(dividerHeight); listViewPod.setDividerHeight(dividerHeight);
int bgcolor = appSettings.isAmoledColorMode() ? Color.BLACK : appSettings.getAccentColor(); int bgcolor = appSettings.isAmoledColorMode() ? Color.DKGRAY : appSettings.getAccentColor();
buttonUseCustomPod.setBackgroundColor(bgcolor); buttonUseCustomPod.setBackgroundColor(bgcolor);
buttonUseCustomPod.setTextColor(_cu.shouldColorOnTopBeLight(bgcolor) ? Color.WHITE : Color.BLACK); buttonUseCustomPod.setTextColor(_cu.shouldColorOnTopBeLight(bgcolor) ? Color.WHITE : Color.BLACK);

View file

@ -131,7 +131,7 @@ public class SettingsActivity extends ThemedActivity implements SharedPreference
if (top != null && top.getFragmentTag().equals(SettingsFragmentProxy.TAG)) { if (top != null && top.getFragmentTag().equals(SettingsFragmentProxy.TAG)) {
ProxyHandler.ProxySettings newProxySettings = getAppSettings().getProxySettings(); ProxyHandler.ProxySettings newProxySettings = getAppSettings().getProxySettings();
if (oldProxySettings.isEnabled() && !newProxySettings.isEnabled()) { if (oldProxySettings.isEnabled() && !newProxySettings.isEnabled()) {
Toast.makeText(this, R.string.toast__proxy_disabled__restart_required, Toast.LENGTH_LONG).show(); Toast.makeText(this, R.string.app_needs_restart_to_disable_proxy_usage, Toast.LENGTH_LONG).show();
} }
} }
super.onBackPressed(); super.onBackPressed();
@ -226,7 +226,7 @@ public class SettingsActivity extends ThemedActivity implements SharedPreference
} else if (settings.isKeyEqual(key, R.string.pref_key__change_account)) { } else if (settings.isKeyEqual(key, R.string.pref_key__change_account)) {
new ThemedAlertDialogBuilder(getActivity(), AppSettings.get()) new ThemedAlertDialogBuilder(getActivity(), AppSettings.get())
.setTitle(getString(R.string.confirmation)) .setTitle(getString(R.string.confirmation))
.setMessage(getString(R.string.pref_warning__change_account)) .setMessage(getString(R.string.logout_warning_description))
.setNegativeButton(android.R.string.no, null) .setNegativeButton(android.R.string.no, null)
.setPositiveButton(android.R.string.yes, .setPositiveButton(android.R.string.yes,
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@ -271,6 +271,8 @@ public class SettingsActivity extends ThemedActivity implements SharedPreference
@Override @Override
public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) { public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
AppSettings settings = ((App) getActivity().getApplication()).getSettings();
DiasporaUrlHelper diasporaUrlHelper = new DiasporaUrlHelper(settings);
if (isAdded() && preference.hasKey()) { if (isAdded() && preference.hasKey()) {
String key = preference.getKey(); String key = preference.getKey();
if (key.equals(getString(R.string.pref_key__primary_color__preference_click))) { if (key.equals(getString(R.string.pref_key__primary_color__preference_click))) {
@ -279,6 +281,13 @@ public class SettingsActivity extends ThemedActivity implements SharedPreference
} else if (key.equals(getString(R.string.pref_key__accent_color__preference_click))) { } else if (key.equals(getString(R.string.pref_key__accent_color__preference_click))) {
showColorPickerDialog(2); showColorPickerDialog(2);
return true; return true;
} else if (key.equals(getString(R.string.pref_key__manage_theme))) {
Intent intent = new Intent(getActivity(), MainActivity.class);
intent.setAction(MainActivity.ACTION_OPEN_URL);
intent.putExtra(MainActivity.URL_MESSAGE, diasporaUrlHelper.getThemeUrl());
startActivity(intent);
getActivity().finish();
return true;
} }
} }
return super.onPreferenceTreeClick(screen, preference); return super.onPreferenceTreeClick(screen, preference);
@ -310,7 +319,7 @@ public class SettingsActivity extends ThemedActivity implements SharedPreference
final LineColorPicker base = dialogLayout.findViewById(R.id.color_picker_dialog__base_picker); final LineColorPicker base = dialogLayout.findViewById(R.id.color_picker_dialog__base_picker);
final LineColorPicker shade = dialogLayout.findViewById(R.id.color_picker_dialog__shade_picker); final LineColorPicker shade = dialogLayout.findViewById(R.id.color_picker_dialog__shade_picker);
title.setText(type == 1 ? R.string.pref_title__primary_color : R.string.pref_title__accent_color); title.setText(type == 1 ? R.string.primary_colors : R.string.accent_color);
title.setTextColor(getResources().getColor(R.color.white)); title.setTextColor(getResources().getColor(R.color.white));
final int[] current = (type == 1 ? appSettings.getPrimaryColorSettings() : appSettings.getAccentColorSettings()); final int[] current = (type == 1 ? appSettings.getPrimaryColorSettings() : appSettings.getAccentColorSettings());
base.setColors((type == 1 ? ColorPalette.getBaseColors(context) : ColorPalette.getAccentColors(context))); base.setColors((type == 1 ? ColorPalette.getBaseColors(context) : ColorPalette.getAccentColors(context)));
@ -412,7 +421,7 @@ public class SettingsActivity extends ThemedActivity implements SharedPreference
if (appSettings.isKeyEqual(key, R.string.pref_key__http_proxy_load_tor_preset)) { if (appSettings.isKeyEqual(key, R.string.pref_key__http_proxy_load_tor_preset)) {
appSettings.setProxyHttpHost("127.0.0.1"); appSettings.setProxyHttpHost("127.0.0.1");
appSettings.setProxyHttpPort(8118); appSettings.setProxyHttpPort(8118);
Toast.makeText(screen.getContext(), R.string.toast__proxy_orbot_preset_loaded, Toast.LENGTH_SHORT).show(); Toast.makeText(screen.getContext(), R.string.orbot_proxy_preset_loaded, Toast.LENGTH_SHORT).show();
return true; return true;
} }
} }
@ -477,17 +486,13 @@ public class SettingsActivity extends ThemedActivity implements SharedPreference
ThemedAlertDialogBuilder builder = new ThemedAlertDialogBuilder(getActivity(), appSettings); ThemedAlertDialogBuilder builder = new ThemedAlertDialogBuilder(getActivity(), appSettings);
builder.setTitle(R.string.confirmation) builder.setTitle(R.string.confirmation)
.setMessage(R.string.dialog_content__wipe_settings) .setMessage(R.string.wipe_settings_warning__appspecific)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialogInterface, int i) { public void onClick(DialogInterface dialogInterface, int i) {
appSettings.resetAppSettings(); appSettings.resetAppSettings();
appSettings.resetPodSettings(); appSettings.resetPodSettings();
Intent restartActivity = new Intent(getActivity(), MainActivity.class); new net.gsantner.opoc.util.ContextUtils(appSettings.getContext()).restartApp(MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(getActivity(), 12374, restartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager mgr = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, pendingIntent);
System.exit(0);
} }
}).setNegativeButton(android.R.string.cancel, null) }).setNegativeButton(android.R.string.cancel, null)
.create().show(); .create().show();

View file

@ -1,206 +0,0 @@
/*
This file is part of the dandelion*.
dandelion* is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dandelion* is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with the dandelion*.
If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.dfa.diaspora_android.activity;
import android.content.Context;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.support.v7.widget.AppCompatImageView;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.github.dfa.diaspora_android.App;
import com.github.dfa.diaspora_android.R;
import com.github.dfa.diaspora_android.listener.OnSomethingClickListener;
import com.github.dfa.diaspora_android.ui.theme.ThemedFragment;
import com.github.dfa.diaspora_android.util.AppSettings;
import com.github.dfa.diaspora_android.util.ContextUtils;
import com.github.dfa.diaspora_android.util.DiasporaUrlHelper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
/**
* Fragment that shows a list of the HashTags the user follows
*/
public class TagListFragment extends ThemedFragment implements OnSomethingClickListener<Object> {
public static final String TAG = "com.github.dfa.diaspora_android.TagListFragment";
@BindView(R.id.fragment_list__recycler_view)
public RecyclerView followedTagsRecyclerView;
@BindView(R.id.fragment_list__spacer)
public View space;
@BindView(R.id.fragment_list__root)
public RelativeLayout rootView;
protected App app;
protected DiasporaUrlHelper urls;
@Override
protected int getLayoutResId() {
return R.layout.recycler_list__fragment;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ButterKnife.bind(this, view);
app = (App) getActivity().getApplication();
AppSettings appSettings = app.getSettings();
urls = new DiasporaUrlHelper(appSettings);
followedTagsRecyclerView.setHasFixedSize(true);
followedTagsRecyclerView.setNestedScrollingEnabled(false);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext());
followedTagsRecyclerView.setLayoutManager(layoutManager);
final FollowedTagsAdapter adapter = new FollowedTagsAdapter(appSettings, this);
followedTagsRecyclerView.setAdapter(adapter);
//Set window title
getActivity().setTitle(R.string.nav_followed_tags);
}
@Override
public String getFragmentTag() {
return TAG;
}
@Override
public boolean onBackPressed() {
return false;
}
@Override
public void onSomethingClicked(Object null1, Integer null2, String tag) {
((MainActivity) getActivity()).openDiasporaUrl(urls.getSearchTagsUrl(tag));
}
@Override
protected void applyColorToViews() {
followedTagsRecyclerView.invalidate();
if (getAppSettings().isAmoledColorMode()) {
rootView.setBackgroundColor(Color.BLACK);
space.setBackgroundColor(Color.BLACK);
}
}
public static class FollowedTagsAdapter extends RecyclerView.Adapter<FollowedTagsAdapter.ViewHolder> {
private boolean isAmoledColorMode;
private AppSettings appSettings;
private String[] followedTagsList;
private List<String> followedTagsFavsList;
private OnSomethingClickListener<Object> tagClickedListener;
static class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.recycler_view__list_item__text)
public TextView title;
@BindView(R.id.recycler_view__list_item__favourite)
AppCompatImageView favouriteImage;
@BindView(R.id.recycler_view__list_item__root)
RelativeLayout root;
ViewHolder(View v) {
super(v);
ButterKnife.bind(this, v);
}
}
FollowedTagsAdapter(AppSettings appSettings, OnSomethingClickListener<Object> tagClickedListener) {
this.appSettings = appSettings;
this.followedTagsList = appSettings.getFollowedTags();
this.followedTagsFavsList = new ArrayList<>(Arrays.asList(appSettings.getFollowedTagsFavs()));
this.tagClickedListener = tagClickedListener;
this.isAmoledColorMode = appSettings.isAmoledColorMode();
}
@Override
public int getItemCount() {
return followedTagsList.length;
}
@Override
public FollowedTagsAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recycler_list__list_item_with_fav, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
// Alternating colors
final Context c = holder.root.getContext();
final String tag = followedTagsList[position];
holder.title.setText(tag);
if (position % 2 == 1) {
holder.root.setBackgroundColor(isAmoledColorMode ? Color.BLACK : ContextUtils.get().rcolor(R.color.alternate_row_color));
holder.title.setTextColor(isAmoledColorMode ? Color.GRAY : Color.BLACK);
} else {
holder.root.setBackgroundColor(isAmoledColorMode ? Color.BLACK : Color.WHITE);
holder.title.setTextColor(isAmoledColorMode ? Color.GRAY : Color.BLACK);
}
// Favourite (Star) Image
applyFavouriteImage(holder.favouriteImage, isFollowedTagFaved(tag));
// Click on fav button
holder.favouriteImage.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (isFollowedTagFaved(tag)) {
followedTagsFavsList.remove(followedTagsFavsList.indexOf(tag));
} else {
followedTagsFavsList.add(tag);
}
appSettings.setFollowedTagsFavs(followedTagsFavsList);
applyFavouriteImage(holder.favouriteImage, isFollowedTagFaved(tag));
}
});
holder.root.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
tagClickedListener.onSomethingClicked(null, null, tag);
}
});
}
private boolean isFollowedTagFaved(String tag) {
return followedTagsFavsList.contains(tag);
}
private void applyFavouriteImage(AppCompatImageView imageView, boolean isFaved) {
imageView.setImageResource(isFaved ? R.drawable.ic_star_filled_48px : R.drawable.ic_star_border_black_48px);
imageView.setColorFilter(isFaved ? appSettings.getAccentColor() : (isAmoledColorMode ? Color.GRAY : 0), PorterDuff.Mode.SRC_ATOP);
}
}
}

View file

@ -13,7 +13,7 @@ import java.util.List;
/** /**
* Created by gsantner (http://gsantner.net/ on 30.09.16. * Created by gsantner (gsantner AT mailbox DOT org on 30.09.16.
* DiasporaPodList - List container for DiasporaPod's, with methods to merge with other DiasporaPodLists * DiasporaPodList - List container for DiasporaPod's, with methods to merge with other DiasporaPodLists
* DiasporaPod - Data container for a Pod, can include N DiasporaPodUrl's * DiasporaPod - Data container for a Pod, can include N DiasporaPodUrl's
* DiasporaPodUrl - A Url of an DiasporaPod * DiasporaPodUrl - A Url of an DiasporaPod

View file

@ -32,7 +32,7 @@ import org.json.JSONObject;
/** /**
* User profile * User profile
* Created by gsantner (http://gsantner.net/) on 24.03.16. Part of dandelion*. * Created by gsantner (gsantner AT mailbox DOT org) on 24.03.16. Part of dandelion*.
*/ */
public class DiasporaUserProfile { public class DiasporaUserProfile {
private static final int MINIMUM_USERPROFILE_LOAD_TIMEDIFF = 5000; private static final int MINIMUM_USERPROFILE_LOAD_TIMEDIFF = 5000;

View file

@ -21,7 +21,7 @@ package com.github.dfa.diaspora_android.listener;
import com.github.dfa.diaspora_android.data.DiasporaUserProfile; import com.github.dfa.diaspora_android.data.DiasporaUserProfile;
/** /**
* Created by gsantner (http://gsantner.net/) on 26.03.16. * Created by gsantner (gsantner AT mailbox DOT org) on 26.03.16.
* Interface that needs to be implemented by classes that listen for Profile related changes * Interface that needs to be implemented by classes that listen for Profile related changes
*/ */
public interface DiasporaUserProfileChangedListener { public interface DiasporaUserProfileChangedListener {

View file

@ -59,7 +59,7 @@ public class FetchPodsService extends Service {
} }
class GetPodsTask extends AsyncTask<Void, Void, DiasporaPodList> { class GetPodsTask extends AsyncTask<Void, Void, DiasporaPodList> {
private static final String PODDY_PODLIST_URL = "https://raw.githubusercontent.com/Diaspora-for-Android/dandelion/master/app/src/main/res/raw/podlist.json"; private static final String PODDY_PODLIST_URL = "https://raw.githubusercontent.com/gsantner/dandelion/master/app/src/main/res/raw/podlist.json";
private final Service service; private final Service service;

View file

@ -37,7 +37,7 @@ import butterknife.OnItemSelected;
/** /**
* Dialog that helps the user configure a pod * Dialog that helps the user configure a pod
* Created by gsantner (http://gsantner.net) on 06.10.16. * Created by gsantner on 06.10.16.
*/ */
public class PodSelectionDialog extends ThemedAppCompatDialogFragment { public class PodSelectionDialog extends ThemedAppCompatDialogFragment {
public static final String TAG = "com.github.dfa.diaspora_android.ui.PodSelectionDialog"; public static final String TAG = "com.github.dfa.diaspora_android.ui.PodSelectionDialog";

View file

@ -0,0 +1,85 @@
package com.github.dfa.diaspora_android.ui;
import android.app.Activity;
import android.support.v4.content.ContextCompat;
import com.github.dfa.diaspora_android.R;
import com.github.dfa.diaspora_android.data.DiasporaAspect;
import com.github.dfa.diaspora_android.util.AppSettings;
import net.gsantner.opoc.ui.SearchOrCustomTextDialog;
import net.gsantner.opoc.util.Callback;
import java.util.ArrayList;
import java.util.Arrays;
public class SearchOrCustomTextDialogCreator {
public static final String SPECIAL_PREFIX = "\uD83D\uDCA0";
public static void showDiasporaTagsDialog(final Activity activity, final Callback.a1<String> callback) {
SearchOrCustomTextDialog.DialogOptions dopt = new SearchOrCustomTextDialog.DialogOptions();
baseConf(activity, dopt);
dopt.callback = callback;
dopt.isSearchEnabled = true;
dopt.searchHintText = R.string.search;
dopt.titleText = R.string.tags;
new Thread(() -> {
AppSettings appSettings = AppSettings.get();
ArrayList<String> hl = new ArrayList<>();
ArrayList<String> data = new ArrayList<>(Arrays.asList(appSettings.getFollowedTags()));
if (data.size() > 0) {
String highlighted = surroundString(data.remove(0));
data.add(0, highlighted);
hl.add(highlighted);
}
for (int strid : new int[]{R.string.manage_hashtags}) {
String special = surroundString(appSettings.rstr(strid));
data.add(0, special);
hl.add(special);
}
dopt.data = data;
dopt.highlightData = hl;
activity.runOnUiThread(() -> SearchOrCustomTextDialog.showMultiChoiceDialogWithSearchFilterUI(activity, dopt));
}).start();
}
private static String surroundString(String text) {
return SPECIAL_PREFIX + " " + text + " ";
}
public static void showDiasporaAspectsDialog(final Activity activity, final Callback.a1<String> callback) {
SearchOrCustomTextDialog.DialogOptions dopt = new SearchOrCustomTextDialog.DialogOptions();
baseConf(activity, dopt);
dopt.callback = callback;
dopt.isSearchEnabled = false;
dopt.titleText = R.string.contacts;
new Thread(() -> {
AppSettings appSettings = AppSettings.get();
ArrayList<String> hl = new ArrayList<>();
ArrayList<String> data = new ArrayList<>();
for (DiasporaAspect aspect : AppSettings.get().getAspects()) {
data.add(aspect.name);
}
for (int strid : new int[]{R.string.nav_profile, R.string.manage_your_contact_list}) {
String special = surroundString(appSettings.rstr(strid));
data.add(0, special);
hl.add(special);
}
dopt.data = data;
dopt.highlightData = hl;
activity.runOnUiThread(() -> SearchOrCustomTextDialog.showMultiChoiceDialogWithSearchFilterUI(activity, dopt));
}).start();
}
private static void baseConf(Activity activity, SearchOrCustomTextDialog.DialogOptions dopt) {
AppSettings as = new AppSettings(activity);
dopt.isDarkDialog = as.isAmoledColorMode();
dopt.textColor = ContextCompat.getColor(activity, dopt.isDarkDialog ? R.color.white : R.color.primary_text);
dopt.highlightColor = as.getAccentColor();
}
}

View file

@ -52,7 +52,7 @@ public class ActivityUtils extends net.gsantner.opoc.util.ActivityUtils {
public boolean showInfoIfUserNotConnectedToInternet(View anchor) { public boolean showInfoIfUserNotConnectedToInternet(View anchor) {
boolean isOnline = WebHelper.isOnline(_context); boolean isOnline = WebHelper.isOnline(_context);
if (!isOnline) { if (!isOnline) {
showSnackBar(R.string.no_internet, true); showSnackBar(R.string.sorry_need_to_be_connected_to_internet, true);
} }
return !isOnline; return !isOnline;
} }

View file

@ -37,7 +37,7 @@ import java.util.List;
/** /**
* Settings * Settings
* Created by gsantner (http://gsantner.net/) on 20.03.16. Part of dandelion*. * Created by gsantner (gsantner AT mailbox DOT org) on 20.03.16. Part of dandelion*.
*/ */
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
public class AppSettings extends SharedPreferencesPropertyBackend { public class AppSettings extends SharedPreferencesPropertyBackend {
@ -347,8 +347,8 @@ public class AppSettings extends SharedPreferencesPropertyBackend {
return getBool(R.string.pref_key__visibility_nav__reports, false); return getBool(R.string.pref_key__visibility_nav__reports, false);
} }
public boolean isVisibleInNavDandelionAccount() { public boolean isVisibleInNavGsantnerAccount() {
return getBool(R.string.pref_key__visibility_nav__dandelion_account, false); return getBool(R.string.pref_key__visibility_nav__gsantner_account, false);
} }
public boolean isVisibleInNavToggleMobileDesktop() { public boolean isVisibleInNavToggleMobileDesktop() {
@ -359,6 +359,14 @@ public class AppSettings extends SharedPreferencesPropertyBackend {
return getBool(R.string.pref_key__topbar_stream_shortcut, false); return getBool(R.string.pref_key__topbar_stream_shortcut, false);
} }
public boolean isOpenYoutubeExternalEnabled() {
return getBool(R.string.pref_key__open_youtube_external_enabled, true);
}
public boolean isSwipeRefreshEnabled() {
return getBool(R.string.pref_key__swipe_refresh_enabled, true);
}
public String getScreenRotation() { public String getScreenRotation() {
return getString(R.string.pref_key__screen_rotation, R.string.rotation_val_system); return getString(R.string.pref_key__screen_rotation, R.string.rotation_val_system);
} }
@ -444,6 +452,10 @@ public class AppSettings extends SharedPreferencesPropertyBackend {
return getBool(R.string.pref_key__primary_color__amoled_mode, false); return getBool(R.string.pref_key__primary_color__amoled_mode, false);
} }
public void setAmoledColorMode(boolean enable) {
setBool(R.string.pref_key__primary_color__amoled_mode, enable);
}
public boolean isAdBlockEnabled() { public boolean isAdBlockEnabled() {
return getBool(R.string.pref_key__adblock_enable, true); return getBool(R.string.pref_key__adblock_enable, true);
} }

View file

@ -62,6 +62,7 @@ public class DiasporaUrlHelper {
public static final String SUBURL_NOTIFICATIONS_MENTIONED = "/notifications?type=mentioned"; public static final String SUBURL_NOTIFICATIONS_MENTIONED = "/notifications?type=mentioned";
public static final String SUBURL_NOTIFICATIONS_RESHARED = "/notifications?type=reshared"; public static final String SUBURL_NOTIFICATIONS_RESHARED = "/notifications?type=reshared";
public static final String SUBURL_NOTIFICATIONS_STARTED_SHARING = "/notifications?type=started_sharing"; public static final String SUBURL_NOTIFICATIONS_STARTED_SHARING = "/notifications?type=started_sharing";
public static final String SUBURL_THEME = "/user/edit";
public DiasporaUrlHelper(AppSettings settings) { public DiasporaUrlHelper(AppSettings settings) {
this.settings = settings; this.settings = settings;
@ -229,6 +230,15 @@ public class DiasporaUrlHelper {
return getPodUrl() + SUBURL_SEARCH_TAGS + query; return getPodUrl() + SUBURL_SEARCH_TAGS + query;
} }
/**
* Return a url that queries posts for the given hashtag query
*
* @return https://(pod-domain.tld)/followed_tags
*/
public String getAllFollowedTagsUrl() {
return getPodUrl() + SUBURL_FOLOWED_TAGS;
}
/** /**
* Return a url that queries user accounts for query * Return a url that queries user accounts for query
* *
@ -344,4 +354,13 @@ public class DiasporaUrlHelper {
} }
return app.getString(R.string.aspects); return app.getString(R.string.aspects);
} }
/**
* Return a url that points to the settings of the pod.
*
* @return https://(pod-domain.tld)/user/edit
*/
public String getThemeUrl() {
return getPodUrl() + SUBURL_THEME;
}
} }

View file

@ -21,6 +21,7 @@ package com.github.dfa.diaspora_android.web;
import android.content.Context; import android.content.Context;
import android.content.MutableContextWrapper; import android.content.MutableContextWrapper;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.webkit.WebSettings; import android.webkit.WebSettings;
@ -50,6 +51,7 @@ public class BrowserFragment extends ThemedFragment {
protected WebSettings webSettings; protected WebSettings webSettings;
protected String pendingUrl; protected String pendingUrl;
protected SwipeRefreshLayout swipe;//pull to refresh
@Override @Override
protected int getLayoutResId() { protected int getLayoutResId() {
@ -90,6 +92,18 @@ public class BrowserFragment extends ThemedFragment {
webView.setParentActivity(getActivity()); webView.setParentActivity(getActivity());
this.setRetainInstance(true); this.setRetainInstance(true);
//pull to refresh
swipe = view.findViewById(R.id.swipe);
swipe.setDistanceToTriggerSync(2000);
swipe.setOnRefreshListener(() -> reloadUrl());
if (appSettings.isSwipeRefreshEnabled()) {
swipe.setEnabled(true);
} else {
swipe.setRefreshing(false);
swipe.setEnabled(false);
return;
}
} }
@Override @Override
@ -192,6 +206,7 @@ public class BrowserFragment extends ThemedFragment {
@Override @Override
public void run() { public void run() {
getWebView().reload(); getWebView().reload();
swipe.setRefreshing(false);//pull to refresh
} }
}); });

View file

@ -78,14 +78,14 @@ public class ContextMenuWebView extends NestedWebView {
public boolean onMenuItemClick(MenuItem item) { public boolean onMenuItemClick(MenuItem item) {
HitTestResult result = getHitTestResult(); HitTestResult result = getHitTestResult();
String url = result.getExtra(); String url = result.getExtra();
final ShareUtil shu = new ShareUtil(context).setFileProviderAuthority(BuildConfig.APPLICATION_ID); final ShareUtil shu = new ShareUtil(context);
final PermissionChecker permc = new PermissionChecker(parentActivity); final PermissionChecker permc = new PermissionChecker(parentActivity);
final AppSettings appSettings = new AppSettings(context); final AppSettings appSettings = new AppSettings(context);
switch (item.getItemId()) { switch (item.getItemId()) {
//Save image to external memory //Save image to external memory
case ID_SAVE_IMAGE: { case ID_SAVE_IMAGE: {
if (permc.doIfExtStoragePermissionGranted(context.getString(R.string.permissions_image))) { if (permc.doIfExtStoragePermissionGranted(context.getString(R.string.image_permission_description__appspecific))) {
File fileSaveDirectory = appSettings.getAppSaveDirectory(); File fileSaveDirectory = appSettings.getAppSaveDirectory();
if (permc.mkdirIfStoragePermissionGranted(fileSaveDirectory)) { if (permc.mkdirIfStoragePermissionGranted(fileSaveDirectory)) {
String filename = "dandelion-" + ShareUtil.SDF_SHORT.format(new Date()) + url.substring(url.lastIndexOf(".")); String filename = "dandelion-" + ShareUtil.SDF_SHORT.format(new Date()) + url.substring(url.lastIndexOf("."));
@ -95,7 +95,7 @@ public class ContextMenuWebView extends NestedWebView {
((DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE)).enqueue(request);*/ ((DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE)).enqueue(request);*/
new DownloadTask(new File(fileSaveDirectory, filename), (ok, dlfile) -> { new DownloadTask(new File(fileSaveDirectory, filename), (ok, dlfile) -> {
if (ok) { if (ok) {
Toast.makeText(context, context.getText(R.string.share__toast_saved_image_to_location) + " " + dlfile.getName(), Toast.LENGTH_LONG).show(); Toast.makeText(context, context.getText(R.string.saving_image_to) + " " + dlfile.getName(), Toast.LENGTH_LONG).show();
} }
}).execute(url); }).execute(url);
} }
@ -104,13 +104,13 @@ public class ContextMenuWebView extends NestedWebView {
} }
case ID_SHARE_IMAGE: { case ID_SHARE_IMAGE: {
if (permc.doIfExtStoragePermissionGranted(context.getString(R.string.permissions_image))) { if (permc.doIfExtStoragePermissionGranted(context.getString(R.string.image_permission_description__appspecific))) {
File fileSaveDirectory = appSettings.getAppSaveDirectory(); File fileSaveDirectory = appSettings.getAppSaveDirectory();
if (permc.mkdirIfStoragePermissionGranted(fileSaveDirectory)) { if (permc.mkdirIfStoragePermissionGranted(fileSaveDirectory)) {
String filename = ".dandelion-shared" + url.substring(url.lastIndexOf(".")); String filename = ".dandelion-shared" + url.substring(url.lastIndexOf("."));
new DownloadTask(new File(fileSaveDirectory, filename), (ok, dlfile) -> { new DownloadTask(new File(fileSaveDirectory, filename), (ok, dlfile) -> {
if (ok) { if (ok) {
Toast.makeText(context, context.getText(R.string.share__toast_saved_image_to_location) + " " + dlfile.getName(), Toast.LENGTH_LONG).show(); Toast.makeText(context, context.getText(R.string.saving_image_to) + " " + dlfile.getName(), Toast.LENGTH_LONG).show();
shu.shareStream(dlfile, "image/" + dlfile.getAbsolutePath().lastIndexOf(".") + 1); shu.shareStream(dlfile, "image/" + dlfile.getAbsolutePath().lastIndexOf(".") + 1);
} }
}).execute(url); }).execute(url);
@ -132,7 +132,7 @@ public class ContextMenuWebView extends NestedWebView {
if (url != null) { if (url != null) {
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setPrimaryClip(ClipData.newPlainText("text", url)); clipboard.setPrimaryClip(ClipData.newPlainText("text", url));
Toast.makeText(context, R.string.share__toast_link_address_copied, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.link_adress_copied, Toast.LENGTH_SHORT).show();
} }
break; break;
@ -144,7 +144,7 @@ public class ContextMenuWebView extends NestedWebView {
sendIntent.putExtra(Intent.EXTRA_TEXT, url); sendIntent.putExtra(Intent.EXTRA_TEXT, url);
sendIntent.setType("text/plain"); sendIntent.setType("text/plain");
context.startActivity(Intent.createChooser(sendIntent, getResources() context.startActivity(Intent.createChooser(sendIntent, getResources()
.getText(R.string.context_menu_share_link))); .getText(R.string.share_link_address)));
} }
break; break;
} }
@ -157,16 +157,16 @@ public class ContextMenuWebView extends NestedWebView {
result.getType() == HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { result.getType() == HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
// Menu options for an image. // Menu options for an image.
menu.setHeaderTitle(result.getExtra()); menu.setHeaderTitle(result.getExtra());
menu.add(0, ID_SAVE_IMAGE, 0, context.getString(R.string.context_menu_save_image)).setOnMenuItemClickListener(handler); menu.add(0, ID_SAVE_IMAGE, 0, context.getString(R.string.save_image)).setOnMenuItemClickListener(handler);
menu.add(0, ID_IMAGE_EXTERNAL_BROWSER, 0, context.getString(R.string.context_menu_open_external_browser)).setOnMenuItemClickListener(handler); menu.add(0, ID_IMAGE_EXTERNAL_BROWSER, 0, context.getString(R.string.open_in_external_browser)).setOnMenuItemClickListener(handler);
menu.add(0, ID_SHARE_IMAGE, 0, context.getString(R.string.context_menu_share_image)).setOnMenuItemClickListener(handler); menu.add(0, ID_SHARE_IMAGE, 0, context.getString(R.string.share_image)).setOnMenuItemClickListener(handler);
menu.add(0, ID_COPY_IMAGE_LINK, 0, context.getString(R.string.context_menu_copy_image_link)).setOnMenuItemClickListener(handler); menu.add(0, ID_COPY_IMAGE_LINK, 0, context.getString(R.string.copy_image_address_to_clipboard)).setOnMenuItemClickListener(handler);
} else if (result.getType() == HitTestResult.ANCHOR_TYPE || } else if (result.getType() == HitTestResult.ANCHOR_TYPE ||
result.getType() == HitTestResult.SRC_ANCHOR_TYPE) { result.getType() == HitTestResult.SRC_ANCHOR_TYPE) {
// Menu options for a hyperlink. // Menu options for a hyperlink.
menu.setHeaderTitle(result.getExtra()); menu.setHeaderTitle(result.getExtra());
menu.add(0, ID_COPY_LINK, 0, context.getString(R.string.copy_link_to_clipboard)).setOnMenuItemClickListener(handler); menu.add(0, ID_COPY_LINK, 0, context.getString(R.string.copy_link_adress_to_clipboard)).setOnMenuItemClickListener(handler);
menu.add(0, ID_SHARE_LINK, 0, context.getString(R.string.context_menu_share_link)).setOnMenuItemClickListener(handler); menu.add(0, ID_SHARE_LINK, 0, context.getString(R.string.share_link_address)).setOnMenuItemClickListener(handler);
} }
} }

View file

@ -20,6 +20,7 @@ package com.github.dfa.diaspora_android.web;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Intent; import android.content.Intent;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.webkit.CookieManager; import android.webkit.CookieManager;
@ -38,6 +39,7 @@ public class CustomWebViewClient extends WebViewClient {
private final App app; private final App app;
private String lastLoadUrl = ""; private String lastLoadUrl = "";
private boolean isAdBlockEnabled = false; private boolean isAdBlockEnabled = false;
AppSettings appSettings = AppSettings.get();
public CustomWebViewClient(App app, WebView webView) { public CustomWebViewClient(App app, WebView webView) {
this.app = app; this.app = app;
@ -56,6 +58,11 @@ public class CustomWebViewClient extends WebViewClient {
|| (host != null && (url.startsWith("https://" + host) || (host != null && (url.startsWith("https://" + host)
|| url.startsWith("http://" + host)))) { || url.startsWith("http://" + host)))) {
return false; return false;
}//make youtube links open external-->never customtab
else if (appSettings.isOpenYoutubeExternalEnabled() && (url.startsWith("https://youtube.com/") || url.startsWith("https://www.youtube.com/") || url.startsWith("https://m.youtube.com/") || url.startsWith("https://youtu.be/"))) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
view.getContext().startActivity(intent);
return true;
} else { } else {
Intent i = new Intent(MainActivity.ACTION_OPEN_EXTERNAL_URL); Intent i = new Intent(MainActivity.ACTION_OPEN_EXTERNAL_URL);
i.putExtra(MainActivity.EXTRA_URL, url); i.putExtra(MainActivity.EXTRA_URL, url);

View file

@ -32,7 +32,6 @@ import com.github.dfa.diaspora_android.activity.MainActivity;
/** /**
* Created by Gregor Santner on 07.08.16. * Created by Gregor Santner on 07.08.16.
* http://gsantner.net
*/ */
public class WebHelper { public class WebHelper {

View file

@ -1,7 +1,6 @@
/*####################################################### /*#######################################################
* *
* Maintained by Gregor Santner, 2017- * Maintained 2017-2023 by Gregor Santner <gsantner AT mailbox DOT org>
* https://gsantner.net/
* *
* License: Apache 2.0 * License: Apache 2.0
* https://github.com/gsantner/opoc/#licensing * https://github.com/gsantner/opoc/#licensing
@ -16,10 +15,15 @@ import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import net.gsantner.opoc.android.dummy.MenuItemDummy;
import net.gsantner.opoc.util.ContextUtils; import net.gsantner.opoc.util.ContextUtils;
import butterknife.ButterKnife; import butterknife.ButterKnife;
@ -33,6 +37,7 @@ public abstract class GsFragmentBase extends Fragment {
protected ContextUtils _cu; protected ContextUtils _cu;
protected Bundle _savedInstanceState = null; protected Bundle _savedInstanceState = null;
protected Menu _fragmentMenu = new MenuItemDummy.Menu();
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -51,6 +56,9 @@ public abstract class GsFragmentBase extends Fragment {
_cu = new ContextUtils(inflater.getContext()); _cu = new ContextUtils(inflater.getContext());
_cu.setAppLanguage(getAppLanguage()); _cu.setAppLanguage(getAppLanguage());
_savedInstanceState = savedInstanceState; _savedInstanceState = savedInstanceState;
if (getLayoutResId() == 0) {
Log.e(getClass().getCanonicalName(), "Error: GsFragmentbase.onCreateview: Returned 0 for getLayoutResId");
}
View view = inflater.inflate(getLayoutResId(), container, false); View view = inflater.inflate(getLayoutResId(), container, false);
ButterKnife.bind(this, view); ButterKnife.bind(this, view);
return view; return view;
@ -126,4 +134,27 @@ public abstract class GsFragmentBase extends Fragment {
} }
} }
} }
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
_fragmentMenu = menu;
}
public Menu getFragmentMenu() {
return _fragmentMenu;
}
/**
* Get the toolbar from activity
* Requires id to be set to @+id/toolbar
*/
@SuppressWarnings("ConstantConditions")
protected Toolbar getToolbar() {
try {
return (Toolbar) getActivity().findViewById(new ContextUtils(getActivity()).getResId(ContextUtils.ResType.ID, "toolbar"));
} catch (Exception e) {
return null;
}
}
} }

View file

@ -0,0 +1,351 @@
/*
* Maintained 2017-2023 by Gregor Santner <gsantner AT mailbox DOT org>
* License: Creative Commons Zero (CC0 1.0) / Public Domain
* http://creativecommons.org/publicdomain/zero/1.0/
*
* You can do whatever you want with this. If we meet some day, and you think it is worth it,
* you can buy me a drink in return. Provided as is without any kind of warranty. Do not blame
* or ask for support if something goes wrong. - Gregor Santner
*
* 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 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.
*
*/
package net.gsantner.opoc.android.dummy;
import android.content.ComponentName;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.view.ActionProvider;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
public class MenuItemDummy implements MenuItem {
private final int _itemId;
public MenuItemDummy(final int itemId) {
_itemId = itemId;
}
@Override
public int getItemId() {
return _itemId;
}
@Override
public int getGroupId() {
return 0;
}
@Override
public int getOrder() {
return 0;
}
@Override
public MenuItem setTitle(CharSequence title) {
return null;
}
@Override
public MenuItem setTitle(int title) {
return null;
}
@Override
public CharSequence getTitle() {
return null;
}
@Override
public MenuItem setTitleCondensed(CharSequence title) {
return null;
}
@Override
public CharSequence getTitleCondensed() {
return null;
}
@Override
public MenuItem setIcon(Drawable icon) {
return null;
}
@Override
public MenuItem setIcon(int iconRes) {
return null;
}
@Override
public Drawable getIcon() {
return null;
}
@Override
public MenuItem setIntent(Intent intent) {
return null;
}
@Override
public Intent getIntent() {
return null;
}
@Override
public MenuItem setShortcut(char numericChar, char alphaChar) {
return null;
}
@Override
public MenuItem setNumericShortcut(char numericChar) {
return null;
}
@Override
public char getNumericShortcut() {
return 0;
}
@Override
public MenuItem setAlphabeticShortcut(char alphaChar) {
return null;
}
@Override
public char getAlphabeticShortcut() {
return 0;
}
@Override
public MenuItem setCheckable(boolean checkable) {
return null;
}
@Override
public boolean isCheckable() {
return false;
}
@Override
public MenuItem setChecked(boolean checked) {
return null;
}
@Override
public boolean isChecked() {
return false;
}
@Override
public MenuItem setVisible(boolean visible) {
return null;
}
@Override
public boolean isVisible() {
return false;
}
@Override
public MenuItem setEnabled(boolean enabled) {
return null;
}
@Override
public boolean isEnabled() {
return false;
}
@Override
public boolean hasSubMenu() {
return false;
}
@Override
public SubMenu getSubMenu() {
return null;
}
@Override
public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener) {
return null;
}
@Override
public ContextMenu.ContextMenuInfo getMenuInfo() {
return null;
}
@Override
public void setShowAsAction(int actionEnum) {
}
@Override
public MenuItem setShowAsActionFlags(int actionEnum) {
return null;
}
@Override
public MenuItem setActionView(View view) {
return null;
}
@Override
public MenuItem setActionView(int resId) {
return null;
}
@Override
public View getActionView() {
return null;
}
@Override
public MenuItem setActionProvider(ActionProvider actionProvider) {
return null;
}
@Override
public ActionProvider getActionProvider() {
return null;
}
@Override
public boolean expandActionView() {
return false;
}
@Override
public boolean collapseActionView() {
return false;
}
@Override
public boolean isActionViewExpanded() {
return false;
}
@Override
public MenuItem setOnActionExpandListener(OnActionExpandListener listener) {
return null;
}
public static class Menu implements android.view.Menu {
@Override
public MenuItem add(CharSequence title) {
return add(0, 0, 0, "");
}
@Override
public MenuItem add(int titleRes) {
return add(0, 0, 0, "");
}
@Override
public MenuItem add(int groupId, int itemId, int order, CharSequence title) {
return new MenuItemDummy(itemId);
}
@Override
public MenuItem add(int groupId, int itemId, int order, int titleRes) {
return add(0, 0, 0, "");
}
@Override
public SubMenu addSubMenu(CharSequence title) {
return null;
}
@Override
public SubMenu addSubMenu(int titleRes) {
return null;
}
@Override
public SubMenu addSubMenu(int groupId, int itemId, int order, CharSequence title) {
return null;
}
@Override
public SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes) {
return null;
}
@Override
public int addIntentOptions(int groupId, int itemId, int order, ComponentName caller, Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems) {
return 0;
}
@Override
public void removeItem(int id) {
}
@Override
public void removeGroup(int groupId) {
}
@Override
public void clear() {
}
@Override
public void setGroupCheckable(int group, boolean checkable, boolean exclusive) {
}
@Override
public void setGroupVisible(int group, boolean visible) {
}
@Override
public void setGroupEnabled(int group, boolean enabled) {
}
@Override
public boolean hasVisibleItems() {
return false;
}
@Override
public MenuItem findItem(int id) {
return null;
}
@Override
public int size() {
return 0;
}
@Override
public MenuItem getItem(int index) {
return null;
}
@Override
public void close() {
}
@Override
public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
return false;
}
@Override
public boolean isShortcutKey(int keyCode, KeyEvent event) {
return false;
}
@Override
public boolean performIdentifierAction(int id, int flags) {
return false;
}
@Override
public void setQwertyMode(boolean isQwerty) {
}
}
}

View file

@ -0,0 +1,58 @@
/*
* Maintained 2017-2023 by Gregor Santner <gsantner AT mailbox DOT org>
* License: Creative Commons Zero (CC0 1.0) / Public Domain
* http://creativecommons.org/publicdomain/zero/1.0/
*
* You can do whatever you want with this. If we meet some day, and you think it is worth it,
* you can buy me a drink in return. Provided as is without any kind of warranty. Do not blame
* or ask for support if something goes wrong. - Gregor Santner
*
* 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 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.
*
*/
package net.gsantner.opoc.android.dummy;
import android.text.Editable;
import android.text.TextWatcher;
import net.gsantner.opoc.util.Callback;
@SuppressWarnings({"unused", "SpellCheckingInspection"})
public class TextWatcherDummy implements TextWatcher {
@Override
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
}
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
}
@Override
public void afterTextChanged(final Editable s) {
}
public static TextWatcher before(final Callback.a4<CharSequence, Integer, Integer, Integer> impl) {
return new TextWatcherDummy() {
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
impl.callback(s, start, count, after);
}
};
}
public static TextWatcher on(final Callback.a4<CharSequence, Integer, Integer, Integer> impl) {
return new TextWatcherDummy() {
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
impl.callback(s, start, before, count);
}
};
}
public static TextWatcher after(final Callback.a1<Editable> impl) {
return new TextWatcherDummy() {
public void afterTextChanged(final Editable s) {
impl.callback(s);
}
};
}
}

View file

@ -1,7 +1,6 @@
/*####################################################### /*#######################################################
* *
* Maintained by Gregor Santner, 2016- * Maintained 2018-2023 by Gregor Santner <gsantner AT mailbox DOT org>
* https://gsantner.net/
* *
* License: Apache 2.0 * License: Apache 2.0
* https://github.com/gsantner/opoc/#licensing * https://github.com/gsantner/opoc/#licensing
@ -93,8 +92,8 @@ public class SimpleMarkdownParser {
.replaceAll("!\\[(.*?)\\]\\((.*?)\\)", "<img src=\\'$2\\' alt='$1' />") // img .replaceAll("!\\[(.*?)\\]\\((.*?)\\)", "<img src=\\'$2\\' alt='$1' />") // img
.replaceAll("<(http|https):\\/\\/(.*)>", "<a href='$1://$2'>$1://$2</a>") // a href (DEP: img) .replaceAll("<(http|https):\\/\\/(.*)>", "<a href='$1://$2'>$1://$2</a>") // a href (DEP: img)
.replaceAll("\\[(.*?)\\]\\((.*?)\\)", "<a href=\\'$2\\'>$1</a>") // a href (DEP: img) .replaceAll("\\[(.*?)\\]\\((.*?)\\)", "<a href=\\'$2\\'>$1</a>") // a href (DEP: img)
.replaceAll("(?m)^([-*] )(.*)$", "<font color='#000001'>&#8226;</font> $2 ") // unordered list + end line .replaceAll("(?m)^[-*] (.*)$", "<font color='#000001'>&#8226;</font> $1 ") // unordered list + end line
.replaceAll("(?m)^ (-|\\*) ([^<]*)$", "&nbsp;&nbsp;<font color='#000001'>&#8226;</font> $2 ") // unordered list2 + end line .replaceAll("(?m)^ [-*] (.*)$", "&nbsp;&nbsp;<font color='#000001'>&#8226;</font> $1 ") // unordered list2 + end line
.replaceAll("`([^<]*)`", "<code>$1</code>") // code .replaceAll("`([^<]*)`", "<code>$1</code>") // code
.replace("\\*", "") // temporary replace escaped star symbol .replace("\\*", "") // temporary replace escaped star symbol
.replaceAll("(?m)\\*\\*(.*)\\*\\*", "<b>$1</b>") // bold (DEP: temp star) .replaceAll("(?m)\\*\\*(.*)\\*\\*", "<b>$1</b>") // bold (DEP: temp star)
@ -111,6 +110,7 @@ public class SimpleMarkdownParser {
public String filter(String text) { public String filter(String text) {
text = text text = text
.replace("New:", "<font color='#276230'>New:</font>") .replace("New:", "<font color='#276230'>New:</font>")
.replace("New features:", "<font color='#276230'>New:</font>")
.replace("Added:", "<font color='#276230'>Added:</font>") .replace("Added:", "<font color='#276230'>Added:</font>")
.replace("Add:", "<font color='#276230'>Add:</font>") .replace("Add:", "<font color='#276230'>Add:</font>")
.replace("Fixed:", "<font color='#005688'>Fixed:</font>") .replace("Fixed:", "<font color='#005688'>Fixed:</font>")
@ -124,6 +124,26 @@ public class SimpleMarkdownParser {
return text; return text;
} }
}; };
public final static SmpFilter FILTER_H_TO_SUP = new SmpFilter() {
@Override
public String filter(String text) {
text = text
.replace("<h1>", "<sup><sup><sup>")
.replace("</h1>", "</sup></sup></sup>")
.replace("<h2>", "<sup><sup>")
.replace("</h2>", "</sup></sup>")
.replace("<h3>", "<sup>")
.replace("</h3>", "</sup>")
;
return text;
}
};
public final static SmpFilter FILTER_NONE = new SmpFilter() {
@Override
public String filter(String text) {
return text;
}
};
//######################## //########################
//## Singleton //## Singleton

View file

@ -1,7 +1,6 @@
/*####################################################### /*#######################################################
* *
* Maintained by Gregor Santner, 2018- * Maintained 2018-2023 by Gregor Santner <gsantner AT mailbox DOT org>
* https://gsantner.net/
* *
* License: Apache 2.0 * License: Apache 2.0
* https://github.com/gsantner/opoc/#licensing * https://github.com/gsantner/opoc/#licensing
@ -45,5 +44,4 @@ public interface PropertyBackend<TKEY, TTHIS> {
TTHIS setIntList(TKEY key, List<Integer> value); TTHIS setIntList(TKEY key, List<Integer> value);
TTHIS setStringList(TKEY key, List<String> value); TTHIS setStringList(TKEY key, List<String> value);
} }

View file

@ -1,7 +1,6 @@
/*####################################################### /*#######################################################
* *
* Maintained by Gregor Santner, 2016- * Maintained 2016-2023 by Gregor Santner <gsantner AT mailbox DOT org>
* https://gsantner.net/
* *
* License: Apache 2.0 * License: Apache 2.0
* https://github.com/gsantner/opoc/#licensing * https://github.com/gsantner/opoc/#licensing
@ -42,8 +41,12 @@ import android.support.annotation.StringRes;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.text.TextUtils; import android.text.TextUtils;
import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List; import java.util.List;
@ -56,6 +59,8 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
protected static final String ARRAY_SEPARATOR = "%%%"; protected static final String ARRAY_SEPARATOR = "%%%";
protected static final String ARRAY_SEPARATOR_SUBSTITUTE = "§§§"; protected static final String ARRAY_SEPARATOR_SUBSTITUTE = "§§§";
public static final String SHARED_PREF_APP = "app"; public static final String SHARED_PREF_APP = "app";
private static String _debugLog = "";
// //
// Members, Constructors // Members, Constructors
@ -136,6 +141,22 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
return (pref != null && pref.length > 0 ? pref[0] : _prefApp); return (pref != null && pref.length > 0 ? pref[0] : _prefApp);
} }
public static void limitListTo(final List<?> list, int maxSize, boolean removeDuplicates) {
Object o;
int pos;
for (int i = 0; removeDuplicates && i < list.size(); i++) {
o = list.get(i);
while ((pos = list.lastIndexOf(o)) != i && pos >= 0) {
list.remove(pos);
}
}
while ((pos = list.size()) > maxSize && pos > 0) {
list.remove(list.size() - 1);
}
}
// //
// Getter for resources // Getter for resources
// //
@ -147,6 +168,14 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
return ContextCompat.getColor(_context, resColorId); return ContextCompat.getColor(_context, resColorId);
} }
public String[] rstrs(int... keyResourceIds) {
String[] ret = new String[keyResourceIds.length];
for (int i = 0; i < keyResourceIds.length; i++) {
ret[i] = rstr(keyResourceIds[i]);
}
return ret;
}
// //
// Getter & Setter for String // Getter & Setter for String
@ -172,11 +201,15 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
} }
public String getString(String key, String defaultValue, final SharedPreferences... pref) { public String getString(String key, String defaultValue, final SharedPreferences... pref) {
return gp(pref).getString(key, defaultValue); try {
return gp(pref).getString(key, defaultValue);
} catch (ClassCastException e) {
return defaultValue;
}
} }
public String getString(@StringRes int keyResourceId, String defaultValue, @StringRes int keyResourceIdDefaultValue, final SharedPreferences... pref) { public String getString(@StringRes int keyResourceId, String defaultValue, @StringRes int keyResourceIdDefaultValue, final SharedPreferences... pref) {
return gp(pref).getString(rstr(keyResourceId), rstr(keyResourceIdDefaultValue)); return getString(rstr(keyResourceId), rstr(keyResourceIdDefaultValue), pref);
} }
private void setStringListOne(String key, List<String> values, final SharedPreferences pref) { private void setStringListOne(String key, List<String> values, final SharedPreferences pref) {
@ -190,10 +223,8 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
private ArrayList<String> getStringListOne(String key, final SharedPreferences pref) { private ArrayList<String> getStringListOne(String key, final SharedPreferences pref) {
ArrayList<String> ret = new ArrayList<>(); ArrayList<String> ret = new ArrayList<>();
String value = pref String value = getString(key, ARRAY_SEPARATOR).replace(ARRAY_SEPARATOR_SUBSTITUTE, ARRAY_SEPARATOR);
.getString(key, ARRAY_SEPARATOR) if (value.equals(ARRAY_SEPARATOR) || TextUtils.isEmpty(value)) {
.replace(ARRAY_SEPARATOR_SUBSTITUTE, ARRAY_SEPARATOR);
if (value.equals(ARRAY_SEPARATOR)) {
return ret; return ret;
} }
ret.addAll(Arrays.asList(value.split(ARRAY_SEPARATOR))); ret.addAll(Arrays.asList(value.split(ARRAY_SEPARATOR)));
@ -227,6 +258,7 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
return list.toArray(new String[list.size()]); return list.toArray(new String[list.size()]);
} }
public ArrayList<String> getStringList(@StringRes int keyResourceId, final SharedPreferences... pref) { public ArrayList<String> getStringList(@StringRes int keyResourceId, final SharedPreferences... pref) {
return getStringListOne(rstr(keyResourceId), gp(pref)); return getStringListOne(rstr(keyResourceId), gp(pref));
} }
@ -247,11 +279,15 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
} }
public int getInt(@StringRes int keyResourceId, int defaultValue, final SharedPreferences... pref) { public int getInt(@StringRes int keyResourceId, int defaultValue, final SharedPreferences... pref) {
return gp(pref).getInt(rstr(keyResourceId), defaultValue); return getInt(rstr(keyResourceId), defaultValue, pref);
} }
public int getInt(String key, int defaultValue, final SharedPreferences... pref) { public int getInt(String key, int defaultValue, final SharedPreferences... pref) {
return gp(pref).getInt(key, defaultValue); try {
return gp(pref).getInt(key, defaultValue);
} catch (ClassCastException e) {
return defaultValue;
}
} }
public int getIntOfStringPref(@StringRes int keyResId, int defaultValue, final SharedPreferences... pref) { public int getIntOfStringPref(@StringRes int keyResId, int defaultValue, final SharedPreferences... pref) {
@ -274,7 +310,7 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
private ArrayList<Integer> getIntListOne(String key, final SharedPreferences pref) { private ArrayList<Integer> getIntListOne(String key, final SharedPreferences pref) {
ArrayList<Integer> ret = new ArrayList<>(); ArrayList<Integer> ret = new ArrayList<>();
String value = pref.getString(key, ARRAY_SEPARATOR); String value = getString(key, ARRAY_SEPARATOR);
if (value.equals(ARRAY_SEPARATOR)) { if (value.equals(ARRAY_SEPARATOR)) {
return ret; return ret;
} }
@ -331,11 +367,15 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
} }
public long getLong(@StringRes int keyResourceId, long defaultValue, final SharedPreferences... pref) { public long getLong(@StringRes int keyResourceId, long defaultValue, final SharedPreferences... pref) {
return gp(pref).getLong(rstr(keyResourceId), defaultValue); return getLong(rstr(keyResourceId), defaultValue, pref);
} }
public long getLong(String key, long defaultValue, final SharedPreferences... pref) { public long getLong(String key, long defaultValue, final SharedPreferences... pref) {
return gp(pref).getLong(key, defaultValue); try {
return gp(pref).getLong(key, defaultValue);
} catch (ClassCastException e) {
return defaultValue;
}
} }
// //
@ -350,11 +390,15 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
} }
public float getFloat(@StringRes int keyResourceId, float defaultValue, final SharedPreferences... pref) { public float getFloat(@StringRes int keyResourceId, float defaultValue, final SharedPreferences... pref) {
return gp(pref).getFloat(rstr(keyResourceId), defaultValue); return getFloat(rstr(keyResourceId), defaultValue);
} }
public float getFloat(String key, float defaultValue, final SharedPreferences... pref) { public float getFloat(String key, float defaultValue, final SharedPreferences... pref) {
return gp(pref).getFloat(key, defaultValue); try {
return gp(pref).getFloat(key, defaultValue);
} catch (ClassCastException e) {
return defaultValue;
}
} }
// //
@ -388,22 +432,26 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
} }
public boolean getBool(@StringRes int keyResourceId, boolean defaultValue, final SharedPreferences... pref) { public boolean getBool(@StringRes int keyResourceId, boolean defaultValue, final SharedPreferences... pref) {
return gp(pref).getBoolean(rstr(keyResourceId), defaultValue); return getBool(rstr(keyResourceId), defaultValue);
} }
public boolean getBool(String key, boolean defaultValue, final SharedPreferences... pref) { public boolean getBool(String key, boolean defaultValue, final SharedPreferences... pref) {
return gp(pref).getBoolean(key, defaultValue); try {
return gp(pref).getBoolean(key, defaultValue);
} catch (ClassCastException e) {
return defaultValue;
}
} }
// //
// Getter & Setter for Color // Getter & Setter for Color
// //
public int getColor(String key, @ColorRes int defaultColor, final SharedPreferences... pref) { public int getColor(String key, @ColorRes int defaultColor, final SharedPreferences... pref) {
return gp(pref).getInt(key, rcolor(defaultColor)); return getInt(key, rcolor(defaultColor));
} }
public int getColor(@StringRes int keyResourceId, @ColorRes int defaultColor, final SharedPreferences... pref) { public int getColor(@StringRes int keyResourceId, @ColorRes int defaultColor, final SharedPreferences... pref) {
return gp(pref).getInt(rstr(keyResourceId), rcolor(defaultColor)); return getColor(rstr(keyResourceId), defaultColor);
} }
// //
@ -496,4 +544,74 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
setStringListOne(key, value, _prefApp); setStringListOne(key, value, _prefApp);
return this; return this;
} }
public boolean contains(String key, final SharedPreferences... pref) {
return gp(pref).contains(key);
}
/**
* A method to determine if current hour is between begin and end.
* This is especially useful for time-based light/dark mode
*/
public static boolean isCurrentHourOfDayBetween(int begin, int end) {
begin = (begin >= 23 || begin < 0) ? 0 : begin;
end = (end >= 23 || end < 0) ? 0 : end;
int h = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
return h >= begin && h <= end;
}
/**
* Substract current datetime by given amount of days
*/
public Date getDateOfDaysAgo(int days) {
Calendar cal = new GregorianCalendar();
cal.add(Calendar.DATE, -days);
return cal.getTime();
}
/**
* Substract current datetime by given amount of days and check if the given date passed
*/
public boolean didDaysPassedSince(Date date, int days) {
if (date == null || days < 0) {
return false;
}
return date.before(getDateOfDaysAgo(days));
}
public boolean afterDaysTrue(String key, int daysSinceLastTime, int firstTime, final SharedPreferences... pref) {
Date d = new Date(System.currentTimeMillis());
if (!contains(key)) {
d = getDateOfDaysAgo(daysSinceLastTime - firstTime);
setLong(key, d.getTime());
return firstTime < 1;
} else {
d = new Date(getLong(key, d.getTime()));
}
boolean trigger = didDaysPassedSince(d, daysSinceLastTime);
if (trigger) {
setLong(key, new Date(System.currentTimeMillis()).getTime());
}
return trigger;
}
public static void clearDebugLog() {
_debugLog = "";
}
public static String getDebugLog() {
return _debugLog;
}
public static synchronized void appendDebugLog(String text) {
_debugLog += "[" + new Date().toString() + "] " + text + "\n";
}
public static boolean ne(final String str) {
return str != null && !str.trim().isEmpty();
}
public static boolean fexists(final String fp) {
return ne(fp) && (new File(fp)).exists();
}
} }

View file

@ -1,7 +1,6 @@
/*####################################################### /*#######################################################
* *
* Maintained by Gregor Santner, 2017- * Maintained 2017-2023 by Gregor Santner <gsantner AT mailbox DOT org>
* https://gsantner.net/
* *
* License: Apache 2.0 * License: Apache 2.0
* https://github.com/gsantner/opoc/#licensing * https://github.com/gsantner/opoc/#licensing

View file

@ -0,0 +1,394 @@
/*#######################################################
*
* Maintained 2017-2023 by Gregor Santner <gsantner AT mailbox DOT org>
*
* License: Apache 2.0
* https://github.com/gsantner/opoc/#licensing
* https://www.apache.org/licenses/LICENSE-2.0
*
#########################################################*/
package net.gsantner.opoc.ui;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.AppCompatEditText;
import android.support.v7.widget.TooltipCompat;
import android.text.InputType;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Checkable;
import android.widget.Filter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import net.gsantner.opoc.android.dummy.TextWatcherDummy;
import net.gsantner.opoc.util.Callback;
import net.gsantner.opoc.util.ContextUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Pattern;
@SuppressLint("SetTextI18n")
public class SearchOrCustomTextDialog {
public static class DialogOptions {
// Callback for search text or text of single item
@Nullable
public Callback.a1<String> callback = null;
// Callback for indices of selected items.
// List will contain single item if isMultiSelectEnabled == false;
@Nullable
public Callback.a1<List<Integer>> positionCallback = null;
public boolean isMultiSelectEnabled = false;
public List<? extends CharSequence> data = null;
public List<? extends CharSequence> highlightData = null;
public List<Integer> iconsForData;
public String messageText = "";
public String defaultText = "";
public boolean isSearchEnabled = true;
public boolean isDarkDialog = false;
public int dialogWidthDp = WindowManager.LayoutParams.MATCH_PARENT;
public int dialogHeightDp = WindowManager.LayoutParams.WRAP_CONTENT;
public int gravity = Gravity.NO_GRAVITY;
public int searchInputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
public boolean searchIsRegex = false;
public Callback.a1<Spannable> highlighter = null;
public String extraFilter = null;
public List<Integer> preSelected = null;
public Callback.a0 neutralButtonCallback = null;
@ColorInt
public int textColor = 0xFF000000;
@ColorInt
public int highlightColor = 0xFF00FF00;
@StringRes
public int cancelButtonText = android.R.string.cancel;
@StringRes
public int neutralButtonText = 0;
@StringRes
public int okButtonText = android.R.string.ok;
@StringRes
public int titleText = 0;
@StringRes
public int searchHintText = android.R.string.search_go;
@DrawableRes
public int clearInputIcon = android.R.drawable.ic_input_delete;
}
private static class Adapter extends ArrayAdapter<Integer> {
@LayoutRes
private final int _layout;
private final int _layoutHeight;
private final LayoutInflater _inflater;
private final DialogOptions _dopt;
private final List<Integer> _filteredItems;
private final Set<Integer> _selectedItems;
private final Pattern _extraPattern;
public static Adapter create(final Context context, final DialogOptions dopt) {
return new Adapter(context, dopt, dopt.isMultiSelectEnabled ? android.R.layout.simple_list_item_multiple_choice : android.R.layout.simple_list_item_1, new ArrayList<>());
}
private Adapter(final Context context, final DialogOptions dopt, final int layout, final List<Integer> filteredItems) {
super(context, layout, filteredItems);
_layout = layout;
_filteredItems = filteredItems;
_inflater = LayoutInflater.from(context);
_dopt = dopt;
_extraPattern = (_dopt.extraFilter == null ? null : Pattern.compile(_dopt.extraFilter));
_selectedItems = new HashSet<>(_dopt.preSelected != null ? _dopt.preSelected : Collections.emptyList());
ContextUtils cu = new ContextUtils(context);
_layoutHeight = (int) cu.convertDpToPx(36);
cu.freeContextRef();
}
@NonNull
@Override
public View getView(int pos, @Nullable View convertView, @NonNull ViewGroup parent) {
final int index = getItem(pos);
final TextView textView;
if (convertView == null) {
textView = (TextView) _inflater.inflate(_layout, parent, false);
textView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
textView.setMinHeight(_layoutHeight);
} else {
textView = (TextView) convertView;
}
if (textView instanceof Checkable) {
((Checkable) textView).setChecked(_selectedItems.contains(index));
}
if (index >= 0 && _dopt.iconsForData != null && index < _dopt.iconsForData.size() && _dopt.iconsForData.get(index) != 0) {
textView.setCompoundDrawablesWithIntrinsicBounds(_dopt.iconsForData.get(index), 0, 0, 0);
textView.setCompoundDrawablePadding(32);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
textView.setCompoundDrawableTintList(ColorStateList.valueOf(_dopt.isDarkDialog ? Color.WHITE : Color.BLACK));
}
} else {
textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}
final CharSequence text = _dopt.data.get(index).toString();
if (_dopt.highlightData != null) {
final boolean hl = _dopt.highlightData.contains(text);
textView.setTextColor(hl ? _dopt.highlightColor : _dopt.textColor);
textView.setTypeface(null, hl ? Typeface.BOLD : Typeface.NORMAL);
}
if (_dopt.highlighter != null) {
Spannable s = new SpannableString(text);
_dopt.highlighter.callback(s);
textView.setText(s);
} else {
textView.setText(text);
}
return textView;
}
@NonNull
@Override
public Filter getFilter() {
return new Filter() {
@SuppressWarnings("unchecked")
@Override
protected void publishResults(final CharSequence constraint, final FilterResults results) {
_filteredItems.clear();
_filteredItems.addAll((List<Integer>) results.values);
notifyDataSetChanged();
}
@Override
protected FilterResults performFiltering(final CharSequence constraint) {
final List<Integer> resList = new ArrayList<>();
if (_dopt.data != null) {
final String fil = constraint.toString();
final boolean emptySearch = fil.isEmpty();
for (int i = 0; i < _dopt.data.size(); i++) {
final String str = _dopt.data.get(i).toString();
final boolean matchExtra = (_extraPattern == null) || _extraPattern.matcher(str).find();
final Locale locale = Locale.getDefault();
final boolean matchNormal = str.toLowerCase(locale).contains(fil.toLowerCase(locale));
final boolean matchRegex = _dopt.searchIsRegex && (str.matches(fil));
if (matchExtra && (matchNormal || matchRegex || emptySearch)) {
resList.add(i);
}
}
}
final FilterResults res = new FilterResults();
res.values = resList;
res.count = resList.size();
return res;
}
};
}
}
public static void showMultiChoiceDialogWithSearchFilterUI(final Activity activity, final DialogOptions dopt) {
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(activity, dopt.isDarkDialog
? android.support.v7.appcompat.R.style.Theme_AppCompat_Dialog
: android.support.v7.appcompat.R.style.Theme_AppCompat_Light_Dialog
);
final Adapter listAdapter = Adapter.create(activity, dopt);
final AppCompatEditText searchEditText = new AppCompatEditText(activity);
searchEditText.setText(dopt.defaultText);
searchEditText.setSingleLine(true);
searchEditText.setMaxLines(1);
searchEditText.setTextColor(dopt.textColor);
searchEditText.setHintTextColor((dopt.textColor & 0x00FFFFFF) | 0x99000000);
searchEditText.setHint(dopt.searchHintText);
searchEditText.setInputType(dopt.searchInputType == 0 ? searchEditText.getInputType() : dopt.searchInputType);
searchEditText.addTextChangedListener(TextWatcherDummy.after((cbEditable) -> listAdapter.getFilter().filter(cbEditable)));
final ContextUtils cu = new ContextUtils(activity);
final int margin = (int) cu.convertDpToPx(8);
cu.freeContextRef();
final LinearLayout searchLayout = new LinearLayout(activity);
searchLayout.setOrientation(LinearLayout.HORIZONTAL);
LinearLayout.LayoutParams lp;
lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT, 1);
lp.gravity = Gravity.START | Gravity.BOTTOM;
searchLayout.addView(searchEditText, lp);
// 'Button to clear the search box'
final ImageView clearButton = new ImageView(activity);
clearButton.setImageResource(dopt.clearInputIcon);
TooltipCompat.setTooltipText(clearButton, activity.getString(android.R.string.cancel));
clearButton.setColorFilter(dopt.isDarkDialog ? Color.WHITE : Color.parseColor("#ff505050"));
lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT, 0);
lp.gravity = Gravity.END | Gravity.CENTER_VERTICAL;
lp.setMargins(margin, 0, (int) (margin * 1.5), 0);
searchLayout.addView(clearButton, lp);
clearButton.setOnClickListener((v) -> searchEditText.setText(""));
final ListView listView = new ListView(activity);
final LinearLayout linearLayout = new LinearLayout(activity);
listView.setAdapter(listAdapter);
listView.setVisibility(dopt.data != null && !dopt.data.isEmpty() ? View.VISIBLE : View.GONE);
linearLayout.setOrientation(LinearLayout.VERTICAL);
if (dopt.isSearchEnabled) {
lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
lp.setMargins(margin, margin / 2, margin, margin / 2);
linearLayout.addView(searchLayout, lp);
}
final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0);
layoutParams.weight = 1;
linearLayout.addView(listView, layoutParams);
if (!TextUtils.isEmpty(dopt.messageText)) {
dialogBuilder.setMessage(dopt.messageText);
}
dialogBuilder.setView(linearLayout)
.setOnCancelListener(null)
.setNegativeButton(dopt.cancelButtonText, (dialogInterface, i) -> dialogInterface.dismiss());
if (dopt.titleText != 0) {
dialogBuilder.setTitle(dopt.titleText);
}
// Ok button action
if ((dopt.isSearchEnabled && dopt.callback != null) || (dopt.isMultiSelectEnabled)) {
dialogBuilder.setPositiveButton(dopt.okButtonText, (dialogInterface, i) -> {
final String searchText = dopt.isSearchEnabled ? searchEditText.getText().toString() : null;
if (dopt.positionCallback != null && !listAdapter._selectedItems.isEmpty()) {
final List<Integer> sel = new ArrayList<>(listAdapter._selectedItems);
Collections.sort(sel);
dopt.positionCallback.callback(sel);
} else if (dopt.callback != null && !TextUtils.isEmpty(searchText)) {
dopt.callback.callback(searchText);
}
});
}
// Setup neutralbutton
if (dopt.neutralButtonCallback != null && dopt.neutralButtonText != 0) {
dialogBuilder.setNeutralButton(dopt.neutralButtonText, (dialogInterface, i) -> {
dopt.neutralButtonCallback.callback();
});
}
final AlertDialog dialog = dialogBuilder.create();
searchEditText.setOnKeyListener((keyView, keyCode, keyEvent) -> {
if ((keyEvent.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
dialog.dismiss();
if (dopt.callback != null && !TextUtils.isEmpty(searchEditText.getText().toString())) {
dopt.callback.callback(searchEditText.getText().toString());
}
return true;
}
return false;
});
Window w;
if ((w = dialog.getWindow()) != null && dopt.isSearchEnabled) {
w.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
}
dialog.show();
if ((w = dialog.getWindow()) != null) {
int ds_w = dopt.dialogWidthDp < 100 ? dopt.dialogWidthDp : ((int) (dopt.dialogWidthDp * activity.getResources().getDisplayMetrics().density));
int ds_h = dopt.dialogHeightDp < 100 ? dopt.dialogHeightDp : ((int) (dopt.dialogHeightDp * activity.getResources().getDisplayMetrics().density));
w.setLayout(ds_w, ds_h);
}
if ((w = dialog.getWindow()) != null && dopt.gravity != Gravity.NO_GRAVITY) {
WindowManager.LayoutParams wlp = w.getAttributes();
wlp.gravity = dopt.gravity;
w.setAttributes(wlp);
}
if (dopt.isSearchEnabled) {
searchEditText.requestFocus();
}
if (dopt.defaultText != null) {
listAdapter.getFilter().filter(searchEditText.getText());
}
// Helper function to trigger callback with single item
final Callback.b1<Integer> directActivate = (position) -> {
final int index = listAdapter._filteredItems.get(position);
dialog.dismiss();
if (dopt.callback != null) {
dopt.callback.callback(dopt.data.get(index).toString());
}
if (dopt.positionCallback != null) {
dopt.positionCallback.callback(Collections.singletonList(index));
}
return true;
};
// Helper function to append selection count to OK button
final Button okButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
final String okText = activity.getString(dopt.okButtonText) + (dopt.isMultiSelectEnabled ? " (%d)" : "");
final Callback.a0 setOkButtonState = () -> {
okButton.setText(okText.replace("%d", Integer.toString(listAdapter._selectedItems.size())));
};
// Set ok button text initially
setOkButtonState.callback();
// Item click action
listView.setOnItemClickListener((parent, textView, pos, id) -> {
if (dopt.isMultiSelectEnabled) {
final int index = listAdapter._filteredItems.get(pos);
if (listAdapter._selectedItems.contains(index)) {
listAdapter._selectedItems.remove(index);
} else {
listAdapter._selectedItems.add(index);
}
if (textView instanceof Checkable) {
((Checkable) textView).setChecked(listAdapter._selectedItems.contains(index));
}
setOkButtonState.callback();
} else {
directActivate.callback(pos);
}
});
// long click always activates
listView.setOnItemLongClickListener((parent, view, pos, id) -> directActivate.callback(pos));
}
}

View file

@ -1,23 +1,30 @@
/*####################################################### /*#######################################################
* *
* Maintained by Gregor Santner, 2016- * Maintained 2016-2023 by Gregor Santner <gsantner AT mailbox DOT org>
* https://gsantner.net/
* *
* License: Apache 2.0 * License of this file: Apache 2.0
* https://github.com/gsantner/opoc/#licensing * https://www.apache.org/licenses/LICENSE-2.0
* https://www.apache.org/licenses/LICENSE-2.0 * https://github.com/gsantner/opoc/#licensing
* *
#########################################################*/ #########################################################*/
package net.gsantner.opoc.util; package net.gsantner.opoc.util;
import android.app.Activity; import android.app.Activity;
import android.app.ActivityManager;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.provider.CalendarContract;
import android.support.annotation.ColorInt;
import android.support.annotation.StringRes; import android.support.annotation.StringRes;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.support.v7.widget.AppCompatTextView; import android.support.v7.widget.AppCompatTextView;
import android.text.Html; import android.text.Html;
@ -25,12 +32,16 @@ import android.text.SpannableString;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.View; import android.view.View;
import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.webkit.WebView; import android.webkit.WebView;
import android.widget.ScrollView;
import java.util.List;
@SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue", "SpellCheckingInspection"}) @SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue", "SpellCheckingInspection", "rawtypes", "UnusedReturnValue"})
public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils { public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
//######################## //########################
//## Members, Constructors //## Members, Constructors
@ -42,6 +53,12 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
_activity = activity; _activity = activity;
} }
@Override
public void freeContextRef() {
super.freeContextRef();
_activity = null;
}
//######################## //########################
//## Methods //## Methods
//######################## //########################
@ -80,9 +97,11 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
} }
public void showSnackBar(@StringRes int stringResId, boolean showLong) { public Snackbar showSnackBar(@StringRes int stringResId, boolean showLong) {
Snackbar.make(_activity.findViewById(android.R.id.content), stringResId, Snackbar s = Snackbar.make(_activity.findViewById(android.R.id.content), stringResId,
showLong ? Snackbar.LENGTH_LONG : Snackbar.LENGTH_SHORT).show(); showLong ? Snackbar.LENGTH_LONG : Snackbar.LENGTH_SHORT);
s.show();
return s;
} }
public void showSnackBar(@StringRes int stringResId, boolean showLong, @StringRes int actionResId, View.OnClickListener listener) { public void showSnackBar(@StringRes int stringResId, boolean showLong, @StringRes int actionResId, View.OnClickListener listener) {
@ -92,18 +111,58 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
.show(); .show();
} }
public void hideSoftKeyboard() { public ActivityUtils setSoftKeyboardVisibile(boolean visible, View... editView) {
InputMethodManager imm = (InputMethodManager) _activity.getSystemService(Activity.INPUT_METHOD_SERVICE); final Activity activity = _activity;
if (imm != null && _activity.getCurrentFocus() != null && _activity.getCurrentFocus().getWindowToken() != null) { if (activity != null) {
imm.hideSoftInputFromWindow(_activity.getCurrentFocus().getWindowToken(), 0); final View v = (editView != null && editView.length > 0) ? (editView[0]) : (activity.getCurrentFocus() != null && activity.getCurrentFocus().getWindowToken() != null ? activity.getCurrentFocus() : null);
final InputMethodManager imm = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
if (v != null && imm != null) {
Runnable r = () -> {
if (visible) {
v.requestFocus();
imm.showSoftInput(v, InputMethodManager.SHOW_FORCED);
} else {
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
};
r.run();
for (int d : new int[]{100, 350}) {
v.postDelayed(r, d);
}
}
} }
return this;
} }
public void showSoftKeyboard() { public ActivityUtils hideSoftKeyboard() {
InputMethodManager imm = (InputMethodManager) _activity.getSystemService(Activity.INPUT_METHOD_SERVICE); if (_activity != null) {
if (imm != null && _activity.getCurrentFocus() != null && _activity.getCurrentFocus().getWindowToken() != null) { InputMethodManager imm = (InputMethodManager) _activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
imm.showSoftInput(_activity.getCurrentFocus(), InputMethodManager.SHOW_FORCED); if (imm != null && _activity.getCurrentFocus() != null && _activity.getCurrentFocus().getWindowToken() != null) {
imm.hideSoftInputFromWindow(_activity.getCurrentFocus().getWindowToken(), 0);
}
} }
return this;
}
public ActivityUtils showSoftKeyboard() {
if (_activity != null) {
InputMethodManager imm = (InputMethodManager) _activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
if (imm != null && _activity.getCurrentFocus() != null && _activity.getCurrentFocus().getWindowToken() != null) {
showSoftKeyboard(_activity.getCurrentFocus());
}
}
return this;
}
public ActivityUtils showSoftKeyboard(View textInputView) {
if (_activity != null) {
InputMethodManager imm = (InputMethodManager) _activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
if (imm != null && textInputView != null) {
imm.showSoftInput(textInputView, InputMethodManager.SHOW_FORCED);
}
}
return this;
} }
public void showDialogWithHtmlTextView(@StringRes int resTitleId, String html) { public void showDialogWithHtmlTextView(@StringRes int resTitleId, String html) {
@ -111,19 +170,23 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
} }
public void showDialogWithHtmlTextView(@StringRes int resTitleId, String text, boolean isHtml, DialogInterface.OnDismissListener dismissedListener) { public void showDialogWithHtmlTextView(@StringRes int resTitleId, String text, boolean isHtml, DialogInterface.OnDismissListener dismissedListener) {
ScrollView scroll = new ScrollView(_context);
AppCompatTextView textView = new AppCompatTextView(_context); AppCompatTextView textView = new AppCompatTextView(_context);
int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, _context.getResources().getDisplayMetrics());
_context.getResources().getDisplayMetrics());
textView.setMovementMethod(new LinkMovementMethod());
textView.setPadding(padding, 0, padding, 0);
scroll.setPadding(padding, 0, padding, 0);
scroll.addView(textView);
textView.setMovementMethod(new LinkMovementMethod());
textView.setText(isHtml ? new SpannableString(Html.fromHtml(text)) : text); textView.setText(isHtml ? new SpannableString(Html.fromHtml(text)) : text);
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 17);
AlertDialog.Builder dialog = new AlertDialog.Builder(_context) AlertDialog.Builder dialog = new AlertDialog.Builder(_context)
.setPositiveButton(android.R.string.ok, null) .setPositiveButton(android.R.string.ok, null).setOnDismissListener(dismissedListener)
.setOnDismissListener(dismissedListener) .setView(scroll);
.setTitle(resTitleId) if (resTitleId != 0) {
.setView(textView); dialog.setTitle(resTitleId);
dialog.show(); }
dialogFullWidth(dialog.show(), true, false);
} }
public void showDialogWithRawFileInWebView(String fileInRaw, @StringRes int resTitleId) { public void showDialogWithRawFileInWebView(String fileInRaw, @StringRes int resTitleId) {
@ -133,11 +196,11 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
.setPositiveButton(android.R.string.ok, null) .setPositiveButton(android.R.string.ok, null)
.setTitle(resTitleId) .setTitle(resTitleId)
.setView(wv); .setView(wv);
dialog.show(); dialogFullWidth(dialog.show(), true, false);
} }
// Toggle with no param, else set visibility according to first bool // Toggle with no param, else set visibility according to first bool
public void toggleStatusbarVisibility(boolean... optionalForceVisible) { public ActivityUtils toggleStatusbarVisibility(boolean... optionalForceVisible) {
WindowManager.LayoutParams attrs = _activity.getWindow().getAttributes(); WindowManager.LayoutParams attrs = _activity.getWindow().getAttributes();
int flag = WindowManager.LayoutParams.FLAG_FULLSCREEN; int flag = WindowManager.LayoutParams.FLAG_FULLSCREEN;
if (optionalForceVisible.length == 0) { if (optionalForceVisible.length == 0) {
@ -148,9 +211,10 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
attrs.flags |= flag; attrs.flags |= flag;
} }
_activity.getWindow().setAttributes(attrs); _activity.getWindow().setAttributes(attrs);
return this;
} }
public void showGooglePlayEntryForThisApp() { public ActivityUtils showGooglePlayEntryForThisApp() {
String pkgId = "details?id=" + _activity.getPackageName(); String pkgId = "details?id=" + _activity.getPackageName();
Intent goToMarket = new Intent(Intent.ACTION_VIEW, Uri.parse("market://" + pkgId)); Intent goToMarket = new Intent(Intent.ACTION_VIEW, Uri.parse("market://" + pkgId));
goToMarket.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | goToMarket.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY |
@ -160,7 +224,123 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
_activity.startActivity(goToMarket); _activity.startActivity(goToMarket);
} catch (ActivityNotFoundException e) { } catch (ActivityNotFoundException e) {
_activity.startActivity(new Intent(Intent.ACTION_VIEW, _activity.startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse("http://play.google.com/store/apps/" + pkgId))); Uri.parse("https://play.google.com/store/apps/" + pkgId)));
}
return this;
}
public ActivityUtils setStatusbarColor(int color, boolean... fromRes) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (fromRes != null && fromRes.length > 0 && fromRes[0]) {
color = ContextCompat.getColor(_context, color);
}
_activity.getWindow().setStatusBarColor(color);
}
return this;
}
public ActivityUtils setLauncherActivityEnabled(Class activityClass, boolean enable) {
try {
ComponentName component = new ComponentName(_context, activityClass);
_context.getPackageManager().setComponentEnabledSetting(component, enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
} catch (Exception ignored) {
}
return this;
}
public boolean isLauncherEnabled(Class activityClass) {
try {
ComponentName component = new ComponentName(_context, activityClass);
return _context.getPackageManager().getComponentEnabledSetting(component) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
} catch (Exception ignored) {
}
return false;
}
@ColorInt
public Integer getCurrentPrimaryColor() {
TypedValue typedValue = new TypedValue();
_context.getTheme().resolveAttribute(getResId(ResType.ATTR, "colorPrimary"), typedValue, true);
return typedValue.data;
}
@ColorInt
public Integer getCurrentPrimaryDarkColor() {
TypedValue typedValue = new TypedValue();
_context.getTheme().resolveAttribute(getResId(ResType.ATTR, "colorPrimaryDark"), typedValue, true);
return typedValue.data;
}
@ColorInt
public Integer getCurrentAccentColor() {
TypedValue typedValue = new TypedValue();
_context.getTheme().resolveAttribute(getResId(ResType.ATTR, "colorAccent"), typedValue, true);
return typedValue.data;
}
@ColorInt
public Integer getActivityBackgroundColor() {
TypedArray array = _activity.getTheme().obtainStyledAttributes(new int[]{
android.R.attr.colorBackground,
});
int c = array.getColor(0, 0xFF0000);
array.recycle();
return c;
}
public ActivityUtils startCalendarApp() {
Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
builder.appendPath("time");
builder.appendPath(Long.toString(System.currentTimeMillis()));
Intent intent = new Intent(Intent.ACTION_VIEW, builder.build());
_activity.startActivity(intent);
return this;
}
/**
* Detect if the activity is currently in splitscreen/multiwindow mode
*/
public boolean isInSplitScreenMode() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return _activity.isInMultiWindowMode();
}
return false;
}
/**
* Show dialog in full width / show keyboard
*
* @param dialog Get via dialog.show()
*/
public void dialogFullWidth(AlertDialog dialog, boolean fullWidth, boolean showKeyboard) {
try {
Window w;
if (dialog != null && (w = dialog.getWindow()) != null) {
if (fullWidth) {
w.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
}
if (showKeyboard) {
w.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
}
}
} catch (Exception ignored) {
} }
} }
// Make activity/app not show up in the recents history - call before finish / System.exit
public ActivityUtils removeActivityFromHistory() {
try {
ActivityManager am = (ActivityManager) _activity.getSystemService(Context.ACTIVITY_SERVICE);
if (am != null && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
List<ActivityManager.AppTask> tasks = am.getAppTasks();
if (tasks != null && !tasks.isEmpty()) {
tasks.get(0).setExcludeFromRecents(true);
}
}
} catch (Exception ignored) {
}
return this;
}
} }

View file

@ -1,7 +1,6 @@
/*####################################################### /*#######################################################
* *
* Maintained by Gregor Santner, 2017- * Maintained 2017-2023 by Gregor Santner <gsantner AT mailbox DOT org>
* https://gsantner.net/
* *
* License: Apache 2.0 * License: Apache 2.0
* https://github.com/gsantner/opoc/#licensing * https://github.com/gsantner/opoc/#licensing
@ -46,8 +45,9 @@ import java.util.Set;
/** /**
* Simple Host-Based AdBlocker * Simple Host-Based AdBlocker
*/ */
@SuppressWarnings({"WeakerAccess", "SpellCheckingInspection", "unused"}) @SuppressWarnings({"WeakerAccess", "SpellCheckingInspection", "unused", "TryFinallyCanBeTryWithResources"})
public class AdBlock { public class AdBlock {
private static final Object synchronizeObj = new Object();
private static final AdBlock instance = new AdBlock(); private static final AdBlock instance = new AdBlock();
public static AdBlock getInstance() { public static AdBlock getInstance() {
@ -61,7 +61,9 @@ public class AdBlock {
//######################## //########################
private final Set<String> _adblockHostsFromRaw = new HashSet<>(); private final Set<String> _adblockHostsFromRaw = new HashSet<>();
private final Set<String> _adblockHosts = new HashSet<>(); private final Set<String> _adblockHosts = new HashSet<>();
private boolean _isLoaded; private final List<Callback.b3<URI, String, String>> _customBlockCallbacks = new ArrayList<>();
private boolean _isLoaded = false;
private boolean _isAdblockLogging = false;
//######################## //########################
//## //##
@ -72,25 +74,47 @@ public class AdBlock {
} }
public boolean isAdHost(String urlS) { public boolean isAdHost(String urlS) {
boolean block = false;
if (urlS != null && !urlS.isEmpty() && urlS.startsWith("http")) { if (urlS != null && !urlS.isEmpty() && urlS.startsWith("http")) {
try { try {
URI url = new URI(urlS); URI url;
try {
url = new URI(urlS);
} catch (Exception e) {
url = new URI(urlS.replaceFirst("[?].*", ""));
}
String host = url.getHost().trim(); String host = url.getHost().trim();
if (host.startsWith("www.") && host.length() >= 4) { if (host.startsWith("www.") && host.length() >= 4) {
host = host.substring(4); host = host.substring(4);
} }
return _adblockHosts.contains(host) || _adblockHosts.contains("www." + host); block = _adblockHosts.contains(host) || _adblockHosts.contains("www." + host);
for (Callback.b3<URI, String, String> cb : _customBlockCallbacks) {
if (block) {
break;
}
try {
block = cb.callback(url, urlS, host);
} catch (Exception ignored) {
}
}
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
return false;
if (_isAdblockLogging) {
Log.d(getClass().getSimpleName(), "UrlAllowed-" + (block ? "N" : "Y") + " " + urlS);
}
return block;
} }
public AdBlock reset() { public AdBlock reset() {
_adblockHosts.clear(); synchronized (synchronizeObj) {
_adblockHosts.addAll(_adblockHostsFromRaw); _adblockHosts.clear();
_adblockHosts.addAll(_adblockHostsFromRaw);
_customBlockCallbacks.clear();
}
return this; return this;
} }
@ -102,7 +126,7 @@ public class AdBlock {
return new WebResourceResponse("text/plain", "utf-8", new ByteArrayInputStream("".getBytes())); return new WebResourceResponse("text/plain", "utf-8", new ByteArrayInputStream("".getBytes()));
} }
public void addBlockedHosts(String... hosts) { public AdBlock addBlockedHosts(String... hosts) {
for (String host : hosts) { for (String host : hosts) {
if (host != null) { if (host != null) {
host = host.trim(); host = host.trim();
@ -110,23 +134,29 @@ public class AdBlock {
host = host.substring(4); host = host.substring(4);
} }
if (!host.startsWith("#") && !host.startsWith("\"")) { if (!host.startsWith("#") && !host.startsWith("\"")) {
_adblockHosts.add(host); synchronized (synchronizeObj) {
_adblockHosts.add(host);
}
} }
} }
} }
return this;
} }
public void loadHostsFromRawAssetsAsync(final Context context) { public void loadHostsFromRawAssetsAsync(final Context context, final boolean... debugIgnoreAssets) {
new Thread(new Runnable() { if (debugIgnoreAssets != null && debugIgnoreAssets.length > 0 && debugIgnoreAssets[0]) {
@Override _isLoaded = true;
public void run() { return;
try { }
new Thread(() -> {
try {
synchronized (synchronizeObj) {
loadHostsFromRawAssets(context); loadHostsFromRawAssets(context);
_isLoaded = true; _isLoaded = true;
} catch (IOException e) {
e.printStackTrace();
} }
} catch (IOException e) {
e.printStackTrace();
} }
}).start(); }).start();
} }
@ -172,4 +202,17 @@ public class AdBlock {
} }
return adblockResIds; return adblockResIds;
} }
// URI uri, String url, String host
public AdBlock addCustomBlockCallback(Callback.b3<URI, String, String> cb) {
synchronized (synchronizeObj) {
_customBlockCallbacks.add(cb);
}
return this;
}
public AdBlock setLogEnabled(boolean isAdblockLogging) {
_isAdblockLogging = isAdblockLogging;
return this;
}
} }

View file

@ -1,17 +1,21 @@
/*####################################################### /*#######################################################
* *
* Maintained by Gregor Santner, 2018- * Maintained 2018-2023 by Gregor Santner <gsantner AT mailbox DOT org>
* https://gsantner.net/
* *
* License: Apache 2.0 * License of this file: Apache 2.0
* https://github.com/gsantner/opoc/#licensing * https://www.apache.org/licenses/LICENSE-2.0
* https://www.apache.org/licenses/LICENSE-2.0 * https://github.com/gsantner/opoc/#licensing
* *
#########################################################*/ #########################################################*/
package net.gsantner.opoc.util; package net.gsantner.opoc.util;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class Callback { public class Callback {
public interface a0 {
void callback();
}
public interface a1<A> { public interface a1<A> {
void callback(A arg1); void callback(A arg1);
} }
@ -31,4 +35,52 @@ public class Callback {
public interface a5<A, B, C, D, E> { public interface a5<A, B, C, D, E> {
void callback(A arg1, B arg2, C arg3, D arg4, E arg5); void callback(A arg1, B arg2, C arg3, D arg4, E arg5);
} }
public interface b0 {
boolean callback();
}
public interface b1<A> {
boolean callback(A arg1);
}
public interface b2<A, B> {
boolean callback(A arg1, B arg2);
}
public interface b3<A, B, C> {
boolean callback(A arg1, B arg2, C arg3);
}
public interface b4<A, B, C, D> {
boolean callback(A arg1, B arg2, C arg3, D arg4);
}
public interface b5<A, B, C, D, E> {
boolean callback(A arg1, B arg2, C arg3, D arg4, E arg5);
}
public interface s0 {
String callback();
}
public interface s1<A> {
String callback(A arg1);
}
public interface s2<A, B> {
String callback(A arg1, B arg2);
}
public interface s3<A, B, C> {
String callback(A arg1, B arg2, C arg3);
}
public interface s4<A, B, C, D> {
String callback(A arg1, B arg2, C arg3, D arg4);
}
public interface s5<A, B, C, D, E> {
String callback(A arg1, B arg2, C arg3, D arg4, E arg5);
}
} }

View file

@ -1,19 +1,21 @@
/*####################################################### /*#######################################################
* *
* Maintained by Gregor Santner, 2016- * Maintained 2016-2023 by Gregor Santner <gsantner AT mailbox DOT org>
* https://gsantner.net/
* *
* License: Apache 2.0 * License of this file: Apache 2.0
* https://github.com/gsantner/opoc/#licensing * https://www.apache.org/licenses/LICENSE-2.0
* https://www.apache.org/licenses/LICENSE-2.0 * https://github.com/gsantner/opoc/#licensing
* *
#########################################################*/ #########################################################*/
package net.gsantner.opoc.util; package net.gsantner.opoc.util;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
@ -36,6 +38,10 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Environment;
import android.os.SystemClock;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.support.annotation.ColorInt; import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes; import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes; import android.support.annotation.DrawableRes;
@ -43,9 +49,14 @@ import android.support.annotation.Nullable;
import android.support.annotation.RawRes; import android.support.annotation.RawRes;
import android.support.annotation.StringRes; import android.support.annotation.StringRes;
import android.support.graphics.drawable.VectorDrawableCompat; import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.v4.app.ActivityManagerCompat;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.drawable.DrawableCompat; import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.text.TextUtilsCompat;
import android.support.v4.util.Pair;
import android.support.v4.view.ViewCompat;
import android.text.Html; import android.text.Html;
import android.text.InputFilter;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
@ -54,6 +65,11 @@ import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
import android.webkit.MimeTypeMap;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
@ -61,17 +77,21 @@ import net.gsantner.opoc.format.markdown.SimpleMarkdownParser;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import static android.content.Context.VIBRATOR_SERVICE;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.graphics.Bitmap.CompressFormat; import static android.graphics.Bitmap.CompressFormat;
@SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue", "ObsoleteSdkInt", "deprecation", "SpellCheckingInspection"}) @SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue", "ObsoleteSdkInt", "deprecation", "SpellCheckingInspection", "TryFinallyCanBeTryWithResources", "UnusedAssignment", "UnusedReturnValue"})
public class ContextUtils { public class ContextUtils {
// //
// Members, Constructors // Members, Constructors
@ -86,6 +106,9 @@ public class ContextUtils {
return _context; return _context;
} }
public void freeContextRef() {
_context = null;
}
// //
// Class Methods // Class Methods
@ -100,39 +123,55 @@ public class ContextUtils {
* *
* @return A valid id if the id could be found, else 0 * @return A valid id if the id could be found, else 0
*/ */
public int getResId(ResType resType, final String name) { public int getResId(final ResType resType, final String name) {
return _context.getResources().getIdentifier(name, resType.name().toLowerCase(), _context.getPackageName()); try {
return _context.getResources().getIdentifier(name, resType.name().toLowerCase(), _context.getPackageName());
} catch (Exception e) {
return 0;
}
} }
/** /**
* Get String by given string ressource id (nuermic) * Get String by given string ressource id (nuermic)
*/ */
public String rstr(@StringRes int strResId) { public String rstr(@StringRes final int strResId) {
return _context.getString(strResId); try {
return _context.getString(strResId);
} catch (Exception e) {
return null;
}
} }
/** /**
* Get String by given string ressource identifier (textual) * Get String by given string ressource identifier (textual)
*/ */
public String rstr(String strResKey) { public String rstr(final String strResKey, Object... a0getResKeyAsFallback) {
try { try {
return rstr(getResId(ResType.STRING, strResKey)); return rstr(getResId(ResType.STRING, strResKey));
} catch (Resources.NotFoundException e) { } catch (Resources.NotFoundException e) {
return null; return a0getResKeyAsFallback != null && a0getResKeyAsFallback.length > 0 ? strResKey : null;
} }
} }
/** /**
* Get drawable from given ressource identifier * Get drawable from given ressource identifier
*/ */
public Drawable rdrawable(@DrawableRes int resId) { public Drawable rdrawable(@DrawableRes final int resId) {
return ContextCompat.getDrawable(_context, resId); try {
return ContextCompat.getDrawable(_context, resId);
} catch (Exception e) {
return null;
}
} }
/** /**
* Get color by given color ressource id * Get color by given color ressource id
*/ */
public int rcolor(@ColorRes int resId) { public int rcolor(@ColorRes final int resId) {
if (resId == 0) {
Log.e(getClass().getName(), "ContextUtils::rcolor: resId is 0!");
return Color.BLACK;
}
return ContextCompat.getColor(_context, resId); return ContextCompat.getColor(_context, resId);
} }
@ -158,35 +197,92 @@ public class ContextUtils {
* @param intColor The color coded in int * @param intColor The color coded in int
* @param withAlpha Optional; Set first bool parameter to true to also include alpha value * @param withAlpha Optional; Set first bool parameter to true to also include alpha value
*/ */
public String colorToHexString(int intColor, boolean... withAlpha) { public static String colorToHexString(final int intColor, final boolean... withAlpha) {
boolean a = withAlpha != null && withAlpha.length >= 1 && withAlpha[0]; boolean a = withAlpha != null && withAlpha.length >= 1 && withAlpha[0];
return String.format(a ? "#%08X" : "#%06X", (a ? 0xFFFFFFFF : 0xFFFFFF) & intColor); return String.format(a ? "#%08X" : "#%06X", (a ? 0xFFFFFFFF : 0xFFFFFF) & intColor);
} }
public static String getAndroidVersion() {
return Build.VERSION.RELEASE + " (" + Build.VERSION.SDK_INT + ")";
}
public String getAppVersionName() { public String getAppVersionName() {
PackageManager manager = _context.getPackageManager();
try { try {
PackageManager manager = _context.getPackageManager(); PackageInfo info = manager.getPackageInfo(getPackageIdManifest(), 0);
PackageInfo info = manager.getPackageInfo(_context.getPackageName(), 0);
return info.versionName; return info.versionName;
} catch (PackageManager.NameNotFoundException e) { } catch (PackageManager.NameNotFoundException e) {
e.printStackTrace(); try {
return "?"; PackageInfo info = manager.getPackageInfo(getPackageIdReal(), 0);
return info.versionName;
} catch (PackageManager.NameNotFoundException ignored) {
}
} }
return "?";
}
public String getAppInstallationSource() {
String src = null;
try {
src = _context.getPackageManager().getInstallerPackageName(getPackageIdManifest());
} catch (Exception ignored) {
}
if (src == null || src.trim().isEmpty()) {
return "Sideloaded";
} else if (src.toLowerCase().contains(".amazon.")) {
return "Amazon Appstore";
}
switch (src) {
case "com.android.vending":
case "com.google.android.feedback": {
return "Google Play";
}
case "org.fdroid.fdroid.privileged":
case "org.fdroid.fdroid": {
return "F-Droid";
}
case "com.github.yeriomin.yalpstore": {
return "Yalp Store";
}
case "cm.aptoide.pt": {
return "Aptoide";
}
case "com.android.packageinstaller": {
return "Package Installer";
}
}
return src;
} }
/** /**
* Send a {@link Intent#ACTION_VIEW} Intent with given paramter * Send a {@link Intent#ACTION_VIEW} Intent with given paramter
* If the parameter is an string a browser will get triggered * If the parameter is an string a browser will get triggered
*/ */
public void openWebpageInExternalBrowser(final String url) { public ContextUtils openWebpageInExternalBrowser(final String url) {
Uri uri = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
try { try {
Uri uri = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
_context.startActivity(intent); _context.startActivity(intent);
} catch (ActivityNotFoundException e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
return this;
}
/**
* Get the apps base packagename, which is equal with all build flavors and variants
*/
public String getPackageIdManifest() {
String pkg = rstr("manifest_package_id");
return !TextUtils.isEmpty(pkg) ? pkg : _context.getPackageName();
}
/**
* Get this apps package name, returns the flavor specific package name.
*/
public String getPackageIdReal() {
return _context.getPackageName();
} }
/** /**
@ -197,24 +293,36 @@ public class ContextUtils {
* of the package set in manifest (root element). * of the package set in manifest (root element).
* Falls back to applicationId of the app which may differ from manifest. * Falls back to applicationId of the app which may differ from manifest.
*/ */
public Object getBuildConfigValue(String fieldName) { public Object getBuildConfigValue(final String fieldName) {
String pkg = rstr("manifest_package_id"); final String pkg = getPackageIdManifest() + ".BuildConfig";
pkg = (pkg != null ? pkg : _context.getPackageName()) + ".BuildConfig";
try { try {
Class<?> c = Class.forName(pkg); Class<?> c = Class.forName(pkg);
return c.getField(fieldName).get(null); return c.getField(fieldName).get(null);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return null;
} }
return null;
}
public List<String> getBuildConfigFields() {
final String pkg = getPackageIdManifest() + ".BuildConfig";
final List<String> fields = new ArrayList<>();
try {
for (Field f : Class.forName(pkg).getFields()) {
fields.add(f.getName());
}
} catch (Exception e) {
e.printStackTrace();
}
return fields;
} }
/** /**
* Get a BuildConfig bool value * Get a BuildConfig bool value
*/ */
public Boolean bcbool(String fieldName, Boolean defaultValue) { public Boolean bcbool(final String fieldName, final Boolean defaultValue) {
Object field = getBuildConfigValue(fieldName); Object field = getBuildConfigValue(fieldName);
if (field != null && field instanceof Boolean) { if (field instanceof Boolean) {
return (Boolean) field; return (Boolean) field;
} }
return defaultValue; return defaultValue;
@ -223,14 +331,25 @@ public class ContextUtils {
/** /**
* Get a BuildConfig string value * Get a BuildConfig string value
*/ */
public String bcstr(String fieldName, String defaultValue) { public String bcstr(final String fieldName, final String defaultValue) {
Object field = getBuildConfigValue(fieldName); Object field = getBuildConfigValue(fieldName);
if (field != null && field instanceof String) { if (field instanceof String) {
return (String) field; return (String) field;
} }
return defaultValue; return defaultValue;
} }
/**
* Get a BuildConfig string value
*/
public Integer bcint(final String fieldName, final int defaultValue) {
Object field = getBuildConfigValue(fieldName);
if (field instanceof Integer) {
return (Integer) field;
}
return defaultValue;
}
/** /**
* Check if this is a gplay build (requires BuildConfig field) * Check if this is a gplay build (requires BuildConfig field)
*/ */
@ -245,26 +364,6 @@ public class ContextUtils {
return bcbool("IS_FOSS_BUILD", false); return bcbool("IS_FOSS_BUILD", false);
} }
/**
* Request a bitcoin donation with given details.
* All parameters are awaited as string resource ids
*/
public void showDonateBitcoinRequest(@StringRes final int srBitcoinId, @StringRes final int srBitcoinAmount, @StringRes final int srBitcoinMessage, @StringRes final int srAlternativeDonateUrl) {
if (!isGooglePlayBuild()) {
String btcUri = String.format("bitcoin:%s?amount=%s&label=%s&message=%s",
rstr(srBitcoinId), rstr(srBitcoinAmount),
rstr(srBitcoinMessage), rstr(srBitcoinMessage));
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(btcUri));
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
try {
_context.startActivity(intent);
} catch (ActivityNotFoundException e) {
openWebpageInExternalBrowser(rstr(srAlternativeDonateUrl));
}
}
}
public String readTextfileFromRawRes(@RawRes int rawResId, String linePrefix, String linePostfix) { public String readTextfileFromRawRes(@RawRes int rawResId, String linePrefix, String linePostfix) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
BufferedReader br = null; BufferedReader br = null;
@ -313,8 +412,8 @@ public class ContextUtils {
* Check if app with given {@code packageName} is installed * Check if app with given {@code packageName} is installed
*/ */
public boolean isAppInstalled(String packageName) { public boolean isAppInstalled(String packageName) {
PackageManager pm = _context.getApplicationContext().getPackageManager();
try { try {
PackageManager pm = _context.getApplicationContext().getPackageManager();
pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES); pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
return true; return true;
} catch (PackageManager.NameNotFoundException e) { } catch (PackageManager.NameNotFoundException e) {
@ -326,14 +425,17 @@ public class ContextUtils {
* Restart the current app. Supply the class to start on startup * Restart the current app. Supply the class to start on startup
*/ */
public void restartApp(Class classToStart) { public void restartApp(Class classToStart) {
Intent inte = new Intent(_context, classToStart); Intent intent = new Intent(_context, classToStart);
PendingIntent inteP = PendingIntent.getActivity(_context, 555, inte, PendingIntent.FLAG_CANCEL_CURRENT); PendingIntent pendi = PendingIntent.getActivity(_context, 555, intent, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager mgr = (AlarmManager) _context.getSystemService(Context.ALARM_SERVICE); AlarmManager mgr = (AlarmManager) _context.getSystemService(Context.ALARM_SERVICE);
if (_context instanceof Activity) {
((Activity) _context).finish();
}
if (mgr != null) { if (mgr != null) {
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, inteP); mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, pendi);
} else { } else {
inte.addFlags(FLAG_ACTIVITY_NEW_TASK); intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
_context.startActivity(inte); _context.startActivity(intent);
} }
Runtime.getRuntime().exit(0); Runtime.getRuntime().exit(0);
} }
@ -402,19 +504,25 @@ public class ContextUtils {
* {@code androidLC} may be in any of the forms: en, de, de-rAt * {@code androidLC} may be in any of the forms: en, de, de-rAt
* If given an empty string, the default (system) locale gets loaded * If given an empty string, the default (system) locale gets loaded
*/ */
public void setAppLanguage(String androidLC) { public void setAppLanguage(final String androidLC) {
Locale locale = getLocaleByAndroidCode(androidLC); Locale locale = getLocaleByAndroidCode(androidLC);
locale = (locale != null && !androidLC.isEmpty()) ? locale : Resources.getSystem().getConfiguration().locale;
setLocale(locale);
}
public ContextUtils setLocale(final Locale locale) {
Configuration config = _context.getResources().getConfiguration(); Configuration config = _context.getResources().getConfiguration();
config.locale = (locale != null && !androidLC.isEmpty()) config.locale = (locale != null ? locale : Resources.getSystem().getConfiguration().locale);
? locale : Resources.getSystem().getConfiguration().locale;
_context.getResources().updateConfiguration(config, null); _context.getResources().updateConfiguration(config, null);
Locale.setDefault(locale);
return this;
} }
/** /**
* Try to guess if the color on top of the given {@code colorOnBottomInt} * Try to guess if the color on top of the given {@code colorOnBottomInt}
* should be light or dark. Returns true if top color should be light * should be light or dark. Returns true if top color should be light
*/ */
public boolean shouldColorOnTopBeLight(@ColorInt int colorOnBottomInt) { public boolean shouldColorOnTopBeLight(@ColorInt final int colorOnBottomInt) {
return 186 > (((0.299 * Color.red(colorOnBottomInt)) return 186 > (((0.299 * Color.red(colorOnBottomInt))
+ ((0.587 * Color.green(colorOnBottomInt)) + ((0.587 * Color.green(colorOnBottomInt))
+ (0.114 * Color.blue(colorOnBottomInt))))); + (0.114 * Color.blue(colorOnBottomInt)))));
@ -423,7 +531,7 @@ public class ContextUtils {
/** /**
* Convert a html string to an android {@link Spanned} object * Convert a html string to an android {@link Spanned} object
*/ */
public Spanned htmlToSpanned(String html) { public Spanned htmlToSpanned(final String html) {
Spanned result; Spanned result;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
result = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY); result = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
@ -447,12 +555,87 @@ public class ContextUtils {
return dp * _context.getResources().getDisplayMetrics().density; return dp * _context.getResources().getDisplayMetrics().density;
} }
/**
* Get the private directory for the current package (usually /data/data/package.name/)
*/
@SuppressWarnings("StatementWithEmptyBody")
public File getAppDataPrivateDir() {
File filesDir;
try {
filesDir = new File(new File(_context.getPackageManager().getPackageInfo(getPackageIdReal(), 0).applicationInfo.dataDir), "files");
} catch (PackageManager.NameNotFoundException e) {
filesDir = _context.getFilesDir();
}
if (!filesDir.exists() && filesDir.mkdirs()) ;
return filesDir;
}
/**
* Get public (accessible) appdata folders
*/
@SuppressWarnings("StatementWithEmptyBody")
public List<Pair<File, String>> getAppDataPublicDirs(boolean internalStorageFolder, boolean sdcardFolders, boolean storageNameWithoutType) {
List<Pair<File, String>> dirs = new ArrayList<>();
for (File externalFileDir : ContextCompat.getExternalFilesDirs(_context, null)) {
if (externalFileDir == null || Environment.getExternalStorageDirectory() == null) {
continue;
}
boolean isInt = externalFileDir.getAbsolutePath().startsWith(Environment.getExternalStorageDirectory().getAbsolutePath());
boolean add = (internalStorageFolder && isInt) || (sdcardFolders && !isInt);
if (add) {
dirs.add(new Pair<>(externalFileDir, getStorageName(externalFileDir, storageNameWithoutType)));
if (!externalFileDir.exists() && externalFileDir.mkdirs()) ;
}
}
return dirs;
}
public String getStorageName(final File externalFileDir, final boolean storageNameWithoutType) {
boolean isInt = externalFileDir.getAbsolutePath().startsWith(Environment.getExternalStorageDirectory().getAbsolutePath());
String[] split = externalFileDir.getAbsolutePath().split("/");
if (split.length > 2) {
return isInt ? (storageNameWithoutType ? "Internal Storage" : "") : (storageNameWithoutType ? split[2] : ("SD Card (" + split[2] + ")"));
} else {
return "Storage";
}
}
public List<Pair<File, String>> getStorages(final boolean internalStorageFolder, final boolean sdcardFolders) {
List<Pair<File, String>> storages = new ArrayList<>();
for (Pair<File, String> pair : getAppDataPublicDirs(internalStorageFolder, sdcardFolders, true)) {
if (pair.first != null && pair.first.getAbsolutePath().lastIndexOf("/Android/data") > 0) {
try {
storages.add(new Pair<>(new File(pair.first.getCanonicalPath().replaceFirst("/Android/data.*", "")), pair.second));
} catch (IOException ignored) {
}
}
}
return storages;
}
public File getStorageRootFolder(final File file) {
String filepath;
try {
filepath = file.getCanonicalPath();
} catch (Exception ignored) {
return null;
}
for (Pair<File, String> storage : getStorages(false, true)) {
//noinspection ConstantConditions
if (filepath.startsWith(storage.first.getAbsolutePath())) {
return storage.first;
}
}
return null;
}
/** /**
* Request the givens paths to be scanned by MediaScanner * Request the givens paths to be scanned by MediaScanner
* *
* @param files Files and folders to scan * @param files Files and folders to scan
*/ */
public void mediaScannerScanFile(File... files) { public void mediaScannerScanFile(final File... files) {
if (android.os.Build.VERSION.SDK_INT > 19) { if (android.os.Build.VERSION.SDK_INT > 19) {
String[] paths = new String[files.length]; String[] paths = new String[files.length];
for (int i = 0; i < files.length; i++) { for (int i = 0; i < files.length; i++) {
@ -501,8 +684,12 @@ public class ContextUtils {
/** /**
* Get a {@link Bitmap} out of a {@link DrawableRes} * Get a {@link Bitmap} out of a {@link DrawableRes}
*/ */
public Bitmap drawableToBitmap(@DrawableRes int drawableId) { public Bitmap drawableToBitmap(@DrawableRes final int drawableId) {
return drawableToBitmap(ContextCompat.getDrawable(_context, drawableId)); try {
return drawableToBitmap(ContextCompat.getDrawable(_context, drawableId));
} catch (Exception e) {
return null;
}
} }
/** /**
@ -510,7 +697,7 @@ public class ContextUtils {
* Specifying a {@code maxDimen} is also possible and a value below 2000 * Specifying a {@code maxDimen} is also possible and a value below 2000
* is recommended, otherwise a {@link OutOfMemoryError} may occur * is recommended, otherwise a {@link OutOfMemoryError} may occur
*/ */
public Bitmap loadImageFromFilesystem(File imagePath, int maxDimen) { public Bitmap loadImageFromFilesystem(final File imagePath, final int maxDimen) {
BitmapFactory.Options options = new BitmapFactory.Options(); BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imagePath.getAbsolutePath(), options); BitmapFactory.decodeFile(imagePath.getAbsolutePath(), options);
@ -526,7 +713,7 @@ public class ContextUtils {
* @param maxDimen Max size of the Bitmap (width or height) * @param maxDimen Max size of the Bitmap (width or height)
* @return the scaling factor that needs to be applied to the bitmap * @return the scaling factor that needs to be applied to the bitmap
*/ */
public int calculateInSampleSize(BitmapFactory.Options options, int maxDimen) { public int calculateInSampleSize(final BitmapFactory.Options options, final int maxDimen) {
// Raw height and width of image // Raw height and width of image
int height = options.outHeight; int height = options.outHeight;
int width = options.outWidth; int width = options.outWidth;
@ -542,7 +729,7 @@ public class ContextUtils {
* Scale the bitmap so both dimensions are lower or equal to {@code maxDimen} * Scale the bitmap so both dimensions are lower or equal to {@code maxDimen}
* This keeps the aspect ratio * This keeps the aspect ratio
*/ */
public Bitmap scaleBitmap(Bitmap bitmap, int maxDimen) { public Bitmap scaleBitmap(final Bitmap bitmap, final int maxDimen) {
int picSize = Math.min(bitmap.getHeight(), bitmap.getWidth()); int picSize = Math.min(bitmap.getHeight(), bitmap.getWidth());
float scale = 1.f * maxDimen / picSize; float scale = 1.f * maxDimen / picSize;
Matrix matrix = new Matrix(); Matrix matrix = new Matrix();
@ -550,44 +737,27 @@ public class ContextUtils {
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
} }
/**
* Write the given {@link Bitmap} to {@code imageFile}, in {@link CompressFormat#JPEG} format
*/
public boolean writeImageToFileJpeg(File imageFile, Bitmap image) {
return writeImageToFile(imageFile, image, Bitmap.CompressFormat.JPEG, 95);
}
/** /**
* Write the given {@link Bitmap} to filesystem * Write the given {@link Bitmap} to filesystem
* *
* @param targetFile The file to be written in * @param targetFile The file to be written in
* @param image The image as android {@link Bitmap} * @param image Android {@link Bitmap}
* @param format One format of {@link CompressFormat}, null will determine based on filename
* @param quality Quality level, defaults to 95
* @return True if writing was successful * @return True if writing was successful
*/ */
public boolean writeImageToFile(File targetFile, Bitmap image, CompressFormat format, Integer quality) { public boolean writeImageToFile(final File targetFile, final Bitmap image, Integer... a0quality) {
final int quality = (a0quality != null && a0quality.length > 0 && a0quality[0] >= 0 && a0quality[0] <= 100) ? a0quality[0] : 70;
final String lc = targetFile.getAbsolutePath().toLowerCase(Locale.ROOT);
final CompressFormat format = lc.endsWith(".webp") ? CompressFormat.WEBP : (lc.endsWith(".png") ? CompressFormat.PNG : CompressFormat.JPEG);
boolean ok = false;
File folder = new File(targetFile.getParent()); File folder = new File(targetFile.getParent());
if (quality == null || quality < 0 || quality > 100) {
quality = 95;
}
if (format == null) {
format = CompressFormat.JPEG;
String lc = targetFile.getAbsolutePath().toLowerCase(Locale.ROOT);
if (lc.endsWith(".png")) {
format = CompressFormat.PNG;
}
if (lc.endsWith(".webp")) {
format = CompressFormat.WEBP;
}
}
if (folder.exists() || folder.mkdirs()) { if (folder.exists() || folder.mkdirs()) {
FileOutputStream stream = null; FileOutputStream stream = null;
try { try {
stream = new FileOutputStream(targetFile); // overwrites this image every time stream = new FileOutputStream(targetFile);
image.compress(format, quality, stream); image.compress(format, quality, stream);
return true; ok = true;
} catch (FileNotFoundException ignored) { } catch (Exception ignored) {
} finally { } finally {
try { try {
if (stream != null) { if (stream != null) {
@ -597,14 +767,18 @@ public class ContextUtils {
} }
} }
} }
return false; try {
image.recycle();
} catch (Exception ignored) {
}
return ok;
} }
/** /**
* Draw text in the center of the given {@link DrawableRes} * Draw text in the center of the given {@link DrawableRes}
* This may be useful for e.g. badge counts * This may be useful for e.g. badge counts
*/ */
public Bitmap drawTextOnDrawable(@DrawableRes int drawableRes, String text, int textSize) { public Bitmap drawTextOnDrawable(@DrawableRes final int drawableRes, final String text, final int textSize) {
Resources resources = _context.getResources(); Resources resources = _context.getResources();
float scale = resources.getDisplayMetrics().density; float scale = resources.getDisplayMetrics().density;
Bitmap bitmap = drawableToBitmap(drawableRes); Bitmap bitmap = drawableToBitmap(drawableRes);
@ -629,12 +803,16 @@ public class ContextUtils {
* Try to tint all {@link Menu}s {@link MenuItem}s with given color * Try to tint all {@link Menu}s {@link MenuItem}s with given color
*/ */
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
public void tintMenuItems(Menu menu, boolean recurse, @ColorInt int iconColor) { public void tintMenuItems(final Menu menu, final boolean recurse, @ColorInt final int iconColor) {
for (int i = 0; i < menu.size(); i++) { for (int i = 0; i < menu.size(); i++) {
MenuItem item = menu.getItem(i); MenuItem item = menu.getItem(i);
tintDrawable(item.getIcon(), iconColor); try {
if (item.hasSubMenu() && recurse) { tintDrawable(item.getIcon(), iconColor);
tintMenuItems(item.getSubMenu(), recurse, iconColor); if (item.hasSubMenu() && recurse) {
tintMenuItems(item.getSubMenu(), recurse, iconColor);
}
} catch (Exception ignored) {
// This should not happen at all, but may in bad menu.xml configuration
} }
} }
} }
@ -642,14 +820,14 @@ public class ContextUtils {
/** /**
* Loads {@link Drawable} by given {@link DrawableRes} and applies a color * Loads {@link Drawable} by given {@link DrawableRes} and applies a color
*/ */
public Drawable tintDrawable(@DrawableRes int drawableRes, @ColorInt int color) { public Drawable tintDrawable(@DrawableRes final int drawableRes, @ColorInt final int color) {
return tintDrawable(rdrawable(drawableRes), color); return tintDrawable(rdrawable(drawableRes), color);
} }
/** /**
* Tint a {@link Drawable} with given {@code color} * Tint a {@link Drawable} with given {@code color}
*/ */
public Drawable tintDrawable(@Nullable Drawable drawable, @ColorInt int color) { public Drawable tintDrawable(@Nullable Drawable drawable, @ColorInt final int color) {
if (drawable != null) { if (drawable != null) {
drawable = DrawableCompat.wrap(drawable); drawable = DrawableCompat.wrap(drawable);
DrawableCompat.setTint(drawable.mutate(), color); DrawableCompat.setTint(drawable.mutate(), color);
@ -661,7 +839,10 @@ public class ContextUtils {
* Try to make icons in Toolbar/ActionBars SubMenus visible * Try to make icons in Toolbar/ActionBars SubMenus visible
* This may not work on some devices and it maybe won't work on future android updates * This may not work on some devices and it maybe won't work on future android updates
*/ */
public void setSubMenuIconsVisiblity(Menu menu, boolean visible) { public void setSubMenuIconsVisiblity(final Menu menu, final boolean visible) {
if (TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL) {
return;
}
if (menu.getClass().getSimpleName().equals("MenuBuilder")) { if (menu.getClass().getSimpleName().equals("MenuBuilder")) {
try { try {
@SuppressLint("PrivateApi") Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE); @SuppressLint("PrivateApi") Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE);
@ -672,4 +853,165 @@ public class ContextUtils {
} }
} }
} }
public String getLocalizedDateFormat() {
return ((SimpleDateFormat) android.text.format.DateFormat.getDateFormat(_context)).toPattern();
}
public String getLocalizedTimeFormat() {
return ((SimpleDateFormat) android.text.format.DateFormat.getTimeFormat(_context)).toPattern();
}
public String getLocalizedDateTimeFormat() {
return getLocalizedDateFormat() + " " + getLocalizedTimeFormat();
}
/**
* A {@link InputFilter} for filenames
*/
@SuppressWarnings("Convert2Lambda")
public static final InputFilter INPUTFILTER_FILENAME = new InputFilter() {
public CharSequence filter(CharSequence src, int start, int end, Spanned dest, int dstart, int dend) {
if (src.length() < 1) return null;
char last = src.charAt(src.length() - 1);
String illegal = "|\\?*<\":>[]/'";
if (illegal.indexOf(last) > -1) return src.subSequence(0, src.length() - 1);
return null;
}
};
/**
* A simple {@link Runnable} which does a touch event on a view.
* This pops up e.g. the keyboard on a {@link android.widget.EditText}
* <p>
* Example: new Handler().postDelayed(new DoTouchView(editView), 200);
*/
public static class DoTouchView implements Runnable {
View _view;
public DoTouchView(View view) {
_view = view;
}
@Override
public void run() {
_view.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 0, 0, 0));
_view.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 0, 0, 0));
}
}
public String getMimeType(final File file) {
return getMimeType(Uri.fromFile(file));
}
/**
* Detect MimeType of given file
* Android/Java's own MimeType map is very very small and detection barely works at all
* Hence use custom map for some file extensions
*/
public String getMimeType(final Uri uri) {
String mimeType = null;
if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
ContentResolver cr = _context.getContentResolver();
mimeType = cr.getType(uri);
} else {
String filename = uri.toString();
if (filename.endsWith(".jenc")) {
filename = filename.replace(".jenc", "");
}
String ext = MimeTypeMap.getFileExtensionFromUrl(filename);
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext.toLowerCase());
// Try to guess if the recommended methods fail
if (TextUtils.isEmpty(mimeType)) {
switch (ext) {
case "md":
case "markdown":
case "mkd":
case "mdown":
case "mkdn":
case "mdwn":
case "rmd":
mimeType = "text/markdown";
break;
case "yaml":
case "yml":
mimeType = "text/yaml";
break;
case "json":
mimeType = "text/json";
break;
case "txt":
mimeType = "text/plain";
break;
}
}
}
if (TextUtils.isEmpty(mimeType)) {
mimeType = "*/*";
}
return mimeType;
}
public Integer parseColor(final String colorstr) {
if (colorstr == null || colorstr.trim().isEmpty()) {
return null;
}
try {
return Color.parseColor(colorstr);
} catch (IllegalArgumentException ignored) {
return null;
}
}
public boolean isDeviceGoodHardware() {
try {
ActivityManager activityManager = (ActivityManager) _context.getSystemService(Context.ACTIVITY_SERVICE);
return !ActivityManagerCompat.isLowRamDevice(activityManager) &&
Runtime.getRuntime().availableProcessors() >= 4 &&
activityManager.getMemoryClass() >= 128;
} catch (Exception ignored) {
return true;
}
}
// Vibrate device one time by given amount of time, defaulting to 50ms
// Requires <uses-permission android:name="android.permission.VIBRATE" /> in AndroidManifest to work
@SuppressWarnings("UnnecessaryReturnStatement")
@SuppressLint("MissingPermission")
public void vibrate(final int... ms) {
int ms_v = ms != null && ms.length > 0 ? ms[0] : 50;
Vibrator vibrator = ((Vibrator) _context.getSystemService(VIBRATOR_SERVICE));
if (vibrator == null) {
return;
} else if (Build.VERSION.SDK_INT >= 26) {
vibrator.vibrate(VibrationEffect.createOneShot(ms_v, VibrationEffect.DEFAULT_AMPLITUDE));
} else {
vibrator.vibrate(ms_v);
}
}
/*
Check if Wifi is connected. Requires these permissions in AndroidManifest:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
*/
@SuppressLint("MissingPermission")
public boolean isWifiConnected(boolean... enabledOnly) {
final boolean doEnabledCheckOnly = enabledOnly != null && enabledOnly.length > 0 && enabledOnly[0];
final ConnectivityManager connectivityManager = (ConnectivityManager) _context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo wifiInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
return wifiInfo != null && (doEnabledCheckOnly ? wifiInfo.isAvailable() : wifiInfo.isConnected());
}
// Returns if the device is currently in portrait orientation (landscape=false)
public boolean isDeviceOrientationPortrait() {
final int rotation = ((WindowManager) _context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getOrientation();
return (rotation == Surface.ROTATION_0) || (rotation == Surface.ROTATION_180);
}
} }

View file

@ -1,41 +1,66 @@
/*####################################################### /*#######################################################
* *
* Maintained by Gregor Santner, 2017- * Maintained 2017-2023 by Gregor Santner <gsantner AT mailbox DOT org>
* https://gsantner.net/
* *
* License: Apache 2.0 * License of this file: Apache 2.0
* https://github.com/gsantner/opoc/#licensing * https://www.apache.org/licenses/LICENSE-2.0
* https://www.apache.org/licenses/LICENSE-2.0 * https://github.com/gsantner/opoc/#licensing
* *
#########################################################*/ #########################################################*/
package net.gsantner.opoc.util; package net.gsantner.opoc.util;
import android.text.TextUtils;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.URLConnection;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue", "SpellCheckingInspection", "deprecation"}) @SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue", "SpellCheckingInspection", "deprecation", "TryFinallyCanBeTryWithResources"})
public class FileUtils { public class FileUtils {
// Used on methods like copyFile(src, dst) // Used on methods like copyFile(src, dst)
private static final int BUFFER_SIZE = 4096; private static final int BUFFER_SIZE = 4096;
public static String readTextFileFast(final File file) {
try {
return new String(readCloseStreamWithSize(new FileInputStream(file), (int) file.length()));
} catch (FileNotFoundException e) {
System.err.println("readTextFileFast: File " + file + " not found.");
}
return "";
}
public static byte[] readCloseStreamWithSize(final InputStream stream, int size) {
byte[] data = new byte[size];
try (DataInputStream dis = new DataInputStream(stream)) {
dis.readFully(data);
} catch (IOException ignored) {
}
return data;
}
public static String readTextFile(final File file) { public static String readTextFile(final File file) {
try { try {
return readCloseTextStream(new FileInputStream(file)); return readCloseTextStream(new FileInputStream(file));
@ -217,6 +242,30 @@ public class FileUtils {
} }
} }
public static boolean copyFile(final File src, final FileOutputStream os) {
InputStream is = null;
try {
try {
is = new FileInputStream(src);
byte[] buf = new byte[BUFFER_SIZE];
int len;
while ((len = is.read(buf)) > 0) {
os.write(buf, 0, len);
}
return true;
} finally {
if (is != null) {
is.close();
}
if (os != null) {
os.close();
}
}
} catch (IOException ex) {
return false;
}
}
// Returns -1 if the file did not contain any of the needles, otherwise, // Returns -1 if the file did not contain any of the needles, otherwise,
// the index of which needle was found in the contents of the file. // the index of which needle was found in the contents of the file.
// //
@ -337,4 +386,142 @@ public class FileUtils {
return null; return null;
} }
} }
/**
* Try to detect MimeType by backwards compatible methods
*/
public static String getMimeType(File file) {
String guess = null;
if (file != null) {
if (file.exists() && file.isFile()) {
InputStream is = null;
try {
is = new BufferedInputStream(new FileInputStream(file));
guess = URLConnection.guessContentTypeFromStream(is);
} catch (Exception ignored) {
} finally {
if (is != null) {
try {
is.close();
} catch (Exception ignored) {
}
}
}
}
String filename = file.getName().replace(".jenc", "");
int dot = filename.lastIndexOf(".") + 1;
if (dot > 0 && dot < filename.length()) {
switch (filename.substring(dot)) {
case "md":
case "markdown":
case "mkd":
case "mdown":
case "mkdn":
case "mdwn":
case "rmd":
guess = "text/markdown";
break;
case "txt":
guess = "text/plain";
break;
case "webp":
guess = "image/webp";
break;
case "jpg":
case "jpeg":
guess = "image/jpeg";
break;
case "png":
guess = "image/png";
break;
}
}
if (TextUtils.isEmpty(guess)) {
guess = URLConnection.guessContentTypeFromName(filename);
}
}
return TextUtils.isEmpty(guess) ? "*/*" : guess;
}
public static boolean isTextFile(File file) {
String mime = getMimeType(file);
return mime != null && mime.startsWith("text/");
}
/**
* Analyze given textfile and retrieve multiple information from it
* Information is written back to the {@link AtomicInteger} parameters
*/
public static void retrieveTextFileSummary(File file, AtomicInteger numCharacters, AtomicInteger numLines, AtomicInteger numWords) {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(file));
String line;
while ((line = br.readLine()) != null) {
numLines.getAndIncrement();
numCharacters.getAndSet(numCharacters.get() + line.length());
if (!line.equals("")) {
numWords.getAndSet(numWords.get() + line.split("\\s+").length);
}
}
} catch (Exception e) {
e.printStackTrace();
numCharacters.set(-1);
numLines.set(-1);
numWords.set(-1);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException ignored) {
}
}
}
}
/**
* Format filesize to human readable format
* Get size in bytes e.g. from {@link File} using {@code File#length()}
*/
public static String getReadableFileSize(long size, boolean abbreviation) {
if (size <= 0) {
return "0B";
}
String[] units = abbreviation ? new String[]{"B", "kB", "MB", "GB", "TB"} : new String[]{"Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"};
int unit = (int) (Math.log10(size) / Math.log10(1024));
return new DecimalFormat("#,##0.#", DecimalFormatSymbols.getInstance(Locale.ENGLISH)).format(size / Math.pow(1024, unit)) + " " + units[unit];
}
public static int[] getTimeDiffHMS(long now, long past) {
int[] ret = new int[3];
long diff = Math.abs(now - past);
ret[0] = (int) (diff / (1000 * 60 * 60)); // hours
ret[1] = (int) (diff / (1000 * 60)) % 60; // min
ret[2] = (int) (diff / 1000) % 60; // sec
return ret;
}
public static String getHumanReadableByteCountSI(final long bytes) {
if (bytes < 1000) {
return String.format(Locale.getDefault(), "%d%s", bytes, "B");
} else if (bytes < 1000000) {
return String.format(Locale.getDefault(), "%.2f%s", (bytes / 1000f), "KB");
} else if (bytes < 1000000000) {
return String.format(Locale.getDefault(), "%.2f%s", (bytes / 1000000f), "MB");
} else if (bytes < 1000000000000L) {
return String.format(Locale.getDefault(), "%.2f%s", (bytes / 1000000000f), "GB");
} else {
return String.format(Locale.getDefault(), "%.2f%s", (bytes / 1000000000000f), "TB");
}
}
public static File join(File file, String... childSegments) {
for (final String s : childSegments != null ? childSegments : new String[0]) {
file = new File(file, s);
}
return file;
}
} }

View file

@ -1,11 +1,10 @@
/*####################################################### /*#######################################################
* *
* Maintained by Gregor Santner, 2017- * Maintained 2017-2023 by Gregor Santner <gsantner AT mailbox DOT org>
* https://gsantner.net/
* *
* License: Apache 2.0 * License of this file: Apache 2.0
* https://github.com/gsantner/opoc/#licensing * https://www.apache.org/licenses/LICENSE-2.0
* https://www.apache.org/licenses/LICENSE-2.0 * https://github.com/gsantner/opoc/#licensing
* *
#########################################################*/ #########################################################*/
package net.gsantner.opoc.util; package net.gsantner.opoc.util;
@ -77,7 +76,7 @@ public class NetworkUtils {
int written = 0; int written = 0;
final float invLength = 1f / connection.getContentLength(); final float invLength = 1f / connection.getContentLength();
byte data[] = new byte[BUFFER_SIZE]; byte[] data = new byte[BUFFER_SIZE];
while ((count = input.read(data)) != -1) { while ((count = input.read(data)) != -1) {
output.write(data, 0, count); output.write(data, 0, count);
if (invLength != -1f && progressCallback != null) { if (invLength != -1f && progressCallback != null) {
@ -150,6 +149,7 @@ public class NetworkUtils {
return performCall(url, method, data, null); return performCall(url, method, data, null);
} }
@SuppressWarnings("CharsetObjectCanBeUsed")
private static String performCall(final URL url, final String method, final String data, final HttpURLConnection existingConnection) { private static String performCall(final URL url, final String method, final String data, final HttpURLConnection existingConnection) {
try { try {
final HttpURLConnection connection = existingConnection != null final HttpURLConnection connection = existingConnection != null
@ -160,7 +160,7 @@ public class NetworkUtils {
if (data != null && !data.isEmpty()) { if (data != null && !data.isEmpty()) {
connection.setDoOutput(true); connection.setDoOutput(true);
final OutputStream output = connection.getOutputStream(); final OutputStream output = connection.getOutputStream();
output.write(data.getBytes(Charset.forName(UTF8))); output.write(data.getBytes(Charset.forName("UTF-8")));
output.flush(); output.flush();
output.close(); output.close();
} }
@ -220,4 +220,14 @@ public class NetworkUtils {
return result; return result;
} }
public static void httpGetAsync(final String url, final Callback.a1<String> callback) {
new Thread(() -> {
try {
String c = NetworkUtils.performCall(url, GET);
callback.callback(c);
} catch (Exception ignored) {
}
}).start();
}
} }

View file

@ -1,11 +1,10 @@
/*####################################################### /*#######################################################
* *
* Maintained by Gregor Santner, 2017- * Maintained 2017-2023 by Gregor Santner <gsantner AT mailbox DOT org>
* https://gsantner.net/
* *
* License: Apache 2.0 * License of this file: Apache 2.0
* https://github.com/gsantner/opoc/#licensing * https://www.apache.org/licenses/LICENSE-2.0
* https://www.apache.org/licenses/LICENSE-2.0 * https://github.com/gsantner/opoc/#licensing
* *
#########################################################*/ #########################################################*/
package net.gsantner.opoc.util; package net.gsantner.opoc.util;
@ -21,9 +20,9 @@ import java.io.File;
@SuppressWarnings({"unused", "WeakerAccess"}) @SuppressWarnings({"unused", "WeakerAccess"})
public class PermissionChecker { public class PermissionChecker {
private static final int CODE_PERMISSION_EXTERNAL_STORAGE = 4000; protected static final int CODE_PERMISSION_EXTERNAL_STORAGE = 4000;
private Activity _activity; protected Activity _activity;
public PermissionChecker(Activity activity) { public PermissionChecker(Activity activity) {
_activity = activity; _activity = activity;

File diff suppressed because it is too large Load diff

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_test_background"/>
<foreground android:drawable="@drawable/ic_launcher_test_foreground"/>
</adaptive-icon>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_test_background"/>
<foreground android:drawable="@drawable/ic_launcher_test_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:viewportHeight="48"
android:viewportWidth="48" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000"
android:pathData="M19.0447,29.8394 L32.0088,23.143 19.0447,16.3663ZM24,6.7503c10.0981,0 16.7945,0.4821 16.7945,0.4821 0.9375,0.1071 3,0.1071 4.8214,2.0357 0,0 1.4732,1.4464 1.9018,4.7678 0.5089,3.8839 0.4821,7.7678 0.4821,7.7678v3.6428c0,0 0.0268,3.8839 -0.4821,7.7678 -0.4286,3.2946 -1.9018,4.7678 -1.9018,4.7678 -1.8214,1.9018 -3.8839,1.9018 -4.8214,2.0089C40.7945,39.9911 34.0981,40.5 24,40.5 11.518,40.3929 7.6877,40.0179 7.6877,40.0179 6.6162,39.8304 4.2056,39.8839 2.3841,37.9822c0,0 -1.4732,-1.4732 -1.9018,-4.7678C-0.0265,29.3305 0.0002,25.4466 0.0002,25.4466v-3.6428c0,0 -0.0268,-3.8839 0.4821,-7.7678C0.9109,10.7146 2.3841,9.2682 2.3841,9.2682 4.2056,7.3396 6.268,7.3396 7.2055,7.2325c0,0 6.6964,-0.4821 16.7945,-0.4821z" android:strokeWidth="0.02678544"/>
</vector>

View file

@ -43,13 +43,13 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:text="@string/fragment_debug__app_version"/> android:text="@string/app_version_with_arg"/>
<TextView <TextView
android:id="@+id/fragment_about__app_codename" android:id="@+id/fragment_about__app_codename"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/fragment_debug__app_codename" android:text="@string/code_name_witharg"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:visibility="gone"/> <!-- Change later --> android:visibility="gone"/> <!-- Change later -->
@ -57,7 +57,7 @@
android:textAppearance="@style/TextAppearance.AppCompat" android:textAppearance="@style/TextAppearance.AppCompat"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/fragment_about__about_text" android:text="@string/dandelion_is_your_companion__appspecific"
android:layout_marginBottom="16dp"/> android:layout_marginBottom="16dp"/>
</LinearLayout> </LinearLayout>
@ -86,14 +86,14 @@
style="@android:style/TextAppearance.DeviceDefault.Large" style="@android:style/TextAppearance.DeviceDefault.Large"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/fragment_about__contribute" android:text="@string/contribute_code"
android:layout_marginBottom="8dp"/> android:layout_marginBottom="8dp"/>
<TextView <TextView
android:textAppearance="@style/TextAppearance.AppCompat" android:textAppearance="@style/TextAppearance.AppCompat"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/fragment_about__contribute_text"/> android:text="@string/dandelion_is_developed_as_foss__appspecific"/>
<Button <Button
android:id="@+id/fragment_about__contribute_button" android:id="@+id/fragment_about__contribute_button"
@ -101,7 +101,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
style="?android:attr/buttonBarButtonStyle" style="?android:attr/buttonBarButtonStyle"
android:text="@string/fragment_about__contribute_button"/> android:text="@string/get_the_source"/>
</LinearLayout> </LinearLayout>
</android.support.v7.widget.CardView> </android.support.v7.widget.CardView>
@ -128,14 +128,14 @@
style="@android:style/TextAppearance.DeviceDefault.Large" style="@android:style/TextAppearance.DeviceDefault.Large"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/fragment_about__translate" android:text="@string/translate_the_app"
android:layout_marginBottom="8dp"/> android:layout_marginBottom="8dp"/>
<TextView <TextView
android:textAppearance="@style/TextAppearance.AppCompat" android:textAppearance="@style/TextAppearance.AppCompat"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/fragment_about__translate_text"/> android:text="@string/app_not_available_in_your_lang_translate_with_stringlate"/>
<Button <Button
android:id="@+id/fragment_about__translate_button" android:id="@+id/fragment_about__translate_button"
@ -143,7 +143,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
style="?android:attr/buttonBarButtonStyle" style="?android:attr/buttonBarButtonStyle"
android:text="@string/fragment_about__translate_button"/> android:text="@string/let_me_translate"/>
</LinearLayout> </LinearLayout>
</android.support.v7.widget.CardView> </android.support.v7.widget.CardView>
@ -170,14 +170,14 @@
style="@android:style/TextAppearance.DeviceDefault.Large" style="@android:style/TextAppearance.DeviceDefault.Large"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/fragment_about__feedback" android:text="@string/give_feedback"
android:layout_marginBottom="8dp"/> android:layout_marginBottom="8dp"/>
<TextView <TextView
android:textAppearance="@style/TextAppearance.AppCompat" android:textAppearance="@style/TextAppearance.AppCompat"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/fragment_about__feedback_text"/> android:text="@string/dandelion_still_in_development__appspecific"/>
<Button <Button
android:id="@+id/fragment_about__feedback_button" android:id="@+id/fragment_about__feedback_button"
@ -185,7 +185,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
style="?android:attr/buttonBarButtonStyle" style="?android:attr/buttonBarButtonStyle"
android:text="@string/fragment_about__feedback_button"/> android:text="@string/report_bugs"/>
</LinearLayout> </LinearLayout>
</android.support.v7.widget.CardView> </android.support.v7.widget.CardView>
@ -212,7 +212,7 @@
style="@android:style/TextAppearance.DeviceDefault.Large" style="@android:style/TextAppearance.DeviceDefault.Large"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/fragment_about__spread_the_word" android:text="@string/spread_the_word"
android:layout_marginBottom="8dp"/> android:layout_marginBottom="8dp"/>
<com.github.dfa.diaspora_android.ui.HtmlTextView <com.github.dfa.diaspora_android.ui.HtmlTextView
@ -220,7 +220,7 @@
android:textAppearance="@style/TextAppearance.AppCompat" android:textAppearance="@style/TextAppearance.AppCompat"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/fragment_about__spread_the_word_text"/> android:text="@string/tell_your_friends_about_diaspora__appspecific"/>
<Button <Button
android:id="@+id/fragment_about__spread_the_word_button" android:id="@+id/fragment_about__spread_the_word_button"
@ -228,7 +228,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
style="?android:attr/buttonBarButtonStyle" style="?android:attr/buttonBarButtonStyle"
android:text="@string/fragment_about__spread_the_word_button"/> android:text="@string/share_the_app"/>
</LinearLayout> </LinearLayout>
</android.support.v7.widget.CardView> </android.support.v7.widget.CardView>

View file

@ -23,7 +23,7 @@
style="@android:style/TextAppearance.DeviceDefault.Large" style="@android:style/TextAppearance.DeviceDefault.Large"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/fragment_debug__section_app" /> android:text="@string/application" />
<TextView <TextView
android:id="@+id/fragment_debug__package_name" android:id="@+id/fragment_debug__package_name"
@ -42,7 +42,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="@dimen/activity_vertical_margin" android:paddingTop="@dimen/activity_vertical_margin"
android:text="@string/fragment_debug__section_device" /> android:text="@string/device" />
<TextView <TextView
android:id="@+id/fragment_debug__android_version" android:id="@+id/fragment_debug__android_version"
@ -61,7 +61,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="@dimen/activity_vertical_margin" android:paddingTop="@dimen/activity_vertical_margin"
android:text="@string/fragment_debug__section_pod" /> android:text="@string/diaspora_pod__appspecific" />
<TextView <TextView
android:id="@+id/fragment_debug__account_profile_name" android:id="@+id/fragment_debug__account_profile_name"
@ -80,7 +80,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="@dimen/activity_vertical_margin" android:paddingTop="@dimen/activity_vertical_margin"
android:text="@string/fragment_debug__section_log" /> android:text="@string/debug_log" />
<ScrollView <ScrollView
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -36,14 +36,14 @@
style="@android:style/TextAppearance.DeviceDefault.Large" style="@android:style/TextAppearance.DeviceDefault.Large"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/fragment_license__maintainers"/> android:text="@string/maintainers"/>
<com.github.dfa.diaspora_android.ui.HtmlTextView <com.github.dfa.diaspora_android.ui.HtmlTextView
android:id="@+id/fragment_license__maintainers_text" android:id="@+id/fragment_license__maintainers_text"
android:textAppearance="@style/TextAppearance.AppCompat" android:textAppearance="@style/TextAppearance.AppCompat"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/fragment_license__maintainers_text" android:text="@string/this_app_is_currently_developed_and_maintained_by_witharg"
android:layout_marginBottom="8dp"/> android:layout_marginBottom="8dp"/>
</LinearLayout> </LinearLayout>
@ -72,7 +72,7 @@
style="@android:style/TextAppearance.DeviceDefault.Large" style="@android:style/TextAppearance.DeviceDefault.Large"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/fragment_license__contributors" android:text="@string/contributors"
android:layout_marginBottom="8dp"/> android:layout_marginBottom="8dp"/>
<com.github.dfa.diaspora_android.ui.HtmlTextView <com.github.dfa.diaspora_android.ui.HtmlTextView
@ -123,7 +123,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
style="?android:attr/buttonBarButtonStyle" style="?android:attr/buttonBarButtonStyle"
android:text="@string/fragment_license__license_button"/> android:text="@string/gnu_gplv3_license"/>
</LinearLayout> </LinearLayout>
</android.support.v7.widget.CardView> </android.support.v7.widget.CardView>
@ -150,14 +150,14 @@
style="@android:style/TextAppearance.DeviceDefault.Large" style="@android:style/TextAppearance.DeviceDefault.Large"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/fragment_license__thirdparty_libs" android:text="@string/show_third_party_licenses"
android:layout_marginBottom="8dp"/> android:layout_marginBottom="8dp"/>
<TextView <TextView
android:textAppearance="@style/TextAppearance.AppCompat" android:textAppearance="@style/TextAppearance.AppCompat"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/fragment_license__thirdparty_libs_text"/> android:text="@string/the_following_libraries_are_used"/>
<com.github.dfa.diaspora_android.ui.HtmlTextView <com.github.dfa.diaspora_android.ui.HtmlTextView
android:id="@+id/fragment_license__thirdparty_libs_text" android:id="@+id/fragment_license__thirdparty_libs_text"
@ -197,7 +197,7 @@
android:textAppearance="@style/TextAppearance.AppCompat" android:textAppearance="@style/TextAppearance.AppCompat"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/fragment_license__misc_leafpic"/> android:text="@string/inspiration_from_leafpic__appspecific"/>
<Button <Button
android:id="@+id/fragment_license__leafpic_button" android:id="@+id/fragment_license__leafpic_button"
@ -205,7 +205,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
style="?android:attr/buttonBarButtonStyle" style="?android:attr/buttonBarButtonStyle"
android:text="@string/fragment_license__misc_leafpic_button"/> android:text="@string/tell_me_more"/>
</LinearLayout> </LinearLayout>
</android.support.v7.widget.CardView> </android.support.v7.widget.CardView>

View file

@ -5,11 +5,16 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:context="com.github.dfa.diaspora_android.activity.MainActivity"> tools:context="com.github.dfa.diaspora_android.activity.MainActivity">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.github.dfa.diaspora_android.web.ContextMenuWebView <com.github.dfa.diaspora_android.web.ContextMenuWebView
android:id="@+id/webView" android:id="@+id/webView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
</android.support.v4.widget.SwipeRefreshLayout>
<ProgressBar <ProgressBar
android:id="@+id/progressBar" android:id="@+id/progressBar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal" style="@style/Widget.AppCompat.ProgressBar.Horizontal"
@ -18,4 +23,4 @@
android:indeterminate="false" android:indeterminate="false"
android:progressDrawable="@drawable/progressbar" /> android:progressDrawable="@drawable/progressbar" />
</RelativeLayout> </RelativeLayout>

View file

@ -73,7 +73,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_horizontal_margin_half" android:layout_marginTop="@dimen/activity_horizontal_margin_half"
android:text="@string/http_protocol" android:text="@string/protocol"
android:textAppearance="@style/AppTheme.TextAppearance.Caption" /> android:textAppearance="@style/AppTheme.TextAppearance.Caption" />
<RadioGroup <RadioGroup
@ -113,7 +113,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin_half" android:layout_marginStart="@dimen/activity_horizontal_margin_half"
android:text="@string/pref_title__http_proxy_load_tor_preset" android:text="@string/load_tor_preset"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:visibility="gone" /> android:visibility="gone" />

View file

@ -14,7 +14,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:text="@string/podselection__custom_pod" android:text="@string/custom_pod"
tools:text="Benutzerdefinierter Pod" /> tools:text="Benutzerdefinierter Pod" />

View file

@ -26,43 +26,43 @@
<item <item
android:id="@+id/action_notifications_all" android:id="@+id/action_notifications_all"
android:icon="@drawable/ic_dashboard_black_48px" android:icon="@drawable/ic_dashboard_black_48px"
android:title="@string/notifications__all" /> android:title="@string/all_notifications" />
<item <item
android:id="@+id/action_notifications_also_commented" android:id="@+id/action_notifications_also_commented"
android:icon="@drawable/ic_question_answer_black_48px" android:icon="@drawable/ic_question_answer_black_48px"
android:title="@string/notifications__also_commented" /> android:title="@string/also_commented" />
<item <item
android:id="@+id/action_notifications_comment_on_post" android:id="@+id/action_notifications_comment_on_post"
android:icon="@drawable/ic_comment_black_48px" android:icon="@drawable/ic_comment_black_48px"
android:title="@string/notifications__comment_on_post" /> android:title="@string/comment_on_post" />
<item <item
android:id="@+id/action_notifications_liked" android:id="@+id/action_notifications_liked"
android:icon="@drawable/ic_thumb_up_black_48px" android:icon="@drawable/ic_thumb_up_black_48px"
android:title="@string/notifications__liked" /> android:title="@string/deleteme_____1" />
<item <item
android:id="@+id/action_notifications_mentioned" android:id="@+id/action_notifications_mentioned"
android:icon="@drawable/ic_person_pin_black_48px" android:icon="@drawable/ic_person_pin_black_48px"
android:title="@string/notifications__mentioned" /> android:title="@string/mentioned" />
<item <item
android:id="@+id/action_notifications_reshared" android:id="@+id/action_notifications_reshared"
android:icon="@drawable/ic_repeat_black_48px" android:icon="@drawable/ic_repeat_black_48px"
android:title="@string/notifications__reshared" /> android:title="@string/reshared" />
<item <item
android:id="@+id/action_notifications_started_sharing" android:id="@+id/action_notifications_started_sharing"
android:icon="@drawable/ic_person_add_black_48px" android:icon="@drawable/ic_person_add_black_48px"
android:title="@string/notifications__started_sharing" /> android:title="@string/started_sharing" />
</menu> </menu>
</item> </item>
<item <item
android:id="@+id/action_search" android:id="@+id/action_search"
android:icon="@drawable/ic_search_white_48px" android:icon="@drawable/ic_search_white_48px"
android:title="@string/action_search_by_tags_or_persons" android:title="@string/search_by_tags_or_persons__appspecific"
app:showAsAction="always" /> app:showAsAction="always" />
<item <item

View file

@ -17,7 +17,7 @@
<item <item
android:id="@+id/nav_followed_tags" android:id="@+id/nav_followed_tags"
android:icon="@drawable/ic_local_offer_black_48px" android:icon="@drawable/ic_local_offer_black_48px"
android:title="@string/nav_followed_tags" /> android:title="@string/tags" />
<item <item
android:id="@+id/nav_aspects" android:id="@+id/nav_aspects"
android:icon="@drawable/ic_group_black_48px" android:icon="@drawable/ic_group_black_48px"
@ -51,12 +51,12 @@
<item <item
android:id="@+id/nav_public" android:id="@+id/nav_public"
android:icon="@drawable/ic_public_black_48px" android:icon="@drawable/ic_public_black_48px"
android:title="@string/nav_public_activities" /> android:title="@string/public_activities" />
<item <item
android:id="@+id/nav_dandelion" android:id="@+id/nav_product_support"
android:icon="@drawable/ic_person_black_24px" android:icon="@drawable/ic_person_black_24px"
android:title="@string/dandelion" /> android:title="@string/gsantner" />
<item <item
android:id="@+id/nav_statistics" android:id="@+id/nav_statistics"
@ -66,12 +66,12 @@
<item <item
android:id="@+id/nav_reports" android:id="@+id/nav_reports"
android:icon="@drawable/ic_report_black_48px" android:icon="@drawable/ic_report_black_48px"
android:title="@string/nav_reports" /> android:title="@string/reports" />
<item <item
android:id="@+id/nav_exit" android:id="@+id/nav_exit"
android:icon="@drawable/ic_cancel_black_48px" android:icon="@drawable/ic_cancel_black_48px"
android:title="@string/action_exit_app" android:title="@string/exit_app"
android:visible="false" /> android:visible="false" />
</group> </group>
@ -79,7 +79,7 @@
<item <item
android:id="@+id/nav_toggle_desktop_page" android:id="@+id/nav_toggle_desktop_page"
android:icon="@drawable/ic_sync_white_48px" android:icon="@drawable/ic_sync_white_48px"
android:title="@string/action_toggle_desktop_page" /> android:title="@string/toggle_mobile_desktop_view" />
<item <item
android:id="@+id/nav_settings" android:id="@+id/nav_settings"
@ -89,7 +89,7 @@
<item <item
android:id="@+id/nav_about" android:id="@+id/nav_about"
android:icon="@drawable/ic_info_black_48px" android:icon="@drawable/ic_info_black_48px"
android:title="@string/nav_help_license" /> android:title="@string/help_license" />
</group> </group>
</menu> </menu>

View file

@ -5,25 +5,25 @@
<item <item
android:icon="@drawable/ic_share_white_48px" android:icon="@drawable/ic_share_white_48px"
android:orderInCategory="300" android:orderInCategory="300"
android:title="@string/action_share_dotdotdot" android:title="@string/share_dotdotdot"
app:showAsAction="never"> app:showAsAction="never">
<menu> <menu>
<item <item
android:id="@+id/action_share_screenshot" android:id="@+id/action_share_screenshot"
android:title="@string/share__share_screenshot" /> android:title="@string/share_screenshot_of_webpage" />
<item <item
android:id="@+id/action_take_screenshot" android:id="@+id/action_take_screenshot"
android:title="@string/share__take_screenshot" /> android:title="@string/take_screenshot_of_webpage" />
<item <item
android:id="@+id/action_share_pdf" android:id="@+id/action_share_pdf"
android:title="@string/pdf" android:title="@string/pdf"
android:visible="false" /> android:visible="false" />
<item <item
android:id="@+id/action_share_link" android:id="@+id/action_share_link"
android:title="@string/share__share_link_as_text" /> android:title="@string/share_link_as_text" />
<item <item
android:id="@+id/action_share_link_to_clipboard" android:id="@+id/action_share_link_to_clipboard"
android:title="@string/copy_link_to_clipboard" /> android:title="@string/copy_link_adress_to_clipboard" />
<item <item
android:id="@+id/action_create_launcher_shortcut" android:id="@+id/action_create_launcher_shortcut"
android:title="@string/launcher_shortcut" /> android:title="@string/launcher_shortcut" />
@ -34,7 +34,7 @@
android:id="@+id/action_go_to_top" android:id="@+id/action_go_to_top"
android:icon="@drawable/ic_arrow_upward_white_48px" android:icon="@drawable/ic_arrow_upward_white_48px"
android:orderInCategory="400" android:orderInCategory="400"
android:title="@string/action_go_to_top" android:title="@string/go_to_top"
app:showAsAction="never" /> app:showAsAction="never" />
<item <item

View file

@ -1,5 +1,5 @@
* Gregor Santner (gsantner) * Gregor Santner (gsantner)
~° http://gsantner.net ~° https://github.com/gsantner
* Paul Schaub (vanitasvitae) * Paul Schaub (vanitasvitae)
~° https://github.com/vanitasvitae ~° https://github.com/vanitasvitae

Some files were not shown because too many files have changed in this diff Show more