Compare commits
338 Commits
v1.2.0+for
...
v1.2.3+for
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99f0817bdb | ||
|
|
220cd35d82 | ||
|
|
07f4ef1697 | ||
|
|
f20732ddc2 | ||
|
|
b1e0dc5843 | ||
|
|
285eb25706 | ||
|
|
ec556511e6 | ||
|
|
85c3d9f65f | ||
|
|
a7ebadf269 | ||
|
|
94c09d46c2 | ||
|
|
f6f08d176c | ||
|
|
66cdd63496 | ||
|
|
8b502b605c | ||
|
|
2c0ec28803 | ||
|
|
a9ab9cb249 | ||
|
|
961c69b525 | ||
|
|
c70f393559 | ||
|
|
9abdc174f4 | ||
|
|
2e5bfa1d9c | ||
|
|
33cbd85e19 | ||
|
|
8cb1f3f387 | ||
|
|
3f0c6fcec5 | ||
|
|
797cf893da | ||
|
|
a3564b70e1 | ||
|
|
43004307b8 | ||
|
|
acd1e4ced3 | ||
|
|
6717070f93 | ||
|
|
387499ae49 | ||
|
|
8ab140c55d | ||
|
|
914abb95dd | ||
|
|
5360c0f0f7 | ||
|
|
243d803b51 | ||
|
|
b343fe3835 | ||
|
|
3c42c1120f | ||
|
|
ad840dcef6 | ||
|
|
f73072d95e | ||
|
|
95cb9b5079 | ||
|
|
c6684d3c9b | ||
|
|
5c5989d8c0 | ||
|
|
60e92d30b0 | ||
|
|
8bf8e3f86b | ||
|
|
891ee2d06b | ||
|
|
b450bc7ae8 | ||
|
|
4ca1e0d5db | ||
|
|
859213dd9e | ||
|
|
ad2857791d | ||
|
|
497827f2e2 | ||
|
|
967e333022 | ||
|
|
8df1406006 | ||
|
|
4af42fafdc | ||
|
|
a9e6a452c1 | ||
|
|
a4a4632397 | ||
|
|
421f39e414 | ||
|
|
f8121e2dc4 | ||
|
|
b1784fc51c | ||
|
|
96db0d7de7 | ||
|
|
3837ed9cb1 | ||
|
|
2be789a43c | ||
|
|
fd8d96169a | ||
|
|
1562dc32c1 | ||
|
|
38f377ca09 | ||
|
|
cc28bba884 | ||
|
|
beb3081918 | ||
|
|
1b3c9106b5 | ||
|
|
385b91761b | ||
|
|
d7b76ed70a | ||
|
|
43600756c0 | ||
|
|
3c3e0633ad | ||
|
|
f819ad6917 | ||
|
|
2e84faa505 | ||
|
|
e7e8d13d9e | ||
|
|
a683c2cb11 | ||
|
|
addf7de316 | ||
|
|
44d4eada51 | ||
|
|
40bfdea5b1 | ||
|
|
55138c1e86 | ||
|
|
e7ad396fc6 | ||
|
|
0aef680572 | ||
|
|
6dc37d6bde | ||
|
|
60ea7cedf6 | ||
|
|
c986b10e14 | ||
|
|
d52174bd9e | ||
|
|
c65d138911 | ||
|
|
ad9bb8ad58 | ||
|
|
63e536c66c | ||
|
|
b5a08b1b98 | ||
|
|
226e2a7cdc | ||
|
|
4d7c4aed4c | ||
|
|
c9bcd000c3 | ||
|
|
b1cb4d4257 | ||
|
|
de42145f30 | ||
|
|
7bcdd6070a | ||
|
|
8a215e90d0 | ||
|
|
b736fa18bb | ||
|
|
43c19e4942 | ||
|
|
ffc18029bb | ||
|
|
b88b3d15f8 | ||
|
|
c817886a2d | ||
|
|
aae239494e | ||
|
|
b0b2daa5d5 | ||
|
|
eea2e38f1b | ||
|
|
f894ecd25b | ||
|
|
e0b6ed7103 | ||
|
|
a78e75747a | ||
|
|
3b25e367bb | ||
|
|
08b29dff3d | ||
|
|
2f2e053d26 | ||
|
|
191d582c30 | ||
|
|
8d3380ff6e | ||
|
|
ba85d18574 | ||
|
|
0f53b17515 | ||
|
|
cb9c869712 | ||
|
|
aa3d9e7b8f | ||
|
|
b3a9b5824d | ||
|
|
b6186a349f | ||
|
|
100bd4b062 | ||
|
|
7da09d9b37 | ||
|
|
f46eb07228 | ||
|
|
7627b5eb25 | ||
|
|
c710448c6b | ||
|
|
1ad270b1d6 | ||
|
|
099e253b2b | ||
|
|
66de4a5b91 | ||
|
|
41437d91d5 | ||
|
|
d33d5a6efa | ||
|
|
4f9248d040 | ||
|
|
f40c0e41f3 | ||
|
|
15fcb0e25d | ||
|
|
2dae662333 | ||
|
|
3ad46926f1 | ||
|
|
2385d102ae | ||
|
|
deeb03ff2b | ||
|
|
5c2a09e243 | ||
|
|
2473c999db | ||
|
|
ea2cc265e3 | ||
|
|
a0cd2d42cf | ||
|
|
0a17ceb984 | ||
|
|
4ef18f1f4a | ||
|
|
0de227ab9c | ||
|
|
19cb8703a6 | ||
|
|
e18567dd82 | ||
|
|
bfb3bcdbfb | ||
|
|
565cd14d88 | ||
|
|
ebc37eac75 | ||
|
|
c3702db577 | ||
|
|
e15c4fa342 | ||
|
|
8330b9f1c5 | ||
|
|
f759150982 | ||
|
|
6af177b596 | ||
|
|
657bb94975 | ||
|
|
3c946212b1 | ||
|
|
b4e80f7fca | ||
|
|
c9efc2cb2b | ||
|
|
0d62e33dc7 | ||
|
|
ac88b9e19c | ||
|
|
871dfda79e | ||
|
|
e0c2c208ae | ||
|
|
22ac112bdb | ||
|
|
afd0cca176 | ||
|
|
c083c8bce5 | ||
|
|
63bde032b3 | ||
|
|
49492c0788 | ||
|
|
b439c64add | ||
|
|
1868bfe8e3 | ||
|
|
f240a3d996 | ||
|
|
788e5bd12e | ||
|
|
a55fed4502 | ||
|
|
a8fdaf1a47 | ||
|
|
4a758bd488 | ||
|
|
2c9731ec2a | ||
|
|
eef33266fc | ||
|
|
58d2c3e5a6 | ||
|
|
9e6a355db0 | ||
|
|
0d10e09fd6 | ||
|
|
f85bb995ba | ||
|
|
268e5639f6 | ||
|
|
51809df8ca | ||
|
|
94fb676b0c | ||
|
|
a47106594b | ||
|
|
d93d66f702 | ||
|
|
b2f9f7ae54 | ||
|
|
de7b908c78 | ||
|
|
75d3c2fdce | ||
|
|
ea1b6c5835 | ||
|
|
cb7887da41 | ||
|
|
a80313ee6b | ||
|
|
e1a821bc43 | ||
|
|
924ea2d03a | ||
|
|
55270fe654 | ||
|
|
a125fab57b | ||
|
|
395ee0aa99 | ||
|
|
0f50fa6ba1 | ||
|
|
adb7df3c71 | ||
|
|
5d7bcb629b | ||
|
|
a00f1417d2 | ||
|
|
8efd7e8ebf | ||
|
|
b016d277e0 | ||
|
|
fdb39617d1 | ||
|
|
89f83fbf62 | ||
|
|
ecee9e01a6 | ||
|
|
20dc9bb8b9 | ||
|
|
2c47d0e9ed | ||
|
|
8e13d52e51 | ||
|
|
cc40198c9e | ||
|
|
290897ea41 | ||
|
|
b9e1c84304 | ||
|
|
3c44c80e2e | ||
|
|
dffa4e4594 | ||
|
|
fa2d9fec58 | ||
|
|
09c1a2cfa0 | ||
|
|
d1f90eb231 | ||
|
|
1f7d97134b | ||
|
|
79be91784d | ||
|
|
de2654def3 | ||
|
|
56343dacff | ||
|
|
0e677f8ce7 | ||
|
|
aa911896d6 | ||
|
|
c62a8635b9 | ||
|
|
4e900247c5 | ||
|
|
b3bd62bc6c | ||
|
|
8e5fd48ecd | ||
|
|
b2bca9dd2c | ||
|
|
b8c0dc3181 | ||
|
|
cccdc5292e | ||
|
|
76d77a0e7a | ||
|
|
e737f4bf9a | ||
|
|
391db2f1c9 | ||
|
|
359d61183c | ||
|
|
46fd05d88e | ||
|
|
cde22a0945 | ||
|
|
111b7e25c5 | ||
|
|
4f8d8f0c8d | ||
|
|
915b0603d0 | ||
|
|
6ec43a6f86 | ||
|
|
df93a1a845 | ||
|
|
41a70a353c | ||
|
|
8d69bcfd4b | ||
|
|
0ef30f82a7 | ||
|
|
be60e78ea6 | ||
|
|
5434325fa8 | ||
|
|
0a04c9357c | ||
|
|
075aab8074 | ||
|
|
21c4cef397 | ||
|
|
4b2fcd760a | ||
|
|
6ebe4c86af | ||
|
|
0925c8c582 | ||
|
|
9824b5fb56 | ||
|
|
78fcf31e34 | ||
|
|
eadb62d3a8 | ||
|
|
f6279fcc0c | ||
|
|
a683fdce62 | ||
|
|
b958299446 | ||
|
|
3f80be8377 | ||
|
|
ced0accde5 | ||
|
|
b454ff5ec7 | ||
|
|
45af198f32 | ||
|
|
ff374f8899 | ||
|
|
faecb3bc4b | ||
|
|
6b893fadef | ||
|
|
c328467a41 | ||
|
|
182325470b | ||
|
|
f330ad71ac | ||
|
|
ba0c064f36 | ||
|
|
8d7aaee5b9 | ||
|
|
68cba2de63 | ||
|
|
5a914f9c0e | ||
|
|
b0e6805a20 | ||
|
|
21e7e44c01 | ||
|
|
f7df4abdae | ||
|
|
7674ceefe9 | ||
|
|
4be575c534 | ||
|
|
dd0f0a7d5a | ||
|
|
759b44c224 | ||
|
|
ebdd1a0b03 | ||
|
|
6c44575f7a | ||
|
|
8be832239a | ||
|
|
c7be202430 | ||
|
|
244c5dc6b4 | ||
|
|
6bbb9a638e | ||
|
|
44041b136a | ||
|
|
7e17c30ce2 | ||
|
|
c4d3d1b409 | ||
|
|
03de63754b | ||
|
|
0e506f0b1a | ||
|
|
a2ab752870 | ||
|
|
eaa032828a | ||
|
|
cceb0b4c6c | ||
|
|
a58640a718 | ||
|
|
920384b26c | ||
|
|
193a2c4f70 | ||
|
|
793dec98b2 | ||
|
|
11ddf8015d | ||
|
|
1753bdbd8b | ||
|
|
d6e563486b | ||
|
|
0112bfa9c4 | ||
|
|
5951611fb0 | ||
|
|
2d31b726ac | ||
|
|
d2f295ef88 | ||
|
|
d1b53554ce | ||
|
|
ec5db122d0 | ||
|
|
0216e22fcc | ||
|
|
5734b19d8c | ||
|
|
cc8c818e13 | ||
|
|
e58aeec097 | ||
|
|
58b000927a | ||
|
|
797642b972 | ||
|
|
2e1f273d78 | ||
|
|
35d6800877 | ||
|
|
781856b822 | ||
|
|
ff272179e7 | ||
|
|
bec47f40f7 | ||
|
|
f9607a434a | ||
|
|
b650ca85bc | ||
|
|
91e154bbee | ||
|
|
f4365ed163 | ||
|
|
0c02b0cb68 | ||
|
|
b0aaa58fa7 | ||
|
|
054ab774d5 | ||
|
|
8ca33b552d | ||
|
|
6734c2b9f7 | ||
|
|
c484477d6a | ||
|
|
d9b5223749 | ||
|
|
55856450b3 | ||
|
|
bb28a3bf83 | ||
|
|
5e4e56bd2c | ||
|
|
82fac1d4e7 | ||
|
|
c97a7e5158 | ||
|
|
32e2d24b15 | ||
|
|
c102aae819 | ||
|
|
bed72cb5ed | ||
|
|
f0c1046fe9 | ||
|
|
d88104d105 | ||
|
|
f3e21e5a82 | ||
|
|
a77bee8664 | ||
|
|
b5d0aed59e | ||
|
|
2440cc6af5 | ||
|
|
90114dfbe0 | ||
|
|
64b8cdf7dc |
11
.github/workflows/validate-gradle-wrapper.yml
vendored
Normal file
11
.github/workflows/validate-gradle-wrapper.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
name: Validate Gradle Wrapper
|
||||||
|
|
||||||
|
on: [pull_request, push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validation:
|
||||||
|
name: Validation
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: gradle/wrapper-validation-action@v1
|
||||||
@@ -55,7 +55,7 @@ On the Fediverse, it’s quite common for people to pin posts they want others t
|
|||||||
|
|
||||||
[apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk](https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk)
|
[apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk](https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk)
|
||||||
|
|
||||||
<a href="#installation"><img height="50" alt="Get it on IzzyOnDroid" src="img/izzy-badge.png"></a>
|
<a href="https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk"><img height="50" alt="Get it on IzzyOnDroid" src="img/izzy-badge.png"></a>
|
||||||
|
|
||||||
Note that you'll need to add Izzy's F-Droid repository to your F-Droid app first:
|
Note that you'll need to add Izzy's F-Droid repository to your F-Droid app first:
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Megalodon</title>
|
<title>Megalodon</title>
|
||||||
<link rel="icon" href="mastodon/src/main/res/mipmap-mdpi/ic_launcher_round.png">
|
<link rel="icon" href="mastodon/src/main/res/mipmap-mdpi/ic_launcher_round.png">
|
||||||
<link rel="me" href="https://floss.social/@mastodon">
|
<link rel="me" href="https://floss.social/@megalodon">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css">
|
||||||
</head>
|
</head>
|
||||||
<body class="markdown-body">
|
<body class="markdown-body">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.3.1'
|
classpath 'com.android.tools.build:gradle:8.0.0'
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
files:
|
|
||||||
- source: /mastodon/src/main/res/values/strings.xml
|
|
||||||
translation: /mastodon/src/main/res/values-%android_code%/strings.xml
|
|
||||||
- source: /fastlane/metadata/android/en-US/*.txt
|
|
||||||
translation: /fastlane/metadata/android/%locale%/%original_file_name%
|
|
||||||
@@ -16,4 +16,7 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
|||||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
# Automatically convert third-party libraries to use AndroidX
|
# Automatically convert third-party libraries to use AndroidX
|
||||||
android.enableJetifier=false
|
android.enableJetifier=false
|
||||||
|
android.defaults.buildfeatures.buildconfig=true
|
||||||
|
android.nonTransitiveRClass=true
|
||||||
|
android.nonFinalResIds=false
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
7
gradle/wrapper/gradle-wrapper.properties
vendored
7
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,7 @@
|
|||||||
#Thu Jan 13 11:33:43 MSK 2022
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
distributionSha256Sum=e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
288
gradlew
vendored
288
gradlew
vendored
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright 2015 the original author or authors.
|
# Copyright © 2015-2021 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -17,67 +17,98 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
##
|
#
|
||||||
## Gradle start up script for UN*X
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
##
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
# Resolve links: $0 may be a link
|
# Resolve links: $0 may be a link
|
||||||
PRG="$0"
|
app_path=$0
|
||||||
# Need this for relative symlinks.
|
|
||||||
while [ -h "$PRG" ] ; do
|
# Need this for daisy-chained symlinks.
|
||||||
ls=`ls -ld "$PRG"`
|
while
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
[ -h "$app_path" ]
|
||||||
PRG="$link"
|
do
|
||||||
else
|
ls=$( ls -ld "$app_path" )
|
||||||
PRG=`dirname "$PRG"`"/$link"
|
link=${ls#*' -> '}
|
||||||
fi
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
done
|
done
|
||||||
SAVED="`pwd`"
|
|
||||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
|
||||||
APP_HOME="`pwd -P`"
|
|
||||||
cd "$SAVED" >/dev/null
|
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
# This is normally unused
|
||||||
APP_BASE_NAME=`basename "$0"`
|
# shellcheck disable=SC2034
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD=maximum
|
||||||
|
|
||||||
warn () {
|
warn () {
|
||||||
echo "$*"
|
echo "$*"
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
die () {
|
die () {
|
||||||
echo
|
echo
|
||||||
echo "$*"
|
echo "$*"
|
||||||
echo
|
echo
|
||||||
exit 1
|
exit 1
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
# OS specific support (must be 'true' or 'false').
|
||||||
cygwin=false
|
cygwin=false
|
||||||
msys=false
|
msys=false
|
||||||
darwin=false
|
darwin=false
|
||||||
nonstop=false
|
nonstop=false
|
||||||
case "`uname`" in
|
case "$( uname )" in #(
|
||||||
CYGWIN* )
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
cygwin=true
|
Darwin* ) darwin=true ;; #(
|
||||||
;;
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
Darwin* )
|
NONSTOP* ) nonstop=true ;;
|
||||||
darwin=true
|
|
||||||
;;
|
|
||||||
MINGW* )
|
|
||||||
msys=true
|
|
||||||
;;
|
|
||||||
NONSTOP* )
|
|
||||||
nonstop=true
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
@@ -87,9 +118,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
else
|
else
|
||||||
JAVACMD="$JAVA_HOME/bin/java"
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
fi
|
fi
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
@@ -98,7 +129,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
|||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD="java"
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
@@ -106,80 +137,109 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
MAX_FD_LIMIT=`ulimit -H -n`
|
case $MAX_FD in #(
|
||||||
if [ $? -eq 0 ] ; then
|
max*)
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
MAX_FD="$MAX_FD_LIMIT"
|
# shellcheck disable=SC3045
|
||||||
fi
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
ulimit -n $MAX_FD
|
warn "Could not query maximum file descriptor limit"
|
||||||
if [ $? -ne 0 ] ; then
|
esac
|
||||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
case $MAX_FD in #(
|
||||||
fi
|
'' | soft) :;; #(
|
||||||
else
|
*)
|
||||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
fi
|
# shellcheck disable=SC3045
|
||||||
fi
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
# For Darwin, add options to specify how the application appears in the dock
|
|
||||||
if $darwin; then
|
|
||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
|
||||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
|
||||||
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
|
||||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
|
||||||
SEP=""
|
|
||||||
for dir in $ROOTDIRSRAW ; do
|
|
||||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
|
||||||
SEP="|"
|
|
||||||
done
|
|
||||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
|
||||||
# Add a user-defined pattern to the cygpath arguments
|
|
||||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
|
||||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
|
||||||
fi
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
|
||||||
i=0
|
|
||||||
for arg in "$@" ; do
|
|
||||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
|
||||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
|
||||||
|
|
||||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
|
||||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
|
||||||
else
|
|
||||||
eval `echo args$i`="\"$arg\""
|
|
||||||
fi
|
|
||||||
i=`expr $i + 1`
|
|
||||||
done
|
|
||||||
case $i in
|
|
||||||
0) set -- ;;
|
|
||||||
1) set -- "$args0" ;;
|
|
||||||
2) set -- "$args0" "$args1" ;;
|
|
||||||
3) set -- "$args0" "$args1" "$args2" ;;
|
|
||||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
|
||||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
|
||||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
|
||||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
|
||||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
|
||||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Escape application args
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
save () {
|
# * args from the command line
|
||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
# * the main class name
|
||||||
echo " "
|
# * -classpath
|
||||||
}
|
# * -D...appname settings
|
||||||
APP_ARGS=`save "$@"`
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Collect all arguments for the java command;
|
||||||
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
|
# double quotes to make sure that they get re-expanded; and
|
||||||
|
# * put everything else in single quotes, so that it's not re-expanded.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
|||||||
15
gradlew.bat
vendored
15
gradlew.bat
vendored
@@ -14,7 +14,7 @@
|
|||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@@ -25,7 +25,8 @@
|
|||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
exit /b 1
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|||||||
@@ -2,6 +2,12 @@ plugins {
|
|||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(17)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdk 33
|
compileSdk 33
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
@@ -9,11 +15,11 @@ android {
|
|||||||
applicationId "org.joinmastodon.android.sk"
|
applicationId "org.joinmastodon.android.sk"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 79
|
versionCode 83
|
||||||
versionName "1.2.0+fork.79"
|
versionName "1.2.3+fork.83"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
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", "kab", "ko-rKR", "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", "vi-rVN", "zh-rCN", "zh-rTW"
|
resourceConfigurations += ['ar-rSA', 'ar-rDZ', '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', 'kab', 'ko-rKR', '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 {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
@@ -49,14 +55,19 @@ android {
|
|||||||
setRoot "src/github"
|
setRoot "src/github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lintOptions{
|
namespace 'org.joinmastodon.android'
|
||||||
checkReleaseBuilds false
|
lint {
|
||||||
abortOnError false
|
abortOnError false
|
||||||
|
checkReleaseBuilds false
|
||||||
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
buildConfig true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api 'androidx.annotation:annotation:1.3.0'
|
api 'androidx.annotation:annotation:1.6.0'
|
||||||
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
|
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
|
||||||
implementation 'me.grishka.litex:recyclerview:1.2.1.1'
|
implementation 'me.grishka.litex:recyclerview:1.2.1.1'
|
||||||
implementation 'me.grishka.litex:swiperefreshlayout:1.1.0.1'
|
implementation 'me.grishka.litex:swiperefreshlayout:1.1.0.1'
|
||||||
@@ -65,7 +76,7 @@ dependencies {
|
|||||||
implementation 'me.grishka.litex:viewpager:1.0.0'
|
implementation 'me.grishka.litex:viewpager:1.0.0'
|
||||||
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
||||||
implementation 'me.grishka.appkit:appkit:1.2.7'
|
implementation 'me.grishka.appkit:appkit:1.2.7'
|
||||||
implementation 'com.google.code.gson:gson:2.8.9'
|
implementation 'com.google.code.gson:gson:2.9.0'
|
||||||
implementation 'org.jsoup:jsoup:1.14.3'
|
implementation 'org.jsoup:jsoup:1.14.3'
|
||||||
implementation 'com.squareup:otto:1.3.8'
|
implementation 'com.squareup:otto:1.3.8'
|
||||||
implementation 'de.psdev:async-otto:1.0.3'
|
implementation 'de.psdev:async-otto:1.0.3'
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
package="org.joinmastodon.android">
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
package="org.joinmastodon.android">
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
@@ -17,6 +16,9 @@
|
|||||||
<action android:name="android.intent.action.PROCESS_TEXT" />
|
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||||
<data android:mimeType="text/plain" />
|
<data android:mimeType="text/plain" />
|
||||||
</intent>
|
</intent>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.TRANSLATE" />
|
||||||
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
@@ -36,6 +38,22 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".PanicResponderActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleInstance"
|
||||||
|
android:noHistory="true"
|
||||||
|
android:theme="@android:style/Theme.NoDisplay">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="info.guardianproject.panic.action.TRIGGER" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".ExitActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:theme="@android:style/Theme.NoDisplay" />
|
||||||
<activity android:name=".OAuthActivity" android:exported="true" android:configChanges="orientation|screenSize" android:launchMode="singleTask">
|
<activity android:name=".OAuthActivity" android:exported="true" android:configChanges="orientation|screenSize" android:launchMode="singleTask">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW"/>
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
# lists.d Mastodon Blocklist (c) 2022 Greyhat Academy LICENSED UNDER: CC-BY-NC-SA 4.0
|
|
||||||
# https://raw.githubusercontent.com/greyhat-academy/lists.d/main/mastodon.domains.block.list.tsv
|
|
||||||
# This list contains domains of toxic mastodon instances
|
|
||||||
# Last-Modified: 1672044500
|
|
||||||
|
|
||||||
# gab - a neonazi social network
|
|
||||||
gab.ai
|
|
||||||
gab.com
|
|
||||||
gab.protohype.net
|
|
||||||
|
|
||||||
# consequence-free speech
|
|
||||||
social.unzensiert.to
|
|
||||||
freeatlantis.com
|
|
||||||
|
|
||||||
# reactionary bigotry and hatespeech against magrinalized groups
|
|
||||||
poa.st
|
|
||||||
freespeechextremist.com
|
|
||||||
rdrama.cc
|
|
||||||
outpoa.st
|
|
||||||
anime.website
|
|
||||||
gameliberty.club
|
|
||||||
social.byoblu.com
|
|
||||||
yggdrasil.social
|
|
||||||
smuglo.li
|
|
||||||
dogeposting.social
|
|
||||||
unsafe.space
|
|
||||||
freezepeach.xyz
|
|
||||||
|
|
||||||
# + CSAM
|
|
||||||
rojogato.com
|
|
||||||
|
|
||||||
# antivaxxer shitposting & fearmongering
|
|
||||||
shadowsocial.org
|
|
||||||
|
|
||||||
# Kiwifarms
|
|
||||||
kiwifarms.net
|
|
||||||
kiwifarms.cc
|
|
||||||
kiwifarms.is
|
|
||||||
kiwifarms.pleroma.net
|
|
||||||
|
|
||||||
|
|
||||||
# https://mastodon.art/@Curator/109649354849593592
|
|
||||||
|
|
||||||
poa.st antisemitic racist homophobic
|
|
||||||
nicecrew.digital antisemitic
|
|
||||||
beefyboys.win antisemitic racist homophobic harassment
|
|
||||||
cawfee.club antisemitic racist homophobic
|
|
||||||
comfyboy.club antisemitic racist homophobic
|
|
||||||
freespeechextremist.com racist homophobic
|
|
||||||
cum.salon racist misogynist
|
|
||||||
bae.st racist
|
|
||||||
natehiggers.online racist
|
|
||||||
rapemeat.solutions misogynist
|
|
||||||
rapist.town misogynist
|
|
||||||
rapefeminists.network misogynist
|
|
||||||
kiwifarms.cc harassment
|
|
||||||
noagendasocial.com noagenda
|
|
||||||
posting.lolicon.rocks underage
|
|
||||||
urchan.org harassment homophobic racist
|
|
||||||
ryona.agency harassment
|
|
||||||
yggdrasil.social antisemitic homophobic racist
|
|
||||||
genderheretics.xyz transphobic
|
|
||||||
baraag.net underage
|
|
||||||
lolison.top underage
|
|
||||||
shota.house underage
|
|
||||||
shota.social underage
|
|
||||||
aethy.com underage
|
|
||||||
taullo.social underage
|
|
||||||
childpawn.shop underage
|
|
||||||
posting.lolicon.rocks underage
|
|
||||||
loli.best underage
|
|
||||||
gothloli.club underage
|
|
||||||
smuglo.li underage
|
|
||||||
youjo.love underage
|
|
||||||
pedo.school underage
|
|
||||||
lolison.network underage
|
|
||||||
freak.university underage
|
|
||||||
mirr0r.city underage
|
|
||||||
xhais.love underage
|
|
||||||
refusal.biz underage
|
|
||||||
refusal.llc underage
|
|
||||||
mirr0r.city underage
|
|
||||||
nnia.space underage
|
|
||||||
ignorelist.com malicious
|
|
||||||
repl.co malicious
|
|
||||||
|
|
||||||
# custom
|
|
||||||
|
|
||||||
pawoo.net csam
|
|
||||||
|
171
mastodon/src/main/assets/blocks.txt
Normal file
171
mastodon/src/main/assets/blocks.txt
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
13bells.com
|
||||||
|
4aem.com
|
||||||
|
aethy.com
|
||||||
|
anime.website
|
||||||
|
annihilation.social
|
||||||
|
anon-kenkai.com
|
||||||
|
asbestos.cafe
|
||||||
|
bae.st
|
||||||
|
bajax.us
|
||||||
|
banepo.st
|
||||||
|
baraag.net
|
||||||
|
beefyboys.win
|
||||||
|
beepboop.ga
|
||||||
|
berserker.town
|
||||||
|
bikeshed.party
|
||||||
|
boks.moe
|
||||||
|
brainsoap.net
|
||||||
|
breastmilk.club
|
||||||
|
brighteon.social
|
||||||
|
cawfee.club
|
||||||
|
clew.lol
|
||||||
|
clubcyberia.co
|
||||||
|
collapsitarian.io
|
||||||
|
comfyboy.club
|
||||||
|
contrapointsfan.club
|
||||||
|
cum.camp
|
||||||
|
cum.salon
|
||||||
|
cybercriminal.eu
|
||||||
|
darknight-coffee.org
|
||||||
|
dembased.xyz
|
||||||
|
desupost.soy
|
||||||
|
detroitriotcity.com
|
||||||
|
eatthebugs.social
|
||||||
|
eientei.org
|
||||||
|
elementality.org
|
||||||
|
eveningzoo.club
|
||||||
|
firedragonstudios.com
|
||||||
|
firefaithfellowship.com
|
||||||
|
fluf.club
|
||||||
|
foxfam.club
|
||||||
|
freak.university
|
||||||
|
freeatlantis.com
|
||||||
|
freecumextremist.com
|
||||||
|
freedomstrike.org
|
||||||
|
freesoftwareextremist.com
|
||||||
|
freespeech.group
|
||||||
|
freespeechextremist.com
|
||||||
|
freetalklive.com
|
||||||
|
froth.zone
|
||||||
|
fulltermprivacy.com
|
||||||
|
gameliberty.club
|
||||||
|
gearlandia.haus
|
||||||
|
genderheretics.xyz
|
||||||
|
geofront.rocks
|
||||||
|
gleasonator.com
|
||||||
|
glee.li
|
||||||
|
glindr.org
|
||||||
|
goyim.app
|
||||||
|
goyslop.cafe
|
||||||
|
haeder.net
|
||||||
|
handholding.io
|
||||||
|
hidamari.apartments
|
||||||
|
hitchhiker.social
|
||||||
|
hunk.city
|
||||||
|
iddqd.social
|
||||||
|
intkos.link
|
||||||
|
justicewarrior.social
|
||||||
|
kawa-kun.com
|
||||||
|
kitsunemimi.club
|
||||||
|
kiwifarms.cc
|
||||||
|
kompost.cz
|
||||||
|
kurosawa.moe
|
||||||
|
leafposter.club
|
||||||
|
leftychan.net
|
||||||
|
lewdieheaven.com
|
||||||
|
liberdon.com
|
||||||
|
ligma.pro
|
||||||
|
lizards.live
|
||||||
|
lolicon.rocks
|
||||||
|
lolison.top
|
||||||
|
lovingexpressions.net
|
||||||
|
lucasvl.nl
|
||||||
|
mahodou.moe
|
||||||
|
makemysarcophagus.com
|
||||||
|
maladaptive.art
|
||||||
|
masochi.st
|
||||||
|
mastinator.com
|
||||||
|
merovingian.club
|
||||||
|
midwaytrades.com
|
||||||
|
mirr0r.city
|
||||||
|
moa.st
|
||||||
|
mouse.services
|
||||||
|
mugicha.club
|
||||||
|
narrativerry.xyz
|
||||||
|
natehiggers.online
|
||||||
|
neckbeard.xyz
|
||||||
|
needs.vodka
|
||||||
|
neenster.org
|
||||||
|
nicecrew.digital
|
||||||
|
nnia.space
|
||||||
|
noagendasocial.com
|
||||||
|
noagendasocial.nl
|
||||||
|
noagendatube.com
|
||||||
|
nobodyhasthe.biz
|
||||||
|
nukem.biz
|
||||||
|
obo.sh
|
||||||
|
onionfarms.org
|
||||||
|
outpoa.st
|
||||||
|
pawlicker.com
|
||||||
|
pawoo.net
|
||||||
|
pedo.school
|
||||||
|
piazza.today
|
||||||
|
pibvt.net
|
||||||
|
pieville.net
|
||||||
|
pisskey.io
|
||||||
|
plagu.ee
|
||||||
|
pmth.us
|
||||||
|
poa.st
|
||||||
|
poast.org
|
||||||
|
poast.tv
|
||||||
|
poster.place
|
||||||
|
prospeech.space
|
||||||
|
quodverum.com
|
||||||
|
rakket.app
|
||||||
|
rapemeat.solutions
|
||||||
|
rdrama.cc
|
||||||
|
rebelbase.site
|
||||||
|
retardedniggers.forsale
|
||||||
|
rojogato.com
|
||||||
|
ryona.agency
|
||||||
|
schwartzwelt.xyz
|
||||||
|
seal.cafe
|
||||||
|
shigusegubu.club
|
||||||
|
shitpost.cloud
|
||||||
|
shitposter.club
|
||||||
|
shota.house
|
||||||
|
silliness.observer
|
||||||
|
skinheads.eu
|
||||||
|
skinheads.io
|
||||||
|
skinheads.social
|
||||||
|
skinheads.uk
|
||||||
|
skippers-bin.com
|
||||||
|
skyshanty.xyz
|
||||||
|
slash.cl
|
||||||
|
sleepy.cafe
|
||||||
|
smuglo.li
|
||||||
|
sneed.social
|
||||||
|
sonichu.com
|
||||||
|
spinster.xyz
|
||||||
|
springbo.cc
|
||||||
|
starnix.network
|
||||||
|
stereophonic.space
|
||||||
|
strelizia.net
|
||||||
|
syspxl.xyz
|
||||||
|
tastingtraffic.net
|
||||||
|
teci.world
|
||||||
|
theapex.social
|
||||||
|
thepostearthdestination.com
|
||||||
|
tkammer.de
|
||||||
|
trumpislovetrumpis.life
|
||||||
|
truthsocial.co.in
|
||||||
|
urchan.org
|
||||||
|
varishangout.net
|
||||||
|
whinge.house
|
||||||
|
whinge.town
|
||||||
|
wideboys.org
|
||||||
|
wolfgirl.bar
|
||||||
|
xn--p1abe3d.xn--80asehdb
|
||||||
|
yggdrasil.social
|
||||||
|
youjo.love
|
||||||
|
zztails.gay
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package org.joinmastodon.android;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
public class ExitActivity extends Activity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
finishAndRemoveTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void exit(Context context) {
|
||||||
|
Intent intent = new Intent(context, ExitActivity.class);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import org.joinmastodon.android.api.session.AccountSession;
|
|||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.jsoup.internal.StringUtil;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -51,9 +52,15 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
|||||||
String subject = "";
|
String subject = "";
|
||||||
if (intent.hasExtra(Intent.EXTRA_SUBJECT)) {
|
if (intent.hasExtra(Intent.EXTRA_SUBJECT)) {
|
||||||
subject = intent.getStringExtra(Intent.EXTRA_SUBJECT);
|
subject = intent.getStringExtra(Intent.EXTRA_SUBJECT);
|
||||||
if (!subject.isBlank()) builder.append(subject).append("\n\n");
|
if (!StringUtil.isBlank(subject)) builder.append(subject).append("\n\n");
|
||||||
|
}
|
||||||
|
if (intent.hasExtra(Intent.EXTRA_TEXT)) {
|
||||||
|
String extra = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||||
|
if (!StringUtil.isBlank(extra)) {
|
||||||
|
if (extra.startsWith(subject)) extra = extra.substring(subject.length()).trim();
|
||||||
|
builder.append(extra).append("\n\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (intent.hasExtra(Intent.EXTRA_TEXT)) builder.append(intent.getStringExtra(Intent.EXTRA_TEXT)).append("\n");
|
|
||||||
String text=builder.toString();
|
String text=builder.toString();
|
||||||
List<Uri> mediaUris;
|
List<Uri> mediaUris;
|
||||||
if(Intent.ACTION_SEND.equals(intent.getAction())){
|
if(Intent.ACTION_SEND.equals(intent.getAction())){
|
||||||
@@ -80,8 +87,7 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
|||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
if(!TextUtils.isEmpty(text))
|
if(!TextUtils.isEmpty(text))
|
||||||
args.putString("prefilledText", text);
|
args.putString("prefilledText", text);
|
||||||
if(!subject.isBlank())
|
args.putInt("selectionStart", StringUtil.isBlank(subject) ? 0 : subject.length());
|
||||||
args.putInt("selectionEnd", subject.length());
|
|
||||||
if(mediaUris!=null && !mediaUris.isEmpty())
|
if(mediaUris!=null && !mediaUris.isEmpty())
|
||||||
args.putParcelableArrayList("mediaAttachments", toArrayList(mediaUris));
|
args.putParcelableArrayList("mediaAttachments", toArrayList(mediaUris));
|
||||||
Fragment fragment=new ComposeFragment();
|
Fragment fragment=new ComposeFragment();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.content.SharedPreferences;
|
|||||||
import com.google.gson.JsonSyntaxException;
|
import com.google.gson.JsonSyntaxException;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.model.ContentType;
|
||||||
import org.joinmastodon.android.model.TimelineDefinition;
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
@@ -46,16 +47,20 @@ public class GlobalUserPreferences{
|
|||||||
public static boolean autoHideFab;
|
public static boolean autoHideFab;
|
||||||
public static boolean replyLineAboveHeader;
|
public static boolean replyLineAboveHeader;
|
||||||
public static boolean compactReblogReplyLine;
|
public static boolean compactReblogReplyLine;
|
||||||
|
public static boolean confirmBeforeReblog;
|
||||||
public static String publishButtonText;
|
public static String publishButtonText;
|
||||||
public static ThemePreference theme;
|
public static ThemePreference theme;
|
||||||
public static ColorPreference color;
|
public static ColorPreference color;
|
||||||
|
|
||||||
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
|
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
|
||||||
private final static Type pinnedTimelinesType = new TypeToken<Map<String, List<TimelineDefinition>>>() {}.getType();
|
private final static Type pinnedTimelinesType = new TypeToken<Map<String, List<TimelineDefinition>>>() {}.getType();
|
||||||
|
private final static Type accountsDefaultContentTypesType = new TypeToken<Map<String, ContentType>>() {}.getType();
|
||||||
public static Map<String, List<String>> recentLanguages;
|
public static Map<String, List<String>> recentLanguages;
|
||||||
public static Map<String, List<TimelineDefinition>> pinnedTimelines;
|
public static Map<String, List<TimelineDefinition>> pinnedTimelines;
|
||||||
public static Set<String> accountsWithLocalOnlySupport;
|
public static Set<String> accountsWithLocalOnlySupport;
|
||||||
public static Set<String> accountsInGlitchMode;
|
public static Set<String> accountsInGlitchMode;
|
||||||
|
public static Set<String> accountsWithContentTypesEnabled;
|
||||||
|
public static Map<String, ContentType> accountsDefaultContentTypes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pleroma
|
* Pleroma
|
||||||
@@ -102,6 +107,7 @@ public class GlobalUserPreferences{
|
|||||||
autoHideFab=prefs.getBoolean("autoHideFab", true);
|
autoHideFab=prefs.getBoolean("autoHideFab", true);
|
||||||
replyLineAboveHeader=prefs.getBoolean("replyLineAboveHeader", true);
|
replyLineAboveHeader=prefs.getBoolean("replyLineAboveHeader", true);
|
||||||
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
|
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
|
||||||
|
confirmBeforeReblog=prefs.getBoolean("confirmBeforeReblog", false);
|
||||||
publishButtonText=prefs.getString("publishButtonText", "");
|
publishButtonText=prefs.getString("publishButtonText", "");
|
||||||
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
||||||
recentLanguages=fromJson(prefs.getString("recentLanguages", null), recentLanguagesType, new HashMap<>());
|
recentLanguages=fromJson(prefs.getString("recentLanguages", null), recentLanguagesType, new HashMap<>());
|
||||||
@@ -109,6 +115,8 @@ public class GlobalUserPreferences{
|
|||||||
accountsWithLocalOnlySupport=prefs.getStringSet("accountsWithLocalOnlySupport", new HashSet<>());
|
accountsWithLocalOnlySupport=prefs.getStringSet("accountsWithLocalOnlySupport", new HashSet<>());
|
||||||
accountsInGlitchMode=prefs.getStringSet("accountsInGlitchMode", new HashSet<>());
|
accountsInGlitchMode=prefs.getStringSet("accountsInGlitchMode", new HashSet<>());
|
||||||
replyVisibility=prefs.getString("replyVisibility", null);
|
replyVisibility=prefs.getString("replyVisibility", null);
|
||||||
|
accountsWithContentTypesEnabled=prefs.getStringSet("accountsWithContentTypesEnabled", new HashSet<>());
|
||||||
|
accountsDefaultContentTypes=fromJson(prefs.getString("accountsDefaultContentTypes", null), accountsDefaultContentTypesType, new HashMap<>());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.PINK.name()));
|
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.PINK.name()));
|
||||||
@@ -148,6 +156,7 @@ public class GlobalUserPreferences{
|
|||||||
.putString("publishButtonText", publishButtonText)
|
.putString("publishButtonText", publishButtonText)
|
||||||
.putBoolean("bottomEncoding", bottomEncoding)
|
.putBoolean("bottomEncoding", bottomEncoding)
|
||||||
.putBoolean("replyLineAboveHeader", replyLineAboveHeader)
|
.putBoolean("replyLineAboveHeader", replyLineAboveHeader)
|
||||||
|
.putBoolean("confirmBeforeReblog", confirmBeforeReblog)
|
||||||
.putInt("theme", theme.ordinal())
|
.putInt("theme", theme.ordinal())
|
||||||
.putString("color", color.name())
|
.putString("color", color.name())
|
||||||
.putString("recentLanguages", gson.toJson(recentLanguages))
|
.putString("recentLanguages", gson.toJson(recentLanguages))
|
||||||
@@ -155,6 +164,8 @@ public class GlobalUserPreferences{
|
|||||||
.putStringSet("accountsWithLocalOnlySupport", accountsWithLocalOnlySupport)
|
.putStringSet("accountsWithLocalOnlySupport", accountsWithLocalOnlySupport)
|
||||||
.putStringSet("accountsInGlitchMode", accountsInGlitchMode)
|
.putStringSet("accountsInGlitchMode", accountsInGlitchMode)
|
||||||
.putString("replyVisibility", replyVisibility)
|
.putString("replyVisibility", replyVisibility)
|
||||||
|
.putStringSet("accountsWithContentTypesEnabled", accountsWithContentTypesEnabled)
|
||||||
|
.putString("accountsDefaultContentTypes", gson.toJson(accountsDefaultContentTypes))
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package org.joinmastodon.android;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
|
||||||
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
|
||||||
|
|
||||||
|
public class PanicResponderActivity extends Activity {
|
||||||
|
public static final String PANIC_TRIGGER_ACTION = "info.guardianproject.panic.action.TRIGGER";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
final Intent intent = getIntent();
|
||||||
|
if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) {
|
||||||
|
AccountSessionManager.getInstance().getLoggedInAccounts().forEach(accountSession -> logOut(accountSession.getID()));
|
||||||
|
ExitActivity.exit(this);
|
||||||
|
}
|
||||||
|
finishAndRemoveTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logOut(String accountID){
|
||||||
|
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
|
new RevokeOauthToken(session.app.clientId, session.app.clientSecret, session.token.accessToken)
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Object result){
|
||||||
|
onLoggedOut(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
onLoggedOut(accountID);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onLoggedOut(String accountID){
|
||||||
|
AccountSessionManager.getInstance().removeAccount(accountID);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
|
|||||||
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
|
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.events.NotificationReceivedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.NotificationAction;
|
import org.joinmastodon.android.model.NotificationAction;
|
||||||
import org.joinmastodon.android.model.Preferences;
|
import org.joinmastodon.android.model.Preferences;
|
||||||
@@ -55,6 +56,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent){
|
public void onReceive(Context context, Intent intent){
|
||||||
|
UiUtils.setUserPreferredTheme(context);
|
||||||
if(BuildConfig.DEBUG){
|
if(BuildConfig.DEBUG){
|
||||||
Log.e(TAG, "received: "+intent);
|
Log.e(TAG, "received: "+intent);
|
||||||
Bundle extras=intent.getExtras();
|
Bundle extras=intent.getExtras();
|
||||||
@@ -84,6 +86,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
}
|
}
|
||||||
String accountID=account.getID();
|
String accountID=account.getID();
|
||||||
PushNotification pn=AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().decryptNotification(k, p, s);
|
PushNotification pn=AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().decryptNotification(k, p, s);
|
||||||
|
E.post(new NotificationReceivedEvent(accountID, pn.notificationId+""));
|
||||||
new GetNotificationByID(pn.notificationId+"")
|
new GetNotificationByID(pn.notificationId+"")
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
@@ -185,7 +188,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
.setShowWhen(true)
|
.setShowWhen(true)
|
||||||
.setCategory(Notification.CATEGORY_SOCIAL)
|
.setCategory(Notification.CATEGORY_SOCIAL)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setColor(context.getColor(R.color.shortcut_icon_background));
|
.setColor(UiUtils.getThemeColor(context, android.R.attr.colorAccent));
|
||||||
|
|
||||||
if (!GlobalUserPreferences.uniformNotificationIcon) {
|
if (!GlobalUserPreferences.uniformNotificationIcon) {
|
||||||
builder.setSmallIcon(switch (pn.notificationType) {
|
builder.setSmallIcon(switch (pn.notificationType) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public class ApiUtils{
|
|||||||
//no instance
|
//no instance
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <E extends Enum<E>> List<String> enumSetToStrings(EnumSet<E> e, Class<E> cls){
|
public static <E extends Enum<E>> List<String> enumSetToStrings(EnumSet<E> e, Class<E> cls){
|
||||||
return e.stream().map(ev->{
|
return e.stream().map(ev->{
|
||||||
try{
|
try{
|
||||||
SerializedName annotation=cls.getField(ev.name()).getAnnotation(SerializedName.class);
|
SerializedName annotation=cls.getField(ev.name()).getAnnotation(SerializedName.class);
|
||||||
|
|||||||
@@ -74,10 +74,8 @@ public class CacheController{
|
|||||||
int flags=cursor.getInt(1);
|
int flags=cursor.getInt(1);
|
||||||
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0);
|
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0);
|
||||||
newMaxID=status.id;
|
newMaxID=status.id;
|
||||||
for(Filter filter:filters){
|
if (!new StatusFilterPredicate(filters, Filter.FilterContext.HOME).test(status))
|
||||||
if(filter.matches(status))
|
continue outer;
|
||||||
continue outer;
|
|
||||||
}
|
|
||||||
result.add(status);
|
result.add(status);
|
||||||
}while(cursor.moveToNext());
|
}while(cursor.moveToNext());
|
||||||
String _newMaxID=newMaxID;
|
String _newMaxID=newMaxID;
|
||||||
@@ -92,7 +90,7 @@ public class CacheController{
|
|||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(new StatusFilterPredicate(filters)).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
|
callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(new StatusFilterPredicate(filters, Filter.FilterContext.HOME)).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
|
||||||
putHomeTimeline(result, maxID==null);
|
putHomeTimeline(result, maxID==null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +126,7 @@ public class CacheController{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void getNotifications(String maxID, int count, boolean onlyMentions, boolean onlyPosts, boolean forceReload, Callback<PaginatedResponse<List<Notification>>> callback){
|
public void getNotifications(String maxID, int count, boolean onlyMentions, boolean onlyPosts, boolean forceReload, Callback<CacheablePaginatedResponse<List<Notification>>> callback){
|
||||||
cancelDelayedClose();
|
cancelDelayedClose();
|
||||||
databaseThread.postRunnable(()->{
|
databaseThread.postRunnable(()->{
|
||||||
try{
|
try{
|
||||||
@@ -148,15 +146,13 @@ public class CacheController{
|
|||||||
ntf.postprocess();
|
ntf.postprocess();
|
||||||
newMaxID=ntf.id;
|
newMaxID=ntf.id;
|
||||||
if(ntf.status!=null){
|
if(ntf.status!=null){
|
||||||
for(Filter filter:filters){
|
if (!new StatusFilterPredicate(filters, Filter.FilterContext.NOTIFICATIONS).test(ntf.status))
|
||||||
if(filter.matches(ntf.status))
|
continue outer;
|
||||||
continue outer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
result.add(ntf);
|
result.add(ntf);
|
||||||
}while(cursor.moveToNext());
|
}while(cursor.moveToNext());
|
||||||
String _newMaxID=newMaxID;
|
String _newMaxID=newMaxID;
|
||||||
uiHandler.post(()->callback.onSuccess(new PaginatedResponse<>(result, _newMaxID)));
|
uiHandler.post(()->callback.onSuccess(new CacheablePaginatedResponse<>(result, _newMaxID, true)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}catch(IOException x){
|
}catch(IOException x){
|
||||||
@@ -168,16 +164,12 @@ public class CacheController{
|
|||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Notification> result){
|
public void onSuccess(List<Notification> result){
|
||||||
callback.onSuccess(new PaginatedResponse<>(result.stream().filter(ntf->{
|
callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(ntf->{
|
||||||
if(ntf.status!=null){
|
if(ntf.status!=null){
|
||||||
for(Filter filter:filters){
|
return new StatusFilterPredicate(filters, Filter.FilterContext.NOTIFICATIONS).test(ntf.status);
|
||||||
if(filter.matches(ntf.status)){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id));
|
}).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
|
||||||
putNotifications(result, onlyMentions, onlyPosts, maxID==null);
|
putNotifications(result, onlyMentions, onlyPosts, maxID==null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -51,7 +52,9 @@ public class MastodonAPIController{
|
|||||||
.registerTypeAdapter(Status.class, new Status.StatusDeserializer())
|
.registerTypeAdapter(Status.class, new Status.StatusDeserializer())
|
||||||
.create();
|
.create();
|
||||||
private static WorkerThread thread=new WorkerThread("MastodonAPIController");
|
private static WorkerThread thread=new WorkerThread("MastodonAPIController");
|
||||||
private static OkHttpClient httpClient=new OkHttpClient.Builder().build();
|
private static OkHttpClient httpClient=new OkHttpClient.Builder()
|
||||||
|
.readTimeout(5, TimeUnit.MINUTES)
|
||||||
|
.build();
|
||||||
|
|
||||||
private AccountSession session;
|
private AccountSession session;
|
||||||
private static List<String> badDomains = new ArrayList<>();
|
private static List<String> badDomains = new ArrayList<>();
|
||||||
@@ -60,7 +63,7 @@ public class MastodonAPIController{
|
|||||||
thread.start();
|
thread.start();
|
||||||
try {
|
try {
|
||||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(
|
final BufferedReader reader = new BufferedReader(new InputStreamReader(
|
||||||
MastodonApp.context.getAssets().open("blocks.tsv")
|
MastodonApp.context.getAssets().open("blocks.txt")
|
||||||
));
|
));
|
||||||
String line;
|
String line;
|
||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
@@ -91,7 +94,7 @@ public class MastodonAPIController{
|
|||||||
Request.Builder builder=new Request.Builder()
|
Request.Builder builder=new Request.Builder()
|
||||||
.url(req.getURL().toString())
|
.url(req.getURL().toString())
|
||||||
.method(req.getMethod(), req.getRequestBody())
|
.method(req.getMethod(), req.getRequestBody())
|
||||||
.header("User-Agent", "MastodonAndroid/"+BuildConfig.VERSION_NAME);
|
.header("User-Agent", "MegalodonAndroid/"+BuildConfig.VERSION_NAME);
|
||||||
|
|
||||||
String token=null;
|
String token=null;
|
||||||
if(session!=null)
|
if(session!=null)
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import android.view.View;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
|
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
|
||||||
public class MastodonErrorResponse extends ErrorResponse{
|
public class MastodonErrorResponse extends ErrorResponse{
|
||||||
@@ -22,7 +20,7 @@ public class MastodonErrorResponse extends ErrorResponse{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bindErrorView(View view){
|
public void bindErrorView(View view){
|
||||||
TextView text=view.findViewById(R.id.error_text);
|
TextView text=view.findViewById(me.grishka.appkit.R.id.error_text);
|
||||||
text.setText(error);
|
text.setText(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.markers;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.ApiUtils;
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.Marker;
|
||||||
|
import org.joinmastodon.android.model.Markers;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
public class GetMarkers extends MastodonAPIRequest<Markers> {
|
||||||
|
public GetMarkers(EnumSet<Marker.Type> timelines) {
|
||||||
|
super(HttpMethod.GET, "/markers", Markers.class);
|
||||||
|
for (String type : ApiUtils.enumSetToStrings(timelines, Marker.Type.class)){
|
||||||
|
addQueryParameter("timeline[]", type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.joinmastodon.android.api.requests.statuses;
|
package org.joinmastodon.android.api.requests.statuses;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.ContentType;
|
||||||
import org.joinmastodon.android.model.ScheduledStatus;
|
import org.joinmastodon.android.model.ScheduledStatus;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.StatusPrivacy;
|
import org.joinmastodon.android.model.StatusPrivacy;
|
||||||
@@ -46,6 +47,7 @@ public class CreateStatus extends MastodonAPIRequest<Status>{
|
|||||||
public String language;
|
public String language;
|
||||||
|
|
||||||
public String quoteId;
|
public String quoteId;
|
||||||
|
public ContentType contentType;
|
||||||
|
|
||||||
public static class Poll{
|
public static class Poll{
|
||||||
public ArrayList<String> options=new ArrayList<>();
|
public ArrayList<String> options=new ArrayList<>();
|
||||||
|
|||||||
@@ -2,17 +2,22 @@ package org.joinmastodon.android.api.requests.statuses;
|
|||||||
|
|
||||||
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.api.RequiredField;
|
||||||
import org.joinmastodon.android.model.BaseModel;
|
import org.joinmastodon.android.model.BaseModel;
|
||||||
|
import org.joinmastodon.android.model.ContentType;
|
||||||
|
|
||||||
public class GetStatusSourceText extends MastodonAPIRequest<GetStatusSourceText.Response>{
|
public class GetStatusSourceText extends MastodonAPIRequest<GetStatusSourceText.Response>{
|
||||||
public GetStatusSourceText(String id){
|
public GetStatusSourceText(String id){
|
||||||
super(HttpMethod.GET, "/statuses/"+id+"/source", Response.class);
|
super(HttpMethod.GET, "/statuses/"+id+"/source", Response.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AllFieldsAreRequired
|
|
||||||
public static class Response extends BaseModel{
|
public static class Response extends BaseModel{
|
||||||
|
@RequiredField
|
||||||
public String id;
|
public String id;
|
||||||
|
@RequiredField
|
||||||
public String text;
|
public String text;
|
||||||
|
@RequiredField
|
||||||
public String spoilerText;
|
public String spoilerText;
|
||||||
|
public ContentType contentType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import org.joinmastodon.android.api.StatusInteractionController;
|
|||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Application;
|
import org.joinmastodon.android.model.Application;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
|
import org.joinmastodon.android.model.Markers;
|
||||||
import org.joinmastodon.android.model.Preferences;
|
import org.joinmastodon.android.model.Preferences;
|
||||||
import org.joinmastodon.android.model.PushSubscription;
|
import org.joinmastodon.android.model.PushSubscription;
|
||||||
import org.joinmastodon.android.model.Token;
|
import org.joinmastodon.android.model.Token;
|
||||||
@@ -31,6 +32,7 @@ public class AccountSession{
|
|||||||
public String pushAccountID;
|
public String pushAccountID;
|
||||||
public Preferences preferences;
|
public Preferences preferences;
|
||||||
public AccountActivationInfo activationInfo;
|
public AccountActivationInfo activationInfo;
|
||||||
|
public Markers markers;
|
||||||
private transient MastodonAPIController apiController;
|
private transient MastodonAPIController apiController;
|
||||||
private transient StatusInteractionController statusInteractionController, remoteStatusInteractionController;
|
private transient StatusInteractionController statusInteractionController, remoteStatusInteractionController;
|
||||||
private transient CacheController cacheController;
|
private transient CacheController cacheController;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import org.joinmastodon.android.api.requests.accounts.GetWordFilters;
|
|||||||
import org.joinmastodon.android.api.requests.instance.GetCustomEmojis;
|
import org.joinmastodon.android.api.requests.instance.GetCustomEmojis;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
||||||
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||||
|
import org.joinmastodon.android.api.requests.markers.GetMarkers;
|
||||||
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
|
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
|
||||||
import org.joinmastodon.android.events.EmojiUpdatedEvent;
|
import org.joinmastodon.android.events.EmojiUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
@@ -33,6 +34,8 @@ import org.joinmastodon.android.model.Emoji;
|
|||||||
import org.joinmastodon.android.model.EmojiCategory;
|
import org.joinmastodon.android.model.EmojiCategory;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
|
import org.joinmastodon.android.model.Marker;
|
||||||
|
import org.joinmastodon.android.model.Markers;
|
||||||
import org.joinmastodon.android.model.Preferences;
|
import org.joinmastodon.android.model.Preferences;
|
||||||
import org.joinmastodon.android.model.Token;
|
import org.joinmastodon.android.model.Token;
|
||||||
|
|
||||||
@@ -46,6 +49,7 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.EnumSet;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -255,6 +259,7 @@ public class AccountSessionManager{
|
|||||||
// if(now-session.filtersLastUpdated>3600_000L){
|
// if(now-session.filtersLastUpdated>3600_000L){
|
||||||
updateSessionWordFilters(session);
|
updateSessionWordFilters(session);
|
||||||
// }
|
// }
|
||||||
|
updateSessionMarkers(session);
|
||||||
}
|
}
|
||||||
if(loadedInstances){
|
if(loadedInstances){
|
||||||
maybeUpdateCustomEmojis(domains);
|
maybeUpdateCustomEmojis(domains);
|
||||||
@@ -271,6 +276,15 @@ public class AccountSessionManager{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void preferencesFromSource(AccountSession session, Account account) {
|
||||||
|
if (account != null && account.source != null && session.preferences != null) {
|
||||||
|
if (account.source.privacy != null)
|
||||||
|
session.preferences.postingDefaultVisibility = account.source.privacy;
|
||||||
|
if (account.source.language != null)
|
||||||
|
session.preferences.postingDefaultLanguage = account.source.language;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void updateSessionLocalInfo(AccountSession session){
|
private void updateSessionLocalInfo(AccountSession session){
|
||||||
new GetOwnAccount()
|
new GetOwnAccount()
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@@ -278,19 +292,12 @@ public class AccountSessionManager{
|
|||||||
public void onSuccess(Account result){
|
public void onSuccess(Account result){
|
||||||
session.self=result;
|
session.self=result;
|
||||||
session.infoLastUpdated=System.currentTimeMillis();
|
session.infoLastUpdated=System.currentTimeMillis();
|
||||||
if(session.preferences != null && session.preferences.postingDefaultVisibility != null){
|
preferencesFromSource(session, result);
|
||||||
session.preferences.postingDefaultVisibility = result.source.privacy;
|
|
||||||
}
|
|
||||||
if(session.preferences != null && session.preferences.postingDefaultLanguage != null){
|
|
||||||
session.preferences.postingDefaultLanguage = result.source.language;
|
|
||||||
}
|
|
||||||
writeAccountsFile();
|
writeAccountsFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){}
|
||||||
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.exec(session.getID());
|
.exec(session.getID());
|
||||||
}
|
}
|
||||||
@@ -300,16 +307,13 @@ public class AccountSessionManager{
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(Preferences preferences) {
|
public void onSuccess(Preferences preferences) {
|
||||||
session.preferences=preferences;
|
session.preferences=preferences;
|
||||||
|
preferencesFromSource(session, session.self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error) {
|
public void onError(ErrorResponse error) {
|
||||||
Preferences preferences = new Preferences();
|
session.preferences = new Preferences();
|
||||||
if(session.self != null){
|
preferencesFromSource(session, session.self);
|
||||||
preferences.postingDefaultVisibility = session.self.source.privacy;
|
|
||||||
preferences.postingDefaultLanguage = session.self.source.language;
|
|
||||||
}
|
|
||||||
session.preferences = preferences;
|
|
||||||
}
|
}
|
||||||
}).exec(session.getID());
|
}).exec(session.getID());
|
||||||
}
|
}
|
||||||
@@ -332,6 +336,21 @@ public class AccountSessionManager{
|
|||||||
.exec(session.getID());
|
.exec(session.getID());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateSessionMarkers(AccountSession session) {
|
||||||
|
new GetMarkers(EnumSet.allOf(Marker.Type.class)).setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Markers markers) {
|
||||||
|
session.markers = markers;
|
||||||
|
writeAccountsFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}).exec(session.getID());
|
||||||
|
}
|
||||||
|
|
||||||
public void updateInstanceInfo(String domain){
|
public void updateInstanceInfo(String domain){
|
||||||
new GetInstance()
|
new GetInstance()
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package org.joinmastodon.android.events;
|
||||||
|
|
||||||
|
public class AllNotificationsSeenEvent {
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package org.joinmastodon.android.events;
|
||||||
|
|
||||||
|
public class NotificationReceivedEvent {
|
||||||
|
public String account, id;
|
||||||
|
public NotificationReceivedEvent(String account, String id) {
|
||||||
|
this.account = account;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,6 @@ package org.joinmastodon.android.fragments;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.animation.TranslateAnimation;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||||
@@ -64,7 +60,12 @@ public class AccountTimelineFragment extends StatusListFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
if(getActivity()==null) return;
|
if(getActivity()==null) return;
|
||||||
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.ACCOUNT)).collect(Collectors.toList());
|
AccountSessionManager asm = AccountSessionManager.getInstance();
|
||||||
|
result=result.stream().filter(status -> {
|
||||||
|
// don't hide own posts in own profile
|
||||||
|
if (asm.isSelf(accountID, user) && asm.isSelf(accountID, status.account)) return true;
|
||||||
|
else return new StatusFilterPredicate(accountID, getFilterContext()).test(status);
|
||||||
|
}).collect(Collectors.toList());
|
||||||
onDataLoaded(result, !result.isEmpty());
|
onDataLoaded(result, !result.isEmpty());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -122,4 +123,10 @@ public class AccountTimelineFragment extends StatusListFragment{
|
|||||||
protected void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){
|
protected void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){
|
||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter.FilterContext getFilterContext() {
|
||||||
|
return Filter.FilterContext.ACCOUNT;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
|||||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
|
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
|
||||||
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
|
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.MediaGridLayout;
|
|
||||||
import org.joinmastodon.android.utils.TypedObjectPool;
|
import org.joinmastodon.android.utils.TypedObjectPool;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -62,7 +61,6 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
@@ -70,7 +68,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
|||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends BaseRecyclerFragment<T> implements PhotoViewerHost, ScrollableToTop{
|
public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends RecyclerFragment<T> implements PhotoViewerHost, ScrollableToTop, HasFab{
|
||||||
protected ArrayList<StatusDisplayItem> displayItems=new ArrayList<>();
|
protected ArrayList<StatusDisplayItem> displayItems=new ArrayList<>();
|
||||||
protected DisplayItemsAdapter adapter;
|
protected DisplayItemsAdapter adapter;
|
||||||
protected String accountID;
|
protected String accountID;
|
||||||
@@ -84,16 +82,17 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
|
|
||||||
public BaseStatusListFragment(){
|
public BaseStatusListFragment(){
|
||||||
super(20);
|
super(20);
|
||||||
if (withComposeButton()) setListLayoutId(R.layout.recycler_fragment_with_fab);
|
if (wantsComposeButton()) setListLayoutId(R.layout.recycler_fragment_with_fab);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean withComposeButton() {
|
protected boolean wantsComposeButton() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
UiUtils.loadMaxWidth(getContext());
|
||||||
if(GlobalUserPreferences.disableMarquee){
|
if(GlobalUserPreferences.disableMarquee){
|
||||||
setTitleMarqueeEnabled(false);
|
setTitleMarqueeEnabled(false);
|
||||||
setSubtitleMarqueeEnabled(false);
|
setSubtitleMarqueeEnabled(false);
|
||||||
@@ -102,8 +101,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RecyclerView.Adapter getAdapter(){
|
protected RecyclerView.Adapter getAdapter(){
|
||||||
return adapter=new DisplayItemsAdapter();
|
return adapter=new DisplayItemsAdapter();
|
||||||
@@ -271,37 +268,59 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable View getFab() {
|
||||||
|
if (getParentFragment() instanceof HasFab l) return l.getFab();
|
||||||
|
else return fab;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showFab() {
|
||||||
|
View fab = getFab();
|
||||||
|
if (fab == null || fab.getVisibility() == View.VISIBLE) return;
|
||||||
|
fab.setVisibility(View.VISIBLE);
|
||||||
|
TranslateAnimation animate = new TranslateAnimation(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
fab.getHeight() * 2,
|
||||||
|
0);
|
||||||
|
animate.setDuration(300);
|
||||||
|
fab.startAnimation(animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void hideFab() {
|
||||||
|
View fab = getFab();
|
||||||
|
if (fab == null || fab.getVisibility() != View.VISIBLE) return;
|
||||||
|
TranslateAnimation animate = new TranslateAnimation(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
fab.getHeight() * 2);
|
||||||
|
animate.setDuration(300);
|
||||||
|
fab.startAnimation(animate);
|
||||||
|
fab.setVisibility(View.INVISIBLE);
|
||||||
|
scrollDiff = 0;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
fab=view.findViewById(R.id.fab);
|
fab=view.findViewById(R.id.fab);
|
||||||
|
|
||||||
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
||||||
@Override
|
@Override
|
||||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
|
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
|
||||||
if(currentPhotoViewer!=null)
|
if(currentPhotoViewer!=null)
|
||||||
currentPhotoViewer.offsetView(-dx, -dy);
|
currentPhotoViewer.offsetView(-dx, -dy);
|
||||||
|
|
||||||
|
View fab = getFab();
|
||||||
if (fab!=null && GlobalUserPreferences.autoHideFab) {
|
if (fab!=null && GlobalUserPreferences.autoHideFab) {
|
||||||
if (dy > 0 && fab.getVisibility() == View.VISIBLE) {
|
if (dy > 0 && fab.getVisibility() == View.VISIBLE) {
|
||||||
TranslateAnimation animate = new TranslateAnimation(
|
hideFab();
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
fab.getHeight() * 2);
|
|
||||||
animate.setDuration(300);
|
|
||||||
fab.startAnimation(animate);
|
|
||||||
fab.setVisibility(View.INVISIBLE);
|
|
||||||
scrollDiff = 0;
|
|
||||||
} else if (dy < 0 && fab.getVisibility() != View.VISIBLE) {
|
} else if (dy < 0 && fab.getVisibility() != View.VISIBLE) {
|
||||||
if (list.getChildAt(0).getTop() == 0 || scrollDiff > 400) {
|
if (list.getChildAt(0).getTop() == 0 || scrollDiff > 400) {
|
||||||
fab.setVisibility(View.VISIBLE);
|
showFab();
|
||||||
TranslateAnimation animate = new TranslateAnimation(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
fab.getHeight() * 2,
|
|
||||||
0);
|
|
||||||
animate.setDuration(300);
|
|
||||||
fab.startAnimation(animate);
|
|
||||||
scrollDiff = 0;
|
scrollDiff = 0;
|
||||||
} else {
|
} else {
|
||||||
scrollDiff += Math.abs(dy);
|
scrollDiff += Math.abs(dy);
|
||||||
@@ -344,10 +363,12 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
((UsableRecyclerView) list).setIncludeMarginsInItemHitbox(true);
|
((UsableRecyclerView) list).setIncludeMarginsInItemHitbox(true);
|
||||||
updateToolbar();
|
updateToolbar();
|
||||||
|
|
||||||
if (withComposeButton()) {
|
if (wantsComposeButton() && !getArguments().getBoolean("__disable_fab", false)) {
|
||||||
fab.setVisibility(View.VISIBLE);
|
fab.setVisibility(View.VISIBLE);
|
||||||
fab.setOnClickListener(this::onFabClick);
|
fab.setOnClickListener(this::onFabClick);
|
||||||
fab.setOnLongClickListener(this::onFabLongClick);
|
fab.setOnLongClickListener(this::onFabLongClick);
|
||||||
|
} else if (fab != null) {
|
||||||
|
fab.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,13 +677,13 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
currentPhotoViewer.onPause();
|
currentPhotoViewer.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onFabClick(View v){
|
public void onFabClick(View v){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
Nav.go(getActivity(), ComposeFragment.class, args);
|
Nav.go(getActivity(), ComposeFragment.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean onFabLongClick(View v) {
|
public boolean onFabLongClick(View v) {
|
||||||
return UiUtils.pickAccountForCompose(getActivity(), accountID);
|
return UiUtils.pickAccountForCompose(getActivity(), accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -759,7 +780,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
if(!imgHolder.getItem().status.spoilerRevealed){
|
if(!imgHolder.getItem().status.spoilerRevealed){
|
||||||
if(TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
|
if(TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
|
||||||
int listWidth=getListWidthForMediaLayout();
|
int listWidth=getListWidthForMediaLayout();
|
||||||
int width=Math.min(listWidth, V.dp(MediaGridLayout.MAX_WIDTH));
|
int width=Math.min(listWidth, UiUtils.MAX_WIDTH);
|
||||||
if(currentMediaHiddenLayoutsWidth!=width)
|
if(currentMediaHiddenLayoutsWidth!=width)
|
||||||
rebuildMediaHiddenLayouts(width-V.dp(32));
|
rebuildMediaHiddenLayouts(width-V.dp(32));
|
||||||
c.save();
|
c.save();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.app.Activity;
|
|||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetBookmarkedStatuses;
|
import org.joinmastodon.android.api.requests.statuses.GetBookmarkedStatuses;
|
||||||
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
@@ -35,4 +36,9 @@ public class BookmarkedStatusListFragment extends StatusListFragment{
|
|||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter.FilterContext getFilterContext() {
|
||||||
|
return Filter.FilterContext.ACCOUNT;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ import org.joinmastodon.android.events.StatusCreatedEvent;
|
|||||||
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Attachment;
|
import org.joinmastodon.android.model.Attachment;
|
||||||
|
import org.joinmastodon.android.model.ContentType;
|
||||||
import org.joinmastodon.android.model.Emoji;
|
import org.joinmastodon.android.model.Emoji;
|
||||||
import org.joinmastodon.android.model.EmojiCategory;
|
import org.joinmastodon.android.model.EmojiCategory;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
@@ -179,8 +180,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
private int charCount, charLimit, trimmedCharCount;
|
private int charCount, charLimit, trimmedCharCount;
|
||||||
|
|
||||||
private Button publishButton, languageButton, scheduleTimeBtn, draftsBtn;
|
private Button publishButton, languageButton, scheduleTimeBtn, draftsBtn;
|
||||||
private PopupMenu languagePopup, visibilityPopup, draftOptionsPopup;
|
private PopupMenu languagePopup, contentTypePopup, visibilityPopup, draftOptionsPopup;
|
||||||
private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn, visibilityBtn, scheduleDraftDismiss;
|
private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn, visibilityBtn, scheduleDraftDismiss, contentTypeBtn;
|
||||||
private ImageView sensitiveIcon;
|
private ImageView sensitiveIcon;
|
||||||
private ComposeMediaLayout attachmentsView;
|
private ComposeMediaLayout attachmentsView;
|
||||||
private TextView replyText;
|
private TextView replyText;
|
||||||
@@ -234,6 +235,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
private Runnable updateUploadEtaRunnable;
|
private Runnable updateUploadEtaRunnable;
|
||||||
|
|
||||||
private String language, encoding;
|
private String language, encoding;
|
||||||
|
private ContentType contentType;
|
||||||
private MastodonLanguage.LanguageResolver languageResolver;
|
private MastodonLanguage.LanguageResolver languageResolver;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -242,6 +244,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
|
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
|
contentType = GlobalUserPreferences.accountsDefaultContentTypes.get(accountID);
|
||||||
|
if (contentType == null && GlobalUserPreferences.accountsWithContentTypesEnabled.contains(accountID)) {
|
||||||
|
// if formatting is enabled, use plain to avoid confusing unspecified default setting
|
||||||
|
contentType = ContentType.PLAIN;
|
||||||
|
}
|
||||||
|
|
||||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
self=session.self;
|
self=session.self;
|
||||||
instanceDomain=session.domain;
|
instanceDomain=session.domain;
|
||||||
@@ -330,6 +338,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
emojiBtn=view.findViewById(R.id.btn_emoji);
|
emojiBtn=view.findViewById(R.id.btn_emoji);
|
||||||
spoilerBtn=view.findViewById(R.id.btn_spoiler);
|
spoilerBtn=view.findViewById(R.id.btn_spoiler);
|
||||||
visibilityBtn=view.findViewById(R.id.btn_visibility);
|
visibilityBtn=view.findViewById(R.id.btn_visibility);
|
||||||
|
contentTypeBtn=view.findViewById(R.id.btn_content_type);
|
||||||
scheduleDraftView=view.findViewById(R.id.schedule_draft_view);
|
scheduleDraftView=view.findViewById(R.id.schedule_draft_view);
|
||||||
scheduleDraftText=view.findViewById(R.id.schedule_draft_text);
|
scheduleDraftText=view.findViewById(R.id.schedule_draft_text);
|
||||||
scheduleDraftDismiss=view.findViewById(R.id.schedule_draft_dismiss);
|
scheduleDraftDismiss=view.findViewById(R.id.schedule_draft_dismiss);
|
||||||
@@ -364,6 +373,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
visibilityBtn.setOnClickListener(v->visibilityPopup.show());
|
visibilityBtn.setOnClickListener(v->visibilityPopup.show());
|
||||||
visibilityBtn.setOnTouchListener(visibilityPopup.getDragToOpenListener());
|
visibilityBtn.setOnTouchListener(visibilityPopup.getDragToOpenListener());
|
||||||
|
|
||||||
|
buildContentTypePopup(contentTypeBtn);
|
||||||
|
contentTypeBtn.setOnClickListener(v->contentTypePopup.show());
|
||||||
|
contentTypeBtn.setOnTouchListener(contentTypePopup.getDragToOpenListener());
|
||||||
|
|
||||||
scheduleDraftDismiss.setOnClickListener(v->updateScheduledAt(null));
|
scheduleDraftDismiss.setOnClickListener(v->updateScheduledAt(null));
|
||||||
scheduleTimeBtn.setOnClickListener(v->pickScheduledDateTime());
|
scheduleTimeBtn.setOnClickListener(v->pickScheduledDateTime());
|
||||||
|
|
||||||
@@ -466,8 +479,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(editingStatus!=null && editingStatus.visibility!=null) {
|
if (savedInstanceState != null) {
|
||||||
statusVisibility=editingStatus.visibility;
|
statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility");
|
||||||
|
} else if (editingStatus != null && editingStatus.visibility != null) {
|
||||||
|
statusVisibility = editingStatus.visibility;
|
||||||
} else {
|
} else {
|
||||||
loadDefaultStatusVisibility(savedInstanceState);
|
loadDefaultStatusVisibility(savedInstanceState);
|
||||||
}
|
}
|
||||||
@@ -482,6 +497,20 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}).setChecked(true);
|
}).setChecked(true);
|
||||||
visibilityPopup.getMenu().findItem(R.id.local_only).setChecked(localOnly);
|
visibilityPopup.getMenu().findItem(R.id.local_only).setChecked(localOnly);
|
||||||
|
|
||||||
|
|
||||||
|
if (savedInstanceState != null && savedInstanceState.containsKey("contentType")) {
|
||||||
|
contentType = (ContentType) savedInstanceState.getSerializable("contentType");
|
||||||
|
} else if (getArguments().containsKey("sourceContentType")) {
|
||||||
|
try {
|
||||||
|
String val = getArguments().getString("sourceContentType");
|
||||||
|
contentType = val == null ? null : ContentType.valueOf(val);
|
||||||
|
} catch (IllegalArgumentException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
int contentTypeId = ContentType.getContentTypeRes(contentType);
|
||||||
|
contentTypePopup.getMenu().findItem(contentTypeId).setChecked(true);
|
||||||
|
contentTypeBtn.setSelected(contentTypeId != R.id.content_type_null && contentTypeId != R.id.content_type_plain);
|
||||||
|
|
||||||
autocompleteViewController=new ComposeAutocompleteViewController(getActivity(), accountID);
|
autocompleteViewController=new ComposeAutocompleteViewController(getActivity(), accountID);
|
||||||
autocompleteViewController.setCompletionSelectedListener(this::onAutocompleteOptionSelected);
|
autocompleteViewController.setCompletionSelectedListener(this::onAutocompleteOptionSelected);
|
||||||
View autocompleteView=autocompleteViewController.getView();
|
View autocompleteView=autocompleteViewController.getView();
|
||||||
@@ -518,6 +547,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
outState.putParcelableArrayList("attachments", serializedAttachments);
|
outState.putParcelableArrayList("attachments", serializedAttachments);
|
||||||
}
|
}
|
||||||
outState.putSerializable("visibility", statusVisibility);
|
outState.putSerializable("visibility", statusVisibility);
|
||||||
|
outState.putSerializable("contentType", contentType);
|
||||||
if (scheduledAt != null) outState.putSerializable("scheduledAt", scheduledAt);
|
if (scheduledAt != null) outState.putSerializable("scheduledAt", scheduledAt);
|
||||||
if (scheduledStatus != null) outState.putParcelable("scheduledStatus", Parcels.wrap(scheduledStatus));
|
if (scheduledStatus != null) outState.putParcelable("scheduledStatus", Parcels.wrap(scheduledStatus));
|
||||||
}
|
}
|
||||||
@@ -907,6 +937,17 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getContentTypeName(String id) {
|
||||||
|
return switch (id) {
|
||||||
|
case "text/plain" -> R.string.sk_content_type_plain;
|
||||||
|
case "text/html" -> R.string.sk_content_type_html;
|
||||||
|
case "text/markdown" -> R.string.sk_content_type_markdown;
|
||||||
|
case "text/bbcode" -> R.string.sk_content_type_bbcode;
|
||||||
|
case "text/x.misskeymarkdown" -> R.string.sk_content_type_mfm;
|
||||||
|
default -> throw new IllegalArgumentException("Invalid content type");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private void addBottomLanguage(Menu menu) {
|
private void addBottomLanguage(Menu menu) {
|
||||||
if (menu.findItem(allLanguages.size()) == null) {
|
if (menu.findItem(allLanguages.size()) == null) {
|
||||||
menu.add(0, allLanguages.size(), Menu.NONE, "bottom (\uD83E\uDD7A\uD83D\uDC49\uD83D\uDC48)");
|
menu.add(0, allLanguages.size(), Menu.NONE, "bottom (\uD83E\uDD7A\uD83D\uDC49\uD83D\uDC48)");
|
||||||
@@ -1053,6 +1094,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
req.visibility=localOnly && instance.pleroma != null ? StatusPrivacy.LOCAL : statusVisibility;
|
req.visibility=localOnly && instance.pleroma != null ? StatusPrivacy.LOCAL : statusVisibility;
|
||||||
req.sensitive=sensitive;
|
req.sensitive=sensitive;
|
||||||
req.language=language;
|
req.language=language;
|
||||||
|
req.contentType=contentType;
|
||||||
req.scheduledAt = scheduledAt;
|
req.scheduledAt = scheduledAt;
|
||||||
if(!attachments.isEmpty()){
|
if(!attachments.isEmpty()){
|
||||||
req.mediaIds=attachments.stream().map(a->a.serverAttachment.id).collect(Collectors.toList());
|
req.mediaIds=attachments.stream().map(a->a.serverAttachment.id).collect(Collectors.toList());
|
||||||
@@ -1547,7 +1589,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
if(att.isUploadingOrProcessing())
|
if(att.isUploadingOrProcessing())
|
||||||
att.cancelUpload();
|
att.cancelUpload();
|
||||||
attachments.remove(att);
|
attachments.remove(att);
|
||||||
uploadNextQueuedAttachment();
|
if(!areThereAnyUploadingAttachments())
|
||||||
|
uploadNextQueuedAttachment();
|
||||||
attachmentsView.removeView(att.view);
|
attachmentsView.removeView(att.view);
|
||||||
if(getMediaAttachmentsCount()==0)
|
if(getMediaAttachmentsCount()==0)
|
||||||
attachmentsView.setVisibility(View.GONE);
|
attachmentsView.setVisibility(View.GONE);
|
||||||
@@ -1894,14 +1937,36 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
private void buildContentTypePopup(View btn) {
|
||||||
|
contentTypePopup=new PopupMenu(getActivity(), btn);
|
||||||
|
contentTypePopup.inflate(R.menu.compose_content_type);
|
||||||
|
Menu m = contentTypePopup.getMenu();
|
||||||
|
ContentType.adaptMenuToInstance(m, instance);
|
||||||
|
if (contentType != null) m.findItem(R.id.content_type_null).setVisible(false);
|
||||||
|
|
||||||
|
contentTypePopup.setOnMenuItemClickListener(i->{
|
||||||
|
int id=i.getItemId();
|
||||||
|
if (id == R.id.content_type_null) contentType = null;
|
||||||
|
else if (id == R.id.content_type_plain) contentType = ContentType.PLAIN;
|
||||||
|
else if (id == R.id.content_type_html) contentType = ContentType.HTML;
|
||||||
|
else if (id == R.id.content_type_markdown) contentType = ContentType.MARKDOWN;
|
||||||
|
else if (id == R.id.content_type_bbcode) contentType = ContentType.BBCODE;
|
||||||
|
else if (id == R.id.content_type_misskey_markdown) contentType = ContentType.MISSKEY_MARKDOWN;
|
||||||
|
else return false;
|
||||||
|
btn.setSelected(id != R.id.content_type_null && id != R.id.content_type_plain);
|
||||||
|
i.setChecked(true);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!GlobalUserPreferences.accountsWithContentTypesEnabled.contains(accountID)) {
|
||||||
|
btn.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void loadDefaultStatusVisibility(Bundle savedInstanceState) {
|
private void loadDefaultStatusVisibility(Bundle savedInstanceState) {
|
||||||
if(replyTo != null) statusVisibility = replyTo.visibility;
|
if(replyTo != null) statusVisibility = replyTo.visibility;
|
||||||
|
|
||||||
// A saved privacy setting from a previous compose session wins over the reply visibility
|
|
||||||
if(savedInstanceState !=null){
|
|
||||||
statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility");
|
|
||||||
}
|
|
||||||
|
|
||||||
AccountSessionManager asm = AccountSessionManager.getInstance();
|
AccountSessionManager asm = AccountSessionManager.getInstance();
|
||||||
Preferences prefs = asm.getAccount(accountID).preferences;
|
Preferences prefs = asm.getAccount(accountID).preferences;
|
||||||
if (prefs != null) {
|
if (prefs != null) {
|
||||||
|
|||||||
@@ -47,11 +47,10 @@ import java.util.Map;
|
|||||||
|
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class EditTimelinesFragment extends BaseRecyclerFragment<TimelineDefinition> implements ScrollableToTop {
|
public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition> implements ScrollableToTop {
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private TimelinesAdapter adapter;
|
private TimelinesAdapter adapter;
|
||||||
private final ItemTouchHelper itemTouchHelper;
|
private final ItemTouchHelper itemTouchHelper;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.app.Activity;
|
|||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetFavoritedStatuses;
|
import org.joinmastodon.android.api.requests.statuses.GetFavoritedStatuses;
|
||||||
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
@@ -35,4 +36,9 @@ public class FavoritedStatusListFragment extends StatusListFragment{
|
|||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter.FilterContext getFilterContext() {
|
||||||
|
return Filter.FilterContext.ACCOUNT;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import me.grishka.appkit.Nav;
|
|||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
@@ -47,7 +46,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
|||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class FollowRequestsListFragment extends BaseRecyclerFragment<FollowRequestsListFragment.AccountWrapper> implements ScrollableToTop{
|
public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsListFragment.AccountWrapper> implements ScrollableToTop{
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private Map<String, Relationship> relationships=Collections.emptyMap();
|
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||||
private GetAccountRelationships relationshipsRequest;
|
private GetAccountRelationships relationshipsRequest;
|
||||||
|
|||||||
@@ -16,11 +16,10 @@ import org.joinmastodon.android.ui.DividerItemDecoration;
|
|||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class FollowedHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop {
|
public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implements ScrollableToTop {
|
||||||
private String nextMaxID;
|
private String nextMaxID;
|
||||||
private String accountId;
|
private String accountId;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
public interface HasFab {
|
||||||
|
View getFab();
|
||||||
|
void showFab();
|
||||||
|
void hideFab();
|
||||||
|
}
|
||||||
@@ -39,7 +39,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
|||||||
private MenuItem followButton;
|
private MenuItem followButton;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean withComposeButton() {
|
protected boolean wantsComposeButton() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
if (getActivity() == null) return;
|
if (getActivity() == null) return;
|
||||||
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList());
|
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
|
||||||
onDataLoaded(result, !result.isEmpty());
|
onDataLoaded(result, !result.isEmpty());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -138,12 +138,12 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onFabLongClick(View v) {
|
public boolean onFabLongClick(View v) {
|
||||||
return UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtag+' ');
|
return UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtag+' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onFabClick(View v){
|
public void onFabClick(View v){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putString("prefilledText", '#'+hashtag+' ');
|
args.putString("prefilledText", '#'+hashtag+' ');
|
||||||
@@ -154,4 +154,9 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
|||||||
protected void onSetFabBottomInset(int inset){
|
protected void onSetFabBottomInset(int inset){
|
||||||
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(24)+inset;
|
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(24)+inset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter.FilterContext getFilterContext() {
|
||||||
|
return Filter.FilterContext.PUBLIC;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,21 +16,34 @@ import android.widget.FrameLayout;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.events.AllNotificationsSeenEvent;
|
||||||
|
import org.joinmastodon.android.events.NotificationReceivedEvent;
|
||||||
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
|
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.Instance;
|
||||||
|
import org.joinmastodon.android.model.Notification;
|
||||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.TabBar;
|
import org.joinmastodon.android.ui.views.TabBar;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import androidx.annotation.IdRes;
|
import androidx.annotation.IdRes;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.squareup.otto.Subscribe;
|
||||||
|
|
||||||
import me.grishka.appkit.FragmentStackActivity;
|
import me.grishka.appkit.FragmentStackActivity;
|
||||||
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.fragments.AppKitFragment;
|
import me.grishka.appkit.fragments.AppKitFragment;
|
||||||
import me.grishka.appkit.fragments.LoaderFragment;
|
import me.grishka.appkit.fragments.LoaderFragment;
|
||||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||||
@@ -48,6 +61,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
private TabBar tabBar;
|
private TabBar tabBar;
|
||||||
private View tabBarWrap;
|
private View tabBarWrap;
|
||||||
private ImageView tabBarAvatar;
|
private ImageView tabBarAvatar;
|
||||||
|
private ImageView notificationTabIcon;
|
||||||
@IdRes
|
@IdRes
|
||||||
private int currentTab=R.id.tab_home;
|
private int currentTab=R.id.tab_home;
|
||||||
|
|
||||||
@@ -56,6 +70,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
E.register(this);
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
setTitle(R.string.sk_app_name);
|
setTitle(R.string.sk_app_name);
|
||||||
|
|
||||||
@@ -89,7 +104,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
content.setOrientation(LinearLayout.VERTICAL);
|
content.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
|
||||||
FrameLayout fragmentContainer=new FrameLayout(getActivity());
|
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));
|
content.addView(fragmentContainer, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
|
||||||
|
|
||||||
inflater.inflate(R.layout.tab_bar, content);
|
inflater.inflate(R.layout.tab_bar, content);
|
||||||
@@ -108,12 +123,15 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
Account self=AccountSessionManager.getInstance().getAccount(accountID).self;
|
Account self=AccountSessionManager.getInstance().getAccount(accountID).self;
|
||||||
ViewImageLoader.load(tabBarAvatar, null, new UrlImageLoaderRequest(self.avatar, V.dp(28), V.dp(28)));
|
ViewImageLoader.load(tabBarAvatar, null, new UrlImageLoaderRequest(self.avatar, V.dp(28), V.dp(28)));
|
||||||
|
|
||||||
|
notificationTabIcon=content.findViewById(R.id.tab_notifications);
|
||||||
|
updateNotificationBadge();
|
||||||
|
|
||||||
if(savedInstanceState==null){
|
if(savedInstanceState==null){
|
||||||
getChildFragmentManager().beginTransaction()
|
getChildFragmentManager().beginTransaction()
|
||||||
.add(R.id.fragment_wrap, homeTabFragment)
|
.add(me.grishka.appkit.R.id.fragment_wrap, homeTabFragment)
|
||||||
.add(R.id.fragment_wrap, searchFragment).hide(searchFragment)
|
.add(me.grishka.appkit.R.id.fragment_wrap, searchFragment).hide(searchFragment)
|
||||||
.add(R.id.fragment_wrap, notificationsFragment).hide(notificationsFragment)
|
.add(me.grishka.appkit.R.id.fragment_wrap, notificationsFragment).hide(notificationsFragment)
|
||||||
.add(R.id.fragment_wrap, profileFragment).hide(profileFragment)
|
.add(me.grishka.appkit.R.id.fragment_wrap, profileFragment).hide(profileFragment)
|
||||||
.commit();
|
.commit();
|
||||||
|
|
||||||
String defaultTab=getArguments().getString("tab");
|
String defaultTab=getArguments().getString("tab");
|
||||||
@@ -142,6 +160,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
notificationsFragment=(NotificationsFragment) getChildFragmentManager().getFragment(savedInstanceState, "notificationsFragment");
|
notificationsFragment=(NotificationsFragment) getChildFragmentManager().getFragment(savedInstanceState, "notificationsFragment");
|
||||||
profileFragment=(ProfileFragment) getChildFragmentManager().getFragment(savedInstanceState, "profileFragment");
|
profileFragment=(ProfileFragment) getChildFragmentManager().getFragment(savedInstanceState, "profileFragment");
|
||||||
currentTab=savedInstanceState.getInt("selectedTab");
|
currentTab=savedInstanceState.getInt("selectedTab");
|
||||||
|
tabBar.selectTab(currentTab);
|
||||||
Fragment current=fragmentForTab(currentTab);
|
Fragment current=fragmentForTab(currentTab);
|
||||||
getChildFragmentManager().beginTransaction()
|
getChildFragmentManager().beginTransaction()
|
||||||
.hide(homeTabFragment)
|
.hide(homeTabFragment)
|
||||||
@@ -209,6 +228,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
}
|
}
|
||||||
getChildFragmentManager().beginTransaction().hide(fragmentForTab(currentTab)).show(newFragment).commit();
|
getChildFragmentManager().beginTransaction().hide(fragmentForTab(currentTab)).show(newFragment).commit();
|
||||||
maybeTriggerLoading(newFragment);
|
maybeTriggerLoading(newFragment);
|
||||||
|
if (newFragment instanceof HasFab fabulous) fabulous.showFab();
|
||||||
currentTab=tab;
|
currentTab=tab;
|
||||||
((FragmentStackActivity)getActivity()).invalidateSystemBarColors(this);
|
((FragmentStackActivity)getActivity()).invalidateSystemBarColors(this);
|
||||||
}
|
}
|
||||||
@@ -267,4 +287,47 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
if (notificationsFragment.isAdded()) getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment);
|
if (notificationsFragment.isAdded()) getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment);
|
||||||
if (profileFragment.isAdded()) getChildFragmentManager().putFragment(outState, "profileFragment", profileFragment);
|
if (profileFragment.isAdded()) getChildFragmentManager().putFragment(outState, "profileFragment", profileFragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateNotificationBadge() {
|
||||||
|
AccountSession session = AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
|
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
|
||||||
|
if (instance == null) return;
|
||||||
|
|
||||||
|
new GetNotifications(null, 1, EnumSet.allOf(Notification.Type.class), instance != null && instance.pleroma != null)
|
||||||
|
.setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<Notification> notifications) {
|
||||||
|
if (notifications.size() > 0) {
|
||||||
|
try {
|
||||||
|
long newestId = Long.parseLong(notifications.get(0).id);
|
||||||
|
long lastSeenId = Long.parseLong(session.markers.notifications.lastReadId);
|
||||||
|
setNotificationBadge(newestId > lastSeenId);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
setNotificationBadge(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
setNotificationBadge(false);
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotificationBadge(boolean badge) {
|
||||||
|
notificationTabIcon.setImageResource(badge
|
||||||
|
? R.drawable.ic_fluent_alert_28_selector_badged
|
||||||
|
: R.drawable.ic_fluent_alert_28_selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onNotificationReceived(NotificationReceivedEvent notificationReceivedEvent) {
|
||||||
|
if (notificationReceivedEvent.account.equals(accountID)) setNotificationBadge(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onAllNotificationsSeen(AllNotificationsSeenEvent allNotificationsSeenEvent) {
|
||||||
|
setNotificationBadge(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import android.view.ViewParent;
|
|||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.PopupMenu;
|
import android.widget.PopupMenu;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -70,7 +71,7 @@ import me.grishka.appkit.fragments.OnBackPressedListener;
|
|||||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener {
|
public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener, HasFab {
|
||||||
private static final int ANNOUNCEMENTS_RESULT = 654;
|
private static final int ANNOUNCEMENTS_RESULT = 654;
|
||||||
|
|
||||||
private String accountID;
|
private String accountID;
|
||||||
@@ -98,6 +99,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
private PopupMenu overflowPopup;
|
private PopupMenu overflowPopup;
|
||||||
private View overflowActionView = null;
|
private View overflowActionView = null;
|
||||||
private boolean announcementsBadged, settingsBadged;
|
private boolean announcementsBadged, settingsBadged;
|
||||||
|
private ImageButton fab;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -122,6 +124,10 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
@Override
|
@Override
|
||||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||||
FrameLayout view = new FrameLayout(getContext());
|
FrameLayout view = new FrameLayout(getContext());
|
||||||
|
inflater.inflate(R.layout.compose_fab, view);
|
||||||
|
fab = view.findViewById(R.id.fab);
|
||||||
|
fab.setOnClickListener(this::onFabClick);
|
||||||
|
fab.setOnLongClickListener(this::onFabLongClick);
|
||||||
pager = new ViewPager2(getContext());
|
pager = new ViewPager2(getContext());
|
||||||
toolbarFrame = (FrameLayout) LayoutInflater.from(getContext()).inflate(R.layout.home_toolbar, getToolbar(), false);
|
toolbarFrame = (FrameLayout) LayoutInflater.from(getContext()).inflate(R.layout.home_toolbar, getToolbar(), false);
|
||||||
|
|
||||||
@@ -129,6 +135,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putBoolean("__is_tab", true);
|
args.putBoolean("__is_tab", true);
|
||||||
|
args.putBoolean("__disable_fab", true);
|
||||||
args.putBoolean("onlyPosts", true);
|
args.putBoolean("onlyPosts", true);
|
||||||
|
|
||||||
for (int i = 0; i < timelineDefinitions.size(); i++) {
|
for (int i = 0; i < timelineDefinitions.size(); i++) {
|
||||||
@@ -280,6 +287,20 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
}).exec(accountID);
|
}).exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onFabClick(View v){
|
||||||
|
if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment<?> l) {
|
||||||
|
l.onFabClick(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onFabLongClick(View v) {
|
||||||
|
if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment<?> l) {
|
||||||
|
return l.onFabLongClick(v);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void addListsToOverflowMenu() {
|
private void addListsToOverflowMenu() {
|
||||||
Context ctx = getContext();
|
Context ctx = getContext();
|
||||||
listsMenu.clear();
|
listsMenu.clear();
|
||||||
@@ -427,9 +448,20 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
updateSwitcherIcon(i);
|
updateSwitcherIcon(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showFab() {
|
||||||
|
if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment<?> l) l.showFab();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void hideFab() {
|
||||||
|
if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment<?> l) l.hideFab();
|
||||||
|
}
|
||||||
|
|
||||||
private void updateSwitcherIcon(int i) {
|
private void updateSwitcherIcon(int i) {
|
||||||
timelineIcon.setImageResource(timelines[i].getIcon().iconRes);
|
timelineIcon.setImageResource(timelines[i].getIcon().iconRes);
|
||||||
timelineTitle.setText(timelines[i].getTitle(getContext()));
|
timelineTitle.setText(timelines[i].getTitle(getContext()));
|
||||||
|
showFab();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -657,6 +689,10 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
return hashtagsItems.values();
|
return hashtagsItems.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ImageButton getFab() {
|
||||||
|
return fab;
|
||||||
|
}
|
||||||
|
|
||||||
private class HomePagerAdapter extends RecyclerView.Adapter<SimpleViewHolder> {
|
private class HomePagerAdapter extends RecyclerView.Adapter<SimpleViewHolder> {
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.E;
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
@@ -38,7 +36,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
private String lastSavedMarkerID;
|
private String lastSavedMarkerID;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean withComposeButton() {
|
protected boolean wantsComposeButton() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +148,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
result.get(result.size()-1).hasGapAfter=true;
|
result.get(result.size()-1).hasGapAfter=true;
|
||||||
toAdd=result;
|
toAdd=result;
|
||||||
}
|
}
|
||||||
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, Filter.FilterContext.HOME);
|
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, getFilterContext());
|
||||||
toAdd=toAdd.stream().filter(filterPredicate).collect(Collectors.toList());
|
toAdd=toAdd.stream().filter(filterPredicate).collect(Collectors.toList());
|
||||||
if(!toAdd.isEmpty()){
|
if(!toAdd.isEmpty()){
|
||||||
prependItems(toAdd, true);
|
prependItems(toAdd, true);
|
||||||
@@ -166,6 +164,10 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
|
|
||||||
|
if (parent.getParentFragment() instanceof HomeFragment homeFragment) {
|
||||||
|
homeFragment.updateNotificationBadge();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -225,7 +227,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
||||||
targetList.clear();
|
targetList.clear();
|
||||||
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
||||||
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, Filter.FilterContext.HOME);
|
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, getFilterContext());
|
||||||
for(Status s:result){
|
for(Status s:result){
|
||||||
if(idsBelowGap.contains(s.id))
|
if(idsBelowGap.contains(s.id))
|
||||||
break;
|
break;
|
||||||
@@ -278,4 +280,9 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
protected boolean shouldRemoveAccountPostsWhenUnfollowing(){
|
protected boolean shouldRemoveAccountPostsWhenUnfollowing(){
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter.FilterContext getFilterContext() {
|
||||||
|
return Filter.FilterContext.HOME;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
|||||||
private ListTimeline.RepliesPolicy repliesPolicy;
|
private ListTimeline.RepliesPolicy repliesPolicy;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean withComposeButton() {
|
protected boolean wantsComposeButton() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result) {
|
public void onSuccess(List<Status> result) {
|
||||||
if (getActivity() == null) return;
|
if (getActivity() == null) return;
|
||||||
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.HOME)).collect(Collectors.toList());
|
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
|
||||||
onDataLoaded(result, !result.isEmpty());
|
onDataLoaded(result, !result.isEmpty());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -152,7 +152,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onFabClick(View v){
|
public void onFabClick(View v){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
Nav.go(getActivity(), ComposeFragment.class, args);
|
Nav.go(getActivity(), ComposeFragment.class, args);
|
||||||
@@ -162,4 +162,10 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
|||||||
protected void onSetFabBottomInset(int inset) {
|
protected void onSetFabBottomInset(int inset) {
|
||||||
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(24)+inset;
|
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(24)+inset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter.FilterContext getFilterContext() {
|
||||||
|
return Filter.FilterContext.HOME;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,11 +37,10 @@ import me.grishka.appkit.Nav;
|
|||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> implements ScrollableToTop {
|
public class ListTimelinesFragment extends RecyclerFragment<ListTimeline> implements ScrollableToTop {
|
||||||
private String accountId;
|
private String accountId;
|
||||||
private String profileAccountId;
|
private String profileAccountId;
|
||||||
private final HashMap<String, Boolean> userInListBefore = new HashMap<>();
|
private final HashMap<String, Boolean> userInListBefore = new HashMap<>();
|
||||||
|
|||||||
@@ -11,17 +11,20 @@ import org.joinmastodon.android.E;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.events.AllNotificationsSeenEvent;
|
||||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||||
|
import org.joinmastodon.android.model.Emoji;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
import org.joinmastodon.android.model.PaginatedResponse;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||||
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
@@ -46,8 +49,8 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
private final DiscoverInfoBannerHelper bannerHelper = new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.POST_NOTIFICATIONS);
|
private final DiscoverInfoBannerHelper bannerHelper = new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.POST_NOTIFICATIONS);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean withComposeButton() {
|
protected boolean wantsComposeButton() {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -81,6 +84,13 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
protected List<StatusDisplayItem> buildDisplayItems(Notification n){
|
protected List<StatusDisplayItem> buildDisplayItems(Notification n){
|
||||||
Account reportTarget = n.report == null ? null : n.report.targetAccount == null ? null :
|
Account reportTarget = n.report == null ? null : n.report.targetAccount == null ? null :
|
||||||
n.report.targetAccount;
|
n.report.targetAccount;
|
||||||
|
Emoji emoji = new Emoji();
|
||||||
|
if(n.emojiUrl!=null){
|
||||||
|
emoji.shortcode=n.emoji.substring(1,n.emoji.length()-1);
|
||||||
|
emoji.url=n.emojiUrl;
|
||||||
|
emoji.staticUrl=n.emojiUrl;
|
||||||
|
emoji.visibleInPicker=false;
|
||||||
|
}
|
||||||
String extraText=switch(n.type){
|
String extraText=switch(n.type){
|
||||||
case FOLLOW -> getString(R.string.user_followed_you);
|
case FOLLOW -> getString(R.string.user_followed_you);
|
||||||
case FOLLOW_REQUEST -> getString(R.string.user_sent_follow_request);
|
case FOLLOW_REQUEST -> getString(R.string.user_sent_follow_request);
|
||||||
@@ -91,8 +101,10 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
case UPDATE -> getString(R.string.sk_post_edited);
|
case UPDATE -> getString(R.string.sk_post_edited);
|
||||||
case SIGN_UP -> getString(R.string.sk_signed_up);
|
case SIGN_UP -> getString(R.string.sk_signed_up);
|
||||||
case REPORT -> getString(R.string.sk_reported);
|
case REPORT -> getString(R.string.sk_reported);
|
||||||
|
case REACTION, PLEROMA_EMOJI_REACTION ->
|
||||||
|
n.emoji != null ? getString(R.string.sk_reacted_with, n.emoji) : getString(R.string.sk_reacted);
|
||||||
};
|
};
|
||||||
HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, n.status, extraText, n, null) : null;
|
HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, n.status, n.emojiUrl!=null ? HtmlParser.parseCustomEmoji(extraText, Collections.singletonList(emoji)) : extraText, n, null) : null;
|
||||||
if(n.status!=null){
|
if(n.status!=null){
|
||||||
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n, false, Filter.FilterContext.NOTIFICATIONS);
|
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n, false, Filter.FilterContext.NOTIFICATIONS);
|
||||||
if(titleItem!=null)
|
if(titleItem!=null)
|
||||||
@@ -127,7 +139,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
.getAccount(accountID).getCacheController()
|
.getAccount(accountID).getCacheController()
|
||||||
.getNotifications(offset>0 ? maxID : null, count, onlyMentions, onlyPosts, refreshing, new SimpleCallback<>(this){
|
.getNotifications(offset>0 ? maxID : null, count, onlyMentions, onlyPosts, refreshing, new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(PaginatedResponse<List<Notification>> result){
|
public void onSuccess(CacheablePaginatedResponse<List<Notification>> result){
|
||||||
if (getActivity() == null) return;
|
if (getActivity() == null) return;
|
||||||
if(refreshing)
|
if(refreshing)
|
||||||
relationships.clear();
|
relationships.clear();
|
||||||
@@ -139,8 +151,13 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
loadRelationships(needRelationships);
|
loadRelationships(needRelationships);
|
||||||
maxID=result.maxID;
|
maxID=result.maxID;
|
||||||
|
|
||||||
if(offset==0 && !result.items.isEmpty()){
|
if(offset==0 && !result.items.isEmpty() && !result.isFromCache()){
|
||||||
|
E.post(new AllNotificationsSeenEvent());
|
||||||
new SaveMarkers(null, result.items.get(0).id).exec(accountID);
|
new SaveMarkers(null, result.items.get(0).id).exec(accountID);
|
||||||
|
if (AccountSessionManager.getInstance().getAccount(accountID).markers != null)
|
||||||
|
AccountSessionManager.getInstance().getAccount(accountID).markers
|
||||||
|
.notifications.lastReadId = result.items.get(0).id;
|
||||||
|
AccountSessionManager.getInstance().writeAccountsFile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -190,7 +207,6 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
list.addItemDecoration(new InsetStatusItemDecoration(this));
|
list.addItemDecoration(new InsetStatusItemDecoration(this));
|
||||||
if (getParentFragment() instanceof NotificationsFragment) fab.setVisibility(View.GONE);
|
|
||||||
if (onlyPosts) bannerHelper.maybeAddBanner(contentWrap);
|
if (onlyPosts) bannerHelper.maybeAddBanner(contentWrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
|
|||||||
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState){
|
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState){
|
||||||
super.onSelectedChanged(viewHolder, actionState);
|
super.onSelectedChanged(viewHolder, actionState);
|
||||||
if(actionState==ItemTouchHelper.ACTION_STATE_DRAG){
|
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();
|
viewHolder.itemView.animate().translationZ(V.dp(1)).setDuration(200).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
||||||
draggedViewHolder=viewHolder;
|
draggedViewHolder=viewHolder;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,11 @@ import android.os.Bundle;
|
|||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.style.ImageSpan;
|
import android.text.style.ImageSpan;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.SubMenu;
|
import android.view.SubMenu;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -112,7 +110,7 @@ import me.grishka.appkit.utils.CubicBezierInterpolator;
|
|||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class ProfileFragment extends LoaderFragment implements OnBackPressedListener, ScrollableToTop{
|
public class ProfileFragment extends LoaderFragment implements OnBackPressedListener, ScrollableToTop, HasFab{
|
||||||
private static final int AVATAR_RESULT=722;
|
private static final int AVATAR_RESULT=722;
|
||||||
private static final int COVER_RESULT=343;
|
private static final int COVER_RESULT=343;
|
||||||
|
|
||||||
@@ -152,7 +150,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
private WindowInsets childInsets;
|
private WindowInsets childInsets;
|
||||||
private PhotoViewer currentPhotoViewer;
|
private PhotoViewer currentPhotoViewer;
|
||||||
private boolean editModeLoading;
|
private boolean editModeLoading;
|
||||||
protected int scrollDiff = 0;
|
|
||||||
|
|
||||||
private static final int MAX_FIELDS=4;
|
private static final int MAX_FIELDS=4;
|
||||||
|
|
||||||
@@ -439,6 +436,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
toolbarTitleView.setTranslationY(titleTransY);
|
toolbarTitleView.setTranslationY(titleTransY);
|
||||||
toolbarSubtitleView.setTranslationY(titleTransY);
|
toolbarSubtitleView.setTranslationY(titleTransY);
|
||||||
}
|
}
|
||||||
|
RecyclerFragment.setRefreshLayoutColors(refreshLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -760,6 +758,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
return fab;
|
return fab;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showFab() {
|
||||||
|
if (getFragmentForPage(pager.getCurrentItem()) instanceof HasFab fabulous) fabulous.showFab();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void hideFab() {
|
||||||
|
if (getFragmentForPage(pager.getCurrentItem()) instanceof HasFab fabulous) fabulous.hideFab();
|
||||||
|
}
|
||||||
|
|
||||||
private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
|
private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
|
||||||
int topBarsH=getToolbar().getHeight()+statusBarHeight;
|
int topBarsH=getToolbar().getHeight()+statusBarHeight;
|
||||||
if(scrollY>avatarBorder.getTop()-topBarsH){
|
if(scrollY>avatarBorder.getTop()-topBarsH){
|
||||||
@@ -790,35 +798,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
if(currentPhotoViewer!=null){
|
if(currentPhotoViewer!=null){
|
||||||
currentPhotoViewer.offsetView(0, oldScrollY-scrollY);
|
currentPhotoViewer.offsetView(0, oldScrollY-scrollY);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GlobalUserPreferences.autoHideFab) {
|
|
||||||
int dy = scrollY - oldScrollY;
|
|
||||||
if (dy > 0 && fab.getVisibility() == View.VISIBLE) {
|
|
||||||
TranslateAnimation animate = new TranslateAnimation(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
fab.getHeight() * 2);
|
|
||||||
animate.setDuration(300);
|
|
||||||
fab.startAnimation(animate);
|
|
||||||
fab.setVisibility(View.INVISIBLE);
|
|
||||||
scrollDiff = 0;
|
|
||||||
} else if (dy < 0 && fab.getVisibility() != View.VISIBLE) {
|
|
||||||
if (v.getScrollY() == 0 || scrollDiff > 400) {
|
|
||||||
fab.setVisibility(View.VISIBLE);
|
|
||||||
TranslateAnimation animate = new TranslateAnimation(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
fab.getHeight() * 2,
|
|
||||||
0);
|
|
||||||
animate.setDuration(300);
|
|
||||||
fab.startAnimation(animate);
|
|
||||||
scrollDiff = 0;
|
|
||||||
} else {
|
|
||||||
scrollDiff += Math.abs(dy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Fragment getFragmentForPage(int page){
|
private Fragment getFragmentForPage(int page){
|
||||||
@@ -1374,7 +1353,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState){
|
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState){
|
||||||
super.onSelectedChanged(viewHolder, actionState);
|
super.onSelectedChanged(viewHolder, actionState);
|
||||||
if(actionState==ItemTouchHelper.ACTION_STATE_DRAG){
|
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();
|
viewHolder.itemView.animate().translationZ(V.dp(1)).setDuration(200).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
||||||
draggedViewHolder=viewHolder;
|
draggedViewHolder=viewHolder;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class RecyclerFragment<T> extends BaseRecyclerFragment<T> {
|
||||||
|
public RecyclerFragment(int perPage) {
|
||||||
|
super(perPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RecyclerFragment(int layout, int perPage) {
|
||||||
|
super(layout, perPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
if (refreshLayout != null) setRefreshLayoutColors(refreshLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setRefreshLayoutColors(SwipeRefreshLayout l) {
|
||||||
|
List<Integer> colors = new ArrayList<>(Arrays.asList(
|
||||||
|
R.color.primary_600,
|
||||||
|
R.color.red_primary_600,
|
||||||
|
R.color.green_primary_600,
|
||||||
|
R.color.blue_primary_600,
|
||||||
|
R.color.purple_600
|
||||||
|
));
|
||||||
|
int primary = UiUtils.getThemeColorRes(l.getContext(), R.attr.colorPrimary600);
|
||||||
|
if (!colors.contains(primary)) colors.add(0, primary);
|
||||||
|
int offset = colors.indexOf(primary);
|
||||||
|
int[] sorted = new int[colors.size()];
|
||||||
|
for (int i = 0; i < colors.size(); i++) {
|
||||||
|
sorted[i] = colors.get((i + offset) % colors.size());
|
||||||
|
}
|
||||||
|
l.setColorSchemeResources(sorted);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
|||||||
private static final int SCHEDULED_STATUS_LIST_OPENED = 161;
|
private static final int SCHEDULED_STATUS_LIST_OPENED = 161;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean withComposeButton() {
|
protected boolean wantsComposeButton() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onFabClick(View v) {
|
public void onFabClick(View v) {
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putSerializable("scheduledAt", CreateStatus.getDraftInstant());
|
args.putSerializable("scheduledAt", CreateStatus.getDraftInstant());
|
||||||
@@ -64,7 +64,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onFabLongClick(View v) {
|
public boolean onFabLongClick(View v) {
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putSerializable("scheduledAt", CreateStatus.getDraftInstant());
|
args.putSerializable("scheduledAt", CreateStatus.getDraftInstant());
|
||||||
@@ -79,7 +79,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(ScheduledStatus s) {
|
protected List<StatusDisplayItem> buildDisplayItems(ScheduledStatus s) {
|
||||||
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, null, true);
|
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, null, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -96,6 +96,8 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
|||||||
args.putString("sourceText", status.text);
|
args.putString("sourceText", status.text);
|
||||||
args.putString("sourceSpoiler", status.spoilerText);
|
args.putString("sourceSpoiler", status.spoilerText);
|
||||||
args.putBoolean("redraftStatus", true);
|
args.putBoolean("redraftStatus", true);
|
||||||
|
args.putString("sourceContentType", scheduledStatus.params.contentType != null ?
|
||||||
|
scheduledStatus.params.contentType.name() : null);
|
||||||
setResult(true, null);
|
setResult(true, null);
|
||||||
|
|
||||||
// closing this scheduled status list if another status list is opened from compose fragment
|
// closing this scheduled status list if another status list is opened from compose fragment
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import android.util.LruCache;
|
|||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -48,6 +49,7 @@ import org.joinmastodon.android.api.session.AccountSession;
|
|||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||||
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
|
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
|
||||||
|
import org.joinmastodon.android.model.ContentType;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
||||||
import org.joinmastodon.android.model.PushNotification;
|
import org.joinmastodon.android.model.PushNotification;
|
||||||
@@ -64,6 +66,7 @@ import java.util.function.Consumer;
|
|||||||
|
|
||||||
import androidx.annotation.DrawableRes;
|
import androidx.annotation.DrawableRes;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
@@ -80,7 +83,8 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
private ArrayList<Item> items=new ArrayList<>();
|
private ArrayList<Item> items=new ArrayList<>();
|
||||||
private ThemeItem themeItem;
|
private ThemeItem themeItem;
|
||||||
private NotificationPolicyItem notificationPolicyItem;
|
private NotificationPolicyItem notificationPolicyItem;
|
||||||
private SwitchItem showNewPostsButtonItem, glitchModeItem, compactReblogReplyLineItem;
|
private SwitchItem showNewPostsItem, glitchModeItem, compactReblogReplyLineItem;
|
||||||
|
private ButtonItem defaultContentTypeButtonItem;
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private boolean needUpdateNotificationSettings;
|
private boolean needUpdateNotificationSettings;
|
||||||
private boolean needAppRestart;
|
private boolean needAppRestart;
|
||||||
@@ -89,7 +93,9 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
private ImageView themeTransitionWindowView;
|
private ImageView themeTransitionWindowView;
|
||||||
private TextItem checkForUpdateItem, clearImageCacheItem;
|
private TextItem checkForUpdateItem, clearImageCacheItem;
|
||||||
private ImageCache imageCache;
|
private ImageCache imageCache;
|
||||||
|
private Menu contentTypeMenu;
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -207,6 +213,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
GlobalUserPreferences.prefixRepliesWithRe=i.checked;
|
GlobalUserPreferences.prefixRepliesWithRe=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
|
items.add(new SwitchItem(R.string.sk_settings_confirm_before_reblog, R.drawable.ic_fluent_checkmark_circle_24_regular, GlobalUserPreferences.confirmBeforeReblog, i->{
|
||||||
|
GlobalUserPreferences.confirmBeforeReblog=i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}));
|
||||||
|
|
||||||
items.add(new HeaderItem(R.string.sk_timelines));
|
items.add(new HeaderItem(R.string.sk_timelines));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_show_replies, R.drawable.ic_fluent_chat_multiple_24_regular, GlobalUserPreferences.showReplies, i->{
|
items.add(new SwitchItem(R.string.sk_settings_show_replies, R.drawable.ic_fluent_chat_multiple_24_regular, GlobalUserPreferences.showReplies, i->{
|
||||||
@@ -235,15 +245,15 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}));
|
}));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_load_new_posts, R.drawable.ic_fluent_arrow_sync_24_regular, GlobalUserPreferences.loadNewPosts, i->{
|
items.add(new SwitchItem(R.string.sk_settings_load_new_posts, R.drawable.ic_fluent_arrow_sync_24_regular, GlobalUserPreferences.loadNewPosts, i->{
|
||||||
GlobalUserPreferences.loadNewPosts=i.checked;
|
GlobalUserPreferences.loadNewPosts=i.checked;
|
||||||
showNewPostsButtonItem.enabled = i.checked;
|
showNewPostsItem.enabled = i.checked;
|
||||||
if (!i.checked) {
|
if (!i.checked) {
|
||||||
GlobalUserPreferences.showNewPostsButton = false;
|
GlobalUserPreferences.showNewPostsButton = false;
|
||||||
showNewPostsButtonItem.checked = false;
|
showNewPostsItem.checked = false;
|
||||||
}
|
}
|
||||||
if (list.findViewHolderForAdapterPosition(items.indexOf(showNewPostsButtonItem)) instanceof SwitchViewHolder svh) svh.rebind();
|
if (list.findViewHolderForAdapterPosition(items.indexOf(showNewPostsItem)) instanceof SwitchViewHolder svh) svh.rebind();
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
items.add(showNewPostsButtonItem = new SwitchItem(R.string.sk_settings_see_new_posts_button, R.drawable.ic_fluent_arrow_up_24_regular, GlobalUserPreferences.showNewPostsButton, i->{
|
items.add(showNewPostsItem = new SwitchItem(R.string.sk_settings_see_new_posts_button, R.drawable.ic_fluent_arrow_up_24_regular, GlobalUserPreferences.showNewPostsButton, i->{
|
||||||
GlobalUserPreferences.showNewPostsButton=i.checked;
|
GlobalUserPreferences.showNewPostsButton=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
@@ -326,6 +336,36 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
if (!TextUtils.isEmpty(instance.version)) items.add(new SmallTextItem(getString(R.string.sk_settings_server_version, instance.version)));
|
if (!TextUtils.isEmpty(instance.version)) items.add(new SmallTextItem(getString(R.string.sk_settings_server_version, instance.version)));
|
||||||
|
|
||||||
items.add(new HeaderItem(R.string.sk_instance_features));
|
items.add(new HeaderItem(R.string.sk_instance_features));
|
||||||
|
items.add(new SwitchItem(R.string.sk_settings_content_types, 0, GlobalUserPreferences.accountsWithContentTypesEnabled.contains(accountID), (i)->{
|
||||||
|
if (i.checked) {
|
||||||
|
GlobalUserPreferences.accountsWithContentTypesEnabled.add(accountID);
|
||||||
|
if (GlobalUserPreferences.accountsDefaultContentTypes.get(accountID) == null) {
|
||||||
|
GlobalUserPreferences.accountsDefaultContentTypes.put(accountID, ContentType.PLAIN);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GlobalUserPreferences.accountsWithContentTypesEnabled.remove(accountID);
|
||||||
|
GlobalUserPreferences.accountsDefaultContentTypes.remove(accountID);
|
||||||
|
}
|
||||||
|
if (list.findViewHolderForAdapterPosition(items.indexOf(defaultContentTypeButtonItem))
|
||||||
|
instanceof ButtonViewHolder bvh) bvh.rebind();
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}));
|
||||||
|
items.add(new SmallTextItem(getString(R.string.sk_settings_content_types_explanation)));
|
||||||
|
items.add(defaultContentTypeButtonItem = new ButtonItem(R.string.sk_settings_default_content_type, 0, b->{
|
||||||
|
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
|
||||||
|
popupMenu.inflate(R.menu.compose_content_type);
|
||||||
|
popupMenu.setOnMenuItemClickListener(item -> this.onContentTypeChanged(item, b));
|
||||||
|
b.setOnTouchListener(popupMenu.getDragToOpenListener());
|
||||||
|
b.setOnClickListener(v->popupMenu.show());
|
||||||
|
ContentType contentType = GlobalUserPreferences.accountsDefaultContentTypes.get(accountID);
|
||||||
|
b.setText(getContentTypeString(contentType));
|
||||||
|
contentTypeMenu = popupMenu.getMenu();
|
||||||
|
contentTypeMenu.findItem(ContentType.getContentTypeRes(contentType)).setChecked(true);
|
||||||
|
ContentType.adaptMenuToInstance(contentTypeMenu, instance);
|
||||||
|
contentTypeMenu.findItem(R.id.content_type_null).setVisible(
|
||||||
|
!GlobalUserPreferences.accountsWithContentTypesEnabled.contains(accountID));
|
||||||
|
}));
|
||||||
|
items.add(new SmallTextItem(getString(R.string.sk_settings_default_content_type_explanation)));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_support_local_only, 0, GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID), i->{
|
items.add(new SwitchItem(R.string.sk_settings_support_local_only, 0, GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID), i->{
|
||||||
glitchModeItem.enabled = i.checked;
|
glitchModeItem.enabled = i.checked;
|
||||||
if (i.checked) {
|
if (i.checked) {
|
||||||
@@ -496,6 +536,34 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private @StringRes int getContentTypeString(@Nullable ContentType contentType) {
|
||||||
|
if (contentType == null) return R.string.sk_content_type_unspecified;
|
||||||
|
return switch (contentType) {
|
||||||
|
case PLAIN -> R.string.sk_content_type_plain;
|
||||||
|
case HTML -> R.string.sk_content_type_html;
|
||||||
|
case MARKDOWN -> R.string.sk_content_type_markdown;
|
||||||
|
case BBCODE -> R.string.sk_content_type_bbcode;
|
||||||
|
case MISSKEY_MARKDOWN -> R.string.sk_content_type_mfm;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onContentTypeChanged(MenuItem item, Button btn){
|
||||||
|
int id = item.getItemId();
|
||||||
|
ContentType contentType = switch (id) {
|
||||||
|
case R.id.content_type_plain -> ContentType.PLAIN;
|
||||||
|
case R.id.content_type_html -> ContentType.HTML;
|
||||||
|
case R.id.content_type_markdown -> ContentType.MARKDOWN;
|
||||||
|
case R.id.content_type_bbcode -> ContentType.BBCODE;
|
||||||
|
case R.id.content_type_misskey_markdown -> ContentType.MISSKEY_MARKDOWN;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
GlobalUserPreferences.accountsDefaultContentTypes.put(accountID, contentType);
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
btn.setText(getContentTypeString(contentType));
|
||||||
|
item.setChecked(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean onReplyVisibilityChanged(MenuItem item, Button btn){
|
private boolean onReplyVisibilityChanged(MenuItem item, Button btn){
|
||||||
String pref = null;
|
String pref = null;
|
||||||
int id = item.getItemId();
|
int id = item.getItemId();
|
||||||
@@ -1003,7 +1071,11 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onBind(ButtonItem item){
|
public void onBind(ButtonItem item){
|
||||||
text.setText(item.text);
|
text.setText(item.text);
|
||||||
icon.setImageResource(item.icon);
|
if (item.icon == 0) {
|
||||||
|
icon.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
icon.setImageResource(item.icon);
|
||||||
|
}
|
||||||
item.buttonConsumer.accept(button);
|
item.buttonConsumer.accept(button);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,47 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
import android.graphics.drawable.ColorDrawable;
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.SpannableString;
|
|
||||||
import android.text.style.ReplacementSpan;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.Button;
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||||
import org.joinmastodon.android.fragments.onboarding.InstanceCatalogSignupFragment;
|
import org.joinmastodon.android.fragments.onboarding.InstanceCatalogSignupFragment;
|
||||||
import org.joinmastodon.android.fragments.onboarding.InstanceChooserLoginFragment;
|
import org.joinmastodon.android.fragments.onboarding.InstanceChooserLoginFragment;
|
||||||
|
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
|
||||||
|
import org.joinmastodon.android.model.Instance;
|
||||||
|
import org.joinmastodon.android.ui.InterpolatingMotionEffect;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.SizeListenerFrameLayout;
|
import org.joinmastodon.android.ui.views.SizeListenerFrameLayout;
|
||||||
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import androidx.viewpager2.widget.ViewPager2;
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.fragments.AppKitFragment;
|
import me.grishka.appkit.fragments.AppKitFragment;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
import me.grishka.appkit.views.BottomSheet;
|
||||||
|
|
||||||
public class SplashFragment extends AppKitFragment{
|
public class SplashFragment extends AppKitFragment{
|
||||||
|
|
||||||
|
private static final String DEFAULT_SERVER="mastodon.social";
|
||||||
|
|
||||||
private SizeListenerFrameLayout contentView;
|
private SizeListenerFrameLayout contentView;
|
||||||
private View artContainer, blueFill, greenFill;
|
private View artContainer, blueFill, greenFill;
|
||||||
private ViewPager2 pager;
|
private InterpolatingMotionEffect motionEffect;
|
||||||
private ViewGroup pagerDots;
|
|
||||||
private View artClouds, artPlaneElephant, artRightHill, artLeftHill, artCenterHill;
|
private View artClouds, artPlaneElephant, artRightHill, artLeftHill, artCenterHill;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
motionEffect=new InterpolatingMotionEffect(MastodonApp.context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -46,44 +50,26 @@ public class SplashFragment extends AppKitFragment{
|
|||||||
contentView=(SizeListenerFrameLayout) inflater.inflate(R.layout.fragment_splash, container, false);
|
contentView=(SizeListenerFrameLayout) inflater.inflate(R.layout.fragment_splash, container, false);
|
||||||
contentView.findViewById(R.id.btn_get_started).setOnClickListener(this::onButtonClick);
|
contentView.findViewById(R.id.btn_get_started).setOnClickListener(this::onButtonClick);
|
||||||
contentView.findViewById(R.id.btn_log_in).setOnClickListener(this::onButtonClick);
|
contentView.findViewById(R.id.btn_log_in).setOnClickListener(this::onButtonClick);
|
||||||
|
Button joinDefault=contentView.findViewById(R.id.btn_join_default_server);
|
||||||
|
joinDefault.setText(getString(R.string.join_default_server, DEFAULT_SERVER));
|
||||||
|
joinDefault.setOnClickListener(this::onJoinDefaultServerClick);
|
||||||
|
contentView.findViewById(R.id.btn_learn_more).setOnClickListener(this::onLearnMoreClick);
|
||||||
|
|
||||||
artClouds=contentView.findViewById(R.id.art_clouds);
|
artClouds=contentView.findViewById(R.id.art_clouds);
|
||||||
artPlaneElephant=contentView.findViewById(R.id.art_plane_elephant);
|
artPlaneElephant=contentView.findViewById(R.id.art_plane_elephant);
|
||||||
artRightHill=contentView.findViewById(R.id.art_right_hill);
|
artRightHill=contentView.findViewById(R.id.art_right_hill);
|
||||||
artLeftHill=contentView.findViewById(R.id.art_left_hill);
|
artLeftHill=contentView.findViewById(R.id.art_left_hill);
|
||||||
artCenterHill=contentView.findViewById(R.id.art_center_hill);
|
artCenterHill=contentView.findViewById(R.id.art_center_hill);
|
||||||
pager=contentView.findViewById(R.id.pager);
|
|
||||||
pagerDots=contentView.findViewById(R.id.pager_dots);
|
|
||||||
pager.setAdapter(new PagerAdapter());
|
|
||||||
pager.setOffscreenPageLimit(3);
|
|
||||||
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
|
||||||
@Override
|
|
||||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels){
|
|
||||||
for(int i=0;i<pagerDots.getChildCount();i++){
|
|
||||||
float alpha;
|
|
||||||
if(i==position){
|
|
||||||
alpha=0.3f+0.7f*(1f-positionOffset);
|
|
||||||
}else if(i==position+1){
|
|
||||||
alpha=0.3f+0.7f*positionOffset;
|
|
||||||
}else{
|
|
||||||
alpha=0.3f;
|
|
||||||
}
|
|
||||||
pagerDots.getChildAt(i).setAlpha(alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
float parallaxProgress=(position+positionOffset)/2f;
|
|
||||||
artClouds.setTranslationX(V.dp(-27)*(position>=1 ? 1f : positionOffset));
|
|
||||||
artPlaneElephant.setTranslationX(V.dp(101.55f)*parallaxProgress);
|
|
||||||
artLeftHill.setTranslationX(V.dp(-88)*parallaxProgress);
|
|
||||||
artLeftHill.setTranslationY(V.dp(24)*parallaxProgress);
|
|
||||||
artRightHill.setTranslationX(V.dp(-88)*parallaxProgress);
|
|
||||||
artRightHill.setTranslationY(V.dp(-24)*parallaxProgress);
|
|
||||||
artCenterHill.setTranslationX(V.dp(-40)*parallaxProgress);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
artContainer=contentView.findViewById(R.id.art_container);
|
artContainer=contentView.findViewById(R.id.art_container);
|
||||||
blueFill=contentView.findViewById(R.id.blue_fill);
|
blueFill=contentView.findViewById(R.id.blue_fill);
|
||||||
greenFill=contentView.findViewById(R.id.green_fill);
|
greenFill=contentView.findViewById(R.id.green_fill);
|
||||||
|
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(artClouds, V.dp(-5), V.dp(5), V.dp(-5), V.dp(5)));
|
||||||
|
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(artRightHill, V.dp(-15), V.dp(25), V.dp(-10), V.dp(10)));
|
||||||
|
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(artLeftHill, V.dp(-25), V.dp(15), V.dp(-15), V.dp(15)));
|
||||||
|
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(artCenterHill, V.dp(-14), V.dp(14), V.dp(-5), V.dp(25)));
|
||||||
|
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(artPlaneElephant, V.dp(-20), V.dp(12), V.dp(-20), V.dp(12)));
|
||||||
|
artContainer.setOnTouchListener(motionEffect);
|
||||||
|
|
||||||
contentView.setSizeListener(new SizeListenerFrameLayout.OnSizeChangedListener(){
|
contentView.setSizeListener(new SizeListenerFrameLayout.OnSizeChangedListener(){
|
||||||
@Override
|
@Override
|
||||||
@@ -109,6 +95,38 @@ public class SplashFragment extends AppKitFragment{
|
|||||||
Nav.go(getActivity(), isSignup ? InstanceCatalogSignupFragment.class : InstanceChooserLoginFragment.class, extras);
|
Nav.go(getActivity(), isSignup ? InstanceCatalogSignupFragment.class : InstanceChooserLoginFragment.class, extras);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onJoinDefaultServerClick(View v){
|
||||||
|
new GetInstance()
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Instance result){
|
||||||
|
if(getActivity()==null)
|
||||||
|
return;
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putParcelable("instance", Parcels.wrap(result));
|
||||||
|
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
if(getActivity()==null)
|
||||||
|
return;
|
||||||
|
error.showToast(getActivity());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.wrapProgress(getActivity(), R.string.loading_instance, true)
|
||||||
|
.execNoAuth(DEFAULT_SERVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onLearnMoreClick(View v){
|
||||||
|
View sheetView=getActivity().getLayoutInflater().inflate(R.layout.intro_bottom_sheet, null);
|
||||||
|
BottomSheet sheet=new BottomSheet(getActivity());
|
||||||
|
sheet.setContentView(sheetView);
|
||||||
|
sheet.setNavigationBarBackground(new ColorDrawable(UiUtils.alphaBlendColors(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Surface),
|
||||||
|
UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme());
|
||||||
|
sheet.show();
|
||||||
|
}
|
||||||
|
|
||||||
private void updateArtSize(int w, int h){
|
private void updateArtSize(int w, int h){
|
||||||
float scale=w/(float)V.dp(360);
|
float scale=w/(float)V.dp(360);
|
||||||
artContainer.setScaleX(scale);
|
artContainer.setScaleX(scale);
|
||||||
@@ -139,60 +157,15 @@ public class SplashFragment extends AppKitFragment{
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PagerAdapter extends RecyclerView.Adapter<PagerViewHolder>{
|
@Override
|
||||||
|
protected void onShown(){
|
||||||
@NonNull
|
super.onShown();
|
||||||
@Override
|
motionEffect.activate();
|
||||||
public PagerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
|
||||||
return new PagerViewHolder(viewType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull PagerViewHolder holder, int position){}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount(){
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemViewType(int position){
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PagerViewHolder extends RecyclerView.ViewHolder{
|
@Override
|
||||||
public PagerViewHolder(int page){
|
protected void onHidden(){
|
||||||
super(new LinearLayout(getActivity()));
|
super.onHidden();
|
||||||
LinearLayout ll=(LinearLayout) itemView;
|
motionEffect.deactivate();
|
||||||
ll.setOrientation(LinearLayout.VERTICAL);
|
|
||||||
int pad=V.dp(16);
|
|
||||||
ll.setPadding(pad, pad, pad, pad);
|
|
||||||
ll.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
|
||||||
|
|
||||||
TextView title=new TextView(getActivity());
|
|
||||||
title.setTextAppearance(R.style.m3_headline_medium);
|
|
||||||
title.setText(switch(page){
|
|
||||||
case 0 -> getString(R.string.welcome_page1_title);
|
|
||||||
case 1 -> getString(R.string.welcome_page2_title);
|
|
||||||
case 2 -> getString(R.string.welcome_page3_title);
|
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
|
||||||
});
|
|
||||||
title.setTextColor(0xFF17063B);
|
|
||||||
LinearLayout.LayoutParams lp=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(page==0 ? 46 : 36));
|
|
||||||
lp.bottomMargin=V.dp(page==0 ? 4 : 14);
|
|
||||||
ll.addView(title, lp);
|
|
||||||
|
|
||||||
TextView text=new TextView(getActivity());
|
|
||||||
text.setTextAppearance(R.style.m3_body_medium);
|
|
||||||
text.setText(switch(page){
|
|
||||||
case 0 -> R.string.welcome_page1_text;
|
|
||||||
case 1 -> R.string.welcome_page2_text;
|
|
||||||
case 2 -> R.string.welcome_page3_text;
|
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
|
||||||
});
|
|
||||||
text.setTextColor(0xFF17063B);
|
|
||||||
ll.addView(text, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.view.View;
|
|||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetStatusEditHistory;
|
import org.joinmastodon.android.api.requests.statuses.GetStatusEditHistory;
|
||||||
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
@@ -55,7 +56,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||||
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null);
|
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null, null);
|
||||||
int idx=data.indexOf(s);
|
int idx=data.indexOf(s);
|
||||||
if(idx>=0){
|
if(idx>=0){
|
||||||
String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault()));
|
String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault()));
|
||||||
@@ -156,4 +157,9 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
|||||||
public boolean isItemEnabled(String id){
|
public boolean isItemEnabled(String id){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter.FilterContext getFilterContext() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,9 +34,11 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
|||||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||||
boolean addFooter = !GlobalUserPreferences.spectatorMode ||
|
boolean addFooter = !GlobalUserPreferences.spectatorMode ||
|
||||||
(this instanceof ThreadFragment t && s.id.equals(t.mainStatus.id));
|
(this instanceof ThreadFragment t && s.id.equals(t.mainStatus.id));
|
||||||
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, addFooter, null, Filter.FilterContext.HOME);
|
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, addFooter, null, getFilterContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract Filter.FilterContext getFilterContext();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void addAccountToKnown(Status s){
|
protected void addAccountToKnown(Status s){
|
||||||
if(!knownAccounts.containsKey(s.account.id))
|
if(!knownAccounts.containsKey(s.account.id))
|
||||||
@@ -151,7 +153,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
|||||||
|
|
||||||
protected void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){
|
protected void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){
|
||||||
List<Status> toRemove=Stream.concat(data.stream(), preloadedData.stream())
|
List<Status> toRemove=Stream.concat(data.stream(), preloadedData.stream())
|
||||||
.filter(s->s.account.id.equals(ev.postsByAccountID) || (s.reblog!=null && s.reblog.account.id.equals(ev.postsByAccountID)))
|
.filter(s->s.account.id.equals(ev.postsByAccountID) || (!ev.isUnfollow && s.reblog!=null && s.reblog.account.id.equals(ev.postsByAccountID)))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
for(Status s:toRemove){
|
for(Status s:toRemove){
|
||||||
removeStatus(s);
|
removeStatus(s);
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ public class ThreadFragment extends StatusListFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<Status> filterStatuses(List<Status> statuses){
|
private List<Status> filterStatuses(List<Status> statuses){
|
||||||
StatusFilterPredicate statusFilterPredicate=new StatusFilterPredicate(accountID,Filter.FilterContext.THREAD);
|
StatusFilterPredicate statusFilterPredicate=new StatusFilterPredicate(accountID,getFilterContext());
|
||||||
return statuses.stream()
|
return statuses.stream()
|
||||||
.filter(statusFilterPredicate)
|
.filter(statusFilterPredicate)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
@@ -181,4 +181,10 @@ public class ThreadFragment extends StatusListFragment{
|
|||||||
public boolean wantsLightNavigationBar(){
|
public boolean wantsLightNavigationBar(){
|
||||||
return !UiUtils.isDarkTheme();
|
return !UiUtils.isDarkTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter.FilterContext getFilterContext() {
|
||||||
|
return Filter.FilterContext.THREAD;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
|||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.ListTimelinesFragment;
|
import org.joinmastodon.android.fragments.ListTimelinesFragment;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
|
import org.joinmastodon.android.fragments.RecyclerFragment;
|
||||||
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
@@ -48,7 +49,6 @@ import me.grishka.appkit.Nav;
|
|||||||
import me.grishka.appkit.api.APIRequest;
|
import me.grishka.appkit.api.APIRequest;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
@@ -57,7 +57,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
|||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseAccountListFragment.AccountItem>{
|
public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccountListFragment.AccountItem> {
|
||||||
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
||||||
protected String accountID;
|
protected String accountID;
|
||||||
protected ArrayList<APIRequest<?>> relationshipsRequests=new ArrayList<>();
|
protected ArrayList<APIRequest<?>> relationshipsRequests=new ArrayList<>();
|
||||||
@@ -295,7 +295,6 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
|||||||
|
|
||||||
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getShortUsername()));
|
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getShortUsername()));
|
||||||
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getShortUsername()));
|
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getShortUsername()));
|
||||||
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername())).setVisible(relationship.following);
|
|
||||||
menu.findItem(R.id.soft_block).setVisible(relationship.followedBy && !relationship.following);
|
menu.findItem(R.id.soft_block).setVisible(relationship.followedBy && !relationship.following);
|
||||||
MenuItem hideBoosts=menu.findItem(R.id.hide_boosts);
|
MenuItem hideBoosts=menu.findItem(R.id.hide_boosts);
|
||||||
MenuItem manageUserLists=menu.findItem(R.id.manage_user_lists);
|
MenuItem manageUserLists=menu.findItem(R.id.manage_user_lists);
|
||||||
@@ -309,7 +308,7 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
|||||||
manageUserLists.setVisible(true);
|
manageUserLists.setVisible(true);
|
||||||
}else{
|
}else{
|
||||||
hideBoosts.setVisible(false);
|
hideBoosts.setVisible(false);
|
||||||
manageUserLists.setVisible(true);
|
manageUserLists.setVisible(false);
|
||||||
}
|
}
|
||||||
menu.findItem(R.id.block_domain).setVisible(false);
|
menu.findItem(R.id.block_domain).setVisible(false);
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
|||||||
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
|
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
|
||||||
import org.joinmastodon.android.fragments.IsOnTop;
|
import org.joinmastodon.android.fragments.IsOnTop;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
|
import org.joinmastodon.android.fragments.RecyclerFragment;
|
||||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.FollowSuggestion;
|
import org.joinmastodon.android.model.FollowSuggestion;
|
||||||
@@ -40,7 +41,6 @@ import me.grishka.appkit.Nav;
|
|||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
@@ -49,7 +49,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
|||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccountsFragment.AccountWrapper> implements ScrollableToTop, IsOnTop {
|
public class DiscoverAccountsFragment extends RecyclerFragment<DiscoverAccountsFragment.AccountWrapper> implements ScrollableToTop, IsOnTop {
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private Map<String, Relationship> relationships=Collections.emptyMap();
|
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||||
private GetAccountRelationships relationshipsRequest;
|
private GetAccountRelationships relationshipsRequest;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import android.widget.TextView;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.trends.GetTrendingLinks;
|
import org.joinmastodon.android.api.requests.trends.GetTrendingLinks;
|
||||||
import org.joinmastodon.android.fragments.IsOnTop;
|
import org.joinmastodon.android.fragments.IsOnTop;
|
||||||
|
import org.joinmastodon.android.fragments.RecyclerFragment;
|
||||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||||
import org.joinmastodon.android.model.Card;
|
import org.joinmastodon.android.model.Card;
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
@@ -26,7 +27,6 @@ import java.util.stream.Collectors;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
@@ -35,7 +35,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
|||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements ScrollableToTop, IsOnTop {
|
public class DiscoverNewsFragment extends RecyclerFragment<Card> implements ScrollableToTop, IsOnTop {
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private List<ImageLoaderRequest> imageRequests=Collections.emptyList();
|
private List<ImageLoaderRequest> imageRequests=Collections.emptyList();
|
||||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS);
|
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class DiscoverPostsFragment extends StatusListFragment implements IsOnTop
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
if (getActivity() == null) return;
|
if (getActivity() == null) return;
|
||||||
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList());
|
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
|
||||||
onDataLoaded(result, !result.isEmpty());
|
onDataLoaded(result, !result.isEmpty());
|
||||||
}
|
}
|
||||||
}).exec(accountID);
|
}).exec(accountID);
|
||||||
@@ -42,4 +42,10 @@ public class DiscoverPostsFragment extends StatusListFragment implements IsOnTop
|
|||||||
public boolean isOnTop() {
|
public boolean isOnTop() {
|
||||||
return isRecyclerViewOnTop(list);
|
return isRecyclerViewOnTop(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter.FilterContext getFilterContext() {
|
||||||
|
return Filter.FilterContext.PUBLIC;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class FederatedTimelineFragment extends StatusListFragment {
|
|||||||
private String maxID;
|
private String maxID;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean withComposeButton() {
|
protected boolean wantsComposeButton() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ public class FederatedTimelineFragment extends StatusListFragment {
|
|||||||
if(!result.isEmpty())
|
if(!result.isEmpty())
|
||||||
maxID=result.get(result.size()-1).id;
|
maxID=result.get(result.size()-1).id;
|
||||||
if (getActivity() == null) return;
|
if (getActivity() == null) return;
|
||||||
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList());
|
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
|
||||||
onDataLoaded(result, !result.isEmpty());
|
onDataLoaded(result, !result.isEmpty());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -47,4 +47,9 @@ public class FederatedTimelineFragment extends StatusListFragment {
|
|||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
bannerHelper.maybeAddBanner(contentWrap);
|
bannerHelper.maybeAddBanner(contentWrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter.FilterContext getFilterContext() {
|
||||||
|
return Filter.FilterContext.PUBLIC;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public class LocalTimelineFragment extends StatusListFragment {
|
|||||||
private String maxID;
|
private String maxID;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean withComposeButton() {
|
protected boolean wantsComposeButton() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ public class LocalTimelineFragment extends StatusListFragment {
|
|||||||
if(!result.isEmpty())
|
if(!result.isEmpty())
|
||||||
maxID=result.get(result.size()-1).id;
|
maxID=result.get(result.size()-1).id;
|
||||||
if (getActivity() == null) return;
|
if (getActivity() == null) return;
|
||||||
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList());
|
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
|
||||||
onDataLoaded(result, !result.isEmpty());
|
onDataLoaded(result, !result.isEmpty());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -46,4 +46,9 @@ public class LocalTimelineFragment extends StatusListFragment {
|
|||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
bannerHelper.maybeAddBanner(contentWrap);
|
bannerHelper.maybeAddBanner(contentWrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter.FilterContext getFilterContext() {
|
||||||
|
return Filter.FilterContext.PUBLIC;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.joinmastodon.android.fragments.IsOnTop;
|
|||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.model.SearchResult;
|
import org.joinmastodon.android.model.SearchResult;
|
||||||
import org.joinmastodon.android.model.SearchResults;
|
import org.joinmastodon.android.model.SearchResults;
|
||||||
@@ -80,7 +81,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult> impleme
|
|||||||
return switch(s.type){
|
return switch(s.type){
|
||||||
case ACCOUNT -> Collections.singletonList(new AccountStatusDisplayItem(s.id, this, s.account));
|
case ACCOUNT -> Collections.singletonList(new AccountStatusDisplayItem(s.id, this, s.account));
|
||||||
case HASHTAG -> Collections.singletonList(new HashtagStatusDisplayItem(s.id, this, s.hashtag));
|
case HASHTAG -> Collections.singletonList(new HashtagStatusDisplayItem(s.id, this, s.hashtag));
|
||||||
case STATUS -> StatusDisplayItem.buildItems(this, s.status, accountID, s, knownAccounts, false, true, null);
|
case STATUS -> StatusDisplayItem.buildItems(this, s.status, accountID, s, knownAccounts, false, true, null, Filter.FilterContext.PUBLIC);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,7 +248,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult> impleme
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setQuery(String q){
|
public void setQuery(String q){
|
||||||
if(Objects.equals(q, currentQuery))
|
if(Objects.equals(q, currentQuery) || q.isBlank())
|
||||||
return;
|
return;
|
||||||
if(currentRequest!=null){
|
if(currentRequest!=null){
|
||||||
currentRequest.cancel();
|
currentRequest.cancel();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.widget.TextView;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.trends.GetTrendingHashtags;
|
import org.joinmastodon.android.api.requests.trends.GetTrendingHashtags;
|
||||||
import org.joinmastodon.android.fragments.IsOnTop;
|
import org.joinmastodon.android.fragments.IsOnTop;
|
||||||
|
import org.joinmastodon.android.fragments.RecyclerFragment;
|
||||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
@@ -20,11 +21,10 @@ import java.util.List;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop, IsOnTop {
|
public class TrendingHashtagsFragment extends RecyclerFragment<Hashtag> implements ScrollableToTop, IsOnTop {
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_HASHTAGS);
|
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_HASHTAGS);
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import org.joinmastodon.android.R;
|
|||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
import org.joinmastodon.android.api.MastodonErrorResponse;
|
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||||
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||||
|
import org.joinmastodon.android.fragments.RecyclerFragment;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.catalog.CatalogInstance;
|
import org.joinmastodon.android.model.catalog.CatalogInstance;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
@@ -44,7 +45,6 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
@@ -52,7 +52,7 @@ import okhttp3.Call;
|
|||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
|
||||||
abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstance>{
|
abstract class InstanceCatalogFragment extends RecyclerFragment<CatalogInstance> {
|
||||||
protected RecyclerView.Adapter adapter;
|
protected RecyclerView.Adapter adapter;
|
||||||
protected MergeRecyclerAdapter mergeAdapter;
|
protected MergeRecyclerAdapter mergeAdapter;
|
||||||
protected CatalogInstance chosenInstance;
|
protected CatalogInstance chosenInstance;
|
||||||
@@ -75,7 +75,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||||||
private static final double DUNBAR=Math.log(800);
|
private static final double DUNBAR=Math.log(800);
|
||||||
|
|
||||||
public InstanceCatalogFragment(int layout, int perPage){
|
public InstanceCatalogFragment(int layout, int perPage){
|
||||||
super(layout, perPage);
|
super(layout, perPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
|
|||||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||||
import org.joinmastodon.android.fragments.HomeFragment;
|
import org.joinmastodon.android.fragments.HomeFragment;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
|
import org.joinmastodon.android.fragments.RecyclerFragment;
|
||||||
import org.joinmastodon.android.model.FollowSuggestion;
|
import org.joinmastodon.android.model.FollowSuggestion;
|
||||||
import org.joinmastodon.android.model.ParsedAccount;
|
import org.joinmastodon.android.model.ParsedAccount;
|
||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
@@ -43,7 +44,6 @@ import me.grishka.appkit.Nav;
|
|||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
@@ -52,7 +52,7 @@ import me.grishka.appkit.utils.V;
|
|||||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class OnboardingFollowSuggestionsFragment extends BaseRecyclerFragment<ParsedAccount>{
|
public class OnboardingFollowSuggestionsFragment extends RecyclerFragment<ParsedAccount> {
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private Map<String, Relationship> relationships=Collections.emptyMap();
|
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||||
private GetAccountRelationships relationshipsRequest;
|
private GetAccountRelationships relationshipsRequest;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
|||||||
import org.joinmastodon.android.events.FinishReportFragmentsEvent;
|
import org.joinmastodon.android.events.FinishReportFragmentsEvent;
|
||||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.displayitems.AudioStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.AudioStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||||
@@ -261,4 +262,9 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
|||||||
protected boolean wantsOverlaySystemNavigation(){
|
protected boolean wantsOverlaySystemNavigation(){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter.FilterContext getFilterContext() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package org.joinmastodon.android.model;
|
||||||
|
|
||||||
|
import android.view.Menu;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
|
||||||
|
public enum ContentType {
|
||||||
|
@SerializedName("text/plain")
|
||||||
|
PLAIN,
|
||||||
|
@SerializedName("text/html")
|
||||||
|
HTML,
|
||||||
|
@SerializedName("text/markdown")
|
||||||
|
MARKDOWN,
|
||||||
|
@SerializedName("text/bbcode")
|
||||||
|
BBCODE, // akkoma
|
||||||
|
@SerializedName("text/x.misskeymarkdown")
|
||||||
|
MISSKEY_MARKDOWN; // akkoma/*key
|
||||||
|
|
||||||
|
public static int getContentTypeRes(@Nullable ContentType contentType) {
|
||||||
|
return contentType == null ? R.id.content_type_null : switch(contentType) {
|
||||||
|
case PLAIN -> R.id.content_type_plain;
|
||||||
|
case HTML -> R.id.content_type_html;
|
||||||
|
case MARKDOWN -> R.id.content_type_markdown;
|
||||||
|
case BBCODE -> R.id.content_type_bbcode;
|
||||||
|
case MISSKEY_MARKDOWN -> R.id.content_type_misskey_markdown;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void adaptMenuToInstance(Menu m, Instance i) {
|
||||||
|
if (i.pleroma == null) {
|
||||||
|
// memo: change this if glitch or another mastodon fork supports bbcode or mfm
|
||||||
|
m.findItem(R.id.content_type_bbcode).setVisible(false);
|
||||||
|
m.findItem(R.id.content_type_misskey_markdown).setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,22 +2,22 @@ package org.joinmastodon.android.model;
|
|||||||
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.ObjectValidationException;
|
import org.joinmastodon.android.api.ObjectValidationException;
|
||||||
import org.joinmastodon.android.api.RequiredField;
|
|
||||||
import org.parceler.Parcel;
|
import org.parceler.Parcel;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@Parcel
|
@Parcel
|
||||||
public class Filter extends BaseModel{
|
public class Filter extends BaseModel{
|
||||||
@RequiredField
|
|
||||||
public String id;
|
public String id;
|
||||||
@RequiredField
|
|
||||||
public String phrase;
|
public String phrase;
|
||||||
public String title;
|
public String title;
|
||||||
public transient EnumSet<FilterContext> context=EnumSet.noneOf(FilterContext.class);
|
public transient EnumSet<FilterContext> context=EnumSet.noneOf(FilterContext.class);
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
package org.joinmastodon.android.model;
|
package org.joinmastodon.android.model;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.ObjectValidationException;
|
||||||
import org.parceler.Parcel;
|
import org.parceler.Parcel;
|
||||||
|
|
||||||
@Parcel
|
@Parcel
|
||||||
public class FilterResult extends BaseModel {
|
public class FilterResult extends BaseModel {
|
||||||
public Filter filter;
|
public Filter filter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postprocess() throws ObjectValidationException {
|
||||||
|
super.postprocess();
|
||||||
|
if (filter != null) filter.postprocess();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.joinmastodon.android.model;
|
package org.joinmastodon.android.model;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
@@ -18,4 +20,11 @@ public class Marker extends BaseModel{
|
|||||||
", updatedAt="+updatedAt+
|
", updatedAt="+updatedAt+
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum Type {
|
||||||
|
@SerializedName("home")
|
||||||
|
HOME,
|
||||||
|
@SerializedName("notifications")
|
||||||
|
NOTIFICATIONS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.joinmastodon.android.model;
|
||||||
|
|
||||||
|
public class Markers {
|
||||||
|
public Marker notifications;
|
||||||
|
public Marker home;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Markers{" +
|
||||||
|
"notifications=" + notifications +
|
||||||
|
", home=" + home +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,8 @@ public class Notification extends BaseModel implements DisplayItemsParent{
|
|||||||
public Account account;
|
public Account account;
|
||||||
public Status status;
|
public Status status;
|
||||||
public Report report;
|
public Report report;
|
||||||
|
public String emoji;
|
||||||
|
public String emojiUrl;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postprocess() throws ObjectValidationException{
|
public void postprocess() throws ObjectValidationException{
|
||||||
@@ -51,6 +53,10 @@ public class Notification extends BaseModel implements DisplayItemsParent{
|
|||||||
STATUS,
|
STATUS,
|
||||||
@SerializedName("update")
|
@SerializedName("update")
|
||||||
UPDATE,
|
UPDATE,
|
||||||
|
@SerializedName("reaction")
|
||||||
|
REACTION,
|
||||||
|
@SerializedName("pleroma:emoji_reaction")
|
||||||
|
PLEROMA_EMOJI_REACTION,
|
||||||
@SerializedName("admin.sign_up")
|
@SerializedName("admin.sign_up")
|
||||||
SIGN_UP,
|
SIGN_UP,
|
||||||
@SerializedName("admin.report")
|
@SerializedName("admin.report")
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ public class ScheduledStatus extends BaseModel implements DisplayItemsParent{
|
|||||||
public String idempotency;
|
public String idempotency;
|
||||||
public String applicationId;
|
public String applicationId;
|
||||||
public List<String> mediaIds;
|
public List<String> mediaIds;
|
||||||
|
public ContentType contentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Parcel
|
@Parcel
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
|||||||
public transient boolean spoilerRevealed;
|
public transient boolean spoilerRevealed;
|
||||||
public transient boolean textExpanded, textExpandable;
|
public transient boolean textExpanded, textExpandable;
|
||||||
public transient boolean hasGapAfter;
|
public transient boolean hasGapAfter;
|
||||||
|
public transient TranslatedStatus translation;
|
||||||
|
public transient boolean translationShown;
|
||||||
private transient String strippedText;
|
private transient String strippedText;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -99,6 +101,9 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
|||||||
card.postprocess();
|
card.postprocess();
|
||||||
if(reblog!=null)
|
if(reblog!=null)
|
||||||
reblog.postprocess();
|
reblog.postprocess();
|
||||||
|
if(filtered!=null)
|
||||||
|
for(FilterResult fr : filtered)
|
||||||
|
fr.postprocess();
|
||||||
|
|
||||||
spoilerRevealed=GlobalUserPreferences.alwaysExpandContentWarnings || !sensitive;
|
spoilerRevealed=GlobalUserPreferences.alwaysExpandContentWarnings || !sensitive;
|
||||||
if (visibility.equals(StatusPrivacy.LOCAL)) localOnly = true;
|
if (visibility.equals(StatusPrivacy.LOCAL)) localOnly = true;
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
package org.joinmastodon.android.ui;
|
package org.joinmastodon.android.ui;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.hardware.Sensor;
|
import android.hardware.Sensor;
|
||||||
import android.hardware.SensorEvent;
|
import android.hardware.SensorEvent;
|
||||||
import android.hardware.SensorEventListener;
|
import android.hardware.SensorEventListener;
|
||||||
import android.hardware.SensorManager;
|
import android.hardware.SensorManager;
|
||||||
|
import android.view.MotionEvent;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
import android.view.animation.PathInterpolator;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class InterpolatingMotionEffect implements SensorEventListener{
|
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||||
|
import androidx.dynamicanimation.animation.FloatValueHolder;
|
||||||
|
import androidx.dynamicanimation.animation.SpringAnimation;
|
||||||
|
import androidx.dynamicanimation.animation.SpringForce;
|
||||||
|
|
||||||
|
public class InterpolatingMotionEffect implements SensorEventListener, View.OnTouchListener{
|
||||||
|
|
||||||
private SensorManager sm;
|
private SensorManager sm;
|
||||||
private WindowManager wm;
|
private WindowManager wm;
|
||||||
@@ -20,6 +28,34 @@ public class InterpolatingMotionEffect implements SensorEventListener{
|
|||||||
private Sensor accelerometer;
|
private Sensor accelerometer;
|
||||||
private boolean accelerometerEnabled;
|
private boolean accelerometerEnabled;
|
||||||
private ArrayList<ViewEffect> views=new ArrayList<>();
|
private ArrayList<ViewEffect> views=new ArrayList<>();
|
||||||
|
private float pitch, roll;
|
||||||
|
private float touchDownX, touchDownY, touchAddX, touchAddY, touchAddLastAnimX, touchAddLastAnimY;
|
||||||
|
private PathInterpolator touchInterpolator=new PathInterpolator(0.5f, 1f, 0.89f, 1f);
|
||||||
|
private SpringAnimation touchSpringX, touchSpringY;
|
||||||
|
private FloatValueHolder touchSpringXHolder=new FloatValueHolder(){
|
||||||
|
@Override
|
||||||
|
public float getValue(){
|
||||||
|
return touchAddX;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(float value){
|
||||||
|
touchAddX=value;
|
||||||
|
updateEffects();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private FloatValueHolder touchSpringYHolder=new FloatValueHolder(){
|
||||||
|
@Override
|
||||||
|
public float getValue(){
|
||||||
|
return touchAddY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(float value){
|
||||||
|
touchAddY=value;
|
||||||
|
updateEffects();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public InterpolatingMotionEffect(Context context){
|
public InterpolatingMotionEffect(Context context){
|
||||||
sm=context.getSystemService(SensorManager.class);
|
sm=context.getSystemService(SensorManager.class);
|
||||||
@@ -50,8 +86,8 @@ public class InterpolatingMotionEffect implements SensorEventListener{
|
|||||||
float z=event.values[2]/SensorManager.GRAVITY_EARTH;
|
float z=event.values[2]/SensorManager.GRAVITY_EARTH;
|
||||||
|
|
||||||
|
|
||||||
float pitch=(float) (Math.atan2(x, Math.sqrt(y*y+z*z))/Math.PI*2.0);
|
pitch=(float) (Math.atan2(x, Math.sqrt(y*y+z*z))/Math.PI*2.0);
|
||||||
float roll=(float) (Math.atan2(y, Math.sqrt(x*x+z*z))/Math.PI*2.0);
|
roll=(float) (Math.atan2(y, Math.sqrt(x*x+z*z))/Math.PI*2.0);
|
||||||
|
|
||||||
switch(rotation){
|
switch(rotation){
|
||||||
case Surface.ROTATION_0:
|
case Surface.ROTATION_0:
|
||||||
@@ -88,9 +124,7 @@ public class InterpolatingMotionEffect implements SensorEventListener{
|
|||||||
}else if(roll<-1f){
|
}else if(roll<-1f){
|
||||||
roll=-2f-roll;
|
roll=-2f-roll;
|
||||||
}
|
}
|
||||||
for(ViewEffect view:views){
|
updateEffects();
|
||||||
view.update(pitch, roll);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -110,6 +144,62 @@ public class InterpolatingMotionEffect implements SensorEventListener{
|
|||||||
views.clear();
|
views.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View v, MotionEvent ev){
|
||||||
|
switch(ev.getAction()){
|
||||||
|
case MotionEvent.ACTION_DOWN -> {
|
||||||
|
if(touchSpringX!=null){
|
||||||
|
touchAddLastAnimX=touchAddX;
|
||||||
|
touchSpringX.cancel();
|
||||||
|
touchSpringX=null;
|
||||||
|
}else{
|
||||||
|
touchAddLastAnimX=0;
|
||||||
|
}
|
||||||
|
if(touchSpringY!=null){
|
||||||
|
touchAddLastAnimY=touchAddY;
|
||||||
|
touchSpringY.cancel();
|
||||||
|
touchSpringY=null;
|
||||||
|
}else{
|
||||||
|
touchAddLastAnimY=0;
|
||||||
|
}
|
||||||
|
touchDownX=ev.getX();
|
||||||
|
touchDownY=ev.getY();
|
||||||
|
}
|
||||||
|
case MotionEvent.ACTION_MOVE -> {
|
||||||
|
touchAddX=touchInterpolator.getInterpolation(Math.min(1f, Math.abs((ev.getX()-touchDownX)/(v.getWidth()/2f))));
|
||||||
|
touchAddY=touchInterpolator.getInterpolation(Math.min(1f, Math.abs((ev.getY()-touchDownY)/(v.getHeight()/2f))));
|
||||||
|
if(ev.getX()>touchDownX)
|
||||||
|
touchAddX=-touchAddX;
|
||||||
|
if(ev.getY()<touchDownY)
|
||||||
|
touchAddY=-touchAddY;
|
||||||
|
touchAddX+=touchAddLastAnimX;
|
||||||
|
touchAddY+=touchAddLastAnimY;
|
||||||
|
updateEffects();
|
||||||
|
}
|
||||||
|
case MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||||
|
touchSpringX=new SpringAnimation(touchSpringXHolder, 0f);
|
||||||
|
touchSpringX.setMinimumVisibleChange(0.01f);
|
||||||
|
touchSpringX.getSpring().setStiffness(SpringForce.STIFFNESS_LOW).setDampingRatio(0.85f);
|
||||||
|
touchSpringX.addEndListener((animation, canceled, value, velocity)->touchSpringX=null);
|
||||||
|
touchSpringX.start();
|
||||||
|
touchSpringY=new SpringAnimation(touchSpringYHolder, 0f);
|
||||||
|
touchSpringY.setMinimumVisibleChange(0.01f);
|
||||||
|
touchSpringY.getSpring().setStiffness(SpringForce.STIFFNESS_LOW).setDampingRatio(0.85f);
|
||||||
|
touchSpringY.addEndListener((animation, canceled, value, velocity)->touchSpringY=null);
|
||||||
|
touchSpringY.start();
|
||||||
|
updateEffects();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateEffects(){
|
||||||
|
for(ViewEffect view:views){
|
||||||
|
view.update(Math.min(1f, Math.max(-1f, pitch+touchAddX)), Math.min(1f, Math.max(-1f, roll+touchAddY)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class ViewEffect{
|
public static class ViewEffect{
|
||||||
private View view;
|
private View view;
|
||||||
private float minX, maxX, minY, maxY;
|
private float minX, maxX, minY, maxY;
|
||||||
|
|||||||
@@ -200,6 +200,11 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onBoostClick(View v){
|
private void onBoostClick(View v){
|
||||||
|
if (GlobalUserPreferences.confirmBeforeReblog) {
|
||||||
|
v.startAnimation(opacityIn);
|
||||||
|
onBoostLongClick(v);
|
||||||
|
return;
|
||||||
|
}
|
||||||
boost.setSelected(!item.status.reblogged);
|
boost.setSelected(!item.status.reblogged);
|
||||||
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setReblogged(item.status, !item.status.reblogged, null, r->boostConsumer(v, r));
|
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setReblogged(item.status, !item.status.reblogged, null, r->boostConsumer(v, r));
|
||||||
}
|
}
|
||||||
@@ -235,9 +240,9 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||||||
reblogHeader.setVisibility(item.status.reblogged ? View.GONE : View.VISIBLE);
|
reblogHeader.setVisibility(item.status.reblogged ? View.GONE : View.VISIBLE);
|
||||||
reblogAs.setVisibility(AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1 ? View.VISIBLE : View.GONE);
|
reblogAs.setVisibility(AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1 ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
itemPublic.setVisibility(item.status.reblogged || item.status.visibility.isLessVisibleThan(StatusPrivacy.PUBLIC) ? View.GONE : View.VISIBLE);
|
itemPublic.setVisibility(item.status.reblogged ? View.GONE : View.VISIBLE);
|
||||||
itemUnlisted.setVisibility(item.status.reblogged || item.status.visibility.isLessVisibleThan(StatusPrivacy.UNLISTED) ? View.GONE : View.VISIBLE);
|
itemUnlisted.setVisibility(item.status.reblogged ? View.GONE : View.VISIBLE);
|
||||||
itemFollowers.setVisibility(item.status.reblogged || item.status.visibility.isLessVisibleThan(StatusPrivacy.PRIVATE) ? View.GONE : View.VISIBLE);
|
itemFollowers.setVisibility(item.status.reblogged ? View.GONE : View.VISIBLE);
|
||||||
|
|
||||||
Drawable checkMark = ctx.getDrawable(R.drawable.ic_fluent_checkmark_circle_20_regular);
|
Drawable checkMark = ctx.getDrawable(R.drawable.ic_fluent_checkmark_circle_20_regular);
|
||||||
Drawable publicDrawable = ctx.getDrawable(R.drawable.ic_fluent_earth_24_regular);
|
Drawable publicDrawable = ctx.getDrawable(R.drawable.ic_fluent_earth_24_regular);
|
||||||
@@ -245,16 +250,6 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||||||
Drawable followersDrawable = ctx.getDrawable(R.drawable.ic_fluent_lock_closed_24_regular);
|
Drawable followersDrawable = ctx.getDrawable(R.drawable.ic_fluent_lock_closed_24_regular);
|
||||||
|
|
||||||
StatusPrivacy defaultVisibility = session.preferences != null ? session.preferences.postingDefaultVisibility : null;
|
StatusPrivacy defaultVisibility = session.preferences != null ? session.preferences.postingDefaultVisibility : null;
|
||||||
// e.g. post visibility is unlisted, but default is public
|
|
||||||
// in this case, we want to display the check mark on the most visible visibility
|
|
||||||
if (defaultVisibility != null && item.status.visibility.isLessVisibleThan(defaultVisibility)) {
|
|
||||||
for (StatusPrivacy vis : StatusPrivacy.values()) {
|
|
||||||
if (vis.equals(item.status.visibility)) {
|
|
||||||
defaultVisibility = vis;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
itemPublic.setCompoundDrawablesWithIntrinsicBounds(publicDrawable, null, StatusPrivacy.PUBLIC.equals(defaultVisibility) ? checkMark : null, null);
|
itemPublic.setCompoundDrawablesWithIntrinsicBounds(publicDrawable, null, StatusPrivacy.PUBLIC.equals(defaultVisibility) ? checkMark : null, null);
|
||||||
itemUnlisted.setCompoundDrawablesWithIntrinsicBounds(unlistedDrawable, null, StatusPrivacy.UNLISTED.equals(defaultVisibility) ? checkMark : null, null);
|
itemUnlisted.setCompoundDrawablesWithIntrinsicBounds(unlistedDrawable, null, StatusPrivacy.UNLISTED.equals(defaultVisibility) ? checkMark : null, null);
|
||||||
itemFollowers.setCompoundDrawablesWithIntrinsicBounds(followersDrawable, null, StatusPrivacy.PRIVATE.equals(defaultVisibility) ? checkMark : null, null);
|
itemFollowers.setCompoundDrawablesWithIntrinsicBounds(followersDrawable, null, StatusPrivacy.PRIVATE.equals(defaultVisibility) ? checkMark : null, null);
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
package org.joinmastodon.android.ui.displayitems;
|
package org.joinmastodon.android.ui.displayitems;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.graphics.Outline;
|
import android.graphics.Outline;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.Typeface;
|
||||||
import android.graphics.drawable.Animatable;
|
import android.graphics.drawable.Animatable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
@@ -40,6 +44,7 @@ import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
|||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Announcement;
|
import org.joinmastodon.android.model.Announcement;
|
||||||
import org.joinmastodon.android.model.Attachment;
|
import org.joinmastodon.android.model.Attachment;
|
||||||
|
import org.joinmastodon.android.model.ContentType;
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
import org.joinmastodon.android.model.ScheduledStatus;
|
import org.joinmastodon.android.model.ScheduledStatus;
|
||||||
@@ -79,13 +84,13 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
public final Status status;
|
public final Status status;
|
||||||
private boolean hasVisibilityToggle;
|
private boolean hasVisibilityToggle;
|
||||||
boolean needBottomPadding;
|
boolean needBottomPadding;
|
||||||
private String extraText;
|
private CharSequence extraText;
|
||||||
private Notification notification;
|
private Notification notification;
|
||||||
private ScheduledStatus scheduledStatus;
|
private ScheduledStatus scheduledStatus;
|
||||||
private Announcement announcement;
|
private Announcement announcement;
|
||||||
private Consumer<String> consumeReadAnnouncement;
|
private Consumer<String> consumeReadAnnouncement;
|
||||||
|
|
||||||
public HeaderStatusDisplayItem(String parentID, Account user, Instant createdAt, BaseStatusListFragment parentFragment, String accountID, Status status, String extraText, Notification notification, ScheduledStatus scheduledStatus){
|
public HeaderStatusDisplayItem(String parentID, Account user, Instant createdAt, BaseStatusListFragment parentFragment, String accountID, Status status, CharSequence extraText, Notification notification, ScheduledStatus scheduledStatus){
|
||||||
super(parentID, parentFragment);
|
super(parentID, parentFragment);
|
||||||
user=scheduledStatus != null ? AccountSessionManager.getInstance().getAccount(accountID).self : user;
|
user=scheduledStatus != null ? AccountSessionManager.getInstance().getAccount(accountID).self : user;
|
||||||
this.user=user;
|
this.user=user;
|
||||||
@@ -110,6 +115,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.extraText=extraText;
|
this.extraText=extraText;
|
||||||
|
emojiHelper.addText(extraText);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HeaderStatusDisplayItem fromAnnouncement(Announcement a, Status fakeStatus, Account instanceUser, BaseStatusListFragment parentFragment, String accountID, Consumer<String> consumeReadID) {
|
public static HeaderStatusDisplayItem fromAnnouncement(Announcement a, Status fakeStatus, Account instanceUser, BaseStatusListFragment parentFragment, String accountID, Consumer<String> consumeReadID) {
|
||||||
@@ -212,6 +218,9 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
public void onSuccess(GetStatusSourceText.Response result){
|
public void onSuccess(GetStatusSourceText.Response result){
|
||||||
args.putString("sourceText", result.text);
|
args.putString("sourceText", result.text);
|
||||||
args.putString("sourceSpoiler", result.spoilerText);
|
args.putString("sourceSpoiler", result.spoilerText);
|
||||||
|
if (result.contentType != null) {
|
||||||
|
args.putString("sourceContentType", result.contentType.name());
|
||||||
|
}
|
||||||
if (redraft) {
|
if (redraft) {
|
||||||
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{
|
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{
|
||||||
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
||||||
@@ -264,7 +273,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
progress.dismiss();
|
progress.dismiss();
|
||||||
}, rel->{
|
}, rel->{
|
||||||
relationship=rel;
|
relationship=rel;
|
||||||
Toast.makeText(activity, activity.getString(rel.following ? R.string.followed_user : R.string.unfollowed_user, account.getShortUsername()), Toast.LENGTH_SHORT).show();
|
Toast.makeText(activity, activity.getString(rel.following ? R.string.followed_user : rel.requested ? R.string.following_user_requested : R.string.unfollowed_user, account.getDisplayUsername()), Toast.LENGTH_SHORT).show();
|
||||||
});
|
});
|
||||||
}else if(id==R.id.block_domain){
|
}else if(id==R.id.block_domain){
|
||||||
UiUtils.confirmToggleBlockDomain(activity, item.parentFragment.getAccountID(), account.getDomain(), relationship!=null && relationship.domainBlocking, ()->{});
|
UiUtils.confirmToggleBlockDomain(activity, item.parentFragment.getAccountID(), account.getDomain(), relationship!=null && relationship.domainBlocking, ()->{});
|
||||||
@@ -463,6 +472,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
|
|
||||||
Account account=item.user;
|
Account account=item.user;
|
||||||
|
String username = account.getShortUsername();
|
||||||
boolean isOwnPost=AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account);
|
boolean isOwnPost=AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account);
|
||||||
boolean isPostScheduled=item.scheduledStatus!=null;
|
boolean isPostScheduled=item.scheduledStatus!=null;
|
||||||
menu.findItem(R.id.open_with_account).setVisible(!isPostScheduled && hasMultipleAccounts);
|
menu.findItem(R.id.open_with_account).setVisible(!isPostScheduled && hasMultipleAccounts);
|
||||||
@@ -498,14 +508,15 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
manageUserLists.setVisible(false);
|
manageUserLists.setVisible(false);
|
||||||
}else{
|
}else{
|
||||||
mute.setVisible(true);
|
mute.setVisible(true);
|
||||||
block.setVisible(true);
|
// hiding when following to keep menu item count equal (trading it for user lists)
|
||||||
|
block.setVisible(relationship == null || !relationship.following);
|
||||||
report.setVisible(true);
|
report.setVisible(true);
|
||||||
follow.setVisible(relationship==null || relationship.following || (!relationship.blocking && !relationship.blockedBy && !relationship.domainBlocking && !relationship.muting));
|
follow.setVisible(relationship==null || relationship.following || (!relationship.blocking && !relationship.blockedBy && !relationship.domainBlocking && !relationship.muting));
|
||||||
mute.setTitle(item.parentFragment.getString(relationship!=null && relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
|
mute.setTitle(item.parentFragment.getString(relationship!=null && relationship.muting ? R.string.unmute_user : R.string.mute_user, username));
|
||||||
mute.setIcon(relationship!=null && relationship.muting ? R.drawable.ic_fluent_speaker_0_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
|
mute.setIcon(relationship!=null && relationship.muting ? R.drawable.ic_fluent_speaker_0_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
|
||||||
UiUtils.insetPopupMenuIcon(item.parentFragment.getContext(), mute);
|
UiUtils.insetPopupMenuIcon(item.parentFragment.getContext(), mute);
|
||||||
block.setTitle(item.parentFragment.getString(relationship!=null && relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getShortUsername()));
|
block.setTitle(item.parentFragment.getString(relationship!=null && relationship.blocking ? R.string.unblock_user : R.string.block_user, username));
|
||||||
report.setTitle(item.parentFragment.getString(R.string.report_user, account.getShortUsername()));
|
report.setTitle(item.parentFragment.getString(R.string.report_user, username));
|
||||||
// disabled in megalodon. domain blocks from a post clutters the context menu and looks out of place
|
// disabled in megalodon. domain blocks from a post clutters the context menu and looks out of place
|
||||||
// if(!account.isLocal()){
|
// if(!account.isLocal()){
|
||||||
// blockDomain.setVisible(true);
|
// blockDomain.setVisible(true);
|
||||||
@@ -514,12 +525,53 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
blockDomain.setVisible(false);
|
blockDomain.setVisible(false);
|
||||||
// }
|
// }
|
||||||
boolean following = relationship!=null && relationship.following;
|
boolean following = relationship!=null && relationship.following;
|
||||||
follow.setTitle(item.parentFragment.getString(following ? R.string.unfollow_user : R.string.follow_user, account.getShortUsername()));
|
follow.setTitle(item.parentFragment.getString(following ? R.string.unfollow_user : R.string.follow_user, username));
|
||||||
follow.setIcon(following ? R.drawable.ic_fluent_person_delete_24_regular : R.drawable.ic_fluent_person_add_24_regular);
|
follow.setIcon(following ? R.drawable.ic_fluent_person_delete_24_regular : R.drawable.ic_fluent_person_add_24_regular);
|
||||||
manageUserLists.setVisible(relationship != null && relationship.following);
|
manageUserLists.setVisible(relationship != null && relationship.following);
|
||||||
manageUserLists.setTitle(item.parentFragment.getString(R.string.sk_lists_with_user, account.getShortUsername()));
|
manageUserLists.setTitle(item.parentFragment.getString(R.string.sk_lists_with_user, username));
|
||||||
UiUtils.insetPopupMenuIcon(item.parentFragment.getContext(), follow);
|
UiUtils.insetPopupMenuIcon(item.parentFragment.getContext(), follow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
workaroundChangingMenuItemWidths(menu, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ugliest piece of code you'll see in a while: i measure the menu items' text widths to
|
||||||
|
// determine the biggest one, because it's probably not being displayed at first
|
||||||
|
// (before the relationship loaded). i take the largest one's size and add a space to the
|
||||||
|
// last item ("open in browser") until it takes up as much space as the largest item.
|
||||||
|
// goal: no more ugly ellipsis after the relationship loads in when opening the context menu
|
||||||
|
// of a post
|
||||||
|
private void workaroundChangingMenuItemWidths(Menu menu, String username) {
|
||||||
|
String openInBrowserText = item.parentFragment.getString(R.string.open_in_browser);
|
||||||
|
if (relationship == null) {
|
||||||
|
float largestWidth = 0;
|
||||||
|
Paint paint = new Paint();
|
||||||
|
paint.setTypeface(Typeface.create("sans-serif", Typeface.NORMAL));
|
||||||
|
String[] otherStrings = new String[] {
|
||||||
|
item.parentFragment.getString(R.string.unfollow_user, username),
|
||||||
|
item.parentFragment.getString(R.string.unblock_user, username),
|
||||||
|
item.parentFragment.getString(R.string.unmute_user, username),
|
||||||
|
item.parentFragment.getString(R.string.sk_lists_with_user, username),
|
||||||
|
};
|
||||||
|
for (int i = 0; i < menu.size(); i++) {
|
||||||
|
MenuItem item = menu.getItem(i);
|
||||||
|
if (item.getItemId() == R.id.open_in_browser || !item.isVisible()) continue;
|
||||||
|
float width = paint.measureText(menu.getItem(i).getTitle().toString());
|
||||||
|
if (width > largestWidth) largestWidth = width;
|
||||||
|
}
|
||||||
|
for (String str : otherStrings) {
|
||||||
|
float width = paint.measureText(str);
|
||||||
|
if (width > largestWidth) largestWidth = width;
|
||||||
|
}
|
||||||
|
float textWidth = paint.measureText(openInBrowserText);
|
||||||
|
float missingWidth = Math.max(0, largestWidth - textWidth);
|
||||||
|
float singleSpaceWidth = paint.measureText(" ");
|
||||||
|
int howManySpaces = (int) Math.ceil(missingWidth / singleSpaceWidth);
|
||||||
|
String enlargedText = openInBrowserText + " ".repeat(howManySpaces);
|
||||||
|
menu.findItem(R.id.open_in_browser).setTitle(enlargedText);
|
||||||
|
} else {
|
||||||
|
menu.findItem(R.id.open_in_browser).setTitle(openInBrowserText);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package org.joinmastodon.android.ui.displayitems;
|
package org.joinmastodon.android.ui.displayitems;
|
||||||
|
|
||||||
import static org.joinmastodon.android.ui.utils.MediaAttachmentViewController.altWrapPadding;
|
import static org.joinmastodon.android.GlobalUserPreferences.*;
|
||||||
|
|
||||||
import android.animation.Animator;
|
import android.animation.Animator;
|
||||||
import android.animation.AnimatorListenerAdapter;
|
import android.animation.AnimatorListenerAdapter;
|
||||||
@@ -55,7 +55,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
|||||||
for(Attachment att:attachments){
|
for(Attachment att:attachments){
|
||||||
requests.add(new UrlImageLoaderRequest(switch(att.type){
|
requests.add(new UrlImageLoaderRequest(switch(att.type){
|
||||||
case IMAGE -> att.url;
|
case IMAGE -> att.url;
|
||||||
case VIDEO, GIFV -> att.previewUrl;
|
case VIDEO, GIFV -> att.previewUrl != null ? att.previewUrl : att.url;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+att.type);
|
default -> throw new IllegalStateException("Unexpected value: "+att.type);
|
||||||
}, 1000, 1000));
|
}, 1000, 1000));
|
||||||
}
|
}
|
||||||
@@ -183,10 +183,11 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
|||||||
altTextIndex=index;
|
altTextIndex=index;
|
||||||
Attachment att=item.attachments.get(index);
|
Attachment att=item.attachments.get(index);
|
||||||
boolean hasAltText = !TextUtils.isEmpty(att.description);
|
boolean hasAltText = !TextUtils.isEmpty(att.description);
|
||||||
altTextButton.setVisibility(hasAltText && GlobalUserPreferences.showAltIndicator ? View.VISIBLE : View.GONE);
|
if ((hasAltText && !showAltIndicator) || (!hasAltText && !showNoAltIndicator)) return;
|
||||||
noAltTextButton.setVisibility(!hasAltText && GlobalUserPreferences.showNoAltIndicator ? View.VISIBLE : View.GONE);
|
altTextButton.setVisibility(hasAltText && showAltIndicator ? View.VISIBLE : View.GONE);
|
||||||
altText.setVisibility(hasAltText && GlobalUserPreferences.showAltIndicator ? View.VISIBLE : View.GONE);
|
noAltTextButton.setVisibility(!hasAltText && showNoAltIndicator ? View.VISIBLE : View.GONE);
|
||||||
noAltText.setVisibility(!hasAltText && GlobalUserPreferences.showNoAltIndicator ? View.VISIBLE : View.GONE);
|
altText.setVisibility(hasAltText && showAltIndicator ? View.VISIBLE : View.GONE);
|
||||||
|
noAltText.setVisibility(!hasAltText && showNoAltIndicator ? View.VISIBLE : View.GONE);
|
||||||
altText.setText(att.description);
|
altText.setText(att.description);
|
||||||
altTextWrapper.setVisibility(View.VISIBLE);
|
altTextWrapper.setVisibility(View.VISIBLE);
|
||||||
altTextWrapper.setBackgroundResource(hasAltText ? R.drawable.bg_image_alt_overlay : R.drawable.bg_image_no_alt_overlay);
|
altTextWrapper.setBackgroundResource(hasAltText ? R.drawable.bg_image_alt_overlay : R.drawable.bg_image_no_alt_overlay);
|
||||||
@@ -202,15 +203,16 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
|||||||
btnL-=loc[0];
|
btnL-=loc[0];
|
||||||
btnT-=loc[1];
|
btnT-=loc[1];
|
||||||
|
|
||||||
|
ViewGroup.MarginLayoutParams margins = (ViewGroup.MarginLayoutParams) altTextWrapper.getLayoutParams();
|
||||||
ArrayList<Animator> anims=new ArrayList<>();
|
ArrayList<Animator> anims=new ArrayList<>();
|
||||||
anims.add(ObjectAnimator.ofFloat(altTextButton, View.ALPHA, 1, 0));
|
anims.add(ObjectAnimator.ofFloat(altTextButton, View.ALPHA, 1, 0));
|
||||||
anims.add(ObjectAnimator.ofFloat(noAltTextButton, View.ALPHA, 1, 0));
|
anims.add(ObjectAnimator.ofFloat(noAltTextButton, View.ALPHA, 1, 0));
|
||||||
anims.add(ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, 0, 1));
|
anims.add(ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, 0, 1));
|
||||||
anims.add(ObjectAnimator.ofFloat(altTextClose, View.ALPHA, 0, 1));
|
anims.add(ObjectAnimator.ofFloat(altTextClose, View.ALPHA, 0, 1));
|
||||||
anims.add(ObjectAnimator.ofInt(altTextWrapper, "left", btnL+altWrapPadding[0], altTextWrapper.getLeft()));
|
anims.add(ObjectAnimator.ofInt(altTextWrapper, "left", btnL+margins.leftMargin, altTextWrapper.getLeft()));
|
||||||
anims.add(ObjectAnimator.ofInt(altTextWrapper, "top", btnT+altWrapPadding[1], altTextWrapper.getTop()));
|
anims.add(ObjectAnimator.ofInt(altTextWrapper, "top", btnT+margins.topMargin, altTextWrapper.getTop()));
|
||||||
anims.add(ObjectAnimator.ofInt(altTextWrapper, "right", btnL+v.getWidth()-altWrapPadding[2], altTextWrapper.getRight()));
|
anims.add(ObjectAnimator.ofInt(altTextWrapper, "right", btnL+v.getWidth()-margins.rightMargin, altTextWrapper.getRight()));
|
||||||
anims.add(ObjectAnimator.ofInt(altTextWrapper, "bottom", btnT+v.getHeight()-altWrapPadding[3], altTextWrapper.getBottom()));
|
anims.add(ObjectAnimator.ofInt(altTextWrapper, "bottom", btnT+v.getHeight()-margins.bottomMargin, altTextWrapper.getBottom()));
|
||||||
for(Animator a:anims)
|
for(Animator a:anims)
|
||||||
a.setDuration(300);
|
a.setDuration(300);
|
||||||
|
|
||||||
@@ -247,10 +249,14 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
|||||||
altTextAnimator.cancel();
|
altTextAnimator.cancel();
|
||||||
|
|
||||||
View btn=controllers.get(altTextIndex).btnsWrap;
|
View btn=controllers.get(altTextIndex).btnsWrap;
|
||||||
|
int i=0;
|
||||||
for(MediaAttachmentViewController c:controllers){
|
for(MediaAttachmentViewController c:controllers){
|
||||||
if(c.btnsWrap!=null && c.btnsWrap!=btn) {
|
boolean hasAltText = !TextUtils.isEmpty(item.attachments.get(i).description);
|
||||||
c.btnsWrap.setVisibility(View.VISIBLE);
|
if(c.btnsWrap!=null
|
||||||
}
|
&& c.btnsWrap!=btn
|
||||||
|
&& ((hasAltText && showAltIndicator) || (!hasAltText && showNoAltIndicator))
|
||||||
|
) c.btnsWrap.setVisibility(View.VISIBLE);
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
int[] loc={0, 0};
|
int[] loc={0, 0};
|
||||||
@@ -260,15 +266,16 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
|||||||
btnL-=loc[0];
|
btnL-=loc[0];
|
||||||
btnT-=loc[1];
|
btnT-=loc[1];
|
||||||
|
|
||||||
|
ViewGroup.MarginLayoutParams margins = (ViewGroup.MarginLayoutParams) altTextWrapper.getLayoutParams();
|
||||||
ArrayList<Animator> anims=new ArrayList<>();
|
ArrayList<Animator> anims=new ArrayList<>();
|
||||||
anims.add(ObjectAnimator.ofFloat(altTextButton, View.ALPHA, 1));
|
anims.add(ObjectAnimator.ofFloat(altTextButton, View.ALPHA, 1));
|
||||||
anims.add(ObjectAnimator.ofFloat(noAltTextButton, View.ALPHA, 1));
|
anims.add(ObjectAnimator.ofFloat(noAltTextButton, View.ALPHA, 1));
|
||||||
anims.add(ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, 0));
|
anims.add(ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, 0));
|
||||||
anims.add(ObjectAnimator.ofFloat(altTextClose, View.ALPHA, 0));
|
anims.add(ObjectAnimator.ofFloat(altTextClose, View.ALPHA, 0));
|
||||||
anims.add(ObjectAnimator.ofInt(altTextWrapper, "left", btnL+altWrapPadding[0]));
|
anims.add(ObjectAnimator.ofInt(altTextWrapper, "left", btnL+margins.leftMargin));
|
||||||
anims.add(ObjectAnimator.ofInt(altTextWrapper, "top", btnT+altWrapPadding[1]));
|
anims.add(ObjectAnimator.ofInt(altTextWrapper, "top", btnT+margins.topMargin));
|
||||||
anims.add(ObjectAnimator.ofInt(altTextWrapper, "right", btnL+btn.getWidth()-altWrapPadding[2]));
|
anims.add(ObjectAnimator.ofInt(altTextWrapper, "right", btnL+btn.getWidth()-margins.rightMargin));
|
||||||
anims.add(ObjectAnimator.ofInt(altTextWrapper, "bottom", btnT+btn.getHeight()-altWrapPadding[3]));
|
anims.add(ObjectAnimator.ofInt(altTextWrapper, "bottom", btnT+btn.getHeight()-margins.bottomMargin));
|
||||||
for(Animator a:anims)
|
for(Animator a:anims)
|
||||||
a.setDuration(300);
|
a.setDuration(300);
|
||||||
|
|
||||||
|
|||||||
@@ -32,23 +32,23 @@ import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
|||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
||||||
private CharSequence text, compactText;
|
private CharSequence text;
|
||||||
@DrawableRes
|
@DrawableRes
|
||||||
private int icon;
|
private int icon;
|
||||||
private StatusPrivacy visibility;
|
private StatusPrivacy visibility;
|
||||||
@DrawableRes
|
@DrawableRes
|
||||||
private int iconEnd;
|
private int iconEnd;
|
||||||
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
|
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper(), fullTextEmojiHelper;
|
||||||
private View.OnClickListener handleClick;
|
private View.OnClickListener handleClick;
|
||||||
boolean belowHeader, needBottomPadding;
|
boolean belowHeader, needBottomPadding;
|
||||||
ReblogOrReplyLineStatusDisplayItem extra;
|
ReblogOrReplyLineStatusDisplayItem extra;
|
||||||
String contentDescription;
|
CharSequence fullText;
|
||||||
|
|
||||||
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick) {
|
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick) {
|
||||||
this(parentID, parentFragment, text, emojis, icon, visibility, handleClick, null);
|
this(parentID, parentFragment, text, emojis, icon, visibility, handleClick, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, String contentDescription) {
|
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, CharSequence fullText) {
|
||||||
super(parentID, parentFragment);
|
super(parentID, parentFragment);
|
||||||
SpannableStringBuilder ssb=new SpannableStringBuilder(text);
|
SpannableStringBuilder ssb=new SpannableStringBuilder(text);
|
||||||
HtmlParser.parseCustomEmoji(ssb, emojis);
|
HtmlParser.parseCustomEmoji(ssb, emojis);
|
||||||
@@ -59,7 +59,15 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
|||||||
TypedValue outValue = new TypedValue();
|
TypedValue outValue = new TypedValue();
|
||||||
context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
|
context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
|
||||||
updateVisibility(visibility);
|
updateVisibility(visibility);
|
||||||
this.contentDescription = contentDescription;
|
|
||||||
|
if (fullText != null) {
|
||||||
|
fullTextEmojiHelper = new CustomEmojiHelper();
|
||||||
|
SpannableStringBuilder fullTextSsb = new SpannableStringBuilder(fullText);
|
||||||
|
HtmlParser.parseCustomEmoji(fullTextSsb, emojis);
|
||||||
|
this.fullText=fullTextSsb;
|
||||||
|
fullTextEmojiHelper.setText(fullTextSsb);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateVisibility(StatusPrivacy visibility) {
|
public void updateVisibility(StatusPrivacy visibility) {
|
||||||
@@ -90,31 +98,23 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
|||||||
public static class Holder extends StatusDisplayItem.Holder<ReblogOrReplyLineStatusDisplayItem> implements ImageLoaderViewHolder{
|
public static class Holder extends StatusDisplayItem.Holder<ReblogOrReplyLineStatusDisplayItem> implements ImageLoaderViewHolder{
|
||||||
private final TextView text, extraText;
|
private final TextView text, extraText;
|
||||||
private final View separator;
|
private final View separator;
|
||||||
private int currentOrientation = -1;
|
private final ViewGroup parent;
|
||||||
|
|
||||||
public Holder(Activity activity, ViewGroup parent){
|
public Holder(Activity activity, ViewGroup parent){
|
||||||
super(activity, R.layout.display_item_reblog_or_reply_line, parent);
|
super(activity, R.layout.display_item_reblog_or_reply_line, parent);
|
||||||
|
this.parent = parent;
|
||||||
text=findViewById(R.id.text);
|
text=findViewById(R.id.text);
|
||||||
extraText=findViewById(R.id.extra_text);
|
extraText=findViewById(R.id.extra_text);
|
||||||
separator=findViewById(R.id.separator);
|
separator=findViewById(R.id.separator);
|
||||||
if (GlobalUserPreferences.replyLineAboveHeader && GlobalUserPreferences.compactReblogReplyLine) {
|
if (GlobalUserPreferences.replyLineAboveHeader && GlobalUserPreferences.compactReblogReplyLine) {
|
||||||
itemView.getViewTreeObserver().addOnPreDrawListener(() -> {
|
parent.addOnLayoutChangeListener((v, l, t, right, b, ol, ot, oldRight, ob) -> {
|
||||||
if (item == null) return true;
|
if (right != oldRight) layoutLine();
|
||||||
int orientation = ((LinearLayout) itemView).getOrientation();
|
|
||||||
if (orientation == currentOrientation) return true; // only run once
|
|
||||||
currentOrientation = orientation;
|
|
||||||
extraText.setPaddingRelative(extraText.getPaddingStart(), item.extra != null && orientation == LinearLayout.VERTICAL ? 0 : V.dp(16), extraText.getPaddingEnd(), extraText.getPaddingBottom());
|
|
||||||
separator.setVisibility(item.extra != null && orientation == LinearLayout.HORIZONTAL ? View.VISIBLE : View.GONE);
|
|
||||||
((LinearLayout) itemView).removeView(extraText);
|
|
||||||
if (orientation == LinearLayout.VERTICAL) ((LinearLayout) itemView).addView(extraText);
|
|
||||||
else ((LinearLayout) itemView).addView(extraText, 0);
|
|
||||||
return true;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bindLine(ReblogOrReplyLineStatusDisplayItem item, TextView text) {
|
private void bindLine(ReblogOrReplyLineStatusDisplayItem item, TextView text) {
|
||||||
if (item.contentDescription != null) text.setContentDescription(item.contentDescription);
|
if (item.fullText != null) text.setContentDescription(item.fullText);
|
||||||
text.setText(item.text);
|
text.setText(item.text);
|
||||||
text.setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, item.iconEnd, 0);
|
text.setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, item.iconEnd, 0);
|
||||||
text.setOnClickListener(item.handleClick);
|
text.setOnClickListener(item.handleClick);
|
||||||
@@ -146,6 +146,27 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
|||||||
params.topMargin = item.belowHeader ? V.dp(-6) : 0;
|
params.topMargin = item.belowHeader ? V.dp(-6) : 0;
|
||||||
itemView.setLayoutParams(params);
|
itemView.setLayoutParams(params);
|
||||||
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : 0);
|
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : 0);
|
||||||
|
layoutLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void layoutLine() {
|
||||||
|
// layout line only if above header, compact and has extra
|
||||||
|
if (!GlobalUserPreferences.replyLineAboveHeader
|
||||||
|
|| !GlobalUserPreferences.compactReblogReplyLine
|
||||||
|
|| item.extra == null) return;
|
||||||
|
itemView.measure(
|
||||||
|
View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY),
|
||||||
|
View.MeasureSpec.UNSPECIFIED);
|
||||||
|
boolean isVertical = ((LinearLayout) itemView).getOrientation() == LinearLayout.VERTICAL;
|
||||||
|
extraText.setPaddingRelative(extraText.getPaddingStart(), item.extra != null && isVertical ? 0 : V.dp(16), extraText.getPaddingEnd(), extraText.getPaddingBottom());
|
||||||
|
separator.setVisibility(item.extra != null && !isVertical ? View.VISIBLE : View.GONE);
|
||||||
|
((LinearLayout) itemView).removeView(extraText);
|
||||||
|
if (isVertical) ((LinearLayout) itemView).addView(extraText);
|
||||||
|
else ((LinearLayout) itemView).addView(extraText, 0);
|
||||||
|
text.setText(isVertical ? item.fullText : item.text);
|
||||||
|
if (item.extra != null) {
|
||||||
|
extraText.setText(isVertical ? item.extra.fullText : item.extra.text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -81,18 +81,10 @@ public abstract class StatusDisplayItem{
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification){
|
|
||||||
return buildItems(fragment, status, accountID, parentObject, knownAccounts, inset, addFooter, notification, false, Filter.FilterContext.HOME);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, Filter.FilterContext filterContext){
|
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, Filter.FilterContext filterContext){
|
||||||
return buildItems(fragment, status, accountID, parentObject, knownAccounts, inset, addFooter, notification, false, filterContext);
|
return buildItems(fragment, status, accountID, parentObject, knownAccounts, inset, addFooter, notification, false, filterContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate){
|
|
||||||
return buildItems(fragment, status, accountID, parentObject, knownAccounts, inset, addFooter, notification, disableTranslate, Filter.FilterContext.HOME);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate, Filter.FilterContext filterContext){
|
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate, Filter.FilterContext filterContext){
|
||||||
String parentID=parentObject.getID();
|
String parentID=parentObject.getID();
|
||||||
ArrayList<StatusDisplayItem> items=new ArrayList<>();
|
ArrayList<StatusDisplayItem> items=new ArrayList<>();
|
||||||
@@ -102,12 +94,8 @@ public abstract class StatusDisplayItem{
|
|||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
ScheduledStatus scheduledStatus = parentObject instanceof ScheduledStatus ? (ScheduledStatus) parentObject : null;
|
ScheduledStatus scheduledStatus = parentObject instanceof ScheduledStatus ? (ScheduledStatus) parentObject : null;
|
||||||
|
|
||||||
List<Filter> filters = AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream()
|
if (!statusForContent.filterRevealed) {
|
||||||
.filter(f -> f.context.contains(filterContext)).collect(Collectors.toList());
|
statusForContent.filterRevealed = new StatusFilterPredicate(accountID, filterContext, Filter.FilterAction.WARN).test(status);
|
||||||
StatusFilterPredicate filterPredicate = new StatusFilterPredicate(filters);
|
|
||||||
|
|
||||||
if(!statusForContent.filterRevealed){
|
|
||||||
statusForContent.filterRevealed = filterPredicate.testWithWarning(status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ReblogOrReplyLineStatusDisplayItem replyLine = null;
|
ReblogOrReplyLineStatusDisplayItem replyLine = null;
|
||||||
@@ -201,8 +189,6 @@ public abstract class StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
if(addFooter){
|
if(addFooter){
|
||||||
items.add(new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID));
|
items.add(new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID));
|
||||||
if(status.hasGapAfter && !(fragment instanceof ThreadFragment))
|
|
||||||
items.add(new GapStatusDisplayItem(parentID, fragment));
|
|
||||||
}
|
}
|
||||||
int i=1;
|
int i=1;
|
||||||
for(StatusDisplayItem item:items){
|
for(StatusDisplayItem item:items){
|
||||||
@@ -210,13 +196,16 @@ public abstract class StatusDisplayItem{
|
|||||||
item.index=i++;
|
item.index=i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!statusForContent.filterRevealed) {
|
ArrayList<StatusDisplayItem> result = statusForContent.filterRevealed ? items :
|
||||||
return new ArrayList<>(List.of(
|
new ArrayList<>(List.of(new WarningFilteredStatusDisplayItem(parentID, fragment, statusForContent, items)));
|
||||||
new WarningFilteredStatusDisplayItem(parentID, fragment, statusForContent, items)
|
|
||||||
));
|
if (addFooter && status.hasGapAfter && !(fragment instanceof ThreadFragment)) {
|
||||||
|
StatusDisplayItem gap = new GapStatusDisplayItem(parentID, fragment);
|
||||||
|
gap.index = i++;
|
||||||
|
result.add(gap);
|
||||||
}
|
}
|
||||||
|
|
||||||
return items;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, List<StatusDisplayItem> items){
|
public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, List<StatusDisplayItem> items){
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.graphics.drawable.Drawable;
|
|||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewTreeObserver;
|
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.ScrollView;
|
import android.widget.ScrollView;
|
||||||
@@ -49,9 +48,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
private CharSequence parsedSpoilerText;
|
private CharSequence parsedSpoilerText;
|
||||||
public boolean textSelectable;
|
public boolean textSelectable;
|
||||||
public final Status status;
|
public final Status status;
|
||||||
public boolean disableTranslate;
|
public boolean disableTranslate, translationShown;
|
||||||
public boolean translated = false;
|
|
||||||
public TranslatedStatus translation = null;
|
|
||||||
private AccountSession session;
|
private AccountSession session;
|
||||||
public static final Pattern BOTTOM_TEXT_PATTERN = Pattern.compile("(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️)(?:\uD83D\uDC49\uD83D\uDC48(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️))*\uD83D\uDC49\uD83D\uDC48");
|
public static final Pattern BOTTOM_TEXT_PATTERN = Pattern.compile("(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️)(?:\uD83D\uDC49\uD83D\uDC48(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️))*\uD83D\uDC49\uD83D\uDC48");
|
||||||
|
|
||||||
@@ -60,6 +57,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
this.text=text;
|
this.text=text;
|
||||||
this.status=status;
|
this.status=status;
|
||||||
this.disableTranslate=disableTranslate;
|
this.disableTranslate=disableTranslate;
|
||||||
|
this.translationShown=status.translationShown;
|
||||||
emojiHelper.setText(text);
|
emojiHelper.setText(text);
|
||||||
if(!TextUtils.isEmpty(status.spoilerText)){
|
if(!TextUtils.isEmpty(status.spoilerText)){
|
||||||
parsedSpoilerText=HtmlParser.parseCustomEmoji(status.spoilerText, status.emojis);
|
parsedSpoilerText=HtmlParser.parseCustomEmoji(status.spoilerText, status.emojis);
|
||||||
@@ -69,6 +67,11 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
session = AccountSessionManager.getInstance().getAccount(parentFragment.getAccountID());
|
session = AccountSessionManager.getInstance().getAccount(parentFragment.getAccountID());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setTranslationShown(boolean translationShown) {
|
||||||
|
this.translationShown = translationShown;
|
||||||
|
status.translationShown = translationShown;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Type getType(){
|
public Type getType(){
|
||||||
return Type.TEXT;
|
return Type.TEXT;
|
||||||
@@ -131,8 +134,8 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(TextStatusDisplayItem item){
|
public void onBind(TextStatusDisplayItem item){
|
||||||
text.setText(item.translated
|
text.setText(item.translationShown
|
||||||
? HtmlParser.parse(item.translation.content, item.status.emojis, item.status.mentions, item.status.tags, item.parentFragment.getAccountID())
|
? HtmlParser.parse(item.status.translation.content, item.status.emojis, item.status.mentions, item.status.tags, item.parentFragment.getAccountID())
|
||||||
: item.text);
|
: item.text);
|
||||||
text.setTextIsSelectable(item.textSelectable);
|
text.setTextIsSelectable(item.textSelectable);
|
||||||
if (item.textSelectable) {
|
if (item.textSelectable) {
|
||||||
@@ -169,8 +172,14 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
boolean translateEnabled = !item.disableTranslate && instanceInfo != null &&
|
boolean translateEnabled = !item.disableTranslate && instanceInfo != null &&
|
||||||
instanceInfo.v2 != null && instanceInfo.v2.configuration.translation != null &&
|
instanceInfo.v2 != null && instanceInfo.v2.configuration.translation != null &&
|
||||||
instanceInfo.v2.configuration.translation.enabled;
|
instanceInfo.v2.configuration.translation.enabled;
|
||||||
boolean isBottomText = BOTTOM_TEXT_PATTERN.matcher(item.status.getStrippedText()).find();
|
String bottomText = null;
|
||||||
boolean translateVisible = (isBottomText || (
|
try {
|
||||||
|
bottomText = BOTTOM_TEXT_PATTERN.matcher(item.status.getStrippedText()).find()
|
||||||
|
? new StatusTextEncoder(Bottom::decode).decode(item.status.getStrippedText(), BOTTOM_TEXT_PATTERN)
|
||||||
|
: null;
|
||||||
|
} catch (TranslationError ignored) {}
|
||||||
|
|
||||||
|
boolean translateVisible = (bottomText != null || (
|
||||||
translateEnabled &&
|
translateEnabled &&
|
||||||
!item.status.visibility.isLessVisibleThan(StatusPrivacy.UNLISTED) &&
|
!item.status.visibility.isLessVisibleThan(StatusPrivacy.UNLISTED) &&
|
||||||
item.status.language != null &&
|
item.status.language != null &&
|
||||||
@@ -178,17 +187,18 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
!item.status.language.equalsIgnoreCase(Locale.getDefault().getLanguage())))
|
!item.status.language.equalsIgnoreCase(Locale.getDefault().getLanguage())))
|
||||||
&& (!GlobalUserPreferences.translateButtonOpenedOnly || item.textSelectable);
|
&& (!GlobalUserPreferences.translateButtonOpenedOnly || item.textSelectable);
|
||||||
translateWrap.setVisibility(translateVisible ? View.VISIBLE : View.GONE);
|
translateWrap.setVisibility(translateVisible ? View.VISIBLE : View.GONE);
|
||||||
translateButton.setText(item.translated ? R.string.sk_translate_show_original : R.string.sk_translate_post);
|
translateButton.setText(item.translationShown ? R.string.sk_translate_show_original : R.string.sk_translate_post);
|
||||||
translateInfo.setText(item.translated ? itemView.getResources().getString(R.string.sk_translated_using, isBottomText ? "bottom-java" : item.translation.provider) : "");
|
translateInfo.setText(item.translationShown ? itemView.getResources().getString(R.string.sk_translated_using, bottomText != null ? "bottom-java" : item.status.translation.provider) : "");
|
||||||
|
String finalBottomText = bottomText;
|
||||||
translateButton.setOnClickListener(v->{
|
translateButton.setOnClickListener(v->{
|
||||||
if (item.translation == null) {
|
if (item.status.translation == null) {
|
||||||
if (isBottomText) {
|
if (finalBottomText != null) {
|
||||||
try {
|
try {
|
||||||
item.translation = new TranslatedStatus();
|
item.status.translation = new TranslatedStatus();
|
||||||
item.translation.content = new StatusTextEncoder(Bottom::decode).decode(item.status.getStrippedText(), BOTTOM_TEXT_PATTERN);
|
item.status.translation.content = finalBottomText;
|
||||||
item.translated = true;
|
item.setTranslationShown(true);
|
||||||
} catch (TranslationError err) {
|
} catch (TranslationError err) {
|
||||||
item.translation = null;
|
item.status.translation = null;
|
||||||
Toast.makeText(itemView.getContext(), err.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
|
Toast.makeText(itemView.getContext(), err.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
rebind();
|
rebind();
|
||||||
@@ -200,8 +210,8 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
new TranslateStatus(item.status.id).setCallback(new Callback<>() {
|
new TranslateStatus(item.status.id).setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(TranslatedStatus translatedStatus) {
|
public void onSuccess(TranslatedStatus translatedStatus) {
|
||||||
item.translation = translatedStatus;
|
item.status.translation = translatedStatus;
|
||||||
item.translated = true;
|
item.setTranslationShown(true);
|
||||||
if (item.parentFragment.getActivity() == null) return;
|
if (item.parentFragment.getActivity() == null) return;
|
||||||
translateProgress.setVisibility(View.GONE);
|
translateProgress.setVisibility(View.GONE);
|
||||||
translateButton.setClickable(true);
|
translateButton.setClickable(true);
|
||||||
@@ -218,7 +228,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
}).exec(item.parentFragment.getAccountID());
|
}).exec(item.parentFragment.getAccountID());
|
||||||
} else {
|
} else {
|
||||||
item.translated = !item.translated;
|
item.setTranslationShown(!item.translationShown);
|
||||||
rebind();
|
rebind();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -237,9 +247,8 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
|
|
||||||
if (GlobalUserPreferences.collapseLongPosts && !item.status.textExpandable) {
|
if (GlobalUserPreferences.collapseLongPosts && !item.status.textExpandable) {
|
||||||
boolean tooBig = text.getMeasuredHeight() > textMaxHeight;
|
boolean tooBig = text.getMeasuredHeight() > textMaxHeight;
|
||||||
boolean inTimeline = !item.textSelectable;
|
|
||||||
boolean hasSpoiler = !TextUtils.isEmpty(item.status.spoilerText);
|
boolean hasSpoiler = !TextUtils.isEmpty(item.status.spoilerText);
|
||||||
boolean expandable = inTimeline && tooBig && !hasSpoiler;
|
boolean expandable = tooBig && !hasSpoiler;
|
||||||
item.parentFragment.onEnableExpandable(Holder.this, expandable);
|
item.parentFragment.onEnableExpandable(Holder.this, expandable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,16 @@ public class CustomEmojiHelper{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addText(CharSequence text) {
|
||||||
|
if(!(text instanceof Spanned))
|
||||||
|
return;
|
||||||
|
CustomEmojiSpan[] spans=((Spanned) text).getSpans(0, text.length(), CustomEmojiSpan.class);
|
||||||
|
for(List<CustomEmojiSpan> group:Arrays.stream(spans).collect(Collectors.groupingBy(s->s.emoji)).values()){
|
||||||
|
this.spans.add(group);
|
||||||
|
requests.add(group.get(0).createImageLoaderRequest());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int getImageCount(){
|
public int getImageCount(){
|
||||||
return requests.size();
|
return requests.size();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ public class MediaAttachmentViewController{
|
|||||||
public final MediaGridStatusDisplayItem.GridItemType type;
|
public final MediaGridStatusDisplayItem.GridItemType type;
|
||||||
public final ImageView photo;
|
public final ImageView photo;
|
||||||
public final View altButton, noAltButton, btnsWrap;
|
public final View altButton, noAltButton, btnsWrap;
|
||||||
public static int[] altWrapPadding = null;
|
|
||||||
private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable();
|
private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable();
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private boolean didClear;
|
private boolean didClear;
|
||||||
@@ -37,9 +36,6 @@ public class MediaAttachmentViewController{
|
|||||||
btnsWrap=view.findViewById(R.id.alt_badges);
|
btnsWrap=view.findViewById(R.id.alt_badges);
|
||||||
this.type=type;
|
this.type=type;
|
||||||
this.context=context;
|
this.context=context;
|
||||||
if (altWrapPadding == null) {
|
|
||||||
altWrapPadding = new int[] { btnsWrap.getPaddingLeft(), btnsWrap.getPaddingTop(), btnsWrap.getPaddingRight(), btnsWrap.getPaddingBottom() };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bind(Attachment attachment, Status status){
|
public void bind(Attachment attachment, Status status){
|
||||||
|
|||||||
@@ -137,10 +137,15 @@ public class UiUtils {
|
|||||||
private static Handler mainHandler = new Handler(Looper.getMainLooper());
|
private static Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
private static final DateTimeFormatter DATE_FORMATTER_SHORT_WITH_YEAR = DateTimeFormatter.ofPattern("d MMM uuuu"), DATE_FORMATTER_SHORT = DateTimeFormatter.ofPattern("d MMM");
|
private static final DateTimeFormatter DATE_FORMATTER_SHORT_WITH_YEAR = DateTimeFormatter.ofPattern("d MMM uuuu"), DATE_FORMATTER_SHORT = DateTimeFormatter.ofPattern("d MMM");
|
||||||
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT);
|
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT);
|
||||||
|
public static int MAX_WIDTH;
|
||||||
|
|
||||||
private UiUtils() {
|
private UiUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void loadMaxWidth(Context ctx) {
|
||||||
|
if (MAX_WIDTH == 0) MAX_WIDTH = (int) ctx.getResources().getDimension(R.dimen.layout_max_width);
|
||||||
|
}
|
||||||
|
|
||||||
public static void launchWebBrowser(Context context, String url) {
|
public static void launchWebBrowser(Context context, String url) {
|
||||||
try {
|
try {
|
||||||
if (GlobalUserPreferences.useCustomTabs) {
|
if (GlobalUserPreferences.useCustomTabs) {
|
||||||
@@ -338,12 +343,21 @@ public class UiUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int getThemeColor(Context context, @AttrRes int attr) {
|
public static int getThemeColor(Context context, @AttrRes int attr) {
|
||||||
|
if (context == null) return 0xff00ff00;
|
||||||
TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
|
TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
|
||||||
int color = ta.getColor(0, 0xff00ff00);
|
int color = ta.getColor(0, 0xff00ff00);
|
||||||
ta.recycle();
|
ta.recycle();
|
||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getThemeColorRes(Context context, @AttrRes int attr) {
|
||||||
|
if (context == null) return 0xff00ff00;
|
||||||
|
TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
|
||||||
|
int color = ta.getResourceId(0, R.color.black);
|
||||||
|
ta.recycle();
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
public static void openProfileByID(Context context, String selfID, String id) {
|
public static void openProfileByID(Context context, String selfID, String id) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putString("account", selfID);
|
args.putString("account", selfID);
|
||||||
@@ -712,7 +726,7 @@ public class UiUtils {
|
|||||||
public void onSuccess(Relationship result) {
|
public void onSuccess(Relationship result) {
|
||||||
resultCallback.accept(result);
|
resultCallback.accept(result);
|
||||||
progressCallback.accept(false);
|
progressCallback.accept(false);
|
||||||
if (!result.following) {
|
if(!result.following && !result.requested){
|
||||||
E.post(new RemoveAccountPostsEvent(accountID, account.id, true));
|
E.post(new RemoveAccountPostsEvent(accountID, account.id, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ package org.joinmastodon.android.ui.views;
|
|||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class ComposeMediaLayout extends ViewGroup{
|
public class ComposeMediaLayout extends ViewGroup{
|
||||||
private static final int MAX_WIDTH_DP=400;
|
|
||||||
private static final int GAP_DP=8;
|
private static final int GAP_DP=8;
|
||||||
private static final float ASPECT_RATIO=0.5625f;
|
private static final float ASPECT_RATIO=0.5625f;
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@ public class ComposeMediaLayout extends ViewGroup{
|
|||||||
|
|
||||||
public ComposeMediaLayout(Context context, AttributeSet attrs, int defStyle){
|
public ComposeMediaLayout(Context context, AttributeSet attrs, int defStyle){
|
||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
|
UiUtils.loadMaxWidth(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -30,7 +31,7 @@ public class ComposeMediaLayout extends ViewGroup{
|
|||||||
int mode=MeasureSpec.getMode(widthMeasureSpec);
|
int mode=MeasureSpec.getMode(widthMeasureSpec);
|
||||||
@SuppressLint("SwitchIntDef")
|
@SuppressLint("SwitchIntDef")
|
||||||
int width=switch(mode){
|
int width=switch(mode){
|
||||||
case MeasureSpec.AT_MOST -> Math.min(V.dp(MAX_WIDTH_DP), MeasureSpec.getSize(widthMeasureSpec));
|
case MeasureSpec.AT_MOST -> Math.min(UiUtils.MAX_WIDTH, MeasureSpec.getSize(widthMeasureSpec));
|
||||||
case MeasureSpec.EXACTLY -> MeasureSpec.getSize(widthMeasureSpec);
|
case MeasureSpec.EXACTLY -> MeasureSpec.getSize(widthMeasureSpec);
|
||||||
default -> throw new IllegalArgumentException("unsupported measure mode");
|
default -> throw new IllegalArgumentException("unsupported measure mode");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import android.view.View;
|
|||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A LinearLayout for TextViews. First child TextView will get truncated if it doesn't fit, remaining will always wrap content.
|
* A LinearLayout for TextViews. First child TextView will get truncated if it doesn't fit, remaining will always wrap content.
|
||||||
*/
|
*/
|
||||||
@@ -36,7 +38,8 @@ public class HeaderSubtitleLinearLayout extends LinearLayout{
|
|||||||
}
|
}
|
||||||
View first=getChildAt(0);
|
View first=getChildAt(0);
|
||||||
if(first instanceof TextView){
|
if(first instanceof TextView){
|
||||||
((TextView) first).setMaxWidth(remainingWidth);
|
// guaranteeing at least 64dp of width for the display name
|
||||||
|
((TextView) first).setMaxWidth(Math.max(remainingWidth, V.dp(64)));
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
View first=getChildAt(0);
|
View first=getChildAt(0);
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class MediaGridLayout extends ViewGroup{
|
public class MediaGridLayout extends ViewGroup{
|
||||||
private static final String TAG="MediaGridLayout";
|
private static final String TAG="MediaGridLayout";
|
||||||
|
|
||||||
public static final int MAX_WIDTH=400; // dp
|
|
||||||
private static final int GAP=1; // dp
|
private static final int GAP=1; // dp
|
||||||
private PhotoLayoutHelper.TiledLayoutResult tiledLayout;
|
private PhotoLayoutHelper.TiledLayoutResult tiledLayout;
|
||||||
private int[] columnStarts=new int[10], columnEnds=new int[10], rowStarts=new int[10], rowEnds=new int[10];
|
private int[] columnStarts=new int[10], columnEnds=new int[10], rowStarts=new int[10], rowEnds=new int[10];
|
||||||
@@ -27,7 +27,7 @@ public class MediaGridLayout extends ViewGroup{
|
|||||||
|
|
||||||
public MediaGridLayout(Context context, AttributeSet attrs, int defStyle){
|
public MediaGridLayout(Context context, AttributeSet attrs, int defStyle){
|
||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
|
UiUtils.loadMaxWidth(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -36,7 +36,7 @@ public class MediaGridLayout extends ViewGroup{
|
|||||||
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), 0);
|
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int width=Math.min(V.dp(MAX_WIDTH), MeasureSpec.getSize(widthMeasureSpec));
|
int width=Math.min(UiUtils.MAX_WIDTH, MeasureSpec.getSize(widthMeasureSpec));
|
||||||
int height=Math.round(width*(tiledLayout.height/(float)PhotoLayoutHelper.MAX_WIDTH));
|
int height=Math.round(width*(tiledLayout.height/(float)PhotoLayoutHelper.MAX_WIDTH));
|
||||||
|
|
||||||
int offset=0;
|
int offset=0;
|
||||||
@@ -74,10 +74,9 @@ public class MediaGridLayout extends ViewGroup{
|
|||||||
if(tiledLayout==null)
|
if(tiledLayout==null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int maxWidth=V.dp(MAX_WIDTH);
|
|
||||||
int xOffset=0;
|
int xOffset=0;
|
||||||
if(r-l>maxWidth){
|
if(r-l>UiUtils.MAX_WIDTH){
|
||||||
xOffset=(r-l)/2-maxWidth/2;
|
xOffset=(r-l)/2-UiUtils.MAX_WIDTH/2;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(int i=0;i<getChildCount();i++){
|
for(int i=0;i<getChildCount();i++){
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -8,52 +8,53 @@ import java.time.Instant;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class StatusFilterPredicate implements Predicate<Status>{
|
public class StatusFilterPredicate implements Predicate<Status>{
|
||||||
private final List<Filter> filters;
|
private final List<Filter> filters;
|
||||||
|
private final Filter.FilterContext context;
|
||||||
|
private final Filter.FilterAction action;
|
||||||
|
|
||||||
public StatusFilterPredicate(List<Filter> filters){
|
public StatusFilterPredicate(List<Filter> filters, Filter.FilterContext context){
|
||||||
this.filters=filters;
|
this.filters=filters;
|
||||||
|
this.context = context;
|
||||||
|
this.action = Filter.FilterAction.HIDE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param context null makes the predicate pass automatically
|
||||||
|
*/
|
||||||
public StatusFilterPredicate(String accountID, Filter.FilterContext context){
|
public StatusFilterPredicate(String accountID, Filter.FilterContext context){
|
||||||
|
this(accountID, context, Filter.FilterAction.HIDE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param context null makes the predicate pass automatically
|
||||||
|
* @param action defines what the predicate should check:
|
||||||
|
* should not be hidden or should not display with warning
|
||||||
|
*/
|
||||||
|
public StatusFilterPredicate(String accountID, Filter.FilterContext context, Filter.FilterAction action){
|
||||||
filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(context)).collect(Collectors.toList());
|
filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(context)).collect(Collectors.toList());
|
||||||
|
this.context = context;
|
||||||
|
this.action = action;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean test(Status status){
|
public boolean test(Status status){
|
||||||
if(status.filtered!=null){
|
if (context == null) return true;
|
||||||
if (status.filtered.isEmpty()){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
boolean matches=status.filtered.stream()
|
|
||||||
.map(filterResult->filterResult.filter)
|
|
||||||
.filter(filter->filter.expiresAt==null||filter.expiresAt.isAfter(Instant.now()))
|
|
||||||
.anyMatch(filter->filter.filterAction==Filter.FilterAction.HIDE);
|
|
||||||
return !matches;
|
|
||||||
}
|
|
||||||
for(Filter filter:filters){
|
|
||||||
if(filter.matches(status))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean testWithWarning(Status status) {
|
Stream<Filter> stream = status.filtered != null
|
||||||
if(status.filtered!=null){
|
// use server-provided per-status info (status.filtered) if available
|
||||||
if (status.filtered.isEmpty()){
|
? status.filtered.stream().map(f -> f.filter)
|
||||||
return true;
|
// or fall back to cached filters
|
||||||
}
|
: filters.stream().filter(filter -> filter.matches(status));
|
||||||
boolean matches=status.filtered.stream()
|
|
||||||
.map(filterResult->filterResult.filter)
|
return stream
|
||||||
.filter(filter->filter.expiresAt==null||filter.expiresAt.isAfter(Instant.now()))
|
// discard expired filters
|
||||||
.anyMatch(filter->filter.filterAction==Filter.FilterAction.WARN);
|
.filter(filter -> filter.expiresAt == null || filter.expiresAt.isAfter(Instant.now()))
|
||||||
return !matches;
|
// only apply filters for given context
|
||||||
}
|
.filter(filter -> filter.context.contains(context))
|
||||||
for(Filter filter:filters){
|
// treating filterAction = null (from filters list) as FilterAction.HIDE
|
||||||
if(filter.matches(status))
|
.noneMatch(filter -> filter.filterAction == null ? action == Filter.FilterAction.HIDE : filter.filterAction == action);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
mastodon/src/main/res/color/m3_on_surface_overlay.xml
Normal file
4
mastodon/src/main/res/color/m3_on_surface_overlay.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:color="?colorM3OnSurface" android:alpha="0.12"/>
|
||||||
|
</selector>
|
||||||
@@ -2,8 +2,14 @@
|
|||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item>
|
<item>
|
||||||
<shape>
|
<shape>
|
||||||
<solid android:color="?colorWindowBackground"/>
|
<solid android:color="?colorM3Surface"/>
|
||||||
<corners android:topLeftRadius="12dp" android:topRightRadius="12dp"/>
|
<corners android:topLeftRadius="28dp" android:topRightRadius="28dp"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<shape android:tint="?colorM3Primary">
|
||||||
|
<solid android:color="#0D000000"/>
|
||||||
|
<corners android:topLeftRadius="28dp" android:topRightRadius="28dp"/>
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
</layer-list>
|
</layer-list>
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:gravity="center" android:width="36dp" android:height="4dp">
|
<item android:gravity="center" android:width="32dp" android:height="4dp">
|
||||||
<shape>
|
<shape android:tint="?colorM3Outline">
|
||||||
<solid android:color="?android:textColorSecondary"/>
|
<solid android:color="#66000000"/>
|
||||||
<corners android:radius="2dp"/>
|
<corners android:radius="2dp"/>
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
25
mastodon/src/main/res/drawable/bg_button_m3_outlined.xml
Normal file
25
mastodon/src/main/res/drawable/bg_button_m3_outlined.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple android:color="@color/m3_primary_overlay" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item>
|
||||||
|
<selector>
|
||||||
|
<item android:state_enabled="true">
|
||||||
|
<shape>
|
||||||
|
<stroke android:color="?colorM3Outline" android:width="1dp"/>
|
||||||
|
<corners android:radius="20dp"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<shape>
|
||||||
|
<stroke android:color="@color/m3_on_surface_overlay" android:width="1dp"/>
|
||||||
|
<corners android:radius="20dp"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
|
</item>
|
||||||
|
<item android:id="@android:id/mask">
|
||||||
|
<shape>
|
||||||
|
<solid android:color="#000"/>
|
||||||
|
<corners android:radius="20dp"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple android:color="#1effffff" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item>
|
||||||
|
<selector>
|
||||||
|
<item android:state_enabled="true">
|
||||||
|
<shape>
|
||||||
|
<stroke android:color="?colorM3Outline" android:width="1dp"/>
|
||||||
|
<corners android:radius="20dp"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<shape>
|
||||||
|
<stroke android:color="@color/m3_on_surface_overlay" android:width="1dp"/>
|
||||||
|
<corners android:radius="20dp"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
|
</item>
|
||||||
|
<item android:id="@android:id/mask">
|
||||||
|
<shape>
|
||||||
|
<solid android:color="#000"/>
|
||||||
|
<corners android:radius="20dp"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple android:color="#1effffff" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:id="@android:id/mask">
|
||||||
|
<shape>
|
||||||
|
<solid android:color="#000"/>
|
||||||
|
<corners android:radius="20dp"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@drawable/ic_fluent_alert_28_filled" android:left="2dp" android:right="2dp" android:top="2dp" android:bottom="2dp"/>
|
||||||
|
<item android:width="14dp" android:height="14dp" android:gravity="top|right">
|
||||||
|
<shape android:shape="oval">
|
||||||
|
<stroke android:color="?toolbarBackground" android:width="2dp"/>
|
||||||
|
<solid android:color="?android:colorAccent"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@drawable/ic_fluent_alert_28_regular" android:left="2dp" android:right="2dp" android:top="2dp" android:bottom="2dp"/>
|
||||||
|
<item android:width="14dp" android:height="14dp" android:gravity="top|right">
|
||||||
|
<shape android:shape="oval">
|
||||||
|
<stroke android:color="?toolbarBackground" android:width="2dp"/>
|
||||||
|
<solid android:color="?android:colorAccent"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--~ Copyright (c) 2022. ~ Microsoft Corporation. All rights reserved.-->
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@drawable/ic_fluent_alert_28_filled_badged" android:state_activated="true"/>
|
||||||
|
<item android:drawable="@drawable/ic_fluent_alert_28_filled_badged" android:state_checked="true"/>
|
||||||
|
<item android:drawable="@drawable/ic_fluent_alert_28_filled_badged" android:state_selected="true"/>
|
||||||
|
<item android:drawable="@drawable/ic_fluent_alert_28_regular_badged"/>
|
||||||
|
</selector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M20.063 8.445c1.256 1.258 1.255 3.295-0.002 4.551l-7.114 7.102c-0.271 0.272-0.608 0.469-0.978 0.573l-4.613 1.303c-0.57 0.162-1.094-0.373-0.92-0.94l1.387-4.543c0.107-0.354 0.3-0.675 0.562-0.936l7.124-7.112c1.258-1.256 3.297-1.255 4.554 0.002zM8.15 2.37L8.2 2.475l3.253 8.249-1.157 1.155L9.556 10H5.443l-0.995 2.52c-0.14 0.354-0.518 0.542-0.876 0.454l-0.098-0.031c-0.353-0.14-0.54-0.518-0.452-0.876l0.03-0.098 3.754-9.495C7.042 1.88 7.85 1.844 8.151 2.37zM7.503 4.792L6.036 8.5h2.928L7.503 4.792z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user