Compare commits

..

1 Commits

Author SHA1 Message Date
LucasGGamerM
f6ea0404ef Revert "fix: allows for editing timeline badges again. fixes sk22#800 (#804)"
This reverts commit f14df2bb0f.
2023-09-03 18:26:55 -03:00
464 changed files with 4438 additions and 13397 deletions

View File

@@ -15,8 +15,8 @@ android {
applicationId "org.joinmastodon.android.sk" applicationId "org.joinmastodon.android.sk"
minSdk 23 minSdk 23
targetSdk 33 targetSdk 33
versionCode 109 versionCode 98
versionName "2.1.6+fork.109" versionName "2.0.3+fork.98"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
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'] 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']
} }
@@ -33,6 +33,7 @@ android {
applicationIdSuffix '.debug' applicationIdSuffix '.debug'
} }
githubRelease { initWith release } githubRelease { initWith release }
playRelease { initWith release }
fdroidRelease { initWith release } fdroidRelease { initWith release }
} }
compileOptions { compileOptions {
@@ -69,7 +70,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.litex:palette:1.0.0' implementation 'me.grishka.litex:palette:1.0.0'
implementation 'me.grishka.appkit:appkit:1.2.14' implementation 'me.grishka.appkit:appkit:1.2.9'
implementation 'com.google.code.gson:gson:2.9.0' 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'

View File

@@ -257,9 +257,5 @@ public class UiUtilsTest {
assertEquals("* (asterisk)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount( assertEquals("* (asterisk)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
makeField("pronouns", "-- * (asterisk) --") makeField("pronouns", "-- * (asterisk) --")
)).orElseThrow()); )).orElseThrow());
assertEquals("they/(she?)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
makeField("pronouns", "they/(she?)...")
)).orElseThrow());
} }
} }

View File

@@ -1,43 +1,56 @@
13bells.com 13bells.com
1611.social 1611.social
4aem.com 4aem.com
5dollah.click
adachi.party adachi.party
adtension.com anime.website
annihilation.social annihilation.social
anon-kenkai.com anon-kenkai.com
asbestos.cafe asbestos.cafe
bae.st bae.st
bajax.us
banepo.st banepo.st
baraag.net
bassam.social bassam.social
battlepenguin.video
beefyboys.win beefyboys.win
beepboop.ga
berserker.town
bikeshed.party
boks.moe
boymoder.biz boymoder.biz
brainsoap.net brainsoap.net
breastmilk.club breastmilk.club
brighteon.social brighteon.social
cachapa.xyz bungle.online
canary.fedinuke.example.com
catgirl.life
cawfee.club cawfee.club
childlove.space
clew.lol clew.lol
clubcyberia.co clubcyberia.co
collapsitarian.io
comfyboy.club
contrapointsfan.club contrapointsfan.club
crucible.world
cum.camp cum.camp
cum.salon cum.salon
darknight-coffee.org
decayable.ink decayable.ink
dembased.xyz dembased.xyz
desupost.soy
detroitriotcity.com detroitriotcity.com
djsumdog.com eatthebugs.social
eientei.org eientei.org
elementality.org
eveningzoo.club eveningzoo.club
firedragonstudios.com
firefaithfellowship.com
fluf.club fluf.club
foxfam.club
freak.university freak.university
freeatlantis.com freeatlantis.com
freedomstrike.org
freesoftwareextremist.com
freespeech.group
freespeechextremist.com freespeechextremist.com
freetalklive.com
froth.zone froth.zone
fulltermprivacy.com
gameliberty.club gameliberty.club
gearlandia.haus gearlandia.haus
genderheretics.xyz genderheretics.xyz
@@ -46,34 +59,42 @@ gleasonator.com
glee.li glee.li
glindr.org glindr.org
goyim.app goyim.app
h5q.net goyslop.cafe
haeder.net haeder.net
handholding.io handholding.io
hitchhiker.social hitchhiker.social
hunk.city
iddqd.social iddqd.social
intkos.link
justicewarrior.social
kawa-kun.com
kitsunemimi.club kitsunemimi.club
kiwifarms.cc kiwifarms.cc
kompost.cz
kurosawa.moe kurosawa.moe
kyaruc.moe
leafposter.club leafposter.club
leftychan.net
lewdieheaven.com lewdieheaven.com
liberdon.com liberdon.com
ligma.pro ligma.pro
lolicon.rocks lolicon.rocks
lolison.network
lolison.top lolison.top
lovingexpressions.net lovingexpressions.net
mahodou.moe
makemysarcophagus.com makemysarcophagus.com
maladaptive.art
marsey.moe marsey.moe
masochi.st
mastinator.com mastinator.com
merovingian.club merovingian.club
midwaytrades.com midwaytrades.com
mirr0r.city mirr0r.city
morale.ch moa.st
mouse.services mouse.services
mugicha.club mugicha.club
narrativerry.xyz narrativerry.xyz
natehiggers.online natehiggers.online
neckbeard.xyz
needs.vodka needs.vodka
neenster.org neenster.org
nicecrew.digital nicecrew.digital
@@ -82,18 +103,18 @@ noagendasocial.com
noagendasocial.nl noagendasocial.nl
noagendatube.com noagendatube.com
nobodyhasthe.biz nobodyhasthe.biz
norwoodzero.net nukem.biz
nyanide.com obo.sh
onionfarms.org onionfarms.org
pawlicker.com pawlicker.com
pawoo.net pawoo.net
pedo.school pedo.school
peervideo.club
piazza.today piazza.today
pibvt.net pibvt.net
pieville.net pieville.net
pisskey.io pisskey.io
plagu.ee plagu.ee
pmth.us
poa.st poa.st
poast.org poast.org
poast.tv poast.tv
@@ -102,18 +123,17 @@ prospeech.space
quodverum.com quodverum.com
r18.social r18.social
rakket.app rakket.app
rapemeat.express
rapemeat.solutions rapemeat.solutions
rayci.st rdrama.cc
rebelbase.site rebelbase.site
retardedniggers.forsale
rojogato.com
ryona.agency ryona.agency
sad.cab
schwartzwelt.xyz schwartzwelt.xyz
seal.cafe seal.cafe
shaw.app
shigusegubu.club shigusegubu.club
shitpost.cloud shitpost.cloud
shortstacksran.ch shota.house
silliness.observer silliness.observer
skinheads.eu skinheads.eu
skinheads.io skinheads.io
@@ -128,20 +148,23 @@ sneed.social
sonichu.com sonichu.com
spinster.xyz spinster.xyz
springbo.cc springbo.cc
starnix.network
strelizia.net strelizia.net
syspxl.xyz
tastingtraffic.net tastingtraffic.net
teci.world teci.world
theapex.social theapex.social
thechimp.zone
thenobody.club
thepostearthdestination.com thepostearthdestination.com
tkammer.de tkammer.de
trumpislovetrumpis.life trumpislovetrumpis.life
truthsocial.co.in truthsocial.co.in
usualsuspects.lol urchan.org
varishangout.net varishangout.net
vtuberfan.social whinge.house
whinge.town
wideboys.org
wolfgirl.bar wolfgirl.bar
xn--p1abe3d.xn--80asehdb xn--p1abe3d.xn--80asehdb
yggdrasil.social yggdrasil.social
youjo.love youjo.love
zztails.gay

View File

