Compare commits
286 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1124bc48c2 | ||
|
|
69b7484a4a | ||
|
|
19939e457b | ||
|
|
3e256d41d2 | ||
|
|
72f546ed15 | ||
|
|
0b48414715 | ||
|
|
ca67c1eaca | ||
|
|
4dd5a80ef2 | ||
|
|
c9f211807a | ||
|
|
440c15d9fa | ||
|
|
86a443c39f | ||
|
|
a8d86db57f | ||
|
|
8359b48285 | ||
|
|
36f4770cae | ||
|
|
932cf91800 | ||
|
|
5cf02e66b7 | ||
|
|
b7251972a8 | ||
|
|
006a423d5c | ||
|
|
374b1edc81 | ||
|
|
0894549687 | ||
|
|
cc5963cc34 | ||
|
|
fffe77501d | ||
|
|
3443c2ae82 | ||
|
|
01324af544 | ||
|
|
d56b1fe89b | ||
|
|
92457d54df | ||
|
|
4d2e30ff85 | ||
|
|
1d0c279956 | ||
|
|
0202ca5b23 | ||
|
|
5e8ebeadc3 | ||
|
|
8bfb0c45a8 | ||
|
|
2c1ecf32ad | ||
|
|
9cc4bd722d | ||
|
|
d5f1e091b8 | ||
|
|
439f3b44cb | ||
|
|
e07bd39e95 | ||
|
|
11fe2ba2a4 | ||
|
|
2b0e507f45 | ||
|
|
84bad0aa6c | ||
|
|
b41cda84e4 | ||
|
|
0ae6fb2833 | ||
|
|
21b6a1f4ef | ||
|
|
69f9da4be4 | ||
|
|
032db0921d | ||
|
|
eef4601ce2 | ||
|
|
71701048f5 | ||
|
|
2361bb7682 | ||
|
|
0b1b8c5c5a | ||
|
|
fb885b0e00 | ||
|
|
a2dec4f7cf | ||
|
|
521797d070 | ||
|
|
d2922dc226 | ||
|
|
ecc2b675d5 | ||
|
|
5a49b650b0 | ||
|
|
5fd57caabf | ||
|
|
bad6afc543 | ||
|
|
ffaa036115 | ||
|
|
65133e969e | ||
|
|
267ee4e03e | ||
|
|
db972ae421 | ||
|
|
ea93dd5b2d | ||
|
|
c4d738844e | ||
|
|
f124d2cabc | ||
|
|
022038878b | ||
|
|
c6a846c602 | ||
|
|
31986a1ce5 | ||
|
|
04eea3b6e4 | ||
|
|
09a5482df5 | ||
|
|
ba8e5a03ea | ||
|
|
ddb3c34078 | ||
|
|
8d4daa5d00 | ||
|
|
65e1787987 | ||
|
|
202f41b34b | ||
|
|
10497f358e | ||
|
|
ffe0dafbdc | ||
|
|
5828be28e8 | ||
|
|
b4e0605016 | ||
|
|
da7686b9b3 | ||
|
|
651d5ae56a | ||
|
|
570d8ce7eb | ||
|
|
a9491e22e4 | ||
|
|
1ba31afa23 | ||
|
|
8f30d0d468 | ||
|
|
4a8cea262b | ||
|
|
e25574ce9a | ||
|
|
9773c1cb98 | ||
|
|
6d3eafe9e8 | ||
|
|
19acab9d18 | ||
|
|
bfcda1d73c | ||
|
|
600fc7939e | ||
|
|
1189aaae4f | ||
|
|
e9c8e8d764 | ||
|
|
3e90620fcc | ||
|
|
62a364a110 | ||
|
|
fbbbe99bf4 | ||
|
|
d078ccc78c | ||
|
|
cbacb6568e | ||
|
|
65f220b570 | ||
|
|
22d83d831d | ||
|
|
ac564a67ca | ||
|
|
d27a8dc29c | ||
|
|
568dfe911e | ||
|
|
64a647ca84 | ||
|
|
6b3c3ac9b0 | ||
|
|
6438df92c6 | ||
|
|
5f98fdfafc | ||
|
|
4a1c8aadf8 | ||
|
|
441567f9d2 | ||
|
|
381fd434ad | ||
|
|
89f713899b | ||
|
|
a82c61791e | ||
|
|
734c1ddab6 | ||
|
|
1d0b31e9de | ||
|
|
1441036475 | ||
|
|
7a84352723 | ||
|
|
6f153f3879 | ||
|
|
f888091e22 | ||
|
|
e59cf2afca | ||
|
|
7516b8e662 | ||
|
|
bb4a480250 | ||
|
|
c6df18c456 | ||
|
|
cb945998d3 | ||
|
|
5622c93bd9 | ||
|
|
7552227da0 | ||
|
|
1904fce32d | ||
|
|
f1e5e572f4 | ||
|
|
d8f83170be | ||
|
|
95c135b270 | ||
|
|
8408daf070 | ||
|
|
bd8eb6a034 | ||
|
|
9c4d0ef85e | ||
|
|
1ee441314f | ||
|
|
d5d1e51bbc | ||
|
|
84aa99ba88 | ||
|
|
23e02d2c24 | ||
|
|
3ea66c6c4c | ||
|
|
50cf737db6 | ||
|
|
bf55b5a802 | ||
|
|
49bf04c6c6 | ||
|
|
f5d64f3882 | ||
|
|
48d7de53c0 | ||
|
|
d53bace4ce | ||
|
|
02c800496c | ||
|
|
0ce39946cb | ||
|
|
52b573d20f | ||
|
|
5be6faa07c | ||
|
|
ee05e818d9 | ||
|
|
29d5e4fa13 | ||
|
|
9bd830b368 | ||
|
|
7d70f816d1 | ||
|
|
0d0cf04b57 | ||
|
|
4a6e514b81 | ||
|
|
b74f9092e7 | ||
|
|
a061347d76 | ||
|
|
71586b1100 | ||
|
|
809aa6afd2 | ||
|
|
0067a036ae | ||
|
|
794d3329fe | ||
|
|
f96ed6c56f | ||
|
|
92d44eebe6 | ||
|
|
93fe734636 | ||
|
|
71a5c132f4 | ||
|
|
15a514aca5 | ||
|
|
71f74ced7d | ||
|
|
5c86c911c1 | ||
|
|
7aebc44062 | ||
|
|
6f6e1f1009 | ||
|
|
2e7f17b823 | ||
|
|
5194dae9a6 | ||
|
|
2db03669ce | ||
|
|
a6c4f83973 | ||
|
|
f625cea183 | ||
|
|
d6ee9db6ff | ||
|
|
9ea94ce177 | ||
|
|
8102163c5b | ||
|
|
1034c16bfb | ||
|
|
956f9547e7 | ||
|
|
95fb241da1 | ||
|
|
7b06af4c8b | ||
|
|
1ca0ff53c8 | ||
|
|
1e0bdf44c2 | ||
|
|
c611d6386a | ||
|
|
b326dc3bc2 | ||
|
|
7f6e21450a | ||
|
|
ee911a15c6 | ||
|
|
aa0e05f085 | ||
|
|
f1fe078cf2 | ||
|
|
e53dcf27ec | ||
|
|
6e9ce8d5a5 | ||
|
|
686d88557b | ||
|
|
b07f14d01b | ||
|
|
c200a72031 | ||
|
|
2a8ff3e50a | ||
|
|
06cde138c1 | ||
|
|
9fae62f289 | ||
|
|
a1333929e9 | ||
|
|
6a05fafe04 | ||
|
|
13c6fc60f8 | ||
|
|
ff7948ad83 | ||
|
|
3972ab207c | ||
|
|
33a8f1dab4 | ||
|
|
27f261ae4a | ||
|
|
ef59331dd3 | ||
|
|
b019731249 | ||
|
|
47aa7fc191 | ||
|
|
9356b26dfd | ||
|
|
41b626ddbd | ||
|
|
f76b41581b | ||
|
|
14f08b7759 | ||
|
|
8c1cec09d6 | ||
|
|
15c10cb14c | ||
|
|
3b2f68a400 | ||
|
|
2a9e4e0b82 | ||
|
|
7b25628f7a | ||
|
|
e06dd1e465 | ||
|
|
a42ab10aed | ||
|
|
cd2ea90006 | ||
|
|
f0295edd83 | ||
|
|
eee0a40224 | ||
|
|
bd189ae322 | ||
|
|
714aa7a20b | ||
|
|
76c6c65018 | ||
|
|
c511b54de2 | ||
|
|
16b4a1f1a3 | ||
|
|
afb5743d31 | ||
|
|
7e91c311d4 | ||
|
|
eef9f61e20 | ||
|
|
0cc55891c1 | ||
|
|
e892eaa3d5 | ||
|
|
bcdce2a880 | ||
|
|
9f38809730 | ||
|
|
77b416a52d | ||
|
|
0684b0ec79 | ||
|
|
20057a55f0 | ||
|
|
a668dee567 | ||
|
|
11bda2fe07 | ||
|
|
a7b83bc058 | ||
|
|
19950e5115 | ||
|
|
11be65c6fe | ||
|
|
b30ce84468 | ||
|
|
c242c7ec82 | ||
|
|
f128556a49 | ||
|
|
3cebc78443 | ||
|
|
c640501430 | ||
|
|
48100a19e1 | ||
|
|
102ca8034a | ||
|
|
c1d8f5904b | ||
|
|
361086fc48 | ||
|
|
10ebacf9cf | ||
|
|
02bfb34665 | ||
|
|
9f9ba7c367 | ||
|
|
4430822768 | ||
|
|
b4904024c6 | ||
|
|
0d047e5cf9 | ||
|
|
58259d9fb0 | ||
|
|
e9dde114b7 | ||
|
|
03c6f4130b | ||
|
|
bb94c7fff4 | ||
|
|
217884aaec | ||
|
|
17508e1f36 | ||
|
|
95b33b35df | ||
|
|
4c0ae74f2d | ||
|
|
9ef9110297 | ||
|
|
71e0115cd6 | ||
|
|
ec045abf53 | ||
|
|
b8ae79faa1 | ||
|
|
b51cccf1d7 | ||
|
|
87b78acd39 | ||
|
|
a965862c1b | ||
|
|
ce427d2a0b | ||
|
|
8ea4c84a29 | ||
|
|
0bb66708c6 | ||
|
|
bf4b04ef5f | ||
|
|
eea2f9c0cd | ||
|
|
3cdc27eb94 | ||
|
|
75237ab7dc | ||
|
|
7601a7de16 | ||
|
|
3745240eea | ||
|
|
963b80cce9 | ||
|
|
8812500734 | ||
|
|
bd41807f3b | ||
|
|
90ee102eaa | ||
|
|
0bac414151 | ||
|
|
36bd1db67e | ||
|
|
d15f3e5daf | ||
|
|
4c9c444dfc |
79
.github/workflows/build_and_deploy.yml
vendored
Normal file
79
.github/workflows/build_and_deploy.yml
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
name: Build and deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'ci_setup'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 21
|
||||
distribution: temurin
|
||||
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.3.0
|
||||
bundler-cache: true
|
||||
|
||||
- name: Set up Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Decode keystore
|
||||
uses: timheuer/base64-to-file@v1
|
||||
id: android_keystore
|
||||
with:
|
||||
fileName: "release.jks"
|
||||
encodedString: ${{ secrets.KEYSTORE_FILE }}
|
||||
|
||||
- name: Prepare Gradle environment
|
||||
run: |
|
||||
echo "apply from: 'ci_signing.gradle'" >> mastodon/build.gradle
|
||||
echo "sdk.dir=$ANDROID_SDK_ROOT" > local.properties
|
||||
|
||||
- name: Build and deploy to Google Play
|
||||
run: bundle exec fastlane deploy
|
||||
env:
|
||||
KEYSTORE_FILE: ${{ steps.android_keystore.outputs.filePath }}
|
||||
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||
SUPPLY_JSON_KEY_DATA: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_KEY }}
|
||||
SUPPLY_SKIP_UPLOAD_METADATA: true
|
||||
SUPPLY_SKIP_UPLOAD_CHANGELOGS: true
|
||||
|
||||
- name: Build release apk
|
||||
run: ./gradlew assembleRelease
|
||||
env:
|
||||
KEYSTORE_FILE: ${{ steps.android_keystore.outputs.filePath }}
|
||||
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||
|
||||
- name: Upload release apk
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: mastodon-release.apk
|
||||
path: mastodon/build/outputs/apk/release/mastodon-release.apk
|
||||
|
||||
- name: Build githubRelease apk
|
||||
run: ./gradlew assembleGithubRelease
|
||||
env:
|
||||
KEYSTORE_FILE: ${{ steps.android_keystore.outputs.filePath }}
|
||||
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||
|
||||
- name: Upload githubRelease apk
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: mastodon-githubRelease.apk
|
||||
path: mastodon/build/outputs/apk/githubRelease/mastodon-githubRelease.apk
|
||||
|
||||
- name: Upload mappings
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: mappings
|
||||
path: mastodon/build/outputs/mapping/*/mapping.txt
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,3 +9,4 @@
|
||||
.cxx
|
||||
local.properties
|
||||
*.jks
|
||||
/fastlane/report.xml
|
||||
|
||||
218
Gemfile.lock
Normal file
218
Gemfile.lock
Normal file
@@ -0,0 +1,218 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.7)
|
||||
base64
|
||||
nkf
|
||||
rexml
|
||||
addressable (2.8.6)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.913.0)
|
||||
aws-sdk-core (3.191.6)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.8)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.78.0)
|
||||
aws-sdk-core (~> 3, >= 3.191.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.146.1)
|
||||
aws-sdk-core (~> 3, >= 3.191.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.8)
|
||||
aws-sigv4 (1.8.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
base64 (0.2.0)
|
||||
claide (1.1.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.6.5)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.6.20240107)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.110.0)
|
||||
faraday (1.10.3)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-cookie_jar (0.0.7)
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.3.1)
|
||||
fastlane (2.220.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored (~> 1.2)
|
||||
commander (~> 4.6)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
faraday (~> 1.0)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-env (>= 1.6.0, < 2.0.0)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
http-cookie (~> 1.0.5)
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (>= 2.0.0, < 3.0.0)
|
||||
naturally (~> 2.2)
|
||||
optparse (>= 0.1.1, < 1.0.0)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.5)
|
||||
simctl (~> 1.6.3)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (~> 3)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.54.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-core (0.11.3)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
google-apis-iamcredentials_v1 (0.17.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.13.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-storage_v1 (0.31.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-cloud-core (1.7.0)
|
||||
google-cloud-env (>= 1.0, < 3.a)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.4.0)
|
||||
google-cloud-storage (1.47.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.31.0)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.8.1)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.5)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
jmespath (1.6.2)
|
||||
json (2.7.2)
|
||||
jwt (2.8.1)
|
||||
base64
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.5)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.4.0)
|
||||
nanaimo (0.3.0)
|
||||
naturally (2.2.1)
|
||||
nkf (0.2.0)
|
||||
optparse (0.4.0)
|
||||
os (1.1.4)
|
||||
plist (3.7.1)
|
||||
public_suffix (5.0.5)
|
||||
rake (13.2.1)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.2.6)
|
||||
rouge (2.0.7)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
security (0.1.5)
|
||||
signet (0.19.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
naturally
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
trailblazer-option (0.1.2)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.2)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
uber (0.1.0)
|
||||
unicode-display_width (2.5.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.24.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.3.0)
|
||||
rexml (~> 3.2.4)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
arm64-darwin-22
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
fastlane
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.4
|
||||
@@ -5,7 +5,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:7.4.2"
|
||||
classpath "com.android.tools.build:gradle:8.2.2"
|
||||
classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1"
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
||||
2
fastlane/Appfile
Normal file
2
fastlane/Appfile
Normal file
@@ -0,0 +1,2 @@
|
||||
json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
|
||||
package_name("org.joinmastodon.android") # e.g. com.krausefx.app
|
||||
36
fastlane/Fastfile
Normal file
36
fastlane/Fastfile
Normal file
@@ -0,0 +1,36 @@
|
||||
# This file contains the fastlane.tools configuration
|
||||
# You can find the documentation at https://docs.fastlane.tools
|
||||
#
|
||||
# For a list of all available actions, check out
|
||||
#
|
||||
# https://docs.fastlane.tools/actions
|
||||
#
|
||||
# For a list of all available plugins, check out
|
||||
#
|
||||
# https://docs.fastlane.tools/plugins/available-plugins
|
||||
#
|
||||
|
||||
# Uncomment the line if you want fastlane to automatically update itself
|
||||
# update_fastlane
|
||||
|
||||
default_platform(:android)
|
||||
|
||||
platform :android do
|
||||
desc "Runs all the tests"
|
||||
lane :test do
|
||||
gradle(task: "test")
|
||||
end
|
||||
|
||||
desc "Deploy a new version to the Google Play"
|
||||
lane :deploy do
|
||||
gradle(
|
||||
task: "bundle",
|
||||
build_type: "release",
|
||||
)
|
||||
upload_to_play_store(
|
||||
changes_not_sent_for_review: true,
|
||||
skip_upload_images: true,
|
||||
skip_upload_screenshots: true
|
||||
)
|
||||
end
|
||||
end
|
||||
40
fastlane/README.md
Normal file
40
fastlane/README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
fastlane documentation
|
||||
----
|
||||
|
||||
# Installation
|
||||
|
||||
Make sure you have the latest version of the Xcode command line tools installed:
|
||||
|
||||
```sh
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
|
||||
|
||||
# Available Actions
|
||||
|
||||
## Android
|
||||
|
||||
### android test
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android test
|
||||
```
|
||||
|
||||
Runs all the tests
|
||||
|
||||
### android deploy
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android deploy
|
||||
```
|
||||
|
||||
Deploy a new version to the Google Play
|
||||
|
||||
----
|
||||
|
||||
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
|
||||
|
||||
More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
|
||||
|
||||
The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
|
||||
3
fastlane/metadata/android/en-US/changelogs/86.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/86.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
- You can now easily share and scan QR codes to quickly find each other
|
||||
- We've updated the look of the tab bar to better match current platform guidelines
|
||||
- Various minor usability improvements
|
||||
3
fastlane/metadata/android/en-US/changelogs/88.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/88.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
- QR code scanner can now be accessed from the explore page
|
||||
- You can now pin posts to your profile
|
||||
- Fixed featured hashtags on profiles not showing the profile's own posts exclusively
|
||||
8
fastlane/metadata/android/en-US/changelogs/93.txt
Normal file
8
fastlane/metadata/android/en-US/changelogs/93.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
- Adjust filters in the Notifications tab to silence unwanted alerts*
|
||||
- Opt into push notifications when a user posts by tapping the bell 🔔 in the top corner of a user's profile.
|
||||
- Mute overly active conversation notifications via the More button ⋮ on your posts
|
||||
- When writing a post, choose Quiet public 🌙 visibility to avoid appearing in feeds or searches
|
||||
- Improved post legibility with adjusted line height
|
||||
- Bug fixes
|
||||
|
||||
*Your server must support filtered notifications to see this option.
|
||||
@@ -1,6 +1,6 @@
|
||||
Mastodon dalah jaringan sosial terdesentralisasi terbesar di internet. Bukan hanya satu situs web, ini adalah jaringan dari jutaan pengguna dalam komunitas tersendiri yang dapat saling interaksi antar sesama, tanpa batasan. Apapun yang kamu minati, kamu dapat bertemu orang-orang baru yang mengirimkan apa yang mereka minati di Mastodon!
|
||||
|
||||
Bergabunglah dalam sebuah komunitas dan buat profil kalian. Temukan dan ikuti orang-orang yang menarik dan baca postingan mereka dalam linimasa yang kronologis serta bebas iklan. Ekspresikan diri Anda dengan emoji kustom, gambar, GIF, video, dan audio dalam kiriman dengan batasan 500 karakter. Balas ke utasan dan bagikan kiriman dari siapa pun ke pengikut Anda untuk membagikan hal-hal yang keren. Temukan akun baru untuk diikuti dan tagar yang sedang tren untuk memperluas jejaring Anda.
|
||||
Bergabunglah dalam sebuah komunitas dan buat profil kalian. Temukan dan ikuti orang-orang yang menarik dan baca postingan mereka dalam lini masa yang kronologis serta bebas iklan. Ekspresikan diri Anda dengan emoji kustom, gambar, GIF, video, dan audio dalam kiriman dengan batasan 500 karakter. Balas ke utasan dan bagikan kiriman dari siapa pun ke pengikut Anda untuk membagikan hal-hal yang keren. Temukan akun baru untuk diikuti dan tagar yang sedang tren untuk memperluas jejaring Anda.
|
||||
|
||||
Mastodon dibuat dengan fokus pada privasi dan keamanan. Tentukan apakah kiriman Anda dibagikan kepada pengikut, hanya orang yang disebut, atau seluruh dunia. Peringatan konten memungkinkan Anda untuk menyembunyikan kiriman yang berisi material sensitif atau memicu sampai Anda siap untuk terlibat dengan mereka. Setiap komunitas memiliki pedoman dan moderator sendiri-sendiri untuk menjaga anggotanya aman, dan alat pemblokiran dan pelaporan yang kokoh membantu mencegah pelecehan.
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@ Mastodon sukurtas daugiausia dėmesio skiriant privatumui ir saugumui. Nuspręsk
|
||||
|
||||
Daugiau funkcijų:
|
||||
|
||||
• Tamsusis režimas: skaityk įrašus šviesiu, tamsiu arba tikru juodu režimu
|
||||
• Apklausos: paklausk sekėjų nuomonės ir suskaičiuok balsus
|
||||
• Naršyti: tendencingos saitažodžiai ir paskyros – vos nuo vieno prisilietimo
|
||||
• Pranešimai: gauk pranešimus apie naujus sekėjus, atsakymus ir tinklaraščių perrašymus
|
||||
• Bendrinimas: skelbk tiesiogiai į Mastodon iš bet kurio bendrinimo lapo bet kurioje programėlėje
|
||||
• Mielumas: mūsų talismanas yra žavus drambliukas, kurį retkarčiais pamatysi
|
||||
• Tamsusis režimas: skaityk įrašus šviesiu, tamsiu arba tikru juodu režimu.
|
||||
• Apklausos: paklausk sekėjų nuomonės ir suskaičiuok balsus.
|
||||
• Naršyti: tendencingos saitažodžiai ir paskyros – vos nuo vieno prisilietimo.
|
||||
• Pranešimai: gauk pranešimus apie naujus sekėjus, atsakymus ir perrašymus.
|
||||
• Bendrinti: skelbk tiesiogiai į Mastodon iš bet kurio bendrinimo lapo bet kurioje programėlėje.
|
||||
• Mielumas: mūsų talismanas – žavus drambliukas, kurį retkarčiais pamatysi.
|
||||
|
||||
Mastodon yra registruota ne pelno siekianti organizacija, kurios plėtra yra tiesiogiai paremta aukomis. Nėra jokios reklamos, jokių monetizacijos ir rizikos kapitalo, ir mes planuojame, kad taip ir liks.
|
||||
Mastodon – registruota ne pelno siekianti organizacija, kurios plėtra yra tiesiogiai paremta aukomis. Nėra jokios reklamos, jokių monetizacijos ir rizikos kapitalo, ir mes planuojame, kad taip ir liks.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Mastodon, internetteki merkezi olmayan en büyük sosyal ağdır. Tek bir web siteye bağlı kalmaksızın, milyonlarca kullanıcının bağımsız olarak birbiri ile kolayca etkileşebileceği bir ağdır. Hangi konuyla ilgili olduğunuz önemli değil, Mastodon'da onunla ilgili gönderi paylaşan tutkulu insanlarla tanışabilirsiniz!
|
||||
Mastodon, internetteki merkezi olmayan en büyük sosyal ağdır. Tek bir web siteye bağlı kalmaksızın, milyonlarca kullanıcının bağımsız olarak birbirleriyle kolayca etkileşebileceği bir ağdır. Hangi konuyla ilgili olduğunuz önemli değil, Mastodon'da onunla ilgili gönderi paylaşan tutkulu insanlarla tanışabilirsiniz!
|
||||
|
||||
Bir topluluğa katılın ve profilinizi oluşturun. Olağanüstü kişileri bulun ve takip edin; gönderilerini reklamsız ve kronolojik bir zaman çizelgesinde okuyun. Gönderilerinizde şimdilik 500 karakter sınırlamasıyla kendinizi emojiler, görseller, GIFler, videolar ve sesler ile ifade edin. Harika içerikler paylaşmak için başlıklara yanıt yazın, insanların gönderilerini yineleyin. Ağınızı genişletmek için takip edilecek yeni hesaplar ve hashtagler bulun.
|
||||
|
||||
@@ -13,4 +13,4 @@ Diğer özellikler:
|
||||
• Paylaşım: Doğrudan Mastodon'a herhangi bir türde gönderi paylaş
|
||||
• Sevimlilik: Maskotumuz şirin bir fil ve onu uygulamada zaman zaman göreceksiniz
|
||||
|
||||
Mastodon kar amacı gütmeyen bir kuruluştur ve geliştirilmesi doğrudan bağışlarınızla sağlanmaktadır. Reklam yok, para kazanma güdüsü yok, risk sermayesi yok ve bu şekilde devam etmeyi planlıyoruz.
|
||||
Mastodon kâr amacı gütmeyen bir kuruluştur ve geliştirilmesi doğrudan bağışlarınızla sağlanmaktadır. Reklam yok, para kazanma güdüsü yok, risk sermayesi yok ve bu şekilde devam etmeyi planlıyoruz.
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Sat Jun 03 23:40:27 MSK 2023
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -4,15 +4,18 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
androidResources {
|
||||
generateLocaleConfig = true
|
||||
}
|
||||
|
||||
compileSdk 33
|
||||
defaultConfig {
|
||||
applicationId "org.joinmastodon.android"
|
||||
minSdk 23
|
||||
targetSdk 33
|
||||
versionCode 86
|
||||
versionName "2.4.0"
|
||||
versionCode 93
|
||||
versionName "2.5.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "da-rDK", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fa-rIR", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "ig-rNG", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "ka-rGE", "kab", "ko-rKR", "lt-rLT", "my-rMM", "nl-rNL", "no-rNO", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "ur-rIN", "vi-rVN", "zh-rCN", "zh-rTW"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -66,7 +69,15 @@ android {
|
||||
}
|
||||
buildFeatures{
|
||||
aidl true
|
||||
buildConfig true
|
||||
}
|
||||
dependenciesInfo{
|
||||
// Disables dependency metadata when building APKs.
|
||||
includeInApk false
|
||||
// Disables dependency metadata when building Android App Bundles.
|
||||
includeInBundle false
|
||||
}
|
||||
namespace 'org.joinmastodon.android'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -79,7 +90,7 @@ dependencies {
|
||||
implementation 'me.grishka.litex:viewpager:1.0.0'
|
||||
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
||||
implementation 'me.grishka.litex:palette:1.0.0'
|
||||
implementation 'me.grishka.appkit:appkit:1.2.16'
|
||||
implementation 'me.grishka.appkit:appkit:1.2.17'
|
||||
implementation 'com.google.code.gson:gson:2.8.9'
|
||||
implementation 'org.jsoup:jsoup:1.14.3'
|
||||
implementation 'com.squareup:otto:1.3.8'
|
||||
|
||||
20
mastodon/ci_signing.gradle
Normal file
20
mastodon/ci_signing.gradle
Normal file
@@ -0,0 +1,20 @@
|
||||
// Included into build.gradle when running in a CI pipeline
|
||||
|
||||
android{
|
||||
signingConfigs{
|
||||
release{
|
||||
keyAlias "key0"
|
||||
keyPassword System.getenv("KEYSTORE_PASSWORD")
|
||||
storeFile file(System.getenv("KEYSTORE_FILE"))
|
||||
storePassword System.getenv("KEYSTORE_PASSWORD")
|
||||
}
|
||||
}
|
||||
buildTypes{
|
||||
release{
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
githubRelease{
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
}
|
||||
8
mastodon/proguard-rules.pro
vendored
8
mastodon/proguard-rules.pro
vendored
@@ -22,7 +22,7 @@
|
||||
|
||||
# Keep all model classes as they're used with gson and their names are shown in errors
|
||||
-keep public class org.joinmastodon.android.model.**{
|
||||
<fields>;
|
||||
*;
|
||||
}
|
||||
|
||||
# Inner classes in api requests are used with gson
|
||||
@@ -50,4 +50,8 @@
|
||||
|
||||
-keep class org.joinmastodon.android.AppCenterWrapper { *; }
|
||||
|
||||
-keepattributes LineNumberTable
|
||||
-keepattributes LineNumberTable
|
||||
-keepattributes Signature
|
||||
-keep class com.google.gson.reflect.TypeToken { *; }
|
||||
-keep class * extends com.google.gson.reflect.TypeToken
|
||||
-keep public class * implements java.lang.reflect.Type
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
|
||||
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>
|
||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
|
||||
@@ -30,7 +31,6 @@
|
||||
android:allowBackup="true"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:theme="@style/Theme.Mastodon.AutoLightDark"
|
||||
android:largeHeap="true">
|
||||
|
||||
@@ -152,6 +152,11 @@ public class MainActivity extends FragmentStackActivity{
|
||||
}
|
||||
fragment.setArguments(args);
|
||||
showFragment(fragment);
|
||||
Intent intent=getIntent();
|
||||
intent.removeExtra("fromNotification");
|
||||
intent.removeExtra("notification");
|
||||
intent.removeExtra("accountID");
|
||||
setIntent(intent);
|
||||
}
|
||||
|
||||
private void showCompose(){
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
@@ -10,7 +12,7 @@ import java.util.List;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class GetAccountStatuses extends MastodonAPIRequest<List<Status>>{
|
||||
public GetAccountStatuses(String id, String maxID, String minID, int limit, @NonNull Filter filter){
|
||||
public GetAccountStatuses(String id, String maxID, String minID, int limit, @NonNull Filter filter, String hashtag){
|
||||
super(HttpMethod.GET, "/accounts/"+id+"/statuses", new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
@@ -29,6 +31,8 @@ public class GetAccountStatuses extends MastodonAPIRequest<List<Status>>{
|
||||
case OWN_POSTS_AND_REPLIES -> addQueryParameter("exclude_reblogs", "true");
|
||||
case PINNED -> addQueryParameter("pinned", "true");
|
||||
}
|
||||
if(!TextUtils.isEmpty(hashtag))
|
||||
addQueryParameter("tagged", hashtag);
|
||||
}
|
||||
|
||||
public enum Filter{
|
||||
|
||||
@@ -4,10 +4,10 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
|
||||
public class SetAccountFollowed extends MastodonAPIRequest<Relationship>{
|
||||
public SetAccountFollowed(String id, boolean followed, boolean showReblogs){
|
||||
public SetAccountFollowed(String id, boolean followed, boolean showReblogs, boolean notify){
|
||||
super(HttpMethod.POST, "/accounts/"+id+"/"+(followed ? "follow" : "unfollow"), Relationship.class);
|
||||
if(followed)
|
||||
setRequestBody(new Request(showReblogs, null));
|
||||
setRequestBody(new Request(showReblogs, notify));
|
||||
else
|
||||
setRequestBody(new Object());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.joinmastodon.android.api.requests.notifications;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||
import org.joinmastodon.android.model.NotificationRequest;
|
||||
|
||||
public class GetNotificationRequests extends HeaderPaginationRequest<NotificationRequest>{
|
||||
public GetNotificationRequests(String maxID){
|
||||
super(HttpMethod.GET, "/notifications/requests", new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.joinmastodon.android.api.requests.notifications;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.ApiUtils;
|
||||
@@ -12,6 +13,10 @@ import java.util.List;
|
||||
|
||||
public class GetNotifications extends MastodonAPIRequest<List<Notification>>{
|
||||
public GetNotifications(String maxID, int limit, EnumSet<Notification.Type> includeTypes){
|
||||
this(maxID, limit, includeTypes, null);
|
||||
}
|
||||
|
||||
public GetNotifications(String maxID, int limit, EnumSet<Notification.Type> includeTypes, String onlyAccountID){
|
||||
super(HttpMethod.GET, "/notifications", new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
@@ -25,6 +30,8 @@ public class GetNotifications extends MastodonAPIRequest<List<Notification>>{
|
||||
addQueryParameter("exclude_types[]", type);
|
||||
}
|
||||
}
|
||||
if(!TextUtils.isEmpty(onlyAccountID))
|
||||
addQueryParameter("account_id", onlyAccountID);
|
||||
removeUnsupportedItems=true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.joinmastodon.android.api.requests.notifications;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.NotificationsPolicy;
|
||||
|
||||
public class GetNotificationsPolicy extends MastodonAPIRequest<NotificationsPolicy>{
|
||||
public GetNotificationsPolicy(){
|
||||
super(HttpMethod.GET, "/notifications/policy", NotificationsPolicy.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.joinmastodon.android.api.requests.notifications;
|
||||
|
||||
import org.joinmastodon.android.api.ResultlessMastodonAPIRequest;
|
||||
|
||||
public class RespondToNotificationRequest extends ResultlessMastodonAPIRequest{
|
||||
public RespondToNotificationRequest(String id, boolean allow){
|
||||
super(HttpMethod.POST, "/notifications/requests/"+id+(allow ? "/accept" : "/dismiss"));
|
||||
setRequestBody(new Object());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.api.requests.notifications;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.NotificationsPolicy;
|
||||
|
||||
public class SetNotificationsPolicy extends MastodonAPIRequest<NotificationsPolicy>{
|
||||
public SetNotificationsPolicy(NotificationsPolicy policy){
|
||||
super(HttpMethod.PUT, "/notifications/policy", NotificationsPolicy.class);
|
||||
setRequestBody(policy);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
public class SetStatusConversationMuted extends MastodonAPIRequest<Status>{
|
||||
public SetStatusConversationMuted(String id, boolean muted){
|
||||
super(HttpMethod.POST, "/statuses/"+id+(muted ? "/mute" : "/unmute"), Status.class);
|
||||
setRequestBody(new Object());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
public class SetStatusPinned extends MastodonAPIRequest<Status>{
|
||||
public SetStatusPinned(String id, boolean pinned){
|
||||
super(HttpMethod.POST, "/statuses/"+id+"/"+(pinned ? "pin" : "unpin"), Status.class);
|
||||
setRequestBody(new Object());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.joinmastodon.android.events;
|
||||
|
||||
public class NotificationRequestRespondedEvent{
|
||||
public final String accountID, requestID;
|
||||
|
||||
public NotificationRequestRespondedEvent(String accountID, String requestID){
|
||||
this.accountID=accountID;
|
||||
this.requestID=requestID;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
|
||||
import org.joinmastodon.android.api.requests.notifications.RespondToNotificationRequest;
|
||||
import org.joinmastodon.android.events.NotificationRequestRespondedEvent;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.ui.Snackbar;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
|
||||
public class AccountNotificationsListFragment extends BaseNotificationsListFragment{
|
||||
private Account account;
|
||||
private String requestID;
|
||||
private TextView expandedTitle;
|
||||
private boolean choiceMade, allowed;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
account=Parcels.unwrap(getArguments().getParcelable("targetAccount"));
|
||||
requestID=getArguments().getString("requestID");
|
||||
setTitleMarqueeEnabled(false);
|
||||
loadData();
|
||||
setTitle(getString(R.string.notifications_from_user, account.displayName));
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
if(!refreshing && endMark!=null)
|
||||
endMark.setVisibility(View.GONE);
|
||||
currentRequest=new GetNotifications(offset==0 ? null : maxID, count, EnumSet.allOf(Notification.Type.class), account.id)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Notification> result){
|
||||
onDataLoaded(result, !result.isEmpty());
|
||||
maxID=result.isEmpty() ? null : result.get(result.size()-1).id;
|
||||
endMark.setVisibility(result.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean needDividerForExtraItem(View child, View bottomSibling, RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder){
|
||||
return super.needDividerForExtraItem(child, bottomSibling, holder, siblingHolder) || (siblingHolder!=null && siblingHolder.getAbsoluteAdapterPosition()>=list.getAdapter().getItemCount());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
MergeRecyclerAdapter mergeAdapter=new MergeRecyclerAdapter();
|
||||
|
||||
expandedTitle=(TextView) LayoutInflater.from(getActivity()).inflate(R.layout.expanded_title_medium, list, false);
|
||||
expandedTitle.setText(getTitle());
|
||||
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(expandedTitle));
|
||||
|
||||
mergeAdapter.addAdapter(super.getAdapter());
|
||||
return mergeAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
|
||||
if(recyclerView.getChildCount()==0)
|
||||
return;
|
||||
float fraction;
|
||||
View topChild=recyclerView.getChildAt(0);
|
||||
if(recyclerView.getChildAdapterPosition(topChild)>0){
|
||||
fraction=1;
|
||||
}else{
|
||||
fraction=(-topChild.getTop())/(float)(topChild.getHeight()-topChild.getPaddingBottom());
|
||||
}
|
||||
expandedTitle.setAlpha(1f-fraction);
|
||||
toolbarTitleView.setAlpha(fraction);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||
inflater.inflate(R.menu.notification_request, menu);
|
||||
MenuItem mute=menu.findItem(R.id.mute);
|
||||
MenuItem allow=menu.findItem(R.id.allow);
|
||||
if(choiceMade && allowed){
|
||||
allow.setIcon(R.drawable.ic_check_wght700_24px);
|
||||
tintMenuIcon(allow, R.attr.colorM3Primary);
|
||||
}else{
|
||||
tintMenuIcon(allow, R.attr.colorM3OnSurfaceVariant);
|
||||
}
|
||||
if(choiceMade && !allowed){
|
||||
mute.setIcon(R.drawable.ic_delete_wght700_24px);
|
||||
tintMenuIcon(mute, R.attr.colorM3Primary);
|
||||
}else{
|
||||
tintMenuIcon(mute, R.attr.colorM3OnSurfaceVariant);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item){
|
||||
if(choiceMade)
|
||||
return true;
|
||||
allowed=item.getItemId()==R.id.allow;
|
||||
new RespondToNotificationRequest(requestID, allowed)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Void result){
|
||||
choiceMade=true;
|
||||
invalidateOptionsMenu();
|
||||
E.post(new NotificationRequestRespondedEvent(accountID, requestID));
|
||||
new Snackbar.Builder(getActivity())
|
||||
.setText(getString(allowed ? R.string.notifications_allowed : R.string.notifications_muted, account.displayName))
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(getActivity());
|
||||
}
|
||||
})
|
||||
.wrapProgress(getActivity(), R.string.loading, false)
|
||||
.exec(accountID);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Notification n){
|
||||
if(n.type==Notification.Type.MENTION || n.type==Notification.Type.STATUS){
|
||||
return StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, StatusDisplayItem.FLAG_MEDIA_FORCE_HIDDEN);
|
||||
}
|
||||
return super.buildDisplayItems(n);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean wantsToolbarMenuIconsTinted(){
|
||||
return false;
|
||||
}
|
||||
|
||||
private void tintMenuIcon(MenuItem item, int color){
|
||||
int tintColor=UiUtils.getThemeColor(getActivity(), color);
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.O){
|
||||
Drawable icon=item.getIcon();
|
||||
if(icon!=null && icon.getColorFilter()==null){
|
||||
icon=icon.mutate();
|
||||
icon.setTintList(ColorStateList.valueOf(tintColor));
|
||||
item.setIcon(icon);
|
||||
}
|
||||
}else{
|
||||
item.setIconTintList(ColorStateList.valueOf(tintColor));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.FilterContext;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
@@ -58,7 +57,7 @@ public class AccountTimelineFragment extends StatusListFragment{
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetAccountStatuses(user.id, offset>0 ? getMaxID() : null, null, count, filter)
|
||||
currentRequest=new GetAccountStatuses(user.id, offset>0 ? getMaxID() : null, null, count, filter, null)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
@@ -141,11 +140,6 @@ public class AccountTimelineFragment extends StatusListFragment{
|
||||
return mergeAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMainAdapterOffset(){
|
||||
return super.getMainAdapterOffset()+1;
|
||||
}
|
||||
|
||||
private FilterChipView getViewForFilter(GetAccountStatuses.Filter filter){
|
||||
return switch(filter){
|
||||
case DEFAULT -> defaultFilter;
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.NotificationHeaderStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
|
||||
public abstract class BaseNotificationsListFragment extends BaseStatusListFragment<Notification>{
|
||||
protected String maxID;
|
||||
protected View endMark;
|
||||
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Notification n){
|
||||
NotificationHeaderStatusDisplayItem titleItem;
|
||||
if(n.type==Notification.Type.MENTION || n.type==Notification.Type.STATUS){
|
||||
titleItem=null;
|
||||
}else{
|
||||
titleItem=new NotificationHeaderStatusDisplayItem(n.id, this, n, accountID);
|
||||
if(n.status!=null){
|
||||
n.status.card=null;
|
||||
n.status.spoilerText=null;
|
||||
}
|
||||
}
|
||||
if(n.status!=null){
|
||||
int flags=titleItem==null ? 0 : (StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_INSET | StatusDisplayItem.FLAG_NO_HEADER);
|
||||
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, flags);
|
||||
if(titleItem!=null)
|
||||
items.add(0, titleItem);
|
||||
return items;
|
||||
}else if(titleItem!=null){
|
||||
return Collections.singletonList(titleItem);
|
||||
}else{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addAccountToKnown(Notification s){
|
||||
if(!knownAccounts.containsKey(s.account.id))
|
||||
knownAccounts.put(s.account.id, s.account);
|
||||
if(s.status!=null && !knownAccounts.containsKey(s.status.account.id))
|
||||
knownAccounts.put(s.status.account.id, s.status.account);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(String id){
|
||||
Notification n=getNotificationByID(id);
|
||||
if(n.status!=null){
|
||||
Status status=n.status;
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("status", Parcels.wrap(status.clone()));
|
||||
if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId))
|
||||
args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(status.inReplyToAccountId)));
|
||||
Nav.go(getActivity(), ThreadFragment.class, args);
|
||||
}else{
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(n.account));
|
||||
Nav.go(getActivity(), ProfileFragment.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
private Notification getNotificationByID(String id){
|
||||
for(Notification n : data){
|
||||
if(n.id.equals(id))
|
||||
return n;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void removeNotification(Notification n){
|
||||
data.remove(n);
|
||||
preloadedData.remove(n);
|
||||
int index=-1;
|
||||
for(int i=0; i<displayItems.size(); i++){
|
||||
if(n.id.equals(displayItems.get(i).parentID)){
|
||||
index=i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(index==-1)
|
||||
return;
|
||||
int lastIndex;
|
||||
for(lastIndex=index; lastIndex<displayItems.size(); lastIndex++){
|
||||
if(!displayItems.get(lastIndex).parentID.equals(n.id))
|
||||
break;
|
||||
}
|
||||
displayItems.subList(index, lastIndex).clear();
|
||||
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateFooterView(LayoutInflater inflater){
|
||||
View v=inflater.inflate(R.layout.load_more_with_end_mark, null);
|
||||
endMark=v.findViewById(R.id.end_mark);
|
||||
endMark.setVisibility(View.GONE);
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addItemDecoration(new InsetStatusItemDecoration(this));
|
||||
}
|
||||
}
|
||||
@@ -325,7 +325,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
toolbar.setNavigationContentDescription(R.string.back);
|
||||
}
|
||||
|
||||
protected int getMainAdapterOffset(){
|
||||
public int getMainAdapterOffset(){
|
||||
if(list.getAdapter() instanceof MergeRecyclerAdapter mergeAdapter){
|
||||
return mergeAdapter.getPositionForAdapter(adapter);
|
||||
}
|
||||
|
||||
@@ -915,6 +915,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
int id=item.getItemId();
|
||||
if(id==R.id.vis_public){
|
||||
statusVisibility=StatusPrivacy.PUBLIC;
|
||||
}else if(id==R.id.vis_unlisted){
|
||||
statusVisibility=StatusPrivacy.UNLISTED;
|
||||
}else if(id==R.id.vis_followers){
|
||||
statusVisibility=StatusPrivacy.PRIVATE;
|
||||
}else if(id==R.id.vis_private){
|
||||
@@ -950,12 +952,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
private void applyPreferencesForPostVisibility(Preferences prefs, Bundle savedInstanceState){
|
||||
// Only override the reply visibility if our preference is more private
|
||||
if(prefs.postingDefaultVisibility.isLessVisibleThan(statusVisibility)){
|
||||
// Map unlisted from the API onto public, because we don't have unlisted in the UI
|
||||
statusVisibility=switch(prefs.postingDefaultVisibility){
|
||||
case PUBLIC, UNLISTED -> StatusPrivacy.PUBLIC;
|
||||
case PRIVATE -> StatusPrivacy.PRIVATE;
|
||||
case DIRECT -> StatusPrivacy.DIRECT;
|
||||
};
|
||||
statusVisibility=prefs.postingDefaultVisibility;
|
||||
}
|
||||
|
||||
// A saved privacy setting from a previous compose session wins over all
|
||||
@@ -973,12 +970,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
statusVisibility=StatusPrivacy.PUBLIC;
|
||||
}
|
||||
visibilityBtn.setText(switch(statusVisibility){
|
||||
case PUBLIC, UNLISTED -> R.string.visibility_public;
|
||||
case PUBLIC -> R.string.visibility_public;
|
||||
case UNLISTED -> R.string.visibility_unlisted;
|
||||
case PRIVATE -> R.string.visibility_followers_only;
|
||||
case DIRECT -> R.string.visibility_private;
|
||||
});
|
||||
Drawable icon=getResources().getDrawable(switch(statusVisibility){
|
||||
case PUBLIC, UNLISTED -> R.drawable.ic_public_20px;
|
||||
case PUBLIC -> R.drawable.ic_public_20px;
|
||||
case UNLISTED -> R.drawable.ic_clear_night_20px;
|
||||
case PRIVATE -> R.drawable.ic_group_20px;
|
||||
case DIRECT -> R.drawable.ic_alternate_email_20px;
|
||||
}, getActivity().getTheme()).mutate();
|
||||
|
||||
@@ -10,7 +10,6 @@ import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -19,6 +18,7 @@ import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
|
||||
public class FeaturedHashtagsListFragment extends BaseStatusListFragment<Hashtag>{
|
||||
private Account account;
|
||||
@@ -45,7 +45,11 @@ public class FeaturedHashtagsListFragment extends BaseStatusListFragment<Hashtag
|
||||
|
||||
@Override
|
||||
public void onItemClick(String id){
|
||||
UiUtils.openHashtagTimeline(getActivity(), accountID, Objects.requireNonNull(findItemOfType(id, HashtagStatusDisplayItem.class)).tag);
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("targetAccount", Parcels.wrap(account));
|
||||
args.putParcelable("hashtag", Parcels.wrap(Objects.requireNonNull(findItemOfType(id, HashtagStatusDisplayItem.class)).tag));
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), HashtagFeaturedTimelineFragment.class, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.FilterContext;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
// The difference between this and HashtagTimelineFragment is that this opens from the featured hashtags
|
||||
// and only shows posts by that account.
|
||||
public class HashtagFeaturedTimelineFragment extends StatusListFragment{
|
||||
private Account targetAccount;
|
||||
private Hashtag hashtag;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
targetAccount=Parcels.unwrap(getArguments().getParcelable("targetAccount"));
|
||||
hashtag=Parcels.unwrap(getArguments().getParcelable("hashtag"));
|
||||
setTitle("#"+hashtag.name);
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetAccountStatuses(targetAccount.id, offset>0 ? getMaxID() : null, null, count, GetAccountStatuses.Filter.DEFAULT, hashtag.name)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
boolean empty=result.isEmpty();
|
||||
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.ACCOUNT);
|
||||
onDataLoaded(result, !empty);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,6 @@ import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
@@ -155,7 +154,7 @@ public class HashtagTimelineFragment extends StatusListFragment{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMainAdapterOffset(){
|
||||
public int getMainAdapterOffset(){
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
content.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
FrameLayout fragmentContainer=new FrameLayout(getActivity());
|
||||
fragmentContainer.setId(R.id.fragment_wrap);
|
||||
fragmentContainer.setId(me.grishka.appkit.R.id.fragment_wrap);
|
||||
content.addView(fragmentContainer, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
|
||||
|
||||
inflater.inflate(R.layout.tab_bar, content);
|
||||
@@ -131,10 +131,10 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
|
||||
if(savedInstanceState==null){
|
||||
getChildFragmentManager().beginTransaction()
|
||||
.add(R.id.fragment_wrap, homeTimelineFragment)
|
||||
.add(R.id.fragment_wrap, searchFragment).hide(searchFragment)
|
||||
.add(R.id.fragment_wrap, notificationsFragment).hide(notificationsFragment)
|
||||
.add(R.id.fragment_wrap, profileFragment).hide(profileFragment)
|
||||
.add(me.grishka.appkit.R.id.fragment_wrap, homeTimelineFragment)
|
||||
.add(me.grishka.appkit.R.id.fragment_wrap, searchFragment).hide(searchFragment)
|
||||
.add(me.grishka.appkit.R.id.fragment_wrap, notificationsFragment).hide(notificationsFragment)
|
||||
.add(me.grishka.appkit.R.id.fragment_wrap, profileFragment).hide(profileFragment)
|
||||
.commit();
|
||||
|
||||
String defaultTab=getArguments().getString("tab");
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.notifications.GetNotificationRequests;
|
||||
import org.joinmastodon.android.api.requests.notifications.RespondToNotificationRequest;
|
||||
import org.joinmastodon.android.events.NotificationRequestRespondedEvent;
|
||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||
import org.joinmastodon.android.model.NotificationRequest;
|
||||
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
|
||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.Snackbar;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class NotificationRequestsFragment extends MastodonRecyclerFragment<NotificationRequest>{
|
||||
private String accountID;
|
||||
private String maxID;
|
||||
private HashMap<String, AccountViewModel> accountViewModels=new HashMap<>();
|
||||
private View endMark;
|
||||
private NotificationRequestsAdapter adapter;
|
||||
|
||||
public NotificationRequestsFragment(){
|
||||
super(50);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
accountID=getArguments().getString("account");
|
||||
setTitle(R.string.filtered_notifications);
|
||||
loadData();
|
||||
E.register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy(){
|
||||
E.unregister(this);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
if(!refreshing && endMark!=null)
|
||||
endMark.setVisibility(View.GONE);
|
||||
currentRequest=new GetNotificationRequests(offset==0 ? null : maxID)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(HeaderPaginationList<NotificationRequest> result){
|
||||
if(data.isEmpty() || refreshing)
|
||||
accountViewModels.clear();
|
||||
maxID=result.getNextPageMaxID();
|
||||
for(NotificationRequest req:result){
|
||||
accountViewModels.put(req.account.id, new AccountViewModel(req.account, accountID, false));
|
||||
}
|
||||
onDataLoaded(result, !TextUtils.isEmpty(maxID));
|
||||
endMark.setVisibility(TextUtils.isEmpty(maxID) ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter<?> getAdapter(){
|
||||
return adapter=new NotificationRequestsAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.setItemAnimator(new BetterItemAnimator());
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3OutlineVariant, 1, 0, 0, vh->vh instanceof NotificationRequestViewHolder).setDrawBelowLastItem(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateFooterView(LayoutInflater inflater){
|
||||
View v=inflater.inflate(R.layout.load_more_with_end_mark, null);
|
||||
endMark=v.findViewById(R.id.end_mark);
|
||||
endMark.setVisibility(View.GONE);
|
||||
return v;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onNotificationRequestResponded(NotificationRequestRespondedEvent ev){
|
||||
if(adapter==null || !ev.accountID.equals(accountID))
|
||||
return;
|
||||
for(int i=0;i<data.size();i++){
|
||||
if(data.get(i).id.equals(ev.requestID)){
|
||||
data.remove(i);
|
||||
adapter.notifyItemRemoved(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for(NotificationRequest nr:preloadedData){
|
||||
if(nr.id.equals(ev.requestID)){
|
||||
preloadedData.remove(nr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class NotificationRequestsAdapter extends UsableRecyclerView.Adapter<NotificationRequestViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
|
||||
public NotificationRequestsAdapter(){
|
||||
super(imgLoader);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public NotificationRequestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new NotificationRequestViewHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return data.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(NotificationRequestViewHolder holder, int position){
|
||||
holder.bind(data.get(position));
|
||||
super.onBindViewHolder(holder, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getImageCountForItem(int position){
|
||||
return Objects.requireNonNull(accountViewModels.get(data.get(position).account.id)).emojiHelper.getImageCount()+1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||
AccountViewModel model=Objects.requireNonNull(accountViewModels.get(data.get(position).account.id));
|
||||
return switch(image){
|
||||
case 0 -> model.avaRequest;
|
||||
default -> model.emojiHelper.getImageRequest(image-1);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private class NotificationRequestViewHolder extends BindableViewHolder<NotificationRequest> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
|
||||
private final TextView name, username, badge;
|
||||
private final ImageView ava;
|
||||
private final ImageButton allow, mute;
|
||||
|
||||
public NotificationRequestViewHolder(){
|
||||
super(getActivity(), R.layout.item_notification_request, list);
|
||||
name=findViewById(R.id.name);
|
||||
username=findViewById(R.id.username);
|
||||
badge=findViewById(R.id.badge);
|
||||
ava=findViewById(R.id.ava);
|
||||
allow=findViewById(R.id.btn_allow);
|
||||
mute=findViewById(R.id.btn_mute);
|
||||
ava.setOutlineProvider(OutlineProviders.roundedRect(8));
|
||||
ava.setClipToOutline(true);
|
||||
allow.setOnClickListener(this::onAllowClick);
|
||||
mute.setOnClickListener(this::onMuteClick);
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
@Override
|
||||
public void onBind(NotificationRequest item){
|
||||
AccountViewModel model=Objects.requireNonNull(accountViewModels.get(item.account.id));
|
||||
name.setText(model.parsedName);
|
||||
username.setText(item.account.getDisplayUsername());
|
||||
badge.setText(item.notificationsCount>99 ? String.format("%d+", 99) : String.format("%d", item.notificationsCount));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(int index, Drawable image){
|
||||
if(index==0){
|
||||
if(image==null)
|
||||
ava.setImageResource(R.drawable.image_placeholder);
|
||||
else
|
||||
ava.setImageDrawable(image);
|
||||
}else{
|
||||
AccountViewModel model=Objects.requireNonNull(accountViewModels.get(item.account.id));
|
||||
model.emojiHelper.setImageDrawable(index-1, image);
|
||||
name.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("targetAccount", Parcels.wrap(item.account));
|
||||
args.putString("requestID", item.id);
|
||||
Nav.go(getActivity(), AccountNotificationsListFragment.class, args);
|
||||
}
|
||||
|
||||
private void onAllowClick(View v){
|
||||
acceptOrDecline(true);
|
||||
}
|
||||
|
||||
private void onMuteClick(View v){
|
||||
acceptOrDecline(false);
|
||||
}
|
||||
|
||||
private void acceptOrDecline(boolean accept){
|
||||
new RespondToNotificationRequest(item.id, accept)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Void result){
|
||||
int pos=data.indexOf(item);
|
||||
data.remove(pos);
|
||||
adapter.notifyItemRemoved(pos);
|
||||
new Snackbar.Builder(getActivity())
|
||||
.setText(getString(accept ? R.string.notifications_allowed : R.string.notifications_muted, item.account.displayName))
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(getActivity());
|
||||
}
|
||||
})
|
||||
.wrapProgress(getActivity(), R.string.loading, false)
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +1,70 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
||||
import org.joinmastodon.android.api.requests.notifications.GetNotificationsPolicy;
|
||||
import org.joinmastodon.android.api.requests.notifications.SetNotificationsPolicy;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.NotificationsPolicy;
|
||||
import org.joinmastodon.android.model.PaginatedResponse;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
|
||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.displayitems.NotificationHeaderStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.viewcontrollers.GenericListItemsViewController;
|
||||
import org.joinmastodon.android.ui.views.NestedRecyclerScrollView;
|
||||
import org.joinmastodon.android.utils.ObjectIdComparator;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
|
||||
public class NotificationsListFragment extends BaseStatusListFragment<Notification>{
|
||||
public class NotificationsListFragment extends BaseNotificationsListFragment{
|
||||
private boolean onlyMentions;
|
||||
private String maxID;
|
||||
private View tabBar;
|
||||
private View mentionsTab, allTab;
|
||||
private View endMark;
|
||||
private String unreadMarker, realUnreadMarker;
|
||||
private MenuItem markAllReadItem;
|
||||
private boolean reloadingFromCache;
|
||||
private ListItem<Void> requestsItem=new ListItem<>(R.string.filtered_notifications, 0, R.drawable.ic_inventory_2_24px, i->openNotificationRequests());
|
||||
private ArrayList<ListItem<Void>> requestsItems=new ArrayList<>();
|
||||
private GenericListItemsAdapter<Void> requestsRowAdapter=new GenericListItemsAdapter<>(requestsItems);
|
||||
private NotificationsPolicy lastPolicy;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -74,43 +87,12 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
setTitle(R.string.notifications);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Notification n){
|
||||
NotificationHeaderStatusDisplayItem titleItem;
|
||||
if(n.type==Notification.Type.MENTION || n.type==Notification.Type.STATUS){
|
||||
titleItem=null;
|
||||
}else{
|
||||
titleItem=new NotificationHeaderStatusDisplayItem(n.id, this, n, accountID);
|
||||
if(n.status!=null){
|
||||
n.status.card=null;
|
||||
n.status.spoilerText=null;
|
||||
}
|
||||
}
|
||||
if(n.status!=null){
|
||||
int flags=titleItem==null ? 0 : (StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_INSET | StatusDisplayItem.FLAG_NO_HEADER);
|
||||
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, flags);
|
||||
if(titleItem!=null)
|
||||
items.add(0, titleItem);
|
||||
return items;
|
||||
}else if(titleItem!=null){
|
||||
return Collections.singletonList(titleItem);
|
||||
}else{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addAccountToKnown(Notification s){
|
||||
if(!knownAccounts.containsKey(s.account.id))
|
||||
knownAccounts.put(s.account.id, s.account);
|
||||
if(s.status!=null && !knownAccounts.containsKey(s.status.account.id))
|
||||
knownAccounts.put(s.status.account.id, s.status.account);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
if(!refreshing && !reloadingFromCache)
|
||||
endMark.setVisibility(View.GONE);
|
||||
if(offset==0)
|
||||
reloadPolicy();
|
||||
AccountSessionManager.getInstance()
|
||||
.getAccount(accountID).getCacheController()
|
||||
.getNotifications(offset>0 ? maxID : null, count, onlyMentions, refreshing && !reloadingFromCache, new SimpleCallback<>(this){
|
||||
@@ -142,30 +124,10 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
resetUnreadBackground();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(String id){
|
||||
Notification n=getNotificationByID(id);
|
||||
if(n.status!=null){
|
||||
Status status=n.status;
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("status", Parcels.wrap(status.clone()));
|
||||
if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId))
|
||||
args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(status.inReplyToAccountId)));
|
||||
Nav.go(getActivity(), ThreadFragment.class, args);
|
||||
}else{
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(n.account));
|
||||
Nav.go(getActivity(), ProfileFragment.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
tabBar=view.findViewById(R.id.tabbar);
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addItemDecoration(new InsetStatusItemDecoration(this));
|
||||
|
||||
View tabBarItself=view.findViewById(R.id.tabbar_inner);
|
||||
tabBarItself.setOutlineProvider(OutlineProviders.roundedRect(20));
|
||||
@@ -215,14 +177,6 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
return views;
|
||||
}
|
||||
|
||||
private Notification getNotificationByID(String id){
|
||||
for(Notification n:data){
|
||||
if(n.id.equals(id))
|
||||
return n;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPollUpdated(PollUpdatedEvent ev){
|
||||
if(!ev.accountID.equals(accountID))
|
||||
@@ -249,25 +203,9 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
}
|
||||
}
|
||||
|
||||
private void removeNotification(Notification n){
|
||||
data.remove(n);
|
||||
preloadedData.remove(n);
|
||||
int index=-1;
|
||||
for(int i=0;i<displayItems.size();i++){
|
||||
if(n.id.equals(displayItems.get(i).parentID)){
|
||||
index=i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(index==-1)
|
||||
return;
|
||||
int lastIndex;
|
||||
for(lastIndex=index;lastIndex<displayItems.size();lastIndex++){
|
||||
if(!displayItems.get(lastIndex).parentID.equals(n.id))
|
||||
break;
|
||||
}
|
||||
displayItems.subList(index, lastIndex).clear();
|
||||
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
||||
@Override
|
||||
protected boolean needDividerForExtraItem(View child, View bottomSibling, RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder){
|
||||
return super.needDividerForExtraItem(child, bottomSibling, holder, siblingHolder) || (siblingHolder!=null && siblingHolder.getAbsoluteAdapterPosition()>=adapter.getItemCount()) || holder.getAbsoluteAdapterPosition()<requestsItems.size();
|
||||
}
|
||||
|
||||
private void onTabClick(View v){
|
||||
@@ -285,34 +223,34 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
AccountSessionManager.get(accountID).setNotificationsMentionsOnly(onlyMentions);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateFooterView(LayoutInflater inflater){
|
||||
View v=inflater.inflate(R.layout.load_more_with_end_mark, null);
|
||||
endMark=v.findViewById(R.id.end_mark);
|
||||
endMark.setVisibility(View.GONE);
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean needDividerForExtraItem(View child, View bottomSibling, RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder){
|
||||
return super.needDividerForExtraItem(child, bottomSibling, holder, siblingHolder) || (siblingHolder!=null && siblingHolder.getAbsoluteAdapterPosition()>=adapter.getItemCount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||
inflater.inflate(R.menu.notifications, menu);
|
||||
markAllReadItem=menu.findItem(R.id.mark_all_read);
|
||||
MenuItem filters=menu.findItem(R.id.filters);
|
||||
filters.setVisible(lastPolicy!=null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item){
|
||||
if(item.getItemId()==R.id.mark_all_read){
|
||||
int id=item.getItemId();
|
||||
if(id==R.id.mark_all_read){
|
||||
markAsRead();
|
||||
resetUnreadBackground();
|
||||
}else if(id==R.id.filters){
|
||||
showFiltersAlert();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
MergeRecyclerAdapter mergeAdapter=new MergeRecyclerAdapter();
|
||||
mergeAdapter.addAdapter(requestsRowAdapter);
|
||||
mergeAdapter.addAdapter(super.getAdapter());
|
||||
return mergeAdapter;
|
||||
}
|
||||
|
||||
private void markAsRead(){
|
||||
if(data.isEmpty())
|
||||
return;
|
||||
@@ -366,4 +304,93 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updatePolicy(NotificationsPolicy policy){
|
||||
int count=policy.summary==null ? 0 : policy.summary.pendingRequestsCount;
|
||||
boolean isShown=!requestsItems.isEmpty();
|
||||
boolean needShow=count>0;
|
||||
if(isShown && !needShow){
|
||||
requestsItems.clear();
|
||||
requestsRowAdapter.notifyItemRemoved(0);
|
||||
}else if(!isShown && needShow){
|
||||
requestsItem.subtitle=getResources().getQuantityString(R.plurals.x_people_you_may_know, count, count);
|
||||
requestsItems.add(requestsItem);
|
||||
requestsRowAdapter.notifyItemInserted(0);
|
||||
}else if(isShown){
|
||||
requestsItem.subtitle=getResources().getQuantityString(R.plurals.x_people_you_may_know, count, count);
|
||||
requestsRowAdapter.notifyItemChanged(0);
|
||||
}
|
||||
lastPolicy=policy;
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
private void reloadPolicy(){
|
||||
new GetNotificationsPolicy()
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(NotificationsPolicy policy){
|
||||
updatePolicy(policy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse errorResponse){
|
||||
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
private void showFiltersAlert(){
|
||||
GenericListItemsViewController<Void> controller=new GenericListItemsViewController<>(getActivity());
|
||||
Consumer<CheckableListItem<Void>> toggler=item->{
|
||||
item.toggle();
|
||||
controller.rebindItem(item);
|
||||
};
|
||||
CheckableListItem<Void> followingItem, followersItem, newAccountsItem, mentionsItem;
|
||||
List<ListItem<Void>> items=List.of(
|
||||
followingItem=new CheckableListItem<>(R.string.notification_filter_following, R.string.notification_filter_following_explanation, CheckableListItem.Style.CHECKBOX, lastPolicy.filterNotFollowing, toggler, true),
|
||||
followersItem=new CheckableListItem<>(R.string.notification_filter_followers, R.string.notification_filter_followers_explanation, CheckableListItem.Style.CHECKBOX, lastPolicy.filterNotFollowers, toggler, true),
|
||||
newAccountsItem=new CheckableListItem<>(R.string.notification_filter_new_accounts, R.string.notification_filter_new_accounts_explanation, CheckableListItem.Style.CHECKBOX, lastPolicy.filterNewAccounts, toggler, true),
|
||||
mentionsItem=new CheckableListItem<>(R.string.notification_filter_mentions, R.string.notification_filter_mentions_explanation, CheckableListItem.Style.CHECKBOX, lastPolicy.filterPrivateMentions, toggler, true)
|
||||
);
|
||||
controller.setItems(items);
|
||||
AlertDialog dlg=new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.filter_notifications)
|
||||
.setView(controller.getView())
|
||||
.setPositiveButton(R.string.save, null)
|
||||
.show();
|
||||
Button btn=dlg.getButton(Dialog.BUTTON_POSITIVE);
|
||||
btn.setOnClickListener(v->{
|
||||
UiUtils.showProgressForAlertButton(btn, true);
|
||||
NotificationsPolicy newPolicy=new NotificationsPolicy();
|
||||
newPolicy.filterNotFollowing=followingItem.checked;
|
||||
newPolicy.filterNotFollowers=followersItem.checked;
|
||||
newPolicy.filterNewAccounts=newAccountsItem.checked;
|
||||
newPolicy.filterPrivateMentions=mentionsItem.checked;
|
||||
new SetNotificationsPolicy(newPolicy)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(NotificationsPolicy policy){
|
||||
updatePolicy(policy);
|
||||
dlg.dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse errorResponse){
|
||||
Activity activity=getActivity();
|
||||
if(activity==null)
|
||||
return;
|
||||
UiUtils.showProgressForAlertButton(btn, false);
|
||||
errorResponse.showToast(activity);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
});
|
||||
}
|
||||
|
||||
private void openNotificationRequests(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), NotificationRequestsFragment.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public class PinnedPostsListFragment extends StatusListFragment{
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
new GetAccountStatuses(account.id, null, null, 100, GetAccountStatuses.Filter.PINNED)
|
||||
new GetAccountStatuses(account.id, null, null, 100, GetAccountStatuses.Filter.PINNED, null)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
|
||||
@@ -310,7 +310,7 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
|
||||
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState){
|
||||
super.onSelectedChanged(viewHolder, actionState);
|
||||
if(actionState==ItemTouchHelper.ACTION_STATE_DRAG){
|
||||
viewHolder.itemView.setTag(R.id.item_touch_helper_previous_elevation, viewHolder.itemView.getElevation()); // prevents the default behavior of changing elevation in onDraw()
|
||||
viewHolder.itemView.setTag(me.grishka.appkit.R.id.item_touch_helper_previous_elevation, viewHolder.itemView.getElevation()); // prevents the default behavior of changing elevation in onDraw()
|
||||
viewHolder.itemView.animate().translationZ(V.dp(1)).setDuration(200).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.SectionHeaderStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -93,7 +92,13 @@ public class ProfileFeaturedFragment extends BaseStatusListFragment<SearchResult
|
||||
args.putParcelable("profileAccount", Parcels.wrap(res.account));
|
||||
Nav.go(getActivity(), ProfileFragment.class, args);
|
||||
}
|
||||
case HASHTAG -> UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag);
|
||||
case HASHTAG -> {
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("targetAccount", Parcels.wrap(profileAccount));
|
||||
args.putParcelable("hashtag", Parcels.wrap(res.hashtag));
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), HashtagFeaturedTimelineFragment.class, args);
|
||||
}
|
||||
case STATUS -> {
|
||||
Status status=res.status.getContentStatus();
|
||||
Bundle args=new Bundle();
|
||||
@@ -109,7 +114,7 @@ public class ProfileFeaturedFragment extends BaseStatusListFragment<SearchResult
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
if(!statusesLoaded){
|
||||
new GetAccountStatuses(profileAccount.id, null, null, 2, GetAccountStatuses.Filter.PINNED)
|
||||
new GetAccountStatuses(profileAccount.id, null, null, 2, GetAccountStatuses.Filter.PINNED, null)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
@@ -193,6 +198,7 @@ public class ProfileFeaturedFragment extends BaseStatusListFragment<SearchResult
|
||||
private void showAllFeaturedHashtags(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(profileAccount));
|
||||
ArrayList<Parcelable> tags=featuredTags.stream().map(Parcels::wrap).collect(Collectors.toCollection(ArrayList::new));
|
||||
args.putParcelableArrayList("hashtags", tags);
|
||||
Nav.go(getActivity(), FeaturedHashtagsListFragment.class, args);
|
||||
|
||||
@@ -20,7 +20,6 @@ import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableString;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.SpannedString;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.ImageSpan;
|
||||
@@ -29,7 +28,6 @@ import android.transition.Fade;
|
||||
import android.transition.Transition;
|
||||
import android.transition.TransitionManager;
|
||||
import android.transition.TransitionSet;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -67,6 +65,7 @@ import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||
import org.joinmastodon.android.ui.SingleImagePhotoViewerListener;
|
||||
import org.joinmastodon.android.ui.Snackbar;
|
||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
||||
import org.joinmastodon.android.ui.sheets.DecentralizationExplainerSheet;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||
@@ -136,6 +135,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
private View actionButtonWrap;
|
||||
private CustomDrawingOrderLinearLayout scrollableContent;
|
||||
private ImageButton qrCodeButton;
|
||||
private ProgressBar innerProgress;
|
||||
private View actions;
|
||||
|
||||
private Account account;
|
||||
private String accountID;
|
||||
@@ -219,6 +220,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
actionButtonWrap=content.findViewById(R.id.profile_action_btn_wrap);
|
||||
scrollableContent=content.findViewById(R.id.scrollable_content);
|
||||
qrCodeButton=content.findViewById(R.id.qr_code);
|
||||
innerProgress=content.findViewById(R.id.profile_progress);
|
||||
actions=content.findViewById(R.id.profile_actions);
|
||||
|
||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(24));
|
||||
avatar.setClipToOutline(true);
|
||||
@@ -306,6 +309,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
followingBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
||||
|
||||
username.setOnLongClickListener(v->{
|
||||
if(account==null)
|
||||
return true;
|
||||
String username=account.acct;
|
||||
if(!username.contains("@")){
|
||||
username+="@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
|
||||
@@ -331,7 +336,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
nameEdit.addTextChangedListener(new SimpleTextWatcher(e->editDirty=true));
|
||||
bioEdit.addTextChangedListener(new SimpleTextWatcher(e->editDirty=true));
|
||||
|
||||
usernameDomain.setOnClickListener(v->new DecentralizationExplainerSheet(getActivity(), accountID, account).show());
|
||||
usernameDomain.setOnClickListener(v->{
|
||||
if(account==null)
|
||||
return;
|
||||
new DecentralizationExplainerSheet(getActivity(), accountID, account).show();
|
||||
});
|
||||
qrCodeButton.setOnClickListener(v->{
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
@@ -462,6 +471,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if(!loaded)
|
||||
bindHeaderViewForPreviewMaybe();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -506,7 +517,41 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
}
|
||||
}
|
||||
|
||||
private void bindHeaderViewForPreviewMaybe(){
|
||||
if(loaded)
|
||||
return;
|
||||
String username=getArguments().getString("accountUsername");
|
||||
String domain=getArguments().getString("accountDomain");
|
||||
if(TextUtils.isEmpty(username) || TextUtils.isEmpty(domain))
|
||||
return;
|
||||
content.setVisibility(View.VISIBLE);
|
||||
progress.setVisibility(View.GONE);
|
||||
errorView.setVisibility(View.GONE);
|
||||
innerProgress.setVisibility(View.VISIBLE);
|
||||
this.username.setText(username);
|
||||
name.setText(username);
|
||||
usernameDomain.setText(domain);
|
||||
avatar.setImageResource(R.drawable.image_placeholder);
|
||||
cover.setImageResource(R.drawable.image_placeholder);
|
||||
actions.setVisibility(View.GONE);
|
||||
bio.setVisibility(View.GONE);
|
||||
countersLayout.setVisibility(View.GONE);
|
||||
tabsDivider.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void bindHeaderView(){
|
||||
if(innerProgress.getVisibility()==View.VISIBLE){
|
||||
TransitionManager.beginDelayedTransition(contentView, new TransitionSet()
|
||||
.addTransition(new Fade(Fade.IN | Fade.OUT))
|
||||
.excludeChildren(actions, true)
|
||||
.setDuration(250)
|
||||
.setInterpolator(CubicBezierInterpolator.DEFAULT)
|
||||
);
|
||||
innerProgress.setVisibility(View.GONE);
|
||||
countersLayout.setVisibility(View.VISIBLE);
|
||||
actions.setVisibility(View.VISIBLE);
|
||||
tabsDivider.setVisibility(View.VISIBLE);
|
||||
}
|
||||
setTitle(account.displayName);
|
||||
setSubtitle(getResources().getQuantityString(R.plurals.x_posts, (int)(account.statusesCount%1000), account.statusesCount));
|
||||
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(100), V.dp(100)));
|
||||
@@ -635,7 +680,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
menu.findItem(R.id.block_domain).setVisible(false);
|
||||
menu.findItem(R.id.add_to_list).setVisible(relationship.following);
|
||||
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P){
|
||||
if(relationship.following){
|
||||
MenuItem notifications=menu.findItem(R.id.notifications);
|
||||
notifications.setVisible(true);
|
||||
notifications.setIcon(relationship.notifying ? R.drawable.ic_notifications_fill1_24px : R.drawable.ic_notifications_24px);
|
||||
notifications.setTitle(getString(relationship.notifying ? R.string.disable_new_post_notifications : R.string.enable_new_post_notifications, account.getDisplayUsername()));
|
||||
}
|
||||
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P && !UiUtils.isEMUI()){
|
||||
menu.setGroupDividerEnabled(true);
|
||||
}
|
||||
}
|
||||
@@ -663,7 +715,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
updateRelationship();
|
||||
}, this::updateRelationship);
|
||||
}else if(id==R.id.hide_boosts){
|
||||
new SetAccountFollowed(account.id, true, !relationship.showingReblogs)
|
||||
new SetAccountFollowed(account.id, true, !relationship.showingReblogs, relationship.notifying)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
@@ -693,6 +745,24 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("targetAccount", Parcels.wrap(account));
|
||||
Nav.go(getActivity(), AddAccountToListsFragment.class, args);
|
||||
}else if(id==R.id.notifications){
|
||||
new SetAccountFollowed(account.id, true, relationship.showingReblogs, !relationship.notifying)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
updateRelationship(result);
|
||||
new Snackbar.Builder(getActivity())
|
||||
.setText(result.notifying ? R.string.new_post_notifications_enabled : R.string.new_post_notifications_disabled)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(getActivity());
|
||||
}
|
||||
})
|
||||
.wrapProgress(getActivity(), R.string.loading, false)
|
||||
.exec(accountID);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -930,7 +1000,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
|
||||
Toolbar toolbar=getToolbar();
|
||||
if(canGoBack()){
|
||||
Drawable back=getToolbarContext().getDrawable(R.drawable.ic_arrow_back).mutate();
|
||||
Drawable back=getToolbarContext().getDrawable(me.grishka.appkit.R.drawable.ic_arrow_back).mutate();
|
||||
back.setTint(UiUtils.getThemeColor(getToolbarContext(), R.attr.colorM3OnSurfaceVariant));
|
||||
toolbar.setNavigationIcon(back);
|
||||
toolbar.setNavigationContentDescription(0);
|
||||
@@ -1058,6 +1128,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
}
|
||||
|
||||
private void onAvatarClick(View v){
|
||||
if(account==null)
|
||||
return;
|
||||
if(isInEditMode){
|
||||
startImagePicker(AVATAR_RESULT);
|
||||
}else{
|
||||
@@ -1071,6 +1143,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
}
|
||||
|
||||
private void onCoverClick(View v){
|
||||
if(account==null)
|
||||
return;
|
||||
if(isInEditMode){
|
||||
startImagePicker(COVER_RESULT);
|
||||
}else{
|
||||
|
||||
@@ -9,7 +9,6 @@ import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.DownloadManager;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
@@ -33,13 +32,11 @@ import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -57,16 +54,6 @@ import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.gms.common.Feature;
|
||||
import com.google.android.gms.common.api.Status;
|
||||
import com.google.android.gms.common.moduleinstall.ModuleAvailabilityResponse;
|
||||
import com.google.android.gms.common.moduleinstall.ModuleInstallIntentResponse;
|
||||
import com.google.android.gms.common.moduleinstall.ModuleInstallResponse;
|
||||
import com.google.android.gms.common.moduleinstall.ModuleInstallStatusUpdate;
|
||||
import com.google.android.gms.common.moduleinstall.internal.ApiFeatureRequest;
|
||||
import com.google.android.gms.common.moduleinstall.internal.IModuleInstallCallbacks;
|
||||
import com.google.android.gms.common.moduleinstall.internal.IModuleInstallService;
|
||||
import com.google.android.gms.common.moduleinstall.internal.IModuleInstallStatusListener;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.WriterException;
|
||||
@@ -122,11 +109,13 @@ public class ProfileQrCodeFragment extends AppKitFragment{
|
||||
private Animator currentTransition;
|
||||
private View saveBtn;
|
||||
private TextView saveBtnText;
|
||||
private View content;
|
||||
|
||||
private String accountID;
|
||||
private Account account;
|
||||
private String accountDomain;
|
||||
private Intent scannerIntent;
|
||||
private boolean dismissing;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -181,7 +170,7 @@ public class ProfileQrCodeFragment extends AppKitFragment{
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
|
||||
View content=View.inflate(themeWrapper, R.layout.fragment_profile_qr, container);
|
||||
content=View.inflate(themeWrapper, R.layout.fragment_profile_qr, container);
|
||||
View decor=getDialog().getWindow().getDecorView();
|
||||
decor.setOnApplyWindowInsetsListener((v, insets)->{
|
||||
content.setPadding(insets.getStableInsetLeft(), insets.getStableInsetTop(), insets.getStableInsetRight(), insets.getStableInsetBottom());
|
||||
@@ -282,77 +271,7 @@ public class ProfileQrCodeFragment extends AppKitFragment{
|
||||
if(scannerIntent.resolveActivity(getActivity().getPackageManager())!=null){
|
||||
startActivityForResult(scannerIntent, SCAN_RESULT);
|
||||
}else{
|
||||
ProgressDialog progress=new ProgressDialog(getActivity());
|
||||
progress.setMessage(getString(R.string.loading));
|
||||
progress.setCancelable(false);
|
||||
progress.show();
|
||||
GmsClient.getModuleInstallerService(getActivity(), new GmsClient.ServiceConnectionCallback<>(){
|
||||
@Override
|
||||
public void onSuccess(IModuleInstallService service, int connectionID){
|
||||
ApiFeatureRequest req=new ApiFeatureRequest();
|
||||
req.callingPackage=getActivity().getPackageName();
|
||||
Feature feature=new Feature();
|
||||
feature.name="mlkit.barcode.ui";
|
||||
feature.version=1;
|
||||
feature.oldVersion=-1;
|
||||
req.features=List.of(feature);
|
||||
req.urgent=true;
|
||||
try{
|
||||
service.installModules(new IModuleInstallCallbacks.Stub(){
|
||||
@Override
|
||||
public void onModuleAvailabilityResponse(Status status, ModuleAvailabilityResponse response) throws RemoteException{}
|
||||
|
||||
@Override
|
||||
public void onModuleInstallResponse(Status status, ModuleInstallResponse response) throws RemoteException{}
|
||||
|
||||
@Override
|
||||
public void onModuleInstallIntentResponse(Status status, ModuleInstallIntentResponse response) throws RemoteException{}
|
||||
|
||||
@Override
|
||||
public void onStatus(Status status) throws RemoteException{}
|
||||
}, req, new IModuleInstallStatusListener.Stub(){
|
||||
@Override
|
||||
public void onModuleInstallStatusUpdate(ModuleInstallStatusUpdate statusUpdate) throws RemoteException{
|
||||
if(statusUpdate.installState==ModuleInstallStatusUpdate.STATE_COMPLETED){
|
||||
Runnable r=new Runnable(){
|
||||
@Override
|
||||
public void run(){
|
||||
if(scannerIntent.resolveActivity(getActivity().getPackageManager())!=null){
|
||||
progress.dismiss();
|
||||
startActivityForResult(scannerIntent, SCAN_RESULT);
|
||||
}else{
|
||||
codeContainer.postDelayed(this, 100);
|
||||
}
|
||||
}
|
||||
};
|
||||
getActivity().runOnUiThread(r);
|
||||
GmsClient.disconnectFromService(getActivity(), connectionID);
|
||||
}else if(statusUpdate.installState==ModuleInstallStatusUpdate.STATE_FAILED || statusUpdate.installState==ModuleInstallStatusUpdate.STATE_CANCELED){
|
||||
getActivity().runOnUiThread(()->{
|
||||
progress.dismiss();
|
||||
Toast.makeText(themeWrapper, R.string.error, Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
GmsClient.disconnectFromService(getActivity(), connectionID);
|
||||
}
|
||||
}
|
||||
});
|
||||
}catch(RemoteException e){
|
||||
Log.e(TAG, "onSuccess: ", e);
|
||||
getActivity().runOnUiThread(()->{
|
||||
progress.dismiss();
|
||||
Toast.makeText(themeWrapper, R.string.error, Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
GmsClient.disconnectFromService(getActivity(), connectionID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception error){
|
||||
Log.e(TAG, "onError() called with: error = ["+error+"]");
|
||||
Toast.makeText(themeWrapper, R.string.error, Toast.LENGTH_SHORT).show();
|
||||
progress.dismiss();
|
||||
}
|
||||
});
|
||||
BarcodeScanner.installScannerModule(themeWrapper, ()->startActivityForResult(scannerIntent, SCAN_RESULT));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -419,6 +338,10 @@ public class ProfileQrCodeFragment extends AppKitFragment{
|
||||
}
|
||||
|
||||
private void dismissWithAnimation(Runnable onDone, boolean animateTranslationDown){
|
||||
if(dismissing)
|
||||
return;
|
||||
dismissing=true;
|
||||
content.setOnTouchListener(null);
|
||||
if(currentTransition!=null)
|
||||
currentTransition.cancel();
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.joinmastodon.android.ui.views.SizeListenerFrameLayout;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -260,10 +261,10 @@ public class SplashFragment extends AppKitFragment{
|
||||
private void loadAndChooseDefaultServer(){
|
||||
ClipData clipData=getActivity().getSystemService(ClipboardManager.class).getPrimaryClip();
|
||||
if(clipData!=null && clipData.getItemCount()>0){
|
||||
CharSequence clipText=clipData.getItemAt(0).coerceToText(getActivity());
|
||||
if(HtmlParser.INVITE_LINK_PATTERN.matcher(clipText).find()){
|
||||
currentInviteLink=Uri.parse(clipText.toString());
|
||||
defaultServerButton.setText(getString(R.string.join_server_x_with_invite, currentInviteLink.getHost()));
|
||||
String clipText=clipData.getItemAt(0).coerceToText(getActivity()).toString();
|
||||
if(HtmlParser.isValidInviteUrl(clipText)){
|
||||
currentInviteLink=Uri.parse(clipText);
|
||||
defaultServerButton.setText(getString(R.string.join_server_x_with_invite, HtmlParser.normalizeDomain(Objects.requireNonNull(currentInviteLink.getHost()))));
|
||||
}
|
||||
}else{
|
||||
loadingDefaultServer=true;
|
||||
@@ -315,7 +316,7 @@ public class SplashFragment extends AppKitFragment{
|
||||
if(defaultServerButton!=null && getActivity()!=null && currentInviteLink==null){
|
||||
defaultServerButton.setTextVisible(true);
|
||||
defaultServerProgress.setVisibility(View.GONE);
|
||||
defaultServerButton.setText(getString(R.string.join_default_server, chosenDefaultServer));
|
||||
defaultServerButton.setText(getString(R.string.join_default_server, HtmlParser.normalizeDomain(chosenDefaultServer)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
@@ -11,9 +14,14 @@ import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||
import org.joinmastodon.android.googleservices.GmsClient;
|
||||
import org.joinmastodon.android.googleservices.barcodescanner.Barcode;
|
||||
import org.joinmastodon.android.googleservices.barcodescanner.BarcodeScanner;
|
||||
import org.joinmastodon.android.model.SearchResult;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||
@@ -33,6 +41,7 @@ import me.grishka.appkit.utils.V;
|
||||
|
||||
public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, OnBackPressedListener{
|
||||
private static final int QUERY_RESULT=937;
|
||||
private static final int SCAN_RESULT=456;
|
||||
|
||||
private TabLayout tabLayout;
|
||||
private ViewPager2 pager;
|
||||
@@ -40,7 +49,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
private TabLayoutMediator tabLayoutMediator;
|
||||
private boolean searchActive;
|
||||
private FrameLayout searchView;
|
||||
private ImageButton searchBack;
|
||||
private ImageButton searchBack, searchScanQR;
|
||||
private TextView searchText;
|
||||
private View tabsDivider;
|
||||
|
||||
@@ -52,6 +61,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
|
||||
private String accountID;
|
||||
private String currentQuery;
|
||||
private Intent scannerIntent;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -60,6 +70,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
setRetainInstance(true);
|
||||
|
||||
accountID=getArguments().getString("account");
|
||||
scannerIntent=BarcodeScanner.createIntent(Barcode.FORMAT_QR_CODE, false, true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -169,11 +180,17 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
searchBack.setImportantForAccessibility(searchActive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES : View.IMPORTANT_FOR_ACCESSIBILITY_NO);
|
||||
searchBack.setOnClickListener(v->exitSearch());
|
||||
if(searchActive){
|
||||
searchBack.setImageResource(R.drawable.ic_arrow_back);
|
||||
searchBack.setImageResource(me.grishka.appkit.R.drawable.ic_arrow_back);
|
||||
pager.setVisibility(View.GONE);
|
||||
tabLayout.setVisibility(View.GONE);
|
||||
searchView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
searchScanQR=view.findViewById(R.id.search_scan_qr);
|
||||
if(!GmsClient.isGooglePlayServicesAvailable(getActivity())){
|
||||
searchScanQR.setVisibility(View.GONE);
|
||||
}else{
|
||||
searchScanQR.setOnClickListener(v->openQrScanner());
|
||||
}
|
||||
|
||||
View searchWrap=view.findViewById(R.id.search_wrap);
|
||||
searchWrap.setOutlineProvider(OutlineProviders.roundedRect(28));
|
||||
@@ -211,7 +228,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
pager.setVisibility(View.GONE);
|
||||
tabLayout.setVisibility(View.GONE);
|
||||
searchView.setVisibility(View.VISIBLE);
|
||||
searchBack.setImageResource(R.drawable.ic_arrow_back);
|
||||
searchBack.setImageResource(me.grishka.appkit.R.drawable.ic_arrow_back);
|
||||
searchBack.setEnabled(true);
|
||||
searchBack.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
|
||||
tabsDivider.setVisibility(View.GONE);
|
||||
@@ -268,6 +285,28 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data){
|
||||
if(requestCode==SCAN_RESULT && resultCode==Activity.RESULT_OK && BarcodeScanner.isValidResult(data)){
|
||||
Barcode code=BarcodeScanner.getResult(data);
|
||||
if(code!=null){
|
||||
if(code.rawValue.startsWith("https:") || code.rawValue.startsWith("http:")){
|
||||
((MainActivity)getActivity()).handleURL(Uri.parse(code.rawValue), accountID);
|
||||
}else{
|
||||
Toast.makeText(getActivity(), R.string.link_not_supported, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void openQrScanner(){
|
||||
if(scannerIntent.resolveActivity(getActivity().getPackageManager())!=null){
|
||||
startActivityForResult(scannerIntent, SCAN_RESULT);
|
||||
}else{
|
||||
BarcodeScanner.installScannerModule(getActivity(), ()->startActivityForResult(scannerIntent, SCAN_RESULT));
|
||||
}
|
||||
}
|
||||
|
||||
private class DiscoverPagerAdapter extends RecyclerView.Adapter<SimpleViewHolder>{
|
||||
@NonNull
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
@@ -18,6 +19,7 @@ import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
|
||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
import org.joinmastodon.android.ui.utils.HorizontalScrollingTouchListener;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -81,6 +83,7 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> im
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
cardsList=new UsableRecyclerView(getActivity());
|
||||
@@ -98,6 +101,7 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> im
|
||||
});
|
||||
cardsList.setSelector(R.drawable.bg_rect_12dp_ripple);
|
||||
cardsList.setDrawSelectorOnTop(true);
|
||||
cardsList.setOnTouchListener(new HorizontalScrollingTouchListener(getActivity()));
|
||||
|
||||
mergeAdapter=new MergeRecyclerAdapter();
|
||||
bannerHelper.maybeAddBanner(list, mergeAdapter);
|
||||
|
||||
@@ -166,7 +166,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
||||
|
||||
navigationIcon=new LayerDrawable(new Drawable[]{
|
||||
searchIcon=getToolbarContext().getResources().getDrawable(R.drawable.ic_search_24px, getToolbarContext().getTheme()).mutate(),
|
||||
backIcon=getToolbarContext().getResources().getDrawable(R.drawable.ic_arrow_back, getToolbarContext().getTheme()).mutate()
|
||||
backIcon=getToolbarContext().getResources().getDrawable(me.grishka.appkit.R.drawable.ic_arrow_back, getToolbarContext().getTheme()).mutate()
|
||||
}){
|
||||
@Override
|
||||
public Drawable mutate(){
|
||||
|
||||
@@ -370,7 +370,7 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
protected void proceedWithAuthOrSignup(Instance instance){
|
||||
if(currentInviteLinkAlert!=null){
|
||||
currentInviteLinkAlert.dismiss();
|
||||
}else if(!TextUtils.isEmpty(currentSearchQuery) && HtmlParser.INVITE_LINK_PATTERN.matcher(currentSearchQueryButWithCasePreserved).find()){
|
||||
}else if(!TextUtils.isEmpty(currentSearchQuery) && HtmlParser.isValidInviteUrl(currentSearchQueryButWithCasePreserved)){
|
||||
if(TextUtils.isEmpty(inviteCode) || !Objects.equals(instance.uri, inviteCodeHost)){
|
||||
Uri inviteLink=Uri.parse(currentSearchQueryButWithCasePreserved);
|
||||
new CheckInviteLink(inviteLink.getPath())
|
||||
@@ -543,8 +543,8 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
|
||||
ClipData clipData=getActivity().getSystemService(ClipboardManager.class).getPrimaryClip();
|
||||
if(clipData!=null && clipData.getItemCount()>0){
|
||||
CharSequence clipText=clipData.getItemAt(0).coerceToText(getActivity());
|
||||
if(HtmlParser.INVITE_LINK_PATTERN.matcher(clipText).find()){
|
||||
String clipText=clipData.getItemAt(0).coerceToText(getActivity()).toString();
|
||||
if(HtmlParser.isValidInviteUrl(clipText)){
|
||||
edit.setText(clipText);
|
||||
supportingText.setText(R.string.invite_link_pasted);
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment
|
||||
}
|
||||
numRunningFollowRequests++;
|
||||
String id=accountIdsToFollow.remove(0);
|
||||
new SetAccountFollowed(id, true, true)
|
||||
new SetAccountFollowed(id, true, true, false)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
|
||||
@@ -81,7 +81,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetAccountStatuses(reportAccount.id, offset>0 ? getMaxID() : null, null, count, GetAccountStatuses.Filter.OWN_POSTS_AND_REPLIES)
|
||||
currentRequest=new GetAccountStatuses(reportAccount.id, offset>0 ? getMaxID() : null, null, count, GetAccountStatuses.Filter.OWN_POSTS_AND_REPLIES, null)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
@@ -134,7 +134,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMainAdapterOffset(){
|
||||
public int getMainAdapterOffset(){
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class ReportDoneFragment extends MastodonToolbarFragment{
|
||||
@@ -177,7 +176,7 @@ public class ReportDoneFragment extends MastodonToolbarFragment{
|
||||
}
|
||||
|
||||
private void onUnfollowClick(){
|
||||
new SetAccountFollowed(reportAccount.id, false, false)
|
||||
new SetAccountFollowed(reportAccount.id, false, false, false)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
|
||||
@@ -1,12 +1,35 @@
|
||||
package org.joinmastodon.android.googleservices.barcodescanner;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.gms.common.Feature;
|
||||
import com.google.android.gms.common.api.Status;
|
||||
import com.google.android.gms.common.moduleinstall.ModuleAvailabilityResponse;
|
||||
import com.google.android.gms.common.moduleinstall.ModuleInstallIntentResponse;
|
||||
import com.google.android.gms.common.moduleinstall.ModuleInstallResponse;
|
||||
import com.google.android.gms.common.moduleinstall.ModuleInstallStatusUpdate;
|
||||
import com.google.android.gms.common.moduleinstall.internal.ApiFeatureRequest;
|
||||
import com.google.android.gms.common.moduleinstall.internal.IModuleInstallCallbacks;
|
||||
import com.google.android.gms.common.moduleinstall.internal.IModuleInstallService;
|
||||
import com.google.android.gms.common.moduleinstall.internal.IModuleInstallStatusListener;
|
||||
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.googleservices.GmsClient;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BarcodeScanner{
|
||||
private static final String TAG="BarcodeScanner";
|
||||
|
||||
public static Intent createIntent(int formats, boolean allowManualInout, boolean enableAutoZoom){
|
||||
Intent intent=new Intent().setPackage("com.google.android.gms").setAction("com.google.android.gms.mlkit.ACTION_SCAN_BARCODE");
|
||||
String appName;
|
||||
@@ -35,4 +58,79 @@ public class BarcodeScanner{
|
||||
parcel.recycle();
|
||||
return barcode;
|
||||
}
|
||||
|
||||
public static void installScannerModule(Context context, Runnable onSuccess){
|
||||
ProgressDialog progress=new ProgressDialog(context);
|
||||
progress.setMessage(context.getString(R.string.loading));
|
||||
progress.setCancelable(false);
|
||||
progress.show();
|
||||
GmsClient.getModuleInstallerService(context, new GmsClient.ServiceConnectionCallback<>(){
|
||||
@Override
|
||||
public void onSuccess(IModuleInstallService service, int connectionID){
|
||||
ApiFeatureRequest req=new ApiFeatureRequest();
|
||||
req.callingPackage=context.getPackageName();
|
||||
Feature feature=new Feature();
|
||||
feature.name="mlkit.barcode.ui";
|
||||
feature.version=1;
|
||||
feature.oldVersion=-1;
|
||||
req.features=List.of(feature);
|
||||
req.urgent=true;
|
||||
try{
|
||||
service.installModules(new IModuleInstallCallbacks.Stub(){
|
||||
@Override
|
||||
public void onModuleAvailabilityResponse(Status status, ModuleAvailabilityResponse response) throws RemoteException{}
|
||||
|
||||
@Override
|
||||
public void onModuleInstallResponse(Status status, ModuleInstallResponse response) throws RemoteException{}
|
||||
|
||||
@Override
|
||||
public void onModuleInstallIntentResponse(Status status, ModuleInstallIntentResponse response) throws RemoteException{}
|
||||
|
||||
@Override
|
||||
public void onStatus(Status status) throws RemoteException{}
|
||||
}, req, new IModuleInstallStatusListener.Stub(){
|
||||
@Override
|
||||
public void onModuleInstallStatusUpdate(ModuleInstallStatusUpdate statusUpdate) throws RemoteException{
|
||||
if(statusUpdate.installState==ModuleInstallStatusUpdate.STATE_COMPLETED){
|
||||
Intent scannerIntent=createIntent(0, false, false);
|
||||
Runnable r=new Runnable(){
|
||||
@Override
|
||||
public void run(){
|
||||
if(scannerIntent.resolveActivity(context.getPackageManager())!=null){
|
||||
progress.dismiss();
|
||||
onSuccess.run();
|
||||
}else{
|
||||
UiUtils.runOnUiThread(this, 100);
|
||||
}
|
||||
}
|
||||
};
|
||||
UiUtils.runOnUiThread(r);
|
||||
GmsClient.disconnectFromService(context, connectionID);
|
||||
}else if(statusUpdate.installState==ModuleInstallStatusUpdate.STATE_FAILED || statusUpdate.installState==ModuleInstallStatusUpdate.STATE_CANCELED){
|
||||
UiUtils.runOnUiThread(()->{
|
||||
progress.dismiss();
|
||||
Toast.makeText(context, R.string.error, Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
GmsClient.disconnectFromService(context, connectionID);
|
||||
}
|
||||
}
|
||||
});
|
||||
}catch(RemoteException e){
|
||||
Log.e(TAG, "onSuccess: ", e);
|
||||
UiUtils.runOnUiThread(()->{
|
||||
progress.dismiss();
|
||||
Toast.makeText(context, R.string.error, Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
GmsClient.disconnectFromService(context, connectionID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception error){
|
||||
Log.e(TAG, "onError() called with: error = ["+error+"]");
|
||||
Toast.makeText(context, R.string.error, Toast.LENGTH_SHORT).show();
|
||||
progress.dismiss();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import org.joinmastodon.android.api.ObjectValidationException;
|
||||
import org.joinmastodon.android.api.RequiredField;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public class NotificationRequest extends BaseModel{
|
||||
@RequiredField
|
||||
public String id;
|
||||
@RequiredField
|
||||
public Instant createdAt;
|
||||
@RequiredField
|
||||
public Instant updatedAt;
|
||||
public int notificationsCount;
|
||||
@RequiredField
|
||||
public Account account;
|
||||
public Status lastStatus;
|
||||
|
||||
@Override
|
||||
public void postprocess() throws ObjectValidationException{
|
||||
super.postprocess();
|
||||
account.postprocess();
|
||||
if(lastStatus!=null)
|
||||
lastStatus.postprocess();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
public class NotificationsPolicy extends BaseModel{
|
||||
public boolean filterNewAccounts;
|
||||
public boolean filterNotFollowers;
|
||||
public boolean filterNotFollowing;
|
||||
public boolean filterPrivateMentions;
|
||||
public Summary summary;
|
||||
|
||||
public static class Summary{
|
||||
public int pendingNotificationsCount;
|
||||
public int pendingRequestsCount;
|
||||
}
|
||||
}
|
||||
@@ -58,9 +58,9 @@ public class Status extends BaseModel implements DisplayItemsParent{
|
||||
|
||||
public boolean favourited;
|
||||
public boolean reblogged;
|
||||
public boolean muted;
|
||||
public Boolean muted;
|
||||
public boolean bookmarked;
|
||||
public boolean pinned;
|
||||
public Boolean pinned;
|
||||
|
||||
public transient boolean spoilerRevealed;
|
||||
public transient boolean hasGapAfter;
|
||||
|
||||
@@ -25,6 +25,10 @@ public class AccountViewModel{
|
||||
public final String verifiedLink;
|
||||
|
||||
public AccountViewModel(Account account, String accountID){
|
||||
this(account, accountID, true);
|
||||
}
|
||||
|
||||
public AccountViewModel(Account account, String accountID, boolean needBio){
|
||||
this.account=account;
|
||||
avaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(50), V.dp(50));
|
||||
emojiHelper=new CustomEmojiHelper();
|
||||
@@ -32,9 +36,13 @@ public class AccountViewModel{
|
||||
parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
|
||||
else
|
||||
parsedName=account.displayName;
|
||||
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID, account);
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder(parsedName);
|
||||
ssb.append(parsedBio);
|
||||
if(needBio){
|
||||
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID, account);
|
||||
ssb.append(parsedBio);
|
||||
}else{
|
||||
parsedBio=null;
|
||||
}
|
||||
emojiHelper.setText(ssb);
|
||||
String verifiedLink=null;
|
||||
for(AccountField fld:account.fields){
|
||||
|
||||
@@ -35,8 +35,9 @@ public class DividerItemDecoration extends RecyclerView.ItemDecoration{
|
||||
this.drawDividerPredicate=drawDividerPredicate;
|
||||
}
|
||||
|
||||
public void setDrawBelowLastItem(boolean drawBelowLastItem){
|
||||
public DividerItemDecoration setDrawBelowLastItem(boolean drawBelowLastItem){
|
||||
this.drawBelowLastItem=drawBelowLastItem;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -50,6 +50,11 @@ public class M3AlertDialogBuilder extends AlertDialog.Builder{
|
||||
helpButton.setSelected(helpText.getVisibility()==View.VISIBLE);
|
||||
});
|
||||
setCustomTitle(titleLayout);
|
||||
}else if(!TextUtils.isEmpty(title)){
|
||||
View titleLayout=getContext().getSystemService(LayoutInflater.class).inflate(R.layout.alert_title, null);
|
||||
TextView title=titleLayout.findViewById(R.id.title);
|
||||
title.setText(this.title);
|
||||
setCustomTitle(titleLayout);
|
||||
}
|
||||
|
||||
alert=super.create();
|
||||
|
||||
@@ -58,7 +58,7 @@ public class PhotoLayoutHelper{
|
||||
float avgRatio=!ratios.isEmpty() ? sum(ratios)/ratios.size() : 1.0f;
|
||||
|
||||
if(cnt==2){
|
||||
if(allAreWide && avgRatio>1.4*maxRatio && (ratios.get(1)-ratios.get(0))<0.2){ // two wide photos, one above the other
|
||||
if(allAreWide && avgRatio>1.4*maxRatio && Math.abs(ratios.get(1)-ratios.get(0))<0.2){ // two wide photos, one above the other
|
||||
float h=Math.max(Math.min(MAX_WIDTH/ratios.get(0), Math.min(MAX_WIDTH/ratios.get(1), (MAX_HEIGHT-GAP)/2.0f)), MIN_HEIGHT/2f);
|
||||
|
||||
result.width=MAX_WIDTH;
|
||||
@@ -69,7 +69,23 @@ public class PhotoLayoutHelper{
|
||||
new TiledLayoutResult.Tile(1, 1, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, 0, 1)
|
||||
};
|
||||
}else if(allAreWide || allAreSquare){ // next to each other, same ratio
|
||||
}else if(allAreWide){ // two wide photos, one above the other, different ratios
|
||||
result.width=MAX_WIDTH;
|
||||
float h0=MAX_WIDTH/ratios.get(0);
|
||||
float h1=MAX_WIDTH/ratios.get(1);
|
||||
if(h0+h1<MIN_HEIGHT){
|
||||
float prevTotalHeight=h0+h1;
|
||||
h0=MIN_HEIGHT*(h0/prevTotalHeight);
|
||||
h1=MIN_HEIGHT*(h1/prevTotalHeight);
|
||||
}
|
||||
result.height=Math.round(h0+h1+GAP);
|
||||
result.rowSizes=new int[]{Math.round(h0), Math.round(h1)};
|
||||
result.columnSizes=new int[]{MAX_WIDTH};
|
||||
result.tiles=new TiledLayoutResult.Tile[]{
|
||||
new TiledLayoutResult.Tile(1, 1, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, 0, 1)
|
||||
};
|
||||
}else if(allAreSquare){ // next to each other, same ratio
|
||||
float w=((MAX_WIDTH-GAP)/2);
|
||||
float h=Math.max(Math.min(w/ratios.get(0), Math.min(w/ratios.get(1), MAX_HEIGHT)), MIN_HEIGHT);
|
||||
|
||||
|
||||
@@ -25,10 +25,13 @@ import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusSourceText;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusConversationMuted;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusPinned;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.AddAccountToListsFragment;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||
import org.joinmastodon.android.fragments.NotificationsListFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
@@ -36,6 +39,7 @@ import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.Snackbar;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
@@ -208,6 +212,40 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
}else if(id==R.id.copy_link){
|
||||
activity.getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText(null, item.status.url));
|
||||
UiUtils.maybeShowTextCopiedToast(activity);
|
||||
}else if(id==R.id.pin){
|
||||
new SetStatusPinned(item.status.id, !item.status.pinned)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
item.status.pinned=!item.status.pinned;
|
||||
new Snackbar.Builder(activity)
|
||||
.setText(item.status.pinned ? R.string.post_pinned : R.string.post_unpinned)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(activity);
|
||||
}
|
||||
})
|
||||
.wrapProgress(activity, R.string.loading, true)
|
||||
.exec(item.accountID);
|
||||
}else if(id==R.id.mute_conversation){
|
||||
new SetStatusConversationMuted(item.status.id, !item.status.muted)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
// TODO snackbar?
|
||||
item.status.muted=result.muted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(activity);
|
||||
}
|
||||
})
|
||||
.wrapProgress(activity, R.string.loading, true)
|
||||
.exec(item.accountID);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -293,11 +331,18 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
MenuItem report=menu.findItem(R.id.report);
|
||||
MenuItem follow=menu.findItem(R.id.follow);
|
||||
MenuItem bookmark=menu.findItem(R.id.bookmark);
|
||||
MenuItem pin=menu.findItem(R.id.pin);
|
||||
MenuItem muteConversation=menu.findItem(R.id.mute_conversation);
|
||||
if(item.status!=null){
|
||||
bookmark.setVisible(true);
|
||||
bookmark.setTitle(item.status.bookmarked ? R.string.remove_bookmark : R.string.add_bookmark);
|
||||
pin.setVisible(item.status.pinned!=null);
|
||||
if(item.status.pinned!=null){
|
||||
pin.setTitle(item.status.pinned ? R.string.unpin_post : R.string.pin_post);
|
||||
}
|
||||
}else{
|
||||
bookmark.setVisible(false);
|
||||
pin.setVisible(false);
|
||||
}
|
||||
if(isOwnPost){
|
||||
mute.setVisible(false);
|
||||
@@ -314,6 +359,12 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
report.setTitle(item.parentFragment.getString(R.string.report_user, account.displayName));
|
||||
follow.setTitle(item.parentFragment.getString(relationship!=null && relationship.following ? R.string.unfollow_user : R.string.follow_user, account.displayName));
|
||||
}
|
||||
if(item.status.muted!=null){
|
||||
muteConversation.setVisible(isOwnPost || item.parentFragment instanceof NotificationsListFragment);
|
||||
muteConversation.setTitle(item.status.muted ? R.string.unmute_conversation : R.string.mute_conversation);
|
||||
}else{
|
||||
muteConversation.setVisible(false);
|
||||
}
|
||||
menu.findItem(R.id.add_to_list).setVisible(relationship!=null && relationship.following);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,11 @@ import org.joinmastodon.android.model.Card;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
@@ -82,7 +85,7 @@ public class LinkCardStatusDisplayItem extends StatusDisplayItem{
|
||||
description.setText(card.description);
|
||||
description.setVisibility(TextUtils.isEmpty(card.description) ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
String cardDomain=Uri.parse(card.url).getHost();
|
||||
String cardDomain=HtmlParser.normalizeDomain(Objects.requireNonNull(Uri.parse(card.url).getHost()));
|
||||
if(isLarge && !TextUtils.isEmpty(card.authorName)){
|
||||
domain.setText(itemView.getContext().getString(R.string.article_by_author, card.authorName)+" · "+cardDomain);
|
||||
}else{
|
||||
|
||||
@@ -151,10 +151,12 @@ public abstract class StatusDisplayItem{
|
||||
if(!imageAttachments.isEmpty()){
|
||||
PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(imageAttachments);
|
||||
MediaGridStatusDisplayItem mediaGrid=new MediaGridStatusDisplayItem(parentID, fragment, layout, imageAttachments, statusForContent);
|
||||
if((flags & FLAG_MEDIA_FORCE_HIDDEN)!=0)
|
||||
if((flags & FLAG_MEDIA_FORCE_HIDDEN)!=0){
|
||||
mediaGrid.sensitiveTitle=fragment.getString(R.string.media_hidden);
|
||||
else if(statusForContent.sensitive && !AccountSessionManager.get(accountID).getLocalPreferences().hideSensitiveMedia)
|
||||
mediaGrid.sensitiveRevealed=false;
|
||||
}else if(statusForContent.sensitive && !AccountSessionManager.get(accountID).getLocalPreferences().hideSensitiveMedia){
|
||||
mediaGrid.sensitiveRevealed=true;
|
||||
}
|
||||
contentItems.add(mediaGrid);
|
||||
}
|
||||
for(Attachment att:statusForContent.mediaAttachments){
|
||||
|
||||
@@ -63,7 +63,7 @@ public class PhotoViewerInfoSheet extends BottomSheet{
|
||||
}
|
||||
|
||||
backButton=new ImageButton(context);
|
||||
backButton.setImageResource(R.drawable.ic_arrow_back);
|
||||
backButton.setImageResource(me.grishka.appkit.R.drawable.ic_arrow_back);
|
||||
backButton.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(context, R.attr.colorM3OnSurfaceVariant)));
|
||||
backButton.setBackgroundResource(R.drawable.bg_button_m3_tonal_icon);
|
||||
backButton.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
|
||||
|
||||
@@ -52,7 +52,7 @@ public class HtmlParser{
|
||||
")" +
|
||||
")";
|
||||
public static final Pattern URL_PATTERN=Pattern.compile(VALID_URL_PATTERN_STRING, Pattern.CASE_INSENSITIVE);
|
||||
public static final Pattern INVITE_LINK_PATTERN=Pattern.compile("^https://"+Regex.URL_VALID_DOMAIN+"/invite/[a-z\\d]+$", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern INVITE_LINK_PATH=Pattern.compile("/invite/[a-z\\d]+$", Pattern.CASE_INSENSITIVE);
|
||||
private static Pattern EMOJI_CODE_PATTERN=Pattern.compile(":([\\w]+):");
|
||||
|
||||
private HtmlParser(){}
|
||||
@@ -86,6 +86,7 @@ public class HtmlParser{
|
||||
// Hashtags in remote posts have remote URLs, these have local URLs so they don't match.
|
||||
// Map<String, String> tagsByUrl=tags.stream().collect(Collectors.toMap(t->t.url, t->t.name));
|
||||
Map<String, Hashtag> tagsByTag=tags.stream().distinct().collect(Collectors.toMap(t->t.name.toLowerCase(), Function.identity()));
|
||||
Map<String, Mention> mentionsByID=mentions.stream().distinct().collect(Collectors.toMap(m->m.id, Function.identity()));
|
||||
|
||||
final SpannableStringBuilder ssb=new SpannableStringBuilder();
|
||||
Jsoup.parseBodyFragment(source).body().traverse(new NodeVisitor(){
|
||||
@@ -115,6 +116,7 @@ public class HtmlParser{
|
||||
if(id!=null){
|
||||
linkType=LinkSpan.Type.MENTION;
|
||||
href=id;
|
||||
linkObject=mentionsByID.get(id);
|
||||
}else{
|
||||
linkType=LinkSpan.Type.URL;
|
||||
}
|
||||
@@ -259,4 +261,14 @@ public class HtmlParser{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isValidInviteUrl(String url){
|
||||
return url.startsWith("https://") && INVITE_LINK_PATH.matcher(url).find();
|
||||
}
|
||||
|
||||
public static String normalizeDomain(String domain){
|
||||
if(domain.startsWith("www."))
|
||||
domain=domain.substring(4);
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,12 @@ package org.joinmastodon.android.ui.text;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextPaint;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.CharacterStyle;
|
||||
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.model.Mention;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
public class LinkSpan extends CharacterStyle {
|
||||
@@ -39,7 +42,21 @@ public class LinkSpan extends CharacterStyle {
|
||||
public void onClick(Context context){
|
||||
switch(getType()){
|
||||
case URL -> UiUtils.openURL(context, accountID, link, parentObject);
|
||||
case MENTION -> UiUtils.openProfileByID(context, accountID, link);
|
||||
case MENTION -> {
|
||||
String username, domain;
|
||||
if(linkObject instanceof Mention m && !TextUtils.isEmpty(m.acct)){
|
||||
String[] parts=m.acct.split("@", 2);
|
||||
username=parts[0];
|
||||
if(parts.length==2){
|
||||
domain=parts[1];
|
||||
}else{
|
||||
domain=AccountSessionManager.get(accountID).domain;
|
||||
}
|
||||
}else{
|
||||
username=domain=null;
|
||||
}
|
||||
UiUtils.openProfileByID(context, accountID, link, username, domain);
|
||||
}
|
||||
case HASHTAG -> {
|
||||
if(linkObject instanceof Hashtag ht)
|
||||
UiUtils.openHashtagTimeline(context, accountID, ht);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.joinmastodon.android.ui.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
public class HorizontalScrollingTouchListener implements View.OnTouchListener{
|
||||
private float downX, touchslop;
|
||||
private boolean didDisallow;
|
||||
|
||||
public HorizontalScrollingTouchListener(Context context){
|
||||
touchslop=ViewConfiguration.get(context).getScaledTouchSlop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent ev){
|
||||
if(ev.getAction()==MotionEvent.ACTION_DOWN){
|
||||
if(v.canScrollHorizontally(-1) || v.canScrollHorizontally(1)){
|
||||
v.getParent().requestDisallowInterceptTouchEvent(true);
|
||||
didDisallow=true;
|
||||
}else{
|
||||
didDisallow=false;
|
||||
}
|
||||
downX=ev.getX();
|
||||
}else if(didDisallow && ev.getAction()==MotionEvent.ACTION_MOVE){
|
||||
if(Math.abs(downX-ev.getX())>=touchslop){
|
||||
if(!v.canScrollHorizontally((int) (downX-ev.getX()))){
|
||||
didDisallow=false;
|
||||
v.getParent().requestDisallowInterceptTouchEvent(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{
|
||||
for(int i=0; i<parent.getChildCount(); i++){
|
||||
View child=parent.getChildAt(i);
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
||||
pos=holder.getAbsoluteAdapterPosition();
|
||||
pos=holder.getAbsoluteAdapterPosition()-listFragment.getMainAdapterOffset();
|
||||
boolean inset=(holder instanceof StatusDisplayItem.Holder<?> sdi) && sdi.getItem().inset;
|
||||
if(inset){
|
||||
if(rect.isEmpty()){
|
||||
@@ -82,7 +82,7 @@ public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
|
||||
if(holder instanceof StatusDisplayItem.Holder<?> sdi){
|
||||
boolean inset=sdi.getItem().inset;
|
||||
int pos=holder.getAbsoluteAdapterPosition();
|
||||
int pos=holder.getAbsoluteAdapterPosition()-listFragment.getMainAdapterOffset();
|
||||
if(inset){
|
||||
boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
|
||||
boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
|
||||
|
||||
@@ -26,8 +26,6 @@ import android.os.SystemClock;
|
||||
import android.os.ext.SdkExtensions;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
@@ -37,7 +35,6 @@ import android.transition.ChangeScroll;
|
||||
import android.transition.Fade;
|
||||
import android.transition.TransitionManager;
|
||||
import android.transition.TransitionSet;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -47,17 +44,14 @@ import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.FileProvider;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetAccountBlocked;
|
||||
@@ -359,9 +353,17 @@ public class UiUtils{
|
||||
}
|
||||
|
||||
public static void openProfileByID(Context context, String selfID, String id){
|
||||
openProfileByID(context, selfID, id, null, null);
|
||||
}
|
||||
|
||||
public static void openProfileByID(Context context, String selfID, String id, String username, String domain){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", selfID);
|
||||
args.putString("profileAccountID", id);
|
||||
if(username!=null && domain!=null){
|
||||
args.putString("accountUsername", username);
|
||||
args.putString("accountDomain", domain);
|
||||
}
|
||||
Nav.go((Activity)context, ProfileFragment.class, args);
|
||||
}
|
||||
|
||||
@@ -590,7 +592,7 @@ public class UiUtils{
|
||||
}else{
|
||||
Runnable action=()->{
|
||||
progressCallback.accept(true);
|
||||
new SetAccountFollowed(account.id, !relationship.following && !relationship.requested, true)
|
||||
new SetAccountFollowed(account.id, !relationship.following && !relationship.requested, true, false)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
@@ -961,9 +963,9 @@ public class UiUtils{
|
||||
ImageCache cache=ImageCache.getInstance(context);
|
||||
try{
|
||||
File ava=cache.getFile(new UrlImageLoaderRequest(account.avatarStatic));
|
||||
if(!ava.exists())
|
||||
if(ava==null || !ava.exists())
|
||||
ava=cache.getFile(new UrlImageLoaderRequest(account.avatar));
|
||||
if(ava.exists()){
|
||||
if(ava!=null && ava.exists()){
|
||||
intent.setClipData(ClipData.newRawUri(null, getFileProviderUri(context, ava)));
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.AlertDialog;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -346,6 +349,13 @@ public class ComposePollViewController{
|
||||
pollOptions.remove(dpo);
|
||||
pollOptionsView.removeView(view);
|
||||
addPollOptionBtn.setEnabled(pollOptions.size()<maxPollOptions);
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.R){
|
||||
Vibrator vibrator=fragment.getActivity().getSystemService(Vibrator.class);
|
||||
if(vibrator.areAllPrimitivesSupported(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)){
|
||||
VibrationEffect effect=VibrationEffect.startComposition().addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE).compose();
|
||||
vibrator.vibrate(effect);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
ReorderableLinearLayout.OnDragListener.super.onDragEnd(view);
|
||||
|
||||
@@ -53,7 +53,7 @@ public abstract class DropdownSubmenuController{
|
||||
backItem=(TextView) dropdownController.getActivity().getLayoutInflater().inflate(R.layout.item_dropdown_menu, contentView, false);
|
||||
((LinearLayout.LayoutParams) backItem.getLayoutParams()).topMargin=V.dp(8);
|
||||
backItem.setText(backTitle);
|
||||
backItem.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_arrow_back, 0, 0, 0);
|
||||
backItem.setCompoundDrawablesRelativeWithIntrinsicBounds(me.grishka.appkit.R.drawable.ic_arrow_back, 0, 0, 0);
|
||||
backItem.setBackground(UiUtils.getThemeDrawable(dropdownController.getActivity(), android.R.attr.selectableItemBackground));
|
||||
backItem.setOnClickListener(v->dropdownController.popSubmenuController());
|
||||
backItem.setAccessibilityDelegate(new View.AccessibilityDelegate(){
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.joinmastodon.android.ui.viewcontrollers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter;
|
||||
import org.joinmastodon.android.ui.viewholders.CheckableListItemViewHolder;
|
||||
import org.joinmastodon.android.ui.viewholders.ListItemViewHolder;
|
||||
import org.joinmastodon.android.ui.viewholders.SimpleListItemViewHolder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class GenericListItemsViewController<T>{
|
||||
private UsableRecyclerView list;
|
||||
private List<ListItem<T>> items;
|
||||
private GenericListItemsAdapter<T> adapter;
|
||||
private Context context;
|
||||
|
||||
public GenericListItemsViewController(Context context, List<ListItem<T>> items){
|
||||
this.context=context;
|
||||
setItems(items);
|
||||
}
|
||||
|
||||
public GenericListItemsViewController(Context context){
|
||||
this.context=context;
|
||||
}
|
||||
|
||||
public void setItems(List<ListItem<T>> items){
|
||||
if(this.items!=null)
|
||||
throw new IllegalStateException("items already set");
|
||||
this.items=items;
|
||||
adapter=new GenericListItemsAdapter<>(items);
|
||||
list=new UsableRecyclerView(context);
|
||||
list.setLayoutManager(new LinearLayoutManager(context));
|
||||
list.setAdapter(adapter);
|
||||
list.addItemDecoration(new DividerItemDecoration(context, R.attr.colorM3OutlineVariant, 1, 16, 16, vh->(vh instanceof SimpleListItemViewHolder ivh && ivh.getItem().dividerAfter) || (vh instanceof CheckableListItemViewHolder cvh && cvh.getItem().dividerAfter)));
|
||||
list.setItemAnimator(new BetterItemAnimator());
|
||||
}
|
||||
|
||||
public GenericListItemsAdapter<T> getAdapter(){
|
||||
return adapter;
|
||||
}
|
||||
|
||||
public View getView(){
|
||||
return list;
|
||||
}
|
||||
|
||||
public void rebindItem(ListItem<?> item){
|
||||
if(list.findViewHolderForAdapterPosition(items.indexOf(item)) instanceof ListItemViewHolder<?> holder){
|
||||
holder.rebind();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,6 @@ package org.joinmastodon.android.ui.viewholders;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
@@ -15,7 +13,6 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
@@ -254,7 +251,7 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
|
||||
bindRelationship();
|
||||
}, this::updateRelationship);
|
||||
}else if(id==R.id.hide_boosts){
|
||||
new SetAccountFollowed(account.id, true, !relationship.showingReblogs)
|
||||
new SetAccountFollowed(account.id, true, !relationship.showingReblogs, relationship.notifying)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
|
||||
@@ -284,7 +284,7 @@ public class ReorderableLinearLayout extends LinearLayout implements CustomViewH
|
||||
|
||||
private int getMaxDragScroll(){
|
||||
if(cachedMaxScrollSpeed==-1){
|
||||
cachedMaxScrollSpeed=getResources().getDimensionPixelSize(R.dimen.item_touch_helper_max_drag_scroll_per_frame);
|
||||
cachedMaxScrollSpeed=getResources().getDimensionPixelSize(me.grishka.appkit.R.dimen.item_touch_helper_max_drag_scroll_per_frame);
|
||||
}
|
||||
return cachedMaxScrollSpeed;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?colorM3OnSurfaceVariant" android:state_enabled="true"/>
|
||||
<item android:color="?colorM3OnSurfaceVariant" android:alpha="0.38"/>
|
||||
<item android:color="?colorM3OnSurface" android:alpha="0.38"/>
|
||||
</selector>
|
||||
6
mastodon/src/main/res/drawable/bg_ava_badge.xml
Normal file
6
mastodon/src/main/res/drawable/bg_ava_badge.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?colorM3Primary"/>
|
||||
<corners android:radius="100dp"/>
|
||||
<stroke android:color="?colorM3Background" android:width="1dp"/>
|
||||
</shape>
|
||||
9
mastodon/src/main/res/drawable/ic_arrow_upward_20px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_arrow_upward_20px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M9.25,16V6.875L5.062,11.062L4,10L10,4L16,10L14.938,11.062L10.75,6.875V16Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_check_wght700_24px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_check_wght700_24px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M9.55,18.8 L3.05,12.3 5.3,10.05 9.55,14.3 18.7,5.15 20.95,7.4Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_clear_night_20px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_clear_night_20px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M10,16.583Q11.125,16.583 12.135,16.26Q13.146,15.938 14.021,15.271Q12.229,14.604 10.865,13.427Q9.5,12.25 8.656,10.74Q7.812,9.229 7.542,7.448Q7.271,5.667 7.667,3.812Q5.771,4.5 4.594,6.177Q3.417,7.854 3.417,10Q3.417,12.75 5.333,14.667Q7.25,16.583 10,16.583ZM10,18.333Q8.271,18.333 6.75,17.677Q5.229,17.021 4.104,15.896Q2.979,14.771 2.323,13.25Q1.667,11.729 1.667,10Q1.667,7 3.635,4.729Q5.604,2.458 8.521,1.854Q9.208,1.708 9.51,2.135Q9.812,2.562 9.562,3.312Q9.021,5 9.198,6.708Q9.375,8.417 10.177,9.875Q10.979,11.333 12.323,12.406Q13.667,13.479 15.438,13.917Q16.208,14.104 16.427,14.594Q16.646,15.083 16.167,15.604Q15.021,16.896 13.438,17.615Q11.854,18.333 10,18.333Z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M6.725,21.85Q5.4,21.85 4.488,20.938Q3.575,20.025 3.575,18.7V6.275H2V3.125H8.425V1.55H15.525V3.125H22V6.275H20.425V18.7Q20.425,20.025 19.513,20.938Q18.6,21.85 17.275,21.85ZM17.275,6.275H6.725V18.7Q6.725,18.7 6.725,18.7Q6.725,18.7 6.725,18.7H17.275Q17.275,18.7 17.275,18.7Q17.275,18.7 17.275,18.7ZM8.55,16.975H11.125V7.975H8.55ZM12.875,16.975H15.45V7.975H12.875ZM6.725,6.275V18.7Q6.725,18.7 6.725,18.7Q6.725,18.7 6.725,18.7Q6.725,18.7 6.725,18.7Q6.725,18.7 6.725,18.7Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_inventory_2_24px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_inventory_2_24px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M3,20V8.7Q2.575,8.425 2.288,8Q2,7.575 2,7V4Q2,3.175 2.588,2.587Q3.175,2 4,2H20Q20.825,2 21.413,2.587Q22,3.175 22,4V7Q22,7.575 21.712,8Q21.425,8.425 21,8.7V20Q21,20.825 20.413,21.413Q19.825,22 19,22H5Q4.175,22 3.587,21.413Q3,20.825 3,20ZM5,9V20Q5,20 5,20Q5,20 5,20H19Q19,20 19,20Q19,20 19,20V9ZM20,7Q20,7 20,7Q20,7 20,7V4Q20,4 20,4Q20,4 20,4H4Q4,4 4,4Q4,4 4,4V7Q4,7 4,7Q4,7 4,7ZM9,14H15V12H9ZM5,20Q5,20 5,20Q5,20 5,20V9V20Q5,20 5,20Q5,20 5,20Z"/>
|
||||
</vector>
|
||||
9
mastodon/src/main/res/drawable/ic_tune_24px.xml
Normal file
9
mastodon/src/main/res/drawable/ic_tune_24px.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M3,19V17H9V19ZM3,7V5H13V7ZM11,21V15H13V17H21V19H13V21ZM7,15V13H3V11H7V9H9V15ZM11,13V11H21V13ZM15,9V3H17V5H21V7H17V9Z"/>
|
||||
</vector>
|
||||
16
mastodon/src/main/res/layout/alert_title.xml
Normal file
16
mastodon/src/main/res/layout/alert_title.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="24dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="?android:windowTitleStyle"
|
||||
tools:text="Title"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -15,7 +15,7 @@
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:textAppearance="@style/m3_body_large"/>
|
||||
android:lineSpacingExtra="5.25dp"/>
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/translation_info"
|
||||
|
||||
13
mastodon/src/main/res/layout/expanded_title_medium.xml
Normal file
13
mastodon/src/main/res/layout/expanded_title_medium.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_headline_small"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingBottom="24dp"
|
||||
android:paddingTop="4dp"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
tools:text="Very long title that does not fit on one line"/>
|
||||
@@ -30,8 +30,9 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/search_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical|start"
|
||||
android:textAlignment="viewStart"
|
||||
android:singleLine="true"
|
||||
@@ -39,6 +40,16 @@
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:text="@string/search_mastodon"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/search_scan_qr"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_margin="8dp"
|
||||
android:contentDescription="@string/scan_qr_code"
|
||||
android:background="@drawable/bg_round_ripple"
|
||||
android:tint="?colorM3OnSurfaceVariant"
|
||||
android:src="@drawable/ic_qr_code_scanner_24px"/>
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
android:id="@+id/cover"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="144dp"
|
||||
android:background="#808080"
|
||||
android:background="@drawable/image_placeholder"
|
||||
android:contentDescription="@string/profile_header"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
@@ -134,6 +134,14 @@
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/profile_progress"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="16dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<org.joinmastodon.android.ui.views.LinkedTextView
|
||||
android:id="@+id/bio"
|
||||
android:layout_width="match_parent"
|
||||
@@ -284,6 +292,7 @@
|
||||
</org.joinmastodon.android.ui.views.FloatingHintEditTextLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/profile_actions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:orientation="horizontal"
|
||||
|
||||
@@ -42,17 +42,17 @@
|
||||
<Button
|
||||
android:id="@+id/new_posts_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:layout_margin="16dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_margin="8dp"
|
||||
android:background="@drawable/round_rect"
|
||||
android:backgroundTint="?colorM3Primary"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:paddingVertical="0dp"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
android:textColor="?colorM3OnPrimary"
|
||||
android:drawableStart="@drawable/ic_arrow_upward_24px"
|
||||
android:drawableStart="@drawable/ic_arrow_upward_20px"
|
||||
android:drawableTint="?colorM3OnPrimary"
|
||||
android:drawablePadding="8dp"
|
||||
android:drawablePadding="4dp"
|
||||
android:elevation="@dimen/m3_sys_elevation_level4"
|
||||
android:stateListAnimator="@animator/squish"
|
||||
android:text="@string/see_new_posts"/>
|
||||
|
||||
83
mastodon/src/main/res/layout/item_notification_request.xml
Normal file
83
mastodon/src/main/res/layout/item_notification_request.xml
Normal file
@@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ava"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginVertical="20dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:importantForAccessibility="no"
|
||||
tools:src="#0f0"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/badge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="16dp"
|
||||
android:layout_alignEnd="@id/ava"
|
||||
android:layout_alignBottom="@id/ava"
|
||||
android:layout_marginEnd="-4dp"
|
||||
android:layout_marginBottom="-4dp"
|
||||
android:minWidth="16dp"
|
||||
android:gravity="center"
|
||||
android:background="@drawable/bg_ava_badge"
|
||||
android:paddingHorizontal="4dp"
|
||||
android:textAppearance="@style/m3_label_small"
|
||||
android:textColor="?colorM3OnPrimary"
|
||||
tools:text="99+"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_allow"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
style="@style/Widget.Mastodon.M3.Button.Outlined"
|
||||
android:contentDescription="@string/allow_notifications"
|
||||
android:src="@drawable/ic_check_24px"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_mute"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_toStartOf="@id/btn_allow"
|
||||
android:layout_centerVertical="true"
|
||||
style="@style/Widget.Mastodon.M3.Button.Outlined"
|
||||
android:contentDescription="@string/mute_notifications"
|
||||
android:src="@drawable/ic_delete_24px"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="24dp"
|
||||
android:layout_toEndOf="@id/ava"
|
||||
android:layout_toStartOf="@id/btn_mute"
|
||||
android:layout_marginTop="14dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:gravity="center_vertical"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
tools:text="User Name"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_below="@id/name"
|
||||
android:layout_toEndOf="@id/ava"
|
||||
android:layout_toStartOf="@id/btn_mute"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:gravity="center_vertical"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
tools:text="\@username@domain"/>
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -7,43 +7,6 @@
|
||||
android:paddingTop="8dp"
|
||||
android:paddingHorizontal="8dp">
|
||||
|
||||
<org.joinmastodon.android.ui.views.CheckableLinearLayout
|
||||
android:id="@+id/multiple_choice"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:background="@drawable/bg_rect_4dp_ripple"
|
||||
android:gravity="center_horizontal">
|
||||
<ImageView
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:src="@drawable/poll_multiple"
|
||||
android:duplicateParentState="true"
|
||||
android:importantForAccessibility="no"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:text="@string/compose_poll_multiple_choice"/>
|
||||
<FrameLayout
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:duplicateParentState="true">
|
||||
<RadioButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:duplicateParentState="true"/>
|
||||
</FrameLayout>
|
||||
</org.joinmastodon.android.ui.views.CheckableLinearLayout>
|
||||
|
||||
<org.joinmastodon.android.ui.views.CheckableLinearLayout
|
||||
android:id="@+id/single_choice"
|
||||
android:layout_width="0dp"
|
||||
@@ -81,4 +44,41 @@
|
||||
</FrameLayout>
|
||||
</org.joinmastodon.android.ui.views.CheckableLinearLayout>
|
||||
|
||||
<org.joinmastodon.android.ui.views.CheckableLinearLayout
|
||||
android:id="@+id/multiple_choice"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:background="@drawable/bg_rect_4dp_ripple"
|
||||
android:gravity="center_horizontal">
|
||||
<ImageView
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:src="@drawable/poll_multiple"
|
||||
android:duplicateParentState="true"
|
||||
android:importantForAccessibility="no"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:text="@string/compose_poll_multiple_choice"/>
|
||||
<FrameLayout
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:duplicateParentState="true">
|
||||
<RadioButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:duplicateParentState="true"/>
|
||||
</FrameLayout>
|
||||
</org.joinmastodon.android.ui.views.CheckableLinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -56,6 +56,7 @@
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:textIsSelectable="true"
|
||||
tools:text="A cute black cat"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/vis_public"
|
||||
android:title="@string/visibility_public"/>
|
||||
<item android:id="@+id/vis_unlisted"
|
||||
android:title="@string/visibility_unlisted"/>
|
||||
<item android:id="@+id/vis_followers"
|
||||
android:title="@string/visibility_followers_only"/>
|
||||
<item android:id="@+id/vis_private"
|
||||
|
||||
15
mastodon/src/main/res/menu/notification_request.xml
Normal file
15
mastodon/src/main/res/menu/notification_request.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/mute"
|
||||
android:title="@string/mute_notifications"
|
||||
android:icon="@drawable/ic_delete_24px"
|
||||
android:checkable="true"
|
||||
android:showAsAction="always"/>
|
||||
<item
|
||||
android:id="@+id/allow"
|
||||
android:title="@string/allow_notifications"
|
||||
android:icon="@drawable/ic_check_24px"
|
||||
android:checkable="true"
|
||||
android:showAsAction="always"/>
|
||||
</menu>
|
||||
@@ -1,4 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/mark_all_read" android:icon="@drawable/ic_done_all_24px" android:title="@string/mark_all_notifications_read" android:showAsAction="always"/>
|
||||
<item
|
||||
android:id="@+id/mark_all_read"
|
||||
android:icon="@drawable/ic_done_all_24px"
|
||||
android:showAsAction="always"
|
||||
android:title="@string/mark_all_notifications_read" />
|
||||
<item
|
||||
android:id="@+id/filters"
|
||||
android:icon="@drawable/ic_tune_24px"
|
||||
android:showAsAction="always"
|
||||
android:title="@string/filter_notifications"/>
|
||||
</menu>
|
||||
@@ -2,12 +2,14 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<group android:id="@+id/menu_group1">
|
||||
<item android:id="@+id/translate" android:title="@string/translate_post"/>
|
||||
<item android:id="@+id/pin" android:title="@string/pin_post"/>
|
||||
<item android:id="@+id/bookmark" android:title="@string/add_bookmark"/>
|
||||
<item android:id="@+id/share" android:title="@string/button_share"/>
|
||||
<item android:id="@+id/open_in_browser" android:title="@string/open_in_browser"/>
|
||||
<item android:id="@+id/copy_link" android:title="@string/fallback_menu_item_copy_link"/>
|
||||
<item android:id="@+id/edit" android:title="@string/edit"/>
|
||||
<item android:id="@+id/delete" android:title="@string/delete"/>
|
||||
<item android:id="@+id/mute_conversation" android:title="@string/mute_conversation"/>
|
||||
</group>
|
||||
<group android:id="@+id/menu_group2">
|
||||
<item android:id="@+id/add_to_list" android:title="@string/add_user_to_list"/>
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<menu xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/notifications"
|
||||
android:icon="@drawable/ic_tab_notifications"
|
||||
android:showAsAction="always"
|
||||
android:visible="false"
|
||||
tools:ignore="MenuTitle" />
|
||||
|
||||
<group android:orderInCategory="1" android:id="@+id/menu_group1">
|
||||
<item android:id="@+id/share" android:title="@string/share_user"/>
|
||||
<item android:id="@+id/copy_link" android:title="@string/copy_profile_link"/>
|
||||
|
||||
1
mastodon/src/main/res/resources.properties
Normal file
1
mastodon/src/main/res/resources.properties
Normal file
@@ -0,0 +1 @@
|
||||
unqualifiedResLocale=en-US
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="log_in">تسجيلُ الدخول</string>
|
||||
<string name="next">التالي</string>
|
||||
<string name="loading_instance">جارٍ جلب معلومات الخادم…</string>
|
||||
@@ -707,4 +707,5 @@
|
||||
<string name="list_find_users">البحث عن مستخدمين للإضافة</string>
|
||||
<!-- %s is a time interval ("5 months") -->
|
||||
<!-- Shown on a button that saves a file, after it was successfully saved -->
|
||||
<!-- %s is the username -->
|
||||
</resources>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user