@@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -266,7 +266,7 @@ public class AudioPlayerService extends Service{
private void updateNotification(boolean dismissable, boolean removeNotification){ private void updateNotification(boolean dismissable, boolean removeNotification){
Notification.Builder bldr=new Notification.Builder(this) Notification.Builder bldr=new Notification.Builder(this)
.setSmallIcon(R.drawable.ic_ntf_logo) .setSmallIcon(R.drawable.ic_ntf_logo)
.setContentTitle(status.account.getDisplayName()) .setContentTitle(status.account.displayName)
.setContentText(HtmlParser.strip(status.content)) .setContentText(HtmlParser.strip(status.content))
.setOngoing(!dismissable) .setOngoing(!dismissable)
.setShowWhen(false) .setShowWhen(false)
@@ -281,7 +281,7 @@ public class AudioPlayerService extends Service{
if(playerReady){ if(playerReady){
boolean isPlaying=player.isPlaying(); boolean isPlaying=player.isPlaying();
bldr.addAction(new Notification.Action.Builder(Icon.createWithResource(this, isPlaying ? R.drawable.ic_fluent_pause_24_filled : R.drawable.ic_fluent_play_24_filled), bldr.addAction(new Notification.Action.Builder(Icon.createWithResource(this, isPlaying ? R.drawable.ic_pause_24 : R.drawable.ic_play_24),
getString(isPlaying ? R.string.pause : R.string.play), getString(isPlaying ? R.string.pause : R.string.play),
PendingIntent.getBroadcast(this, 2, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_IMMUTABLE)) PendingIntent.getBroadcast(this, 2, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_IMMUTABLE))
.build()); .build());

View File

@@ -32,6 +32,7 @@ public class ExternalShareActivity extends FragmentStackActivity{
UiUtils.setUserPreferredTheme(this); UiUtils.setUserPreferredTheme(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if(savedInstanceState==null){ if(savedInstanceState==null){
Optional<String> text = Optional.ofNullable(getIntent().getStringExtra(Intent.EXTRA_TEXT)); Optional<String> text = Optional.ofNullable(getIntent().getStringExtra(Intent.EXTRA_TEXT));
Optional<Pair<String, Optional<String>>> fediHandle = text.flatMap(UiUtils::parseFediverseHandle); Optional<Pair<String, Optional<String>>> fediHandle = text.flatMap(UiUtils::parseFediverseHandle);
boolean isFediUrl = text.map(UiUtils::looksLikeFediverseUrl).orElse(false); boolean isFediUrl = text.map(UiUtils::looksLikeFediverseUrl).orElse(false);

View File

@@ -1,17 +1,17 @@
package org.joinmastodon.android; package org.joinmastodon.android;
import static org.joinmastodon.android.api.MastodonAPIController.gson; import static org.joinmastodon.android.api.MastodonAPIController.gson;
import static org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference.MATERIAL3;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.util.Log; import android.util.Log;
import androidx.annotation.StringRes;
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.api.session.AccountLocalPreferences; import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference;
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.model.ContentType; import org.joinmastodon.android.model.ContentType;
@@ -51,17 +51,15 @@ public class GlobalUserPreferences{
public static boolean collapseLongPosts; public static boolean collapseLongPosts;
public static boolean spectatorMode; public static boolean spectatorMode;
public static boolean autoHideFab; public static boolean autoHideFab;
public static boolean compactReblogReplyLine;
public static boolean allowRemoteLoading; public static boolean allowRemoteLoading;
public static boolean forwardReportDefault; public static boolean forwardReportDefault;
public static AutoRevealMode autoRevealEqualSpoilers; public static AutoRevealMode autoRevealEqualSpoilers;
public static ColorPreference color;
public static boolean disableM3PillActiveIndicator; public static boolean disableM3PillActiveIndicator;
public static boolean showNavigationLabels; public static boolean showNavigationLabels;
public static boolean displayPronounsInTimelines, displayPronounsInThreads, displayPronounsInUserListings; public static boolean displayPronounsInTimelines, displayPronounsInThreads, displayPronounsInUserListings;
public static boolean overlayMedia; public static boolean overlayMedia;
public static boolean showSuicideHelp;
public static boolean underlinedLinks;
public static ColorPreference color;
public static boolean likeIcon;
private static SharedPreferences getPrefs(){ private static SharedPreferences getPrefs(){
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE); return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
@@ -111,6 +109,7 @@ public class GlobalUserPreferences{
collapseLongPosts=prefs.getBoolean("collapseLongPosts", true); collapseLongPosts=prefs.getBoolean("collapseLongPosts", true);
spectatorMode=prefs.getBoolean("spectatorMode", false); spectatorMode=prefs.getBoolean("spectatorMode", false);
autoHideFab=prefs.getBoolean("autoHideFab", true); autoHideFab=prefs.getBoolean("autoHideFab", true);
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
allowRemoteLoading=prefs.getBoolean("allowRemoteLoading", true); allowRemoteLoading=prefs.getBoolean("allowRemoteLoading", true);
autoRevealEqualSpoilers=AutoRevealMode.valueOf(prefs.getString("autoRevealEqualSpoilers", AutoRevealMode.THREADS.name())); autoRevealEqualSpoilers=AutoRevealMode.valueOf(prefs.getString("autoRevealEqualSpoilers", AutoRevealMode.THREADS.name()));
forwardReportDefault=prefs.getBoolean("forwardReportDefault", true); forwardReportDefault=prefs.getBoolean("forwardReportDefault", true);
@@ -120,10 +119,6 @@ public class GlobalUserPreferences{
displayPronounsInThreads=prefs.getBoolean("displayPronounsInThreads", true); displayPronounsInThreads=prefs.getBoolean("displayPronounsInThreads", true);
displayPronounsInUserListings=prefs.getBoolean("displayPronounsInUserListings", true); displayPronounsInUserListings=prefs.getBoolean("displayPronounsInUserListings", true);
overlayMedia=prefs.getBoolean("overlayMedia", false); overlayMedia=prefs.getBoolean("overlayMedia", false);
showSuicideHelp=prefs.getBoolean("showSuicideHelp", true);
underlinedLinks=prefs.getBoolean("underlinedLinks", true);
color=ColorPreference.valueOf(prefs.getString("color", MATERIAL3.name()));
likeIcon=prefs.getBoolean("likeIcon", false);
if (prefs.contains("prefixRepliesWithRe")) { if (prefs.contains("prefixRepliesWithRe")) {
prefixReplies = prefs.getBoolean("prefixRepliesWithRe", false) prefixReplies = prefs.getBoolean("prefixRepliesWithRe", false)
@@ -134,11 +129,14 @@ public class GlobalUserPreferences{
.apply(); .apply();
} }
int migrationLevel=prefs.getInt("migrationLevel", BuildConfig.VERSION_CODE); try {
if(migrationLevel < 61) color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.PINK.name()));
migrateToUpstreamVersion61(); } catch (IllegalArgumentException|ClassCastException ignored) {
if(migrationLevel < BuildConfig.VERSION_CODE) // invalid color name or color was previously saved as integer
prefs.edit().putInt("migrationLevel", BuildConfig.VERSION_CODE).apply(); color=ColorPreference.PINK;
}
if(prefs.getInt("migrationLevel", 0) < 61) migrateToUpstreamVersion61();
} }
public static void save(){ public static void save(){
@@ -168,6 +166,8 @@ public class GlobalUserPreferences{
.putBoolean("collapseLongPosts", collapseLongPosts) .putBoolean("collapseLongPosts", collapseLongPosts)
.putBoolean("spectatorMode", spectatorMode) .putBoolean("spectatorMode", spectatorMode)
.putBoolean("autoHideFab", autoHideFab) .putBoolean("autoHideFab", autoHideFab)
.putBoolean("compactReblogReplyLine", compactReblogReplyLine)
.putString("color", color.name())
.putBoolean("allowRemoteLoading", allowRemoteLoading) .putBoolean("allowRemoteLoading", allowRemoteLoading)
.putString("autoRevealEqualSpoilers", autoRevealEqualSpoilers.name()) .putString("autoRevealEqualSpoilers", autoRevealEqualSpoilers.name())
.putBoolean("forwardReportDefault", forwardReportDefault) .putBoolean("forwardReportDefault", forwardReportDefault)
@@ -177,34 +177,9 @@ public class GlobalUserPreferences{
.putBoolean("displayPronounsInThreads", displayPronounsInThreads) .putBoolean("displayPronounsInThreads", displayPronounsInThreads)
.putBoolean("displayPronounsInUserListings", displayPronounsInUserListings) .putBoolean("displayPronounsInUserListings", displayPronounsInUserListings)
.putBoolean("overlayMedia", overlayMedia) .putBoolean("overlayMedia", overlayMedia)
.putBoolean("showSuicideHelp", showSuicideHelp)
.putBoolean("underlinedLinks", underlinedLinks)
.putString("color", color.name())
.putBoolean("likeIcon", likeIcon)
.apply(); .apply();
} }
public enum ThemePreference{
AUTO,
LIGHT,
DARK
}
public enum AutoRevealMode {
NEVER,
THREADS,
DISCUSSIONS
}
public enum PrefixRepliesMode {
NEVER,
ALWAYS,
TO_OTHERS
}
//region preferences migrations
private static void migrateToUpstreamVersion61(){ private static void migrateToUpstreamVersion61(){
Log.d(TAG, "Migrating preferences to upstream version 61!!"); Log.d(TAG, "Migrating preferences to upstream version 61!!");
@@ -251,8 +226,49 @@ public class GlobalUserPreferences{
localPrefs.save(); localPrefs.save();
} }
prefs.edit().putInt("migrationLevel", 61).apply();
} }
//endregion public enum ColorPreference{
MATERIAL3,
PINK,
PURPLE,
GREEN,
BLUE,
BROWN,
RED,
YELLOW;
public @StringRes int getName() {
return switch(this){
case MATERIAL3 -> R.string.sk_color_palette_material3;
case PINK -> R.string.sk_color_palette_pink;
case PURPLE -> R.string.sk_color_palette_purple;
case GREEN -> R.string.sk_color_palette_green;
case BLUE -> R.string.sk_color_palette_blue;
case BROWN -> R.string.sk_color_palette_brown;
case RED -> R.string.sk_color_palette_red;
case YELLOW -> R.string.sk_color_palette_yellow;
};
}
}
public enum ThemePreference{
AUTO,
LIGHT,
DARK
}
public enum AutoRevealMode {
NEVER,
THREADS,
DISCUSSIONS
}
public enum PrefixRepliesMode {
NEVER,
ALWAYS,
TO_OTHERS
}
} }

View File

@@ -31,46 +31,62 @@ import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.parceler.Parcels; import org.parceler.Parcels;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.Instant;
import me.grishka.appkit.FragmentStackActivity; import me.grishka.appkit.FragmentStackActivity;
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;
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent { public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
private static final String TAG="MainActivity";
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState){ protected void onCreate(@Nullable Bundle savedInstanceState){
AccountSession session=getCurrentSession(); UiUtils.setUserPreferredTheme(this);
UiUtils.setUserPreferredTheme(this, session);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Thread.UncaughtExceptionHandler defaultHandler=Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler((t, e)->{
File file=new File(MastodonApp.context.getFilesDir(), "crash.log");
try(FileOutputStream out=new FileOutputStream(file)){
PrintWriter writer=new PrintWriter(out);
writer.println(BuildConfig.VERSION_NAME+" ("+BuildConfig.VERSION_CODE+")");
writer.println(Instant.now().toString());
writer.println();
e.printStackTrace(writer);
writer.flush();
}catch(IOException x){
Log.e(TAG, "Error writing crash.log", x);
}finally{
defaultHandler.uncaughtException(t, e);
}
});
if(savedInstanceState==null){ if(savedInstanceState==null){
restartHomeFragment(); if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
showFragmentClearingBackStack(new CustomWelcomeFragment());
}else{
AccountSession session;
Bundle args=new Bundle();
Intent intent=getIntent();
if(intent.hasExtra("fromExternalShare")) {
AccountSessionManager.getInstance()
.setLastActiveAccountID(intent.getStringExtra("account"));
AccountSessionManager.getInstance().maybeUpdateLocalInfo(
AccountSessionManager.getInstance().getLastActiveAccount());
showFragmentForExternalShare(intent.getExtras());
return;
}
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
boolean hasNotification = intent.hasExtra("notification");
if(fromNotification){
String accountID=intent.getStringExtra("accountID");
try{
session=AccountSessionManager.getInstance().getAccount(accountID);
if(!hasNotification) args.putString("tab", "notifications");
}catch(IllegalStateException x){
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
}else{
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
AccountSessionManager.getInstance().maybeUpdateLocalInfo(session);
args.putString("account", session.getID());
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
fragment.setArguments(args);
if(fromNotification && hasNotification){
Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
showFragmentForNotification(notification, session.getID());
} else if (intent.getBooleanExtra("compose", false)){
showCompose();
} else if (Intent.ACTION_VIEW.equals(intent.getAction())){
handleURL(intent.getData(), null);
} else {
showFragmentClearingBackStack(fragment);
maybeRequestNotificationsPermission();
}
}
} }
if(GithubSelfUpdater.needSelfUpdating()){ if(GithubSelfUpdater.needSelfUpdating()){
@@ -127,7 +143,7 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
} }
public void openSearchQuery(String q, String accountID, int progressText, boolean fromSearch){ public void openSearchQuery(String q, String accountID, int progressText, boolean fromSearch){
new GetSearchResults(q, null, true, null, 0, 0) new GetSearchResults(q, null, true)
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
public void onSuccess(SearchResults result){ public void onSuccess(SearchResults result){
@@ -243,81 +259,4 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
Fragment fragment = getCurrentFragment(); Fragment fragment = getCurrentFragment();
if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent); if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent);
} }
public AccountSession getCurrentSession(){
AccountSession session;
Bundle args=new Bundle();
Intent intent=getIntent();
if(intent.hasExtra("fromExternalShare")) {
return AccountSessionManager.getInstance()
.getAccount(intent.getStringExtra("account"));
}
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
boolean hasNotification = intent.hasExtra("notification");
if(fromNotification){
String accountID=intent.getStringExtra("accountID");
try{
session=AccountSessionManager.getInstance().getAccount(accountID);
if(!hasNotification) args.putString("tab", "notifications");
}catch(IllegalStateException x){
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
}else{
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
return session;
}
public void restartActivity(){
finish();
startActivity(new Intent(this, MainActivity.class));
}
public void restartHomeFragment(){
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
showFragmentClearingBackStack(new CustomWelcomeFragment());
}else{
AccountSession session;
Bundle args=new Bundle();
Intent intent=getIntent();
if(intent.hasExtra("fromExternalShare")) {
AccountSessionManager.getInstance()
.setLastActiveAccountID(intent.getStringExtra("account"));
AccountSessionManager.getInstance().maybeUpdateLocalInfo(
AccountSessionManager.getInstance().getLastActiveAccount());
showFragmentForExternalShare(intent.getExtras());
return;
}
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
boolean hasNotification = intent.hasExtra("notification");
if(fromNotification){
String accountID=intent.getStringExtra("accountID");
try{
session=AccountSessionManager.getInstance().getAccount(accountID);
if(!hasNotification) args.putString("tab", "notifications");
}catch(IllegalStateException x){
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
}else{
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
AccountSessionManager.getInstance().maybeUpdateLocalInfo(session);
args.putString("account", session.getID());
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
fragment.setArguments(args);
if(fromNotification && hasNotification){
Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
showFragmentForNotification(notification, session.getID());
} else if (intent.getBooleanExtra("compose", false)){
showCompose();
} else if (Intent.ACTION_VIEW.equals(intent.getAction())){
handleURL(intent.getData(), null);
} else {
showFragmentClearingBackStack(fragment);
maybeRequestNotificationsPermission();
}
}
}
} }

View File

@@ -14,6 +14,7 @@ import android.content.Intent;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
@@ -32,6 +33,7 @@ import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.PushNotification; import org.joinmastodon.android.model.PushNotification;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy; import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels; import org.parceler.Parcels;
@@ -174,8 +176,6 @@ public class PushNotificationReceiver extends BroadcastReceiver{
List<NotificationChannel> channels=Arrays.stream(PushNotification.Type.values()) List<NotificationChannel> channels=Arrays.stream(PushNotification.Type.values())
.map(type->{ .map(type->{
NotificationChannel channel=new NotificationChannel(accountID+"_"+type, context.getString(type.localizedName), NotificationManager.IMPORTANCE_DEFAULT); NotificationChannel channel=new NotificationChannel(accountID+"_"+type, context.getString(type.localizedName), NotificationManager.IMPORTANCE_DEFAULT);
channel.setLightColor(context.getColor(R.color.primary_700));
channel.enableLights(true);
channel.setGroup(accountID); channel.setGroup(accountID);
return channel; return channel;
}) })
@@ -205,12 +205,11 @@ public class PushNotificationReceiver extends BroadcastReceiver{
.setShowWhen(true) .setShowWhen(true)
.setCategory(Notification.CATEGORY_SOCIAL) .setCategory(Notification.CATEGORY_SOCIAL)
.setAutoCancel(true) .setAutoCancel(true)
.setLights(UiUtils.getThemeColor(context, android.R.attr.colorAccent), 500, 1000)
.setColor(UiUtils.getThemeColor(context, android.R.attr.colorAccent)); .setColor(UiUtils.getThemeColor(context, android.R.attr.colorAccent));
if (!GlobalUserPreferences.uniformNotificationIcon) { if (!GlobalUserPreferences.uniformNotificationIcon) {
builder.setSmallIcon(switch (pn.notificationType) { builder.setSmallIcon(switch (pn.notificationType) {
case FAVORITE -> GlobalUserPreferences.likeIcon ? R.drawable.ic_fluent_heart_24_filled : R.drawable.ic_fluent_star_24_filled; case FAVORITE -> R.drawable.ic_fluent_star_24_filled;
case REBLOG -> R.drawable.ic_fluent_arrow_repeat_all_24_filled; case REBLOG -> R.drawable.ic_fluent_arrow_repeat_all_24_filled;
case FOLLOW -> R.drawable.ic_fluent_person_add_24_filled; case FOLLOW -> R.drawable.ic_fluent_person_add_24_filled;
case MENTION -> R.drawable.ic_fluent_mention_24_filled; case MENTION -> R.drawable.ic_fluent_mention_24_filled;
@@ -322,8 +321,8 @@ public class PushNotificationReceiver extends BroadcastReceiver{
CreateStatus.Request req=new CreateStatus.Request(); CreateStatus.Request req=new CreateStatus.Request();
req.status = initialText + input.toString(); req.status = initialText + input.toString();
req.language = notification.status.language; req.language = preferences.postingDefaultLanguage;
req.visibility = notification.status.visibility; req.visibility = preferences.postingDefaultVisibility;
req.inReplyToId = notification.status.id; req.inReplyToId = notification.status.id;
if (notification.status.hasSpoiler() && if (notification.status.hasSpoiler() &&

View File

@@ -26,7 +26,6 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.function.Consumer; import java.util.function.Consumer;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
@@ -70,11 +69,12 @@ public class CacheController{
Status status=MastodonAPIController.gson.fromJson(cursor.getString(0), Status.class); Status status=MastodonAPIController.gson.fromJson(cursor.getString(0), Status.class);
status.postprocess(); status.postprocess();
int flags=cursor.getInt(1); int flags=cursor.getInt(1);
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0) ? status.id : null; status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0);
newMaxID=status.id; newMaxID=status.id;
result.add(status); result.add(status);
}while(cursor.moveToNext()); }while(cursor.moveToNext());
String _newMaxID=newMaxID; String _newMaxID=newMaxID;
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.HOME);
uiHandler.post(()->callback.onSuccess(new CacheablePaginatedResponse<>(result, _newMaxID, true))); uiHandler.post(()->callback.onSuccess(new CacheablePaginatedResponse<>(result, _newMaxID, true)));
return; return;
} }
@@ -86,7 +86,9 @@ 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, result.isEmpty() ? null : result.get(result.size()-1).id, false)); ArrayList<Status> filtered=new ArrayList<>(result);
AccountSessionManager.get(accountID).filterStatuses(filtered, FilterContext.HOME);
callback.onSuccess(new CacheablePaginatedResponse<>(filtered, result.isEmpty() ? null : result.get(result.size()-1).id, false));
putHomeTimeline(result, maxID==null); putHomeTimeline(result, maxID==null);
} }
@@ -114,14 +116,12 @@ public class CacheController{
values.put("id", s.id); values.put("id", s.id);
values.put("json", MastodonAPIController.gson.toJson(s)); values.put("json", MastodonAPIController.gson.toJson(s));
int flags=0; int flags=0;
if(Objects.equals(s.hasGapAfter, s.id)) if(s.hasGapAfter)
flags|=POST_FLAG_GAP_AFTER; flags|=POST_FLAG_GAP_AFTER;
values.put("flags", flags); values.put("flags", flags);
values.put("time", s.createdAt.getEpochSecond()); values.put("time", s.createdAt.getEpochSecond());
db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE); db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE);
} }
if(!clear)
db.delete("home_timeline", "`id` NOT IN (SELECT `id` FROM `home_timeline` ORDER BY `time` DESC LIMIT ?)", new String[]{"1000"});
}); });
} }
@@ -273,28 +273,6 @@ public class CacheController{
public void deleteStatus(String id){ public void deleteStatus(String id){
runOnDbThread((db)->{ runOnDbThread((db)->{
String gapId=null;
int gapFlags=0;
// select to-be-removed and newer row
try(Cursor cursor=db.query("home_timeline", new String[]{"id", "flags"}, "`time`>=(SELECT `time` FROM `home_timeline` WHERE `id`=?)", new String[]{id}, null, null, "`time` ASC", "2")){
boolean hadGapAfter=false;
// always either one or two iterations (only one if there's no newer post)
while(cursor.moveToNext()){
String currentId=cursor.getString(0);
int currentFlags=cursor.getInt(1);
if(currentId.equals(id)){
hadGapAfter=((currentFlags & POST_FLAG_GAP_AFTER)!=0);
}else if(hadGapAfter){
gapFlags=currentFlags|POST_FLAG_GAP_AFTER;
gapId=currentId;
}
}
}
if(gapId!=null){
ContentValues values=new ContentValues();
values.put("flags", gapFlags);
db.update("home_timeline", values, "`id`=?", new String[]{gapId});
}
db.delete("home_timeline", "`id`=?", new String[]{id}); db.delete("home_timeline", "`id`=?", new String[]{id});
}); });
} }

View File

@@ -54,7 +54,7 @@ public class MastodonAPIController{
.create(); .create();
private static WorkerThread thread=new WorkerThread("MastodonAPIController"); private static WorkerThread thread=new WorkerThread("MastodonAPIController");
private static OkHttpClient httpClient=new OkHttpClient.Builder() private static OkHttpClient httpClient=new OkHttpClient.Builder()
.readTimeout(30, TimeUnit.SECONDS) .readTimeout(5, TimeUnit.MINUTES)
.build(); .build();
private AccountSession session; private AccountSession session;
@@ -113,13 +113,13 @@ public class MastodonAPIController{
} }
Request hreq=builder.build(); Request hreq=builder.build();
OkHttpClient client=req.timeout>0 Call call=httpClient.newCall(hreq);
? httpClient.newBuilder().readTimeout(req.timeout, TimeUnit.MILLISECONDS).build()
: httpClient;
Call call=client.newCall(hreq);
synchronized(req){ synchronized(req){
req.okhttpCall=call; req.okhttpCall=call;
} }
if(req.timeout>0){
call.timeout().timeout(req.timeout, TimeUnit.MILLISECONDS);
}
if(BuildConfig.DEBUG) if(BuildConfig.DEBUG)
Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] Sending request: "+hreq); Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] Sending request: "+hreq);

View File

@@ -153,9 +153,8 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
headers.put(key, value); headers.put(key, value);
} }
public MastodonAPIRequest<T> setTimeout(long timeout){ protected void setTimeout(long timeout){
this.timeout=timeout; this.timeout=timeout;
return this;
} }
protected String getPathPrefix(){ protected String getPathPrefix(){

View File

@@ -130,11 +130,12 @@ public class PushSubscriptionManager{
return; return;
} }
String endpoint = "https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/"; String endpoint = "https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/"+accountID;
registerAccountForPush(subscription, endpoint); registerAccountForPush(subscription, endpoint);
} }
public void registerAccountForPush(PushSubscription subscription, String endpoint){ public void registerAccountForPush(PushSubscription subscription, String endpoint){
MastodonAPIController.runInBackground(()->{ MastodonAPIController.runInBackground(()->{
Log.d(TAG, "registerAccountForPush: started for "+accountID); Log.d(TAG, "registerAccountForPush: started for "+accountID);
String encodedPublicKey, encodedAuthKey, pushAccountID; String encodedPublicKey, encodedAuthKey, pushAccountID;
@@ -163,13 +164,7 @@ public class PushSubscriptionManager{
Log.e(TAG, "registerAccountForPush: error generating encryption key", e); Log.e(TAG, "registerAccountForPush: error generating encryption key", e);
return; return;
} }
new RegisterForPushNotifications(endpoint,
//work-around for adding the randomAccountId
String newEndpoint = endpoint;
if (endpoint.startsWith("https://app.joinmastodon.org/relay-to/fcm/"))
newEndpoint += pushAccountID;
new RegisterForPushNotifications(newEndpoint,
encodedPublicKey, encodedPublicKey,
encodedAuthKey, encodedAuthKey,
subscription==null ? PushSubscription.Alerts.ofAll() : subscription.alerts, subscription==null ? PushSubscription.Alerts.ofAll() : subscription.alerts,

View File

@@ -7,10 +7,7 @@ import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked; import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited; 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.events.ReblogDeletedEvent;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent; import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.events.StatusDeletedEvent;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy; import org.joinmastodon.android.model.StatusPrivacy;
@@ -85,11 +82,7 @@ public class StatusInteractionController{
runningReblogRequests.remove(status.id); runningReblogRequests.remove(status.id);
result.reblogsCount = Math.max(0, status.reblogsCount + (reblogged ? 1 : -1)); result.reblogsCount = Math.max(0, status.reblogsCount + (reblogged ? 1 : -1));
cb.accept(result); cb.accept(result);
if(updateCounters){ if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
E.post(new StatusCountersUpdatedEvent(result));
if(reblogged) E.post(new StatusCreatedEvent(reblog, accountID));
else E.post(new ReblogDeletedEvent(status.id, accountID));
}
} }
@Override @Override

View File

@@ -1,16 +0,0 @@
package org.joinmastodon.android.api.requests.accounts;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
import org.joinmastodon.android.model.Account;
public class GetAccountBlocks extends HeaderPaginationRequest<Account>{
public GetAccountBlocks(String maxID, int limit){
super(HttpMethod.GET, "/blocks", new TypeToken<>(){});
if(maxID!=null)
addQueryParameter("max_id", maxID);
if(limit>0)
addQueryParameter("limit", limit+"");
}
}

View File

@@ -1,16 +0,0 @@
package org.joinmastodon.android.api.requests.accounts;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
import org.joinmastodon.android.model.Account;
public class GetAccountMutes extends HeaderPaginationRequest<Account>{
public GetAccountMutes(String maxID, int limit){
super(HttpMethod.GET, "/mutes/", new TypeToken<>(){});
if(maxID!=null)
addQueryParameter("max_id", maxID);
if(limit>0)
addQueryParameter("limit", limit+"");
}
}

View File

@@ -1,19 +0,0 @@
package org.joinmastodon.android.api.requests.accounts;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Relationship;
public class SetPrivateNote extends MastodonAPIRequest<Relationship>{
public SetPrivateNote(String id, String comment){
super(MastodonAPIRequest.HttpMethod.POST, "/accounts/"+id+"/note", Relationship.class);
Request req = new Request(comment);
setRequestBody(req);
}
private static class Request{
public String comment;
public Request(String comment){
this.comment=comment;
}
}
}

View File

@@ -6,19 +6,18 @@ import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.StatusPrivacy; import org.joinmastodon.android.model.StatusPrivacy;
public class UpdateAccountCredentialsPreferences extends MastodonAPIRequest<Account>{ public class UpdateAccountCredentialsPreferences extends MastodonAPIRequest<Account>{
public UpdateAccountCredentialsPreferences(Preferences preferences, Boolean locked, Boolean discoverable, Boolean indexable){ public UpdateAccountCredentialsPreferences(Preferences preferences, Boolean locked, Boolean discoverable){
super(HttpMethod.PATCH, "/accounts/update_credentials", Account.class); super(HttpMethod.PATCH, "/accounts/update_credentials", Account.class);
setRequestBody(new Request(locked, discoverable, indexable, new RequestSource(preferences.postingDefaultVisibility, preferences.postingDefaultLanguage))); setRequestBody(new Request(locked, discoverable, new RequestSource(preferences.postingDefaultVisibility, preferences.postingDefaultLanguage)));
} }
private static class Request{ private static class Request{
public Boolean locked, discoverable, indexable; public Boolean locked, discoverable;
public RequestSource source; public RequestSource source;
public Request(Boolean locked, Boolean discoverable, Boolean indexable, RequestSource source){ public Request(Boolean locked, Boolean discoverable, RequestSource source){
this.locked=locked; this.locked=locked;
this.discoverable=discoverable; this.discoverable=discoverable;
this.indexable=indexable;
this.source=source; this.source=source;
} }
} }

View File

@@ -6,9 +6,6 @@ import org.joinmastodon.android.model.FilterContext;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import androidx.annotation.Keep;
@Keep
class FilterRequest{ class FilterRequest{
public String title; public String title;
public EnumSet<FilterContext> context; public EnumSet<FilterContext> context;

View File

@@ -13,7 +13,7 @@ import okhttp3.MultipartBody;
import okhttp3.RequestBody; import okhttp3.RequestBody;
public class PleromaMarkNotificationsRead extends MastodonAPIRequest<List<Notification>> { public class PleromaMarkNotificationsRead extends MastodonAPIRequest<List<Notification>> {
private final String maxID; private String maxID;
public PleromaMarkNotificationsRead(String maxID) { public PleromaMarkNotificationsRead(String maxID) {
super(HttpMethod.POST, "/pleroma/notifications/read", new TypeToken<>(){}); super(HttpMethod.POST, "/pleroma/notifications/read", new TypeToken<>(){});
this.maxID = maxID; this.maxID = maxID;

View File

@@ -4,19 +4,13 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.SearchResults; import org.joinmastodon.android.model.SearchResults;
public class GetSearchResults extends MastodonAPIRequest<SearchResults>{ public class GetSearchResults extends MastodonAPIRequest<SearchResults>{
public GetSearchResults(String query, Type type, boolean resolve, String maxID, int offset, int count){ public GetSearchResults(String query, Type type, boolean resolve){
super(HttpMethod.GET, "/search", SearchResults.class); super(HttpMethod.GET, "/search", SearchResults.class);
addQueryParameter("q", query); addQueryParameter("q", query);
if(type!=null) if(type!=null)
addQueryParameter("type", type.name().toLowerCase()); addQueryParameter("type", type.name().toLowerCase());
if(resolve) if(resolve)
addQueryParameter("resolve", "true"); addQueryParameter("resolve", "true");
if(maxID!=null)
addQueryParameter("max_id", maxID);
if(offset>0)
addQueryParameter("offset", String.valueOf(offset));
if(count>0)
addQueryParameter("limit", String.valueOf(count));
} }
public GetSearchResults limit(int limit){ public GetSearchResults limit(int limit){

View File

@@ -1,11 +0,0 @@
package org.joinmastodon.android.api.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.AkkomaTranslation;
public class AkkomaTranslateStatus extends MastodonAPIRequest<AkkomaTranslation>{
public AkkomaTranslateStatus(String id, String lang){
super(HttpMethod.GET, "/statuses/"+id+"/translations/"+lang.toUpperCase(), AkkomaTranslation.class);
}
}

View File

@@ -48,8 +48,6 @@ public class CreateStatus extends MastodonAPIRequest<Status>{
public String quoteId; public String quoteId;
public ContentType contentType; public ContentType contentType;
public boolean preview;
public static class Poll{ public static class Poll{
public ArrayList<String> options=new ArrayList<>(); public ArrayList<String> options=new ArrayList<>();
public int expiresIn; public int expiresIn;

View File

@@ -26,8 +26,6 @@ public class GetStatusEditHistory extends MastodonAPIRequest<List<Status>>{
s.visibility=StatusPrivacy.PUBLIC; s.visibility=StatusPrivacy.PUBLIC;
s.mentions=Collections.emptyList(); s.mentions=Collections.emptyList();
s.tags=Collections.emptyList(); s.tags=Collections.emptyList();
if (s.poll != null)
s.poll.id="fakeID"+i;
i++; i++;
} }
super.validateAndPostprocessResponse(respObj, httpResponse); super.validateAndPostprocessResponse(respObj, httpResponse);

View File

@@ -1,13 +1,11 @@
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.Translation; import org.joinmastodon.android.model.TranslatedStatus;
import java.util.Map; public class TranslateStatus extends MastodonAPIRequest<TranslatedStatus> {
public TranslateStatus(String id) {
public class TranslateStatus extends MastodonAPIRequest<Translation>{ super(HttpMethod.POST, "/statuses/"+id+"/translate", TranslatedStatus.class);
public TranslateStatus(String id, String lang){ setRequestBody(new Object());
super(HttpMethod.POST, "/statuses/"+id+"/translate", Translation.class);
setRequestBody(Map.of("lang", lang));
} }
} }

View File

@@ -1,10 +0,0 @@
package org.joinmastodon.android.api.requests.tags;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Hashtag;
public class GetTag extends MastodonAPIRequest<Hashtag>{
public GetTag(String tag){
super(HttpMethod.GET, "/tags/"+tag, Hashtag.class);
}
}

View File

@@ -1,11 +0,0 @@
package org.joinmastodon.android.api.requests.tags;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Hashtag;
public class SetTagFollowed extends MastodonAPIRequest<Hashtag>{
public SetTagFollowed(String tag, boolean followed){
super(HttpMethod.POST, "/tags/"+tag+(followed ? "/follow" : "/unfollow"), Hashtag.class);
setRequestBody(new Object());
}
}

View File

@@ -6,14 +6,9 @@ import static org.joinmastodon.android.api.MastodonAPIController.gson;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import androidx.annotation.StringRes;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.ContentType; import org.joinmastodon.android.model.ContentType;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.TimelineDefinition; import org.joinmastodon.android.model.TimelineDefinition;
import java.lang.reflect.Type; import java.lang.reflect.Type;
@@ -41,14 +36,12 @@ public class AccountLocalPreferences{
public String publishButtonText; public String publishButtonText;
public String timelineReplyVisibility; // akkoma-only public String timelineReplyVisibility; // akkoma-only
public boolean keepOnlyLatestNotification; public boolean keepOnlyLatestNotification;
public boolean emojiReactionsEnabled; public boolean emojiReactionsEnabled;
public ShowEmojiReactions showEmojiReactions; public ShowEmojiReactions showEmojiReactions;
public ColorPreference color;
public ArrayList<Emoji> recentCustomEmoji;
private final static Type recentLanguagesType = new TypeToken<ArrayList<String>>() {}.getType(); private final static Type recentLanguagesType = new TypeToken<ArrayList<String>>() {}.getType();
private final static Type timelinesType = new TypeToken<ArrayList<TimelineDefinition>>() {}.getType(); private final static Type timelinesType = new TypeToken<ArrayList<TimelineDefinition>>() {}.getType();
private final static Type recentCustomEmojiType=new TypeToken<ArrayList<Emoji>>() {}.getType();
public AccountLocalPreferences(SharedPreferences prefs, AccountSession session){ public AccountLocalPreferences(SharedPreferences prefs, AccountSession session){
this.prefs=prefs; this.prefs=prefs;
@@ -73,8 +66,6 @@ public class AccountLocalPreferences{
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false); keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", session.getInstance().isPresent() && session.getInstance().get().isAkkoma()); emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", session.getInstance().isPresent() && session.getInstance().get().isAkkoma());
showEmojiReactions=ShowEmojiReactions.valueOf(prefs.getString("showEmojiReactions", ShowEmojiReactions.HIDE_EMPTY.name())); showEmojiReactions=ShowEmojiReactions.valueOf(prefs.getString("showEmojiReactions", ShowEmojiReactions.HIDE_EMPTY.name()));
color=prefs.contains("color") ? ColorPreference.valueOf(prefs.getString("color", null)) : null;
recentCustomEmoji=fromJson(prefs.getString("recentCustomEmoji", null), recentCustomEmojiType, new ArrayList<>());
} }
public long getNotificationsPauseEndTime(){ public long getNotificationsPauseEndTime(){
@@ -85,10 +76,6 @@ public class AccountLocalPreferences{
prefs.edit().putLong("notificationsPauseTime", time).apply(); prefs.edit().putLong("notificationsPauseTime", time).apply();
} }
public ColorPreference getCurrentColor(){
return color!=null ? color : GlobalUserPreferences.color!=null ? GlobalUserPreferences.color : ColorPreference.MATERIAL3;
}
public void save(){ public void save(){
prefs.edit() prefs.edit()
.putBoolean("interactionCounts", showInteractionCounts) .putBoolean("interactionCounts", showInteractionCounts)
@@ -112,35 +99,9 @@ public class AccountLocalPreferences{
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification) .putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
.putBoolean("emojiReactionsEnabled", emojiReactionsEnabled) .putBoolean("emojiReactionsEnabled", emojiReactionsEnabled)
.putString("showEmojiReactions", showEmojiReactions.name()) .putString("showEmojiReactions", showEmojiReactions.name())
.putString("color", color!=null ? color.name() : null)
.putString("recentCustomEmoji", gson.toJson(recentCustomEmoji))
.apply(); .apply();
} }
public enum ColorPreference{
MATERIAL3,
PINK,
PURPLE,
GREEN,
BLUE,
BROWN,
RED,
YELLOW;
public @StringRes int getName() {
return switch(this){
case MATERIAL3 -> R.string.sk_color_palette_material3;
case PINK -> R.string.sk_color_palette_pink;
case PURPLE -> R.string.sk_color_palette_purple;
case GREEN -> R.string.sk_color_palette_green;
case BLUE -> R.string.sk_color_palette_blue;
case BROWN -> R.string.sk_color_palette_brown;
case RED -> R.string.sk_color_palette_red;
case YELLOW -> R.string.sk_color_palette_yellow;
};
}
}
public enum ShowEmojiReactions{ public enum ShowEmojiReactions{
HIDE_EMPTY, HIDE_EMPTY,
ONLY_OPENED, ONLY_OPENED,

View File

@@ -40,6 +40,7 @@ import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
@@ -218,7 +219,7 @@ public class AccountSession{
public void savePreferencesIfPending(){ public void savePreferencesIfPending(){
if(preferencesNeedSaving){ if(preferencesNeedSaving){
new UpdateAccountCredentialsPreferences(preferences, self.locked, self.discoverable, self.source.indexable) new UpdateAccountCredentialsPreferences(preferences, null, null)
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
public void onSuccess(Account result){ public void onSuccess(Account result){
@@ -254,77 +255,52 @@ public class AccountSession{
filterStatusContainingObjects(objects, extractor, context, null); filterStatusContainingObjects(objects, extractor, context, null);
} }
private boolean statusIsOnOwnProfile(Status s, Account profile){
return self != null && profile != null && s.account != null
&& Objects.equals(self.id, profile.id) && Objects.equals(self.id, s.account.id);
}
private boolean isFilteredType(Status s){
AccountLocalPreferences localPreferences = getLocalPreferences();
return (!localPreferences.showReplies && s.inReplyToId != null)
|| (!localPreferences.showBoosts && s.reblog != null);
}
public <T> void filterStatusContainingObjects(List<T> objects, Function<T, Status> extractor, FilterContext context, Account profile){ public <T> void filterStatusContainingObjects(List<T> objects, Function<T, Status> extractor, FilterContext context, Account profile){
AccountLocalPreferences localPreferences = getLocalPreferences(); Predicate<Status> statusIsOnOwnProfile = (s) -> self != null && profile != null && s.account != null
if(!localPreferences.serverSideFiltersSupported) for(T obj:objects){ && Objects.equals(self.id, profile.id) && Objects.equals(self.id, s.account.id);
Status s=extractor.apply(obj);
if(s!=null && s.filtered!=null){
localPreferences.serverSideFiltersSupported=true;
localPreferences.save();
break;
}
}
List<T> removeUs=new ArrayList<>(); if(getLocalPreferences().serverSideFiltersSupported){
for(int i=0; i<objects.size(); i++){ // Even with server-side filters, clients are expected to remove statuses that match a filter that hides them
T o=objects.get(i); objects.removeIf(o->{
if(filterStatusContainingObject(o, extractor, context, profile)){
Status s=extractor.apply(o); Status s=extractor.apply(o);
removeUs.add(o);
if(s!=null && s.hasGapAfter!=null && i>0){
// oops, we're about to remove an item that has a gap after...
// gotta find the previous status that's not also about to be removed
for(int j=i-1; j>=0; j--){
T p=objects.get(j);
Status prev=extractor.apply(objects.get(j));
if(prev!=null && !removeUs.contains(p)){
prev.hasGapAfter=s.hasGapAfter;
break;
}
}
}
}
}
objects.removeAll(removeUs);
}
public <T> boolean filterStatusContainingObject(T object, Function<T, Status> extractor, FilterContext context, Account profile){
Status s=extractor.apply(object);
if(s==null) if(s==null)
return false; return false;
// don't hide own posts in own profile if(s.filtered==null)
if(statusIsOnOwnProfile(s, profile)) return false;
// don't hide own posts in own profile
if (statusIsOnOwnProfile.test(s))
return false; return false;
if(isFilteredType(s) && (context == FilterContext.HOME || context == FilterContext.PUBLIC))
return true;
// Even with server-side filters, clients are expected to remove statuses that match a filter that hides them
if(getLocalPreferences().serverSideFiltersSupported){
for(FilterResult filter:s.filtered){ for(FilterResult filter:s.filtered){
if(filter.filter.isActive() && filter.filter.filterAction==FilterAction.HIDE) if(filter.filter.isActive() && filter.filter.filterAction==FilterAction.HIDE)
return true; return true;
} }
}else if(wordFilters!=null){ return false;
});
return;
}
if(wordFilters==null)
return;
for(T obj:objects){
Status s=extractor.apply(obj);
if(s!=null && s.filtered!=null){
getLocalPreferences().serverSideFiltersSupported=true;
getLocalPreferences().save();
return;
}
}
objects.removeIf(o->{
Status s=extractor.apply(o);
if(s==null)
return false;
// don't hide own posts in own profile
if (statusIsOnOwnProfile.test(s))
return false;
for(LegacyFilter filter:wordFilters){ for(LegacyFilter filter:wordFilters){
if(filter.context.contains(context) && filter.matches(s) && filter.isActive()) if(filter.context.contains(context) && filter.matches(s) && filter.isActive())
return true; return true;
} }
}
return false; return false;
} });
public void updateAccountInfo(){
AccountSessionManager.getInstance().updateSessionLocalInfo(this);
} }
public Optional<Instance> getInstance() { public Optional<Instance> getInstance() {
@@ -337,10 +313,4 @@ public class AccountSession{
.authority(getInstance().map(i -> i.normalizedUri).orElse(domain)) .authority(getInstance().map(i -> i.normalizedUri).orElse(domain))
.build(); .build();
} }
public String getDefaultAvatarUrl() {
return getInstance()
.map(instance->"https://"+domain+(instance.isAkkoma() ? "/images/avi.png" : "/avatars/original/missing.png"))
.orElse("");
}
} }

View File

@@ -1,7 +1,5 @@
package org.joinmastodon.android.api.session; package org.joinmastodon.android.api.session;
import static org.unifiedpush.android.connector.UnifiedPush.getDistributor;
import android.app.Activity; import android.app.Activity;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.content.ComponentName; import android.content.ComponentName;
@@ -36,7 +34,6 @@ import org.joinmastodon.android.model.EmojiCategory;
import org.joinmastodon.android.model.LegacyFilter; import org.joinmastodon.android.model.LegacyFilter;
import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Token; import org.joinmastodon.android.model.Token;
import org.unifiedpush.android.connector.UnifiedPush;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@@ -104,7 +101,6 @@ public class AccountSessionManager{
} }
public void addAccount(Instance instance, Token token, Account self, Application app, AccountActivationInfo activationInfo){ public void addAccount(Instance instance, Token token, Account self, Application app, AccountActivationInfo activationInfo){
Context context = MastodonApp.context;
instances.put(instance.uri, instance); instances.put(instance.uri, instance);
AccountSession session=new AccountSession(token, self, app, instance.uri, activationInfo==null, activationInfo); AccountSession session=new AccountSession(token, self, app, instance.uri, activationInfo==null, activationInfo);
sessions.put(session.getID(), session); sessions.put(session.getID(), session);
@@ -117,14 +113,7 @@ public class AccountSessionManager{
MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, instance.uri)); MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, instance.uri));
updateMoreInstanceInfo(instance, instance.uri); updateMoreInstanceInfo(instance, instance.uri);
if (!UnifiedPush.getDistributor(context).isEmpty()) { if(PushSubscriptionManager.arePushNotificationsAvailable()){
UnifiedPush.registerApp(
context,
session.getID(),
new ArrayList<>(),
context.getPackageName()
);
} else if(PushSubscriptionManager.arePushNotificationsAvailable()){
session.getPushSubscriptionManager().registerAccountForPush(null); session.getPushSubscriptionManager().registerAccountForPush(null);
} }
maybeUpdateShortcuts(); maybeUpdateShortcuts();
@@ -314,7 +303,8 @@ public class AccountSessionManager{
} }
} }
/*package*/ void updateSessionLocalInfo(AccountSession session){
private void updateSessionLocalInfo(AccountSession session){
new GetOwnAccount() new GetOwnAccount()
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override

View File

@@ -1,11 +0,0 @@
package org.joinmastodon.android.events;
public class ReblogDeletedEvent{
public final String statusID;
public final String accountID;
public ReblogDeletedEvent(String statusID, String accountID){
this.statusID=statusID;
this.accountID=accountID;
}
}

View File

@@ -1,5 +1,7 @@
package org.joinmastodon.android.events; package org.joinmastodon.android.events;
import org.joinmastodon.android.model.ScheduledStatus;
public class ScheduledStatusDeletedEvent{ public class ScheduledStatusDeletedEvent{
public final String id; public final String id;
public final String accountID; public final String accountID;

View File

@@ -9,6 +9,5 @@ public class StatusCreatedEvent{
public StatusCreatedEvent(Status status, String accountID){ public StatusCreatedEvent(Status status, String accountID){
this.status=status; this.status=status;
this.accountID=accountID; this.accountID=accountID;
status.fromStatusCreated=true;
} }
} }

View File

@@ -9,16 +9,19 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses; import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.RemoveAccountPostsEvent; import org.joinmastodon.android.events.RemoveAccountPostsEvent;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.events.StatusUnpinnedEvent; import org.joinmastodon.android.events.StatusUnpinnedEvent;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.FilterContext; import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
@@ -52,14 +55,15 @@ public class AccountTimelineFragment extends StatusListFragment{
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
currentRequest=new GetAccountStatuses(user.id, getMaxID(), null, count, filter) currentRequest=new GetAccountStatuses(user.id, offset>0 ? getMaxID() : null, null, count, filter)
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if(getActivity()==null) return; if(getActivity()==null) return;
boolean more=applyMaxID(result); AccountSessionManager asm = AccountSessionManager.getInstance();
boolean empty=result.isEmpty();
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext(), user); AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext(), user);
onDataLoaded(result, more); onDataLoaded(result, !empty);
} }
}) })
.exec(accountID); .exec(accountID);

View File

@@ -9,6 +9,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowInsets; import android.view.WindowInsets;
@@ -19,23 +20,18 @@ import android.widget.Toolbar;
import org.joinmastodon.android.E; import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships; import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.polls.SubmitPollVote; import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
import org.joinmastodon.android.api.requests.statuses.AkkomaTranslateStatus;
import org.joinmastodon.android.api.requests.statuses.TranslateStatus;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.PollUpdatedEvent; import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.AkkomaTranslation;
import org.joinmastodon.android.model.DisplayItemsParent; import org.joinmastodon.android.model.DisplayItemsParent;
import org.joinmastodon.android.model.Poll; import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.Translation;
import org.joinmastodon.android.ui.BetterItemAnimator; import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
@@ -50,7 +46,6 @@ import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
import org.joinmastodon.android.ui.photoviewer.PhotoViewer; 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.InsetStatusItemDecoration;
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.utils.ProvidesAssistContent; import org.joinmastodon.android.utils.ProvidesAssistContent;
@@ -60,10 +55,8 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -93,7 +86,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
protected Rect tmpRect=new Rect(); protected Rect tmpRect=new Rect();
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView); protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView);
protected boolean currentlyScrolling; protected boolean currentlyScrolling;
protected String maxID;
public BaseStatusListFragment(){ public BaseStatusListFragment(){
super(20); super(20);
@@ -160,8 +152,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
} }
protected String getMaxID(){ protected String getMaxID(){
if(refreshing) return null;
if(maxID!=null) return maxID;
if(!preloadedData.isEmpty()) if(!preloadedData.isEmpty())
return preloadedData.get(preloadedData.size()-1).getID(); return preloadedData.get(preloadedData.size()-1).getID();
else if(!data.isEmpty()) else if(!data.isEmpty())
@@ -170,12 +160,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
return null; return null;
} }
protected boolean applyMaxID(List<Status> result){
boolean empty=result.isEmpty();
if(!empty) maxID=result.get(result.size()-1).id;
return !empty;
}
protected abstract List<StatusDisplayItem> buildDisplayItems(T s); protected abstract List<StatusDisplayItem> buildDisplayItems(T s);
protected abstract void addAccountToKnown(T s); protected abstract void addAccountToKnown(T s);
@@ -363,12 +347,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
} }
}); });
list.addItemDecoration(new StatusListItemDecoration()); list.addItemDecoration(new StatusListItemDecoration());
list.addItemDecoration(new InsetStatusItemDecoration(this));
((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){ ((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){
private Rect tmpRect=new Rect(); private Rect tmpRect=new Rect();
@Override @Override
public void getSelectorBounds(View view, Rect outRect){ public void getSelectorBounds(View view, Rect outRect){
if(list!=view.getParent()) return;
boolean hasDescendant = false, hasAncestor = false, isWarning = false; boolean hasDescendant = false, hasAncestor = false, isWarning = false;
int lastIndex = -1, firstIndex = -1; int lastIndex = -1, firstIndex = -1;
if(((UsableRecyclerView) list).isIncludeMarginsInItemHitbox()){ if(((UsableRecyclerView) list).isIncludeMarginsInItemHitbox()){
@@ -475,14 +457,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
protected void updatePoll(String itemID, Status status, Poll poll){ protected void updatePoll(String itemID, Status status, Poll poll){
status.poll=poll; status.poll=poll;
int firstOptionIndex=-1, footerIndex=-1; int firstOptionIndex=-1, footerIndex=-1;
int spoilerFirstOptionIndex=-1, spoilerFooterIndex=-1;
SpoilerStatusDisplayItem spoilerItem=null;
int i=0; int i=0;
for(StatusDisplayItem item:displayItems){ for(StatusDisplayItem item:displayItems){
if(item.parentID.equals(itemID)){ if(item.parentID.equals(itemID)){
if(item instanceof SpoilerStatusDisplayItem){ if(item instanceof PollOptionStatusDisplayItem && firstOptionIndex==-1){
spoilerItem=(SpoilerStatusDisplayItem) item;
}else if(item instanceof PollOptionStatusDisplayItem && firstOptionIndex==-1){
firstOptionIndex=i; firstOptionIndex=i;
}else if(item instanceof PollFooterStatusDisplayItem){ }else if(item instanceof PollFooterStatusDisplayItem){
footerIndex=i; footerIndex=i;
@@ -495,16 +473,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
throw new IllegalStateException("Can't find all poll items in displayItems"); throw new IllegalStateException("Can't find all poll items in displayItems");
List<StatusDisplayItem> pollItems=displayItems.subList(firstOptionIndex, footerIndex+1); List<StatusDisplayItem> pollItems=displayItems.subList(firstOptionIndex, footerIndex+1);
int prevSize=pollItems.size(); int prevSize=pollItems.size();
if(spoilerItem!=null){
spoilerFirstOptionIndex=spoilerItem.contentItems.indexOf(pollItems.get(0));
spoilerFooterIndex=spoilerItem.contentItems.indexOf(pollItems.get(pollItems.size()-1));
}
pollItems.clear(); pollItems.clear();
StatusDisplayItem.buildPollItems(itemID, this, poll, status, pollItems); StatusDisplayItem.buildPollItems(itemID, this, poll, pollItems);
if(spoilerItem!=null){
spoilerItem.contentItems.subList(spoilerFirstOptionIndex, spoilerFooterIndex+1).clear();
spoilerItem.contentItems.addAll(spoilerFirstOptionIndex, pollItems);
}
if(prevSize!=pollItems.size()){ if(prevSize!=pollItems.size()){
adapter.notifyItemRangeRemoved(firstOptionIndex, prevSize); adapter.notifyItemRangeRemoved(firstOptionIndex, prevSize);
adapter.notifyItemRangeInserted(firstOptionIndex, pollItems.size()); adapter.notifyItemRangeInserted(firstOptionIndex, pollItems.size());
@@ -571,39 +541,37 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
public void onRevealSpoilerClick(SpoilerStatusDisplayItem.Holder holder){ public void onRevealSpoilerClick(SpoilerStatusDisplayItem.Holder holder){
Status status=holder.getItem().status; Status status=holder.getItem().status;
boolean isForQuote=holder.getItem().isForQuote; toggleSpoiler(status, holder.getItemID());
toggleSpoiler(status, isForQuote, holder.getItemID());
} }
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder) { public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder) {
Status status = holder.getItem().status; Status status = holder.getItem().status;
if(holder.getItem().hasVisibilityToggle) holder.animateVisibilityToggle(false);
MediaGridStatusDisplayItem.Holder mediaGrid = findHolderOfType(holder.getItemID(), MediaGridStatusDisplayItem.Holder.class); MediaGridStatusDisplayItem.Holder mediaGrid = findHolderOfType(holder.getItemID(), MediaGridStatusDisplayItem.Holder.class);
if (mediaGrid != null) { if (mediaGrid != null) {
if (!status.sensitiveRevealed) mediaGrid.revealSensitive(); if (!status.sensitiveRevealed) mediaGrid.revealSensitive();
else mediaGrid.hideSensitive(); else mediaGrid.hideSensitive();
} else { } else {
status.sensitiveRevealed=false; // media grid's methods normally change the status' state - we still want to be able
notifyItemChangedAfter(holder.getItem(), MediaGridStatusDisplayItem.class); // to do this if the media grid is not bound, tho - so, doing it ourselves here
status.sensitiveRevealed = !status.sensitiveRevealed;
} }
holder.rebind();
} }
public void onSensitiveRevealed(MediaGridStatusDisplayItem.Holder holder) { public void onSensitiveRevealed(MediaGridStatusDisplayItem.Holder holder) {
HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class); HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
if(header!=null && header.getItem().hasVisibilityToggle) header.animateVisibilityToggle(true); if(header != null) header.rebind();
else notifyItemChangedBefore(holder.getItem(), HeaderStatusDisplayItem.class);
} }
protected void toggleSpoiler(Status status, boolean isForQuote, String itemID){ protected void toggleSpoiler(Status status, String itemID){
status.spoilerRevealed=!status.spoilerRevealed; status.spoilerRevealed=!status.spoilerRevealed;
if (!status.spoilerRevealed && !AccountSessionManager.get(accountID).getLocalPreferences().revealCWs) if (!status.spoilerRevealed && !AccountSessionManager.get(accountID).getLocalPreferences().revealCWs)
status.sensitiveRevealed = false; status.sensitiveRevealed = false;
List<SpoilerStatusDisplayItem.Holder> spoilers=findAllHoldersOfType(itemID, SpoilerStatusDisplayItem.Holder.class); SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
SpoilerStatusDisplayItem.Holder spoiler=spoilers.size() > 1 && isForQuote ? spoilers.get(1) : spoilers.get(0); if(spoiler!=null)
if(spoiler!=null) spoiler.rebind(); spoiler.rebind();
else notifyItemChanged(itemID, SpoilerStatusDisplayItem.class); SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(findItemOfType(itemID, SpoilerStatusDisplayItem.class));
SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(spoiler.getItem());
int index=displayItems.indexOf(spoilerItem); int index=displayItems.indexOf(spoilerItem);
if(status.spoilerRevealed){ if(status.spoilerRevealed){
@@ -614,32 +582,42 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
adapter.notifyItemRangeRemoved(index+1, spoilerItem.contentItems.size()); adapter.notifyItemRangeRemoved(index+1, spoilerItem.contentItems.size());
} }
notifyItemChanged(itemID, TextStatusDisplayItem.class); TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null)
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()-getMainAdapterOffset());
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class); HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
if(header!=null) header.rebind(); if(header!=null)
else notifyItemChanged(itemID, HeaderStatusDisplayItem.class); header.rebind();
list.invalidateItemDecorations(); list.invalidateItemDecorations();
} }
public void onEnableExpandable(TextStatusDisplayItem.Holder holder, boolean expandable) { public void onEnableExpandable(TextStatusDisplayItem.Holder holder, boolean expandable) {
Status s=holder.getItem().status; if (holder.getItem().status.textExpandable != expandable && list != null) {
if(s.textExpandable!=expandable && list!=null) { holder.getItem().status.textExpandable = expandable;
s.textExpandable=expandable;
HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class); HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
if(header!=null) header.bindCollapseButton(); if (header != null) header.rebind();
} }
} }
public void onToggleExpanded(Status status, String itemID) { public void onToggleExpanded(Status status, String itemID) {
status.textExpanded = !status.textExpanded; status.textExpanded = !status.textExpanded;
notifyItemChanged(itemID, TextStatusDisplayItem.class); TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class); HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
if(header!=null) header.animateExpandToggle(); if (text != null) text.rebind();
else notifyItemChanged(itemID, HeaderStatusDisplayItem.class); if (header != null) header.rebind();
} }
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){} public void updateEmojiReactions(Status status, String itemID){
EmojiReactionsStatusDisplayItem.Holder reactions=findHolderOfType(itemID, EmojiReactionsStatusDisplayItem.Holder.class);
if(reactions != null){
reactions.getItem().status.reactions.clear();
reactions.getItem().status.reactions.addAll(status.reactions);
reactions.rebind();
}
}
public void onGapClick(GapStatusDisplayItem.Holder item){}
public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){ public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){
int startPos = warning.getAbsoluteAdapterPosition(); int startPos = warning.getAbsoluteAdapterPosition();
@@ -695,58 +673,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
return null; return null;
} }
/**
* Use this as a fallback if findHolderOfType fails to find the ViewHolder.
* It might still be bound but off-screen and therefore not a child of the RecyclerView -
* resulting in the ViewHolder displaying an outdated state once scrolled back into view.
*/
protected <I extends StatusDisplayItem> int notifyItemChanged(String id, Class<I> type){
boolean encounteredParent=false;
for(int i=0; i<displayItems.size(); i++){
StatusDisplayItem item=displayItems.get(i);
boolean idEquals=id.equals(item.parentID);
if(!encounteredParent && idEquals) encounteredParent=true; // reached top of the parent
else if(encounteredParent && !idEquals) break; // passed by bottom of the parent. man muss ja wissen wann schluss is
if(idEquals && type.isInstance(item)){
adapter.notifyItemChanged(i);
return i;
}
}
return -1;
}
protected <I extends StatusDisplayItem> int notifyItemChangedAfter(StatusDisplayItem afterThis, Class<I> type){
int startIndex=displayItems.indexOf(afterThis);
if(startIndex == -1) throw new IllegalStateException("notifyItemChangedAfter didn't find the passed StatusDisplayItem");
String parentID=afterThis.parentID;
for(int i=startIndex; i<displayItems.size(); i++){
StatusDisplayItem item=displayItems.get(i);
if(!parentID.equals(item.parentID)) break; // didn't find anything
if(type.isInstance(item)){
// found it
adapter.notifyItemChanged(i);
return i;
}
}
return -1;
}
protected <I extends StatusDisplayItem> int notifyItemChangedBefore(StatusDisplayItem beforeThis, Class<I> type){
int startIndex=displayItems.indexOf(beforeThis);
if(startIndex == -1) throw new IllegalStateException("notifyItemChangedBefore didn't find the passed StatusDisplayItem");
String parentID=beforeThis.parentID;
for(int i=startIndex; i>=0; i--){
StatusDisplayItem item=displayItems.get(i);
if(!parentID.equals(item.parentID)) break; // didn't find anything
if(type.isInstance(item)){
// found it
adapter.notifyItemChanged(i);
return i;
}
}
return -1;
}
@Nullable @Nullable
protected <I extends StatusDisplayItem, H extends StatusDisplayItem.Holder<I>> H findHolderOfType(String id, Class<H> type){ protected <I extends StatusDisplayItem, H extends StatusDisplayItem.Holder<I>> H findHolderOfType(String id, Class<H> type){
for(int i=0;i<list.getChildCount();i++){ for(int i=0;i<list.getChildCount();i++){
@@ -846,94 +772,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon())); assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
} }
public void togglePostTranslation(Status status, String itemID){
switch(status.translationState){
case LOADING -> {
return;
}
case SHOWN -> {
status.translationState=Status.TranslationState.HIDDEN;
}
case HIDDEN -> {
if(status.translation!=null){
status.translationState=Status.TranslationState.SHOWN;
}else{
status.translationState=Status.TranslationState.LOADING;
Consumer<Translation> successCallback=(result)->{
status.translation=result;
status.translationState=Status.TranslationState.SHOWN;
updateTranslation(itemID);
};
MastodonAPIRequest<?> req=isInstanceAkkoma()
? new AkkomaTranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage()).setCallback(new Callback<>(){
@Override
public void onSuccess(AkkomaTranslation result){
if(getActivity()!=null) successCallback.accept(result.toTranslation());
}
@Override
public void onError(ErrorResponse error){
if(getActivity()!=null) translationCallbackError(status, itemID);
}
})
: new TranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage()).setCallback(new Callback<>(){
@Override
public void onSuccess(Translation result){
if(getActivity()!=null) successCallback.accept(result);
}
@Override
public void onError(ErrorResponse error){
if(getActivity()!=null) translationCallbackError(status, itemID);
}
});
// 1 minute
req.setTimeout(60000).exec(accountID);
}
}
}
updateTranslation(itemID);
}
private void translationCallbackError(Status status, String itemID) {
status.translationState=Status.TranslationState.HIDDEN;
updateTranslation(itemID);
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.error)
.setMessage(R.string.translation_failed)
.setPositiveButton(R.string.ok, null)
.show();
}
private void updateTranslation(String itemID) {
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null){
text.updateTranslation(true);
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
}else{
notifyItemChanged(itemID, TextStatusDisplayItem.class);
}
if(isInstanceAkkoma())
return;
SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
if(spoiler!=null){
spoiler.rebind();
}
MediaGridStatusDisplayItem.Holder media=findHolderOfType(itemID, MediaGridStatusDisplayItem.Holder.class);
if (media!=null) {
media.rebind();
}
for(int i=0;i<list.getChildCount();i++){
if(list.getChildViewHolder(list.getChildAt(i)) instanceof PollOptionStatusDisplayItem.Holder item){
item.rebind();
}
}
}
public void rebuildAllDisplayItems(){ public void rebuildAllDisplayItems(){
displayItems.clear(); displayItems.clear();
for(T item:data){ for(T item:data){
@@ -949,7 +787,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
if(getContext()==null) return; if(getContext()==null) return;
super.onDataLoaded(d, more); super.onDataLoaded(d, more);
// more available, but the page isn't even full yet? seems wrong, let's load some more // more available, but the page isn't even full yet? seems wrong, let's load some more
if(more && data.size() < itemsPerPage){ if(more && d.size() < itemsPerPage){
preloader.onScrolledToLastItem(); preloader.onScrolledToLastItem();
} }
} }

View File

@@ -110,11 +110,11 @@ import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle; import java.time.format.FormatStyle;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.function.Consumer;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -150,7 +150,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
public LinearLayout mainLayout; public LinearLayout mainLayout;
private SizeListenerLinearLayout contentView; private SizeListenerLinearLayout contentView;
private TextView selfName, selfUsername, selfExtraText, extraText; private TextView selfName, selfUsername, selfExtraText, extraText, pronouns;
private ImageView selfAvatar; private ImageView selfAvatar;
private Account self; private Account self;
private String instanceDomain; private String instanceDomain;
@@ -161,7 +161,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private int charCount, charLimit, trimmedCharCount; private int charCount, charLimit, trimmedCharCount;
private Button publishButton, languageButton, scheduleTimeBtn; private Button publishButton, languageButton, scheduleTimeBtn;
private PopupMenu contentTypePopup, visibilityPopup, draftOptionsPopup; private PopupMenu languagePopup, contentTypePopup, visibilityPopup, draftOptionsPopup;
private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn, draftsBtn, scheduleDraftDismiss, contentTypeBtn; private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn, draftsBtn, scheduleDraftDismiss, contentTypeBtn;
private View sensitiveBtn; private View sensitiveBtn;
private TextView replyText; private TextView replyText;
@@ -200,7 +200,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
public Instance instance; public Instance instance;
public Status editingStatus; public Status editingStatus;
public ScheduledStatus scheduledStatus; private ScheduledStatus scheduledStatus;
private boolean redraftStatus; private boolean redraftStatus;
private ContentType contentType; private ContentType contentType;
@@ -293,7 +293,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
@Override @Override
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){ public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
creatingView=true; creatingView=true;
emojiKeyboard=new CustomEmojiPopupKeyboard(getActivity(), accountID, customEmojis, instanceDomain); emojiKeyboard=new CustomEmojiPopupKeyboard(getActivity(), customEmojis, instanceDomain);
emojiKeyboard.setListener(new CustomEmojiPopupKeyboard.Listener(){ emojiKeyboard.setListener(new CustomEmojiPopupKeyboard.Listener(){
@Override @Override
public void onEmojiSelected(Emoji emoji){ public void onEmojiSelected(Emoji emoji){
@@ -326,7 +326,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
selfUsername=view.findViewById(R.id.self_username); selfUsername=view.findViewById(R.id.self_username);
selfAvatar=view.findViewById(R.id.self_avatar); selfAvatar=view.findViewById(R.id.self_avatar);
selfExtraText=view.findViewById(R.id.self_extra_text); selfExtraText=view.findViewById(R.id.self_extra_text);
HtmlParser.setTextWithCustomEmoji(selfName, self.getDisplayName(), self.emojis); HtmlParser.setTextWithCustomEmoji(selfName, self.displayName, self.emojis);
selfUsername.setText('@'+self.username+'@'+instanceDomain); selfUsername.setText('@'+self.username+'@'+instanceDomain);
if(self.avatar!=null) if(self.avatar!=null)
ViewImageLoader.load(selfAvatar, null, new UrlImageLoaderRequest(self.avatar)); ViewImageLoader.load(selfAvatar, null, new UrlImageLoaderRequest(self.avatar));
@@ -626,6 +626,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}); });
View originalPost=view.findViewById(R.id.original_post); View originalPost=view.findViewById(R.id.original_post);
extraText=view.findViewById(R.id.extra_text); extraText=view.findViewById(R.id.extra_text);
pronouns=view.findViewById(R.id.pronouns);
originalPost.setVisibility(View.VISIBLE); originalPost.setVisibility(View.VISIBLE);
originalPost.setOnClickListener(v->{ originalPost.setOnClickListener(v->{
Bundle args=new Bundle(); Bundle args=new Bundle();
@@ -665,7 +666,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
moreBtn.setBackground(null); moreBtn.setBackground(null);
TextView name = view.findViewById(R.id.name); TextView name = view.findViewById(R.id.name);
name.setText(HtmlParser.parseCustomEmoji(status.account.getDisplayName(), status.account.emojis)); name.setText(HtmlParser.parseCustomEmoji(status.account.displayName, status.account.emojis));
UiUtils.loadCustomEmojiInTextView(name); UiUtils.loadCustomEmojiInTextView(name);
String time = status==null || status.editedAt==null String time = status==null || status.editedAt==null
@@ -699,8 +700,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(16))); .setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(16)));
} }
replyText.setText(HtmlParser.parseCustomEmoji(getString(quote!=null? R.string.sk_quoting_user : R.string.in_reply_to, status.account.getDisplayName()), status.account.emojis)); replyText.setText(getString(quote!=null? R.string.sk_quoting_user : R.string.in_reply_to, status.account.displayName));
UiUtils.loadCustomEmojiInTextView(replyText);
int visibilityNameRes = switch (status.visibility) { int visibilityNameRes = switch (status.visibility) {
case PUBLIC -> R.string.visibility_public; case PUBLIC -> R.string.visibility_public;
case UNLISTED -> R.string.sk_visibility_unlisted; case UNLISTED -> R.string.sk_visibility_unlisted;
@@ -708,7 +708,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
case DIRECT -> R.string.visibility_private; case DIRECT -> R.string.visibility_private;
case LOCAL -> R.string.sk_local_only; case LOCAL -> R.string.sk_local_only;
}; };
replyText.setContentDescription(getString(R.string.in_reply_to, status.account.getDisplayName()) + ", " + getString(visibilityNameRes)); replyText.setContentDescription(getString(R.string.in_reply_to, status.account.displayName) + ", " + getString(visibilityNameRes));
replyText.setOnClickListener(v->{ replyText.setOnClickListener(v->{
scrollView.smoothScrollTo(0, 0); scrollView.smoothScrollTo(0, 0);
}); });
@@ -736,7 +736,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
String prefix = (GlobalUserPreferences.prefixReplies == ALWAYS String prefix = (GlobalUserPreferences.prefixReplies == ALWAYS
|| (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(status.account.id))) || (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(status.account.id)))
&& !status.spoilerText.startsWith("re: ") ? "re: " : ""; && !status.spoilerText.startsWith("re: ") ? "re: " : "";
spoilerEdit.setText(prefix + status.spoilerText); spoilerEdit.setText(prefix + replyTo.spoilerText);
spoilerBtn.setSelected(true); spoilerBtn.setSelected(true);
} }
if (status.language != null && !status.language.isEmpty()) setPostLanguage(status.language); if (status.language != null && !status.language.isEmpty()) setPostLanguage(status.language);
@@ -797,7 +797,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
} }
@SuppressLint("ClickableViewAccessibility")
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
inflater.inflate(editingStatus==null ? R.menu.compose : R.menu.compose_edit, menu); inflater.inflate(editingStatus==null ? R.menu.compose : R.menu.compose_edit, menu);
@@ -810,19 +809,16 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
draftsBtn = wrap.findViewById(R.id.drafts_btn); draftsBtn = wrap.findViewById(R.id.drafts_btn);
draftOptionsPopup = new PopupMenu(getContext(), draftsBtn); draftOptionsPopup = new PopupMenu(getContext(), draftsBtn);
draftOptionsPopup.inflate(R.menu.compose_more); draftOptionsPopup.inflate(R.menu.compose_more);
Menu draftOptionsMenu=draftOptionsPopup.getMenu(); draftMenuItem = draftOptionsPopup.getMenu().findItem(R.id.draft);
draftMenuItem=draftOptionsMenu.findItem(R.id.draft); undraftMenuItem = draftOptionsPopup.getMenu().findItem(R.id.undraft);
undraftMenuItem=draftOptionsMenu.findItem(R.id.undraft); scheduleMenuItem = draftOptionsPopup.getMenu().findItem(R.id.schedule);
scheduleMenuItem=draftOptionsMenu.findItem(R.id.schedule); unscheduleMenuItem = draftOptionsPopup.getMenu().findItem(R.id.unschedule);
unscheduleMenuItem=draftOptionsMenu.findItem(R.id.unschedule);
draftOptionsMenu.findItem(R.id.preview).setVisible(isInstanceAkkoma());
draftOptionsPopup.setOnMenuItemClickListener(i->{ draftOptionsPopup.setOnMenuItemClickListener(i->{
int id = i.getItemId(); int id = i.getItemId();
if (id == R.id.draft) updateScheduledAt(getDraftInstant()); if (id == R.id.draft) updateScheduledAt(getDraftInstant());
else if (id == R.id.schedule) pickScheduledDateTime(); else if (id == R.id.schedule) pickScheduledDateTime();
else if (id == R.id.unschedule || id == R.id.undraft) updateScheduledAt(null); else if (id == R.id.unschedule || id == R.id.undraft) updateScheduledAt(null);
else if(id==R.id.drafts) navigateToUnsentPosts(); else navigateToUnsentPosts();
else if(id==R.id.preview) publish(true);
return true; return true;
}); });
UiUtils.enablePopupMenuIcons(getContext(), draftOptionsPopup); UiUtils.enablePopupMenuIcons(getContext(), draftOptionsPopup);
@@ -830,35 +826,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
publishButton = wrap.findViewById(R.id.publish_btn); publishButton = wrap.findViewById(R.id.publish_btn);
languageButton = wrap.findViewById(R.id.language_btn); languageButton = wrap.findViewById(R.id.language_btn);
languageButton.setOnClickListener(v->showLanguageAlert()); languageButton.setOnClickListener(v->showLanguageAlert());
languageButton.setOnLongClickListener(v->{
if(!getLocalPrefs().bottomEncoding){
getLocalPrefs().bottomEncoding=true;
getLocalPrefs().save();
}
return false;
});
publishButton.post(()->publishButton.setMinimumWidth(publishButton.getWidth()));
publishButton.setOnClickListener(v -> { publishButton.setOnClickListener(v -> {
Consumer<Boolean> draftCheckComplete=(isDraft)->{ if(GlobalUserPreferences.altTextReminders && editingStatus==null)
if(GlobalUserPreferences.altTextReminders && !isDraft) checkAltTextsAndPublish(); checkAltTextsAndPublish();
else publish(); else
}; publish();
boolean isAlreadyDraft=scheduledAt!=null && scheduledAt.isAfter(DRAFTS_AFTER_INSTANT);
if(editingStatus!=null && scheduledAt!=null && isAlreadyDraft) {
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_save_draft)
.setMessage(R.string.sk_save_draft_message)
.setPositiveButton(R.string.save, (d, w)->draftCheckComplete.accept(isAlreadyDraft))
.setNegativeButton(R.string.publish, (d, w)->{
updateScheduledAt(null);
draftCheckComplete.accept(false);
})
.show();
}else{
draftCheckComplete.accept(isAlreadyDraft);
}
}); });
draftsBtn.setOnClickListener(v-> draftOptionsPopup.show()); draftsBtn.setOnClickListener(v-> draftOptionsPopup.show());
draftsBtn.setOnTouchListener(draftOptionsPopup.getDragToOpenListener()); draftsBtn.setOnTouchListener(draftOptionsPopup.getDragToOpenListener());
@@ -871,7 +844,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
: languageResolver.getDefault()); : languageResolver.getDefault());
if (isInstancePixelfed()) spoilerBtn.setVisibility(View.GONE); if (isInstancePixelfed()) spoilerBtn.setVisibility(View.GONE);
if(isInstancePixelfed() || (editingStatus!=null && !redraftStatus)) { if (isInstancePixelfed() || (editingStatus != null && scheduledStatus == null)) {
// editing an already published post // editing an already published post
draftsBtn.setVisibility(View.GONE); draftsBtn.setVisibility(View.GONE);
} }
@@ -995,7 +968,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
@Override @Override
protected int getNavigationIconDrawableResource(){ protected int getNavigationIconDrawableResource(){
return R.drawable.ic_fluent_dismiss_24_regular; return R.drawable.ic_baseline_close_24;
} }
@Override @Override
@@ -1054,10 +1027,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
private void publish(){ private void publish(){
publish(false);
}
private void publish(boolean preview){
sendingOverlay=new View(getActivity()); sendingOverlay=new View(getActivity());
WindowManager.LayoutParams overlayParams=new WindowManager.LayoutParams(); WindowManager.LayoutParams overlayParams=new WindowManager.LayoutParams();
overlayParams.type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; overlayParams.type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
@@ -1071,12 +1040,13 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
publishButton.setEnabled(false); publishButton.setEnabled(false);
V.setVisibilityAnimated(sendProgress, View.VISIBLE); V.setVisibilityAnimated(sendProgress, View.VISIBLE);
mediaViewController.saveAltTextsBeforePublishing( mediaViewController.saveAltTextsBeforePublishing(this::actuallyPublish, this::handlePublishError);
()->actuallyPublish(preview),
this::handlePublishError);
} }
private void actuallyPublish(boolean preview){ private void actuallyPublish(){
actuallyPublish(false);
}
private void actuallyPublish(boolean force){
String text=mainEditText.getText().toString(); String text=mainEditText.getText().toString();
CreateStatus.Request req=new CreateStatus.Request(); CreateStatus.Request req=new CreateStatus.Request();
if ("bottom".equals(postLang.encoding)) { if ("bottom".equals(postLang.encoding)) {
@@ -1094,13 +1064,25 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
req.sensitive=sensitive; req.sensitive=sensitive;
req.contentType=contentType==ContentType.UNSPECIFIED ? null : contentType; req.contentType=contentType==ContentType.UNSPECIFIED ? null : contentType;
req.scheduledAt=scheduledAt; req.scheduledAt=scheduledAt;
req.preview=preview;
if(!mediaViewController.isEmpty()){ if(!mediaViewController.isEmpty()){
req.mediaIds=mediaViewController.getAttachmentIDs(); req.mediaIds=mediaViewController.getAttachmentIDs();
if(editingStatus != null){ if(editingStatus != null){
req.mediaAttributes=mediaViewController.getAttachmentAttributes(); req.mediaAttributes=mediaViewController.getAttachmentAttributes();
} }
} }
// ask whether to publish now when editing an existing draft
if (!force && editingStatus != null && scheduledAt != null && scheduledAt.isAfter(DRAFTS_AFTER_INSTANT)) {
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_save_draft)
.setMessage(R.string.sk_save_draft_message)
.setPositiveButton(R.string.save, (d, w) -> actuallyPublish(true))
.setNegativeButton(R.string.publish, (d, w) -> {
updateScheduledAt(null);
actuallyPublish();
})
.show();
return;
}
if(replyTo!=null || (editingStatus != null && editingStatus.inReplyToId!=null)){ if(replyTo!=null || (editingStatus != null && editingStatus.inReplyToId!=null)){
req.inReplyToId=editingStatus!=null ? editingStatus.inReplyToId : replyTo.id; req.inReplyToId=editingStatus!=null ? editingStatus.inReplyToId : replyTo.id;
} }
@@ -1122,11 +1104,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
Callback<Status> resCallback=new Callback<>(){ Callback<Status> resCallback=new Callback<>(){
@Override @Override
public void onSuccess(Status result){ public void onSuccess(Status result){
if(preview){
openPreview(result);
return;
}
maybeDeleteScheduledPost(() -> { maybeDeleteScheduledPost(() -> {
wm.removeView(sendingOverlay); wm.removeView(sendingOverlay);
sendingOverlay=null; sendingOverlay=null;
@@ -1168,11 +1145,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
}; };
if(editingStatus!=null && !redraftStatus && !preview){ if(editingStatus!=null && !redraftStatus){
new EditStatus(req, editingStatus.id) new EditStatus(req, editingStatus.id)
.setCallback(resCallback) .setCallback(resCallback)
.exec(accountID); .exec(accountID);
}else if(req.scheduledAt == null || preview){ }else if(req.scheduledAt == null){
new CreateStatus(req, uuid) new CreateStatus(req, uuid)
.setCallback(resCallback) .setCallback(resCallback)
.exec(accountID); .exec(accountID);
@@ -1225,25 +1202,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
} }
private void openPreview(Status result){
result.preview=true;
wm.removeView(sendingOverlay);
sendingOverlay=null;
publishButton.setEnabled(true);
V.setVisibilityAnimated(sendProgress, View.GONE);
InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class);
imm.hideSoftInputFromWindow(contentView.getWindowToken(), 0);
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("status", Parcels.wrap(result));
if(replyTo!=null){
args.putParcelable("inReplyTo", Parcels.wrap(replyTo));
args.putParcelable("inReplyToAccount", Parcels.wrap(replyTo.account));
}
Nav.go(getActivity(), ThreadFragment.class, args);
}
private void updateRecentLanguages() { private void updateRecentLanguages() {
if (postLang == null || postLang.language == null) return; if (postLang == null || postLang.language == null) return;
String language = postLang.language.getLanguage(); String language = postLang.language.getLanguage();
@@ -1323,7 +1281,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
if (attachmentsPending) new M3AlertDialogBuilder(getActivity()) if (attachmentsPending) new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_unfinished_attachments) .setTitle(R.string.sk_unfinished_attachments)
.setMessage(R.string.sk_unfinished_attachments_message) .setMessage(R.string.sk_unfinished_attachments_message)
.setPositiveButton(R.string.ok, (d, w)->{}) .setPositiveButton(R.string.edit, (d, w) -> {})
.setNegativeButton(R.string.discard, (d, w) -> Nav.finish(this)) .setNegativeButton(R.string.discard, (d, w) -> Nav.finish(this))
.show(); .show();
else new M3AlertDialogBuilder(getActivity()) else new M3AlertDialogBuilder(getActivity())
@@ -1350,7 +1308,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
boolean usePhotoPicker=photoPicker && UiUtils.isPhotoPickerAvailable(); boolean usePhotoPicker=photoPicker && UiUtils.isPhotoPickerAvailable();
if(usePhotoPicker){ if(usePhotoPicker){
intent=new Intent(MediaStore.ACTION_PICK_IMAGES); intent=new Intent(MediaStore.ACTION_PICK_IMAGES);
if(mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount()>1)
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount()); intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount());
}else{ }else{
intent=new Intent(Intent.ACTION_GET_CONTENT); intent=new Intent(Intent.ACTION_GET_CONTENT);
@@ -1489,8 +1446,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
private void updateHeaders() { private void updateHeaders() {
UiUtils.setExtraTextInfo(getContext(), selfExtraText, false, false, localOnly, null); UiUtils.setExtraTextInfo(getContext(), selfExtraText, null, false, false, localOnly || statusVisibility==StatusPrivacy.LOCAL, null);
if (replyTo != null) UiUtils.setExtraTextInfo(getContext(), extraText, true, false, replyTo.localOnly || replyTo.visibility==StatusPrivacy.LOCAL, replyTo.account); if (replyTo != null) UiUtils.setExtraTextInfo(getContext(), extraText, pronouns, true, false, replyTo.localOnly || replyTo.visibility==StatusPrivacy.LOCAL, replyTo.account);
} }
private void buildVisibilityPopup(View v){ private void buildVisibilityPopup(View v){
@@ -1562,7 +1519,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
contentTypePopup.setOnMenuItemClickListener(i->{ contentTypePopup.setOnMenuItemClickListener(i->{
uuid=null;
int index=i.getItemId(); int index=i.getItemId();
contentType=ContentType.values()[index]; contentType=ContentType.values()[index];
btn.setSelected(index!=ContentType.UNSPECIFIED.ordinal() && index!=ContentType.PLAIN.ordinal()); btn.setSelected(index!=ContentType.UNSPECIFIED.ordinal() && index!=ContentType.PLAIN.ordinal());

View File

@@ -26,12 +26,12 @@ import android.widget.ImageView;
import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController; import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Attachment; import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.photoviewer.PhotoViewer; import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
import org.joinmastodon.android.ui.utils.ColorPalette; import org.joinmastodon.android.ui.utils.ColorPalette;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.FixedAspectRatioImageView;
import java.util.Collections; import java.util.Collections;
@@ -54,17 +54,16 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment imp
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
accountID=getArguments().getString("account");
attachmentID=getArguments().getString("attachment");
setHasOptionsMenu(true); setHasOptionsMenu(true);
} }
@Override @Override
public void onAttach(Activity activity){ public void onAttach(Activity activity){
super.onAttach(activity); super.onAttach(activity);
accountID=getArguments().getString("account");
attachmentID=getArguments().getString("attachment");
themeWrapper=new ContextThemeWrapper(activity, R.style.Theme_Mastodon_Dark); themeWrapper=new ContextThemeWrapper(activity, R.style.Theme_Mastodon_Dark);
ColorPalette.palettes.get(AccountSessionManager.get(accountID).getLocalPreferences().getCurrentColor()) ColorPalette.palettes.get(GlobalUserPreferences.color).apply(themeWrapper, GlobalUserPreferences.ThemePreference.DARK);
.apply(themeWrapper, GlobalUserPreferences.ThemePreference.DARK);
setTitle(R.string.add_alt_text); setTitle(R.string.add_alt_text);
} }

View File

@@ -239,21 +239,16 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
private boolean setTagListContent(NachoTextView editText, @Nullable List<String> tags) { private boolean setTagListContent(NachoTextView editText, @Nullable List<String> tags) {
if (tags == null || tags.isEmpty()) return false; if (tags == null || tags.isEmpty()) return false;
editText.setText(tags); editText.setText(String.join(",", tags));
editText.chipifyAllUnterminatedTokens(); editText.chipifyAllUnterminatedTokens();
return true; return true;
} }
private NachoTextView prepareChipTextView(NachoTextView nacho) { private NachoTextView prepareChipTextView(NachoTextView nacho) {
//Ill Be Back nacho.addChipTerminator(',', BEHAVIOR_CHIPIFY_ALL);
nacho.setChipTerminators( nacho.addChipTerminator('\n', BEHAVIOR_CHIPIFY_ALL);
Map.of( nacho.addChipTerminator(' ', BEHAVIOR_CHIPIFY_ALL);
',', BEHAVIOR_CHIPIFY_ALL, nacho.addChipTerminator(';', BEHAVIOR_CHIPIFY_ALL);
'\n', BEHAVIOR_CHIPIFY_ALL,
' ', BEHAVIOR_CHIPIFY_ALL,
';', BEHAVIOR_CHIPIFY_ALL
)
);
nacho.enableEditChipOnTouch(true, true); nacho.enableEditChipOnTouch(true, true);
nacho.setOnFocusChangeListener((v, hasFocus) -> nacho.chipifyAllUnterminatedTokens()); nacho.setOnFocusChangeListener((v, hasFocus) -> nacho.chipifyAllUnterminatedTokens());
return nacho; return nacho;
@@ -271,8 +266,8 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
editText.setHint(item != null ? item.getDefaultTitle(ctx) : ctx.getString(R.string.sk_hashtag)); editText.setHint(item != null ? item.getDefaultTitle(ctx) : ctx.getString(R.string.sk_hashtag));
LinearLayout tagWrap = view.findViewById(R.id.tag_wrap); LinearLayout tagWrap = view.findViewById(R.id.tag_wrap);
boolean hashtagOptionsAvailable=item==null || item.getType()==TimelineDefinition.TimelineType.HASHTAG; boolean advancedOptionsAvailable = item == null || item.getType() == TimelineDefinition.TimelineType.HASHTAG;
advancedBtn.setVisibility(hashtagOptionsAvailable ? View.VISIBLE : View.GONE); advancedBtn.setVisibility(advancedOptionsAvailable ? View.VISIBLE : View.GONE);
advancedBtn.setOnClickListener(l -> { advancedBtn.setOnClickListener(l -> {
advancedBtn.setSelected(!advancedBtn.isSelected()); advancedBtn.setSelected(!advancedBtn.isSelected());
advancedBtn.setText(advancedBtn.isSelected() ? R.string.sk_advanced_options_hide : R.string.sk_advanced_options_show); advancedBtn.setText(advancedBtn.isSelected() ? R.string.sk_advanced_options_hide : R.string.sk_advanced_options_show);
@@ -282,14 +277,14 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
}); });
Switch localOnlySwitch = view.findViewById(R.id.local_only_switch); Switch localOnlySwitch = view.findViewById(R.id.local_only_switch);
view.findViewById(R.id.local_only).setOnClickListener(l->localOnlySwitch.setChecked(!localOnlySwitch.isChecked())); view.findViewById(R.id.local_only)
.setOnClickListener(l -> localOnlySwitch.setChecked(!localOnlySwitch.isChecked()));
EditText tagMain = view.findViewById(R.id.tag_main); EditText tagMain = view.findViewById(R.id.tag_main);
NachoTextView tagsAny = prepareChipTextView(view.findViewById(R.id.tags_any)); NachoTextView tagsAny = prepareChipTextView(view.findViewById(R.id.tags_any));
NachoTextView tagsAll = prepareChipTextView(view.findViewById(R.id.tags_all)); NachoTextView tagsAll = prepareChipTextView(view.findViewById(R.id.tags_all));
NachoTextView tagsNone = prepareChipTextView(view.findViewById(R.id.tags_none)); NachoTextView tagsNone = prepareChipTextView(view.findViewById(R.id.tags_none));
if (item != null) {
if(item!=null && hashtagOptionsAvailable){
tagMain.setText(item.getHashtagName()); tagMain.setText(item.getHashtagName());
boolean hasAdvanced = !TextUtils.isEmpty(item.getCustomTitle()) && !Objects.equals(item.getHashtagName(), item.getCustomTitle()); boolean hasAdvanced = !TextUtils.isEmpty(item.getCustomTitle()) && !Objects.equals(item.getHashtagName(), item.getCustomTitle());
hasAdvanced = setTagListContent(tagsAny, item.getHashtagAny()) || hasAdvanced; hasAdvanced = setTagListContent(tagsAny, item.getHashtagAny()) || hasAdvanced;
@@ -340,29 +335,25 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
.setTitle(item == null ? R.string.sk_add_timeline : R.string.sk_edit_timeline) .setTitle(item == null ? R.string.sk_add_timeline : R.string.sk_edit_timeline)
.setView(view) .setView(view)
.setPositiveButton(R.string.save, (d, which) -> { .setPositiveButton(R.string.save, (d, which) -> {
String name=editText.getText().toString().trim();
String mainHashtag=tagMain.getText().toString().trim();
if(item.getType()==TimelineDefinition.TimelineType.HASHTAG){
tagsAny.chipifyAllUnterminatedTokens(); tagsAny.chipifyAllUnterminatedTokens();
tagsAll.chipifyAllUnterminatedTokens(); tagsAll.chipifyAllUnterminatedTokens();
tagsNone.chipifyAllUnterminatedTokens(); tagsNone.chipifyAllUnterminatedTokens();
String name = editText.getText().toString().trim();
String mainHashtag = tagMain.getText().toString().trim();
if (TextUtils.isEmpty(mainHashtag)) { if (TextUtils.isEmpty(mainHashtag)) {
mainHashtag = name; mainHashtag = name;
name = null; name = null;
} }
if(TextUtils.isEmpty(mainHashtag) && (item!=null && item.getType()==TimelineDefinition.TimelineType.HASHTAG)){ if (TextUtils.isEmpty(mainHashtag)) {
Toast.makeText(ctx, R.string.sk_add_timeline_tag_error_empty, Toast.LENGTH_SHORT).show(); Toast.makeText(ctx, R.string.sk_add_timeline_tag_error_empty, Toast.LENGTH_SHORT).show();
onSave.accept(null); onSave.accept(null);
return; return;
} }
}
TimelineDefinition tl = item != null ? item : TimelineDefinition.ofHashtag(name); TimelineDefinition tl = item != null ? item : TimelineDefinition.ofHashtag(name);
TimelineDefinition.Icon icon = TimelineDefinition.Icon.values()[(int) btn.getTag()]; TimelineDefinition.Icon icon = TimelineDefinition.Icon.values()[(int) btn.getTag()];
tl.setIcon(icon); tl.setIcon(icon);
tl.setTitle(name); tl.setTitle(name);
if(item.getType()==TimelineDefinition.TimelineType.HASHTAG){
tl.setTagOptions( tl.setTagOptions(
mainHashtag, mainHashtag,
tagsAny.getChipValues(), tagsAny.getChipValues(),
@@ -370,7 +361,6 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
tagsNone.getChipValues(), tagsNone.getChipValues(),
localOnlySwitch.isChecked() localOnlySwitch.isChecked()
); );
}
onSave.accept(tl); onSave.accept(tl);
}) })
.setNegativeButton(R.string.cancel, (d, which) -> {}); .setNegativeButton(R.string.cancel, (d, which) -> {});

View File

@@ -45,8 +45,8 @@ public class FeaturedHashtagsListFragment extends BaseStatusListFragment<Hashtag
} }
@Override @Override
public void onItemClick(String id){ public void onItemClick(String hashtag){
UiUtils.openHashtagTimeline(getActivity(), accountID, Objects.requireNonNull(findItemOfType(id, HashtagStatusDisplayItem.class)).tag); UiUtils.openHashtagTimeline(getActivity(), accountID, hashtag, data.stream().filter(h -> Objects.equals(h.name, hashtag)).findAny().map(h -> h.following).orElse(null));
} }
@Override @Override

View File

@@ -17,7 +17,6 @@ import android.widget.TextView;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships; import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.accounts.GetFollowRequests; import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.HeaderPaginationList; import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.Relationship;
@@ -270,7 +269,7 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE); followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE); followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
relationship=relationships.get(item.account.id); relationship=relationships.get(item.account.id);
UiUtils.setExtraTextInfo(getContext(), null, true, false, false, item.account); UiUtils.setExtraTextInfo(getContext(), null, findViewById(R.id.pronouns), true, false, false, item.account);
if(relationship==null || !relationship.followedBy){ if(relationship==null || !relationship.followedBy){
actionWrap.setVisibility(View.GONE); actionWrap.setVisibility(View.GONE);
@@ -358,16 +357,15 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
public AccountWrapper(Account account){ public AccountWrapper(Account account){
this.account=account; this.account=account;
avaRequest=new UrlImageLoaderRequest( if(!TextUtils.isEmpty(account.avatar))
TextUtils.isEmpty(account.avatar) ? AccountSessionManager.get(getAccountID()).getDefaultAvatarUrl() : account.avatar, avaRequest=new UrlImageLoaderRequest(account.avatar, V.dp(50), V.dp(50));
V.dp(50), V.dp(50));
if(!TextUtils.isEmpty(account.header)) if(!TextUtils.isEmpty(account.header))
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000); coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID); parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
if(account.emojis.isEmpty()){ if(account.emojis.isEmpty()){
parsedName= account.getDisplayName(); parsedName=account.displayName;
}else{ }else{
parsedName=HtmlParser.parseCustomEmoji(account.getDisplayName(), account.emojis); parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio)); emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio));
} }
} }

View File

@@ -121,7 +121,7 @@ public class FollowedHashtagsFragment extends MastodonRecyclerFragment<Hashtag>
@Override @Override
public void onClick() { public void onClick() {
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name); UiUtils.openHashtagTimeline(getActivity(), accountID, item.name, item.following);
} }
} }
} }

View File

@@ -1,65 +1,46 @@
package org.joinmastodon.android.fragments; package org.joinmastodon.android.fragments;
import android.app.Activity; import android.app.Activity;
import android.content.res.TypedArray;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.view.HapticFeedbackConstants; import android.view.HapticFeedbackConstants;
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.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonErrorResponse; import org.joinmastodon.android.api.requests.tags.GetHashtag;
import org.joinmastodon.android.api.requests.tags.GetTag; import org.joinmastodon.android.api.requests.tags.SetHashtagFollowed;
import org.joinmastodon.android.api.requests.tags.SetTagFollowed;
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline; import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.events.HashtagUpdatedEvent;
import org.joinmastodon.android.model.FilterContext; import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Hashtag; import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.TimelineDefinition; import org.joinmastodon.android.model.TimelineDefinition;
import org.joinmastodon.android.ui.text.SpacerSpan;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ProgressBarButton; import org.joinmastodon.android.utils.StatusFilterPredicate;
import org.parceler.Parcels;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import androidx.annotation.NonNull;
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.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class HashtagTimelineFragment extends PinnableStatusListFragment { public class HashtagTimelineFragment extends PinnableStatusListFragment {
private Hashtag hashtag; private String hashtag;
private String hashtagName;
private TextView headerTitle, headerSubtitle;
private ProgressBarButton followButton;
private ProgressBar followProgress;
private MenuItem followMenuItem, pinMenuItem;
private boolean followRequestRunning;
private boolean toolbarContentVisible;
private List<String> any; private List<String> any;
private List<String> all; private List<String> all;
private List<String> none; private List<String> none;
private boolean following; private boolean following;
private boolean localOnly; private boolean localOnly;
private Menu optionsMenu; private MenuItem followButton;
private MenuInflater optionsMenuInflater;
@Override @Override
protected boolean wantsComposeButton() { protected boolean wantsComposeButton() {
@@ -69,36 +50,89 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
@Override @Override
public void onAttach(Activity activity){ public void onAttach(Activity activity){
super.onAttach(activity); super.onAttach(activity);
updateTitle(getArguments().getString("hashtag"));
following=getArguments().getBoolean("following", false); following=getArguments().getBoolean("following", false);
localOnly=getArguments().getBoolean("localOnly", false); localOnly=getArguments().getBoolean("localOnly", false);
any=getArguments().getStringArrayList("any"); any=getArguments().getStringArrayList("any");
all=getArguments().getStringArrayList("all"); all=getArguments().getStringArrayList("all");
none=getArguments().getStringArrayList("none"); none=getArguments().getStringArrayList("none");
if(getArguments().containsKey("hashtag")){
hashtag=Parcels.unwrap(getArguments().getParcelable("hashtag"));
hashtagName=hashtag.name;
}else{
hashtagName=getArguments().getString("hashtagName");
}
setTitle('#'+hashtagName);
setHasOptionsMenu(true); setHasOptionsMenu(true);
} }
private void updateTitle(String hashtagName) {
hashtag = hashtagName;
setTitle('#'+hashtag);
}
private void updateFollowingState(boolean newFollowing) {
this.following = newFollowing;
followButton.setTitle(getString(newFollowing ? R.string.unfollow_user : R.string.follow_user, "#" + hashtag));
followButton.setIcon(newFollowing ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
E.post(new HashtagUpdatedEvent(hashtag, following));
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.hashtag_timeline, menu);
super.onCreateOptionsMenu(menu, inflater);
followButton = menu.findItem(R.id.follow_hashtag);
updateFollowingState(following);
new GetHashtag(hashtag).setCallback(new Callback<>() {
@Override
public void onSuccess(Hashtag hashtag) {
if (getActivity() == null) return;
updateTitle(hashtag.name);
updateFollowingState(hashtag.following);
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getActivity());
}
}).exec(accountID);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (super.onOptionsItemSelected(item)) return true;
if (item.getItemId() == R.id.follow_hashtag) {
updateFollowingState(!following);
getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
new SetHashtagFollowed(hashtag, following).setCallback(new Callback<>() {
@Override
public void onSuccess(Hashtag i) {
if (getActivity() == null) return;
if (i.following == following) Toast.makeText(getActivity(), getString(i.following ? R.string.followed_user : R.string.unfollowed_user, "#" + i.name), Toast.LENGTH_SHORT).show();
updateFollowingState(i.following);
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getActivity());
updateFollowingState(!following);
}
}).exec(accountID);
return true;
}
return false;
}
@Override @Override
protected TimelineDefinition makeTimelineDefinition() { protected TimelineDefinition makeTimelineDefinition() {
return TimelineDefinition.ofHashtag(hashtagName); return TimelineDefinition.ofHashtag(hashtag);
} }
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
currentRequest=new GetHashtagTimeline(hashtagName, getMaxID(), null, count, any, all, none, localOnly, getLocalPrefs().timelineReplyVisibility) currentRequest=new GetHashtagTimeline(hashtag, offset==0 ? null : getMaxID(), null, count, any, all, none, localOnly, getLocalPrefs().timelineReplyVisibility)
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if (getActivity() == null) return; if (getActivity() == null) return;
boolean more=applyMaxID(result); result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext()); onDataLoaded(result, !result.isEmpty());
onDataLoaded(result, more);
} }
}) })
.exec(accountID); .exec(accountID);
@@ -112,40 +146,15 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
} }
@Override @Override
public void loadData(){ public boolean onFabLongClick(View v) {
reloadTag(); return UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtag+' ');
super.loadData();
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
fab=view.findViewById(R.id.fab);
fab.setOnClickListener(this::onFabClick);
if(getParentFragment() instanceof HomeTabFragment) return;
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
View topChild=recyclerView.getChildAt(0);
int firstChildPos=recyclerView.getChildAdapterPosition(topChild);
float newAlpha=firstChildPos>0 ? 1f : Math.min(1f, -topChild.getTop()/(float)headerTitle.getHeight());
toolbarTitleView.setAlpha(newAlpha);
boolean newToolbarVisibility=newAlpha>0.5f;
if(newToolbarVisibility!=toolbarContentVisible){
toolbarContentVisible=newToolbarVisibility;
createOptionsMenu();
}
}
});
} }
@Override @Override
public 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", '#'+hashtagName+' '); args.putString("prefilledText", '#'+hashtag+' ');
Nav.go(getActivity(), ComposeFragment.class, args); Nav.go(getActivity(), ComposeFragment.class, args);
} }
@@ -161,202 +170,6 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
@Override @Override
public Uri getWebUri(Uri.Builder base) { public Uri getWebUri(Uri.Builder base) {
return base.path((isInstanceAkkoma() ? "/tag/" : "/tags/") + hashtag).build(); return base.path((isInstanceAkkoma() ? "/tag/" : "/tags") + hashtag).build();
}
@Override
protected RecyclerView.Adapter getAdapter(){
View header=getActivity().getLayoutInflater().inflate(R.layout.header_hashtag_timeline, list, false);
headerTitle=header.findViewById(R.id.title);
headerSubtitle=header.findViewById(R.id.subtitle);
followButton=header.findViewById(R.id.profile_action_btn);
followProgress=header.findViewById(R.id.action_progress);
headerTitle.setText("#"+hashtagName);
followButton.setVisibility(View.GONE);
followButton.setOnClickListener(v->{
if(hashtag==null)
return;
setFollowed(!hashtag.following);
});
followButton.setOnLongClickListener(v->{
if(hashtag==null) return false;
UiUtils.pickAccount(getActivity(), accountID, R.string.sk_follow_as, R.drawable.ic_fluent_person_add_28_regular, session -> {
new SetTagFollowed(hashtagName, true).setCallback(new Callback<>(){
@Override
public void onSuccess(Hashtag hashtag) {
Toast.makeText(
getActivity(),
getString(R.string.sk_followed_as, session.self.getShortUsername()),
Toast.LENGTH_SHORT
).show();
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getActivity());
}
}).exec(session.getID());
}, null);
return true;
});
updateHeader();
MergeRecyclerAdapter mergeAdapter=new MergeRecyclerAdapter();
if(!(getParentFragment() instanceof HomeTabFragment)){
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(header));
}
mergeAdapter.addAdapter(super.getAdapter());
return mergeAdapter;
}
@Override
protected int getMainAdapterOffset(){
return 1;
}
private void createOptionsMenu(){
optionsMenu.clear();
optionsMenuInflater.inflate(R.menu.hashtag_timeline, optionsMenu);
followMenuItem=optionsMenu.findItem(R.id.follow_hashtag);
pinMenuItem=optionsMenu.findItem(R.id.pin);
followMenuItem.setVisible(toolbarContentVisible);
pinMenuItem.setShowAsAction(toolbarContentVisible ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_ALWAYS);
super.updatePinButton(pinMenuItem);
if(toolbarContentVisible){
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu);
}else{
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu, R.id.pin);
}
}
@Override
public void updatePinButton(MenuItem pin){
super.updatePinButton(pin);
if(toolbarContentVisible) UiUtils.insetPopupMenuIcon(getContext(), pin);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
inflater.inflate(R.menu.hashtag_timeline, menu);
super.onCreateOptionsMenu(menu, inflater);
optionsMenu=menu;
optionsMenuInflater=inflater;
createOptionsMenu();
}
@Override
public boolean onOptionsItemSelected(MenuItem item){
if (super.onOptionsItemSelected(item)) return true;
if (item.getItemId() == R.id.follow_hashtag && hashtag!=null) {
setFollowed(!hashtag.following);
}
return true;
}
@Override
protected void onUpdateToolbar(){
super.onUpdateToolbar();
toolbarTitleView.setAlpha(toolbarContentVisible ? 1f : 0f);
createOptionsMenu();
}
private void updateHeader(){
if(hashtag==null || getActivity()==null)
return;
if(hashtag.history!=null && !hashtag.history.isEmpty()){
int weekPosts=hashtag.history.stream().mapToInt(h->h.uses).sum();
int todayPosts=hashtag.history.get(0).uses;
int numAccounts=hashtag.history.stream().mapToInt(h->h.accounts).sum();
int hSpace=V.dp(8);
SpannableStringBuilder ssb=new SpannableStringBuilder();
ssb.append(getResources().getQuantityString(R.plurals.x_posts, weekPosts, weekPosts));
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
ssb.append('·');
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
ssb.append(getResources().getQuantityString(R.plurals.x_participants, numAccounts, numAccounts));
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
ssb.append('·');
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
ssb.append(getResources().getQuantityString(R.plurals.x_posts_today, todayPosts, todayPosts));
headerSubtitle.setText(ssb);
}
int styleRes;
followButton.setVisibility(View.VISIBLE);
if(hashtag.following){
followButton.setText(R.string.button_following);
styleRes=R.style.Widget_Mastodon_M3_Button_Tonal;
}else{
followButton.setText(R.string.button_follow);
styleRes=R.style.Widget_Mastodon_M3_Button_Filled;
}
TypedArray ta=followButton.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background});
followButton.setBackground(ta.getDrawable(0));
ta.recycle();
ta=followButton.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.textColor});
followButton.setTextColor(ta.getColorStateList(0));
followProgress.setIndeterminateTintList(ta.getColorStateList(0));
ta.recycle();
followButton.setTextVisible(true);
followProgress.setVisibility(View.GONE);
if(followMenuItem!=null){
followMenuItem.setTitle(getString(hashtag.following ? R.string.unfollow_user : R.string.follow_user, "#"+hashtagName));
followMenuItem.setIcon(hashtag.following ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
UiUtils.insetPopupMenuIcon(getContext(), followMenuItem);
}
}
private void reloadTag(){
new GetTag(hashtagName)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Hashtag result){
hashtag=result;
updateHeader();
}
@Override
public void onError(ErrorResponse error){
}
})
.exec(accountID);
}
private void setFollowed(boolean followed){
if(followRequestRunning)
return;
getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
followButton.setTextVisible(false);
followProgress.setVisibility(View.VISIBLE);
followRequestRunning=true;
new SetTagFollowed(hashtagName, followed)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Hashtag result){
if(getActivity()==null)
return;
hashtag=result;
updateHeader();
followRequestRunning=false;
}
@Override
public void onError(ErrorResponse error){
if(getActivity()==null)
return;
if(error instanceof MastodonErrorResponse er && "Duplicate record".equals(er.error)){
hashtag.following=true;
}else{
error.showToast(getActivity());
}
updateHeader();
followRequestRunning=false;
}
})
.exec(accountID);
} }
} }

View File

@@ -32,6 +32,7 @@ import org.joinmastodon.android.events.NotificationsMarkerUpdatedEvent;
import org.joinmastodon.android.events.StatusDisplaySettingsChangedEvent; import org.joinmastodon.android.events.StatusDisplaySettingsChangedEvent;
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.model.Notification;
import org.joinmastodon.android.model.PaginatedResponse; import org.joinmastodon.android.model.PaginatedResponse;
import org.joinmastodon.android.ui.AccountSwitcherSheet; import org.joinmastodon.android.ui.AccountSwitcherSheet;
@@ -70,12 +71,14 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
private TextView notificationsBadge; private TextView notificationsBadge;
private String accountID; private String accountID;
private boolean isAkkoma;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
accountID=getArguments().getString("account"); accountID=getArguments().getString("account");
setTitle(R.string.sk_app_name); setTitle(R.string.sk_app_name);
isAkkoma = getInstance().map(Instance::isAkkoma).orElse(false);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N) if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
setRetainInstance(true); setRetainInstance(true);
@@ -86,6 +89,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
homeTabFragment=new HomeTabFragment(); homeTabFragment=new HomeTabFragment();
homeTabFragment.setArguments(args); homeTabFragment.setArguments(args);
args=new Bundle(args); args=new Bundle(args);
args.putBoolean("disableDiscover", isAkkoma);
args.putBoolean("noAutoLoad", true); args.putBoolean("noAutoLoad", true);
discoverFragment=new DiscoverFragment(); discoverFragment=new DiscoverFragment();
discoverFragment.setArguments(args); discoverFragment.setArguments(args);
@@ -180,7 +184,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
}); });
} }
} }
tabBar.selectTab(currentTab);
return content; return content;
} }
@@ -292,7 +295,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
if(tab==R.id.tab_profile){ if(tab==R.id.tab_profile){
ArrayList<String> options=new ArrayList<>(); ArrayList<String> options=new ArrayList<>();
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){ for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
options.add(session.self.getDisplayName()+"\n("+session.self.username+"@"+session.domain+")"); options.add(session.self.displayName+"\n("+session.self.username+"@"+session.domain+")");
} }
new AccountSwitcherSheet(getActivity(), this).show(); new AccountSwitcherSheet(getActivity(), this).show();
return true; return true;

View File

@@ -240,18 +240,14 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
if (toolbarWidth == 0) return; if (toolbarWidth == 0) return;
int toolbarFrameWidth = toolbarFrame.getWidth(); int toolbarFrameWidth = toolbarFrame.getWidth();
int actionsWidth=toolbarWidth-toolbarFrameWidth; int padding = toolbarWidth - toolbarFrameWidth;
// margin (4) + padding (12) + icon (24) + margin (8) + chevron (16) + padding (12)
int switcherWidth=V.dp(76);
FrameLayout parent = ((FrameLayout) toolbarShowNewPostsBtn.getParent()); FrameLayout parent = ((FrameLayout) toolbarShowNewPostsBtn.getParent());
if(actionsWidth==parent.getPaddingStart()) return; if (padding == parent.getPaddingStart()) return;
int paddingMax=Math.max(actionsWidth, switcherWidth);
int paddingEnd=(Math.max(0, switcherWidth-actionsWidth));
// toolbar frame goes from screen edge to beginning of right-aligned option buttons. // toolbar frame goes from screen edge to beginning of right-aligned option buttons.
// centering button by applying the same space on the left // centering button by applying the same space on the left
parent.setPaddingRelative(paddingMax, 0, paddingEnd, 0); parent.setPaddingRelative(padding, 0, 0, 0);
toolbarShowNewPostsBtn.setMaxWidth(toolbarWidth-paddingMax*2); toolbarShowNewPostsBtn.setMaxWidth(toolbarWidth - padding * 2);
switcher.setPivotX(V.dp(28)); // padding + half of icon switcher.setPivotX(V.dp(28)); // padding + half of icon
switcher.setPivotY(switcher.getHeight() / 2f); switcher.setPivotY(switcher.getHeight() / 2f);
@@ -404,9 +400,10 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
addListsToOverflowMenu(); addListsToOverflowMenu();
addHashtagsToOverflowMenu(); addHashtagsToOverflowMenu();
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P && !UiUtils.isEMUI()) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !UiUtils.isEMUI()) {
m.setGroupDividerEnabled(true); m.setGroupDividerEnabled(true);
} }
}
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
@@ -527,7 +524,9 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
if (list.repliesPolicy != null) args.putInt("repliesPolicy", list.repliesPolicy.ordinal()); if (list.repliesPolicy != null) args.putInt("repliesPolicy", list.repliesPolicy.ordinal());
Nav.go(getActivity(), ListTimelineFragment.class, args); Nav.go(getActivity(), ListTimelineFragment.class, args);
} else if ((hashtag = hashtagsItems.get(id)) != null) { } else if ((hashtag = hashtagsItems.get(id)) != null) {
UiUtils.openHashtagTimeline(getContext(), accountID, hashtag); args.putString("hashtag", hashtag.name);
args.putBoolean("following", hashtag.following);
Nav.go(getActivity(), HashtagTimelineFragment.class, args);
} }
return true; return true;
} }

View File

@@ -11,26 +11,27 @@ import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.GlobalUserPreferences;
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.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.model.CacheablePaginatedResponse; import org.joinmastodon.android.model.CacheablePaginatedResponse;
import org.joinmastodon.android.model.FilterContext; import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.TimelineMarkers; import org.joinmastodon.android.model.TimelineMarkers;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
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.utils.V;
public class HomeTimelineFragment extends StatusListFragment { public class HomeTimelineFragment extends StatusListFragment {
private HomeTabFragment parent; private HomeTabFragment parent;
@@ -49,6 +50,16 @@ public class HomeTimelineFragment extends StatusListFragment {
loadData(); loadData();
} }
private boolean typeFilterPredicate(Status s) {
AccountLocalPreferences lp=getLocalPrefs();
return (lp.showReplies || s.inReplyToId == null) &&
(lp.showBoosts || s.reblog == null);
}
private List<Status> filterPosts(List<Status> items) {
return items.stream().filter(this::typeFilterPredicate).collect(Collectors.toList());
}
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
AccountSessionManager.getInstance() AccountSessionManager.getInstance()
@@ -57,11 +68,10 @@ public class HomeTimelineFragment extends StatusListFragment {
@Override @Override
public void onSuccess(CacheablePaginatedResponse<List<Status>> result){ public void onSuccess(CacheablePaginatedResponse<List<Status>> result){
if (getActivity() == null) return; if (getActivity() == null) return;
boolean empty=result.items.isEmpty(); List<Status> filteredItems = filterPosts(result.items);
maxID=result.maxID; maxID=result.maxID;
AccountSessionManager.get(accountID).filterStatuses(result.items, getFilterContext()); onDataLoaded(filteredItems, !result.items.isEmpty());
onDataLoaded(result.items, !empty); if(result.isFromCache())
if(result.isFromCache() && GlobalUserPreferences.loadNewPosts)
loadNewPosts(); loadNewPosts();
} }
}); });
@@ -87,7 +97,7 @@ public class HomeTimelineFragment extends StatusListFragment {
if(!getArguments().getBoolean("noAutoLoad")){ if(!getArguments().getBoolean("noAutoLoad")){
if(!loaded && !dataLoading){ if(!loaded && !dataLoading){
loadData(); loadData();
}else if(!dataLoading && GlobalUserPreferences.loadNewPosts){ }else if(!dataLoading){
loadNewPosts(); loadNewPosts();
} }
} }
@@ -117,46 +127,38 @@ public class HomeTimelineFragment extends StatusListFragment {
} }
public void onStatusCreated(Status status){ public void onStatusCreated(Status status){
if(status.reblog!=null) return;
prependItems(Collections.singletonList(status), true); prependItems(Collections.singletonList(status), true);
} }
private void loadNewPosts(){ private void loadNewPosts(){
if (!GlobalUserPreferences.loadNewPosts) return;
dataLoading=true; dataLoading=true;
// we only care about the data that was actually retrieved from the timeline api since
// user-created statuses are probably in the wrong position
List<Status> dataFromTimeline=data.stream().filter(s->!s.fromStatusCreated).collect(Collectors.toList());
// The idea here is that we request the timeline such that if there are fewer than `limit` posts, // The idea here is that we request the timeline such that if there are fewer than `limit` posts,
// we'll get the currently topmost post as last in the response. This way we know there's no gap // we'll get the currently topmost post as last in the response. This way we know there's no gap
// between the existing and newly loaded parts of the timeline. // between the existing and newly loaded parts of the timeline.
String sinceID=dataFromTimeline.size()>1 ? dataFromTimeline.get(1).id : "1"; String sinceID=data.size()>1 ? data.get(1).id : "1";
currentRequest=new GetHomeTimeline(null, null, 20, sinceID, getLocalPrefs().timelineReplyVisibility) currentRequest=new GetHomeTimeline(null, null, 20, sinceID, getLocalPrefs().timelineReplyVisibility)
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
currentRequest=null; currentRequest=null;
dataLoading=false; dataLoading=false;
refreshDone(); result = filterPosts(result);
if(result.isEmpty() || getActivity()==null) if(result.isEmpty() || getActivity()==null)
return; return;
Status last=result.get(result.size()-1); Status last=result.get(result.size()-1);
List<Status> toAdd; List<Status> toAdd;
if(!dataFromTimeline.isEmpty() && last.id.equals(dataFromTimeline.get(0).id)){ // This part intersects with the existing one if(!data.isEmpty() && last.id.equals(data.get(0).id)){ // This part intersects with the existing one
toAdd=new ArrayList<>(result.subList(0, result.size()-1)); // Remove the already known last post toAdd=result.subList(0, result.size()-1); // Remove the already known last post
}else{ }else{
last.hasGapAfter=last.id; result.get(result.size()-1).hasGapAfter=true;
toAdd=result; toAdd=result;
} }
if(!toAdd.isEmpty())
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(new ArrayList<>(toAdd), false);
// removing statuses that come up as duplicates (hopefully only posts and boosts that were locally created
// and thus were already prepended to the timeline earlier)
List<String> existingIds=data.stream().map(Status::getID).collect(Collectors.toList());
toAdd.removeIf(s->existingIds.contains(s.getID()));
AccountSessionManager.get(accountID).filterStatuses(toAdd, getFilterContext()); AccountSessionManager.get(accountID).filterStatuses(toAdd, getFilterContext());
if(!toAdd.isEmpty()){ if(!toAdd.isEmpty()){
prependItems(toAdd, true); prependItems(toAdd, true);
if (parent != null && GlobalUserPreferences.showNewPostsButton) parent.showNewPostsButton(); if (parent != null && GlobalUserPreferences.showNewPostsButton) parent.showNewPostsButton();
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(toAdd, false);
} }
} }
@@ -164,7 +166,6 @@ public class HomeTimelineFragment extends StatusListFragment {
public void onError(ErrorResponse error){ public void onError(ErrorResponse error){
currentRequest=null; currentRequest=null;
dataLoading=false; dataLoading=false;
refreshDone();
} }
}) })
.exec(accountID); .exec(accountID);
@@ -175,23 +176,15 @@ public class HomeTimelineFragment extends StatusListFragment {
} }
@Override @Override
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){ public void onGapClick(GapStatusDisplayItem.Holder item){
if(dataLoading) if(dataLoading)
return; return;
item.getItem().loading=true;
V.setVisibilityAnimated(item.progress, View.VISIBLE);
V.setVisibilityAnimated(item.text, View.GONE);
GapStatusDisplayItem gap=item.getItem(); GapStatusDisplayItem gap=item.getItem();
gap.loading=true;
dataLoading=true; dataLoading=true;
currentRequest=new GetHomeTimeline(item.getItemID(), null, 20, null, getLocalPrefs().timelineReplyVisibility)
String maxID=null;
String minID=null;
if (downwards) {
maxID=item.getItem().getMaxID();
} else {
int gapPos=displayItems.indexOf(gap);
StatusDisplayItem nextItem=displayItems.get(gapPos + 1);
minID=nextItem.parentID;
}
currentRequest=new GetHomeTimeline(maxID, minID, 20, null, getLocalPrefs().timelineReplyVisibility)
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
@@ -202,18 +195,15 @@ public class HomeTimelineFragment extends StatusListFragment {
int gapPos=displayItems.indexOf(gap); int gapPos=displayItems.indexOf(gap);
if(gapPos==-1) if(gapPos==-1)
return; return;
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
if(result.isEmpty()){ if(result.isEmpty()){
displayItems.remove(gapPos); displayItems.remove(gapPos);
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos); adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
Status gapStatus=getStatusByID(gap.parentID); Status gapStatus=getStatusByID(gap.parentID);
if(gapStatus!=null){ if(gapStatus!=null){
gapStatus.hasGapAfter=null; gapStatus.hasGapAfter=false;
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false); AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
} }
}else{ }else{
// TODO: refactor this code. it's too long. incomprehensible, even
if(downwards) {
Set<String> idsBelowGap=new HashSet<>(); Set<String> idsBelowGap=new HashSet<>();
boolean belowGap=false; boolean belowGap=false;
int gapPostIndex=0; int gapPostIndex=0;
@@ -222,7 +212,7 @@ public class HomeTimelineFragment extends StatusListFragment {
idsBelowGap.add(s.id); idsBelowGap.add(s.id);
}else if(s.id.equals(gap.parentID)){ }else if(s.id.equals(gap.parentID)){
belowGap=true; belowGap=true;
s.hasGapAfter=null; s.hasGapAfter=false;
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false); AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false);
}else{ }else{
gapPostIndex++; gapPostIndex++;
@@ -235,21 +225,23 @@ public class HomeTimelineFragment extends StatusListFragment {
break; break;
} }
if(endIndex==result.size()){ if(endIndex==result.size()){
Status last=result.get(result.size()-1); result.get(result.size()-1).hasGapAfter=true;
last.hasGapAfter=last.id;
}else{ }else{
result=result.subList(0, endIndex); result=result.subList(0, endIndex);
} }
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.HOME);
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, getFilterContext());
for(Status s:result){ for(Status s:result){
if(idsBelowGap.contains(s.id)) if(idsBelowGap.contains(s.id))
break; break;
if(typeFilterPredicate(s) && filterPredicate.test(s)){
targetList.addAll(buildDisplayItems(s)); targetList.addAll(buildDisplayItems(s));
insertedPosts.add(s); insertedPosts.add(s);
} }
}
AccountSessionManager.get(accountID).filterStatuses(insertedPosts, getFilterContext());
if(targetList.isEmpty()){ if(targetList.isEmpty()){
// oops. We didn't add new posts, but at least we know there are none. // oops. We didn't add new posts, but at least we know there are none.
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos); adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
@@ -258,52 +250,6 @@ public class HomeTimelineFragment extends StatusListFragment {
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1); adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
} }
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false); AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
} else {
String aboveGapID = gap.parentID;
int gapPostIndex = 0;
for (;gapPostIndex<data.size();gapPostIndex++){
if (Objects.equals(aboveGapID, data.get(gapPostIndex).id)) {
break;
}
}
// find if there's an overlap between the new data and the current data
int indexOfGapInResponse = 0;
for (;indexOfGapInResponse<result.size();indexOfGapInResponse++){
if (Objects.equals(aboveGapID, result.get(indexOfGapInResponse).id)) {
break;
}
}
// there is an overlap between new and current data
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
if(indexOfGapInResponse<result.size()){
result=result.subList(indexOfGapInResponse+1,result.size());
Optional<Status> gapStatus=data.stream()
.filter(s->Objects.equals(s.id, gap.parentID))
.findFirst();
if (gapStatus.isPresent()) {
gapStatus.get().hasGapAfter=null;
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus.get()), false);
}
targetList.clear();
} else {
gap.loading=false;
}
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
for(Status s:result){
targetList.addAll(buildDisplayItems(s));
insertedPosts.add(s);
}
AccountSessionManager.get(accountID).filterStatuses(insertedPosts, FilterContext.HOME);
if(targetList.isEmpty()){
// oops. We didn't add new posts, but at least we know there are none.
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
}else{
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
}
list.scrollToPosition(getMainAdapterOffset()+gapPos+targetList.size());
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
}
} }
} }

View File

@@ -16,7 +16,6 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.lists.GetList; import org.joinmastodon.android.api.requests.lists.GetList;
import org.joinmastodon.android.api.requests.lists.UpdateList; import org.joinmastodon.android.api.requests.lists.UpdateList;
import org.joinmastodon.android.api.requests.timelines.GetListTimeline; import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.ListDeletedEvent; import org.joinmastodon.android.events.ListDeletedEvent;
import org.joinmastodon.android.events.ListUpdatedCreatedEvent; import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
import org.joinmastodon.android.model.FilterContext; import org.joinmastodon.android.model.FilterContext;
@@ -26,8 +25,10 @@ import org.joinmastodon.android.model.TimelineDefinition;
import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ListEditor; import org.joinmastodon.android.ui.views.ListEditor;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
@@ -133,14 +134,13 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
@Override @Override
protected void doLoadData(int offset, int count) { protected void doLoadData(int offset, int count) {
currentRequest=new GetListTimeline(listID, getMaxID(), null, count, null, getLocalPrefs().timelineReplyVisibility) currentRequest=new GetListTimeline(listID, offset==0 ? null : getMaxID(), null, count, null, getLocalPrefs().timelineReplyVisibility)
.setCallback(new SimpleCallback<>(this) { .setCallback(new SimpleCallback<>(this) {
@Override @Override
public void onSuccess(List<Status> result) { public void onSuccess(List<Status> result) {
if (getActivity() == null) return; if (getActivity() == null) return;
boolean more=applyMaxID(result); result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext()); onDataLoaded(result, !result.isEmpty());
onDataLoaded(result, more);
} }
}) })
.exec(accountID); .exec(accountID);

View File

@@ -96,7 +96,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
if (n.type == Notification.Type.FOLLOW_REQUEST) { if (n.type == Notification.Type.FOLLOW_REQUEST) {
ArrayList<StatusDisplayItem> items = new ArrayList<>(); ArrayList<StatusDisplayItem> items = new ArrayList<>();
items.add(titleItem); items.add(titleItem);
items.add(new AccountCardStatusDisplayItem(n.id, this, accountID, n.account, n)); items.add(new AccountCardStatusDisplayItem(n.id, this, n.account, n));
return items; return items;
} }
if(n.status!=null){ if(n.status!=null){
@@ -134,7 +134,6 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
return; return;
maxID=result.maxID; maxID=result.maxID;
onDataLoaded(result.items.stream().filter(n->n.type!=null).collect(Collectors.toList()), !result.items.isEmpty()); onDataLoaded(result.items.stream().filter(n->n.type!=null).collect(Collectors.toList()), !result.items.isEmpty());
if(bannerHelper!=null) bannerHelper.onBannerBecameVisible();
reloadingFromCache=false; reloadingFromCache=false;
if (getParentFragment() instanceof NotificationsFragment nf) { if (getParentFragment() instanceof NotificationsFragment nf) {
nf.updateMarkAllReadButton(); nf.updateMarkAllReadButton();
@@ -174,6 +173,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
@Override @Override
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 RecyclerView.ItemDecoration(){ list.addItemDecoration(new RecyclerView.ItemDecoration(){
private Paint paint=new Paint(); private Paint paint=new Paint();
private Rect tmpRect=new Rect(); private Rect tmpRect=new Rect();
@@ -237,7 +237,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
continue; continue;
Status contentStatus=ntf.status.getContentStatus(); Status contentStatus=ntf.status.getContentStatus();
if(contentStatus.poll!=null && contentStatus.poll.id.equals(ev.poll.id)){ if(contentStatus.poll!=null && contentStatus.poll.id.equals(ev.poll.id)){
updatePoll(ntf.id, contentStatus, ev.poll); updatePoll(ntf.id, ntf.status, ev.poll);
} }
} }
} }

View File

@@ -5,7 +5,6 @@ import android.os.Bundle;
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;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.FilterContext; import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
@@ -36,8 +35,6 @@ public class PinnedPostsListFragment extends StatusListFragment{
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if(getActivity()==null) return;
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
onDataLoaded(result, false); onDataLoaded(result, false);
} }
}).exec(accountID); }).exec(accountID);

View File

@@ -21,11 +21,9 @@ import android.graphics.drawable.LayerDrawable;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable;
import android.text.InputType;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher; import android.text.style.ImageSpan;
import android.transition.ChangeBounds; import android.transition.ChangeBounds;
import android.transition.Fade; import android.transition.Fade;
import android.transition.TransitionManager; import android.transition.TransitionManager;
@@ -59,14 +57,11 @@ import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses; import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount; import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed; import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
import org.joinmastodon.android.api.requests.accounts.SetPrivateNote;
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials; import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
import org.joinmastodon.android.api.requests.instance.GetInstance; import org.joinmastodon.android.api.requests.instance.GetInstance;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.account_list.BlockedAccountsListFragment;
import org.joinmastodon.android.fragments.account_list.FollowerListFragment; import org.joinmastodon.android.fragments.account_list.FollowerListFragment;
import org.joinmastodon.android.fragments.account_list.FollowingListFragment; import org.joinmastodon.android.fragments.account_list.FollowingListFragment;
import org.joinmastodon.android.fragments.account_list.MutedAccountsListFragment;
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment; import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
import org.joinmastodon.android.fragments.settings.SettingsServerFragment; import org.joinmastodon.android.fragments.settings.SettingsServerFragment;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
@@ -137,7 +132,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private ImageView avatar; private ImageView avatar;
private CoverImageView cover; private CoverImageView cover;
private View avatarBorder; private View avatarBorder;
private View usernameWrap;
private TextView name, username, bio, followersCount, followersLabel, followingCount, followingLabel; private TextView name, username, bio, followersCount, followersLabel, followingCount, followingLabel;
private ImageView lockIcon, botIcon; private ImageView lockIcon, botIcon;
private ProgressBarButton actionButton, notifyButton; private ProgressBarButton actionButton, notifyButton;
@@ -149,7 +143,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private SwipeRefreshLayout refreshLayout; private SwipeRefreshLayout refreshLayout;
private View followersBtn, followingBtn; private View followersBtn, followingBtn;
private EditText nameEdit, bioEdit; private EditText nameEdit, bioEdit;
private ProgressBar actionProgress, notifyProgress, noteSaveProgress; private ProgressBar actionProgress, notifyProgress;
private FrameLayout[] tabViews; private FrameLayout[] tabViews;
private TabLayoutMediator tabLayoutMediator; private TabLayoutMediator tabLayoutMediator;
private TextView followsYouView; private TextView followsYouView;
@@ -190,11 +184,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private ItemTouchHelper dragHelper=new ItemTouchHelper(new ReorderCallback()); private ItemTouchHelper dragHelper=new ItemTouchHelper(new ReorderCallback());
private ListImageLoaderWrapper imgLoader; private ListImageLoaderWrapper imgLoader;
// profile note
private FrameLayout noteWrap;
private ImageButton noteSaveBtn;
private EditText noteEdit;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@@ -243,7 +232,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
cover=content.findViewById(R.id.cover); cover=content.findViewById(R.id.cover);
avatarBorder=content.findViewById(R.id.avatar_border); avatarBorder=content.findViewById(R.id.avatar_border);
name=content.findViewById(R.id.name); name=content.findViewById(R.id.name);
usernameWrap=content.findViewById(R.id.username_wrap);
username=content.findViewById(R.id.username); username=content.findViewById(R.id.username);
lockIcon=content.findViewById(R.id.lock_icon); lockIcon=content.findViewById(R.id.lock_icon);
botIcon=content.findViewById(R.id.bot_icon); botIcon=content.findViewById(R.id.bot_icon);
@@ -266,7 +254,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
bioEditWrap=content.findViewById(R.id.bio_edit_wrap); bioEditWrap=content.findViewById(R.id.bio_edit_wrap);
actionProgress=content.findViewById(R.id.action_progress); actionProgress=content.findViewById(R.id.action_progress);
notifyProgress=content.findViewById(R.id.notify_progress); notifyProgress=content.findViewById(R.id.notify_progress);
noteSaveProgress=content.findViewById(R.id.note_save_progress);
fab=content.findViewById(R.id.fab); fab=content.findViewById(R.id.fab);
followsYouView=content.findViewById(R.id.follows_you); followsYouView=content.findViewById(R.id.follows_you);
countersLayout=content.findViewById(R.id.profile_counters); countersLayout=content.findViewById(R.id.profile_counters);
@@ -281,51 +268,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
avatar.setOutlineProvider(OutlineProviders.roundedRect(24)); avatar.setOutlineProvider(OutlineProviders.roundedRect(24));
avatar.setClipToOutline(true); avatar.setClipToOutline(true);
noteEdit=content.findViewById(R.id.note_edit);
noteWrap=content.findViewById(R.id.note_edit_wrap);
noteSaveBtn=content.findViewById(R.id.note_save_btn);
noteSaveBtn.setOnClickListener((v->{
savePrivateNote(noteEdit.getText().toString());
InputMethodManager imm=(InputMethodManager) getContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(this.getView().getRootView().getWindowToken(), 0);
noteEdit.clearFocus();
noteSaveBtn.clearFocus();
}));
noteEdit.setOnFocusChangeListener((v, hasFocus)->{
if(hasFocus){
hideFab();
V.setVisibilityAnimated(noteSaveBtn, View.VISIBLE);
noteEdit.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
}else if(!noteSaveBtn.hasFocus()){
showFab();
hideNoteSaveBtnIfNotDirty();
}
});
noteEdit.addTextChangedListener(new TextWatcher(){
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after){}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count){
if(relationship!=null && noteSaveBtn.getVisibility()!=View.VISIBLE && !s.toString().equals(relationship.note))
V.setVisibilityAnimated(noteSaveBtn, View.VISIBLE);
}
@Override
public void afterTextChanged(Editable s){}
});
noteSaveBtn.setOnFocusChangeListener((v, hasFocus)->{
if(!hasFocus && !noteEdit.hasFocus()){
showFab();
hideNoteSaveBtnIfNotDirty();
}
});
FrameLayout sizeWrapper=new FrameLayout(getActivity()){ FrameLayout sizeWrapper=new FrameLayout(getActivity()){
@Override @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
@@ -362,12 +304,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
tabbar.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant), UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary)); tabbar.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant), UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary));
tabbar.setTabTextSize(V.dp(14)); tabbar.setTabTextSize(V.dp(14));
tabLayoutMediator=new TabLayoutMediator(tabbar, pager, (tab, position)->tab.setText(switch(position){
case 0 -> R.string.profile_featured;
case 1 -> R.string.profile_timeline;
case 2 -> R.string.profile_about;
default -> throw new IllegalStateException();
}));
tabLayoutMediator=new TabLayoutMediator(tabbar, pager, new TabLayoutMediator.TabConfigurationStrategy(){ tabLayoutMediator=new TabLayoutMediator(tabbar, pager, new TabLayoutMediator.TabConfigurationStrategy(){
@Override @Override
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){ public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
@@ -381,19 +317,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if (position == 4) tab.view.setVisibility(View.GONE); if (position == 4) tab.view.setVisibility(View.GONE);
} }
}); });
tabbar.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
@Override
public void onTabSelected(TabLayout.Tab tab){}
@Override
public void onTabUnselected(TabLayout.Tab tab){}
@Override
public void onTabReselected(TabLayout.Tab tab){
if(getFragmentForPage(tab.getPosition()) instanceof ScrollableToTop stt)
stt.scrollToTop();
}
});
cover.setOutlineProvider(new ViewOutlineProvider(){ cover.setOutlineProvider(new ViewOutlineProvider(){
@Override @Override
@@ -454,7 +377,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
} }
}); });
content.findViewById(R.id.username_wrap).setOnLongClickListener(v->{ username.setOnLongClickListener(v->{
String usernameString=account.acct; String usernameString=account.acct;
if(!usernameString.contains("@")){ if(!usernameString.contains("@")){
usernameString+="@"+domain; usernameString+="@"+domain;
@@ -490,46 +413,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
return sizeWrapper; return sizeWrapper;
} }
private void hideNoteSaveBtnIfNotDirty(){
if(noteEdit.getText().toString().equals(relationship.note)){
V.setVisibilityAnimated(noteSaveBtn, View.INVISIBLE);
}
}
private void showPrivateNote(){
noteWrap.setVisibility(View.VISIBLE);
noteEdit.setText(relationship.note);
}
private void hidePrivateNote(){
noteWrap.setVisibility(View.GONE);
noteEdit.setText(null);
}
private void savePrivateNote(String note){
if(note!=null && note.equals(relationship.note)){
updateRelationship();
invalidateOptionsMenu();
return;
}
V.setVisibilityAnimated(noteSaveProgress, View.VISIBLE);
V.setVisibilityAnimated(noteSaveBtn, View.INVISIBLE);
new SetPrivateNote(profileAccountID, note).setCallback(new Callback<>() {
@Override
public void onSuccess(Relationship result) {
updateRelationship(result);
invalidateOptionsMenu();
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getContext());
V.setVisibilityAnimated(noteSaveProgress, View.GONE);
V.setVisibilityAnimated(noteSaveBtn, View.VISIBLE);
}
}).exec(accountID);
}
private void onAccountLoaded(Account result) { private void onAccountLoaded(Account result) {
account=result; account=result;
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account); isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
@@ -716,14 +599,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private void bindHeaderView(){ private void bindHeaderView(){
setTitle(account.getDisplayName()); setTitle(account.displayName);
setSubtitle(getResources().getQuantityString(R.plurals.x_posts, (int)(account.statusesCount%1000), account.statusesCount)); setSubtitle(getResources().getQuantityString(R.plurals.x_posts, (int)(account.statusesCount%1000), account.statusesCount));
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest( ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(100), V.dp(100)));
TextUtils.isEmpty(account.avatar) ? getSession().getDefaultAvatarUrl() :
GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic,
V.dp(100), V.dp(100)));
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.header : account.headerStatic, 1000, 1000)); ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.header : account.headerStatic, 1000, 1000));
SpannableStringBuilder ssb=new SpannableStringBuilder(account.getDisplayName()); SpannableStringBuilder ssb=new SpannableStringBuilder(account.displayName);
if(AccountSessionManager.get(accountID).getLocalPreferences().customEmojiInNames) if(AccountSessionManager.get(accountID).getLocalPreferences().customEmojiInNames)
HtmlParser.parseCustomEmoji(ssb, account.emojis); HtmlParser.parseCustomEmoji(ssb, account.emojis);
name.setText(ssb); name.setText(ssb);
@@ -857,13 +737,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
getActivity(), s.getID(), account.url, false getActivity(), s.getID(), account.url, false
)); ));
} }
menu.findItem(R.id.share).setTitle(R.string.share_user);
if(isOwnProfile) { if(isOwnProfile) {
if (isInstancePixelfed()) menu.findItem(R.id.scheduled).setVisible(false); if (isInstancePixelfed()) menu.findItem(R.id.scheduled).setVisible(false);
return; return;
} }
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername()));
MenuItem mute = menu.findItem(R.id.mute); MenuItem mute = menu.findItem(R.id.mute);
mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername())); mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
mute.setIcon(relationship.muting ? R.drawable.ic_fluent_speaker_0_24_regular : R.drawable.ic_fluent_speaker_off_24_regular); mute.setIcon(relationship.muting ? R.drawable.ic_fluent_speaker_0_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
@@ -872,24 +751,19 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
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).setVisible(relationship.following); menu.findItem(R.id.manage_user_lists).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);
if (relationship.following) { if (relationship.following) {
MenuItem hideBoosts = menu.findItem(R.id.hide_boosts);
hideBoosts.setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getShortUsername())); hideBoosts.setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getShortUsername()));
hideBoosts.setIcon(relationship.showingReblogs ? R.drawable.ic_fluent_arrow_repeat_all_off_24_regular : R.drawable.ic_fluent_arrow_repeat_all_24_regular); hideBoosts.setIcon(relationship.showingReblogs ? R.drawable.ic_fluent_arrow_repeat_all_off_24_regular : R.drawable.ic_fluent_arrow_repeat_all_24_regular);
UiUtils.insetPopupMenuIcon(getContext(), hideBoosts); UiUtils.insetPopupMenuIcon(getContext(), hideBoosts);
hideBoosts.setVisible(true); menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername()));
} else { } else {
hideBoosts.setVisible(false); menu.findItem(R.id.hide_boosts).setVisible(false);
} }
MenuItem blockDomain=menu.findItem(R.id.block_domain); if(!account.isLocal())
if(!account.isLocal()){ menu.findItem(R.id.block_domain).setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
blockDomain.setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain())); else
blockDomain.setVisible(true); menu.findItem(R.id.block_domain).setVisible(false);
}else{
blockDomain.setVisible(false);
}
menu.findItem(R.id.edit_note).setTitle(noteWrap.getVisibility()==View.GONE && (relationship.note==null || relationship.note.isEmpty())
? R.string.sk_add_note : R.string.sk_delete_note);
} }
@Override @Override
@@ -901,11 +775,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
intent.putExtra(Intent.EXTRA_TEXT, account.url); intent.putExtra(Intent.EXTRA_TEXT, account.url);
startActivity(Intent.createChooser(intent, item.getTitle())); startActivity(Intent.createChooser(intent, item.getTitle()));
}else if(id==R.id.mute){ }else if(id==R.id.mute){
UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship); confirmToggleMuted();
}else if(id==R.id.block){ }else if(id==R.id.block){
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship); confirmToggleBlocked();
}else if(id==R.id.soft_block){ }else if(id==R.id.soft_block){
UiUtils.confirmSoftBlockUser(getActivity(), accountID, account, this::updateRelationship); confirmSoftBlockUser();
}else if(id==R.id.report){ }else if(id==R.id.report){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
@@ -950,16 +824,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
args.putString("profileDisplayUsername", account.getDisplayUsername()); args.putString("profileDisplayUsername", account.getDisplayUsername());
} }
Nav.go(getActivity(), ListsFragment.class, args); Nav.go(getActivity(), ListsFragment.class, args);
}else if(id==R.id.muted_accounts){
final Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("targetAccount", Parcels.wrap(account));
Nav.go(getActivity(), MutedAccountsListFragment.class, args);
}else if(id==R.id.blocked_accounts){
final Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("targetAccount", Parcels.wrap(account));
Nav.go(getActivity(), BlockedAccountsListFragment.class, args);
}else if(id==R.id.followed_hashtags){ }else if(id==R.id.followed_hashtags){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
@@ -971,26 +835,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
}else if(id==R.id.save){ }else if(id==R.id.save){
if(isInEditMode) if(isInEditMode)
saveAndExitEditMode(); saveAndExitEditMode();
}else if(id==R.id.edit_note){
if(noteWrap.getVisibility()==View.GONE){
showPrivateNote();
UiUtils.beginLayoutTransition(scrollableContent);
noteEdit.requestFocus();
noteEdit.postDelayed(()->{
InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class);
imm.showSoftInput(noteEdit, 0);
}, 100);
}else if(relationship.note.isEmpty()){
hidePrivateNote();
UiUtils.beginLayoutTransition(scrollableContent);
}else{
new M3AlertDialogBuilder(getActivity())
.setMessage(getContext().getString(R.string.sk_private_note_confirm_delete, account.getDisplayUsername()))
.setPositiveButton(R.string.delete, (dlg, btn)->savePrivateNote(null))
.setNegativeButton(R.string.cancel, null)
.show();
}
invalidateOptionsMenu();
} }
return true; return true;
} }
@@ -1016,22 +860,15 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private void updateRelationship(){ private void updateRelationship(){
if (getActivity() == null) return; if (getActivity() == null) return;
if(relationship.note!=null && !relationship.note.isEmpty()) showPrivateNote();
else hidePrivateNote();
invalidateOptionsMenu(); invalidateOptionsMenu();
actionButton.setVisibility(View.VISIBLE); actionButton.setVisibility(View.VISIBLE);
notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE); notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE);
UiUtils.setRelationshipToActionButtonM3(relationship, actionButton); UiUtils.setRelationshipToActionButtonM3(relationship, actionButton);
actionProgress.setIndeterminateTintList(actionButton.getTextColors()); actionProgress.setIndeterminateTintList(actionButton.getTextColors());
notifyProgress.setIndeterminateTintList(notifyButton.getTextColors()); notifyProgress.setIndeterminateTintList(notifyButton.getTextColors());
noteSaveProgress.setIndeterminateTintList(noteEdit.getTextColors());
followsYouView.setVisibility(relationship.followedBy ? View.VISIBLE : View.GONE); followsYouView.setVisibility(relationship.followedBy ? View.VISIBLE : View.GONE);
notifyButton.setSelected(relationship.notifying); notifyButton.setSelected(relationship.notifying);
notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username)); notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username));
noteEdit.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
V.setVisibilityAnimated(noteSaveProgress, View.GONE);
V.setVisibilityAnimated(noteSaveBtn, View.INVISIBLE);
UiUtils.beginLayoutTransition(scrollableContent);
} }
public ImageButton getFab() { public ImageButton getFab() {
@@ -1229,7 +1066,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
updateMetadataHeight(); updateMetadataHeight();
Toolbar toolbar=getToolbar(); Toolbar toolbar=getToolbar();
Drawable close=getToolbarContext().getDrawable(R.drawable.ic_fluent_dismiss_24_regular).mutate(); Drawable close=getToolbarContext().getDrawable(R.drawable.ic_baseline_close_24).mutate();
close.setTint(UiUtils.getThemeColor(getToolbarContext(), R.attr.colorM3OnSurfaceVariant)); close.setTint(UiUtils.getThemeColor(getToolbarContext(), R.attr.colorM3OnSurfaceVariant));
toolbar.setNavigationIcon(close); toolbar.setNavigationIcon(close);
toolbar.setNavigationContentDescription(R.string.discard); toolbar.setNavigationContentDescription(R.string.discard);
@@ -1244,7 +1081,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
name.setVisibility(View.GONE); name.setVisibility(View.GONE);
rolesView.setVisibility(View.GONE); rolesView.setVisibility(View.GONE);
usernameWrap.setVisibility(View.GONE); username.setVisibility(View.GONE);
bio.setVisibility(View.GONE); bio.setVisibility(View.GONE);
countersLayout.setVisibility(View.GONE); countersLayout.setVisibility(View.GONE);
@@ -1293,7 +1130,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
bioEditWrap.setVisibility(View.GONE); bioEditWrap.setVisibility(View.GONE);
name.setVisibility(View.VISIBLE); name.setVisibility(View.VISIBLE);
rolesView.setVisibility(View.VISIBLE); rolesView.setVisibility(View.VISIBLE);
usernameWrap.setVisibility(View.VISIBLE); username.setVisibility(View.VISIBLE);
bio.setVisibility(View.VISIBLE); bio.setVisibility(View.VISIBLE);
countersLayout.setVisibility(View.VISIBLE); countersLayout.setVisibility(View.VISIBLE);
refreshLayout.setEnabled(true); refreshLayout.setEnabled(true);
@@ -1305,7 +1142,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
imm.hideSoftInputFromWindow(content.getWindowToken(), 0); imm.hideSoftInputFromWindow(content.getWindowToken(), 0);
V.setVisibilityAnimated(fab, View.VISIBLE); V.setVisibilityAnimated(fab, View.VISIBLE);
bindHeaderView(); bindHeaderView();
V.setVisibilityAnimated(fab, View.VISIBLE);
} }
private void saveAndExitEditMode(){ private void saveAndExitEditMode(){
@@ -1335,6 +1171,18 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
.exec(accountID); .exec(accountID);
} }
private void confirmToggleMuted(){
UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship);
}
private void confirmToggleBlocked(){
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
}
private void confirmSoftBlockUser(){
UiUtils.confirmSoftBlockUser(getActivity(), accountID, account, this::updateRelationship);
}
private void updateRelationship(Relationship r){ private void updateRelationship(Relationship r){
relationship=r; relationship=r;
updateRelationship(); updateRelationship();
@@ -1381,7 +1229,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if(ava==null) if(ava==null)
return; return;
int radius=V.dp(25); int radius=V.dp(25);
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(TextUtils.isEmpty(account.avatar) ? getSession().getDefaultAvatarUrl() : account.avatar, ava), 0, currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.avatar, ava), 0,
new SingleImagePhotoViewerListener(avatar, avatarBorder, new int[]{radius, radius, radius, radius}, this, ()->currentPhotoViewer=null, ()->ava, null, null)); new SingleImagePhotoViewerListener(avatar, avatarBorder, new int[]{radius, radius, radius, radius}, this, ()->currentPhotoViewer=null, ()->ava, null, null));
} }
} }
@@ -1453,7 +1301,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@NonNull @NonNull
@Override @Override
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
FrameLayout view=new FrameLayout(parent.getContext()); FrameLayout view=tabViews[viewType];
if (view.getParent() != null) ((ViewGroup)view.getParent()).removeView(view);
view.setVisibility(View.VISIBLE);
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return new SimpleViewHolder(view); return new SimpleViewHolder(view);
} }
@@ -1461,13 +1311,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@Override @Override
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){ public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
Fragment fragment=getFragmentForPage(position); Fragment fragment=getFragmentForPage(position);
FrameLayout fragmentView=tabViews[position];
fragmentView.setVisibility(View.VISIBLE);
if(fragmentView.getParent() instanceof ViewGroup parent)
parent.removeView(fragmentView);
((FrameLayout)holder.itemView).addView(fragmentView);
if(!fragment.isAdded()){ if(!fragment.isAdded()){
getChildFragmentManager().beginTransaction().add(fragmentView.getId(), fragment).commit(); getChildFragmentManager().beginTransaction().add(holder.itemView.getId(), fragment).commit();
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){ holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@Override @Override
public boolean onPreDraw(){ public boolean onPreDraw(){
@@ -1601,7 +1446,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
title.setText(item.parsedName); title.setText(item.parsedName);
value.setText(item.parsedValue); value.setText(item.parsedValue);
if(item.verifiedAt!=null){ if(item.verifiedAt!=null){
int textColor=UiUtils.getThemeColor(getContext(), R.attr.colorM3Success); int textColor=UiUtils.isDarkTheme() ? 0xFF89bb9c : 0xFF5b8e63;
value.setTextColor(textColor); value.setTextColor(textColor);
value.setLinkTextColor(textColor); value.setLinkTextColor(textColor);
Drawable check=getResources().getDrawable(R.drawable.ic_fluent_checkmark_starburst_20_regular, getActivity().getTheme()).mutate(); Drawable check=getResources().getDrawable(R.drawable.ic_fluent_checkmark_starburst_20_regular, getActivity().getTheme()).mutate();

View File

@@ -2,11 +2,8 @@ package org.joinmastodon.android.fragments;
import android.app.Activity; import android.app.Activity;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.ImageButton; import android.widget.ImageButton;
import com.squareup.otto.Subscribe; import com.squareup.otto.Subscribe;
@@ -29,7 +26,6 @@ import java.util.List;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.V;
public class ScheduledStatusListFragment extends BaseStatusListFragment<ScheduledStatus> { public class ScheduledStatusListFragment extends BaseStatusListFragment<ScheduledStatus> {
private String nextMaxID; private String nextMaxID;
@@ -119,15 +115,6 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
} }
} }
@Override
protected void onShown(){
super.onShown();
// because, for some reason, when navigating back from compose fragment,
// match_parent would otherwise be incorrect (leaving a gap for the keyboard
// where there is none)
list.post(list::requestLayout);
}
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
currentRequest=new GetScheduledStatuses(offset==0 ? null : nextMaxID, count) currentRequest=new GetScheduledStatuses(offset==0 ? null : nextMaxID, count)
@@ -199,21 +186,6 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
return null; return null;
} }
@Override
public void onApplyWindowInsets(WindowInsets insets){
if(contentView!=null){
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0){
int insetBottom=insets.getSystemWindowInsetBottom();
((ViewGroup.MarginLayoutParams) list.getLayoutParams()).bottomMargin=insetBottom;
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(16)+insetBottom;
insets=insets.inset(0, 0, 0, insetBottom);
}else{
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(16);
}
}
super.onApplyWindowInsets(insets);
}
@Override @Override
public Uri getWebUri(Uri.Builder base) { public Uri getWebUri(Uri.Builder base) {
// TODO: adapt when frontends finally implement a scheduled posts list // TODO: adapt when frontends finally implement a scheduled posts list

View File

@@ -47,12 +47,13 @@ public class SplashFragment extends AppKitFragment{
private ProgressBarButton defaultServerButton; private ProgressBarButton defaultServerButton;
private ProgressBar defaultServerProgress; private ProgressBar defaultServerProgress;
private String chosenDefaultServer=DEFAULT_SERVER; private String chosenDefaultServer=DEFAULT_SERVER;
private boolean loadingDefaultServer, loadedDefaultServer; private boolean loadingDefaultServer;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
motionEffect=new InterpolatingMotionEffect(MastodonApp.context); motionEffect=new InterpolatingMotionEffect(MastodonApp.context);
loadAndChooseDefaultServer();
} }
@Nullable @Nullable
@@ -100,8 +101,6 @@ public class SplashFragment extends AppKitFragment{
}); });
} }
}); });
if(!loadedDefaultServer && !loadingDefaultServer)
loadAndChooseDefaultServer();
return contentView; return contentView;
} }
@@ -240,7 +239,6 @@ public class SplashFragment extends AppKitFragment{
private void setChosenDefaultServer(String domain){ private void setChosenDefaultServer(String domain){
chosenDefaultServer=domain; chosenDefaultServer=domain;
loadingDefaultServer=false; loadingDefaultServer=false;
loadedDefaultServer=true;
if(defaultServerButton!=null && getActivity()!=null){ if(defaultServerButton!=null && getActivity()!=null){
defaultServerButton.setTextVisible(true); defaultServerButton.setTextVisible(true);
defaultServerProgress.setVisibility(View.GONE); defaultServerProgress.setVisibility(View.GONE);

View File

@@ -9,25 +9,20 @@ 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.FilterContext; import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.DummyStatusDisplayItem;
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;
import org.joinmastodon.android.ui.text.HtmlParser;
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;
import java.time.ZoneId; import java.time.ZoneId;
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.EnumSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
import name.fraser.neil.plaintext.diff_match_patch;
public class StatusEditHistoryFragment extends StatusListFragment{ public class StatusEditHistoryFragment extends StatusListFragment{
private String id, url; private String id, url;
@@ -52,8 +47,8 @@ public class StatusEditHistoryFragment extends StatusListFragment{
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if(getActivity()==null) return;
Collections.sort(result, Comparator.comparing((Status s)->s.createdAt).reversed()); Collections.sort(result, Comparator.comparing((Status s)->s.createdAt).reversed());
if (getActivity() == null) return;
onDataLoaded(result, false); onDataLoaded(result, false);
} }
}) })
@@ -62,7 +57,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
@Override @Override
protected List<StatusDisplayItem> buildDisplayItems(Status s){ protected List<StatusDisplayItem> buildDisplayItems(Status s){
List<StatusDisplayItem> items=new ArrayList<>(); List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, null, StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_INSET | StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS);
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()));
@@ -87,11 +82,8 @@ public class StatusEditHistoryFragment extends StatusListFragment{
EnumSet<StatusEditChangeType> changes=EnumSet.noneOf(StatusEditChangeType.class); EnumSet<StatusEditChangeType> changes=EnumSet.noneOf(StatusEditChangeType.class);
Status prev=data.get(idx+1); Status prev=data.get(idx+1);
// if only formatting was changed, don't even try to create a diff text if(!Objects.equals(s.content, prev.content)){
if(!Objects.equals(HtmlParser.text(s.content), HtmlParser.text(prev.content))){
changes.add(StatusEditChangeType.TEXT_CHANGED); changes.add(StatusEditChangeType.TEXT_CHANGED);
//update status content to display a diffs
s.content=createDiffText(prev.content, s.content);
} }
if(!Objects.equals(s.spoilerText, prev.spoilerText)){ if(!Objects.equals(s.spoilerText, prev.spoilerText)){
if(s.spoilerText==null){ if(s.spoilerText==null){
@@ -151,13 +143,17 @@ public class StatusEditHistoryFragment extends StatusListFragment{
} }
} }
String sep = getString(R.string.sk_separator); String sep = getString(R.string.sk_separator);
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" "+sep+" "+date, Collections.emptyList(), 0, null, null, s)); items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" "+sep+" "+date, Collections.emptyList(), 0, null, null));
items.add(1, new DummyStatusDisplayItem(s.id, this));
} }
items.addAll(StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, null, StatusDisplayItem.FLAG_NO_FOOTER|StatusDisplayItem.FLAG_INSET|StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS));
return items; return items;
} }
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
list.addItemDecoration(new InsetStatusItemDecoration(this));
}
@Override @Override
public boolean isItemEnabled(String id){ public boolean isItemEnabled(String id){
return false; return false;
@@ -172,28 +168,4 @@ public class StatusEditHistoryFragment extends StatusListFragment{
public Uri getWebUri(Uri.Builder base) { public Uri getWebUri(Uri.Builder base) {
return Uri.parse(url); return Uri.parse(url);
} }
private String createDiffText(String original, String modified) {
diff_match_patch dmp=new diff_match_patch();
LinkedList<diff_match_patch.Diff> diffs=dmp.diff_main(original, modified);
dmp.diff_cleanupSemantic(diffs);
StringBuilder stringBuilder=new StringBuilder();
for(diff_match_patch.Diff diff : diffs){
switch(diff.operation){
case DELETE->{
stringBuilder.append("<edit-diff-delete>");
stringBuilder.append(diff.text);
stringBuilder.append("</edit-diff-delete>");
}
case INSERT->{
stringBuilder.append("<edit-diff-insert>");
stringBuilder.append(diff.text);
stringBuilder.append("</edit-diff-insert>");
}
default->stringBuilder.append(diff.text);
}
}
return stringBuilder.toString();
}
} }

View File

@@ -9,12 +9,10 @@ import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E; import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.CacheController;
import org.joinmastodon.android.api.session.AccountLocalPreferences; import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent; import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
import org.joinmastodon.android.events.PollUpdatedEvent; import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.events.ReblogDeletedEvent;
import org.joinmastodon.android.events.RemoveAccountPostsEvent; import org.joinmastodon.android.events.RemoveAccountPostsEvent;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent; import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusCreatedEvent; import org.joinmastodon.android.events.StatusCreatedEvent;
@@ -25,18 +23,12 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
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.parceler.Parcels; import org.parceler.Parcels;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -54,8 +46,6 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
flags |= StatusDisplayItem.FLAG_NO_FOOTER; flags |= StatusDisplayItem.FLAG_NO_FOOTER;
if (!lp.emojiReactionsEnabled || lp.showEmojiReactions==ONLY_OPENED) if (!lp.emojiReactionsEnabled || lp.showEmojiReactions==ONLY_OPENED)
flags |= StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS; flags |= StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS;
if(GlobalUserPreferences.translateButtonOpenedOnly)
flags |= StatusDisplayItem.FLAG_NO_TRANSLATE;
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, getFilterContext(), isMainThreadStatus ? 0 : flags); return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, getFilterContext(), isMainThreadStatus ? 0 : flags);
} }
@@ -84,7 +74,8 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
@Override @Override
public void onItemClick(String id){ public void onItemClick(String id){
Status status=getContentStatusByID(id); Status status=getContentStatusByID(id);
if(status==null || status.preview) return; if(status==null)
return;
status.filterRevealed=true; status.filterRevealed=true;
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
@@ -149,12 +140,12 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
} }
} }
public Status getContentStatusByID(String id){ protected Status getContentStatusByID(String id){
Status s=getStatusByID(id); Status s=getStatusByID(id);
return s==null ? null : s.getContentStatus(); return s==null ? null : s.getContentStatus();
} }
public Status getStatusByID(String id){ protected Status getStatusByID(String id){
for(Status s:data){ for(Status s:data){
if(s.id.equals(id)){ if(s.id.equals(id)){
return s; return s;
@@ -181,73 +172,41 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
} }
} }
private boolean removeStatusDisplayItems(String parentID, int firstIndex, int ancestorFirstIndex, int ancestorLastIndex){
// did we find an ancestor that is also the status' neighbor?
if(ancestorFirstIndex>=0 && ancestorLastIndex==firstIndex-1){
// update ancestor to have no descendant anymore
displayItems.subList(ancestorFirstIndex, ancestorLastIndex+1).forEach(i->i.hasDescendantNeighbor=false);
adapter.notifyItemRangeChanged(ancestorFirstIndex, ancestorLastIndex-ancestorFirstIndex+1);
}
if(firstIndex==-1) return false;
int lastIndex=firstIndex;
while(lastIndex<displayItems.size()){
StatusDisplayItem item=displayItems.get(lastIndex);
if(!item.parentID.equals(parentID) || item instanceof GapStatusDisplayItem) break;
lastIndex++;
}
int count=lastIndex-firstIndex;
if(count<1) return false;
displayItems.subList(firstIndex, lastIndex).clear();
adapter.notifyItemRangeRemoved(firstIndex, count);
return true;
}
protected void removeStatus(Status status){ protected void removeStatus(Status status){
final AccountSessionManager asm=AccountSessionManager.getInstance(); data.remove(status);
final CacheController cache=AccountSessionManager.get(accountID).getCacheController(); preloadedData.remove(status);
final boolean unReblogging=status.reblog!=null && asm.isSelf(accountID, status.account); int index=-1, ancestorFirstIndex = -1, ancestorLastIndex = -1;
final Predicate<Status> isToBeRemovedReblog=item->item!=null && item.reblog!=null
&& item.reblog.id.equals(status.reblog.id)
&& asm.isSelf(accountID, item.account);
final BiPredicate<String, Supplier<String>> isToBeRemovedContent=(parentId, contentIdSupplier)->
parentId.equals(status.id) || contentIdSupplier.get().equals(status.id);
int ancestorFirstIndex=-1, ancestorLastIndex=-1;
for(int i=0;i<displayItems.size();i++){ for(int i=0;i<displayItems.size();i++){
StatusDisplayItem item = displayItems.get(i); StatusDisplayItem item = displayItems.get(i);
// we found a status that the to-be-removed status replies to! if(status.id.equals(item.parentID)){
// storing indices to maybe update its display items index=i;
break;
}
if (item.parentID.equals(status.inReplyToId)) { if (item.parentID.equals(status.inReplyToId)) {
if (ancestorFirstIndex == -1) ancestorFirstIndex = i; if (ancestorFirstIndex == -1) ancestorFirstIndex = i;
ancestorLastIndex = i; ancestorLastIndex = i;
} }
// if we're un-reblogging, we compare the reblogged status's id with the current status's
if(unReblogging
? isToBeRemovedReblog.test(getStatusByID(item.parentID))
: isToBeRemovedContent.test(item.parentID, item::getContentStatusID)){
// if statuses are removed from index i, the next iteration should be on the same index again
if(removeStatusDisplayItems(item.parentID, i, ancestorFirstIndex, ancestorLastIndex)) i--;
// resetting in case we find another occurrence of the same status that also has ancestors
// (we won't - unless the timeline is being especially weird)
ancestorFirstIndex=-1; ancestorLastIndex=-1;
}
} }
Consumer<List<Status>> removeStatusFromData=(list)->{ // did we find an ancestor that is also the status' neighbor?
Iterator<Status> it=list.iterator(); if (ancestorFirstIndex >= 0 && ancestorLastIndex == index - 1) {
while(it.hasNext()){ for (int i = ancestorFirstIndex; i <= ancestorLastIndex; i++) {
Status s=it.next(); StatusDisplayItem item = displayItems.get(i);
if(unReblogging // update ancestor to have no descendant anymore
? isToBeRemovedReblog.test(s) if (item.parentID.equals(status.inReplyToId)) item.hasDescendantNeighbor = false;
: isToBeRemovedContent.test(s.id, s::getContentStatusID)){
it.remove();
cache.deleteStatus(s.id);
} }
adapter.notifyItemRangeChanged(ancestorFirstIndex, ancestorLastIndex - ancestorFirstIndex + 1);
} }
};
removeStatusFromData.accept(data); if(index==-1)
removeStatusFromData.accept(preloadedData); return;
int lastIndex;
for(lastIndex=index;lastIndex<displayItems.size();lastIndex++){
if(!displayItems.get(lastIndex).parentID.equals(status.id))
break;
}
displayItems.subList(index, lastIndex).clear();
adapter.notifyItemRangeRemoved(index, lastIndex-index);
} }
@Override @Override
@@ -316,22 +275,6 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
removeStatus(status); removeStatus(status);
} }
@Subscribe
public void onReblogDeleted(ReblogDeletedEvent ev){
AccountSessionManager asm=AccountSessionManager.getInstance();
if(!ev.accountID.equals(accountID))
return;
for(Status item : data){
boolean itemIsOwnReblog=item.reblog!=null
&& item.getContentStatusID().equals(ev.statusID)
&& asm.isSelf(accountID, item.account);
if(itemIsOwnReblog){
removeStatus(item);
break;
}
}
}
@Subscribe @Subscribe
public void onStatusCreated(StatusCreatedEvent ev){ public void onStatusCreated(StatusCreatedEvent ev){
if(!ev.accountID.equals(accountID)) if(!ev.accountID.equals(accountID))
@@ -352,7 +295,6 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
Status contentStatus=status.getContentStatus(); Status contentStatus=status.getContentStatus();
if(contentStatus.poll!=null && contentStatus.poll.id.equals(ev.poll.id)){ if(contentStatus.poll!=null && contentStatus.poll.id.equals(ev.poll.id)){
updatePoll(status.id, contentStatus, ev.poll); updatePoll(status.id, contentStatus, ev.poll);
AccountSessionManager.get(accountID).getCacheController().updateStatus(contentStatus);
} }
} }
} }

View File

@@ -13,8 +13,8 @@ import org.joinmastodon.android.GlobalUserPreferences.AutoRevealMode;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.GetStatusByID; import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
import org.joinmastodon.android.api.requests.statuses.GetStatusContext; import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent; import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
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.FilterContext; import org.joinmastodon.android.model.FilterContext;
@@ -24,13 +24,13 @@ import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.SpoilerStatusDisplayItem;
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.displayitems.WarningFilteredStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
import org.joinmastodon.android.ui.text.HtmlParser; import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.ProvidesAssistContent; import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.util.ArrayDeque; import java.util.ArrayDeque;
@@ -50,25 +50,21 @@ import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class ThreadFragment extends StatusListFragment implements ProvidesAssistContent { public class ThreadFragment extends StatusListFragment implements ProvidesAssistContent {
protected Status mainStatus, updatedStatus, replyTo; protected Status mainStatus, updatedStatus;
private final HashMap<String, NeighborAncestryInfo> ancestryMap = new HashMap<>(); private final HashMap<String, NeighborAncestryInfo> ancestryMap = new HashMap<>();
private StatusContext result; private StatusContext result;
protected boolean contextInitiallyRendered, transitionFinished, preview; protected boolean contextInitiallyRendered, transitionFinished;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
mainStatus=Parcels.unwrap(getArguments().getParcelable("status")); mainStatus=Parcels.unwrap(getArguments().getParcelable("status"));
replyTo=Parcels.unwrap(getArguments().getParcelable("inReplyTo"));
Account inReplyToAccount=Parcels.unwrap(getArguments().getParcelable("inReplyToAccount")); Account inReplyToAccount=Parcels.unwrap(getArguments().getParcelable("inReplyToAccount"));
refreshing=contextInitiallyRendered=getArguments().getBoolean("refresh", false);
if(inReplyToAccount!=null) if(inReplyToAccount!=null)
knownAccounts.put(inReplyToAccount.id, inReplyToAccount); knownAccounts.put(inReplyToAccount.id, inReplyToAccount);
data.add(mainStatus); data.add(mainStatus);
onAppendItems(Collections.singletonList(mainStatus)); onAppendItems(Collections.singletonList(mainStatus));
preview=mainStatus.preview; setTitle(HtmlParser.parseCustomEmoji(getString(R.string.post_from_user, mainStatus.account.displayName), mainStatus.account.emojis));
if(preview) setRefreshEnabled(false);
setTitle(preview ? getString(R.string.sk_post_preview) : HtmlParser.parseCustomEmoji(getString(R.string.post_from_user, mainStatus.account.getDisplayName()), mainStatus.account.emojis));
transitionFinished = getArguments().getBoolean("noTransition", false); transitionFinished = getArguments().getBoolean("noTransition", false);
} }
@@ -109,12 +105,6 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
text.textSelectable=true; text.textSelectable=true;
else if(item instanceof FooterStatusDisplayItem footer) else if(item instanceof FooterStatusDisplayItem footer)
footer.hideCounts=true; footer.hideCounts=true;
else if(item instanceof SpoilerStatusDisplayItem spoiler){
for(StatusDisplayItem subItem:spoiler.contentItems){
if(subItem instanceof TextStatusDisplayItem text)
text.textSelectable=true;
}
}
} }
} }
@@ -133,21 +123,11 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
if(preview && replyTo==null){ if (refreshing) loadMainStatus();
result=new StatusContext(); currentRequest=new GetStatusContext(mainStatus.id)
result.descendants=Collections.emptyList();
result.ancestors=Collections.emptyList();
return;
}
if(refreshing && !preview) loadMainStatus();
currentRequest=new GetStatusContext(preview ? replyTo.id : mainStatus.id)
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(StatusContext result){ public void onSuccess(StatusContext result){
if(preview){
result.descendants=Collections.emptyList();
result.ancestors.add(replyTo);
}
ThreadFragment.this.result = result; ThreadFragment.this.result = result;
maybeApplyContext(); maybeApplyContext();
} }
@@ -208,8 +188,8 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
// TODO: figure out how this code works // TODO: figure out how this code works
if (isInstanceAkkoma()) sortStatusContext(mainStatus, result); if (isInstanceAkkoma()) sortStatusContext(mainStatus, result);
filterStatuses(result.descendants); result.descendants=filterStatuses(result.descendants);
filterStatuses(result.ancestors); result.ancestors=filterStatuses(result.ancestors);
restoreStatusStates(result.descendants, oldData); restoreStatusStates(result.descendants, oldData);
restoreStatusStates(result.ancestors, oldData); restoreStatusStates(result.ancestors, oldData);
@@ -345,8 +325,11 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private void filterStatuses(List<Status> statuses){ private List<Status> filterStatuses(List<Status> statuses){
AccountSessionManager.get(accountID).filterStatuses(statuses, getFilterContext()); StatusFilterPredicate statusFilterPredicate=new StatusFilterPredicate(accountID,getFilterContext());
return statuses.stream()
.filter(statusFilterPredicate)
.collect(Collectors.toList());
} }
@Override @Override

View File

@@ -1,36 +0,0 @@
package org.joinmastodon.android.fragments.account_list;
import android.net.Uri;
import android.os.Bundle;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
import org.joinmastodon.android.api.requests.accounts.GetAccountBlocks;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
public class BlockedAccountsListFragment extends AccountRelatedAccountListFragment{
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setTitle(R.string.sk_blocked_accounts);
}
@Override
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
return new GetAccountBlocks(maxID, count);
}
@Override
protected void onConfigureViewHolder(AccountViewHolder holder){
super.onConfigureViewHolder(holder);
holder.setStyle(AccountViewHolder.AccessoryType.NONE, false);
}
@Override
public Uri getWebUri(Uri.Builder base) {
return super.getWebUri(base).buildUpon()
.appendPath("/blocks").build();
}
}

View File

@@ -48,7 +48,7 @@ public class ComposeAccountSearchFragment extends BaseAccountListFragment{
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
refreshing=true; refreshing=true;
currentRequest=new GetSearchResults(currentQuery, GetSearchResults.Type.ACCOUNTS, false, null, 0, 0) currentRequest=new GetSearchResults(currentQuery, GetSearchResults.Type.ACCOUNTS, false)
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(SearchResults result){ public void onSuccess(SearchResults result){

View File

@@ -1,36 +0,0 @@
package org.joinmastodon.android.fragments.account_list;
import android.net.Uri;
import android.os.Bundle;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
import org.joinmastodon.android.api.requests.accounts.GetAccountMutes;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
public class MutedAccountsListFragment extends AccountRelatedAccountListFragment{
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setTitle(R.string.sk_muted_accounts);
}
@Override
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
return new GetAccountMutes(maxID, count);
}
@Override
protected void onConfigureViewHolder(AccountViewHolder holder){
super.onConfigureViewHolder(holder);
holder.setStyle(AccountViewHolder.AccessoryType.NONE, false);
}
@Override
public Uri getWebUri(Uri.Builder base) {
return super.getWebUri(base).buildUpon()
.appendPath("/mutes").build();
}
}

View File

@@ -6,19 +6,21 @@ import android.os.Bundle;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.api.requests.timelines.GetBubbleTimeline; import org.joinmastodon.android.api.requests.timelines.GetBubbleTimeline;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.StatusListFragment; import org.joinmastodon.android.fragments.StatusListFragment;
import org.joinmastodon.android.model.FilterContext; import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper; import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.MergeRecyclerAdapter; import me.grishka.appkit.utils.MergeRecyclerAdapter;
public class BubbleTimelineFragment extends StatusListFragment { public class BubbleTimelineFragment extends StatusListFragment {
private DiscoverInfoBannerHelper bannerHelper; private DiscoverInfoBannerHelper bannerHelper;
private String maxID;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
@@ -34,15 +36,15 @@ public class BubbleTimelineFragment extends StatusListFragment {
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
currentRequest=new GetBubbleTimeline(getMaxID(), count, getLocalPrefs().timelineReplyVisibility) currentRequest=new GetBubbleTimeline(refreshing ? null : maxID, count, getLocalPrefs().timelineReplyVisibility)
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if(!result.isEmpty())
maxID=result.get(result.size()-1).id;
if (getActivity() == null) return; if (getActivity() == null) return;
boolean more=applyMaxID(result); result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext()); onDataLoaded(result, !result.isEmpty());
onDataLoaded(result, more);
bannerHelper.onBannerBecameVisible();
} }
}) })
.exec(accountID); .exec(accountID);

View File

@@ -4,7 +4,6 @@ import android.graphics.Rect;
import android.graphics.drawable.Animatable; import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
@@ -17,7 +16,6 @@ import android.widget.TextView;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships; 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.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.IsOnTop; import org.joinmastodon.android.fragments.IsOnTop;
import org.joinmastodon.android.fragments.MastodonRecyclerFragment; import org.joinmastodon.android.fragments.MastodonRecyclerFragment;
import org.joinmastodon.android.fragments.ProfileFragment; import org.joinmastodon.android.fragments.ProfileFragment;
@@ -66,8 +64,6 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
accountID=getArguments().getString("account"); accountID=getArguments().getString("account");
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
setRetainInstance(true);
} }
@Override @Override
@@ -261,7 +257,7 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE); followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE); followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
relationship=relationships.get(item.account.id); relationship=relationships.get(item.account.id);
UiUtils.setExtraTextInfo(getContext(), null, true, false, false, item.account); UiUtils.setExtraTextInfo(getContext(), null, findViewById(R.id.pronouns), true, false, false, item.account);
if(relationship==null){ if(relationship==null){
actionWrap.setVisibility(View.GONE); actionWrap.setVisibility(View.GONE);
@@ -323,16 +319,15 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
public AccountWrapper(Account account){ public AccountWrapper(Account account){
this.account=account; this.account=account;
avaRequest=new UrlImageLoaderRequest( if(!TextUtils.isEmpty(account.avatar))
TextUtils.isEmpty(account.avatar) ? AccountSessionManager.getInstance().getAccount(accountID).getDefaultAvatarUrl() : account.avatar, avaRequest=new UrlImageLoaderRequest(account.avatar, V.dp(50), V.dp(50));
V.dp(50), V.dp(50));
if(!TextUtils.isEmpty(account.header)) if(!TextUtils.isEmpty(account.header))
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000); coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID); parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
if(account.emojis.isEmpty()){ if(account.emojis.isEmpty()){
parsedName= account.getDisplayName(); parsedName=account.displayName;
}else{ }else{
parsedName=HtmlParser.parseCustomEmoji(account.getDisplayName(), account.emojis); parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio)); emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio));
} }
} }

View File

@@ -13,10 +13,8 @@ import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.IsOnTop; import org.joinmastodon.android.fragments.IsOnTop;
import org.joinmastodon.android.fragments.ScrollableToTop; import org.joinmastodon.android.fragments.ScrollableToTop;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.SearchResult; import org.joinmastodon.android.model.SearchResult;
import org.joinmastodon.android.ui.OutlineProviders; import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.SimpleViewHolder; import org.joinmastodon.android.ui.SimpleViewHolder;
@@ -98,6 +96,8 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){ pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
@Override @Override
public void onPageSelected(int position){ public void onPageSelected(int position){
if(position==0)
return;
Fragment _page=getFragmentForPage(position); Fragment _page=getFragmentForPage(position);
if(_page instanceof BaseRecyclerFragment<?> page){ if(_page instanceof BaseRecyclerFragment<?> page){
if(!page.loaded && !page.isDataLoading()) if(!page.loaded && !page.isDataLoading())
@@ -157,7 +157,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
} }
}); });
disableDiscover=AccountSessionManager.get(accountID).getInstance().map(Instance::isAkkoma).orElse(false); disableDiscover=getArguments().getBoolean("disableDiscover");
searchView=view.findViewById(R.id.search_fragment); searchView=view.findViewById(R.id.search_fragment);
if(searchFragment==null){ if(searchFragment==null){
searchFragment=new SearchFragment(); searchFragment=new SearchFragment();
@@ -289,19 +289,15 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
@NonNull @NonNull
@Override @Override
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
FrameLayout view=new FrameLayout(parent.getContext()); FrameLayout view=tabViews[viewType];
((ViewGroup)view.getParent()).removeView(view);
view.setVisibility(View.VISIBLE);
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return new SimpleViewHolder(view); return new SimpleViewHolder(view);
} }
@Override @Override
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){ public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){}
FrameLayout view=tabViews[position];
if(view.getParent() instanceof ViewGroup parent)
parent.removeView(view);
view.setVisibility(View.VISIBLE);
((FrameLayout)holder.itemView).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
@Override @Override
public int getItemCount(){ public int getItemCount(){

View File

@@ -2,8 +2,8 @@ package org.joinmastodon.android.fragments.discover;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
@@ -14,12 +14,14 @@ import org.joinmastodon.android.api.requests.trends.GetTrendingLinks;
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.model.viewmodel.CardViewModel; import org.joinmastodon.android.model.viewmodel.CardViewModel;
import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.OutlineProviders; import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable; import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper; import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -30,9 +32,11 @@ import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.fragments.BaseRecyclerFragment; 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.ListImageLoaderAdapter;
import me.grishka.appkit.imageloader.ListImageLoaderWrapper; import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
import me.grishka.appkit.imageloader.RecyclerViewDelegate; import me.grishka.appkit.imageloader.RecyclerViewDelegate;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
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.SingleViewRecyclerAdapter; import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
@@ -56,8 +60,6 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> im
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
accountID=getArguments().getString("account"); accountID=getArguments().getString("account");
bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS, accountID); bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS, accountID);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
setRetainInstance(true);
} }
@Override @Override

View File

@@ -4,7 +4,6 @@ import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses; import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.StatusListFragment; import org.joinmastodon.android.fragments.StatusListFragment;
import org.joinmastodon.android.model.FilterContext; import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
@@ -18,7 +17,6 @@ import me.grishka.appkit.utils.MergeRecyclerAdapter;
public class DiscoverPostsFragment extends StatusListFragment{ public class DiscoverPostsFragment extends StatusListFragment{
private DiscoverInfoBannerHelper bannerHelper; private DiscoverInfoBannerHelper bannerHelper;
private int offset;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
@@ -27,17 +25,12 @@ public class DiscoverPostsFragment extends StatusListFragment{
} }
@Override @Override
protected void doLoadData(int o, int count){ protected void doLoadData(int offset, int count){
if(refreshing) offset=0;
currentRequest=new GetTrendingStatuses(offset, count) currentRequest=new GetTrendingStatuses(offset, count)
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if(getActivity()==null) return; onDataLoaded(result, !result.isEmpty());
boolean empty=result.isEmpty();
offset+=result.size();
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
onDataLoaded(result, !empty);
bannerHelper.onBannerBecameVisible(); bannerHelper.onBannerBecameVisible();
} }
}).exec(accountID); }).exec(accountID);

View File

@@ -29,14 +29,15 @@ public class FederatedTimelineFragment extends StatusListFragment{
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
currentRequest=new GetPublicTimeline(false, false, getMaxID(), count, getLocalPrefs().timelineReplyVisibility) currentRequest=new GetPublicTimeline(false, false, refreshing ? null : maxID, count, getLocalPrefs().timelineReplyVisibility)
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if(getActivity()==null) return; if(!result.isEmpty())
boolean more=applyMaxID(result); maxID=result.get(result.size()-1).id;
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext()); boolean empty=result.isEmpty();
onDataLoaded(result, more); AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.PUBLIC);
onDataLoaded(result, !empty);
bannerHelper.onBannerBecameVisible(); bannerHelper.onBannerBecameVisible();
} }
}) })

View File

@@ -2,6 +2,7 @@ package org.joinmastodon.android.fragments.discover;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline; import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
@@ -29,14 +30,15 @@ public class LocalTimelineFragment extends StatusListFragment{
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
currentRequest=new GetPublicTimeline(true, false, getMaxID(), count, getLocalPrefs().timelineReplyVisibility) currentRequest=new GetPublicTimeline(true, false, refreshing ? null : maxID, count, getLocalPrefs().timelineReplyVisibility)
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if(getActivity()==null) return; if(!result.isEmpty())
boolean more=applyMaxID(result); maxID=result.get(result.size()-1).id;
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext()); boolean empty=result.isEmpty();
onDataLoaded(result, more); AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.PUBLIC);
onDataLoaded(result, !empty);
bannerHelper.onBannerBecameVisible(); bannerHelper.onBannerBecameVisible();
} }
}) })

View File

@@ -23,6 +23,7 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.tabs.TabLayout;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels; import org.parceler.Parcels;
@@ -30,13 +31,12 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
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.api.SimpleCallback;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class SearchFragment extends BaseStatusListFragment<SearchResult>{ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
@@ -97,7 +97,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
args.putParcelable("profileAccount", Parcels.wrap(res.account)); args.putParcelable("profileAccount", Parcels.wrap(res.account));
Nav.go(getActivity(), ProfileFragment.class, args); Nav.go(getActivity(), ProfileFragment.class, args);
} }
case HASHTAG -> UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag); case HASHTAG -> UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name, res.hashtag.following);
case STATUS -> { case STATUS -> {
Status status=res.status.getContentStatus(); Status status=res.status.getContentStatus();
Bundle args=new Bundle(); Bundle args=new Bundle();
@@ -113,7 +113,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
} }
@Override @Override
protected void doLoadData(int _offset, int count){ protected void doLoadData(int offset, int count){
GetSearchResults.Type type; GetSearchResults.Type type;
if(currentFilter.size()==1){ if(currentFilter.size()==1){
type=switch(currentFilter.iterator().next()){ type=switch(currentFilter.iterator().next()){
@@ -128,22 +128,8 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
dataLoaded(); dataLoaded();
return; return;
} }
String maxID=null; currentRequest=new GetSearchResults(currentQuery, type, true)
// TODO server-side bug .setCallback(new Callback<>(){
/*int offset=0;
if(_offset>0){
if(type==GetSearchResults.Type.STATUSES){
if(!preloadedData.isEmpty())
maxID=preloadedData.get(preloadedData.size()-1).status.id;
else if(!data.isEmpty())
maxID=data.get(data.size()-1).status.id;
}else{
offset=_offset;
}
}*/
int offset=_offset;
currentRequest=new GetSearchResults(currentQuery, type, type==null, maxID, offset, type==null ? 0 : count)
.setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(SearchResults result){ public void onSuccess(SearchResults result){
ArrayList<SearchResult> results=new ArrayList<>(); ArrayList<SearchResult> results=new ArrayList<>();
@@ -156,18 +142,23 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
results.add(new SearchResult(tag)); results.add(new SearchResult(tag));
} }
if(result.statuses!=null){ if(result.statuses!=null){
Set<String> alreadyLoadedStatuses=data.stream().filter(r->r.type==SearchResult.Type.STATUS).map(r->r.status.id).collect(Collectors.toSet()); for(Status status:result.statuses)
for(Status status:result.statuses){
if(!alreadyLoadedStatuses.contains(status.id))
results.add(new SearchResult(status)); results.add(new SearchResult(status));
} }
}
prevDisplayItems=new ArrayList<>(displayItems); prevDisplayItems=new ArrayList<>(displayItems);
unfilteredResults=results; unfilteredResults=results;
onDataLoaded(filterSearchResults(results), type!=null && !results.isEmpty()); onDataLoaded(filterSearchResults(results), false);
}
@Override
public void onError(ErrorResponse error){
currentRequest=null;
Activity a=getActivity();
if(a==null)
return;
error.showToast(a);
} }
}) })
.setTimeout(180000) // 3 minutes (searches can take a long time)
.exec(accountID); .exec(accountID);
} }

View File

@@ -10,6 +10,7 @@ import android.graphics.Outline;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
@@ -25,7 +26,7 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import android.widget.Toolbar; import android.widget.Toolbar;
import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.search.GetSearchResults; import org.joinmastodon.android.api.requests.search.GetSearchResults;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
@@ -37,16 +38,19 @@ import org.joinmastodon.android.model.SearchResults;
import org.joinmastodon.android.model.viewmodel.ListItem; import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.model.viewmodel.SearchResultViewModel; import org.joinmastodon.android.model.viewmodel.SearchResultViewModel;
import org.joinmastodon.android.ui.DividerItemDecoration; import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.SearchViewHelper; import org.joinmastodon.android.ui.SearchViewHelper;
import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter; import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter;
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter; import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.viewholders.AccountViewHolder; import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
import org.joinmastodon.android.ui.viewholders.SimpleListItemViewHolder; import org.joinmastodon.android.ui.viewholders.SimpleListItemViewHolder;
import org.parceler.Parcels;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -115,14 +119,14 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
onDataLoaded(results.stream().map(sr->{ onDataLoaded(results.stream().map(sr->{
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, true); SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, true);
if(sr.type==SearchResult.Type.HASHTAG){ if(sr.type==SearchResult.Type.HASHTAG){
vm.hashtagItem.setOnClick(i->openHashtag(sr)); vm.hashtagItem.onClick=()->openHashtag(sr);
} }
return vm; return vm;
}).collect(Collectors.toList()), false); }).collect(Collectors.toList()), false);
recentsHeader.setVisible(!data.isEmpty()); recentsHeader.setVisible(!data.isEmpty());
}); });
}else{ }else{
currentRequest=new GetSearchResults(currentQuery, null, false, null, 0, 0) currentRequest=new GetSearchResults(currentQuery, null, false)
.limit(2) .limit(2)
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
@@ -132,7 +136,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
.map(sr->{ .map(sr->{
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, false); SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, false);
if(sr.type==SearchResult.Type.HASHTAG){ if(sr.type==SearchResult.Type.HASHTAG){
vm.hashtagItem.setOnClick(i->openHashtag(sr)); vm.hashtagItem.onClick=()->openHashtag(sr);
} }
return vm; return vm;
}) })
@@ -377,72 +381,30 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
} }
private void openHashtag(SearchResult res){ private void openHashtag(SearchResult res){
wrapSuicideDialog(()->{ UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name, res.hashtag.following);
UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag);
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putRecentSearch(res); AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putRecentSearch(res);
});
} }
private boolean isInRecentMode(){ private boolean isInRecentMode(){
return TextUtils.isEmpty(currentQuery); return TextUtils.isEmpty(currentQuery);
} }
private void wrapSuicideDialog(Runnable r){
if(!GlobalUserPreferences.showSuicideHelp || currentQuery==null){
r.run();
return;
}
String[] terms=getContext().getString(R.string.sk_suicide_search_terms).toLowerCase().split(",");
String query=currentQuery.trim().toLowerCase();
boolean termMatches=false;
for(String term : terms){
if(query.contains(term)){
termMatches=true;
break;
}
}
if(!termMatches){
r.run();
return;
}
String url=getContext().getString(R.string.sk_suicide_helplines_url);
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_search_suicide_title)
.setMessage(R.string.sk_search_suicide_message)
.setNegativeButton(R.string.sk_do_not_show_again, (dialog, which)->{
GlobalUserPreferences.showSuicideHelp = false;
GlobalUserPreferences.save();
r.run();
})
.setNeutralButton(R.string.sk_search_suicide_hotlines, (dialog, which)->UiUtils.launchWebBrowser(getContext(), url))
.setPositiveButton(R.string.ok, (dialog, which)->r.run())
.setOnDismissListener((dialog)->{})
.show();
}
private void onSearchViewEnter(){ private void onSearchViewEnter(){
if(TextUtils.isEmpty(currentQuery) || currentQuery.trim().isEmpty()) deliverResult(currentQuery, null);
return;
wrapSuicideDialog(()->deliverResult(currentQuery, null));
} }
private void onOpenURLClick(ListItem<?> item_){ private void onOpenURLClick(){
UiUtils.openURL(getContext(), accountID, searchViewHelper.getQuery(), false); UiUtils.openURL(getContext(), accountID, searchViewHelper.getQuery(), false);
} }
private void onGoToHashtagClick(ListItem<?> item_){ private void onGoToHashtagClick(){
wrapSuicideDialog(()->{
String q=searchViewHelper.getQuery(); String q=searchViewHelper.getQuery();
if(q.startsWith("#")) if(q.startsWith("#"))
q=q.substring(1); q=q.substring(1);
UiUtils.openHashtagTimeline(getActivity(), accountID, q); UiUtils.openHashtagTimeline(getActivity(), accountID, q, null);
});
} }
private void onGoToAccountClick(ListItem<?> item_){ private void onGoToAccountClick(){
String q=searchViewHelper.getQuery(); String q=searchViewHelper.getQuery();
if(!q.startsWith("@")){ if(!q.startsWith("@")){
q="@"+q; q="@"+q;
@@ -459,12 +421,12 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
}).ifPresent(progress -> progress.wrapProgress((Activity) getContext(), R.string.loading, true)); }).ifPresent(progress -> progress.wrapProgress((Activity) getContext(), R.string.loading, true));
} }
private void onGoToStatusSearchClick(ListItem<?> item_){ private void onGoToStatusSearchClick(){
wrapSuicideDialog(()->deliverResult(searchViewHelper.getQuery(), SearchResult.Type.STATUS)); deliverResult(searchViewHelper.getQuery(), SearchResult.Type.STATUS);
} }
private void onGoToAccountSearchClick(ListItem<?> item_){ private void onGoToAccountSearchClick(){
wrapSuicideDialog(()->deliverResult(searchViewHelper.getQuery(), SearchResult.Type.ACCOUNT)); deliverResult(searchViewHelper.getQuery(), SearchResult.Type.ACCOUNT);
} }
private void onClearRecentClick(){ private void onClearRecentClick(){

View File

@@ -1,6 +1,5 @@
package org.joinmastodon.android.fragments.discover; package org.joinmastodon.android.fragments.discover;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -10,6 +9,8 @@ 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.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.utils.DiscoverInfoBannerHelper;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.HashtagChartView; import org.joinmastodon.android.ui.views.HashtagChartView;
@@ -33,8 +34,6 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
accountID=getArguments().getString("account"); accountID=getArguments().getString("account");
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
setRetainInstance(true);
} }
@Override @Override
@@ -106,7 +105,7 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
@Override @Override
public void onClick(){ public void onClick(){
UiUtils.openHashtagTimeline(getActivity(), accountID, item); UiUtils.openHashtagTimeline(getActivity(), accountID, item.name, item.following);
} }
} }
} }

View File

@@ -165,7 +165,9 @@ public class AccountActivationFragment extends ToolbarFragment{
private void tryGetAccount(){ private void tryGetAccount(){
if(AccountSessionManager.getInstance().tryGetAccount(accountID)==null){ if(AccountSessionManager.getInstance().tryGetAccount(accountID)==null){
uiHandler.removeCallbacks(pollRunnable); uiHandler.removeCallbacks(pollRunnable);
((MainActivity)getActivity()).restartHomeFragment(); getActivity().finish();
Intent intent=new Intent(getActivity(), MainActivity.class);
startActivity(intent);
return; return;
} }
currentRequest=new GetOwnAccount() currentRequest=new GetOwnAccount()

View File

@@ -83,10 +83,9 @@ public class CustomWelcomeFragment extends InstanceCatalogFragment {
@Override @Override
protected void updateFilteredList(){ protected void updateFilteredList(){
String query=getCurrentSearchQuery(); boolean addFakeInstance = currentSearchQuery.length()>0 && currentSearchQuery.matches("^\\S+\\.[^\\.]+$");
boolean addFakeInstance=query.length()>0 && query.matches("^\\S+\\.[^\\.]+$");
if(addFakeInstance){ if(addFakeInstance){
fakeInstance.domain=fakeInstance.normalizedDomain=query; fakeInstance.domain=fakeInstance.normalizedDomain=currentSearchQuery;
fakeInstance.description=getString(R.string.loading_instance); fakeInstance.description=getString(R.string.loading_instance);
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){ if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
if(list.findViewHolderForAdapterPosition(1) instanceof InstanceViewHolder ivh){ if(list.findViewHolderForAdapterPosition(1) instanceof InstanceViewHolder ivh){
@@ -100,12 +99,12 @@ public class CustomWelcomeFragment extends InstanceCatalogFragment {
} }
ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData); ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData);
filteredData.clear(); filteredData.clear();
if(query.length()>0){ if(currentSearchQuery.length()>0){
boolean foundExactMatch=false; boolean foundExactMatch=false;
for(CatalogInstance inst:data){ for(CatalogInstance inst:data){
if(inst.normalizedDomain.contains(query)){ if(inst.normalizedDomain.contains(currentSearchQuery)){
filteredData.add(inst); filteredData.add(inst);
if(inst.normalizedDomain.equals(query)) if(inst.normalizedDomain.equals(currentSearchQuery))
foundExactMatch=true; foundExactMatch=true;
} }
} }

View File

@@ -93,10 +93,10 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim(); currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
updateFilteredList(); updateFilteredList();
searchEdit.removeCallbacks(searchDebouncer); searchEdit.removeCallbacks(searchDebouncer);
Instance instance=instancesCache.get(normalizeInstanceDomain(getCurrentSearchQuery())); Instance instance=instancesCache.get(normalizeInstanceDomain(currentSearchQuery));
if(instance==null){ if(instance==null){
showProgressDialog(); showProgressDialog();
loadInstanceInfo(getCurrentSearchQuery(), false); loadInstanceInfo(currentSearchQuery, false);
}else{ }else{
proceedWithAuthOrSignup(instance); proceedWithAuthOrSignup(instance);
} }
@@ -106,7 +106,7 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
protected void onSearchChangedDebounced(){ protected void onSearchChangedDebounced(){
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim(); currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
updateFilteredList(); updateFilteredList();
loadInstanceInfo(getCurrentSearchQuery(), false); loadInstanceInfo(currentSearchQuery, false);
} }
protected List<CatalogInstance> sortInstances(List<CatalogInstance> result){ protected List<CatalogInstance> sortInstances(List<CatalogInstance> result){
@@ -126,16 +126,9 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
instanceProgressDialog.show(); instanceProgressDialog.show();
} }
protected String getCurrentSearchQuery(){
String[] parts=currentSearchQuery.split("@");
return parts.length>0 ? parts[parts.length-1] : "";
}
protected String normalizeInstanceDomain(String _domain){ protected String normalizeInstanceDomain(String _domain){
if(TextUtils.isEmpty(_domain)) if(TextUtils.isEmpty(_domain))
return null; return null;
String[] parts=_domain.split("@");
_domain=parts[parts.length - 1];
if(_domain.contains(":")){ if(_domain.contains(":")){
try{ try{
_domain=Uri.parse(_domain).getAuthority(); _domain=Uri.parse(_domain).getAuthority();
@@ -205,7 +198,7 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
instanceProgressDialog=null; instanceProgressDialog=null;
proceedWithAuthOrSignup(result); proceedWithAuthOrSignup(result);
} }
if(Objects.equals(domain, getCurrentSearchQuery()) || Objects.equals(getCurrentSearchQuery(), redirects.get(domain)) || Objects.equals(getCurrentSearchQuery(), redirectsInverse.get(domain))){ if(Objects.equals(domain, currentSearchQuery) || Objects.equals(currentSearchQuery, redirects.get(domain)) || Objects.equals(currentSearchQuery, redirectsInverse.get(domain))){
boolean found=false; boolean found=false;
for(CatalogInstance ci:filteredData){ for(CatalogInstance ci:filteredData){
if(ci.domain.equals(domain) && ci!=fakeInstance){ if(ci.domain.equals(domain) && ci!=fakeInstance){

View File

@@ -3,6 +3,7 @@ package org.joinmastodon.android.fragments.onboarding;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;

View File

@@ -271,7 +271,7 @@ public class SignupFragment extends ToolbarFragment{
@Override @Override
public void tail(Node node, int depth){ public void tail(Node node, int depth){
if(node instanceof Element){ if(node instanceof Element){
ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null, null, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new TypefaceSpan("sans-serif-medium"), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); ssb.setSpan(new TypefaceSpan("sans-serif-medium"), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
} }

View File

@@ -1,6 +1,8 @@
package org.joinmastodon.android.fragments.report; package org.joinmastodon.android.fragments.report;
import android.app.Activity; import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect; import android.graphics.Rect;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@@ -23,7 +25,6 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.OutlineProviders; import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.displayitems.AudioStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.AudioStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.CheckableHeaderStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.CheckableHeaderStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.DummyStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
@@ -82,11 +83,10 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
currentRequest=new GetAccountStatuses(reportAccount.id, getMaxID(), null, count, GetAccountStatuses.Filter.OWN_POSTS_AND_REPLIES) currentRequest=new GetAccountStatuses(reportAccount.id, offset>0 ? getMaxID() : null, null, count, GetAccountStatuses.Filter.OWN_POSTS_AND_REPLIES)
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if(getActivity()==null) return;
for(Status s:result){ for(Status s:result){
s.sensitive=true; s.sensitive=true;
} }
@@ -96,14 +96,15 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
.exec(accountID); .exec(accountID);
} }
public void onToggleItem(String id){ @Override
public void onItemClick(String id){
if(selectedIDs.contains(id)) if(selectedIDs.contains(id))
selectedIDs.remove(id); selectedIDs.remove(id);
else else
selectedIDs.add(id); selectedIDs.add(id);
CheckableHeaderStatusDisplayItem.Holder holder=findHolderOfType(id, CheckableHeaderStatusDisplayItem.Holder.class); CheckableHeaderStatusDisplayItem.Holder holder=findHolderOfType(id, CheckableHeaderStatusDisplayItem.Holder.class);
if(holder!=null) holder.rebind(); if(holder!=null)
else notifyItemChanged(id, CheckableHeaderStatusDisplayItem.class); holder.rebind();
} }
@Override @Override
@@ -119,22 +120,15 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view); RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
if(holder.getAbsoluteAdapterPosition()==0 || holder instanceof CheckableHeaderStatusDisplayItem.Holder) if(holder.getAbsoluteAdapterPosition()==0 || holder instanceof CheckableHeaderStatusDisplayItem.Holder)
return; return;
boolean isRTL=parent.getLayoutDirection()==View.LAYOUT_DIRECTION_RTL; outRect.left=V.dp(40);
if(isRTL) outRect.right=V.dp(40);
else outRect.left=V.dp(40);
if(holder instanceof AudioStatusDisplayItem.Holder){ if(holder instanceof AudioStatusDisplayItem.Holder){
outRect.bottom=V.dp(16); outRect.bottom=V.dp(16);
}else if(holder instanceof LinkCardStatusDisplayItem.Holder || holder instanceof MediaGridStatusDisplayItem.Holder){ }else if(holder instanceof LinkCardStatusDisplayItem.Holder || holder instanceof MediaGridStatusDisplayItem.Holder){
outRect.bottom=V.dp(8); outRect.bottom=V.dp(16);
if(isRTL){
outRect.right+=V.dp(16);
outRect.left=V.dp(16);
}else{
outRect.left+=V.dp(16); outRect.left+=V.dp(16);
outRect.right=V.dp(16); outRect.right=V.dp(16);
} }
} }
}
}); });
ProgressBar topProgress=view.findViewById(R.id.top_progress); ProgressBar topProgress=view.findViewById(R.id.top_progress);
@@ -160,6 +154,9 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
return adapter; return adapter;
} }
protected void drawDivider(View child, View bottomSibling, RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder, RecyclerView parent, Canvas c, Paint paint){
}
private void onButtonClick(View v){ private void onButtonClick(View v){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
@@ -203,9 +200,7 @@ public class ReportAddPostsChoiceFragment 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, getFilterContext(), StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_CHECKABLE | StatusDisplayItem.FLAG_MEDIA_FORCE_HIDDEN); return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, getFilterContext(), StatusDisplayItem.FLAG_INSET | StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_CHECKABLE | StatusDisplayItem.FLAG_MEDIA_FORCE_HIDDEN);
items.add(new DummyStatusDisplayItem(s.getID(), this));
return items;
} }
@Override @Override

View File

@@ -34,6 +34,7 @@ import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.imageloader.ViewImageLoader; import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class ReportDoneFragment extends MastodonToolbarFragment{ public class ReportDoneFragment extends MastodonToolbarFragment{
@@ -226,7 +227,7 @@ public class ReportDoneFragment extends MastodonToolbarFragment{
@Override @Override
protected int getNavigationIconDrawableResource(){ protected int getNavigationIconDrawableResource(){
return R.drawable.ic_fluent_dismiss_24_regular; return R.drawable.ic_baseline_close_24;
} }
@Override @Override

View File

@@ -83,7 +83,7 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
reportStatus=Parcels.unwrap(getArguments().getParcelable("status")); reportStatus=Parcels.unwrap(getArguments().getParcelable("status"));
if(reportStatus!=null){ if(reportStatus!=null){
Status hiddenStatus=reportStatus.clone(); Status hiddenStatus=reportStatus.clone();
if(hiddenStatus.spoilerText==null) hiddenStatus.spoilerText=getString(R.string.post_hidden); hiddenStatus.spoilerText=getString(R.string.post_hidden);
onDataLoaded(Collections.singletonList(hiddenStatus)); onDataLoaded(Collections.singletonList(hiddenStatus));
setTitle(R.string.report_title_post); setTitle(R.string.report_title_post);
}else{ }else{
@@ -168,6 +168,17 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
((UsableRecyclerView)list).setIncludeMarginsInItemHitbox(false); ((UsableRecyclerView)list).setIncludeMarginsInItemHitbox(false);
if(reportStatus!=null){ if(reportStatus!=null){
list.addItemDecoration(new RecyclerView.ItemDecoration(){
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
if(holder instanceof LinkCardStatusDisplayItem.Holder || holder instanceof MediaGridStatusDisplayItem.Holder){
outRect.left=V.dp(16);
outRect.right=V.dp(16);
}
}
});
list.addItemDecoration(new RecyclerView.ItemDecoration(){ list.addItemDecoration(new RecyclerView.ItemDecoration(){
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG); private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
{ {
@@ -204,6 +215,18 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
float off=paint.getStrokeWidth()/2f; float off=paint.getStrokeWidth()/2f;
c.drawRoundRect(V.dp(16)-off, top-off, parent.getWidth()-V.dp(16)+off, bottom+off, V.dp(12), V.dp(12), paint); c.drawRoundRect(V.dp(16)-off, top-off, parent.getWidth()-V.dp(16)+off, bottom+off, V.dp(12), V.dp(12), paint);
} }
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
if(holder instanceof StatusDisplayItem.Holder<?>){
outRect.left=outRect.right=V.dp(16);
}
int index=holder.getAbsoluteAdapterPosition()-mergeAdapter.getPositionForAdapter(adapter);
if(index==displayItems.size()){
outRect.top=V.dp(32);
}
}
}); });
} }
} }
@@ -228,6 +251,18 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
return null; return null;
} }
@Override
protected void onModifyItemViewHolder(BindableViewHolder<StatusDisplayItem> holder){
if((Object)holder instanceof MediaGridStatusDisplayItem.Holder h){
View layout=h.getLayout();
layout.setOutlineProvider(OutlineProviders.roundedRect(8));
layout.setClipToOutline(true);
View overlay=h.getSensitiveOverlay();
overlay.setOutlineProvider(OutlineProviders.roundedRect(8));
overlay.setClipToOutline(true);
}
}
@Override @Override
public void putRelationship(String id, Relationship rel){ public void putRelationship(String id, Relationship rel){
super.putRelationship(id, rel); super.putRelationship(id, rel);

View File

@@ -73,7 +73,7 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
durationItem=new ListItem<>(R.string.settings_filter_duration, 0, this::onDurationClick), durationItem=new ListItem<>(R.string.settings_filter_duration, 0, this::onDurationClick),
wordsItem=new ListItem<>(R.string.settings_filter_muted_words, 0, this::onWordsClick), wordsItem=new ListItem<>(R.string.settings_filter_muted_words, 0, this::onWordsClick),
contextItem=new ListItem<>(R.string.settings_filter_context, 0, this::onContextClick), contextItem=new ListItem<>(R.string.settings_filter_context, 0, this::onContextClick),
cwItem=new CheckableListItem<>(R.string.settings_filter_show_cw, R.string.settings_filter_show_cw_explanation, CheckableListItem.Style.SWITCH, filter==null || filter.filterAction==FilterAction.WARN, this::toggleCheckableItem) cwItem=new CheckableListItem<>(R.string.settings_filter_show_cw, R.string.settings_filter_show_cw_explanation, CheckableListItem.Style.SWITCH, filter==null || filter.filterAction==FilterAction.WARN, ()->toggleCheckableItem(cwItem))
)); ));
if(filter!=null){ if(filter!=null){
@@ -113,7 +113,7 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
return 1; return 1;
} }
private void onDurationClick(ListItem<Void> item_){ private void onDurationClick(){
int[] durationOptions={ int[] durationOptions={
1800, 1800,
3600, 3600,
@@ -182,21 +182,21 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
alert.setOnDismissListener(dialog->callback.accept(null)); alert.setOnDismissListener(dialog->callback.accept(null));
} }
private void onWordsClick(ListItem<Void> item){ private void onWordsClick(){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putParcelableArrayList("words", (ArrayList<? extends Parcelable>) keywords.stream().map(Parcels::wrap).collect(Collectors.toCollection(ArrayList::new))); args.putParcelableArrayList("words", (ArrayList<? extends Parcelable>) keywords.stream().map(Parcels::wrap).collect(Collectors.toCollection(ArrayList::new)));
Nav.goForResult(getActivity(), FilterWordsFragment.class, args, WORDS_RESULT, this); Nav.goForResult(getActivity(), FilterWordsFragment.class, args, WORDS_RESULT, this);
} }
private void onContextClick(ListItem<Void> item){ private void onContextClick(){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putSerializable("context", context); args.putSerializable("context", context);
Nav.goForResult(getActivity(), FilterContextFragment.class, args, CONTEXT_RESULT, this); Nav.goForResult(getActivity(), FilterContextFragment.class, args, CONTEXT_RESULT, this);
} }
private void onDeleteClick(ListItem<Void> item_){ private void onDeleteClick(){
AlertDialog alert=new M3AlertDialogBuilder(getActivity()) AlertDialog alert=new M3AlertDialogBuilder(getActivity())
.setTitle(getString(R.string.settings_delete_filter_title, filter.title)) .setTitle(getString(R.string.settings_delete_filter_title, filter.title))
.setMessage(R.string.settings_delete_filter_confirmation) .setMessage(R.string.settings_delete_filter_confirmation)

View File

@@ -22,9 +22,10 @@ public class FilterContextFragment extends BaseSettingsFragment<FilterContext> i
setTitle(R.string.settings_filter_context); setTitle(R.string.settings_filter_context);
context=(EnumSet<FilterContext>) getArguments().getSerializable("context"); context=(EnumSet<FilterContext>) getArguments().getSerializable("context");
onDataLoaded(Arrays.stream(FilterContext.values()).map(c->{ onDataLoaded(Arrays.stream(FilterContext.values()).map(c->{
CheckableListItem<FilterContext> item=new CheckableListItem<>(c.getDisplayNameRes(), 0, CheckableListItem.Style.CHECKBOX, context.contains(c), this::toggleCheckableItem); CheckableListItem<FilterContext> item=new CheckableListItem<>(c.getDisplayNameRes(), 0, CheckableListItem.Style.CHECKBOX, context.contains(c), null);
item.parentObject=c; item.parentObject=c;
item.isEnabled=true; item.isEnabled=true;
item.onClick=()->toggleCheckableItem(item);
return item; return item;
}).collect(Collectors.toList())); }).collect(Collectors.toList()));
} }

View File

@@ -1,6 +1,11 @@
package org.joinmastodon.android.fragments.settings; package org.joinmastodon.android.fragments.settings;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.IntEvaluator;
import android.animation.ObjectAnimator;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
@@ -22,7 +27,6 @@ import org.joinmastodon.android.model.FilterKeyword;
import org.joinmastodon.android.model.viewmodel.CheckableListItem; import org.joinmastodon.android.model.viewmodel.CheckableListItem;
import org.joinmastodon.android.model.viewmodel.ListItem; import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.ActionModeHelper;
import org.joinmastodon.android.ui.utils.SimpleTextWatcher; import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout; import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout;
@@ -33,6 +37,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import me.grishka.appkit.FragmentStackActivity;
import me.grishka.appkit.fragments.OnBackPressedListener; import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
@@ -55,7 +60,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
FilterKeyword word=Parcels.unwrap(p); FilterKeyword word=Parcels.unwrap(p);
ListItem<FilterKeyword> item=new ListItem<>(word.keyword, null, null, word); ListItem<FilterKeyword> item=new ListItem<>(word.keyword, null, null, word);
item.isEnabled=true; item.isEnabled=true;
item.setOnClick(this::onWordClick); item.onClick=()->onWordClick(item);
return item; return item;
}).collect(Collectors.toList())); }).collect(Collectors.toList()));
setHasOptionsMenu(true); setHasOptionsMenu(true);
@@ -92,7 +97,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
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);
fab.setImageResource(R.drawable.ic_fluent_add_24_regular); fab.setImageResource(R.drawable.ic_add_24px);
fab.setContentDescription(getString(R.string.add_muted_word)); fab.setContentDescription(getString(R.string.add_muted_word));
fab.setOnClickListener(v->onFabClick()); fab.setOnClickListener(v->onFabClick());
} }
@@ -109,7 +114,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
inflater.inflate(R.menu.selectable_list, menu); inflater.inflate(R.menu.settings_filter_words, menu);
} }
@Override @Override
@@ -169,7 +174,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
w.keyword=input; w.keyword=input;
ListItem<FilterKeyword> item=new ListItem<>(w.keyword, null, null, w); ListItem<FilterKeyword> item=new ListItem<>(w.keyword, null, null, w);
item.isEnabled=true; item.isEnabled=true;
item.setOnClick(this::onWordClick); item.onClick=()->onWordClick(item);
data.add(item); data.add(item);
itemsAdapter.notifyItemInserted(data.size()-1); itemsAdapter.notifyItemInserted(data.size()-1);
}else{ }else{
@@ -223,15 +228,29 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
return; return;
V.setVisibilityAnimated(fab, View.GONE); V.setVisibilityAnimated(fab, View.GONE);
actionMode=ActionModeHelper.startActionMode(this, ()->elevationOnScrollListener.getCurrentStatusBarColor(), new ActionMode.Callback(){ actionMode=getActivity().startActionMode(new ActionMode.Callback(){
@Override @Override
public boolean onCreateActionMode(ActionMode mode, Menu menu){ public boolean onCreateActionMode(ActionMode mode, Menu menu){
ObjectAnimator anim=ObjectAnimator.ofInt(getActivity().getWindow(), "statusBarColor", elevationOnScrollListener.getCurrentStatusBarColor(), UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary));
anim.setEvaluator(new IntEvaluator(){
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue){
return UiUtils.alphaBlendColors(startValue, endValue, fraction);
}
});
anim.start();
((FragmentStackActivity) getActivity()).invalidateSystemBarColors(FilterWordsFragment.this);
return true; return true;
} }
@Override @Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu){ public boolean onPrepareActionMode(ActionMode mode, Menu menu){
mode.getMenuInflater().inflate(R.menu.settings_filter_words_action_mode, menu); mode.getMenuInflater().inflate(R.menu.settings_filter_words_action_mode, menu);
for(int i=0;i<menu.size();i++){
Drawable icon=menu.getItem(i).getIcon().mutate();
icon.setTint(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnPrimary));
menu.getItem(i).setIcon(icon);
}
deleteItem=menu.findItem(R.id.delete); deleteItem=menu.findItem(R.id.delete);
return true; return true;
} }
@@ -247,6 +266,21 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
@Override @Override
public void onDestroyActionMode(ActionMode mode){ public void onDestroyActionMode(ActionMode mode){
leaveSelectionMode(true); leaveSelectionMode(true);
ObjectAnimator anim=ObjectAnimator.ofInt(getActivity().getWindow(), "statusBarColor", UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary), elevationOnScrollListener.getCurrentStatusBarColor());
anim.setEvaluator(new IntEvaluator(){
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue){
return UiUtils.alphaBlendColors(startValue, endValue, fraction);
}
});
anim.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation){
getActivity().getWindow().setStatusBarColor(0);
}
});
anim.start();
((FragmentStackActivity) getActivity()).invalidateSystemBarColors(FilterWordsFragment.this);
} }
}); });
@@ -255,7 +289,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
ListItem<FilterKeyword> item=data.get(i); ListItem<FilterKeyword> item=data.get(i);
CheckableListItem<FilterKeyword> newItem=new CheckableListItem<>(item.title, null, CheckableListItem.Style.CHECKBOX, selectAll, null); CheckableListItem<FilterKeyword> newItem=new CheckableListItem<>(item.title, null, CheckableListItem.Style.CHECKBOX, selectAll, null);
newItem.isEnabled=true; newItem.isEnabled=true;
newItem.setOnClick(this::onSelectionModeWordClick); newItem.onClick=()->onSelectionModeWordClick(newItem);
newItem.parentObject=item.parentObject; newItem.parentObject=item.parentObject;
if(selectAll) if(selectAll)
selectedItems.add(newItem); selectedItems.add(newItem);
@@ -279,7 +313,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
ListItem<FilterKeyword> item=data.get(i); ListItem<FilterKeyword> item=data.get(i);
ListItem<FilterKeyword> newItem=new ListItem<>(item.title, null, null); ListItem<FilterKeyword> newItem=new ListItem<>(item.title, null, null);
newItem.isEnabled=true; newItem.isEnabled=true;
newItem.setOnClick(this::onWordClick); newItem.onClick=()->onWordClick(newItem);
newItem.parentObject=item.parentObject; newItem.parentObject=item.parentObject;
data.set(i, newItem); data.set(i, newItem);
} }

View File

@@ -2,39 +2,21 @@ package org.joinmastodon.android.fragments.settings;
import android.app.Activity; import android.app.Activity;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.Gravity; import android.view.Gravity;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import org.joinmastodon.android.BuildConfig; import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController; import org.joinmastodon.android.api.MastodonAPIController;
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.model.viewmodel.CheckableListItem;
import org.joinmastodon.android.model.viewmodel.ListItem; import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.updater.GithubSelfUpdater;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.imageloader.ImageCache; import me.grishka.appkit.imageloader.ImageCache;
@@ -43,49 +25,24 @@ import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{
private static final String TAG="SettingsAboutAppFragment"; private ListItem<Void> mediaCacheItem;
private ListItem<Void> mediaCacheItem, copyCrashLogItem;
private CheckableListItem<Void> enablePreReleasesItem;
private AccountSession session;
private boolean timelineCacheCleared=false;
private File crashLogFile=new File(MastodonApp.context.getFilesDir(), "crash.log");
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setTitle(getString(R.string.about_app, getString(R.string.sk_app_name))); setTitle(getString(R.string.about_app, getString(R.string.sk_app_name)));
session=AccountSessionManager.get(accountID); AccountSession s=AccountSessionManager.get(accountID);
onDataLoaded(List.of(
String lastModified=crashLogFile.exists() new ListItem<>(R.string.sk_settings_donate, 0, R.drawable.ic_fluent_heart_24_regular, ()->UiUtils.openHashtagTimeline(getActivity(), accountID, getString(R.string.donate_hashtag), null)),
? DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT).withZone(ZoneId.systemDefault()).format(Instant.ofEpochMilli(crashLogFile.lastModified())) new ListItem<>(R.string.sk_settings_contribute, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), getString(R.string.repo_url))),
: getString(R.string.sk_settings_crash_log_unavailable); new ListItem<>(R.string.settings_tos, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/terms")),
List<ListItem<Void>> items=new ArrayList<>(List.of( new ListItem<>(R.string.settings_privacy_policy, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), getString(R.string.privacy_policy_url)), 0, true),
new ListItem<>(R.string.sk_settings_donate, 0, R.drawable.ic_fluent_heart_24_regular, i->UiUtils.openHashtagTimeline(getActivity(), accountID, getString(R.string.donate_hashtag))), mediaCacheItem=new ListItem<>(R.string.settings_clear_cache, 0, this::onClearMediaCacheClick)
new ListItem<>(R.string.sk_settings_contribute, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), getString(R.string.repo_url))),
new ListItem<>(R.string.settings_tos, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms")),
new ListItem<>(R.string.settings_privacy_policy, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), getString(R.string.privacy_policy_url)), 0, true),
mediaCacheItem=new ListItem<>(R.string.settings_clear_cache, 0, this::onClearMediaCacheClick),
new ListItem<>(getString(R.string.sk_settings_clear_timeline_cache), session.domain, this::onClearTimelineCacheClick),
copyCrashLogItem=new ListItem<>(getString(R.string.sk_settings_copy_crash_log), lastModified, 0, this::onCopyCrashLog)
)); ));
if(GithubSelfUpdater.needSelfUpdating()){
items.add(enablePreReleasesItem=new CheckableListItem<>(R.string.sk_updater_enable_pre_releases, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.enablePreReleases, i->toggleCheckableItem(enablePreReleasesItem)));
}
copyCrashLogItem.isEnabled=crashLogFile.exists();
onDataLoaded(items);
updateMediaCacheItem(); updateMediaCacheItem();
} }
@Override
protected void onHidden(){
super.onHidden();
GlobalUserPreferences.enablePreReleases=enablePreReleasesItem.checked;
GlobalUserPreferences.save();
if(timelineCacheCleared) getActivity().recreate();
}
@Override @Override
protected void doLoadData(int offset, int count){} protected void doLoadData(int offset, int count){}
@@ -106,7 +63,7 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{
return adapter; return adapter;
} }
private void onClearMediaCacheClick(ListItem<?> item){ private void onClearMediaCacheClick(){
MastodonAPIController.runInBackground(()->{ MastodonAPIController.runInBackground(()->{
Activity activity=getActivity(); Activity activity=getActivity();
ImageCache.getInstance(getActivity()).clear(); ImageCache.getInstance(getActivity()).clear();
@@ -117,29 +74,10 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{
}); });
} }
private void onClearTimelineCacheClick(ListItem<?> item){
session.getCacheController().putHomeTimeline(List.of(), true);
Toast.makeText(getContext(), R.string.sk_timeline_cache_cleared, Toast.LENGTH_SHORT).show();
timelineCacheCleared=true;
}
private void updateMediaCacheItem(){ private void updateMediaCacheItem(){
long size=ImageCache.getInstance(getActivity()).getDiskCache().size(); long size=ImageCache.getInstance(getActivity()).getDiskCache().size();
mediaCacheItem.subtitle=UiUtils.formatFileSize(getActivity(), size, false); mediaCacheItem.subtitle=UiUtils.formatFileSize(getActivity(), size, false);
mediaCacheItem.isEnabled=size>0; mediaCacheItem.isEnabled=size>0;
rebindItem(mediaCacheItem); rebindItem(mediaCacheItem);
} }
private void onCopyCrashLog(ListItem<?> item){
if(!crashLogFile.exists()) return;
try(InputStream is=new FileInputStream(crashLogFile)){
BufferedReader reader=new BufferedReader(new InputStreamReader(is));
StringBuilder sb=new StringBuilder();
String line;
while ((line=reader.readLine())!=null) sb.append(line).append("\n");
UiUtils.copyText(list, sb.toString());
} catch(IOException e){
Log.e(TAG, "Error reading crash log", e);
}
}
} }

View File

@@ -20,7 +20,6 @@ import org.joinmastodon.android.utils.MastodonLanguage;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import java.util.stream.Stream;
public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> implements HasAccountID{ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> implements HasAccountID{
private ListItem<Void> languageItem; private ListItem<Void> languageItem;
@@ -46,20 +45,20 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
List<ListItem<Void>> items = new ArrayList<>(List.of( List<ListItem<Void>> items = new ArrayList<>(List.of(
languageItem=new ListItem<>(getString(R.string.default_post_language), postLanguage!=null ? postLanguage.getDisplayName(getContext()) : null, R.drawable.ic_fluent_local_language_24_regular, this::onDefaultLanguageClick), languageItem=new ListItem<>(getString(R.string.default_post_language), postLanguage!=null ? postLanguage.getDisplayName(getContext()) : null, R.drawable.ic_fluent_local_language_24_regular, this::onDefaultLanguageClick),
altTextItem=new CheckableListItem<>(R.string.settings_alt_text_reminders, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.altTextReminders, R.drawable.ic_fluent_image_alt_text_24_regular, i->toggleCheckableItem(altTextItem)), altTextItem=new CheckableListItem<>(R.string.settings_alt_text_reminders, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.altTextReminders, R.drawable.ic_fluent_image_alt_text_24_regular, ()->toggleCheckableItem(altTextItem)),
playGifsItem=new CheckableListItem<>(R.string.settings_gif, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.playGifs, R.drawable.ic_fluent_gif_24_regular, i->toggleCheckableItem(playGifsItem)), playGifsItem=new CheckableListItem<>(R.string.settings_gif, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.playGifs, R.drawable.ic_fluent_gif_24_regular, ()->toggleCheckableItem(playGifsItem)),
overlayMediaItem=new CheckableListItem<>(R.string.sk_settings_continues_playback, R.string.sk_settings_continues_playback_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.overlayMedia, R.drawable.ic_fluent_play_circle_hint_24_regular, i->toggleCheckableItem(overlayMediaItem)), overlayMediaItem=new CheckableListItem<>(R.string.sk_settings_continues_playback, R.string.sk_settings_continues_playback_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.overlayMedia, R.drawable.ic_fluent_play_circle_hint_24_regular, ()->toggleCheckableItem(overlayMediaItem)),
customTabsItem=new CheckableListItem<>(R.string.settings_custom_tabs, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.useCustomTabs, R.drawable.ic_fluent_link_24_regular, i->toggleCheckableItem(customTabsItem)), customTabsItem=new CheckableListItem<>(R.string.settings_custom_tabs, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.useCustomTabs, R.drawable.ic_fluent_link_24_regular, ()->toggleCheckableItem(customTabsItem)),
confirmUnfollowItem=new CheckableListItem<>(R.string.settings_confirm_unfollow, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmUnfollow, R.drawable.ic_fluent_person_delete_24_regular, i->toggleCheckableItem(confirmUnfollowItem)), confirmUnfollowItem=new CheckableListItem<>(R.string.settings_confirm_unfollow, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmUnfollow, R.drawable.ic_fluent_person_delete_24_regular, ()->toggleCheckableItem(confirmUnfollowItem)),
confirmBoostItem=new CheckableListItem<>(R.string.settings_confirm_boost, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmBoost, R.drawable.ic_fluent_arrow_repeat_all_24_regular, i->toggleCheckableItem(confirmBoostItem)), confirmBoostItem=new CheckableListItem<>(R.string.settings_confirm_boost, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmBoost, R.drawable.ic_fluent_arrow_repeat_all_24_regular, ()->toggleCheckableItem(confirmBoostItem)),
confirmDeleteItem=new CheckableListItem<>(R.string.settings_confirm_delete_post, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmDeletePost, R.drawable.ic_fluent_delete_24_regular, i->toggleCheckableItem(confirmDeleteItem)), confirmDeleteItem=new CheckableListItem<>(R.string.settings_confirm_delete_post, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmDeletePost, R.drawable.ic_fluent_delete_24_regular, ()->toggleCheckableItem(confirmDeleteItem)),
prefixRepliesItem=new ListItem<>(R.string.sk_settings_prefix_reply_cw_with_re, getPrefixWithRepliesString(), R.drawable.ic_fluent_arrow_reply_24_regular, this::onPrefixRepliesClick), prefixRepliesItem=new ListItem<>(R.string.sk_settings_prefix_reply_cw_with_re, getPrefixWithRepliesString(), R.drawable.ic_fluent_arrow_reply_24_regular, this::onPrefixRepliesClick),
forwardReportsItem=new CheckableListItem<>(R.string.sk_settings_forward_report_default, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.forwardReportDefault, R.drawable.ic_fluent_arrow_forward_24_regular, i->toggleCheckableItem(forwardReportsItem)), forwardReportsItem=new CheckableListItem<>(R.string.sk_settings_forward_report_default, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.forwardReportDefault, R.drawable.ic_fluent_arrow_forward_24_regular, ()->toggleCheckableItem(forwardReportsItem)),
loadNewPostsItem=new CheckableListItem<>(R.string.sk_settings_load_new_posts, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.loadNewPosts, R.drawable.ic_fluent_arrow_sync_24_regular, i->onLoadNewPostsClick()), loadNewPostsItem=new CheckableListItem<>(R.string.sk_settings_load_new_posts, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.loadNewPosts, R.drawable.ic_fluent_arrow_sync_24_regular, this::onLoadNewPostsClick),
seeNewPostsBtnItem=new CheckableListItem<>(R.string.sk_settings_see_new_posts_button, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showNewPostsButton, R.drawable.ic_fluent_arrow_up_24_regular, i->toggleCheckableItem(seeNewPostsBtnItem)), seeNewPostsBtnItem=new CheckableListItem<>(R.string.sk_settings_see_new_posts_button, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showNewPostsButton, R.drawable.ic_fluent_arrow_up_24_regular, ()->toggleCheckableItem(seeNewPostsBtnItem)),
remoteLoadingItem=new CheckableListItem<>(R.string.sk_settings_allow_remote_loading, R.string.sk_settings_allow_remote_loading_explanation, CheckableListItem.Style.SWITCH, GlobalUserPreferences.allowRemoteLoading, R.drawable.ic_fluent_communication_24_regular, i->toggleCheckableItem(remoteLoadingItem), true), remoteLoadingItem=new CheckableListItem<>(R.string.sk_settings_allow_remote_loading, R.string.sk_settings_allow_remote_loading_explanation, CheckableListItem.Style.SWITCH, GlobalUserPreferences.allowRemoteLoading, R.drawable.ic_fluent_communication_24_regular, ()->toggleCheckableItem(remoteLoadingItem), true),
showBoostsItem=new CheckableListItem<>(R.string.sk_settings_show_boosts, 0, CheckableListItem.Style.SWITCH, lp.showBoosts, R.drawable.ic_fluent_arrow_repeat_all_24_regular, i->toggleCheckableItem(showBoostsItem)), showBoostsItem=new CheckableListItem<>(R.string.sk_settings_show_boosts, 0, CheckableListItem.Style.SWITCH, lp.showBoosts, R.drawable.ic_fluent_arrow_repeat_all_24_regular, ()->toggleCheckableItem(showBoostsItem)),
showRepliesItem=new CheckableListItem<>(R.string.sk_settings_show_replies, 0, CheckableListItem.Style.SWITCH, lp.showReplies, R.drawable.ic_fluent_arrow_reply_24_regular, i->toggleCheckableItem(showRepliesItem)) showRepliesItem=new CheckableListItem<>(R.string.sk_settings_show_replies, 0, CheckableListItem.Style.SWITCH, lp.showReplies, R.drawable.ic_fluent_arrow_reply_24_regular, ()->toggleCheckableItem(showRepliesItem))
)); ));
if(isInstanceAkkoma()) items.add( if(isInstanceAkkoma()) items.add(
@@ -93,7 +92,7 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
@Override @Override
protected void doLoadData(int offset, int count){} protected void doLoadData(int offset, int count){}
private void onDefaultLanguageClick(ListItem<?> item){ private void onDefaultLanguageClick(){
if (languageResolver == null) return; if (languageResolver == null) return;
ComposeLanguageAlertViewController vc=new ComposeLanguageAlertViewController(getActivity(), null, new ComposeLanguageAlertViewController.SelectedOption(postLanguage), null, languageResolver); ComposeLanguageAlertViewController vc=new ComposeLanguageAlertViewController(getActivity(), null, new ComposeLanguageAlertViewController.SelectedOption(postLanguage), null, languageResolver);
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())
@@ -112,14 +111,14 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
.show(); .show();
} }
private void onPrefixRepliesClick(ListItem<?> item){ private void onPrefixRepliesClick(){
int selected=GlobalUserPreferences.prefixReplies.ordinal(); int selected=GlobalUserPreferences.prefixReplies.ordinal();
int[] newSelected={selected}; int[] newSelected={selected};
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_settings_prefix_reply_cw_with_re) .setTitle(R.string.sk_settings_prefix_reply_cw_with_re)
.setSingleChoiceItems((String[]) IntStream.of(R.string.sk_settings_prefix_replies_never, R.string.sk_settings_prefix_replies_always, R.string.sk_settings_prefix_replies_to_others).mapToObj(this::getString).toArray(String[]::new), .setSingleChoiceItems((String[]) IntStream.of(R.string.sk_settings_prefix_replies_never, R.string.sk_settings_prefix_replies_always, R.string.sk_settings_prefix_replies_to_others).mapToObj(this::getString).toArray(String[]::new),
selected, (dlg, which)->newSelected[0]=which) selected, (dlg, item)->newSelected[0]=item)
.setPositiveButton(R.string.ok, (dlg, which)->{ .setPositiveButton(R.string.ok, (dlg, item)->{
GlobalUserPreferences.prefixReplies=GlobalUserPreferences.PrefixRepliesMode.values()[newSelected[0]]; GlobalUserPreferences.prefixReplies=GlobalUserPreferences.PrefixRepliesMode.values()[newSelected[0]];
prefixRepliesItem.subtitleRes=getPrefixWithRepliesString(); prefixRepliesItem.subtitleRes=getPrefixWithRepliesString();
rebindItem(prefixRepliesItem); rebindItem(prefixRepliesItem);
@@ -128,7 +127,7 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
.show(); .show();
} }
private void onReplyVisibilityClick(ListItem<?> item){ private void onReplyVisibilityClick(){
AccountLocalPreferences lp=getLocalPrefs(); AccountLocalPreferences lp=getLocalPrefs();
int selected=lp.timelineReplyVisibility==null ? 2 : switch(lp.timelineReplyVisibility){ int selected=lp.timelineReplyVisibility==null ? 2 : switch(lp.timelineReplyVisibility){
case "following" -> 0; case "following" -> 0;
@@ -139,8 +138,8 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_settings_prefix_reply_cw_with_re) .setTitle(R.string.sk_settings_prefix_reply_cw_with_re)
.setSingleChoiceItems((String[]) IntStream.of(R.string.sk_settings_reply_visibility_following, R.string.sk_settings_reply_visibility_self, R.string.sk_settings_reply_visibility_all).mapToObj(this::getString).toArray(String[]::new), .setSingleChoiceItems((String[]) IntStream.of(R.string.sk_settings_reply_visibility_following, R.string.sk_settings_reply_visibility_self, R.string.sk_settings_reply_visibility_all).mapToObj(this::getString).toArray(String[]::new),
selected, (dlg, which)->newSelected[0]=which) selected, (dlg, item)->newSelected[0]=item)
.setPositiveButton(R.string.ok, (dlg, which)->{ .setPositiveButton(R.string.ok, (dlg, item)->{
lp.timelineReplyVisibility=switch(newSelected[0]){ lp.timelineReplyVisibility=switch(newSelected[0]){
case 0 -> "following"; case 0 -> "following";
case 1 -> "self"; case 1 -> "self";
@@ -167,7 +166,7 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
GlobalUserPreferences.overlayMedia=overlayMediaItem.checked; GlobalUserPreferences.overlayMedia=overlayMediaItem.checked;
GlobalUserPreferences.useCustomTabs=customTabsItem.checked; GlobalUserPreferences.useCustomTabs=customTabsItem.checked;
GlobalUserPreferences.altTextReminders=altTextItem.checked; GlobalUserPreferences.altTextReminders=altTextItem.checked;
GlobalUserPreferences.confirmUnfollow=confirmUnfollowItem.checked; GlobalUserPreferences.confirmUnfollow=customTabsItem.checked;
GlobalUserPreferences.confirmBoost=confirmBoostItem.checked; GlobalUserPreferences.confirmBoost=confirmBoostItem.checked;
GlobalUserPreferences.confirmDeletePost=confirmDeleteItem.checked; GlobalUserPreferences.confirmDeletePost=confirmDeleteItem.checked;
GlobalUserPreferences.forwardReportDefault=forwardReportsItem.checked; GlobalUserPreferences.forwardReportDefault=forwardReportsItem.checked;
@@ -176,8 +175,6 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
GlobalUserPreferences.allowRemoteLoading=remoteLoadingItem.checked; GlobalUserPreferences.allowRemoteLoading=remoteLoadingItem.checked;
GlobalUserPreferences.save(); GlobalUserPreferences.save();
AccountLocalPreferences lp=getLocalPrefs(); AccountLocalPreferences lp=getLocalPrefs();
boolean restartPlease=lp.showBoosts!=showBoostsItem.checked
|| lp.showReplies!=showRepliesItem.checked;
lp.showBoosts=showBoostsItem.checked; lp.showBoosts=showBoostsItem.checked;
lp.showReplies=showRepliesItem.checked; lp.showReplies=showRepliesItem.checked;
lp.save(); lp.save();
@@ -188,7 +185,6 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
s.preferences.postingDefaultLanguage=newPostLanguage.language.getLanguage(); s.preferences.postingDefaultLanguage=newPostLanguage.language.getLanguage();
s.savePreferencesLater(); s.savePreferencesLater();
} }
if(restartPlease) getActivity().recreate();
} }
@Override @Override

View File

@@ -39,7 +39,7 @@ public class SettingsDebugFragment extends BaseSettingsFragment<Void>{
@Override @Override
protected void doLoadData(int offset, int count){} protected void doLoadData(int offset, int count){}
private void onTestEmailConfirmClick(ListItem<?> item){ private void onTestEmailConfirmClick(){
AccountSession sess=AccountSessionManager.getInstance().getAccount(accountID); AccountSession sess=AccountSessionManager.getInstance().getAccount(accountID);
sess.activated=false; sess.activated=false;
sess.activationInfo=new AccountActivationInfo("test@email", System.currentTimeMillis()); sess.activationInfo=new AccountActivationInfo("test@email", System.currentTimeMillis());
@@ -49,18 +49,18 @@ public class SettingsDebugFragment extends BaseSettingsFragment<Void>{
Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args); Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args);
} }
private void onForceSelfUpdateClick(ListItem<?> item){ private void onForceSelfUpdateClick(){
GithubSelfUpdater.forceUpdate=true; GithubSelfUpdater.forceUpdate=true;
GithubSelfUpdater.getInstance().maybeCheckForUpdates(); GithubSelfUpdater.getInstance().maybeCheckForUpdates();
restartUI(); restartUI();
} }
private void onResetUpdaterClick(ListItem<?> item){ private void onResetUpdaterClick(){
GithubSelfUpdater.getInstance().reset(); GithubSelfUpdater.getInstance().reset();
restartUI(); restartUI();
} }
private void onResetDiscoverBannersClick(ListItem<?> item){ private void onResetDiscoverBannersClick(){
DiscoverInfoBannerHelper.reset(); DiscoverInfoBannerHelper.reset();
restartUI(); restartUI();
} }

View File

@@ -1,7 +1,6 @@
package org.joinmastodon.android.fragments.settings; package org.joinmastodon.android.fragments.settings;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.os.Build; import android.os.Build;
@@ -18,7 +17,6 @@ import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MastodonApp; import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountLocalPreferences; import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference;
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.StatusDisplaySettingsChangedEvent; import org.joinmastodon.android.events.StatusDisplaySettingsChangedEvent;
@@ -29,8 +27,6 @@ import org.joinmastodon.android.ui.views.TextInputFrameLayout;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import me.grishka.appkit.FragmentStackActivity; import me.grishka.appkit.FragmentStackActivity;
@@ -41,7 +37,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
private CheckableListItem<Void> revealCWsItem, hideSensitiveMediaItem, interactionCountsItem, emojiInNamesItem; private CheckableListItem<Void> revealCWsItem, hideSensitiveMediaItem, interactionCountsItem, emojiInNamesItem;
// MEGALODON // MEGALODON
private CheckableListItem<Void> trueBlackModeItem, marqueeItem, disableSwipeItem, reduceMotionItem, altIndicatorItem, noAltIndicatorItem, collapsePostsItem, spectatorModeItem, hideFabItem, translateOpenedItem, disablePillItem, showNavigationLabelsItem, likeIconItem, underlinedLinksItem; private CheckableListItem<Void> trueBlackModeItem, marqueeItem, disableSwipeItem, reduceMotionItem, altIndicatorItem, noAltIndicatorItem, collapsePostsItem, spectatorModeItem, hideFabItem, translateOpenedItem, disablePillItem, showNavigationLabelsItem;
private ListItem<Void> colorItem, publishTextItem, autoRevealCWsItem; private ListItem<Void> colorItem, publishTextItem, autoRevealCWsItem;
private CheckableListItem<Void> pronounsInUserListingsItem, pronounsInTimelinesItem, pronounsInThreadsItem; private CheckableListItem<Void> pronounsInUserListingsItem, pronounsInTimelinesItem, pronounsInThreadsItem;
@@ -55,30 +51,28 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
lp=s.getLocalPreferences(); lp=s.getLocalPreferences();
onDataLoaded(List.of( onDataLoaded(List.of(
themeItem=new ListItem<>(R.string.settings_theme, getAppearanceValue(), R.drawable.ic_fluent_weather_moon_24_regular, this::onAppearanceClick), themeItem=new ListItem<>(R.string.settings_theme, getAppearanceValue(), R.drawable.ic_fluent_weather_moon_24_regular, this::onAppearanceClick),
colorItem=new ListItem<>(getString(R.string.sk_settings_color_palette), getColorPaletteValue(), R.drawable.ic_fluent_color_24_regular, this::onColorClick), colorItem=new ListItem<>(R.string.sk_settings_color_palette, getColorPaletteValue(), R.drawable.ic_fluent_color_24_regular, this::onColorClick),
trueBlackModeItem=new CheckableListItem<>(R.string.sk_settings_true_black, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.trueBlackTheme, R.drawable.ic_fluent_dark_theme_24_regular, i->onTrueBlackModeClick(), true), trueBlackModeItem=new CheckableListItem<>(R.string.sk_settings_true_black, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.trueBlackTheme, R.drawable.ic_fluent_dark_theme_24_regular, this::onTrueBlackModeClick, true),
publishTextItem=new ListItem<>(getString(R.string.sk_settings_publish_button_text), getPublishButtonText(), R.drawable.ic_fluent_send_24_regular, this::onPublishTextClick), publishTextItem=new ListItem<>(getString(R.string.sk_settings_publish_button_text), getPublishButtonText(), R.drawable.ic_fluent_send_24_regular, this::onPublishTextClick),
autoRevealCWsItem=new ListItem<>(R.string.sk_settings_auto_reveal_equal_spoilers, getAutoRevealSpoilersText(), R.drawable.ic_fluent_eye_24_regular, this::onAutoRevealSpoilersClick), autoRevealCWsItem=new ListItem<>(R.string.sk_settings_auto_reveal_equal_spoilers, getAutoRevealSpoilersText(), R.drawable.ic_fluent_eye_24_regular, this::onAutoRevealSpoilersClick),
revealCWsItem=new CheckableListItem<>(R.string.sk_settings_always_reveal_content_warnings, 0, CheckableListItem.Style.SWITCH, lp.revealCWs, R.drawable.ic_fluent_chat_warning_24_regular, i->toggleCheckableItem(revealCWsItem)), revealCWsItem=new CheckableListItem<>(R.string.sk_settings_always_reveal_content_warnings, 0, CheckableListItem.Style.SWITCH, lp.revealCWs, R.drawable.ic_fluent_chat_warning_24_regular, ()->toggleCheckableItem(revealCWsItem)),
hideSensitiveMediaItem=new CheckableListItem<>(R.string.settings_hide_sensitive_media, 0, CheckableListItem.Style.SWITCH, lp.hideSensitiveMedia, R.drawable.ic_fluent_flag_24_regular, i->toggleCheckableItem(hideSensitiveMediaItem)), hideSensitiveMediaItem=new CheckableListItem<>(R.string.settings_hide_sensitive_media, 0, CheckableListItem.Style.SWITCH, lp.hideSensitiveMedia, R.drawable.ic_fluent_flag_24_regular, ()->toggleCheckableItem(hideSensitiveMediaItem)),
interactionCountsItem=new CheckableListItem<>(R.string.settings_show_interaction_counts, 0, CheckableListItem.Style.SWITCH, lp.showInteractionCounts, R.drawable.ic_fluent_number_row_24_regular, i->toggleCheckableItem(interactionCountsItem)), interactionCountsItem=new CheckableListItem<>(R.string.settings_show_interaction_counts, 0, CheckableListItem.Style.SWITCH, lp.showInteractionCounts, R.drawable.ic_fluent_number_row_24_regular, ()->toggleCheckableItem(interactionCountsItem)),
emojiInNamesItem=new CheckableListItem<>(R.string.settings_show_emoji_in_names, 0, CheckableListItem.Style.SWITCH, lp.customEmojiInNames, R.drawable.ic_fluent_emoji_24_regular, i->toggleCheckableItem(emojiInNamesItem)), emojiInNamesItem=new CheckableListItem<>(R.string.settings_show_emoji_in_names, 0, CheckableListItem.Style.SWITCH, lp.customEmojiInNames, R.drawable.ic_fluent_emoji_24_regular, ()->toggleCheckableItem(emojiInNamesItem)),
marqueeItem=new CheckableListItem<>(R.string.sk_settings_enable_marquee, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.toolbarMarquee, R.drawable.ic_fluent_text_more_24_regular, i->toggleCheckableItem(marqueeItem)), marqueeItem=new CheckableListItem<>(R.string.sk_settings_enable_marquee, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.toolbarMarquee, R.drawable.ic_fluent_text_more_24_regular, ()->toggleCheckableItem(marqueeItem)),
reduceMotionItem=new CheckableListItem<>(R.string.sk_settings_reduce_motion, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.reduceMotion, R.drawable.ic_fluent_star_emphasis_24_regular, i->toggleCheckableItem(reduceMotionItem)), reduceMotionItem=new CheckableListItem<>(R.string.sk_settings_reduce_motion, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.reduceMotion, R.drawable.ic_fluent_star_emphasis_24_regular, ()->toggleCheckableItem(reduceMotionItem)),
disableSwipeItem=new CheckableListItem<>(R.string.sk_settings_tabs_disable_swipe, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.disableSwipe, R.drawable.ic_fluent_swipe_right_24_regular, i->toggleCheckableItem(disableSwipeItem)), disableSwipeItem=new CheckableListItem<>(R.string.sk_settings_tabs_disable_swipe, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.disableSwipe, R.drawable.ic_fluent_swipe_right_24_regular, ()->toggleCheckableItem(disableSwipeItem)),
altIndicatorItem=new CheckableListItem<>(R.string.sk_settings_show_alt_indicator, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showAltIndicator, R.drawable.ic_fluent_scan_text_24_regular, i->toggleCheckableItem(altIndicatorItem)), altIndicatorItem=new CheckableListItem<>(R.string.sk_settings_show_alt_indicator, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showAltIndicator, R.drawable.ic_fluent_scan_text_24_regular, ()->toggleCheckableItem(altIndicatorItem)),
noAltIndicatorItem=new CheckableListItem<>(R.string.sk_settings_show_no_alt_indicator, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showNoAltIndicator, R.drawable.ic_fluent_important_24_regular, i->toggleCheckableItem(noAltIndicatorItem)), noAltIndicatorItem=new CheckableListItem<>(R.string.sk_settings_show_no_alt_indicator, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showNoAltIndicator, R.drawable.ic_fluent_important_24_regular, ()->toggleCheckableItem(noAltIndicatorItem)),
collapsePostsItem=new CheckableListItem<>(R.string.sk_settings_collapse_long_posts, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.collapseLongPosts, R.drawable.ic_fluent_chevron_down_24_regular, i->toggleCheckableItem(collapsePostsItem)), collapsePostsItem=new CheckableListItem<>(R.string.sk_settings_collapse_long_posts, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.collapseLongPosts, R.drawable.ic_fluent_chevron_down_24_regular, ()->toggleCheckableItem(collapsePostsItem)),
spectatorModeItem=new CheckableListItem<>(R.string.sk_settings_hide_interaction, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.spectatorMode, R.drawable.ic_fluent_star_off_24_regular, i->toggleCheckableItem(spectatorModeItem)), spectatorModeItem=new CheckableListItem<>(R.string.sk_settings_hide_interaction, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.spectatorMode, R.drawable.ic_fluent_star_off_24_regular, ()->toggleCheckableItem(spectatorModeItem)),
hideFabItem=new CheckableListItem<>(R.string.sk_settings_hide_fab, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.autoHideFab, R.drawable.ic_fluent_edit_24_regular, i->toggleCheckableItem(hideFabItem)), hideFabItem=new CheckableListItem<>(R.string.sk_settings_hide_fab, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.autoHideFab, R.drawable.ic_fluent_edit_24_regular, ()->toggleCheckableItem(hideFabItem)),
translateOpenedItem=new CheckableListItem<>(R.string.sk_settings_translate_only_opened, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.translateButtonOpenedOnly, R.drawable.ic_fluent_translate_24_regular, i->toggleCheckableItem(translateOpenedItem)), translateOpenedItem=new CheckableListItem<>(R.string.sk_settings_translate_only_opened, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.translateButtonOpenedOnly, R.drawable.ic_fluent_translate_24_regular, ()->toggleCheckableItem(translateOpenedItem)),
likeIconItem=new CheckableListItem<>(R.string.sk_settings_like_icon, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.likeIcon, R.drawable.ic_fluent_heart_24_regular, i->toggleCheckableItem(likeIconItem)), disablePillItem=new CheckableListItem<>(R.string.sk_disable_pill_shaped_active_indicator, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.disableM3PillActiveIndicator, R.drawable.ic_fluent_pill_24_regular, ()->toggleCheckableItem(disablePillItem)),
underlinedLinksItem=new CheckableListItem<>(R.string.sk_settings_underlined_links, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.underlinedLinks, R.drawable.ic_fluent_text_underline_24_regular, i->toggleCheckableItem(underlinedLinksItem)), showNavigationLabelsItem=new CheckableListItem<>(R.string.sk_settings_show_labels_in_navigation_bar, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showNavigationLabels, R.drawable.ic_fluent_tag_24_regular, ()->toggleCheckableItem(showNavigationLabelsItem), true),
disablePillItem=new CheckableListItem<>(R.string.sk_disable_pill_shaped_active_indicator, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.disableM3PillActiveIndicator, R.drawable.ic_fluent_pill_24_regular, i->toggleCheckableItem(disablePillItem)), pronounsInTimelinesItem=new CheckableListItem<>(R.string.sk_settings_display_pronouns_in_timelines, 0, CheckableListItem.Style.CHECKBOX, GlobalUserPreferences.displayPronounsInTimelines, 0, ()->toggleCheckableItem(pronounsInTimelinesItem)),
showNavigationLabelsItem=new CheckableListItem<>(R.string.sk_settings_show_labels_in_navigation_bar, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showNavigationLabels, R.drawable.ic_fluent_tag_24_regular, i->toggleCheckableItem(showNavigationLabelsItem), true), pronounsInThreadsItem=new CheckableListItem<>(R.string.sk_settings_display_pronouns_in_threads, 0, CheckableListItem.Style.CHECKBOX, GlobalUserPreferences.displayPronounsInThreads, 0, ()->toggleCheckableItem(pronounsInThreadsItem)),
pronounsInTimelinesItem=new CheckableListItem<>(R.string.sk_settings_display_pronouns_in_timelines, 0, CheckableListItem.Style.CHECKBOX, GlobalUserPreferences.displayPronounsInTimelines, 0, i->toggleCheckableItem(pronounsInTimelinesItem)), pronounsInUserListingsItem=new CheckableListItem<>(R.string.sk_settings_display_pronouns_in_user_listings, 0, CheckableListItem.Style.CHECKBOX, GlobalUserPreferences.displayPronounsInUserListings, 0, ()->toggleCheckableItem(pronounsInUserListingsItem))
pronounsInThreadsItem=new CheckableListItem<>(R.string.sk_settings_display_pronouns_in_threads, 0, CheckableListItem.Style.CHECKBOX, GlobalUserPreferences.displayPronounsInThreads, 0, i->toggleCheckableItem(pronounsInThreadsItem)),
pronounsInUserListingsItem=new CheckableListItem<>(R.string.sk_settings_display_pronouns_in_user_listings, 0, CheckableListItem.Style.CHECKBOX, GlobalUserPreferences.displayPronounsInUserListings, 0, i->toggleCheckableItem(pronounsInUserListingsItem))
)); ));
trueBlackModeItem.checkedChangeListener=checked->onTrueBlackModeClick(); trueBlackModeItem.checkedChangeListener=checked->onTrueBlackModeClick();
} }
@@ -100,9 +94,9 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
protected void onHidden(){ protected void onHidden(){
super.onHidden(); super.onHidden();
boolean restartPlease=GlobalUserPreferences.disableM3PillActiveIndicator!=disablePillItem.checked boolean restartPlease=
|| GlobalUserPreferences.showNavigationLabels!=showNavigationLabelsItem.checked GlobalUserPreferences.disableM3PillActiveIndicator!=disablePillItem.checked ||
|| GlobalUserPreferences.likeIcon!=likeIconItem.checked; GlobalUserPreferences.showNavigationLabels!=showNavigationLabelsItem.checked;
lp.revealCWs=revealCWsItem.checked; lp.revealCWs=revealCWsItem.checked;
lp.hideSensitiveMedia=hideSensitiveMediaItem.checked; lp.hideSensitiveMedia=hideSensitiveMediaItem.checked;
@@ -118,8 +112,6 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
GlobalUserPreferences.spectatorMode=spectatorModeItem.checked; GlobalUserPreferences.spectatorMode=spectatorModeItem.checked;
GlobalUserPreferences.autoHideFab=hideFabItem.checked; GlobalUserPreferences.autoHideFab=hideFabItem.checked;
GlobalUserPreferences.translateButtonOpenedOnly=translateOpenedItem.checked; GlobalUserPreferences.translateButtonOpenedOnly=translateOpenedItem.checked;
GlobalUserPreferences.likeIcon=likeIconItem.checked;
GlobalUserPreferences.underlinedLinks=underlinedLinksItem.checked;
GlobalUserPreferences.disableM3PillActiveIndicator=disablePillItem.checked; GlobalUserPreferences.disableM3PillActiveIndicator=disablePillItem.checked;
GlobalUserPreferences.showNavigationLabels=showNavigationLabelsItem.checked; GlobalUserPreferences.showNavigationLabels=showNavigationLabelsItem.checked;
GlobalUserPreferences.displayPronounsInTimelines=pronounsInTimelinesItem.checked; GlobalUserPreferences.displayPronounsInTimelines=pronounsInTimelinesItem.checked;
@@ -138,11 +130,17 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
}; };
} }
private String getColorPaletteValue(){ private @StringRes int getColorPaletteValue(){
ColorPreference color=AccountSessionManager.get(accountID).getLocalPreferences().color; return switch(GlobalUserPreferences.color){
return color==null case MATERIAL3 -> R.string.sk_color_palette_material3;
? getString(R.string.sk_settings_color_palette_default, getString(GlobalUserPreferences.color.getName())) case PINK -> R.string.sk_color_palette_pink;
: getString(color.getName()); case PURPLE -> R.string.sk_color_palette_purple;
case GREEN -> R.string.sk_color_palette_green;
case BLUE -> R.string.sk_color_palette_blue;
case BROWN -> R.string.sk_color_palette_brown;
case RED -> R.string.sk_color_palette_red;
case YELLOW -> R.string.sk_color_palette_yellow;
};
} }
private String getPublishButtonText() { private String getPublishButtonText() {
@@ -166,7 +164,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
maybeApplyNewThemeRightNow(null, null, prev); maybeApplyNewThemeRightNow(null, null, prev);
} }
private void onAppearanceClick(ListItem<?> item_){ private void onAppearanceClick(){
int selected=switch(GlobalUserPreferences.theme){ int selected=switch(GlobalUserPreferences.theme){
case LIGHT -> 0; case LIGHT -> 0;
case DARK -> 1; case DARK -> 1;
@@ -197,44 +195,30 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
.show(); .show();
} }
private void onColorClick(ListItem<?> item_){ private void onColorClick(){
boolean multiple=AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1; int selected=GlobalUserPreferences.color.ordinal();
int indexOffset=multiple ? 1 : 0;
int selected=lp.color==null ? 0 : lp.color.ordinal() + indexOffset;
int[] newSelected={selected}; int[] newSelected={selected};
List<String> items=Arrays.stream(ColorPreference.values()).map(ColorPreference::getName).map(this::getString).collect(Collectors.toList()); String[] names=Arrays.stream(GlobalUserPreferences.ColorPreference.values()).map(GlobalUserPreferences.ColorPreference::getName).map(this::getString).toArray(String[]::new);
if(multiple) new M3AlertDialogBuilder(getActivity())
items.add(0, getString(R.string.sk_settings_color_palette_default, items.get(GlobalUserPreferences.color.ordinal()))); .setTitle(R.string.settings_theme)
.setSingleChoiceItems(names,
Consumer<Boolean> save=(asDefault)->{ selected, (dlg, item)->newSelected[0]=item)
boolean defaultSelected=multiple && newSelected[0]==0; .setPositiveButton(R.string.ok, (dlg, item)->{
ColorPreference pref=defaultSelected ? null : ColorPreference.values()[newSelected[0]-indexOffset]; GlobalUserPreferences.ColorPreference pref=GlobalUserPreferences.ColorPreference.values()[newSelected[0]];
if(pref!=lp.color){ if(pref!=GlobalUserPreferences.color){
ColorPreference prev=lp.color; GlobalUserPreferences.ColorPreference prev=GlobalUserPreferences.color;
lp.color=asDefault ? null : pref;
lp.save();
if((asDefault || !multiple) && pref!=null){
GlobalUserPreferences.color=pref; GlobalUserPreferences.color=pref;
GlobalUserPreferences.save(); GlobalUserPreferences.save();
} colorItem.subtitleRes=getColorPaletteValue();
colorItem.subtitle=getColorPaletteValue();
rebindItem(colorItem); rebindItem(colorItem);
if(prev==null && pref!=null) restartActivityToApplyNewTheme(); maybeApplyNewThemeRightNow(null, prev, null);
else maybeApplyNewThemeRightNow(null, prev, null);
} }
}; })
.setNegativeButton(R.string.cancel, null)
AlertDialog.Builder alert=new M3AlertDialogBuilder(getActivity()) .show();
.setTitle(R.string.sk_settings_color_palette)
.setSingleChoiceItems(items.stream().toArray(String[]::new),
selected, (dlg, item)->newSelected[0]=item)
.setPositiveButton(R.string.ok, (dlg, item)->save.accept(false))
.setNegativeButton(R.string.cancel, null);
if(multiple) alert.setNeutralButton(R.string.sk_set_as_default, (dlg, item)->save.accept(true));
alert.show();
} }
private void onPublishTextClick(ListItem<?> item_){ private void onPublishTextClick(){
TextInputFrameLayout input = new TextInputFrameLayout( TextInputFrameLayout input = new TextInputFrameLayout(
getContext(), getContext(),
getString(R.string.publish), getString(R.string.publish),
@@ -257,7 +241,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
.show(); .show();
} }
private void onAutoRevealSpoilersClick(ListItem<?> item_){ private void onAutoRevealSpoilersClick(){
int selected=GlobalUserPreferences.autoRevealEqualSpoilers.ordinal(); int selected=GlobalUserPreferences.autoRevealEqualSpoilers.ordinal();
int[] newSelected={selected}; int[] newSelected={selected};
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())
@@ -273,17 +257,17 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
.show(); .show();
} }
private void maybeApplyNewThemeRightNow(GlobalUserPreferences.ThemePreference prevTheme, ColorPreference prevColor, Boolean prevTrueBlack){ private void maybeApplyNewThemeRightNow(GlobalUserPreferences.ThemePreference prevTheme, GlobalUserPreferences.ColorPreference prevColor, Boolean prevTrueBlack){
if(prevTheme==null) prevTheme=GlobalUserPreferences.theme; if(prevTheme==null) prevTheme=GlobalUserPreferences.theme;
if(prevTrueBlack==null) prevTrueBlack=GlobalUserPreferences.trueBlackTheme; if(prevTrueBlack==null) prevTrueBlack=GlobalUserPreferences.trueBlackTheme;
if(prevColor==null) prevColor=lp.getCurrentColor(); if(prevColor==null) prevColor=GlobalUserPreferences.color;
boolean isCurrentDark=prevTheme==GlobalUserPreferences.ThemePreference.DARK || boolean isCurrentDark=prevTheme==GlobalUserPreferences.ThemePreference.DARK ||
(prevTheme==GlobalUserPreferences.ThemePreference.AUTO && Build.VERSION.SDK_INT>=30 && getResources().getConfiguration().isNightModeActive()); (prevTheme==GlobalUserPreferences.ThemePreference.AUTO && Build.VERSION.SDK_INT>=30 && getResources().getConfiguration().isNightModeActive());
boolean isNewDark=GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.DARK || boolean isNewDark=GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.DARK ||
(GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.AUTO && Build.VERSION.SDK_INT>=30 && getResources().getConfiguration().isNightModeActive()); (GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.AUTO && Build.VERSION.SDK_INT>=30 && getResources().getConfiguration().isNightModeActive());
boolean isNewBlack=GlobalUserPreferences.trueBlackTheme; boolean isNewBlack=GlobalUserPreferences.trueBlackTheme;
if(isCurrentDark!=isNewDark || prevColor!=lp.getCurrentColor() || (isNewDark && prevTrueBlack!=isNewBlack)){ if(isCurrentDark!=isNewDark || prevColor!=GlobalUserPreferences.color || (isNewDark && prevTrueBlack!=isNewBlack)){
restartActivityToApplyNewTheme(); restartActivityToApplyNewTheme();
} }
} }

View File

@@ -55,7 +55,7 @@ public class SettingsFiltersFragment extends BaseSettingsFragment<Filter>{
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter(); MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
adapter.addAdapter(super.getAdapter()); adapter.addAdapter(super.getAdapter());
adapter.addAdapter(new GenericListItemsAdapter<>(Collections.singletonList( adapter.addAdapter(new GenericListItemsAdapter<>(Collections.singletonList(
new ListItem<Void>(R.string.settings_add_filter, 0, R.drawable.ic_fluent_add_24_regular, this::onAddFilterClick) new ListItem<Void>(R.string.settings_add_filter, 0, R.drawable.ic_add_24px, this::onAddFilterClick)
))); )));
return adapter; return adapter;
} }
@@ -67,14 +67,16 @@ public class SettingsFiltersFragment extends BaseSettingsFragment<Filter>{
Nav.go(getActivity(), EditFilterFragment.class, args); Nav.go(getActivity(), EditFilterFragment.class, args);
} }
private void onAddFilterClick(ListItem<?> item){ private void onAddFilterClick(){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
Nav.go(getActivity(), EditFilterFragment.class, args); Nav.go(getActivity(), EditFilterFragment.class, args);
} }
private ListItem<Filter> makeListItem(Filter f){ private ListItem<Filter> makeListItem(Filter f){
ListItem<Filter> item=new ListItem<>(f.title, getString(f.isActive() ? R.string.filter_active : R.string.filter_inactive), this::onFilterClick, f); ListItem<Filter> item=new ListItem<>(f.title, getString(f.isActive() ? R.string.filter_active : R.string.filter_inactive), null, f);
item.onClick=()->onFilterClick(item);
item.isEnabled=true;
return item; return item;
} }

View File

@@ -19,7 +19,6 @@ import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
@@ -37,15 +36,15 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
lp=s.getLocalPreferences(); lp=s.getLocalPreferences();
onDataLoaded(List.of( onDataLoaded(List.of(
new ListItem<>(AccountSessionManager.get(accountID).domain, getString(R.string.settings_server_explanation), R.drawable.ic_fluent_server_24_regular, this::onServerClick), new ListItem<>(AccountSessionManager.get(accountID).domain, getString(R.string.settings_server_explanation), R.drawable.ic_fluent_server_24_regular, this::onServerClick),
new ListItem<>(R.string.sk_settings_profile, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/settings/profile")), new ListItem<>(R.string.sk_settings_profile, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/settings/profile")),
new ListItem<>(R.string.sk_settings_posting, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/settings/preferences/other")), new ListItem<>(R.string.sk_settings_posting, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/settings/preferences/other")),
new ListItem<>(R.string.sk_settings_auth, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/auth/edit"), 0, true), new ListItem<>(R.string.sk_settings_auth, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/auth/edit"), 0, true),
contentTypesItem=new CheckableListItem<>(R.string.sk_settings_content_types, R.string.sk_settings_content_types_explanation, CheckableListItem.Style.SWITCH, lp.contentTypesEnabled, R.drawable.ic_fluent_text_edit_style_24_regular, i->onContentTypeClick()), contentTypesItem=new CheckableListItem<>(R.string.sk_settings_content_types, R.string.sk_settings_content_types_explanation, CheckableListItem.Style.SWITCH, lp.contentTypesEnabled, R.drawable.ic_fluent_text_edit_style_24_regular, this::onContentTypeClick),
defaultContentTypeItem=new ListItem<>(R.string.sk_settings_default_content_type, lp.defaultContentType.getName(), R.drawable.ic_fluent_text_bold_24_regular, this::onDefaultContentTypeClick, 0, true), defaultContentTypeItem=new ListItem<>(R.string.sk_settings_default_content_type, lp.defaultContentType.getName(), R.drawable.ic_fluent_text_bold_24_regular, this::onDefaultContentTypeClick, 0, true),
emojiReactionsItem=new CheckableListItem<>(R.string.sk_settings_emoji_reactions, R.string.sk_settings_emoji_reactions_explanation, CheckableListItem.Style.SWITCH, lp.emojiReactionsEnabled, R.drawable.ic_fluent_emoji_laugh_24_regular, i->onEmojiReactionsClick()), emojiReactionsItem=new CheckableListItem<>(R.string.sk_settings_emoji_reactions, R.string.sk_settings_emoji_reactions_explanation, CheckableListItem.Style.SWITCH, lp.emojiReactionsEnabled, R.drawable.ic_fluent_emoji_laugh_24_regular, this::onEmojiReactionsClick),
showEmojiReactionsItem=new ListItem<>(R.string.sk_settings_show_emoji_reactions, getShowEmojiReactionsString(), R.drawable.ic_fluent_emoji_24_regular, this::onShowEmojiReactionsClick, 0, true), showEmojiReactionsItem=new ListItem<>(R.string.sk_settings_show_emoji_reactions, getShowEmojiReactionsString(), R.drawable.ic_fluent_emoji_24_regular, this::onShowEmojiReactionsClick, 0, true),
localOnlyItem=new CheckableListItem<>(R.string.sk_settings_support_local_only, R.string.sk_settings_local_only_explanation, CheckableListItem.Style.SWITCH, lp.localOnlySupported, R.drawable.ic_fluent_eye_24_regular, i->onLocalOnlyClick()), localOnlyItem=new CheckableListItem<>(R.string.sk_settings_support_local_only, R.string.sk_settings_local_only_explanation, CheckableListItem.Style.SWITCH, lp.localOnlySupported, R.drawable.ic_fluent_eye_24_regular, this::onLocalOnlyClick),
glitchModeItem=new CheckableListItem<>(R.string.sk_settings_glitch_instance, R.string.sk_settings_glitch_mode_explanation, CheckableListItem.Style.SWITCH, lp.glitchInstance, R.drawable.ic_fluent_eye_24_filled, i->toggleCheckableItem(glitchModeItem)) glitchModeItem=new CheckableListItem<>(R.string.sk_settings_glitch_instance, R.string.sk_settings_glitch_mode_explanation, CheckableListItem.Style.SWITCH, lp.glitchInstance, R.drawable.ic_fluent_eye_24_filled, ()->toggleCheckableItem(glitchModeItem))
)); ));
contentTypesItem.checkedChangeListener=checked->onContentTypeClick(); contentTypesItem.checkedChangeListener=checked->onContentTypeClick();
defaultContentTypeItem.isEnabled=contentTypesItem.checked; defaultContentTypeItem.isEnabled=contentTypesItem.checked;
@@ -69,7 +68,7 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
E.post(new StatusDisplaySettingsChangedEvent(accountID)); E.post(new StatusDisplaySettingsChangedEvent(accountID));
} }
private void onServerClick(ListItem<?> item){ private void onServerClick(){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
Nav.go(getActivity(), SettingsServerFragment.class, args); Nav.go(getActivity(), SettingsServerFragment.class, args);
@@ -88,23 +87,23 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
defaultContentTypeItem.subtitleRes=lp.defaultContentType.getName(); defaultContentTypeItem.subtitleRes=lp.defaultContentType.getName();
} }
private void onDefaultContentTypeClick(ListItem<?> item_){ private void onDefaultContentTypeClick(){
List<ContentType> supportedContentTypes=Arrays.stream(ContentType.values()) int selected=lp.defaultContentType.ordinal();
.filter(t->t.supportedByInstance(getInstance().orElse(null)))
.collect(Collectors.toList());
int selected=supportedContentTypes.indexOf(lp.defaultContentType);
int[] newSelected={selected}; int[] newSelected={selected};
String[] names=supportedContentTypes.stream() ContentType[] supportedContentTypes=Arrays.stream(ContentType.values())
.filter(t->t.supportedByInstance(getInstance().orElse(null)))
.toArray(ContentType[]::new);
String[] names=Arrays.stream(supportedContentTypes)
.map(ContentType::getName) .map(ContentType::getName)
.map(this::getString) .map(this::getString)
.toArray(String[]::new); .toArray(String[]::new);
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_settings_default_content_type) .setTitle(R.string.settings_theme)
.setSingleChoiceItems(names, .setSingleChoiceItems(names,
selected, (dlg, item)->newSelected[0]=item) selected, (dlg, item)->newSelected[0]=item)
.setPositiveButton(R.string.ok, (dlg, item)->{ .setPositiveButton(R.string.ok, (dlg, item)->{
ContentType type=supportedContentTypes.get(newSelected[0]); ContentType type=supportedContentTypes[newSelected[0]];
lp.defaultContentType=type; lp.defaultContentType=type;
defaultContentTypeItem.subtitleRes=type.getName(); defaultContentTypeItem.subtitleRes=type.getName();
rebindItem(defaultContentTypeItem); rebindItem(defaultContentTypeItem);
@@ -113,7 +112,7 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
.show(); .show();
} }
private void onShowEmojiReactionsClick(ListItem<?> item_){ private void onShowEmojiReactionsClick(){
int selected=lp.showEmojiReactions.ordinal(); int selected=lp.showEmojiReactions.ordinal();
int[] newSelected={selected}; int[] newSelected={selected};
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())

View File

@@ -18,7 +18,6 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent; import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.viewmodel.ListItem; import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.AccountSwitcherSheet;
import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter; import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
@@ -56,26 +55,21 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
onDataLoaded(List.of( onDataLoaded(List.of(
new ListItem<>(R.string.settings_behavior, 0, R.drawable.ic_fluent_settings_24_regular, this::onBehaviorClick), new ListItem<>(R.string.settings_behavior, 0, R.drawable.ic_fluent_settings_24_regular, this::onBehaviorClick),
new ListItem<>(R.string.settings_display, 0, R.drawable.ic_fluent_color_24_regular, this::onDisplayClick), new ListItem<>(R.string.settings_display, 0, R.drawable.ic_fluent_color_24_regular, this::onDisplayClick),
new ListItem<>(R.string.settings_privacy, 0, R.drawable.ic_fluent_shield_24_regular, this::onPrivacyClick),
new ListItem<>(R.string.settings_notifications, 0, R.drawable.ic_fluent_alert_24_regular, this::onNotificationsClick), new ListItem<>(R.string.settings_notifications, 0, R.drawable.ic_fluent_alert_24_regular, this::onNotificationsClick),
new ListItem<>(R.string.sk_settings_instance, 0, R.drawable.ic_fluent_server_24_regular, this::onInstanceClick), new ListItem<>(R.string.sk_settings_instance, 0, R.drawable.ic_fluent_server_24_regular, this::onInstanceClick),
new ListItem<>(getString(R.string.about_app, getString(R.string.sk_app_name)), null, R.drawable.ic_fluent_info_24_regular, this::onAboutClick, null, 0, true), new ListItem<>(getString(R.string.about_app, getString(R.string.sk_app_name)), null, R.drawable.ic_fluent_info_24_regular, this::onAboutClick, null, 0, true),
new ListItem<>(R.string.manage_accounts, 0, R.drawable.ic_fluent_person_swap_24_regular, this::onManageAccountsClick),
new ListItem<>(R.string.log_out, 0, R.drawable.ic_fluent_sign_out_24_regular, this::onLogOutClick, R.attr.colorM3Error, false) new ListItem<>(R.string.log_out, 0, R.drawable.ic_fluent_sign_out_24_regular, this::onLogOutClick, R.attr.colorM3Error, false)
)); ));
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(account.domain); Instance instance = AccountSessionManager.getInstance().getInstanceInfo(account.domain);
if(!instance.isAkkoma()){ if (!instance.isAkkoma())
data.add(3, new ListItem<>(R.string.settings_filters, 0, R.drawable.ic_fluent_filter_24_regular, this::onFiltersClick)); data.add(2, new ListItem<>(R.string.settings_filters, 0, R.drawable.ic_fluent_filter_24_regular, this::onFiltersClick));
}
if(BuildConfig.DEBUG || BuildConfig.BUILD_TYPE.equals("appcenterPrivateBeta")){ if(BuildConfig.DEBUG || BuildConfig.BUILD_TYPE.equals("appcenterPrivateBeta")){
data.add(0, new ListItem<>("Debug settings", null, R.drawable.ic_fluent_wrench_screwdriver_24_regular, i->Nav.go(getActivity(), SettingsDebugFragment.class, makeFragmentArgs()), null, 0, true)); data.add(0, new ListItem<>("Debug settings", null, R.drawable.ic_fluent_wrench_screwdriver_24_regular, ()->Nav.go(getActivity(), SettingsDebugFragment.class, makeFragmentArgs()), null, 0, true));
} }
AccountSession session=AccountSessionManager.get(accountID); account.reloadPreferences(null);
session.reloadPreferences(null);
session.updateAccountInfo();
E.register(this); E.register(this);
} }
@@ -131,45 +125,39 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
return args; return args;
} }
private void onBehaviorClick(ListItem<?> item_){ private void onBehaviorClick(){
Nav.go(getActivity(), SettingsBehaviorFragment.class, makeFragmentArgs()); Nav.go(getActivity(), SettingsBehaviorFragment.class, makeFragmentArgs());
} }
private void onDisplayClick(ListItem<?> item_){ private void onDisplayClick(){
Nav.go(getActivity(), SettingsDisplayFragment.class, makeFragmentArgs()); Nav.go(getActivity(), SettingsDisplayFragment.class, makeFragmentArgs());
} }
private void onPrivacyClick(ListItem<?> item_){ private void onFiltersClick(){
Nav.go(getActivity(), SettingsPrivacyFragment.class, makeFragmentArgs());
}
private void onFiltersClick(ListItem<?> item_){
Nav.go(getActivity(), SettingsFiltersFragment.class, makeFragmentArgs()); Nav.go(getActivity(), SettingsFiltersFragment.class, makeFragmentArgs());
} }
private void onNotificationsClick(ListItem<?> item_){ private void onNotificationsClick(){
Nav.go(getActivity(), SettingsNotificationsFragment.class, makeFragmentArgs()); Nav.go(getActivity(), SettingsNotificationsFragment.class, makeFragmentArgs());
} }
private void onInstanceClick(ListItem<?> item_){ private void onInstanceClick(){
Nav.go(getActivity(), SettingsInstanceFragment.class, makeFragmentArgs()); Nav.go(getActivity(), SettingsInstanceFragment.class, makeFragmentArgs());
} }
private void onAboutClick(ListItem<?> item_){ private void onAboutClick(){
Nav.go(getActivity(), SettingsAboutAppFragment.class, makeFragmentArgs()); Nav.go(getActivity(), SettingsAboutAppFragment.class, makeFragmentArgs());
} }
private void onManageAccountsClick(ListItem<?> item){ private void onLogOutClick(){
new AccountSwitcherSheet(getActivity(), null).show();
}
private void onLogOutClick(ListItem<?> item_){
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID); AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())
.setMessage(getString(R.string.confirm_log_out, session.getFullUsername())) .setMessage(getString(R.string.confirm_log_out, session.getFullUsername()))
.setPositiveButton(R.string.log_out, (dialog, which)->account.logOut(getActivity(), ()->{ .setPositiveButton(R.string.log_out, (dialog, which)->account.logOut(getActivity(), ()->{
loggedOut=true; loggedOut=true;
((MainActivity)getActivity()).restartActivity(); getActivity().finish();
Intent intent=new Intent(getActivity(), MainActivity.class);
startActivity(intent);
})) }))
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show(); .show();

View File

@@ -27,6 +27,7 @@ import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter; import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.unifiedpush.android.connector.RegistrationDialogContent;
import org.unifiedpush.android.connector.UnifiedPush; import org.unifiedpush.android.connector.UnifiedPush;
import java.time.Instant; import java.time.Instant;
@@ -72,21 +73,21 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
useUnifiedPush=!getDistributor(getContext()).isEmpty(); useUnifiedPush=!getDistributor(getContext()).isEmpty();
onDataLoaded(List.of( onDataLoaded(List.of(
pauseItem=new CheckableListItem<>(getString(R.string.pause_all_notifications), getPauseItemSubtitle(), CheckableListItem.Style.SWITCH, false, R.drawable.ic_fluent_alert_snooze_24_regular, i->onPauseNotificationsClick(false)), pauseItem=new CheckableListItem<>(getString(R.string.pause_all_notifications), getPauseItemSubtitle(), CheckableListItem.Style.SWITCH, false, R.drawable.ic_fluent_alert_snooze_24_regular, ()->onPauseNotificationsClick(false)),
policyItem=new ListItem<>(R.string.settings_notifications_policy, 0, R.drawable.ic_fluent_people_24_regular, this::onNotificationsPolicyClick, 0, true), policyItem=new ListItem<>(R.string.settings_notifications_policy, 0, R.drawable.ic_fluent_people_24_regular, this::onNotificationsPolicyClick, 0, true),
mentionsItem=new CheckableListItem<>(R.string.notification_type_mentions_and_replies, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.mention, R.drawable.ic_fluent_mention_24_regular, i->toggleCheckableItem(mentionsItem)), mentionsItem=new CheckableListItem<>(R.string.notification_type_mentions_and_replies, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.mention, R.drawable.ic_fluent_mention_24_regular, ()->toggleCheckableItem(mentionsItem)),
boostsItem=new CheckableListItem<>(R.string.notification_type_reblog, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.reblog, R.drawable.ic_fluent_arrow_repeat_all_24_regular, i->toggleCheckableItem(boostsItem)), boostsItem=new CheckableListItem<>(R.string.notification_type_reblog, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.reblog, R.drawable.ic_fluent_arrow_repeat_all_24_regular, ()->toggleCheckableItem(boostsItem)),
favoritesItem=new CheckableListItem<>(R.string.notification_type_favorite, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.favourite, R.drawable.ic_fluent_star_24_regular, i->toggleCheckableItem(favoritesItem)), favoritesItem=new CheckableListItem<>(R.string.notification_type_favorite, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.favourite, R.drawable.ic_fluent_star_24_regular, ()->toggleCheckableItem(favoritesItem)),
followersItem=new CheckableListItem<>(R.string.notification_type_follow, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.follow, R.drawable.ic_fluent_person_add_24_regular, i->toggleCheckableItem(followersItem)), followersItem=new CheckableListItem<>(R.string.notification_type_follow, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.follow, R.drawable.ic_fluent_person_add_24_regular, ()->toggleCheckableItem(followersItem)),
pollsItem=new CheckableListItem<>(R.string.notification_type_poll, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.poll, R.drawable.ic_fluent_poll_24_regular, i->toggleCheckableItem(pollsItem)), pollsItem=new CheckableListItem<>(R.string.notification_type_poll, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.poll, R.drawable.ic_fluent_poll_24_regular, ()->toggleCheckableItem(pollsItem)),
updateItem=new CheckableListItem<>(R.string.sk_notification_type_update, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.update, R.drawable.ic_fluent_history_24_regular, i->toggleCheckableItem(updateItem)), updateItem=new CheckableListItem<>(R.string.sk_notification_type_update, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.update, R.drawable.ic_fluent_history_24_regular, ()->toggleCheckableItem(updateItem)),
postsItem=new CheckableListItem<>(R.string.sk_notification_type_posts, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.status, R.drawable.ic_fluent_chat_24_regular, i->toggleCheckableItem(postsItem), true), postsItem=new CheckableListItem<>(R.string.sk_notification_type_posts, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.status, R.drawable.ic_fluent_chat_24_regular, ()->toggleCheckableItem(postsItem), true),
uniformIconItem=new CheckableListItem<>(R.string.sk_settings_uniform_icon_for_notifications, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.uniformNotificationIcon, R.drawable.ic_ntf_logo, i->toggleCheckableItem(uniformIconItem)), uniformIconItem=new CheckableListItem<>(R.string.sk_settings_uniform_icon_for_notifications, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.uniformNotificationIcon, R.drawable.ic_ntf_logo, ()->toggleCheckableItem(uniformIconItem)),
deleteItem=new CheckableListItem<>(R.string.sk_settings_enable_delete_notifications, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.enableDeleteNotifications, R.drawable.ic_fluent_mail_inbox_dismiss_24_regular, i->toggleCheckableItem(deleteItem)), deleteItem=new CheckableListItem<>(R.string.sk_settings_enable_delete_notifications, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.enableDeleteNotifications, R.drawable.ic_fluent_mail_inbox_dismiss_24_regular, ()->toggleCheckableItem(deleteItem)),
onlyLatestItem=new CheckableListItem<>(R.string.sk_settings_single_notification, 0, CheckableListItem.Style.SWITCH, lp.keepOnlyLatestNotification, R.drawable.ic_fluent_convert_range_24_regular, i->toggleCheckableItem(onlyLatestItem), true), onlyLatestItem=new CheckableListItem<>(R.string.sk_settings_single_notification, 0, CheckableListItem.Style.SWITCH, lp.keepOnlyLatestNotification, R.drawable.ic_fluent_convert_range_24_regular, ()->toggleCheckableItem(onlyLatestItem), true),
unifiedPushItem=new CheckableListItem<>(R.string.sk_settings_unifiedpush, 0, CheckableListItem.Style.SWITCH, useUnifiedPush, R.drawable.ic_fluent_alert_arrow_up_24_regular, i->onUnifiedPushClick(), true) unifiedPushItem=new CheckableListItem<>(R.string.sk_settings_unifiedpush, 0, CheckableListItem.Style.SWITCH, useUnifiedPush, R.drawable.ic_fluent_alert_arrow_up_24_regular, this::onUnifiedPush, true)
)); ));
//only enable when distributors, who can receive notifications, are available //only enable when distributors, who can receive notifications, are available
@@ -97,7 +98,7 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
typeItems=List.of(mentionsItem, boostsItem, favoritesItem, followersItem, pollsItem, updateItem, postsItem); typeItems=List.of(mentionsItem, boostsItem, favoritesItem, followersItem, pollsItem, updateItem, postsItem);
pauseItem.checkedChangeListener=checked->onPauseNotificationsClick(true); pauseItem.checkedChangeListener=checked->onPauseNotificationsClick(true);
unifiedPushItem.checkedChangeListener=checked->onUnifiedPushClick(); unifiedPushItem.checkedChangeListener=checked->onUnifiedPush();
updatePolicyItem(null); updatePolicyItem(null);
updatePauseItem(); updatePauseItem();
} }
@@ -253,7 +254,7 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
alert.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); alert.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
} }
private void onNotificationsPolicyClick(ListItem<?> item_){ private void onNotificationsPolicyClick(){
String[] items=Stream.of( String[] items=Stream.of(
R.string.notifications_policy_anyone, R.string.notifications_policy_anyone,
R.string.notifications_policy_followed, R.string.notifications_policy_followed,
@@ -327,22 +328,20 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
} }
} }
private void onUnifiedPushClick(){ private void onUnifiedPush(){
if(getDistributor(getContext()).isEmpty()){ if(getDistributor(getContext()).isEmpty()){
List<String> distributors = UnifiedPush.getDistributors(getContext(), new ArrayList<>()); List<String> distributors = UnifiedPush.getDistributors(getContext(), new ArrayList<>());
showUnifiedPushRegisterDialog(distributors); showUnifiedPushRegisterDialog(distributors);
return; return;
} }
for (AccountSession accountSession : AccountSessionManager.getInstance().getLoggedInAccounts()) {
UnifiedPush.unregisterApp( UnifiedPush.unregisterApp(
getContext(), getContext(),
accountSession.getID() accountID
); );
//re-register to fcm //re-register to fcm
accountSession.getPushSubscriptionManager().registerAccountForPush(getPushSubscription()); AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().registerAccountForPush(getPushSubscription());
}
unifiedPushItem.toggle(); unifiedPushItem.toggle();
rebindItem(unifiedPushItem); rebindItem(unifiedPushItem);
} }
@@ -352,14 +351,12 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
(dialog, which)->{ (dialog, which)->{
String userDistrib = distributors.get(which); String userDistrib = distributors.get(which);
UnifiedPush.saveDistributor(getContext(), userDistrib); UnifiedPush.saveDistributor(getContext(), userDistrib);
for (AccountSession accountSession : AccountSessionManager.getInstance().getLoggedInAccounts()){
UnifiedPush.registerApp( UnifiedPush.registerApp(
getContext(), getContext(),
accountSession.getID(), accountID,
new ArrayList<>(), new ArrayList<>(),
getContext().getPackageName() getContext().getPackageName()
); );
}
unifiedPushItem.toggle(); unifiedPushItem.toggle();
rebindItem(unifiedPushItem); rebindItem(unifiedPushItem);
}).setOnCancelListener(d->rebindItem(unifiedPushItem)).show(); }).setOnCancelListener(d->rebindItem(unifiedPushItem)).show();

View File

@@ -1,99 +0,0 @@
package org.joinmastodon.android.fragments.settings;
import android.os.Bundle;
import androidx.annotation.StringRes;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import java.util.ArrayList;
import java.util.List;
public class SettingsPrivacyFragment extends BaseSettingsFragment<Void>{
private CheckableListItem<Void> discoverableItem, indexableItem, lockedItem;
private ListItem<Void> privacyItem;
private StatusPrivacy privacy=null;
private Instance instance;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setTitle(R.string.settings_privacy);
AccountSession session=AccountSessionManager.get(accountID);
Account self=session.self;
instance=AccountSessionManager.getInstance().getInstanceInfo(session.domain);
privacy=self.source.privacy;
onDataLoaded(List.of(
privacyItem=new ListItem<>(R.string.sk_settings_default_visibility, getPrivacyString(privacy), R.drawable.ic_fluent_eye_24_regular, this::onPrivacyClick, 0, true),
lockedItem=new CheckableListItem<>(R.string.sk_settings_lock_account, 0, CheckableListItem.Style.SWITCH, self.locked, R.drawable.ic_fluent_person_available_24_regular, i->toggleCheckableItem(lockedItem))
));
if(!instance.isAkkoma()){
data.addAll(List.of(
discoverableItem=new CheckableListItem<>(R.string.settings_discoverable, 0, CheckableListItem.Style.SWITCH, self.discoverable, R.drawable.ic_fluent_thumb_like_dislike_24_regular, i->toggleCheckableItem(discoverableItem)),
indexableItem=new CheckableListItem<>(R.string.settings_indexable, 0, CheckableListItem.Style.SWITCH, self.source.indexable!=null ? self.source.indexable : true, R.drawable.ic_fluent_search_24_regular, i->toggleCheckableItem(indexableItem))
));
if(self.source.indexable==null)
indexableItem.isEnabled=false;
}
}
@Override
protected void doLoadData(int offset, int count){}
private @StringRes int getPrivacyString(StatusPrivacy p){
if(p==null) return R.string.visibility_public;
return switch(p){
case PUBLIC -> R.string.visibility_public;
case UNLISTED -> R.string.sk_visibility_unlisted;
case PRIVATE -> R.string.visibility_followers_only;
case DIRECT -> R.string.visibility_private;
case LOCAL -> R.string.sk_local_only;
};
}
private void onPrivacyClick(ListItem<?> item_){
Account self=AccountSessionManager.get(accountID).self;
List<StatusPrivacy> options=new ArrayList<>(List.of(StatusPrivacy.PUBLIC, StatusPrivacy.UNLISTED, StatusPrivacy.PRIVATE, StatusPrivacy.DIRECT));
if(instance.isAkkoma()) options.add(StatusPrivacy.LOCAL);
int selected=options.indexOf(self.source.privacy);
int[] newSelected={selected};
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_settings_default_visibility)
.setSingleChoiceItems(options.stream().map(this::getPrivacyString).map(this::getString).toArray(String[]::new),
selected, (dlg, item)->newSelected[0]=item)
.setPositiveButton(R.string.ok, (dlg, item)->{
privacy=options.get(newSelected[0]);
privacyItem.subtitleRes=getPrivacyString(privacy);
rebindItem(privacyItem);
})
.setNegativeButton(R.string.cancel, null)
.show();
}
@Override
public void onPause(){
super.onPause();
AccountSession s=AccountSessionManager.get(accountID);
Account self=s.self;
boolean savePlease=self.locked!=lockedItem.checked
|| self.source.privacy!=privacy
|| (discoverableItem!=null && self.discoverable!=discoverableItem.checked)
|| (indexableItem!=null && self.source.indexable!=null && self.source.indexable!=indexableItem.checked);
if(savePlease){
if(discoverableItem!=null) self.discoverable=discoverableItem.checked;
if(indexableItem!=null) self.source.indexable=indexableItem.checked;
self.locked=lockedItem.checked;
s.preferences.postingDefaultVisibility=privacy;
s.savePreferencesLater();
}
}
}

View File

@@ -145,7 +145,7 @@ public class SettingsServerAboutFragment extends LoaderFragment{
if(!TextUtils.isEmpty(instance.email)){ if(!TextUtils.isEmpty(instance.email)){
needDivider=true; needDivider=true;
SimpleListItemViewHolder holder=new SimpleListItemViewHolder(getActivity(), scrollingLayout); SimpleListItemViewHolder holder=new SimpleListItemViewHolder(getActivity(), scrollingLayout);
ListItem<Void> item=new ListItem<>(R.string.send_email_to_server_admin, 0, R.drawable.ic_fluent_mail_24_regular, i->{}); ListItem<Void> item=new ListItem<>(R.string.send_email_to_server_admin, 0, R.drawable.ic_fluent_mail_24_regular, ()->{});
holder.bind(item); holder.bind(item);
holder.itemView.setBackground(UiUtils.getThemeDrawable(getActivity(), android.R.attr.selectableItemBackground)); holder.itemView.setBackground(UiUtils.getThemeDrawable(getActivity(), android.R.attr.selectableItemBackground));
holder.itemView.setOnClickListener(v->openAdminEmail()); holder.itemView.setOnClickListener(v->openAdminEmail());

View File

@@ -153,21 +153,18 @@ public class SettingsServerFragment extends AppKitFragment{
@NonNull @NonNull
@Override @Override
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
FrameLayout view=new FrameLayout(parent.getContext()); FrameLayout view=tabViews[viewType];
((ViewGroup)view.getParent()).removeView(view);
view.setVisibility(View.VISIBLE);
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return new SimpleViewHolder(view); return new SimpleViewHolder(view);
} }
@Override @Override
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){ public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
FrameLayout view=tabViews[position];
if(view.getParent() instanceof ViewGroup parent)
parent.removeView(view);
view.setVisibility(View.VISIBLE);
((FrameLayout)holder.itemView).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
Fragment fragment=getFragmentForPage(position); Fragment fragment=getFragmentForPage(position);
if(!fragment.isAdded()){ if(!fragment.isAdded()){
getChildFragmentManager().beginTransaction().add(view.getId(), fragment).commit(); getChildFragmentManager().beginTransaction().add(holder.itemView.getId(), fragment).commit();
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){ holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@Override @Override
public boolean onPreDraw(){ public boolean onPreDraw(){

View File

@@ -6,11 +6,11 @@ import android.text.TextUtils;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
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.time.LocalDate; import java.time.LocalDate;
import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
@@ -23,22 +23,22 @@ public class Account extends BaseModel implements Searchable{
/** /**
* The account id * The account id
*/ */
// @RequiredField @RequiredField
public String id; public String id;
/** /**
* The username of the account, not including domain. * The username of the account, not including domain.
*/ */
// @RequiredField @RequiredField
public String username; public String username;
/** /**
* The Webfinger account URI. Equal to username for local users, or username@domain for remote users. * The Webfinger account URI. Equal to username for local users, or username@domain for remote users.
*/ */
// @RequiredField @RequiredField
public String acct; public String acct;
/** /**
* The location of the user's profile page. * The location of the user's profile page.
*/ */
// @RequiredField @RequiredField
public String url; public String url;
// Display attributes // Display attributes
@@ -51,12 +51,12 @@ public class Account extends BaseModel implements Searchable{
/** /**
* The profile's bio / description. * The profile's bio / description.
*/ */
// @RequiredField @RequiredField
public String note; public String note;
/** /**
* An image icon that is shown next to statuses and in the profile. * An image icon that is shown next to statuses and in the profile.
*/ */
// @RequiredField @RequiredField
public String avatar; public String avatar;
/** /**
* A static version of the avatar. Equal to avatar if its value is a static image; different if avatar is an animated GIF. * A static version of the avatar. Equal to avatar if its value is a static image; different if avatar is an animated GIF.
@@ -134,7 +134,6 @@ public class Account extends BaseModel implements Searchable{
* When a timed mute will expire, if applicable. * When a timed mute will expire, if applicable.
*/ */
public Instant muteExpiresAt; public Instant muteExpiresAt;
public boolean noindex;
public List<Role> roles; public List<Role> roles;
@@ -158,26 +157,16 @@ public class Account extends BaseModel implements Searchable{
if(fields!=null){ if(fields!=null){
for(AccountField f:fields) for(AccountField f:fields)
f.postprocess(); f.postprocess();
}else{
fields=Collections.emptyList();
} }
if(emojis!=null){ if(emojis!=null){
for(Emoji e:emojis) for(Emoji e:emojis)
e.postprocess(); e.postprocess();
}else{
emojis=Collections.emptyList();
} }
if(moved!=null) if(moved!=null)
moved.postprocess(); moved.postprocess();
if(fqn==null) fqn=getFullyQualifiedName();
if(id==null) id="";
if(username==null) username="";
if(TextUtils.isEmpty(displayName)) if(TextUtils.isEmpty(displayName))
displayName=!TextUtils.isEmpty(username) ? username : ""; displayName=username;
if(acct==null) acct=""; if(fqn == null) fqn = getFullyQualifiedName();
if(url==null) url="";
if(note==null) note="";
if(avatar==null) avatar="";
} }
public boolean isLocal(){ public boolean isLocal(){
@@ -202,15 +191,9 @@ public class Account extends BaseModel implements Searchable{
} }
public String getFullyQualifiedName() { public String getFullyQualifiedName() {
if (TextUtils.isEmpty(acct))
return "";
return fqn != null ? fqn : acct.split("@")[0] + "@" + getDomainFromURL(); return fqn != null ? fqn : acct.split("@")[0] + "@" + getDomainFromURL();
} }
public String getDisplayName(){
return '\u2068'+displayName+'\u2069';
}
@Override @Override
public String toString(){ public String toString(){
return "Account{"+ return "Account{"+
@@ -238,7 +221,6 @@ public class Account extends BaseModel implements Searchable{
", source="+source+ ", source="+source+
", suspended="+suspended+ ", suspended="+suspended+
", muteExpiresAt="+muteExpiresAt+ ", muteExpiresAt="+muteExpiresAt+
", noindex="+noindex+
'}'; '}';
} }
} }

View File

@@ -1,14 +0,0 @@
package org.joinmastodon.android.model;
public class AkkomaTranslation extends BaseModel{
public String text;
public String detectedLanguage;
public Translation toTranslation() {
Translation translation=new Translation();
translation.content=text;
translation.detectedSourceLanguage=detectedLanguage;
translation.provider="Akkoma";
return translation;
}
}

View File

@@ -46,34 +46,26 @@ public class Attachment extends BaseModel{
public int getWidth(){ public int getWidth(){
if(meta==null) if(meta==null)
return 1920; return 0;
if(meta.width>0) if(meta.width>0)
return meta.width; return meta.width;
if(meta.original!=null && meta.original.width>0) if(meta.original!=null && meta.original.width>0)
return meta.original.width; return meta.original.width;
if(meta.small!=null && meta.small.width>0) if(meta.small!=null && meta.small.width>0)
return meta.small.width; return meta.small.width;
return 1920; return 0;
} }
public int getHeight(){ public int getHeight(){
if(meta==null) if(meta==null)
return 1080; return 0;
if(meta.height>0) if(meta.height>0)
return meta.height; return meta.height;
if(meta.original!=null && meta.original.height>0) if(meta.original!=null && meta.original.height>0)
return meta.original.height; return meta.original.height;
if(meta.small!=null && meta.small.height>0) if(meta.small!=null && meta.small.height>0)
return meta.small.height; return meta.small.height;
return 1080; return 0;
}
public boolean hasKnownDimensions(){
return meta!=null && (
(meta.height>0 && meta.width>0)
|| (meta.original!=null && meta.original.height>0 && meta.original.width>0)
|| (meta.small!=null && meta.small.height>0 && meta.small.width>0)
);
} }
public double getDuration(){ public double getDuration(){

View File

@@ -1,6 +1,5 @@
package org.joinmastodon.android.model; package org.joinmastodon.android.model;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class EmojiCategory{ public class EmojiCategory{
@@ -11,8 +10,4 @@ public class EmojiCategory{
this.title=title; this.title=title;
this.emojis=emojis; this.emojis=emojis;
} }
public EmojiCategory(EmojiCategory category){
this.title = category.title;
this.emojis = new ArrayList<>(category.emojis);
}
} }

View File

@@ -23,7 +23,6 @@ public class Hashtag extends BaseModel implements DisplayItemsParent{
", following="+following+ ", following="+following+
", history="+history+ ", history="+history+
", statusesCount="+statusesCount+ ", statusesCount="+statusesCount+
", following="+following+
'}'; '}';
} }
@@ -31,19 +30,4 @@ public class Hashtag extends BaseModel implements DisplayItemsParent{
public String getID(){ public String getID(){
return name; return name;
} }
@Override
public boolean equals(Object o){
if(this==o) return true;
if(o==null || getClass()!=o.getClass()) return false;
Hashtag hashtag=(Hashtag) o;
return name.equals(hashtag.name);
}
@Override
public int hashCode(){
return name.hashCode();
}
} }

View File

@@ -161,15 +161,11 @@ public class Instance extends BaseModel{
case BUBBLE_TIMELINE -> pleromaFeatures case BUBBLE_TIMELINE -> pleromaFeatures
.map(f -> f.contains("bubble_timeline")) .map(f -> f.contains("bubble_timeline"))
.orElse(false); .orElse(false);
case MACHINE_TRANSLATION -> pleromaFeatures
.map(f -> f.contains("akkoma:machine_translation"))
.orElse(false);
}; };
} }
public enum Feature { public enum Feature {
BUBBLE_TIMELINE, BUBBLE_TIMELINE
MACHINE_TRANSLATION
} }
@Parcel @Parcel

View File

@@ -20,22 +20,4 @@ public class Mention extends BaseModel{
", url='"+url+'\''+ ", url='"+url+'\''+
'}'; '}';
} }
@Override
public boolean equals(Object o){
if(this==o) return true;
if(o==null || getClass()!=o.getClass()) return false;
Mention mention=(Mention) o;
if(!id.equals(mention.id)) return false;
return url.equals(mention.url);
}
@Override
public int hashCode(){
int result=id.hashCode();
result=31*result+url.hashCode();
return result;
}
} }

View File

@@ -6,7 +6,6 @@ import org.joinmastodon.android.model.Poll.Option;
import org.parceler.Parcel; import org.parceler.Parcel;
import java.time.Instant; import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -74,7 +73,6 @@ public class ScheduledStatus extends BaseModel implements DisplayItemsParent{
p.ownVotes = List.of(); p.ownVotes = List.of();
p.multiple = multiple; p.multiple = multiple;
p.options = options.stream().map(Option::new).collect(Collectors.toList()); p.options = options.stream().map(Option::new).collect(Collectors.toList());
p.expiresAt=Instant.now().plus(Integer.parseInt(expiresIn)+1, ChronoUnit.SECONDS);
return p; return p;
} }
} }

View File

@@ -37,8 +37,6 @@ public class Source extends BaseModel{
* The number of pending follow requests. * The number of pending follow requests.
*/ */
public int followRequestCount; public int followRequestCount;
public Boolean indexable;
public boolean hideCollections;
@Override @Override
public void postprocess() throws ObjectValidationException{ public void postprocess() throws ObjectValidationException{
@@ -56,8 +54,6 @@ public class Source extends BaseModel{
", sensitive="+sensitive+ ", sensitive="+sensitive+
", language='"+language+'\''+ ", language='"+language+'\''+
", followRequestCount="+followRequestCount+ ", followRequestCount="+followRequestCount+
", indexable="+indexable+
", hideCollections="+hideCollections+
'}'; '}';
} }
} }

View File

@@ -4,37 +4,27 @@ import static org.joinmastodon.android.api.MastodonAPIController.gson;
import static org.joinmastodon.android.api.MastodonAPIController.gsonWithoutDeserializer; import static org.joinmastodon.android.api.MastodonAPIController.gsonWithoutDeserializer;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Pair;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.parceler.Parcel;
import java.time.Instant;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.github.bottomSoftwareFoundation.bottom.Bottom;
import com.github.bottomSoftwareFoundation.bottom.TranslationError;
import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer; import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent; import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
import org.joinmastodon.android.utils.StatusTextEncoder; import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.parceler.Parcel;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.regex.Pattern; import java.util.List;
@Parcel @Parcel
public class Status extends BaseModel implements DisplayItemsParent, Searchable{ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
@@ -92,12 +82,10 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
public transient boolean spoilerRevealed; public transient boolean spoilerRevealed;
public transient boolean sensitiveRevealed; public transient boolean sensitiveRevealed;
public transient boolean textExpanded, textExpandable; public transient boolean textExpanded, textExpandable;
public transient String hasGapAfter; public transient boolean hasGapAfter;
public transient TranslatedStatus translation;
public transient boolean translationShown;
private transient String strippedText; private transient String strippedText;
public transient TranslationState translationState=TranslationState.HIDDEN;
public transient Translation translation;
public transient boolean fromStatusCreated;
public transient boolean preview;
public Status(){} public Status(){}
@@ -125,8 +113,6 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
if(filtered!=null) if(filtered!=null)
for(FilterResult fr:filtered) for(FilterResult fr:filtered)
fr.postprocess(); fr.postprocess();
if(quote!=null)
quote.postprocess();
spoilerRevealed=!hasSpoiler(); spoilerRevealed=!hasSpoiler();
if(!spoilerRevealed) sensitive=true; if(!spoilerRevealed) sensitive=true;
@@ -199,10 +185,6 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
return reblog!=null ? reblog : this; return reblog!=null ? reblog : this;
} }
public String getContentStatusID(){
return getContentStatus().id;
}
public String getStrippedText(){ public String getStrippedText(){
if(strippedText==null) if(strippedText==null)
strippedText=HtmlParser.strip(content); strippedText=HtmlParser.strip(content);
@@ -219,39 +201,6 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
return (Status) super.clone(); return (Status) super.clone();
} }
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 boolean isEligibleForTranslation(AccountSession session){
Instance instanceInfo=AccountSessionManager.getInstance().getInstanceInfo(session.domain);
boolean translateEnabled=instanceInfo!=null && (
(instanceInfo.v2!=null && instanceInfo.v2.configuration.translation!=null && instanceInfo.v2.configuration.translation.enabled) ||
(instanceInfo.isAkkoma() && instanceInfo.hasFeature(Instance.Feature.MACHINE_TRANSLATION))
);
try {
Pair<String, List<String>> decoded=BOTTOM_TEXT_PATTERN.matcher(getStrippedText()).find()
? new StatusTextEncoder(Bottom::decode).decode(getStrippedText(), BOTTOM_TEXT_PATTERN)
: null;
String bottomText=decoded==null || decoded.second.stream().allMatch(s->s.trim().isEmpty()) ? null : decoded.first;
if(bottomText!=null){
translation=new Translation();
translation.content=bottomText;
translation.detectedSourceLanguage="\uD83E\uDD7A\uD83D\uDC49\uD83D\uDC48";
translation.provider="bottom-java";
return true;
}
} catch (TranslationError ignored) {}
return translateEnabled && !TextUtils.isEmpty(content) && !TextUtils.isEmpty(language)
&& !Objects.equals(Locale.getDefault().getLanguage(), language)
&& (visibility==StatusPrivacy.PUBLIC || visibility==StatusPrivacy.UNLISTED);
}
public enum TranslationState{
HIDDEN,
SHOWN,
LOADING
}
public boolean isReblogPermitted(String accountID){ public boolean isReblogPermitted(String accountID){
return visibility.isReblogPermitted(account.id.equals( return visibility.isReblogPermitted(account.id.equals(
AccountSessionManager.getInstance().getAccount(accountID).self.id AccountSessionManager.getInstance().getAccount(accountID).self.id

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