Compare commits

...

1082 commits
3.18.0 ... main

Author SHA1 Message Date
Thomas
cf022002c5 Release 3.33.1 2025-06-06 18:53:44 +02:00
Thomas
4b4b6c49b2 Release 3.33.0 2025-06-06 15:28:38 +02:00
Thomas
49cc9a187e some release notes 2025-06-06 15:21:47 +02:00
Thomas
b954708e3a Fix issue #1238 - Improve language picker when filtered with some languages 2025-06-06 14:52:00 +02:00
Thomas
0fcd1c6d4b Merge remote-tracking branch 'origin/develop' into develop 2025-06-06 11:29:29 +02:00
Thomas
1c2c6f404a Add/Remove featured tags from profile editor 2025-06-06 11:29:00 +02:00
Thomas
2e33cd58f5 Add some margin to delete buttons 2025-06-06 10:42:08 +02:00
Thomas
c5e096728e Change layout for featured hashtags 2025-06-06 10:34:34 +02:00
Thomas
b0c11f5c2c Change layout for custom fields 2025-06-06 10:16:28 +02:00
Thomas
c0b6bbd4ea Change profile activity to manage featured hashtags 2025-06-06 09:50:06 +02:00
Christof Damian
442e27760a
Translated using Weblate (German)
Currently translated at 99.0% (1218 of 1230 strings)

Co-authored-by: Christof Damian <christof@damian.net>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2025-06-06 03:01:52 +02:00
Thomas
d514c7b8d6 Featured tags displayed in profiles 2025-06-05 17:34:51 +02:00
Thomas
5b843291a7 improve fields 2025-06-05 15:39:57 +02:00
Thomas
8a9b0d897f Fix embedded quotes not displayed 2025-06-05 11:56:54 +02:00
Thomas
e051bec7a0 Fix a memory leak for not cropped media 2025-06-05 11:22:50 +02:00
Thomas
6a74a35efe Merge remote-tracking branch 'origin/develop' into develop 2025-06-05 10:33:04 +02:00
Thomas
723771e464 Fix a memory leak for not cropped media 2025-06-05 10:32:39 +02:00
Thomas
b1b3c5230c Change regex for bottom tags 2025-06-05 09:37:57 +02:00
Thomas
764e3a1762 Fix a crash with long threads 2025-06-05 07:35:17 +02:00
大王叫我来巡山
a71fa29f72
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1230 of 1230 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2025-06-04 17:02:18 +02:00
josé m
cb2c811970
Translated using Weblate (Galician)
Currently translated at 100.0% (1230 of 1230 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2025-06-04 17:02:13 +02:00
Максим Горпиніч
bc81e32720
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1230 of 1230 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (1228 of 1228 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic4@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2025-06-04 17:02:09 +02:00
Thomas
a79121a8dd #1214 - Support Trending Links 2025-06-03 16:38:00 +02:00
Thomas
293a811392 #1219 - highlight bottom hashtags 2025-06-03 11:38:31 +02:00
Thomas
1df2cba3d0 Merge remote-tracking branch 'origin/develop' into develop 2025-06-03 09:15:57 +02:00
Thomas
5e2e91df3d Merge branch '0xd9a-update_peertube_picker_drawer' into develop 2025-06-03 09:15:01 +02:00
dicaeffe
b06a5731bd
Translated using Weblate (Italian)
Currently translated at 20.0% (7 of 35 strings)

Co-authored-by: dicaeffe <dicaeffe@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/it/
Translation: Fedilab/description
2025-06-02 19:37:18 +02:00
dicaeffe
dfe30f9bfb
Translated using Weblate (Italian)
Currently translated at 99.5% (1220 of 1226 strings)

Co-authored-by: dicaeffe <dicaeffe@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/it/
Translation: Fedilab/Strings
2025-06-02 19:37:05 +02:00
Thomas
477b2bfc3b #1237 - Wrong messages deleted for scheduled (messages and boosts) 2025-06-02 07:58:08 +02:00
Thomas
e6a8a45e03 Merge remote-tracking branch 'origin/develop' into develop 2025-06-01 10:46:09 +02:00
Thomas
e93d6660f1 #1222 - Open toot by tapping on Scheduled Boost 2025-06-01 10:45:58 +02:00
Thomas
65383b8787 #1165 - Improve animated emoji 2025-06-01 09:53:17 +02:00
Yurt Page
5d78347d3f
Translated using Weblate (Russian)
Currently translated at 93.3% (1144 of 1226 strings)

Co-authored-by: Yurt Page <yurtpage@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ru/
Translation: Fedilab/Strings
2025-05-28 01:06:31 +02:00
Thomas
02a066a253 Avoid a crash when max_char defined by the instance is not an integer 2025-05-27 08:06:16 +02:00
Thomas
749aefe858 Merge remote-tracking branch 'origin/develop' into develop 2025-05-26 12:06:30 +02:00
Thomas
15e10958b8 Add confirmation dialog when long pressing the boost button 2025-05-26 12:03:14 +02:00
Thomas
a12e2910c5 Fix issue #1217 - Pleroma instances cannot select media 2025-05-26 10:18:41 +02:00
Thomas
7e6d0a5c6e Fix issue #1220 - Limits number of fetch for filters 2025-05-26 09:41:31 +02:00
Moonshadow
e9ce3bd664
Translated using Weblate (Kabyle)
Currently translated at 2.8% (1 of 35 strings)

Co-authored-by: Moonshadow <moonshadow@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/kab/
Translation: Fedilab/description
2025-05-25 17:42:43 +02:00
abdelbasset jabrane
7dc61b8aaf
Translated using Weblate (Arabic)
Currently translated at 51.4% (18 of 35 strings)

Co-authored-by: abdelbasset jabrane <naminio201@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/ar/
Translation: Fedilab/description
2025-05-21 19:03:43 +00:00
abdelbasset jabrane
ebdaa79c81
Translated using Weblate (Arabic)
Currently translated at 100.0% (1226 of 1226 strings)

Co-authored-by: abdelbasset jabrane <naminio201@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ar/
Translation: Fedilab/Strings
2025-05-18 12:01:48 +02:00
Максим Горпиніч
d8abb4213e
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1226 of 1226 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic4@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2025-05-17 05:02:42 +00:00
0xd9a
b77d87ccb1 Make pickup button more visible 2025-05-13 20:04:54 +05:30
0xd9a
c611438172 Update Peertube Instance Picker Drawer 2025-05-13 19:41:24 +05:30
Jean-Luc Tibaux
7aa02a8b15
Translated using Weblate (French)
Currently translated at 99.3% (1218 of 1226 strings)

Co-authored-by: Jean-Luc Tibaux <eugentoptic@outlook.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translation: Fedilab/Strings
2025-05-12 00:03:12 +02:00
Wydow
e1da85235d
Translated using Weblate (French)
Currently translated at 99.3% (1218 of 1226 strings)

Co-authored-by: Wydow <wydow@tutanota.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translation: Fedilab/Strings
2025-05-12 00:03:12 +02:00
Dream X
cca71af7c2
Translated using Weblate (French)
Currently translated at 99.3% (1218 of 1226 strings)

Translated using Weblate (Spanish)

Currently translated at 97.3% (1194 of 1226 strings)

Translated using Weblate (Welsh)

Currently translated at 52.2% (640 of 1226 strings)

Co-authored-by: Dream X <nodem49316@daupload.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cy/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/es/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translation: Fedilab/Strings
2025-05-12 00:03:12 +02:00
Dream X
c83acd6b17
Translated using Weblate (Spanish)
Currently translated at 8.5% (3 of 35 strings)

Co-authored-by: Dream X <nodem49316@daupload.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/es/
Translation: Fedilab/description
2025-05-12 00:03:00 +02:00
Joan Pujolar
df50f40843
Translated using Weblate (Catalan)
Currently translated at 100.0% (35 of 35 strings)

Co-authored-by: Joan Pujolar <joan.pujolar@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/ca/
Translation: Fedilab/description
2025-05-09 05:02:03 +00:00
Joan Pujolar
d6e78e78ba
Translated using Weblate (Catalan)
Currently translated at 100.0% (1226 of 1226 strings)

Co-authored-by: Joan Pujolar <joan.pujolar@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ca/
Translation: Fedilab/Strings
2025-05-08 20:03:17 +02:00
Joan Pujolar
cbea08af24
Translated using Weblate (Catalan)
Currently translated at 100.0% (1226 of 1226 strings)

Co-authored-by: Joan Pujolar <joan.pujolar@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ca/
Translation: Fedilab/Strings
2025-05-08 04:03:09 +02:00
Joan Pujolar
db984a485f
Translated using Weblate (Catalan)
Currently translated at 100.0% (1226 of 1226 strings)

Co-authored-by: Joan Pujolar <joan.pujolar@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ca/
Translation: Fedilab/Strings
2025-05-07 09:01:52 +00:00
Bob Idle
a09d08b826
Translated using Weblate (German)
Currently translated at 98.5% (1208 of 1226 strings)

Co-authored-by: Bob Idle <102661087+bobidle@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2025-05-06 08:06:17 +02:00
Joan Pujolar
200488fa6a
Translated using Weblate (Catalan)
Currently translated at 100.0% (1226 of 1226 strings)

Co-authored-by: Joan Pujolar <joan.pujolar@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ca/
Translation: Fedilab/Strings
2025-05-06 08:06:17 +02:00
Joan Pujolar
18ae76c6f7
Translated using Weblate (Catalan)
Currently translated at 17.1% (6 of 35 strings)

Co-authored-by: Joan Pujolar <joan.pujolar@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/ca/
Translation: Fedilab/description
2025-05-05 08:56:41 +02:00
Joan Pujolar
7e2b3d0c8f
Translated using Weblate (Catalan)
Currently translated at 100.0% (1226 of 1226 strings)

Co-authored-by: Joan Pujolar <joan.pujolar@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ca/
Translation: Fedilab/Strings
2025-05-05 08:56:30 +02:00
Bob Idle
e889b19673
Translated using Weblate (German)
Currently translated at 98.5% (1208 of 1226 strings)

Co-authored-by: Bob Idle <102661087+bobidle@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2025-05-05 05:30:51 +02:00
Joan Pujolar
e1cb74911a
Translated using Weblate (Catalan)
Currently translated at 100.0% (1226 of 1226 strings)

Co-authored-by: Joan Pujolar <joan.pujolar@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ca/
Translation: Fedilab/Strings
2025-05-05 05:30:51 +02:00
Vaclovas Intas
1f4503cd96
Translated using Weblate (Lithuanian)
Currently translated at 31.8% (391 of 1226 strings)

Co-authored-by: Vaclovas Intas <Gateway_31@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/lt/
Translation: Fedilab/Strings
2025-05-02 11:12:48 +00:00
Vaclovas Intas
5a51c48302
Translated using Weblate (Lithuanian)
Currently translated at 8.5% (3 of 35 strings)

Co-authored-by: Vaclovas Intas <Gateway_31@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/lt/
Translation: Fedilab/description
2025-05-02 12:02:19 +02:00
Poesty Li
c821c96396
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (35 of 35 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/zh_Hans/
Translation: Fedilab/description
2025-04-28 07:01:52 +02:00
josé m
1374bfdca9
Translated using Weblate (Galician)
Currently translated at 45.7% (16 of 35 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/gl/
Translation: Fedilab/description
2025-04-26 05:29:57 +02:00
Максим Горпиніч
5f83abff97
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (35 of 35 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/uk/
Translation: Fedilab/description
2025-04-25 22:25:46 +02:00
Thomas
e69c46661d Merge remote-tracking branch 'origin/main' 2025-04-25 15:40:57 +02:00
Thomas
66f2bd4639 Release 3.32.3 2025-04-25 15:40:32 +02:00
Thomas
96fa8abbfa Fix trend messages repeated in the timeline 2025-04-25 15:18:49 +02:00
Thomas
9216c86658 Merge pull request 'Add Misskey URL basic support' (#1180) from AntoninDelFabbro/Fedilab:misskey-url into main
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/1180
2025-04-25 04:53:18 +00:00
0xd9a
786a4bc846 Fix: Polls without CWs not viewable 2025-04-24 02:15:17 +05:30
josé m
b06fc3e952
Translated using Weblate (Galician)
Currently translated at 100.0% (1226 of 1226 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2025-04-23 10:14:58 +02:00
大王叫我来巡山
4ea7e592a2
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1226 of 1226 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2025-04-23 02:03:20 +00:00
Jean-Luc Tibaux
36d4c6dc11
Translated using Weblate (German)
Currently translated at 98.1% (1203 of 1226 strings)

Co-authored-by: Jean-Luc Tibaux <eugentoptic@outlook.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2025-04-23 02:03:18 +00:00
Максим Горпиніч
caba5201f9
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (34 of 34 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/uk/
Translation: Fedilab/description
2025-04-21 23:02:20 +00:00
Максим Горпиніч
4ea99e4385
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1226 of 1226 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2025-04-21 23:01:50 +00:00
Thomas
cb0bac8ff6 Release 3.32.2 2025-04-21 18:19:31 +02:00
Antonin Del Fabbro
1d77fd9106
Add Misskey URL basic support 2025-04-21 16:05:27 +02:00
Thomas
6e932b6fd8 Release notes 2025-04-21 10:32:54 +02:00
Thomas
d044d1d36f Disable by default the mention to the booster when replying. Can be enabled in Settings > Compose (per account) 2025-04-21 10:27:56 +02:00
Thomas
bbd9c909b7 Merge branch 'fix_530' into develop 2025-04-21 10:03:02 +02:00
Thomas
ea6cb35b73 Gif media not animated by default 2025-04-21 10:02:08 +02:00
Thomas
fd00042f4d Downgrade animated emoji lib 2025-04-21 09:57:51 +02:00
Thomas
dd2a4dcf28 Merge branch '0xd9a-cw_hide_polls' into develop 2025-04-21 09:55:12 +02:00
0xd9a
78a4b51e4e Make username, displayname in nav drawer clickable
(This restores the previous behavior,
which toggled the accounts panel when clicked)
2025-04-20 18:43:20 +05:30
Alireza Rashidi
c465858eaa
Translated using Weblate (Persian)
Currently translated at 100.0% (1223 of 1223 strings)

Co-authored-by: Alireza Rashidi <alirezarashidigoorabi@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fa/
Translation: Fedilab/Strings
2025-04-18 04:02:49 +00:00
0xd9a
7747a99585 Hide polls with CWs 2025-04-18 00:50:51 +05:30
Alireza Rashidi
75283ef507
Translated using Weblate (Persian)
Currently translated at 100.0% (1223 of 1223 strings)

Co-authored-by: Alireza Rashidi <alirezarashidigoorabi@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fa/
Translation: Fedilab/Strings
2025-04-17 04:47:12 +02:00
Alireza Rashidi
e60d5e9882
Translated using Weblate (Persian)
Currently translated at 9.0% (3 of 33 strings)

Co-authored-by: Alireza Rashidi <alirezarashidigoorabi@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/fa/
Translation: Fedilab/description
2025-04-17 04:47:04 +02:00
Alireza Rashidi
a9386d6927
Translated using Weblate (Persian)
Currently translated at 57.3% (702 of 1223 strings)

Co-authored-by: Alireza Rashidi <alirezarashidigoorabi@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fa/
Translation: Fedilab/Strings
2025-04-16 22:34:43 +02:00
Haui
cc234a6aee
Translated using Weblate (German)
Currently translated at 15.1% (5 of 33 strings)

Co-authored-by: Haui <hauisminecraft@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/de/
Translation: Fedilab/description
2025-04-16 18:28:32 +02:00
Yurt Page
a5c6a60a20
Translated using Weblate (Russian)
Currently translated at 92.8% (1136 of 1223 strings)

Co-authored-by: Yurt Page <yurtpage@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ru/
Translation: Fedilab/Strings
2025-04-15 02:08:00 +02:00
XblateX
d8a417983a
Translated using Weblate (Russian)
Currently translated at 92.5% (1132 of 1223 strings)

Co-authored-by: XblateX <blate@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ru/
Translation: Fedilab/Strings
2025-04-13 04:01:51 +00:00
Lukáš Jelínek
df27c45434
Translated using Weblate (Czech)
Currently translated at 100.0% (1223 of 1223 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2025-04-13 04:01:48 +00:00
Thomas
6d5dbb2585 Merge pull request 'Add an outline around media' (#1174) from 0xd9a/Fedilab:media_borders into develop
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/1174
2025-04-12 08:50:30 +00:00
Thomas
578c9b3d66 Fix issue #1173 - Refresh and pagination broken for the Trending timeline 2025-04-12 10:49:34 +02:00
0xd9a
fb932b293f Add an outline around media 2025-04-10 10:07:18 +05:30
Thomas
3bde1ae578 Fix a potential crash when translating with MinT 2025-04-09 15:57:45 +02:00
Thomas
78fccc9578 Fix issue #810 - Wrong preview picture on share from another app 2025-04-09 12:28:30 +02:00
ButterflyOfFire
1be12f1fe1
Translated using Weblate (Kabyle)
Currently translated at 61.2% (749 of 1223 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/kab/
Translation: Fedilab/Strings
2025-04-09 02:20:00 +02:00
Максим Горпиніч
4fefda1f68
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (33 of 33 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/uk/
Translation: Fedilab/description
2025-04-08 20:33:32 +02:00
ButterflyOfFire
59952c8203
Translated using Weblate (Kabyle)
Currently translated at 61.0% (747 of 1223 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/kab/
Translation: Fedilab/Strings
2025-04-08 20:33:23 +02:00
Thomas
fdc2cb07c8 Release 3.32.1 2025-04-08 18:01:42 +02:00
Thomas
a8b16b1956 Merge remote-tracking branch 'origin/develop' into develop 2025-04-08 17:51:19 +02:00
Thomas
edbe7689bf - Fix layout issues with media descriptions
- Fix a crash when taping the media to open original message
2025-04-08 17:51:07 +02:00
0xd9a
c4cf8f58a1 re-hide quote button by default 2025-04-08 16:29:58 +05:30
大王叫我来巡山
d1e8a6ce43
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1223 of 1223 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2025-04-08 12:49:44 +02:00
josé m
7547d4e681
Translated using Weblate (Galician)
Currently translated at 100.0% (1223 of 1223 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2025-04-08 05:05:50 +02:00
0xd9a
b99d55e5ff Add an id for ConstraintLayout Flow 2025-04-07 23:45:24 +05:30
Максим Горпиніч
b57de8254c
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1223 of 1223 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2025-04-07 20:06:27 +02:00
Максим Горпиніч
0d9f46c66c
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (32 of 32 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/uk/
Translation: Fedilab/description
2025-04-07 20:06:18 +02:00
Thomas
45f573bce1 Merge remote-tracking branch 'origin/main' 2025-04-07 17:42:54 +02:00
Thomas
1b72a95658 Release 3.32.0 2025-04-07 17:42:18 +02:00
Thomas
8db458704c Merge branch 'fix_528' into develop 2025-04-07 17:32:52 +02:00
Thomas
14a3ad5a8e Fix a background color issue when displaying media 2025-04-07 17:30:33 +02:00
Thomas
5cf8332024 Fix a crash when opening conversations 2025-04-07 17:21:03 +02:00
Thomas
4baadfba17 Fix a crash with auto-split messages 2025-04-07 17:16:52 +02:00
Thomas
c2683995a1 Fix #1163 - Posting messages does not work on some Friendica instances 2025-04-07 16:06:32 +02:00
0xd9a
89355894fc Add option to disable auto hiding compose button
(issue #802)
2025-04-07 17:52:17 +05:30
Thomas
7baab31549 Merge remote-tracking branch 'origin/develop' into develop 2025-04-07 12:15:01 +02:00
Thomas
59c05e35fd Fix issue #1164 - Custom emojis in bio do not render 2025-04-07 12:11:39 +02:00
Poesty Li
3cd35a4ea0
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1220 of 1220 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2025-04-07 00:03:01 +02:00
Максим Горпиніч
ab6c56d922
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1220 of 1220 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2025-04-06 06:58:51 +02:00
Yurt Page
991f15c832
Translated using Weblate (Russian)
Currently translated at 92.5% (1129 of 1220 strings)

Co-authored-by: Yurt Page <yurtpage@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ru/
Translation: Fedilab/Strings
2025-04-06 06:58:51 +02:00
0xd9a
5fe2d1ec0d Squeeze buttons when needed to prevent overlapping 2025-04-05 23:09:09 +05:30
Thomas
6a238d970f Fix issue #1169 - Fix crash when media are too heavy 2025-04-05 18:36:25 +02:00
Thomas
470663884f Merge remote-tracking branch 'origin/develop' into develop 2025-04-05 09:05:33 +02:00
Thomas
a2e23ae14a Merge branch '0xd9a-drawer_header' into develop 2025-04-05 09:05:20 +02:00
Максим Горпиніч
89a5ca001c
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1216 of 1216 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2025-04-05 05:01:50 +00:00
josé m
3ac1e85a9c
Translated using Weblate (Galician)
Currently translated at 100.0% (1216 of 1216 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2025-04-05 05:01:48 +00:00
0xd9a
eb276ba874 Update navigation drawer header
- proxy setting moved to Network preferences page
- 'Instance information' is now a nav drawer item
- A 'Manage accounts' button to open accounts list
2025-04-05 03:27:18 +05:30
Thomas
d7774cc5f6 Merge pull request 'Change title in bottom nav menu from Private to Direct' (#1045) from sertonix/Fedilab:nav-menu-direct into main
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/1045
2025-04-04 05:38:53 +00:00
Thomas
c41caddcf5 Merge pull request 'Add some content descriptions + update some buttons' (#1166) from 0xd9a/Fedilab:better_buttons into develop
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/1166
2025-04-04 05:38:02 +00:00
0xd9a
dc486381ff Update some buttons 2025-04-03 17:24:29 +05:30
0xd9a
c542f97df0 Add some content descriptions 2025-04-03 16:33:52 +05:30
Thomas
42b4793b14 update apng lib 2025-04-03 08:08:17 +02:00
Thomas
0ffc7c0440 Restore FOREGROUND_SERVICE 2025-04-02 11:27:27 +02:00
Максим Горпиніч
8f53e6f503
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (31 of 31 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/uk/
Translation: Fedilab/description
2025-04-02 01:16:32 +02:00
Lukáš Jelínek
5bea4662cb
Translated using Weblate (Czech)
Currently translated at 100.0% (1200 of 1200 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2025-04-02 01:16:21 +02:00
Thomas
6d7faa996d Release 3.31.3 2025-04-01 18:28:14 +02:00
Thomas
19f5e4c801 Merge pull request 'Add more logos' (#1162) from 0xd9a/Fedilab:more_icons into develop
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/1162
2025-04-01 16:17:27 +00:00
Thomas
16fb7dcc1f Merge branch 'fix_527' into develop 2025-04-01 18:17:02 +02:00
Thomas
e7b9fd9f1d Display reaction buttons only if instance 2025-04-01 18:16:31 +02:00
0xd9a
97f9c78073 Add new logos 2025-04-01 20:27:02 +05:30
Thomas
c8c1e5e75b Improve a little more media layout with translations 2025-04-01 15:38:30 +02:00
Thomas
d901a50bba Merge remote-tracking branch 'origin/develop' into develop 2025-04-01 14:58:43 +02:00
Thomas
78a0c23e34 Remove permission FOREGROUND_SERVICE 2025-04-01 14:58:01 +02:00
Thomas
c69cf6e295 Remove permission FOREGROUND_SERVICE 2025-04-01 14:57:32 +02:00
Thomas
98a8ac0df3 Make logout/proxy button more visible in main menu 2025-04-01 10:11:11 +02:00
Thomas
90a6297c8f Fix status bar icons not visible in light theme with custom accent color 2025-04-01 09:43:53 +02:00
Максим Горпиніч
1981876fac
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (30 of 30 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/uk/
Translation: Fedilab/description
2025-04-01 07:10:54 +02:00
Thomas
fed33cf600 Release 3.31.2 2025-03-31 18:34:23 +02:00
Thomas
02d6ef3bf5 Change the media activity status bar 2025-03-31 18:28:02 +02:00
Thomas
7bcb8dc9fb Merge remote-tracking branch 'origin/develop' into develop 2025-03-31 18:22:17 +02:00
Thomas
462f13f492 Downgrade Material lib until fix with the new Tonal Surface Color system 2025-03-31 18:21:58 +02:00
Thomas
5dfdb99c06 Merge branch 'fix_526' into develop 2025-03-31 15:06:29 +02:00
Thomas
62951a4a3a Change layout for media descriptions 2025-03-31 15:06:09 +02:00
Максим Горпиніч
27a9199e05
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (29 of 29 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/uk/
Translation: Fedilab/description
2025-03-31 12:54:32 +02:00
Thomas
62fe2cde10 Fix issue #1161 - handle included twice when replying to a self user's boost 2025-03-31 08:33:27 +02:00
Hosted Weblate
f4a5c31f8f
Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/
Translation: Fedilab/description
2025-03-31 07:41:32 +02:00
Thomas
d0835834f7 Merge remote-tracking branch 'origin/develop' into develop 2025-03-31 07:41:23 +02:00
Thomas
40a7d6f29b Merge branch 'fix_526' into develop 2025-03-31 07:41:11 +02:00
Thomas
2f663d1bd9 Fix a crash when translating media descriptions 2025-03-31 07:40:50 +02:00
Ajeje Brazorf
5f509bb481
Translated using Weblate (Sardinian)
Currently translated at 100.0% (1200 of 1200 strings)

Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/sc/
Translation: Fedilab/Strings
2025-03-31 04:01:21 +02:00
Antonin Del Fabbro
7121cca1f1
Translated using Weblate (French)
Currently translated at 99.9% (1199 of 1200 strings)

Co-authored-by: Antonin Del Fabbro <message@antonin.one>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translation: Fedilab/Strings
2025-03-31 04:01:19 +02:00
Thomas
19d64a778f #1160 - Use of fedilinks (scheme web+ap) / Fix url scheme to support with and without // 2025-03-30 12:34:35 +02:00
ButterflyOfFire
f7ca327572
Translated using Weblate (French)
Currently translated at 99.9% (1199 of 1200 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translation: Fedilab/Strings
2025-03-30 12:01:15 +02:00
Thomas
aedfaaddbc #1160 - Open profile when receiving an intent 2025-03-29 16:50:36 +01:00
Thomas
db17df93f0 #1160 - Detects acct and intent in URL with scheme web+activitypub 2025-03-29 16:07:53 +01:00
ButterflyOfFire
67c455f057
Translated using Weblate (Kabyle)
Currently translated at 61.6% (740 of 1200 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/kab/
Translation: Fedilab/Strings
2025-03-29 07:02:50 +01:00
Thomas
b961145575 Release 3.31.1 2025-03-28 16:50:18 +01:00
Thomas
3f12ad0f2d #1122 - Support instance only for GoToSocial 2025-03-28 16:42:59 +01:00
Thomas
e83b4d1acf Merge remote-tracking branch 'origin/develop' into develop 2025-03-28 09:25:04 +01:00
Thomas
4387cac67f Use language of messages for translations with MinT 2025-03-28 09:24:48 +01:00
Thomas
6aa3fd738a Merge pull request 'Update README.md' (#1157) from 0xd9a/Fedilab:update_readme into develop
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/1157
2025-03-28 05:43:00 +00:00
0xd9a
f48753c780 Update README.md 2025-03-28 08:52:40 +05:30
Максим Горпиніч
969d4457d0
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (29 of 29 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/uk/
Translation: Fedilab/description
2025-03-27 19:16:01 +01:00
Thomas
fec6b8510e Release 3.31.1 2025-03-27 16:39:38 +01:00
Thomas
9f57e77d77 Add MinT machine translation system support 2025-03-27 16:31:12 +01:00
Thomas
f3ae0a6cea Fix issue #1136 - Top bar coloring at scroll for conversations 2025-03-27 15:14:28 +01:00
Thomas
f2ad02bbda Fix issue #1154 - Back screen when going back from the Peertube section 2025-03-27 12:22:10 +01:00
Thomas
cb947a7d3c Fix a crash when unpinning timelines 2025-03-27 12:08:54 +01:00
Thomas
7bdc67f922 Merge remote-tracking branch 'origin/develop' into develop 2025-03-27 10:14:08 +01:00
Thomas
504816e21c Fix issue #1156 - GIF are not displayed in timelines 2025-03-27 09:55:56 +01:00
Максим Горпиніч
152d70e4d7
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (28 of 28 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/uk/
Translation: Fedilab/description
2025-03-26 21:01:53 +01:00
大王叫我来巡山
3ba4b64952
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1198 of 1198 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2025-03-26 15:02:03 +01:00
josé m
d3ec23fad5
Translated using Weblate (Galician)
Currently translated at 100.0% (1198 of 1198 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2025-03-26 15:02:01 +01:00
Максим Горпиніч
4160a4be1b
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1198 of 1198 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2025-03-26 01:01:59 +01:00
Thomas
c982e5a452 Release 3.31.0 2025-03-25 17:08:16 +01:00
Thomas
5b8d589598 Merge branch 'Fix_523' into develop 2025-03-25 16:57:04 +01:00
Thomas
0590458738 Merge remote-tracking branch 'origin/develop' into develop 2025-03-25 16:56:25 +01:00
Thomas
2e66b1dad6 Fix issue #1152 - Add a pinned Trending timeline (can be hidden in Manage Timelines) 2025-03-25 16:55:55 +01:00
Максим Горпиніч
59dc4a5a9f
Translated using Weblate (Ukrainian)
Currently translated at 99.8% (1196 of 1198 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2025-03-25 11:55:03 +01:00
Thomas
8ddb02ddeb Add a fallback to default translator when the DeepL API key is empty 2025-03-25 10:59:35 +01:00
Thomas
b46eed26ba Fix issue #727 - Only a part of DeepL translations are shown 2025-03-25 10:30:08 +01:00
Thomas
1b711d03eb Merge remote-tracking branch 'origin/develop' into develop 2025-03-25 09:48:26 +01:00
Thomas
9fbdfc3db0 add icon for the pixelfed settings 2025-03-25 09:46:47 +01:00
Thomas
9f79620d58 Fix issue #1150 - Allow to disable fullscreen media for Pixelfed 2025-03-25 09:41:53 +01:00
Thomas
6f3433da03 - Fix Lingva truncated translations
- Improve deobfuscation process
2025-03-25 08:16:48 +01:00
Thomas
6f26a55ae8 Merge pull request 'Add tooltips for tabs in profile (Issue #104)' (#1151) from 0xd9a/Fedilab:profile_tabs_tooltips into develop
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/1151
2025-03-24 07:07:06 +00:00
Thomas
07b7347417 Fix a crash when fetching remote profiles 2025-03-24 07:47:54 +01:00
Thomas
a9c50ab41f Fix a crash when displaying Home cache charts 2025-03-24 07:38:27 +01:00
Thomas
524fb7dd2a Fix a crash when animating a custom emoji 2025-03-24 07:32:43 +01:00
0xd9a
58aecf8db9 Add tooltips for tabs in profile (Issue #104) 2025-03-23 07:00:35 +05:30
Thomas
657b63b201 Merge pull request 'Increase touch area of reply, menu buttons (Issue #741)' (#1149) from 0xd9a/Fedilab:a_bit_better_buttons into develop
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/1149
2025-03-18 13:06:30 +00:00
0xd9a
f7ac237d6c Increase touch area of reply, menu buttons (Issue #741) 2025-03-18 18:08:38 +05:30
Thomas
5cb3b43eeb Fix issue #1147 - Media description not updated when there are several 2025-03-18 09:05:24 +01:00
Thomas
1d5d874415 Merge pull request 'Show a dialog after settings export (Issue #5)' (#1146) from 0xd9a/Fedilab:develop into develop
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/1146
2025-03-17 06:29:12 +00:00
0xd9a
66b117b81f Show a dialog after settings export (Issue #5) 2025-03-17 11:29:19 +05:30
Vaclovas Intas
97db978c8a
Translated using Weblate (Lithuanian)
Currently translated at 25.9% (310 of 1194 strings)

Co-authored-by: Vaclovas Intas <Gateway_31@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/lt/
Translation: Fedilab/Strings
2025-03-15 22:33:03 +01:00
大王叫我来巡山
721d18669a
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1194 of 1194 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2025-03-15 20:19:46 +01:00
Максим Горпиніч
71c183d8a7
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1194 of 1194 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2025-03-15 20:19:46 +01:00
josé m
aa0ad9d57e
Translated using Weblate (Galician)
Currently translated at 100.0% (1194 of 1194 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2025-03-15 13:30:20 +01:00
Максим Горпиніч
1bf44bad10
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1194 of 1194 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2025-03-14 19:16:48 +00:00
Максим Горпиніч
5b3bbee285
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (27 of 27 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/uk/
Translation: Fedilab/description
2025-03-14 20:16:38 +01:00
Thomas
238f31e3fc Release 3.30.1 2025-03-14 17:21:30 +01:00
Thomas
88c18cc487 Follow Twitter/X tags through Nitter 2025-03-14 16:51:01 +01:00
Thomas
0d2ae2eedf Fix layout issues in small dialogs (padding) 2025-03-14 16:13:01 +01:00
Thomas
a63175ff7d Merge remote-tracking branch 'origin/develop' into develop 2025-03-14 15:58:50 +01:00
Thomas
9001676971 Fix async actions with cached notifications 2025-03-14 10:50:46 +01:00
Thomas
ef6319b735 Update Unifiedpush Android connector to 3.0.7 2025-03-14 07:37:46 +01:00
XblateX
e14d2c143a
Translated using Weblate (Russian)
Currently translated at 30.7% (8 of 26 strings)

Co-authored-by: XblateX <blate@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/ru/
Translation: Fedilab/description
2025-03-14 03:25:24 +01:00
Thomas
b0cc06a3e5 Fix issue #1142 - Filters not applied to media descriptions 2025-03-13 16:37:20 +01:00
Thomas
90da257037 Fix #1143 - Fix a crash when scrolling 2025-03-13 15:52:48 +01:00
Thomas
916fe40381 Fix a crash with threads and decorations 2025-03-13 10:36:45 +01:00
Thomas
4c23100021 Fix a crash with media when exiting the app 2025-03-13 10:36:25 +01:00
Thomas
440fb41f1c Reduce the buffering when looping through GIF 2025-03-13 10:36:03 +01:00
Thomas
41d98b88d2 remove automatic backup for Google 2025-03-10 11:51:34 +01:00
Максим Горпиніч
d9cdfc88e9
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (26 of 26 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/uk/
Translation: Fedilab/description
2025-03-06 23:31:26 +01:00
Thomas
3a571b8a22 Merge pull request 'Update README.md: Change wiki to documentation' (#1138) from 0xd9a/Fedilab:update_readme into develop
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/1138
2025-03-06 21:02:53 +00:00
0xd9a
b51ba4d88b Update README.md: Change wiki to documentation 2025-03-07 02:28:39 +05:30
Thomas
a71bf2c6f3 Release 3.30.0 - quick link fix 2025-03-06 19:10:07 +01:00
Thomas
97d803e97f Release 3.30.0 2025-03-06 17:35:20 +01:00
Thomas
0b29f2ec0a Merge remote-tracking branch 'origin/develop' into develop 2025-03-06 17:31:01 +01:00
Thomas
de9c4f902f Fix admin reports not accessible 2025-03-06 17:30:52 +01:00
Максим Горпиніч
95a344b3c2
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (25 of 25 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/uk/
Translation: Fedilab/description
2025-03-06 16:18:50 +01:00
Thomas
170a5dfd06 small fix with use a token 2025-03-06 16:01:14 +01:00
Thomas
f35e75572f Prepare release notes 2025-03-06 15:55:45 +01:00
Thomas
7cb1e2d920 Fix fetch more size 2025-03-06 14:46:57 +01:00
Thomas
0fad05c049 Merge remote-tracking branch 'origin/develop' into develop 2025-03-06 11:06:52 +01:00
Thomas
94ea385aff Fix issue #1095 - Drafts not saved when adding/editing media descriptions 2025-03-06 11:06:25 +01:00
Thomas
ab30fc7bf2 Fix a crash when adding a media 2025-03-06 10:16:16 +01:00
Hosted Weblate
f16b415f7a
Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/
Translation: Fedilab/description
2025-03-06 10:06:39 +01:00
Thomas
65dea8bf56 Prepare some release notes 2025-03-06 10:05:47 +01:00
Thomas
f266b582df Merge remote-tracking branch 'origin/develop' into develop 2025-03-06 07:33:01 +01:00
Thomas
deef87ef9d Fix issue #1136 - Top bar coloring at scroll 2025-03-06 07:32:50 +01:00
大王叫我来巡山
827124b79b
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1193 of 1193 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2025-03-06 04:12:35 +01:00
Irene
ea677dc7c6
Translated using Weblate (Swedish)
Currently translated at 6.2% (4 of 64 strings)

Co-authored-by: Irene <ps86m042i@mozmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/sv/
Translation: Fedilab/description
2025-03-05 18:03:54 +01:00
Thomas
325fbf8f5f Fix issue #1128 - Move QR code to header 2025-03-05 17:42:46 +01:00
Thomas
592be1f057 Merge remote-tracking branch 'origin/develop' into develop 2025-03-05 17:01:44 +01:00
Thomas
28021b5047 Fix issue #1131 - User search suggestions have duplicates 2025-03-05 17:01:31 +01:00
Irene
52138618bd
Translated using Weblate (Swedish)
Currently translated at 3.1% (2 of 64 strings)

Co-authored-by: Irene <ps86m042i@mozmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/sv/
Translation: Fedilab/description
2025-03-05 14:33:01 +01:00
Thomas
7b2e44c407 Fix Crash when editing scheduled message with attachments 2025-03-05 11:30:40 +01:00
Thomas
acc9f84533 Merge remote-tracking branch 'origin/develop' into develop 2025-03-05 10:38:33 +01:00
Thomas
8f641c4071 Fix issue #113 - Android password manager with Peertube 2025-03-05 10:38:22 +01:00
Vaclovas Intas
82122e2269
Translated using Weblate (Lithuanian)
Currently translated at 24.8% (296 of 1193 strings)

Co-authored-by: Vaclovas Intas <Gateway_31@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/lt/
Translation: Fedilab/Strings
2025-03-05 05:08:55 +01:00
Максим Горпиніч
da81d15771
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1193 of 1193 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2025-03-05 05:08:54 +01:00
josé m
fe714aca0b
Translated using Weblate (Galician)
Currently translated at 100.0% (1193 of 1193 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2025-03-05 05:08:53 +01:00
Vaclovas Intas
5e21096f8c
Translated using Weblate (Lithuanian)
Currently translated at 4.6% (3 of 64 strings)

Co-authored-by: Vaclovas Intas <Gateway_31@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/lt/
Translation: Fedilab/description
2025-03-05 05:08:38 +01:00
Максим Горпиніч
2049abff1e
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (64 of 64 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/uk/
Translation: Fedilab/description
2025-03-05 05:08:37 +01:00
Thomas
848d015fd8 Release 3.30.0-beta-2 2025-03-04 17:58:49 +01:00
Thomas
01109c3842 Allow to scroll buttons for larger screen in conversation 2025-03-04 17:50:03 +01:00
Thomas
cbed45da29 Merge remote-tracking branch 'origin/develop' into develop 2025-03-04 17:05:45 +01:00
Thomas
f8ebd3435d Allow to login with a token 2025-03-04 17:05:33 +01:00
Thomas
020be16f11 Fix a crash 2025-03-04 15:41:15 +01:00
Thomas
35cd1b7b3e Fix a crash 2025-03-04 15:37:33 +01:00
Thomas
0eb3552c3c Add margin to quoted messages 2025-03-04 15:34:55 +01:00
Thomas
8d3621dbe7 Fix Nitter instances 2025-03-04 15:19:51 +01:00
Vaclovas Intas
03cc39d2e1
Translated using Weblate (Lithuanian)
Currently translated at 23.1% (275 of 1189 strings)

Co-authored-by: Vaclovas Intas <Gateway_31@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/lt/
Translation: Fedilab/Strings
2025-03-04 03:52:33 +01:00
Vaclovas Intas
b913f49918
Translated using Weblate (Lithuanian)
Currently translated at 22.0% (262 of 1189 strings)

Co-authored-by: Vaclovas Intas <Gateway_31@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/lt/
Translation: Fedilab/Strings
2025-03-03 23:12:10 +01:00
Lukáš Jelínek
661c373a05
Translated using Weblate (Czech)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2025-03-02 15:22:52 +01:00
Thomas
fae30e63a8 Fix Nitter using web calls 2025-03-02 11:49:22 +01:00
Isard
eb40866ea2
Translated using Weblate (Catalan)
Currently translated at 94.1% (1119 of 1189 strings)

Co-authored-by: Isard <isard@puntnet.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ca/
Translation: Fedilab/Strings
2025-03-02 11:04:12 +01:00
Thomas
c6494d7e04 Fix issue #1130 - Use Pixelfed layout 2025-03-01 16:23:07 +01:00
Thomas
e0b8b60fed Fix issue #1130 - Follow Pixelfed instance from the discover timeline 2025-03-01 16:06:39 +01:00
josé m
cdb0890fac
Translated using Weblate (Galician)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2025-02-28 17:02:55 +00:00
Максим Горпиніч
b5902d7ac8
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (63 of 63 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/uk/
Translation: Fedilab/description
2025-02-28 08:02:21 +01:00
Thomas
885e8eb5d8 Merge remote-tracking branch 'origin/develop' into develop 2025-02-27 16:59:35 +01:00
Thomas
0cfb1b1eae Prepares 2.30.0-beta-1 2025-02-27 16:59:10 +01:00
Thomas
ee89abe16f keep media proportion in timelines for Pixelfed 2025-02-27 16:50:06 +01:00
Thomas
310d6bf4aa Add like button on Pixelfed timelines 2025-02-27 15:06:51 +01:00
Thomas
f94251776b Fix Pixelfed view not used for Pixelfed accounts 2025-02-27 14:11:47 +01:00
Thomas
d98ba21957 Fix issue #1129 - Changed: Give more width to the detailed message in conversations 2025-02-27 11:28:47 +01:00
Thomas
02a9e26f2c UNICODE_CASE for regex 2025-02-27 11:04:03 +01:00
Thomas
55098c50f1 #1119 - Fix URLs not clickable when Markdown is enabled 2025-02-27 10:06:08 +01:00
Thomas
8c0b5032ec #1119 - Deal with URLs having spannable with space characters 2025-02-26 12:00:00 +01:00
Thomas
d28a114650 Fix issue #1112 - Don't lowercase xmpp url 2025-02-26 10:51:36 +01:00
Максим Горпиніч
90a42c6e04
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (62 of 62 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/uk/
Translation: Fedilab/description
2025-02-26 09:49:25 +01:00
Languages add-on
9068a60b99
Added translation using Weblate (Lithuanian)
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
2025-02-26 01:46:13 +01:00
Thomas
efc8d9d502 Release 3.29.2 2025-02-25 18:06:41 +01:00
Thomas
4c3e8b48dd Merge remote-tracking branch 'origin/develop' into develop 2025-02-25 17:50:16 +01:00
Thomas
23b6aa4c2b #1111 - Fix wrong muted time 2025-02-25 17:46:23 +01:00
Thomas
acff2ae390 #1112 - Fix xmpp links not opening xmpp client 2025-02-25 17:18:08 +01:00
Thomas
98adcc1abd Fix issue with names not displayed fully 2025-02-25 10:35:54 +01:00
Thomas
6d47a8736e Merge pull request 'Optimizations regarding push' (#1127) from s1m/Fedilab:optimizations_push into develop
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/1127
2025-02-25 09:28:21 +00:00
sim
ec60b63c68 Unregister push endpoint onUnregistered 2025-02-25 10:20:03 +01:00
sim
b26b10c651 Always register push endpoint
The mastodon server may remove the endpoint if the push server
has been in an inconsistent state (returning a 404), or the endpoint
may not be restored after a backup, or other edge cases
2025-02-25 10:19:43 +01:00
sim
f93533f95b Remove useless exclude 2025-02-25 10:17:09 +01:00
Thomas
ef50b5261a Lib update + fix TransactionTooLargeException 2025-02-25 09:47:53 +01:00
Vaclovas Intas
04e18c7518
Translated using Weblate (Lithuanian)
Currently translated at 25.9% (309 of 1190 strings)

Co-authored-by: Vaclovas Intas <Gateway_31@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/lt/
Translation: Fedilab/Strings
2025-02-25 08:53:11 +01:00
Vaclovas Intas
a2338c5fca
Translated using Weblate (Lithuanian)
Currently translated at 24.7% (294 of 1190 strings)

Co-authored-by: Vaclovas Intas <Gateway_31@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/lt/
Translation: Fedilab/Strings
2025-02-25 07:14:10 +00:00
Vaclovas Intas
7087f80c9c
Translated using Weblate (Lithuanian)
Currently translated at 4.9% (3 of 61 strings)

Co-authored-by: Vaclovas Intas <Gateway_31@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/lt/
Translation: Fedilab/description
2025-02-25 08:13:55 +01:00
Vaclovas Intas
8512db5c01
Translated using Weblate (Lithuanian)
Currently translated at 24.5% (292 of 1190 strings)

Co-authored-by: Vaclovas Intas <Gateway_31@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/lt/
Translation: Fedilab/Strings
2025-02-25 08:11:50 +01:00
Vaclovas Intas
5f53cb39bd
Translated using Weblate (Lithuanian)
Currently translated at 3.2% (2 of 61 strings)

Co-authored-by: Vaclovas Intas <Gateway_31@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/lt/
Translation: Fedilab/description
2025-02-24 19:40:00 +01:00
Languages add-on
e3a862abff
Added translation using Weblate (Lithuanian)
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
2025-02-24 17:57:10 +01:00
Thomas
a20f583dee Fix issue #1125 - Crash when changing the type of notifications 2025-02-24 11:36:02 +01:00
Thomas
efbfbfbc2f remove useless code 2025-02-24 10:41:05 +01:00
Максим Горпиніч
8a408c4925
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (61 of 61 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/uk/
Translation: Fedilab/description
2025-02-24 01:03:36 +01:00
Thomas
5aa8cd909f Fix a crash in notifications for Sharkey 2025-02-23 10:51:50 +01:00
Thomas
2ddc0540a8 Release 3.29.1 2025-02-22 17:04:33 +01:00
Thomas
ef83f58648 Fix a crash 2025-02-22 16:51:05 +01:00
Thomas
91a2d61fd0 Change link for push notification helper 2025-02-22 16:11:43 +01:00
Thomas
f3265b439a Merge remote-tracking branch 'origin/develop' into develop 2025-02-22 15:49:47 +01:00
Thomas
1ca3670cb4 Fix emoji picker when there is no result 2025-02-22 15:49:23 +01:00
大王叫我来巡山
5f5cf2e3e5
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1190 of 1190 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2025-02-22 14:10:12 +01:00
josé m
37d319215c
Translated using Weblate (Galician)
Currently translated at 100.0% (1190 of 1190 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2025-02-22 14:10:12 +01:00
Thomas
661b7fb2b0 - Fix issue #961 - Quotes broken with Markdowns 2025-02-22 10:43:37 +01:00
Thomas
1a0131e05a - Fix a display bug from 2.29.0 2025-02-22 10:26:20 +01:00
Thomas
b034f6731a - Fix reports crashes after submitting
- Use full reports for profiles
2025-02-22 09:41:46 +01:00
Thomas
cb96f0df44 Merge remote-tracking branch 'origin/develop' into develop 2025-02-22 09:10:50 +01:00
Thomas
f39ba15194 Fix issue #1123 - Likes in Sharkey show up as new follows 2025-02-22 09:10:31 +01:00
Thomas
8b42c6d2b6 Remove registration for Google flavor 2025-02-22 08:51:59 +01:00
Максим Горпиніч
753cd5743b
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1190 of 1190 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2025-02-21 20:52:21 +01:00
Максим Горпиніч
39c7157b9f
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (60 of 60 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/uk/
Translation: Fedilab/description
2025-02-21 20:52:11 +01:00
Anonymous
2d38090964
Translated using Weblate (Tamil)
Currently translated at 99.2% (1181 of 1190 strings)

Translated using Weblate (Gaelic)

Currently translated at 99.7% (1187 of 1190 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 54.8% (653 of 1190 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (1187 of 1190 strings)

Translated using Weblate (Vietnamese)

Currently translated at 71.8% (855 of 1190 strings)

Translated using Weblate (Turkish)

Currently translated at 99.7% (1187 of 1190 strings)

Translated using Weblate (Sinhala)

Currently translated at 55.7% (664 of 1190 strings)

Translated using Weblate (Sardinian)

Currently translated at 99.7% (1187 of 1190 strings)

Translated using Weblate (Portuguese)

Currently translated at 84.6% (1007 of 1190 strings)

Translated using Weblate (Polish)

Currently translated at 99.7% (1187 of 1190 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 91.7% (1092 of 1190 strings)

Translated using Weblate (Dutch)

Currently translated at 92.5% (1101 of 1190 strings)

Translated using Weblate (Japanese)

Currently translated at 99.5% (1185 of 1190 strings)

Translated using Weblate (Italian)

Currently translated at 98.9% (1177 of 1190 strings)

Translated using Weblate (Hungarian)

Currently translated at 57.8% (689 of 1190 strings)

Translated using Weblate (Galician)

Currently translated at 99.7% (1187 of 1190 strings)

Translated using Weblate (French)

Currently translated at 99.6% (1186 of 1190 strings)

Translated using Weblate (Basque)

Currently translated at 95.7% (1139 of 1190 strings)

Translated using Weblate (Spanish)

Currently translated at 99.6% (1186 of 1190 strings)

Translated using Weblate (Esperanto)

Currently translated at 81.5% (970 of 1190 strings)

Translated using Weblate (Czech)

Currently translated at 99.7% (1187 of 1190 strings)

Translated using Weblate (Catalan)

Currently translated at 78.9% (939 of 1190 strings)

Translated using Weblate (Arabic)

Currently translated at 84.9% (1011 of 1190 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ar/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ca/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/eo/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/es/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/eu/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gd/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/hu/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/it/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ja/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/nl/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/pl/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/pt/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/sc/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/si/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ta/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/vi/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hant/
Translation: Fedilab/Strings
2025-02-21 17:09:46 +01:00
Thomas
daf1ff37ff Merge remote-tracking branch 'origin/develop' into develop 2025-02-21 16:57:03 +01:00
Thomas
759de7bca0 prepare release 3.29.0 2025-02-21 16:55:45 +01:00
Thomas
031c842ba7 Fix issue #1090 - Instant search of Hashtag repeats results 2025-02-21 16:44:27 +01:00
Thomas
7e78b5da6e Edit scheduled threads 2025-02-21 15:34:02 +01:00
Thomas
7d9d2346cc Allow to edit scheduled messages from server side 2025-02-21 11:32:34 +01:00
Thomas
92eb1ea3d1 Order list name alphabetically in profiles 2025-02-21 08:50:16 +01:00
Thomas
c49fe7901a Fix Peertube instances picker 2025-02-20 18:44:37 +01:00
Thomas
fc24e63319 Fix #1120 - Display a message when clicking a link without Internet connection 2025-02-20 17:29:04 +01:00
Thomas
97e30e00b4 Fallback to 34 + update libs 2025-02-20 11:38:50 +01:00
Thomas
38a630a4e5 Push notifications 2025-02-19 16:44:29 +01:00
ButterflyOfFire
a8e362273c
Translated using Weblate (Kabyle)
Currently translated at 61.5% (732 of 1189 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/kab/
Translation: Fedilab/Strings
2025-02-19 13:16:05 +01:00
Thomas
318a566e76 update libs + fix bugs 2025-02-18 16:10:06 +01:00
Thomas
96a43a7c32 Fix build issue 2025-02-18 12:13:56 +01:00
pitroig
dd0a432b02
Translated using Weblate (Catalan)
Currently translated at 6.7% (4 of 59 strings)

Co-authored-by: pitroig <ona@riseup.net>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/ca/
Translation: Fedilab/description
2025-02-17 08:08:41 +01:00
Максим Горпиніч
4d29a2baf8
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2025-02-15 17:02:00 +01:00
XblateX
c9325d5a53
Translated using Weblate (Russian)
Currently translated at 84.2% (1002 of 1189 strings)

Co-authored-by: XblateX <blate@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ru/
Translation: Fedilab/Strings
2025-02-15 17:01:58 +01:00
Mads Damgaard Mortensen
04e8f7318c
Translated using Weblate (Danish)
Currently translated at 8.4% (5 of 59 strings)

Co-authored-by: Mads Damgaard Mortensen <weblate.upcountry931@passmail.net>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/da/
Translation: Fedilab/description
2025-02-14 06:08:16 +01:00
XblateX
37428d8e24
Translated using Weblate (Russian)
Currently translated at 83.5% (993 of 1189 strings)

Co-authored-by: XblateX <blate@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ru/
Translation: Fedilab/Strings
2025-02-09 03:02:40 +00:00
snue pirat
0914fd1166
Translated using Weblate (Danish)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: snue pirat <snue@proton.me>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/da/
Translation: Fedilab/Strings
2025-02-05 00:02:03 +01:00
snue pirat
3799bf4a48
Translated using Weblate (Danish)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: snue pirat <snue@proton.me>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/da/
Translation: Fedilab/Strings
2025-02-03 04:03:07 +01:00
Максим Горпиніч
992df41002
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2025-02-01 23:41:05 +01:00
snue pirat
a965922dd7
Translated using Weblate (Danish)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: snue pirat <snue@proton.me>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/da/
Translation: Fedilab/Strings
2025-02-01 23:41:04 +01:00
Максим Горпиніч
89eaa0ea3c
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (59 of 59 strings)

Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/uk/
Translation: Fedilab/description
2025-02-01 23:40:46 +01:00
Hosted Weblate
370fb94e21
Update translation files
Updated by "Remove blank strings" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/
Translation: Fedilab/Strings
2025-01-29 21:02:11 +01:00
ButterflyOfFire
c4ad7ff87a
Translated using Weblate (Kabyle)
Currently translated at 61.5% (732 of 1189 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/kab/
Translation: Fedilab/Strings
2025-01-29 21:02:08 +01:00
snue pirat
86c7ea4021
Translated using Weblate (Danish)
Currently translated at 6.7% (4 of 59 strings)

Co-authored-by: snue pirat <snue@proton.me>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/da/
Translation: Fedilab/description
2025-01-29 14:02:07 +01:00
Hosted Weblate
ff6a0c2298
Update translation files
Updated by "Remove blank strings" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/
Translation: Fedilab/Strings
2025-01-29 07:02:13 +01:00
snue pirat
7c50126dd4
Translated using Weblate (Kabyle)
Currently translated at 61.6% (733 of 1189 strings)

Translated using Weblate (Danish)

Currently translated at 99.7% (1186 of 1189 strings)

Co-authored-by: snue pirat <snue@proton.me>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/da/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/kab/
Translation: Fedilab/Strings
2025-01-29 07:02:11 +01:00
Bob Idle
6ad9567215
Translated using Weblate (German)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: Bob Idle <102661087+bobidle@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2025-01-21 09:00:33 +01:00
Bob Idle
e7203e1b4c
Translated using Weblate (German)
Currently translated at 16.9% (10 of 59 strings)

Co-authored-by: Bob Idle <102661087+bobidle@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/de/
Translation: Fedilab/description
2025-01-20 06:00:30 +01:00
Juli
b84ac60239
Translated using Weblate (Finnish)
Currently translated at 1.6% (1 of 59 strings)

Co-authored-by: Juli <julimiro@posteo.net>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/fi/
Translation: Fedilab/description
2025-01-17 10:12:09 +01:00
Bob Idle
7fb987a22b
Translated using Weblate (German)
Currently translated at 15.2% (9 of 59 strings)

Co-authored-by: Bob Idle <102661087+bobidle@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/de/
Translation: Fedilab/description
2025-01-17 10:12:09 +01:00
Juli
d47c5c44c0
Added translation using Weblate (Finnish)
Co-authored-by: Juli <julimiro@posteo.net>
2025-01-16 10:50:55 +01:00
大王叫我来巡山
9590a58b3c
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (59 of 59 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/zh_Hans/
Translation: Fedilab/description
2025-01-16 10:50:30 +01:00
Poesty Li
7369158e29
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (59 of 59 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/zh_Hans/
Translation: Fedilab/description
2025-01-16 06:01:00 +01:00
தமிழ்நேரம்
8d3111b6ab
Translated using Weblate (Tamil)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ta/
Translation: Fedilab/Strings
2024-12-26 13:02:34 +00:00
தமிழ்நேரம்
cbdb950e27
Translated using Weblate (Tamil)
Currently translated at 100.0% (59 of 59 strings)

Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/ta/
Translation: Fedilab/description
2024-12-26 14:00:42 +01:00
Languages add-on
263d332f37
Added translation using Weblate (Tamil)
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
2024-12-25 17:55:08 +01:00
josé m
ec40bf1a5e
Translated using Weblate (Galician)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2024-12-22 00:05:09 +01:00
josé m
a6ee4cb684
Translated using Weblate (Galician)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2024-12-21 04:00:32 +01:00
Miren
36d29c9765
Translated using Weblate (Basque)
Currently translated at 5.0% (3 of 59 strings)

Co-authored-by: Miren <librezale@miren.be>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/eu/
Translation: Fedilab/description
2024-11-25 05:00:29 +01:00
Miren
ca126dfc1e
Translated using Weblate (Basque)
Currently translated at 96.1% (1143 of 1189 strings)

Co-authored-by: Miren <librezale@miren.be>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/eu/
Translation: Fedilab/Strings
2024-11-25 03:00:48 +01:00
phlostically
4620ebe7ff
Translated using Weblate (Esperanto)
Currently translated at 81.6% (971 of 1189 strings)

Co-authored-by: phlostically <phlostically@mailinator.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/eo/
Translation: Fedilab/Strings
2024-11-19 14:00:35 +00:00
Free_squire
4c39cc28a9
Translated using Weblate (Russian)
Currently translated at 83.4% (992 of 1189 strings)

Co-authored-by: Free_squire <danil_pyankov_2004@mail.ru>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ru/
Translation: Fedilab/Strings
2024-11-18 00:00:46 +01:00
phlostically
3de3192797
Translated using Weblate (Esperanto)
Currently translated at 65.6% (780 of 1189 strings)

Co-authored-by: phlostically <phlostically@mailinator.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/eo/
Translation: Fedilab/Strings
2024-11-18 00:00:46 +01:00
Free_squire
601c1e4bb3
Translated using Weblate (Russian)
Currently translated at 11.8% (7 of 59 strings)

Co-authored-by: Free_squire <danil_pyankov_2004@mail.ru>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/ru/
Translation: Fedilab/description
2024-11-18 00:00:33 +01:00
XblateX
5b4199a570
Translated using Weblate (Russian)
Currently translated at 80.4% (956 of 1189 strings)

Co-authored-by: XblateX <blate@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ru/
Translation: Fedilab/Strings
2024-10-11 06:56:47 +02:00
ButterflyOfFire
f3e4e9d177
Translated using Weblate (Kabyle)
Currently translated at 61.5% (732 of 1189 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/kab/
Translation: Fedilab/Strings
2024-10-09 05:15:55 +00:00
Nazar
0df166518f
Translated using Weblate (Ukrainian)
Currently translated at 69.7% (829 of 1189 strings)

Co-authored-by: Nazar <balaraz@tuta.io>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2024-09-12 02:09:33 +00:00
harc esz
0fdb76cd83
Translated using Weblate (Polish)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: harc esz <harcesz@riseup.net>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/pl/
Translation: Fedilab/Strings
2024-09-12 02:09:32 +00:00
ButterflyOfFire
2ea1c52202
Translated using Weblate (French)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translation: Fedilab/Strings
2024-08-30 08:09:27 +02:00
Nazar
d8eee9a010
Translated using Weblate (Ukrainian)
Currently translated at 67.9% (808 of 1189 strings)

Co-authored-by: Nazar <balaraz@tuta.io>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2024-08-25 00:09:16 +02:00
Nazar
822eb59051
Translated using Weblate (Ukrainian)
Currently translated at 66.4% (790 of 1189 strings)

Co-authored-by: Nazar <balaraz@tuta.io>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2024-08-24 04:09:19 +00:00
josé m
de326c7292
Translated using Weblate (Galician)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2024-08-21 19:09:17 +02:00
Nazar
a2c8b36b95
Translated using Weblate (Ukrainian)
Currently translated at 65.6% (780 of 1189 strings)

Co-authored-by: Nazar <balaraz@tuta.io>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2024-08-11 21:09:20 +02:00
Nazar
9ed18854ca
Translated using Weblate (Ukrainian)
Currently translated at 65.2% (776 of 1189 strings)

Co-authored-by: Nazar <balaraz@tuta.io>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2024-08-08 09:09:18 +02:00
Nazar
7e6cc8ddf8
Translated using Weblate (Ukrainian)
Currently translated at 64.6% (769 of 1189 strings)

Co-authored-by: Nazar <balaraz@tuta.io>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2024-08-07 19:09:16 +02:00
Nazar
2597425643
Translated using Weblate (Ukrainian)
Currently translated at 3.3% (2 of 59 strings)

Co-authored-by: Nazar <balaraz@tuta.io>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/uk/
Translation: Fedilab/description
2024-08-06 23:09:28 +02:00
GunChleoc
7ab3bbbf98
Translated using Weblate (Gaelic)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: GunChleoc <fios@foramnagaidhlig.net>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gd/
Translation: Fedilab/Strings
2024-08-06 23:09:16 +02:00
Nazar
36c0816835
Translated using Weblate (Ukrainian)
Currently translated at 62.9% (748 of 1189 strings)

Co-authored-by: Nazar <balaraz@tuta.io>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2024-08-06 23:09:16 +02:00
Wydow
317e565b67
Translated using Weblate (French)
Currently translated at 99.6% (1185 of 1189 strings)

Co-authored-by: Wydow <wydow@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translation: Fedilab/Strings
2024-08-05 06:09:23 +02:00
大王叫我来巡山
0afe1d61ef
Translated using Weblate (Chinese (Simplified))
Currently translated at 6.7% (4 of 59 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/zh_Hans/
Translation: Fedilab/description
2024-08-04 14:09:19 +00:00
大王叫我来巡山
0e0fa784d1
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2024-08-04 09:09:19 +02:00
ed tavinor
368d04e8e7
Translated using Weblate (Esperanto)
Currently translated at 62.9% (749 of 1189 strings)

Co-authored-by: ed tavinor <edtavinor@posteo.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/eo/
Translation: Fedilab/Strings
2024-08-04 09:09:17 +02:00
GunChleoc
379992a836
Translated using Weblate (Gaelic)
Currently translated at 98.4% (1170 of 1189 strings)

Co-authored-by: GunChleoc <fios@foramnagaidhlig.net>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gd/
Translation: Fedilab/Strings
2024-08-02 03:09:23 +00:00
gallegonovato
affa6623c3
Translated using Weblate (Spanish)
Currently translated at 20.3% (12 of 59 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/es/
Translation: Fedilab/description
2024-07-31 05:09:24 +02:00
Ajeje Brazorf
9dd6cc630f
Translated using Weblate (Sardinian)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/sc/
Translation: Fedilab/Strings
2024-07-29 18:42:52 +02:00
Ajeje Brazorf
e9581dfb0a
Translated using Weblate (Sardinian)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/sc/
Translation: Fedilab/Strings
2024-07-28 10:09:11 +02:00
gallegonovato
bf73c2c5ec
Translated using Weblate (Spanish)
Currently translated at 16.9% (10 of 59 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/es/
Translation: Fedilab/description
2024-07-23 00:00:26 +02:00
gallegonovato
8b566f8157
Translated using Weblate (Spanish)
Currently translated at 13.5% (8 of 59 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/es/
Translation: Fedilab/description
2024-07-13 23:09:44 +00:00
Cosmin Humeniuc
20705ef917
Translated using Weblate (Romanian)
Currently translated at 58.7% (699 of 1189 strings)

Co-authored-by: Cosmin Humeniuc <cosmin@hume.ro>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ro/
Translation: Fedilab/Strings
2024-07-13 03:09:18 +02:00
Cosmin Humeniuc
520cdd0524
Translated using Weblate (Romanian)
Currently translated at 58.2% (693 of 1189 strings)

Co-authored-by: Cosmin Humeniuc <cosmin@hume.ro>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ro/
Translation: Fedilab/Strings
2024-07-08 09:09:12 +02:00
Cosmin Humeniuc
308a8e8cfb
Translated using Weblate (Romanian)
Currently translated at 57.3% (682 of 1189 strings)

Co-authored-by: Cosmin Humeniuc <cosmin@hume.ro>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ro/
Translation: Fedilab/Strings
2024-07-07 20:09:23 +02:00
gallegonovato
df311466e8
Translated using Weblate (Spanish)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/es/
Translation: Fedilab/Strings
2024-06-30 09:09:19 +00:00
gallegonovato
7581218888
Translated using Weblate (Spanish)
Currently translated at 99.9% (1188 of 1189 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/es/
Translation: Fedilab/Strings
2024-06-23 00:09:22 +02:00
Roberto Michán Sánchez
e027f70665
Translated using Weblate (Spanish)
Currently translated at 99.9% (1188 of 1189 strings)

Co-authored-by: Roberto Michán Sánchez <robertoms258@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/es/
Translation: Fedilab/Strings
2024-06-23 00:09:22 +02:00
Buffy
275f8dfcf4
Translated using Weblate (Spanish)
Currently translated at 97.2% (1156 of 1189 strings)

Co-authored-by: Buffy <james6cuerdas@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/es/
Translation: Fedilab/Strings
2024-06-21 16:09:22 +02:00
Buffy
cf7ef6ca19
Translated using Weblate (Spanish)
Currently translated at 93.5% (1112 of 1189 strings)

Co-authored-by: Buffy <james6cuerdas@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/es/
Translation: Fedilab/Strings
2024-06-17 06:31:50 +02:00
Buffy
69fea8d8e3
Translated using Weblate (Spanish)
Currently translated at 90.2% (1073 of 1189 strings)

Co-authored-by: Buffy <james6cuerdas@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/es/
Translation: Fedilab/Strings
2024-06-15 21:16:51 +02:00
Cosmin Humeniuc
17fd8850fc
Translated using Weblate (Romanian)
Currently translated at 55.4% (659 of 1189 strings)

Co-authored-by: Cosmin Humeniuc <cosmin@hume.ro>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ro/
Translation: Fedilab/Strings
2024-06-15 04:09:27 +00:00
Buffy
58cc356ea1
Translated using Weblate (Spanish)
Currently translated at 89.4% (1064 of 1189 strings)

Co-authored-by: Buffy <james6cuerdas@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/es/
Translation: Fedilab/Strings
2024-06-15 04:09:25 +00:00
Buffy
b5f8d492a4
Translated using Weblate (Spanish)
Currently translated at 78.3% (931 of 1189 strings)

Co-authored-by: Buffy <james6cuerdas@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/es/
Translation: Fedilab/Strings
2024-06-14 13:09:22 +02:00
Allen M
ad40cc7d4d
Translated using Weblate (Japanese)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: Allen M <3point14159265358@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ja/
Translation: Fedilab/Strings
2024-05-31 12:09:15 +02:00
XblateX
c4963f525a
Translated using Weblate (Russian)
Currently translated at 80.3% (955 of 1189 strings)

Co-authored-by: XblateX <blate@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ru/
Translation: Fedilab/Strings
2024-05-30 01:09:14 +02:00
Software In Interlingua
c511cab2bb
Translated using Weblate (Interlingua)
Currently translated at 24.3% (290 of 1189 strings)

Co-authored-by: Software In Interlingua <softinterlingua@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ia/
Translation: Fedilab/Strings
2024-05-20 10:01:53 +02:00
XblateX
29073ebd0f
Translated using Weblate (Russian)
Currently translated at 80.2% (954 of 1189 strings)

Co-authored-by: XblateX <blate@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ru/
Translation: Fedilab/Strings
2024-05-12 23:01:03 +02:00
Software In Interlingua
c17d5b42b2
Translated using Weblate (Interlingua)
Currently translated at 23.9% (285 of 1189 strings)

Co-authored-by: Software In Interlingua <softinterlingua@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ia/
Translation: Fedilab/Strings
2024-05-07 06:07:12 +02:00
XblateX
83d2fb85f0
Translated using Weblate (Ukrainian)
Currently translated at 59.1% (703 of 1189 strings)

Co-authored-by: XblateX <blate@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2024-05-07 06:07:10 +02:00
XblateX
04347c10f1
Translated using Weblate (Ukrainian)
Currently translated at 59.0% (702 of 1189 strings)

Translated using Weblate (Russian)

Currently translated at 80.0% (952 of 1189 strings)

Co-authored-by: XblateX <blate@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2024-04-29 02:07:22 +02:00
Oğuz Ersen
b3b327f60a
Translated using Weblate (Turkish)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2024-04-25 08:07:28 +02:00
Ajeje Brazorf
93ac90156f
Translated using Weblate (Sardinian)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/sc/
Translation: Fedilab/Strings
2024-04-07 08:02:00 +02:00
Oğuz Ersen
c82f171e07
Translated using Weblate (Turkish)
Currently translated at 99.9% (1188 of 1189 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2024-03-23 03:04:56 +01:00
josé m
a7b2a705f5
Translated using Weblate (Galician)
Currently translated at 25.4% (15 of 59 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/gl/
Translation: Fedilab/description
2024-03-22 18:02:23 +01:00
josé m
437fd6d0bd
Translated using Weblate (Galician)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2024-03-22 18:02:03 +01:00
Lukáš Jelínek
59d0a98729
Translated using Weblate (Czech)
Currently translated at 100.0% (1189 of 1189 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2024-03-22 00:48:59 +01:00
Anna K
3a13c0635f
Translated using Weblate (German)
Currently translated at 15.2% (9 of 59 strings)

Co-authored-by: Anna K <anna@montanha.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/de/
Translation: Fedilab/description
2024-03-22 00:47:58 +01:00
Thomas
b9b381a780 Release 3.28.2 2024-03-21 17:57:29 +01:00
Thomas
38b72e7732 align icon 2024-03-21 17:52:34 +01:00
Thomas
86e8e639a3 Create QR-Code for profile URL 2024-03-21 17:24:28 +01:00
Thomas
9907717638 Fix issue - Non clickable messages in threads 2024-03-21 09:53:30 +01:00
Thomas
f49fdfa2b9 Merge remote-tracking branch 'origin/develop' into develop 2024-03-21 09:37:18 +01:00
Thomas
91941a562d Fix issue #1047 - Long pronouns displace the user name 2024-03-21 09:36:12 +01:00
ButterflyOfFire
11361f50a6
Translated using Weblate (Kabyle)
Currently translated at 59.0% (701 of 1188 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/kab/
Translation: Fedilab/Strings
2024-03-17 02:02:07 +01:00
Lukáš Jelínek
963e10eb04
Translated using Weblate (Czech)
Currently translated at 100.0% (1188 of 1188 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2024-03-10 15:02:00 +01:00
ButterflyOfFire
7e82ec918a
Translated using Weblate (Kabyle)
Currently translated at 55.5% (660 of 1188 strings)

Translated using Weblate (Catalan)

Currently translated at 79.1% (940 of 1188 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ca/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/kab/
Translation: Fedilab/Strings
2024-03-09 09:01:53 +01:00
Max Harmathy
639297df1d
Translated using Weblate (Japanese)
Currently translated at 100.0% (1188 of 1188 strings)

Co-authored-by: Max Harmathy <max.harmathy@web.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ja/
Translation: Fedilab/Strings
2024-03-08 01:01:47 +01:00
Alfika07
b54f7ddaf5
Translated using Weblate (Hungarian)
Currently translated at 58.0% (690 of 1188 strings)

Co-authored-by: Alfika07 <alfika07@vivaldi.net>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/hu/
Translation: Fedilab/Strings
2024-03-04 21:01:45 +01:00
claleb
db4e48eb50
Translated using Weblate (German)
Currently translated at 100.0% (1188 of 1188 strings)

Co-authored-by: claleb <weblate@claleb.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2024-03-04 21:01:45 +01:00
Integral
43697263a8
Translated using Weblate (Chinese (Simplified))
Currently translated at 5.1% (3 of 58 strings)

Co-authored-by: Integral <integral@member.fsf.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/zh_Hans/
Translation: Fedilab/description
2024-03-04 12:01:52 +01:00
Sertonix
a3efb1ddad Change title in bottom nav menu from Private to Direct 2024-03-02 16:38:50 +01:00
Poesty Li
c201b1b918
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1188 of 1188 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2024-03-02 06:02:04 +01:00
josé m
a0e4472ef2
Translated using Weblate (Galician)
Currently translated at 100.0% (1188 of 1188 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2024-02-24 20:02:15 +01:00
Thomas
9e7e100009 Release 3.28.1 2024-02-23 18:10:48 +01:00
Thomas
01d57755c2 Add more pronouns 2024-02-23 17:29:26 +01:00
Thomas
2ecd51230f Merge branch 'improve_pronouns_support' into develop 2024-02-23 15:57:31 +01:00
Thomas
f87c63acec Add more pronouns 2024-02-23 15:57:12 +01:00
Thomas
53b8f9e159 Fix a layout issue 2024-02-23 15:52:50 +01:00
Thomas
c89df20a25 update list for pronouns 2024-02-23 10:31:54 +01:00
Thomas
26d51caaf8 Allow to disable pronouns support - Default enabled 2024-02-23 10:29:36 +01:00
Thomas
b943309dd2 Add localization for pronons support 2024-02-23 10:17:55 +01:00
Thomas
ff026e260a Merge remote-tracking branch 'origin/develop' into develop 2024-02-23 09:39:10 +01:00
Thomas
70405221ea clean code 2024-02-23 09:37:54 +01:00
Thomas
d0fb1b09dc Fix layout overlays 2024-02-23 09:33:52 +01:00
Thomas
28ca138e43 Fix #1040 - Fix a crash when reporting messages 2024-02-22 15:49:42 +01:00
Thomas
ecd25badf7 Fix #1043 - Fix a crash when adding a followed tag in empty list 2024-02-22 09:10:17 +01:00
Thomas
13946fe56e Fix a crash with media 2024-02-22 07:42:31 +01:00
ButterflyOfFire
2bc6ba97f9
Translated using Weblate (Arabic)
Currently translated at 85.3% (1012 of 1186 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ar/
Translation: Fedilab/Strings
2024-02-19 23:07:05 +01:00
Cosmin Humeniuc
29867ceaec
Translated using Weblate (Romanian)
Currently translated at 1.7% (1 of 57 strings)

Co-authored-by: Cosmin Humeniuc <cosmin@hume.ro>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/ro/
Translation: Fedilab/description
2024-02-10 07:02:05 +01:00
v1s7
54771716f0
Translated using Weblate (Russian)
Currently translated at 79.5% (943 of 1186 strings)

Co-authored-by: v1s7 <v1s7@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ru/
Translation: Fedilab/Strings
2024-02-09 23:02:13 +01:00
Cosmin Humeniuc
acc1bcd183
Translated using Weblate (Romanian)
Currently translated at 55.4% (658 of 1186 strings)

Co-authored-by: Cosmin Humeniuc <cosmin@hume.ro>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ro/
Translation: Fedilab/Strings
2024-02-09 23:02:12 +01:00
v1s7
d6a69ee982
Translated using Weblate (Russian)
Currently translated at 78.9% (936 of 1186 strings)

Co-authored-by: v1s7 <v1s7@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ru/
Translation: Fedilab/Strings
2024-02-09 05:02:02 +01:00
v1s7
4b3bb1f073
Translated using Weblate (Russian)
Currently translated at 8.7% (5 of 57 strings)

Co-authored-by: v1s7 <v1s7@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/ru/
Translation: Fedilab/description
2024-02-09 02:02:35 +01:00
Mikhail Kobuk
a9cd58ceab
Translated using Weblate (Russian)
Currently translated at 77.8% (923 of 1186 strings)

Co-authored-by: Mikhail Kobuk <arktixord@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ru/
Translation: Fedilab/Strings
2024-02-08 16:53:44 +01:00
v1s7
71de62a838
Translated using Weblate (Russian)
Currently translated at 77.8% (923 of 1186 strings)

Co-authored-by: v1s7 <v1s7@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ru/
Translation: Fedilab/Strings
2024-02-08 16:53:44 +01:00
Mikhail Kobuk
6ba902607c
Translated using Weblate (Russian)
Currently translated at 77.8% (923 of 1186 strings)

Co-authored-by: Mikhail Kobuk <arktixord@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ru/
Translation: Fedilab/Strings
2024-02-08 16:49:39 +01:00
v1s7
70f754040c
Translated using Weblate (Russian)
Currently translated at 77.8% (923 of 1186 strings)

Co-authored-by: v1s7 <v1s7@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ru/
Translation: Fedilab/Strings
2024-02-08 16:49:39 +01:00
ButterflyOfFire
d4819a19b7
Translated using Weblate (French)
Currently translated at 99.0% (1175 of 1186 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translation: Fedilab/Strings
2024-02-08 16:49:39 +01:00
Thomas
2f11eaf315 Fix a crash with profiles 2024-02-02 18:44:23 +01:00
Thomas
096dc6e98c Fix a crash with back button 2024-02-02 18:28:33 +01:00
Thomas
227d2ef58b Fix a crash when switching to remote profiles 2024-02-02 18:15:42 +01:00
josé m
3b677fa0a1
Translated using Weblate (Galician)
Currently translated at 100.0% (1186 of 1186 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2024-01-31 11:57:07 +01:00
josé m
1b9d7fdb75
Translated using Weblate (Galician)
Currently translated at 17.5% (10 of 57 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/gl/
Translation: Fedilab/description
2024-01-31 11:56:58 +01:00
Thomas
c44330d291 Release 3.28.0 2024-01-30 17:51:42 +01:00
Thomas
8832e89539 Fix issue #1032 - Sharing videos only download them 2024-01-30 17:37:34 +01:00
Thomas
9a8d359592 Fix #1030 - Delay for timed mute sent in ms 2024-01-30 16:48:58 +01:00
Thomas
57decc15bd Merge remote-tracking branch 'origin/develop' into develop 2024-01-30 16:43:39 +01:00
Thomas
4b051a42ae Add position/time for videos 2024-01-30 16:43:10 +01:00
Thomas
6b75118926 Fix self pronouns applied in notifications 2024-01-30 16:12:27 +01:00
Thomas
ec44a6e4f3 parse html for pronouns 2024-01-30 15:53:12 +01:00
claleb
3bda4b3f79
Translated using Weblate (German)
Currently translated at 100.0% (1186 of 1186 strings)

Co-authored-by: claleb <weblate@claleb.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2024-01-30 06:01:58 +01:00
Thomas
0400f316e6 Release notes 2024-01-29 17:26:53 +01:00
Thomas
27436916ee Release 3.28-beta-1 2024-01-29 17:25:42 +01:00
Thomas
4055f1b8c5 Merge remote-tracking branch 'origin/develop' into develop 2024-01-29 17:20:44 +01:00
Thomas
40251607e5 Fix peertuble toggle button 2024-01-29 17:19:32 +01:00
Thomas
bdbfd005fa Fix controller with Media3 2024-01-29 15:24:34 +01:00
Thomas
1ad956e965 change currentAccount with a getter 2024-01-29 15:02:56 +01:00
Lukas
44f1a4faa0
Translated using Weblate (Polish)
Currently translated at 100.0% (1186 of 1186 strings)

Co-authored-by: Lukas <ox86udoo@duck.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/pl/
Translation: Fedilab/Strings
2024-01-27 23:01:57 +01:00
Lukas
5ecc2a5521
Translated using Weblate (Polish)
Currently translated at 60.0% (33 of 55 strings)

Co-authored-by: Lukas <ox86udoo@duck.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/pl/
Translation: Fedilab/description
2024-01-27 20:02:05 +01:00
josé m
275f3d78b6
Translated using Weblate (Galician)
Currently translated at 12.7% (7 of 55 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/gl/
Translation: Fedilab/description
2024-01-27 20:02:03 +01:00
Thomas
1274d83ee1 Merge remote-tracking branch 'origin/develop' into develop 2024-01-27 17:58:43 +01:00
Thomas
bba6a2edf1 Fix a crash with profiles 2024-01-27 17:58:14 +01:00
Oğuz Ersen
5f0435e447
Translated using Weblate (Turkish)
Currently translated at 100.0% (1186 of 1186 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2024-01-27 07:01:47 +01:00
Lukáš Jelínek
63ceca066f
Translated using Weblate (Czech)
Currently translated at 100.0% (1186 of 1186 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2024-01-26 10:01:53 +01:00
Salif Mehmed
7c5f1dccab
Translated using Weblate (Bulgarian)
Currently translated at 52.2% (620 of 1186 strings)

Co-authored-by: Salif Mehmed <mail@salif.eu>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/bg/
Translation: Fedilab/Strings
2024-01-25 05:01:51 +01:00
Thomas
d5aaa94045 Fix issue #1031 - Compile error for google flavor 2024-01-24 10:38:16 +01:00
Thomas
394d72bd01 Support pronouns 2024-01-23 19:03:41 +01:00
Thomas
7ab24d900b Indicator with pronouns 2024-01-23 18:48:42 +01:00
Thomas
773322b60a empty space with media 2024-01-23 17:50:58 +01:00
Thomas
ccce81c49c Merge branch 'develop' of https://codeberg.org/tom79/Fedilab into develop 2024-01-23 16:49:57 +01:00
Thomas
eb89b0a8b2 Merge branch 'fix_511' into develop 2024-01-23 16:49:48 +01:00
Thomas
067fc68402 clean 2024-01-23 16:02:28 +01:00
Thomas
dc72d9a8b0 improve media 2024-01-23 10:07:43 +01:00
Thomas
18b3f0cb26 improve media 2024-01-23 10:06:02 +01:00
Thomas
b16ecbbe72 improve media 2024-01-23 10:03:20 +01:00
Thomas
e18db746a0 Some changes 2024-01-22 17:16:09 +01:00
Thomas
a2c865a4b7 Some fixes 2024-01-22 09:49:08 +01:00
Oğuz Ersen
4de9b0bd85
Translated using Weblate (Turkish)
Currently translated at 100.0% (1185 of 1185 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2024-01-21 14:42:34 +01:00
Thomas
c778c71306 Prepare Media3 2024-01-20 18:10:05 +01:00
Thomas
596c546cce Release 3.27.1 2024-01-19 17:51:56 +01:00
Thomas
d2535fd2f8 Allow to display banner as a media 2024-01-19 17:44:45 +01:00
Thomas
4204f658d7 Fix issue #1014 - Wrong profiles when enabling remote conversations 2024-01-19 17:44:16 +01:00
Thomas
dd119449f3 Fix Peertube instances search 2024-01-19 17:06:20 +01:00
Thomas
fec7039fbe Fix Peertube local timelines 2024-01-19 16:01:19 +01:00
Thomas
5692573d9c Fix issue #1011 - Position lost when switching between accounts 2024-01-19 14:30:29 +01:00
Thomas
1e03a039b8 Merge branch 'develop' of https://codeberg.org/tom79/Fedilab into develop 2024-01-19 11:35:24 +01:00
Thomas
6d411a5033 Merge branch 'fix_510' into develop 2024-01-19 11:35:15 +01:00
Thomas
dd2c1c5bef Fix some crashes 2024-01-19 11:34:53 +01:00
Nils Van Zuijlen
af149351b8
Translated using Weblate (French)
Currently translated at 7.4% (4 of 54 strings)

Co-authored-by: Nils Van Zuijlen <nils.van-zuijlen@mailo.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/fr/
Translation: Fedilab/description
2024-01-18 08:31:09 +01:00
Thomas
a1a6005ca0 Fix potential crashes when clicking on notifications 2024-01-17 14:12:56 +01:00
Scott Starkey
afc9b4712c
Translated using Weblate (Esperanto)
Currently translated at 63.0% (747 of 1185 strings)

Co-authored-by: Scott Starkey <yekrats@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/eo/
Translation: Fedilab/Strings
2024-01-17 08:06:21 +01:00
Lukáš Jelínek
542d26d88d
Translated using Weblate (Czech)
Currently translated at 100.0% (1185 of 1185 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2024-01-17 08:06:19 +01:00
RintanBroadleaf
8953a930a0
Translated using Weblate (Japanese)
Currently translated at 100.0% (1185 of 1185 strings)

Co-authored-by: RintanBroadleaf <rintanbroadleaf@outlook.jp>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ja/
Translation: Fedilab/Strings
2024-01-16 20:06:14 +01:00
josé m
e248fc65bf
Translated using Weblate (Galician)
Currently translated at 100.0% (1185 of 1185 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2024-01-16 20:06:14 +01:00
Scott Starkey
76d5ed41e9
Translated using Weblate (Esperanto)
Currently translated at 62.9% (746 of 1185 strings)

Co-authored-by: Scott Starkey <yekrats@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/eo/
Translation: Fedilab/Strings
2024-01-16 20:06:14 +01:00
claleb
bf6830deb3
Translated using Weblate (German)
Currently translated at 100.0% (1185 of 1185 strings)

Co-authored-by: claleb <weblate@claleb.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2024-01-16 20:06:14 +01:00
Thomas
1f2e6c4327 Merge branch 'develop' 2024-01-16 17:54:54 +01:00
Thomas
886b74c171 Release 3.27.0 2024-01-16 17:51:18 +01:00
Thomas
246bc7e557 Fix dialog colors in Settings 2024-01-16 17:45:55 +01:00
Thomas
aa45683a4f Fix issue #1016 - Content Warning not applied to quoted messages 2024-01-16 10:46:09 +01:00
Thomas
9d1435c1b5 Markdown support disabled by default 2024-01-16 10:32:35 +01:00
Thomas
1a237e00c3 Clean class 2024-01-16 10:32:35 +01:00
Thomas
fbf2d72f5d Fix a crash with wrong Serialization 2024-01-16 10:32:34 +01:00
Salif Mehmed
169b00af30
Translated using Weblate (Bulgarian)
Currently translated at 50.8% (602 of 1185 strings)

Co-authored-by: Salif Mehmed <mail@salif.eu>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/bg/
Translation: Fedilab/Strings
2024-01-16 05:06:35 +01:00
Scott Starkey
018b2b218e
Translated using Weblate (Esperanto)
Currently translated at 58.6% (695 of 1185 strings)

Co-authored-by: Scott Starkey <yekrats@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/eo/
Translation: Fedilab/Strings
2024-01-16 05:06:35 +01:00
Thomas
a399ce674b Release 3.27-beta-4 2024-01-15 17:29:34 +01:00
Thomas
a9e17fb5d6 Fix issue #1017 - Usage frequency of tags when composing 2024-01-15 17:26:29 +01:00
Thomas
892bb521e5 Some improvements 2024-01-15 17:26:29 +01:00
Thomas
e20fdafaa5 Fix crash with profile and context 2024-01-15 17:26:29 +01:00
josé m
7e36abb24d
Translated using Weblate (Galician)
Currently translated at 100.0% (1183 of 1183 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2024-01-15 16:08:15 +01:00
ButterflyOfFire
e3d2e4a8a8
Translated using Weblate (French)
Currently translated at 99.1% (1173 of 1183 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translation: Fedilab/Strings
2024-01-15 16:08:15 +01:00
Thomas
82a7a845f6 Merge pull request 'Fix issue templates' (#1022) from aur/Fedilab:fix_issue_templates_contributing into develop
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/1022
2024-01-15 15:08:02 +00:00
aur
7bca968aff
Fix issue templates 2024-01-15 19:17:49 +09:00
Thomas
4d66a3f9a8 Merge pull request 'Allow to disable scrolling of the top bar in settings' (#1021) from aur/Fedilab:disable_topbar_scrolling into develop
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/1021
2024-01-15 08:09:13 +00:00
Thomas
2d9104c352 Merge remote-tracking branch 'origin/develop' into develop 2024-01-15 09:06:45 +01:00
Thomas
c40f6c3330 Fix a crash when searching tags 2024-01-15 09:06:30 +01:00
Salif Mehmed
56bbb7f9ac
Translated using Weblate (Bulgarian)
Currently translated at 36.2% (429 of 1183 strings)

Co-authored-by: Salif Mehmed <mail@salif.eu>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/bg/
Translation: Fedilab/Strings
2024-01-14 12:31:37 +00:00
Thomas
6b03aef11b Beta 3.27-3 2024-01-13 16:32:28 +01:00
Thomas
68b2c72414 Fix remember position 2024-01-13 16:27:35 +01:00
aur
1c01c018a0
Allow to disable scrolling of the top bar in settings 2024-01-13 12:02:06 +09:00
Thomas
aeb45f0bcf Release 3.27-beta-2 2024-01-12 20:34:46 +01:00
Thomas
b75d274fe8 Merge branch 'develop' of https://codeberg.org/tom79/Fedilab into develop 2024-01-12 18:13:46 +01:00
Thomas
eade00b0ba Release 3.27-beta-1 2024-01-12 18:13:28 +01:00
Software In Interlingua
bc8215b817
Translated using Weblate (Interlingua)
Currently translated at 23.7% (281 of 1183 strings)

Co-authored-by: Software In Interlingua <softinterlingua@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ia/
Translation: Fedilab/Strings
2024-01-12 17:06:21 +01:00
Thomas
381d217bd5 Merge branch 'develop' of https://codeberg.org/tom79/Fedilab into develop 2024-01-12 16:28:32 +01:00
Thomas
318b869b6a Fix hashtags 2024-01-12 16:19:30 +01:00
Thomas
336fe936e8 Fix a crash 2024-01-12 11:19:25 +01:00
Thomas
16686e88eb Fix a crash 2024-01-12 11:11:57 +01:00
Thomas
e589e2549d Keep intent longer 2024-01-12 11:05:42 +01:00
Thomas
504e30c44a Cached intent for media 2024-01-12 10:36:29 +01:00
Thomas
09a8b6c43c Broadcast 2024-01-11 17:05:35 +01:00
Thomas
b052547cc4 Working pagers 2024-01-11 12:02:22 +01:00
Thomas
08c1ba943b Pass ids for pagers 2024-01-11 10:58:22 +01:00
Thomas
85e462e276 Apply for status 2024-01-10 15:58:27 +01:00
Salif Mehmed
0d4cde5a45
Translated using Weblate (Bulgarian)
Currently translated at 32.0% (379 of 1183 strings)

Co-authored-by: Salif Mehmed <mail@salif.eu>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/bg/
Translation: Fedilab/Strings
2024-01-10 01:06:17 +00:00
Thomas
1ff15b0f93 Some cleaning 2024-01-09 16:00:35 +01:00
Thomas
2edfd8c945 All accounts are cached in db before being in Intent 2024-01-09 15:57:12 +01:00
Thomas
2e20e78b7f Apply changes when serializing accounts 2024-01-09 11:39:09 +01:00
Thomas
8040a06300 Prepare db for caching bundle + logic to pass/get data 2024-01-08 11:51:19 +01:00
Salif Mehmed
ad3be92890
Translated using Weblate (Bulgarian)
Currently translated at 24.9% (295 of 1183 strings)

Co-authored-by: Salif Mehmed <mail@salif.eu>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/bg/
Translation: Fedilab/Strings
2024-01-08 03:06:16 +00:00
Salif Mehmed
0a14ea4b2f
Translated using Weblate (Bulgarian)
Currently translated at 20.2% (239 of 1183 strings)

Co-authored-by: Salif Mehmed <mail@salif.eu>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/bg/
Translation: Fedilab/Strings
2024-01-03 05:07:41 +00:00
claleb
310f3f5eda
Translated using Weblate (German)
Currently translated at 100.0% (1183 of 1183 strings)

Co-authored-by: claleb <weblate@claleb.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2024-01-03 05:07:40 +00:00
Salif Mehmed
57c66b541e
Translated using Weblate (Bulgarian)
Currently translated at 4.0% (2 of 49 strings)

Co-authored-by: Salif Mehmed <mail@salif.eu>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/bg/
Translation: Fedilab/description
2024-01-03 00:06:47 +01:00
Weblate
6d7173a487
Added translation using Weblate (Bulgarian)
Co-authored-by: Weblate <noreply@weblate.org>
2024-01-02 00:11:06 +01:00
Poesty Li
e16bfcbe1a
Translated using Weblate (Chinese (Simplified))
Currently translated at 99.5% (1178 of 1183 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2024-01-01 06:08:30 +01:00
Thomas
36e258adb3 Fix a crash with media 2023-12-29 10:29:22 +01:00
Software In Interlingua
951b3bcb06
Translated using Weblate (Interlingua)
Currently translated at 21.5% (255 of 1183 strings)

Co-authored-by: Software In Interlingua <softinterlingua@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ia/
Translation: Fedilab/Strings
2023-12-29 08:10:21 +01:00
ButterflyOfFire
60301948b0
Translated using Weblate (French)
Currently translated at 98.9% (1170 of 1183 strings)

Translated using Weblate (Arabic)

Currently translated at 82.7% (979 of 1183 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ar/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translation: Fedilab/Strings
2023-12-29 08:10:20 +01:00
josé m
268ed75fc0
Translated using Weblate (Galician)
Currently translated at 100.0% (1183 of 1183 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2023-12-23 20:09:00 +00:00
Thomas
a1370693fb Merge branch 'develop' 2023-12-23 17:16:02 +01:00
Thomas
fcc323af2a Release 3.26.0 2023-12-23 17:13:27 +01:00
Thomas
38f7760261 Release 3.26.0 2023-12-23 17:13:19 +01:00
Thomas
d1f9378a11 Fix an issue with poll 2023-12-23 17:12:07 +01:00
Thomas
25c237f0a8 Fix theme issues 2023-12-23 17:01:26 +01:00
Thomas
d60900e2f9 Fix custom colors for Android 14 2023-12-23 16:37:47 +01:00
Thomas
0b24f02bf4 Update release notes 2023-12-23 12:27:43 +01:00
Thomas
893b060a5f Fix issue #982 - Make link clickable in media descriptions 2023-12-23 12:26:52 +01:00
Thomas
bc3b07d774 Merge branch 'develop' of https://codeberg.org/tom79/Fedilab into develop 2023-12-23 10:41:06 +01:00
Thomas
b5d7ce2de0 Prepare release 3.26.0 - Production 2023-12-23 10:40:54 +01:00
claleb
1c2db9362a
Translated using Weblate (German)
Currently translated at 100.0% (1183 of 1183 strings)

Co-authored-by: claleb <weblate@claleb.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2023-12-23 06:09:05 +00:00
Thomas
bc0d25c826 Release 3.25.3 - beta 2023-12-22 18:03:10 +01:00
Thomas
f7714fa738 Fix issue #894 - Add a search bar for custom emojis 2023-12-22 17:55:28 +01:00
Thomas
fadf3fb788 Emoji not displayed in the picker 2023-12-22 16:49:01 +01:00
Thomas
6ee9601e23 Fix an issue with poll and Pleroma 2023-12-22 15:31:35 +01:00
Thomas
6b876158d5 Improve scroll bar 2023-12-22 15:02:47 +01:00
Thomas
8b8e163151 Merge remote-tracking branch 'origin/develop' into develop 2023-12-22 11:03:16 +01:00
Thomas
1c529c52e1 Clear 2023-12-22 11:00:32 +01:00
Thomas
daf3e08995 Fix issue #996 - Crashes with profiles 2023-12-22 11:00:12 +01:00
Oğuz Ersen
90aff02e2d
Translated using Weblate (Turkish)
Currently translated at 100.0% (1183 of 1183 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2023-12-22 07:07:27 +01:00
Thomas
24fbf41d08 Fix issue #994 - Add a scroll bar for timelines (default: disabled) 2023-12-21 18:11:51 +01:00
Thomas
7554238052 Merge remote-tracking branch 'origin/develop' into develop 2023-12-21 09:55:58 +01:00
Thomas
2f60a6496f Fix prompt to split asked several times when refusing 2023-12-21 09:54:37 +01:00
Oğuz Ersen
1b12ee4962
Translated using Weblate (Turkish)
Currently translated at 100.0% (1181 of 1181 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2023-12-20 19:10:00 +01:00
josé m
712c4254ce
Translated using Weblate (Galician)
Currently translated at 100.0% (1181 of 1181 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2023-12-20 19:10:00 +01:00
claleb
3fe9a4ec0c
Translated using Weblate (German)
Currently translated at 100.0% (1181 of 1181 strings)

Co-authored-by: claleb <weblate@claleb.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2023-12-20 19:10:00 +01:00
Thomas
1387a6f2a9 Release 3.25.2 - beta 2023-12-19 17:31:59 +01:00
Thomas
5aae22483f Fix #1004 - Order followed tags (ASC) 2023-12-19 17:23:15 +01:00
Thomas
520a36946a Fix #1005 - Avoid duplicate tags when following 2023-12-19 17:12:02 +01:00
Thomas
ace3dc089e Fix #1007 - Improve account picker when opening a message with another account 2023-12-19 16:03:02 +01:00
Thomas
b6cbd8ea74 Fix #1008 -Allow to copy app version from the about page 2023-12-19 15:56:26 +01:00
Thomas
4ce92e67b1 Fix #1009 -Hide emoji picker when instance has no custom emojis 2023-12-19 15:35:57 +01:00
Thomas
0fe27e5a76 Fix an issue with back button for Followed Tags and List activities 2023-12-19 15:31:38 +01:00
Thomas
6149faee1d typo 2023-12-19 10:56:27 +01:00
Thomas
dade64750d Allow to underline clickable elements 2023-12-19 10:42:36 +01:00
Thomas
0064db72a1 Merge remote-tracking branch 'origin/develop' into develop 2023-12-19 10:31:38 +01:00
Thomas
bee3d772d5 Merge branch 'improve_buttons' into develop 2023-12-19 10:31:21 +01:00
Thomas
47441c3e05 More visible counters 2023-12-19 10:31:06 +01:00
Thomas
f7fc376482 Fix a crash when composing 2023-12-19 07:52:16 +01:00
Thomas
082ecce9be Counters close to button + add full date for messages (default: disabled) 2023-12-18 12:14:18 +01:00
josé m
85f8adbd76
Translated using Weblate (Galician)
Currently translated at 13.0% (6 of 46 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/gl/
Translation: Fedilab/description
2023-12-18 07:10:00 +01:00
josé m
58de6a86f5
Translated using Weblate (Galician)
Currently translated at 100.0% (1174 of 1174 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2023-12-18 06:08:48 +00:00
Thomas
4ebe2a2719 Release 3.25.1 2023-12-17 17:43:14 +01:00
Thomas
5b805ca9a9 Fix a crash 2023-12-17 17:41:17 +01:00
Thomas
6314e6c24a Merge remote-tracking branch 'origin/main' 2023-12-17 11:43:01 +01:00
Thomas
3148d5f9b2 Merge remote-tracking branch 'origin/develop' into develop 2023-12-17 11:42:01 +01:00
Thomas
21c193db90 Release 3.25.0 - beta release 2023-12-17 11:41:43 +01:00
Poesty Li
cc21471244
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1174 of 1174 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2023-12-17 10:31:08 +00:00
Oğuz Ersen
6d9bdb183c
Translated using Weblate (Turkish)
Currently translated at 100.0% (1174 of 1174 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2023-12-17 10:31:07 +00:00
Stefano Volpe
1858deffd3
Translated using Weblate (Italian)
Currently translated at 100.0% (1174 of 1174 strings)

Co-authored-by: Stefano Volpe <foxy@teapot.ovh>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/it/
Translation: Fedilab/Strings
2023-12-17 10:31:06 +00:00
josé m
9b6161d8fe
Translated using Weblate (Galician)
Currently translated at 100.0% (1174 of 1174 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2023-12-17 10:31:05 +00:00
claleb
3282f9f1db
Translated using Weblate (German)
Currently translated at 100.0% (1174 of 1174 strings)

Co-authored-by: claleb <weblate@claleb.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2023-12-17 10:31:05 +00:00
Matyáš Caras
3fa91ed7dc
Translated using Weblate (Czech)
Currently translated at 100.0% (1174 of 1174 strings)

Co-authored-by: Matyáš Caras <contact@hernikplays.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2023-12-17 10:31:04 +00:00
ButterflyOfFire
be18db0649
Translated using Weblate (Arabic)
Currently translated at 81.7% (960 of 1174 strings)

Translated using Weblate (Arabic)

Currently translated at 80.4% (938 of 1166 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ar/
Translation: Fedilab/Strings
2023-12-17 10:31:03 +00:00
Thomas
06e8640219 Merge remote-tracking branch 'origin/develop' into develop 2023-12-17 11:30:48 +01:00
Stefano Volpe
ec9ccf8adf
Translated using Weblate (Italian)
Currently translated at 11.3% (5 of 44 strings)

Co-authored-by: Stefano Volpe <foxy@teapot.ovh>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/it/
Translation: Fedilab/description
2023-12-16 20:08:02 +00:00
Kaambiz
d25751972e
Translated using Weblate (Persian)
Currently translated at 11.3% (5 of 44 strings)

Co-authored-by: Kaambiz <kambizx@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/fa/
Translation: Fedilab/description
2023-12-16 20:08:00 +00:00
Thomas
197702b461 Fix profiles 2023-12-16 18:38:19 +01:00
Thomas
a9712d45b3 Fix mentions length count 2023-12-15 17:37:00 +01:00
Thomas
4711d5fd23 Make links and media clickable when composing 2023-12-15 15:15:38 +01:00
Thomas
0861becc56 Hyper links clickable in original messages when replying 2023-12-15 14:15:45 +01:00
Thomas
739912fd60 clean code 2023-12-15 13:54:39 +01:00
Thomas
c895b1c4af Change dialog title 2023-12-15 13:48:51 +01:00
Thomas
0ea5791fbd Add options in settings for automatically thread long messages (ASK, Disable, Enable) 2023-12-15 09:22:27 +01:00
Thomas
bfc362ef62 Automatically split long copied/pasted messages into threads 2023-12-14 16:55:49 +01:00
Thomas
f5bfb3e250 Allow indentation of thread up to 40 2023-12-14 14:47:06 +01:00
Thomas
cfad7131d2 Fix a crash 2023-12-14 11:16:55 +01:00
Thomas
f0324038e3 Fix issue #990 #421 - Crash during transitions 2023-12-14 10:56:09 +01:00
Thomas
0982e1bb38 Fix issue #997 - Store endpoint and create it only if it changes 2023-12-14 10:01:55 +01:00
Thomas
eb0ecd7583 Fix issue #997 - Store endpoint and create it only if it changes 2023-12-14 08:03:47 +01:00
Thomas
0d949f8dd6 some cleaning 2023-12-14 07:33:59 +01:00
Thomas
e6b34dd622 Fix somme issues with worker and Android 14 2023-12-13 18:04:15 +01:00
Thomas
5cca26e0c4 Cleaning code 1 2023-12-13 17:15:18 +01:00
Thomas
25e42fc7da Set app Id when sending BroadCasts 2023-12-12 17:27:05 +01:00
Thomas
9225ec35b5 Use ContextCompat 2023-12-12 16:07:48 +01:00
Thomas
4a07e7c5b8 Upgrade for Android 14+ 2023-12-12 15:47:15 +01:00
Thomas
4c911705ab fix issue #995 - Don't display translate popup if there is only one choice 2023-12-11 17:35:20 +01:00
Thomas
cf01ee77e1 Merge branch 'develop' of https://codeberg.org/tom79/Fedilab into develop 2023-12-11 10:15:17 +01:00
Thomas
e50a267309 Change regex 2023-12-11 10:13:37 +01:00
Thomas
90e45a2763 Merge pull request 'Adjust right margin of action buttons' (#984) from aur/Fedilab:improve_action_buttons_margin into develop
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/984
2023-12-11 09:03:33 +00:00
Thomas
3e033f517d Merge pull request 'Improve login screen UX' (#983) from aur/Fedilab:develop into develop
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/983
2023-12-09 09:27:17 +00:00
Thomas
f361cc568a Merge pull request 'Fixes #966 - Allow opening toots and profile from server-remote accounts' (#969) from dureuill/Fedilab:dureuill-966 into main
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/969
2023-12-09 09:26:14 +00:00
Software In Interlingua
1fd2515806
Translated using Weblate (Interlingua)
Currently translated at 21.0% (245 of 1165 strings)

Co-authored-by: Software In Interlingua <softinterlingua@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ia/
Translation: Fedilab/Strings
2023-12-01 09:04:22 +01:00
Software In Interlingua
e9cb3c52ff
Added translation using Weblate (Interlingua)
Co-authored-by: Software In Interlingua <softinterlingua@gmail.com>
2023-11-28 19:03:59 +01:00
aur
6071e0bd23
Merge remote-tracking branch 'upstream/develop' into develop 2023-11-28 21:37:59 +09:00
Reza Almanda
00b5da0fe2
Translated using Weblate (Indonesian)
Currently translated at 4.5% (2 of 44 strings)

Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/id/
Translation: Fedilab/description
2023-11-12 12:02:48 +01:00
Reza Almanda
facb376676
Translated using Weblate (Indonesian)
Currently translated at 59.7% (696 of 1165 strings)

Co-authored-by: Reza Almanda <rezaalmanda27@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/id/
Translation: Fedilab/Strings
2023-11-12 12:00:43 +01:00
Bai
ddc0dda209
Translated using Weblate (Turkish)
Currently translated at 100.0% (1165 of 1165 strings)

Co-authored-by: Bai <batuhanakkurt000@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2023-10-23 03:01:30 +02:00
Weblate
809a064e9f
Added translation using Weblate (xx_XX (generated) (xx_XX))
Co-authored-by: Weblate <noreply@weblate.org>
2023-10-21 23:45:04 +00:00
aur
6e653693e7
Add marginEnd to action buttons
to make it easier to tap the overflow menu icon
on the right edge of the action buttons.
2023-10-19 13:48:21 +09:00
aur
6ae6ff8484
Change imeOptions from actionNext to actionDone
After entering an instance name on the login screen,
the soft input method is now automatically hidden.
2023-10-18 23:46:33 +09:00
josé m
655cfa0e75
Translated using Weblate (Galician)
Currently translated at 6.8% (3 of 44 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/gl/
Translation: Fedilab/description
2023-10-16 06:06:44 +02:00
dureuill
b3458e2888 Allow opening toots and profile from server-remote accounts
When opening a link with Fedilab, it can be opened as a status (toot) or a profile even if it is an object that is remote to the server it is requested from.
2023-10-01 20:50:02 +00:00
aur
12ff7e09a6
Translated using Weblate (Japanese)
Currently translated at 100.0% (1165 of 1165 strings)

Co-authored-by: aur <noreply@aurantiacus.bsvr.net>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ja/
Translation: Fedilab/Strings
2023-09-28 15:59:56 +02:00
Ettore Atalan
1423aa37f7
Translated using Weblate (German)
Currently translated at 100.0% (1165 of 1165 strings)

Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2023-09-23 05:00:24 +00:00
Roberto Michán Sánchez
9f57fda4ec
Translated using Weblate (Spanish)
Currently translated at 9.0% (4 of 44 strings)

Co-authored-by: Roberto Michán Sánchez <robertoms258@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/es/
Translation: Fedilab/description
2023-09-19 09:59:29 +00:00
Jean-Luc Tibaux
caaa791760
Translated using Weblate (German)
Currently translated at 18.1% (8 of 44 strings)

Co-authored-by: Jean-Luc Tibaux <eugentoptic@outlook.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/de/
Translation: Fedilab/description
2023-09-17 12:59:35 +02:00
Thomas
ec50416a8b Merge remote-tracking branch 'origin/develop' into develop 2023-09-14 11:14:11 +02:00
Thomas
61ff1608f8 Fix polls 2023-09-14 11:14:01 +02:00
Eduardo
6185597436
Translated using Weblate (Portuguese)
Currently translated at 85.8% (1000 of 1165 strings)

Co-authored-by: Eduardo <edu200399lim@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/pt/
Translation: Fedilab/Strings
2023-09-13 11:58:00 +02:00
Lukáš Jelínek
f4155f9c43
Translated using Weblate (Czech)
Currently translated at 100.0% (1165 of 1165 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2023-09-13 11:58:00 +02:00
ButterflyOfFire
484824d75f
Translated using Weblate (French)
Currently translated at 99.9% (1164 of 1165 strings)

Translated using Weblate (Arabic)

Currently translated at 80.0% (933 of 1165 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ar/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translation: Fedilab/Strings
2023-09-13 11:57:59 +02:00
Thomas
14127fd203 Merge branch 'fix_500' into develop 2023-09-11 11:03:07 +02:00
Thomas
b6659de483 Fix a crash 2023-09-11 11:02:55 +02:00
Thomas
ac6d9f430e forward intent when changing the account 2023-09-10 11:57:30 +02:00
Thomas
d7d5f286f5 Notifications 2023-09-10 09:55:44 +02:00
Thomas
86dc5d2f13 Fix duplicated messages in threads 2023-09-09 16:25:21 +02:00
Thomas
dbc118064c Merge branch 'direct_messages_chat' into develop 2023-09-09 16:11:06 +02:00
Thomas
392efd2c0c Fix Direct messages 2023-09-09 16:10:33 +02:00
Thomas
63e444ffb5 Merge pull request 'fix-lock' (#959) from s1m/Fedilab:fix-lock into develop
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/959
2023-09-09 13:36:12 +00:00
sim
a441ce8ff4 Fix IllegalMonitorStateException
When we unlock a lock the thread don't Held
2023-09-09 14:39:21 +02:00
Thomas
57860793ff Merge pull request 'Set one lock per account for push notif' (#956) from s1m/Fedilab:push-multi-lock into develop
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/956
2023-09-05 04:53:33 +00:00
sim
ceebe74ec1 Set one lock per account for push notif 2023-09-05 00:08:48 +02:00
Thomas
5f3ca62393 Fix issue #955 - Focus the clicked messages and allow selections for remote conversations 2023-09-04 17:17:15 +02:00
Thomas
95e4952012 More visible cursor when adding media descriptions 2023-09-04 11:20:29 +02:00
Thomas
0b6c1d6504 Merge branch 'main' into develop
# Conflicts:
#	app/src/main/java/app/fedilab/android/mastodon/helper/NotificationsHelper.java
2023-09-04 11:07:14 +02:00
Thomas
c5e35b6b48 Merge pull request 'Update fastlane description based on readme (full list of features and supported servers)' (#939) from Efreak/Fedilab:efreak-patch-1-update-fastlane-description into develop
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/939
2023-09-04 09:05:14 +00:00
Thomas
e735947362 Merge pull request 'Fix missing push notifications' (#946) from s1m/Fedilab:fix-push into main
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/946
2023-09-04 09:04:18 +00:00
Thomas
b8c96e872f Add custom user agent 2023-09-04 11:02:40 +02:00
Thomas
5204974688 Merge remote-tracking branch 'origin/develop' into develop 2023-09-04 10:44:48 +02:00
Thomas
b62982074a Apply markdown only for message content 2023-09-04 10:44:30 +02:00
RintanBroadleaf
8f90cbbbe9
Translated using Weblate (Japanese)
Currently translated at 100.0% (1165 of 1165 strings)

Co-authored-by: RintanBroadleaf <rintanbroadleaf@outlook.jp>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ja/
Translation: Fedilab/Strings
2023-09-02 06:57:14 +02:00
sim
36b28a4483 Fix missing push notifications
since_ids and last_notifid_reccorded often contained the same id
so fetching notification was aborted. It looks like these variables
were introduced to avoid multiple fetches at the same time.
So this commit adds a reentrantLock with a tryLock to do it
2023-08-28 23:16:21 +02:00
Thomas
ce92f23c50 Release 3.24.1 2023-08-26 18:18:45 +02:00
Thomas
1bc12d3202 Fix custom instance max length not working 2023-08-26 18:11:26 +02:00
Thomas
2e4c3bf119 Cursor more visible when composing 2023-08-26 18:01:15 +02:00
Thomas
185479287c Change tabs in profiles 2023-08-26 17:53:55 +02:00
Thomas
a9da1752b2 New logos 2023-08-26 12:15:28 +02:00
Thomas
23cce968f8 Mardown: stop parsing tags and support strike text 2023-08-25 18:26:43 +02:00
Thomas
dd83e86382 Fix an issue when opening URLs 2023-08-25 17:46:18 +02:00
Thomas
c43fe177b5 Merge remote-tracking branch 'origin/develop' into develop 2023-08-25 14:15:08 +02:00
Thomas
14515e6ba6 keep position with remote conversations 2023-08-25 14:14:53 +02:00
Thomas
d017952ffe Bug with long values 2023-08-25 13:36:20 +02:00
Eryk Michalak
6bbf10ae7d
Translated using Weblate (Polish)
Currently translated at 99.9% (1164 of 1165 strings)

Co-authored-by: Eryk Michalak <gnu.ewm@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/pl/
Translation: Fedilab/Strings
2023-08-25 09:59:55 +02:00
Efreak
c0f1b3859f Update fastlane description based on readme (full list of features and supported servers) 2023-08-23 21:24:58 +00:00
Thomas
30f3ab8dca Merge remote-tracking branch 'origin/develop' into develop 2023-08-23 09:35:54 +02:00
Thomas
14bd5e9750 Fix a crash when instance is null 2023-08-23 09:35:35 +02:00
Gaute Holmin
6805c214bc
Translated using Weblate (Norwegian Bokmål)
Currently translated at 6.9% (3 of 43 strings)

Co-authored-by: Gaute Holmin <gaute@topdogs.no>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/nb_NO/
Translation: Fedilab/description
2023-08-22 20:48:34 +02:00
Thomas
23aec94de7 Merge pull request 'accent_color_corrections' (#934) from Zekovski/Fedilab:accent_color_corrections into develop
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/934
2023-08-21 18:45:38 +00:00
Thomas
50e88b3f86 Fix not clickable tags for some languages 2023-08-21 17:17:27 +02:00
Thomas
c646e9136c Crash when several gif in same message 2023-08-21 16:49:17 +02:00
Thomas
8b1d7d11fe Merge remote-tracking branch 'origin/develop' into develop 2023-08-21 16:41:05 +02:00
Thomas
12c4e0465e fix poll max chars 2023-08-21 16:40:59 +02:00
Zekovski
c1343f0ef0
Translated using Weblate (French)
Currently translated at 99.7% (1162 of 1165 strings)

Co-authored-by: Zekovski <zekovski@e.email>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translation: Fedilab/Strings
2023-08-21 03:51:03 +02:00
josé m
72df83cbb3
Translated using Weblate (Galician)
Currently translated at 100.0% (1165 of 1165 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2023-08-19 06:28:19 +02:00
Henrik Sørensen
8949bc660e
Translated using Weblate (Danish)
Currently translated at 4.6% (2 of 43 strings)

Co-authored-by: Henrik Sørensen <henrik@millinge.dk>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/da/
Translation: Fedilab/description
2023-08-18 23:53:28 +02:00
Thomas
f11a5c544e make mastodon as default one 2023-08-18 17:47:50 +02:00
Poesty Li
f528fd3e6e
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1165 of 1165 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2023-08-18 03:55:29 +02:00
ButterflyOfFire
15034de546
Translated using Weblate (French)
Currently translated at 99.4% (1159 of 1165 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translation: Fedilab/Strings
2023-08-18 03:55:29 +02:00
Lukáš Jelínek
ad70a1cf2f
Translated using Weblate (Czech)
Currently translated at 100.0% (1165 of 1165 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2023-08-18 03:55:29 +02:00
Michael Hainz
b7e05d48d4
Translated using Weblate (German)
Currently translated at 9.3% (4 of 43 strings)

Co-authored-by: Michael Hainz <michael@familyhainz.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/de/
Translation: Fedilab/description
2023-08-17 05:56:55 +02:00
Thomas
802b3b0926 Release 3.24.0 2023-08-16 17:59:30 +02:00
Thomas
cff4c96a04 some improvements 2023-08-16 16:43:32 +02:00
Thomas
fc643162fb Hide self messages 2023-08-16 16:34:54 +02:00
Thomas
1b80aa8df5 Some improvements 2023-08-16 16:21:54 +02:00
Thomas
c63ae5df4f Make buttons still visible when composing with media 2023-08-16 16:03:12 +02:00
Thomas
1965d196ae Merge remote-tracking branch 'origin/develop' into develop 2023-08-16 14:55:48 +02:00
Thomas
28ee0550ae Make buttons still visible when composing with media 2023-08-16 14:55:42 +02:00
claleb
a759640448
Translated using Weblate (German)
Currently translated at 100.0% (1163 of 1163 strings)

Co-authored-by: claleb <weblate@claleb.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2023-08-16 12:48:06 +02:00
Lukáš Jelínek
705e413661
Translated using Weblate (Czech)
Currently translated at 100.0% (1163 of 1163 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2023-08-16 12:48:05 +02:00
Thomas
d299c7020d CamelCase Tags when forwarding them 2023-08-16 10:22:21 +02:00
Thomas
8cffef92a5 Merge remote-tracking branch 'origin/develop' into develop 2023-08-16 10:09:49 +02:00
Thomas
c1b256f150 Fix spoiler issue when composing threads 2023-08-16 10:09:41 +02:00
Poesty Li
2137604cec
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1159 of 1159 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2023-08-15 17:29:33 +02:00
gbieging
bf200e6745
Translated using Weblate (Portuguese)
Currently translated at 85.4% (990 of 1159 strings)

Co-authored-by: gbieging <gaba_bieging@yahoo.com.br>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/pt/
Translation: Fedilab/Strings
2023-08-15 17:29:33 +02:00
ButterflyOfFire
c530749a46
Translated using Weblate (Arabic)
Currently translated at 78.8% (914 of 1159 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ar/
Translation: Fedilab/Strings
2023-08-15 17:29:33 +02:00
Thomas
eec448491f Release 3.23.5 2023-08-15 17:27:44 +02:00
Thomas
92d52bbecf Allow to hide self boosts and self replies 2023-08-15 17:22:07 +02:00
Thomas
302b3e47d4 More place for media description 2023-08-15 16:58:31 +02:00
Zekovski
ed9563d380 Replaced SDK version check with isDynamicColorAvailable before showing DynamicColors options. 2023-08-15 13:00:26 +02:00
Zekovski
49cd05ee35 Misnamed variable 2023-08-15 12:44:30 +02:00
Thomas
4c00aa9a2e Release 3.23.4 2023-08-14 17:22:51 +02:00
Thomas
be54c49918 Whole in timelines due to cache bug 2023-08-14 16:56:40 +02:00
Thomas
9b91ebf317 Fix tags issue with RTL and bookmark 2023-08-14 11:43:04 +02:00
Thomas
ac24e25da0 Fix #929 - Scrollable bio when editing profiles 2023-08-12 09:56:11 +02:00
Thomas
e07cb78d65 Merge remote-tracking branch 'origin/develop' into develop 2023-08-12 09:48:09 +02:00
Thomas
11559ac68a Fix issue #932 - Crash when user is not in a list 2023-08-12 09:48:03 +02:00
Lukáš Jelínek
cf4d418cd0
Translated using Weblate (Czech)
Currently translated at 100.0% (1159 of 1159 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2023-08-12 09:45:59 +02:00
josé m
68b9b03ec7
Translated using Weblate (Galician)
Currently translated at 100.0% (1159 of 1159 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2023-08-09 21:55:39 +02:00
Thomas
3d0d68d9f5 Release 3.23.3 2023-08-09 18:17:33 +02:00
Thomas
5a804fd892 Improve link side effect with Markdown 2023-08-09 17:56:47 +02:00
Thomas
c9a86aae01 Fix line breaks with Markdown 2023-08-09 17:18:18 +02:00
Thomas
147917fc58 Fix issue #930 - Crash with Pixelfed 2023-08-09 17:06:06 +02:00
Thomas
c017558984 Merge remote-tracking branch 'origin/develop' into develop 2023-08-09 16:59:47 +02:00
Thomas
65b9015d51 Fix issue #931 - Click on card does not open Mastodon posts inside the app 2023-08-09 16:59:40 +02:00
GunChleoc
24486ae041
Translated using Weblate (Gaelic)
Currently translated at 100.0% (1159 of 1159 strings)

Co-authored-by: GunChleoc <fios@foramnagaidhlig.net>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gd/
Translation: Fedilab/Strings
2023-08-09 03:18:50 +02:00
Thomas
243039151a Merge remote-tracking branch 'origin/develop' into develop 2023-08-08 10:55:13 +02:00
Thomas
e66ca47198 longer fields when editing bio 2023-08-08 10:55:06 +02:00
Oğuz Ersen
d2b1ebe725
Translated using Weblate (Turkish)
Currently translated at 100.0% (1159 of 1159 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2023-08-08 08:46:00 +02:00
ButterflyOfFire
a7fdad7f77
Translated using Weblate (French)
Currently translated at 99.7% (1156 of 1159 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translation: Fedilab/Strings
2023-08-08 08:45:59 +02:00
claleb
6e16a645c8
Translated using Weblate (German)
Currently translated at 100.0% (1159 of 1159 strings)

Co-authored-by: claleb <weblate@claleb.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2023-08-08 08:45:59 +02:00
Thomas
e00a3c6717 Release 3.23.2 2023-08-07 17:16:49 +02:00
Thomas
ba0fc54cbc Fix maths converter 2023-08-07 17:09:30 +02:00
Thomas
e954fd860a Markdown support 2023-08-07 17:03:14 +02:00
Thomas
6790158ed9 Support Markdown 2023-08-06 18:13:08 +02:00
Thomas
7825e06f69 Merge branch 'develop' of https://codeberg.org/tom79/Fedilab into develop 2023-07-30 18:18:05 +02:00
Thomas
1a3d95ef30 Release 3.23.1 2023-07-30 18:17:59 +02:00
Thomas
f37bf1df53 Scrollable media description 2023-07-30 18:16:16 +02:00
RintanBroadleaf
72d2082638
Translated using Weblate (Japanese)
Currently translated at 100.0% (1157 of 1157 strings)

Co-authored-by: RintanBroadleaf <rintanbroadleaf@outlook.jp>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ja/
Translation: Fedilab/Strings
2023-07-30 18:02:00 +02:00
Lukáš Jelínek
aa5db1cc99
Translated using Weblate (Czech)
Currently translated at 100.0% (1157 of 1157 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2023-07-30 18:02:00 +02:00
Thomas
bbb47626ce fix for all calls 2023-07-30 11:13:14 +02:00
Thomas
ac290dec2b Fix a crash when visiting Peertube in menu 2023-07-30 10:53:37 +02:00
Thomas
1ed657eb55 Merge remote-tracking branch 'origin/develop' into develop 2023-07-30 10:38:20 +02:00
Thomas
4d2fa93632 Fix a crash when remote profiles is enabled 2023-07-30 10:38:10 +02:00
Thomas
7488ee0914 Merge pull request 'docs: fixing a markdown link in CONTRIBUTING.md' (#906) from mc80/Fedilab:develop into develop
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/906
2023-07-29 16:08:18 +00:00
Thomas
eda23bd3b0 Release 3.23.0 2023-07-29 18:05:57 +02:00
Thomas
cf4b156863 Fix issue #913 - Fix Nitter feeds 2023-07-29 17:37:32 +02:00
Thomas
022d27ecb4 Merge remote-tracking branch 'origin/develop' into develop 2023-07-29 12:34:11 +02:00
Thomas
9e0d44824a Add preview for new logo 2023-07-29 12:34:05 +02:00
Serg
27ad666554
Translated using Weblate (Russian)
Currently translated at 75.9% (879 of 1157 strings)

Co-authored-by: Serg <karesh@bk.ru>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ru/
Translation: Fedilab/Strings
2023-07-29 12:12:47 +02:00
Thomas
a43a97d960 Add new logo 2023-07-29 12:12:35 +02:00
Thomas
fcef89f0c9 Display icon when changing logo 2023-07-29 11:44:13 +02:00
Thomas
bf447d823e Clean code 2023-07-27 14:47:22 +02:00
Thomas
44752cc329 Fix Punycode not supported for domains 2023-07-27 14:46:05 +02:00
Thomas
28501e7929 Merge remote-tracking branch 'origin/develop' into develop 2023-07-27 13:57:44 +02:00
Thomas
8c60348939 voice messages for Android 10+ 2023-07-27 13:57:29 +02:00
Serg
332ae125ac
Translated using Weblate (Russian)
Currently translated at 72.8% (843 of 1157 strings)

Co-authored-by: Serg <karesh@bk.ru>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ru/
Translation: Fedilab/Strings
2023-07-26 22:56:09 +02:00
Serg
3588a8b76e
Translated using Weblate (Russian)
Currently translated at 8.3% (3 of 36 strings)

Co-authored-by: Serg <karesh@bk.ru>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/ru/
Translation: Fedilab/description
2023-07-26 22:54:59 +02:00
Tomíček Rosič
0acdca0d99
Translated using Weblate (Icelandic)
Currently translated at 5.5% (2 of 36 strings)

Co-authored-by: Tomíček Rosič <tomicekrosic@proton.me>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/is/
Translation: Fedilab/description
2023-07-24 07:07:38 +02:00
Tomíček Rosič
d59c16217e
Translated using Weblate (Icelandic)
Currently translated at 35.6% (412 of 1157 strings)

Co-authored-by: Tomíček Rosič <tomicekrosic@proton.me>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/is/
Translation: Fedilab/Strings
2023-07-24 07:07:02 +02:00
Thomas
e06222733c Fix - Videos are played simultaneously 2023-07-23 17:31:35 +02:00
Thomas
870eeeb834 fix Lingva encoding issue 2023-07-22 15:15:43 +02:00
Thomas
958e4f6aa5 Fix issue #904 - Avoid sleep mode for media activity 2023-07-22 10:48:36 +02:00
Thomas
0daee80325 Merge remote-tracking branch 'origin/develop' into develop 2023-07-22 10:38:54 +02:00
Thomas
c1575fbf5f Fix a crash with PixelFed 2023-07-22 10:38:47 +02:00
josé m
40cb45b2da
Translated using Weblate (Galician)
Currently translated at 100.0% (1157 of 1157 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2023-07-19 18:07:19 +02:00
josé m
923fc1f8eb
Translated using Weblate (Galician)
Currently translated at 99.9% (1156 of 1157 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2023-07-18 18:06:43 +02:00
Mathieu CLAVEL
c1540d0267
docs: fixing a markdown link in CONTRIBUTING.md
Adding a missing opening parenthesis before the link URL.
2023-07-17 07:54:03 +02:00
Overplant Poster
47b46423c0
Translated using Weblate (Sinhala)
Currently translated at 56.4% (653 of 1157 strings)

Co-authored-by: Overplant Poster <overplant_poster435@simplelogin.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/si/
Translation: Fedilab/Strings
2023-07-17 04:32:59 +02:00
GunChleoc
23f85768c4
Translated using Weblate (Gaelic)
Currently translated at 100.0% (1157 of 1157 strings)

Co-authored-by: GunChleoc <fios@foramnagaidhlig.net>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gd/
Translation: Fedilab/Strings
2023-07-15 23:51:56 +02:00
claleb
879e8840ad
Translated using Weblate (German)
Currently translated at 100.0% (1157 of 1157 strings)

Co-authored-by: claleb <weblate@claleb.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2023-07-15 23:51:56 +02:00
Thomas
26760c8e15 Release 3.22.2 2023-07-14 18:25:19 +02:00
Thomas
49c5f864a7 Merge branch 'fix_3220' into develop 2023-07-14 18:16:30 +02:00
Thomas
c7f6fefc7f crash 2023-07-14 18:15:47 +02:00
Thomas
fbd2bcb418 Avoid multiple notifications 2023-07-14 15:50:54 +02:00
Thomas
e332df17a4 Merge remote-tracking branch 'origin/develop' into develop 2023-07-14 15:33:59 +02:00
Thomas
bbba486be1 Fix a crash 2023-07-14 15:25:32 +02:00
Thomas
ce607226d9 remote conversations 2023-07-14 11:11:27 +02:00
Overplant Poster
629ae3b9d3
Translated using Weblate (Sinhala)
Currently translated at 56.2% (649 of 1154 strings)

Co-authored-by: Overplant Poster <overplant_poster435@simplelogin.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/si/
Translation: Fedilab/Strings
2023-07-14 10:51:43 +02:00
Thomas
6d983a74de Oops 2023-07-14 10:14:09 +02:00
Oğuz Ersen
f364a08bbc
Translated using Weblate (Turkish)
Currently translated at 100.0% (1154 of 1154 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2023-07-13 17:53:10 +02:00
josé m
c4c45139d6
Translated using Weblate (Galician)
Currently translated at 99.9% (1153 of 1154 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2023-07-13 17:53:09 +02:00
claleb
401d43acfc
Translated using Weblate (German)
Currently translated at 100.0% (1154 of 1154 strings)

Co-authored-by: claleb <weblate@claleb.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2023-07-13 17:53:09 +02:00
Thomas
237801f56c Release 3.22.1 2023-07-12 17:45:28 +02:00
Thomas
7fdebf20f1 Fix issue #901 2023-07-12 16:57:34 +02:00
Thomas
ad178f3cd0 Order by date 2023-07-12 16:33:49 +02:00
Thomas
cbd1332213 Pass first message 2023-07-11 17:08:42 +02:00
Thomas
7cfe2cbecf Fetch comments 2023-07-08 16:41:37 +02:00
Thomas
58f193e71b Display lammy main threads 2023-07-08 16:32:02 +02:00
Thomas
5ca288a04b Add activity for timelines 2023-07-08 16:17:13 +02:00
Thomas
c331cbeb26 converter 2023-07-07 16:59:16 +02:00
Thomas
12b7663653 create entity 2023-07-07 16:27:46 +02:00
Thomas
a53ac333e3 Add timeline action 2023-07-07 16:01:09 +02:00
Thomas
04355116f4 Fix a crash 2023-06-08 16:27:46 +02:00
Thomas
9375abffd3 Allow to mute and unmute own user from Home timeline 2023-06-05 17:29:08 +02:00
Thomas
f6483c0ccb Merge pull request 'Reinstanciate linebreaks in shared content' (#849) from Augier/Fedilab:reinstanciate-linebreaks-in-share into develop
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/849
2023-06-01 14:51:56 +00:00
Thomas
7e86d9377f Fix issue #875 - Visibility of the first message is changed to unlisted when adding replies 2023-06-01 16:46:23 +02:00
Thomas
3861a7b6e4 Crash due to delayed service call (Peertube) 2023-06-01 16:31:54 +02:00
Thomas
443ff2d299 Merge remote-tracking branch 'origin/develop' into develop 2023-05-30 09:45:51 +02:00
Thomas
b9026e2ac0 Release 3.22.0 2023-05-30 09:45:42 +02:00
Thomas
719665a2d8 Release 3.22.0 2023-05-30 09:44:40 +02:00
Thomas
37838681da Fix empty Home when starting 2023-05-30 08:59:28 +02:00
Thomas
cb2ede287d comment #874 - Avoid multiple notification fetch 2023-05-27 14:47:35 +02:00
Thomas
88da62b8e8 Fix a crash when forwarding tags 2023-05-26 12:14:20 +02:00
Thomas
3858220937 Add a fallback when remote profiles are not working 2023-05-26 12:05:18 +02:00
Thomas
be8943163a Fix issue #871 - Remove instance name encoded in UTF-8 2023-05-26 11:02:11 +02:00
GunChleoc
9b26ae718a
Translated using Weblate (Gaelic)
Currently translated at 3.0% (1 of 33 strings)

Co-authored-by: GunChleoc <fios@foramnagaidhlig.net>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/gd/
Translation: Fedilab/description
2023-05-25 03:49:53 +02:00
Parsa
ba0843ecc6
Translated using Weblate (Persian)
Currently translated at 57.0% (658 of 1153 strings)

Co-authored-by: Parsa <parsa@disr.it>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fa/
Translation: Fedilab/Strings
2023-05-12 04:48:58 +02:00
GunChleoc
3860fef6b8
Translated using Weblate (Gaelic)
Currently translated at 100.0% (1153 of 1153 strings)

Co-authored-by: GunChleoc <fios@foramnagaidhlig.net>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gd/
Translation: Fedilab/Strings
2023-05-06 20:48:58 +02:00
GunChleoc
0093d3d27a
Translated using Weblate (Gaelic)
Currently translated at 91.1% (1051 of 1153 strings)

Co-authored-by: GunChleoc <fios@foramnagaidhlig.net>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gd/
Translation: Fedilab/Strings
2023-05-01 02:51:21 +02:00
dreigiau
3ba8b94850
Translated using Weblate (Welsh)
Currently translated at 61.7% (712 of 1153 strings)

Co-authored-by: dreigiau <sterilgrimed23@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cy/
Translation: Fedilab/Strings
2023-04-23 08:50:16 +02:00
dreigiau
c348816809
Translated using Weblate (Welsh)
Currently translated at 9.0% (3 of 33 strings)

Co-authored-by: dreigiau <sterilgrimed23@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/cy/
Translation: Fedilab/description
2023-04-23 08:49:16 +02:00
Blood Axe
c839e82633
Translated using Weblate (Norwegian Bokmål)
Currently translated at 93.7% (1081 of 1153 strings)

Co-authored-by: Blood Axe <bloodaxenor@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/nb_NO/
Translation: Fedilab/Strings
2023-04-18 22:50:19 +02:00
dreigiau
c5179274af
Translated using Weblate (Welsh)
Currently translated at 56.0% (646 of 1153 strings)

Co-authored-by: dreigiau <sterilgrimed23@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cy/
Translation: Fedilab/Strings
2023-04-15 05:54:57 +02:00
Hosted Weblate
45b1c0bb31
Update translation files
Updated by "Remove blank strings" hook in Weblate.

Update translation files

Updated by "Remove blank strings" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/
Translation: Fedilab/Strings
2023-04-14 12:49:20 +02:00
Blood Axe
07bb58730a
Translated using Weblate (Norwegian Bokmål)
Currently translated at 85.1% (982 of 1153 strings)

Co-authored-by: Blood Axe <bloodaxenor@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/nb_NO/
Translation: Fedilab/Strings
2023-04-14 12:49:19 +02:00
Lukas
d9238fffa0
Translated using Weblate (Polish)
Currently translated at 99.8% (1151 of 1153 strings)

Co-authored-by: Lukas <ox86udoo@duck.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/pl/
Translation: Fedilab/Strings
2023-04-12 05:53:08 +02:00
Zekovski
147bc2b709
Translated using Weblate (French)
Currently translated at 99.0% (1142 of 1153 strings)

Co-authored-by: Zekovski <zekovski@e.email>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translation: Fedilab/Strings
2023-04-12 05:53:08 +02:00
Lukas
8076e353d2
Translated using Weblate (Polish)
Currently translated at 98.8% (1140 of 1153 strings)

Co-authored-by: Lukas <ox86udoo@duck.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/pl/
Translation: Fedilab/Strings
2023-04-11 16:53:13 +02:00
Zekovski
8e9be690c3
Translated using Weblate (French)
Currently translated at 15.1% (5 of 33 strings)

Co-authored-by: Zekovski <zekovski@e.email>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/fr/
Translation: Fedilab/description
2023-04-06 01:51:30 +02:00
Jean-Luc Tibaux
d5181ce222
Translated using Weblate (French)
Currently translated at 98.0% (1130 of 1153 strings)

Co-authored-by: Jean-Luc Tibaux <eugentoptic@outlook.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translation: Fedilab/Strings
2023-04-06 01:49:11 +02:00
Zekovski
64ad6a4ba6
Translated using Weblate (French)
Currently translated at 98.0% (1130 of 1153 strings)

Co-authored-by: Zekovski <zekovski@e.email>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translation: Fedilab/Strings
2023-04-06 01:49:11 +02:00
Christophe Henry
0fcc2e5898 Reinstanciate linebreaks in shared content 2023-04-04 17:02:15 +02:00
GunChleoc
f072a618ee
Translated using Weblate (Gaelic)
Currently translated at 79.2% (914 of 1153 strings)

Co-authored-by: GunChleoc <fios@foramnagaidhlig.net>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gd/
Translation: Fedilab/Strings
2023-04-02 09:38:35 +02:00
GunChleoc
d6a335ec25
Translated using Weblate (Gaelic)
Currently translated at 79.2% (914 of 1153 strings)

Co-authored-by: GunChleoc <fios@foramnagaidhlig.net>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gd/
Translation: Fedilab/Strings
2023-03-30 07:37:41 +02:00
GunChleoc
667b1c7a0b
Translated using Weblate (Gaelic)
Currently translated at 74.3% (857 of 1153 strings)

Co-authored-by: GunChleoc <fios@foramnagaidhlig.net>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gd/
Translation: Fedilab/Strings
2023-03-29 19:26:24 +02:00
GunChleoc
6d2edeea29
Translated using Weblate (Gaelic)
Currently translated at 67.9% (784 of 1153 strings)

Co-authored-by: GunChleoc <fios@foramnagaidhlig.net>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gd/
Translation: Fedilab/Strings
2023-03-29 19:17:09 +02:00
Francesc
857a8960bb
Translated using Weblate (Catalan)
Currently translated at 80.4% (928 of 1153 strings)

Co-authored-by: Francesc <franzesk@live.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ca/
Translation: Fedilab/Strings
2023-03-29 19:06:56 +02:00
josé m
269a36217b
Translated using Weblate (Galician)
Currently translated at 99.9% (1152 of 1153 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2023-03-26 22:38:01 +02:00
Oğuz Ersen
4ef04c0dfa
Translated using Weblate (Turkish)
Currently translated at 100.0% (1153 of 1153 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2023-03-26 06:41:00 +02:00
claleb
1e51e9e7d4
Translated using Weblate (German)
Currently translated at 100.0% (1153 of 1153 strings)

Co-authored-by: claleb <weblate@claleb.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2023-03-26 06:41:00 +02:00
Lukáš Jelínek
d0c4a02c5a
Translated using Weblate (Czech)
Currently translated at 99.8% (1151 of 1153 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2023-03-26 06:40:59 +02:00
Poesty Li
dd9571c228
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1153 of 1153 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2023-03-26 06:40:59 +02:00
RintanBroadleaf
98eab21995
Translated using Weblate (Japanese)
Currently translated at 100.0% (1143 of 1143 strings)

Co-authored-by: RintanBroadleaf <rintanbroadleaf@outlook.jp>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ja/
Translation: Fedilab/Strings
2023-03-26 06:40:59 +02:00
Parsa
811b265022
Translated using Weblate (Persian)
Currently translated at 56.6% (648 of 1143 strings)

Co-authored-by: Parsa <parsa@disr.it>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fa/
Translation: Fedilab/Strings
2023-03-26 06:40:59 +02:00
Paúl Sanz
b217e3647e
Translated using Weblate (Spanish)
Currently translated at 12.1% (4 of 33 strings)

Co-authored-by: Paúl Sanz <paulsanzcalvo@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/es/
Translation: Fedilab/description
2023-03-26 06:40:38 +02:00
Thomas
103f030f6b Fix issue #838 - Load and display last notifications when clicking on a push notifications 2023-03-25 18:03:42 +01:00
Thomas
917f33334c some fixes 2023-03-25 17:42:36 +01:00
Thomas
a013c160b7 Release 3.21.2 2023-03-24 17:26:23 +01:00
Thomas
1513f4b667 Merge remote-tracking branch 'origin/develop' into develop 2023-03-24 16:45:12 +01:00
Thomas
a1b08b8edc custom dialog 2023-03-24 16:44:42 +01:00
Thomas
fdbb3b04f3 custom colors per account 2023-03-24 16:27:25 +01:00
Thomas
8ffd5b3a19 some changes 2023-03-24 15:13:24 +01:00
Lukas
5ff9727431
Translated using Weblate (Polish)
Currently translated at 99.3% (1135 of 1143 strings)

Co-authored-by: Lukas <ox86udoo@duck.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/pl/
Translation: Fedilab/Strings
2023-03-23 10:37:14 +01:00
Lukas
c90b832811
Translated using Weblate (Polish)
Currently translated at 18.7% (6 of 32 strings)

Co-authored-by: Lukas <ox86udoo@duck.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/pl/
Translation: Fedilab/description
2023-03-23 09:39:58 +01:00
Thomas
4a8d20ed6b Release 3.21.1 2023-03-22 17:51:33 +01:00
Thomas
45078b10b0 FP when playing audio 2023-03-22 17:49:53 +01:00
Thomas
75139a8523 Fix issue #830 - Poll having html 2023-03-22 17:43:35 +01:00
Thomas
7743de24d2 Fix issue #831 - Filter messages in profiles 2023-03-22 17:32:27 +01:00
Thomas
2f9addb788 Fix crash with gif 2023-03-22 16:44:03 +01:00
Thomas
b89671c08a Merge remote-tracking branch 'origin/develop' into develop 2023-03-22 16:35:44 +01:00
Thomas
dca3e444d7 Fix issue #832 - Fetch More with wrong width 2023-03-22 16:35:36 +01:00
Hosted Weblate
67678ab6f9
Update translation files
Updated by "Remove blank strings" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/
Translation: Fedilab/Strings
2023-03-22 04:47:02 +01:00
Poesty Li
1c3e060715
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1143 of 1143 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2023-03-22 04:47:01 +01:00
Oğuz Ersen
7a7cebc5ce
Translated using Weblate (Turkish)
Currently translated at 100.0% (1143 of 1143 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2023-03-22 04:47:01 +01:00
josé m
77755e0c93
Translated using Weblate (Galician)
Currently translated at 99.9% (1142 of 1143 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2023-03-22 04:47:00 +01:00
claleb
8d264a3af4
Translated using Weblate (German)
Currently translated at 100.0% (1143 of 1143 strings)

Co-authored-by: claleb <weblate@claleb.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2023-03-22 04:47:00 +01:00
Lukáš Jelínek
f9c36f81ae
Translated using Weblate (Czech)
Currently translated at 99.7% (1140 of 1143 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2023-03-22 04:46:59 +01:00
Parsa
efc5c7012c
Translated using Weblate (Persian)
Currently translated at 16.1% (5 of 31 strings)

Co-authored-by: Parsa <parsa@disr.it>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/fa/
Translation: Fedilab/description
2023-03-21 20:40:30 +01:00
Thomas
15416b8c5a Fix issue #833 - URL protocol in upper case 2023-03-21 13:10:14 +01:00
Thomas
6345fe6adb Merge remote-tracking branch 'origin/develop' into develop 2023-03-21 13:03:12 +01:00
Thomas
7930cdf838 Merge branch 'fixes_487' into develop 2023-03-21 13:03:03 +01:00
Thomas
7763d2f5f1 Some fixes 2023-03-21 13:02:52 +01:00
Parsa
7d1f245340
Translated using Weblate (Persian)
Currently translated at 9.6% (3 of 31 strings)

Co-authored-by: Parsa <parsa@disr.it>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/fa/
Translation: Fedilab/description
2023-03-20 20:40:48 +01:00
Thomas
5d97d75a13 Merge remote-tracking branch 'origin/develop' into develop 2023-03-20 18:24:02 +01:00
Thomas
3da5cdf703 Some layouts improvements 2023-03-20 18:23:39 +01:00
Thomas
b6a8df9410 clean code 2023-03-20 14:06:58 +01:00
Thomas
f698b414a5 fix flags 2023-03-20 11:42:06 +01:00
Thomas
a8f8f33a74 fix orientation issue 2023-03-20 11:27:36 +01:00
Thomas
47b767e8ce Add settings 2023-03-20 10:43:21 +01:00
Thomas
9fd701b102 Add settings 2023-03-19 16:26:20 +01:00
Thomas
327fac5f5a working track selectors 2023-03-19 12:19:26 +01:00
Thomas
0a9c5162ca track selector 2023-03-19 12:05:33 +01:00
Thomas
f082611b02 some changes with resolution 2023-03-17 17:28:20 +01:00
RintanBroadleaf
f95f7b2dfc
Translated using Weblate (Japanese)
Currently translated at 100.0% (1142 of 1142 strings)

Co-authored-by: RintanBroadleaf <rintanbroadleaf@outlook.jp>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ja/
Translation: Fedilab/Strings
2023-03-17 14:38:04 +01:00
Ettore Atalan
9703133ad2
Translated using Weblate (German)
Currently translated at 100.0% (1142 of 1142 strings)

Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2023-03-16 18:41:16 +01:00
Lukáš Jelínek
d3b0ccf3d5
Translated using Weblate (Czech)
Currently translated at 99.7% (1139 of 1142 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2023-03-16 18:41:15 +01:00
Poesty Li
f0ef62183e
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1141 of 1141 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2023-03-16 18:41:15 +01:00
Oğuz Ersen
79337d7b7d
Translated using Weblate (Turkish)
Currently translated at 100.0% (1142 of 1142 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (1141 of 1141 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2023-03-16 18:41:14 +01:00
josé m
b1d59592bd
Translated using Weblate (Galician)
Currently translated at 100.0% (1142 of 1142 strings)

Translated using Weblate (Galician)

Currently translated at 100.0% (1141 of 1141 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2023-03-16 18:41:13 +01:00
ButterflyOfFire
38fb341f38
Translated using Weblate (French)
Currently translated at 96.8% (1105 of 1141 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translation: Fedilab/Strings
2023-03-16 18:41:13 +01:00
claleb
2d2e1787d9
Translated using Weblate (German)
Currently translated at 100.0% (1141 of 1141 strings)

Co-authored-by: claleb <weblate@claleb.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2023-03-16 18:41:12 +01:00
Thomas
979739cc21 improve Peertube instance picker behaviour 2023-03-16 17:36:55 +01:00
Thomas
740ce71ac9 Release 3.21.0 2023-03-15 18:56:55 +01:00
Thomas
fd4133d962 some changes 2023-03-15 18:46:47 +01:00
Thomas
1dcb500c7f some changes 2023-03-15 18:46:00 +01:00
Thomas
5fbc9d54b3 some changes 2023-03-15 18:09:15 +01:00
Thomas
d95e48ef86 Change instances 2023-03-15 17:33:06 +01:00
Thomas
462a9a1d44 Some changes 2023-03-15 16:41:20 +01:00
Thomas
ffe7dbf1dd Some changes 2023-03-15 15:23:12 +01:00
Thomas
dddef9706d Merge branch 'develop' into peertube_integration 2023-03-15 09:27:53 +01:00
Thomas
d3c44489f3 Fix a crash with search/directory 2023-03-15 09:27:27 +01:00
Thomas
739183de9c Merge remote-tracking branch 'origin/develop' into develop 2023-03-14 18:20:25 +01:00
Thomas
4dfc9b9f68 Release 3.20.3 2023-03-14 18:20:20 +01:00
Thomas
82133c114e Add some elements 2023-03-14 18:16:01 +01:00
Thomas
e28b3fcf76 Add some elements 2023-03-14 18:00:47 +01:00
Oğuz Ersen
7bb0193211
Translated using Weblate (Turkish)
Currently translated at 100.0% (1140 of 1140 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2023-03-14 16:15:49 +01:00
Lukáš Jelínek
82c7aed836
Translated using Weblate (Czech)
Currently translated at 99.6% (1136 of 1140 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2023-03-14 16:15:49 +01:00
Thomas
20c797520e remote info 2023-03-14 16:15:22 +01:00
Thomas
569e4c680e Release notes 2023-03-14 11:25:56 +01:00
Thomas
50f5f15c6f Check remotely following/followers from profiles 2023-03-14 11:24:20 +01:00
Thomas
ba1aa01d46 Fix a crash 2023-03-14 10:31:18 +01:00
Thomas
3746673f9d Release 3.20.2 2023-03-13 18:09:48 +01:00
Thomas
0383007451 Merge remote-tracking branch 'origin/develop' into develop 2023-03-13 17:19:46 +01:00
Thomas
fdd39704ef Fix jumps with Akkoma/Pleroma when media preview size is not set 2023-03-13 17:19:38 +01:00
Thomas
25c372280f Faster access to remove all notifications 2023-03-13 17:08:05 +01:00
Poesty Li
ddde78f5b9
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1140 of 1140 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2023-03-13 16:46:50 +01:00
Thomas
bbcc9244e3 trim for nodeinfo 2023-03-13 16:40:49 +01:00
Thomas
d79c09c692 Merge remote-tracking branch 'origin/develop' into develop 2023-03-13 10:37:18 +01:00
Thomas
f6af6f9519 - Open media description when it is missing in the warning dialog 2023-03-13 10:37:08 +01:00
Thomas
f045f71337 Fix nitter 2023-03-12 18:41:08 +01:00
Thomas
cd40704d0f force left alignment with maths 2023-03-12 18:36:05 +01:00
Thomas
ac9eb64368 no controller for gif 2023-03-12 18:10:45 +01:00
Thomas
6261eadeb7 logs 2023-03-12 16:18:52 +01:00
Thomas
95ad3d687c Add indicator 2023-03-12 16:16:42 +01:00
Poesty Li
f41fdc1ea5
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1138 of 1138 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2023-03-12 01:41:27 +01:00
josé m
8b560b5916
Translated using Weblate (Galician)
Currently translated at 100.0% (1138 of 1138 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2023-03-12 01:41:27 +01:00
claleb
6e87baff6f
Translated using Weblate (German)
Currently translated at 100.0% (1138 of 1138 strings)

Co-authored-by: claleb <weblate@claleb.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2023-03-12 01:41:26 +01:00
Lukáš Jelínek
621e59eae5
Translated using Weblate (Czech)
Currently translated at 99.6% (1134 of 1138 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2023-03-12 01:41:26 +01:00
Thomas
1b88633887 Add fetching message 2023-03-11 11:12:21 +01:00
Thomas
bc27445103 Fix a crash with null media URLs 2023-03-11 09:33:46 +01:00
Oğuz Ersen
6b589fc9f6
Translated using Weblate (Turkish)
Currently translated at 100.0% (1138 of 1138 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2023-03-10 00:13:52 +01:00
ButterflyOfFire
5af213ca48
Translated using Weblate (Italian)
Currently translated at 89.4% (1018 of 1138 strings)

Translated using Weblate (French)

Currently translated at 96.6% (1100 of 1138 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/it/
Translation: Fedilab/Strings
2023-03-10 00:13:51 +01:00
Ettore Atalan
f3165acd64
Translated using Weblate (German)
Currently translated at 99.3% (1131 of 1138 strings)

Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2023-03-10 00:13:50 +01:00
Thomas
da67ec0e03 Release 3.20.1 2023-03-09 18:03:39 +01:00
Poesty Li
047916cf9c
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1135 of 1135 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2023-03-09 17:44:29 +01:00
Lukáš Jelínek
26074ae7b7
Translated using Weblate (Czech)
Currently translated at 99.6% (1131 of 1135 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2023-03-09 17:44:29 +01:00
Thomas
915841aa04 Fix some crashes 2023-03-09 17:44:22 +01:00
Thomas
6a1a144ca3 extend media description warning to cross boost 2023-03-09 17:31:41 +01:00
Thomas
22b43db39b Warn before boosting a message having no media description 2023-03-09 17:18:57 +01:00
Thomas
09e00cbbee Merge remote-tracking branch 'origin/develop' into develop 2023-03-09 16:42:18 +01:00
Thomas
b5f0cc3097 Fix issue #824 2023-03-09 16:42:12 +01:00
Poesty Li
aeddaf6462
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1132 of 1132 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1130 of 1130 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2023-03-09 15:35:21 +01:00
Oğuz Ersen
64c5bffbfa
Translated using Weblate (Turkish)
Currently translated at 100.0% (1130 of 1130 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2023-03-09 15:35:21 +01:00
josé m
0af1a9071b
Translated using Weblate (Galician)
Currently translated at 100.0% (1132 of 1132 strings)

Translated using Weblate (Galician)

Currently translated at 99.9% (1129 of 1130 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2023-03-09 15:35:21 +01:00
ButterflyOfFire
70d4434d36
Translated using Weblate (French)
Currently translated at 96.2% (1088 of 1130 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translation: Fedilab/Strings
2023-03-09 15:35:21 +01:00
Thomas
7c1d5a4830 Follow Request By 2023-03-09 15:35:12 +01:00
Thomas
7f9d6ca2d5 Mandatory media description warn by default 2023-03-09 15:17:59 +01:00
Thomas
62f84f5637 Fix an issue with instance social 2023-03-09 14:56:43 +01:00
Thomas
426658695e move string 2023-03-09 14:31:56 +01:00
Thomas
68d9abea67 settings to automatically fetch remote media when it fails (default: disabled) 2023-03-09 14:31:02 +01:00
Thomas
8c74343843 Fix some settings not properly restored 2023-03-09 14:17:29 +01:00
Thomas
ca3142046b Button to load media from remote instance when it fails 2023-03-09 13:59:55 +01:00
Thomas
7e2c9ab4a2 Release 3.20.0 2023-03-08 18:23:03 +01:00
Thomas
e8e7527094 Release logs 2023-03-08 17:06:18 +01:00
Thomas
6e15bc93d1 Chat view by default and add a privacy indicator 2023-03-08 17:02:07 +01:00
Thomas
9a08f5eb5f Some fixes 2023-03-08 16:41:01 +01:00
Thomas
2687f85935 Add ignore battery optimizations in cache settings 2023-03-08 16:08:45 +01:00
Thomas
8548e6f098 Add follow indicator 2023-03-08 16:03:53 +01:00
Thomas
b77239b874 Add follow indicator 2023-03-08 16:01:25 +01:00
Thomas
a8ffbcae29 Merge remote-tracking branch 'origin/develop' into develop 2023-03-08 15:50:11 +01:00
Thomas
5782fe9da6 improve media description 2023-03-08 15:50:05 +01:00
Thomas
70a66ae4cd some fixes 2023-03-08 15:07:52 +01:00
Oğuz Ersen
be7a9105a0
Translated using Weblate (Turkish)
Currently translated at 100.0% (1129 of 1129 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2023-03-08 09:40:12 +01:00
claleb
97da17773e
Translated using Weblate (German)
Currently translated at 99.8% (1127 of 1129 strings)

Co-authored-by: claleb <weblate@claleb.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2023-03-08 09:40:12 +01:00
Lukáš Jelínek
ddc59156ea
Translated using Weblate (Czech)
Currently translated at 99.7% (1126 of 1129 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2023-03-08 09:40:11 +01:00
Thomas
827acad36d Fetch also remotely when fetching missing 2023-03-07 19:02:39 +01:00
Thomas
e22d063a1e Merge remote-tracking branch 'origin/develop' into develop 2023-03-07 18:25:21 +01:00
Thomas
caee0ad276 Records home logs 2023-03-07 18:25:15 +01:00
Thomas
f616b7df0e Records home logs 2023-03-07 12:05:27 +01:00
Dan
275d941afd
Translated using Weblate (Ukrainian)
Currently translated at 61.3% (682 of 1112 strings)

Co-authored-by: Dan <denqwerta@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2023-03-06 19:40:10 +01:00
RintanBroadleaf
7675543a31
Translated using Weblate (Japanese)
Currently translated at 100.0% (1112 of 1112 strings)

Co-authored-by: RintanBroadleaf <rintanbroadleaf@outlook.jp>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ja/
Translation: Fedilab/Strings
2023-03-06 19:40:09 +01:00
Thomas
84814d3291 Records home logs 2023-03-06 17:36:57 +01:00
Thomas
e1c833fcd6 Records home logs 2023-03-05 18:30:08 +01:00
Thomas
0dc1fcf341 some fixes 2023-03-05 17:34:40 +01:00
Thomas
d3984bc06b some fixes 2023-03-05 16:27:43 +01:00
Thomas
74b107f1d3 Charts 2023-03-04 18:28:59 +01:00
Thomas
c04a51f3b7 Charts 2023-03-04 17:40:23 +01:00
Hosted Weblate
a13b7d9f80
Update translation files
Updated by "Remove blank strings" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/
Translation: Fedilab/Strings
2023-03-04 01:51:59 +01:00
GunChleoc
86c899f007
Translated using Weblate (Gaelic)
Currently translated at 52.7% (587 of 1112 strings)

Co-authored-by: GunChleoc <fios@foramnagaidhlig.net>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gd/
Translation: Fedilab/Strings
2023-03-04 01:51:59 +01:00
Thomas
6019f5129b Merge remote-tracking branch 'origin/develop' into develop 2023-03-03 16:51:10 +01:00
Thomas
6fb6d295d4 Fix issue #815 - Bad behavior with gif 2023-03-03 16:51:04 +01:00
GunChleoc
39bac4e8f8
Translated using Weblate (Gaelic)
Currently translated at 51.7% (576 of 1112 strings)

Co-authored-by: GunChleoc <fios@foramnagaidhlig.net>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gd/
Translation: Fedilab/Strings
2023-03-03 12:33:55 +01:00
Thomas
dc9b31b4eb some changes 2023-03-03 12:25:09 +01:00
Thomas
c89cabdcb4 some changes 2023-03-03 11:59:38 +01:00
Thomas
f2c42de0ca Merge remote-tracking branch 'origin/develop' into develop 2023-03-03 11:58:23 +01:00
Thomas
f40d2d7424 Fix issue #819 - Lists are removed when failing to sync 2023-03-03 11:58:17 +01:00
claleb
3ffda44c1d
Translated using Weblate (German)
Currently translated at 100.0% (1112 of 1112 strings)

Co-authored-by: claleb <weblate@claleb.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2023-03-03 01:54:12 +01:00
Thomas
8a0f104992 Merge remote-tracking branch 'origin/develop' into develop 2023-03-02 14:19:49 +01:00
Thomas
688f5d8a9a Fix issue #818 2023-03-02 14:19:41 +01:00
Oğuz Ersen
5c2e1c4a4b
Translated using Weblate (Turkish)
Currently translated at 100.0% (1112 of 1112 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2023-03-01 19:36:21 +01:00
Lukáš Jelínek
2f984aa848
Translated using Weblate (Czech)
Currently translated at 99.8% (1110 of 1112 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2023-03-01 19:36:20 +01:00
Poesty Li
16ad17aebb
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1112 of 1112 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1110 of 1110 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2023-03-01 19:36:20 +01:00
josé m
39ca6e01b0
Translated using Weblate (Galician)
Currently translated at 100.0% (1112 of 1112 strings)

Translated using Weblate (Galician)

Currently translated at 100.0% (1108 of 1108 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2023-03-01 19:36:19 +01:00
Thomas
cea9abbc5b Release 3.19.1 2023-02-28 18:32:07 +01:00
Thomas
a7c76e80a0 Merge remote-tracking branch 'origin/develop' into develop 2023-02-28 16:10:32 +01:00
Thomas
a655cab7ae Request remove battery optimization 2023-02-28 16:10:24 +01:00
Oğuz Ersen
c5b7bac8f8
Translated using Weblate (Turkish)
Currently translated at 100.0% (1104 of 1104 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2023-02-28 11:38:05 +01:00
josé m
1ca468314e
Translated using Weblate (Galician)
Currently translated at 100.0% (1104 of 1104 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2023-02-28 11:38:05 +01:00
claleb
82dd3c82a6
Translated using Weblate (German)
Currently translated at 100.0% (1104 of 1104 strings)

Co-authored-by: claleb <weblate@claleb.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2023-02-28 11:38:05 +01:00
Lukáš Jelínek
1e998cc103
Translated using Weblate (Czech)
Currently translated at 99.9% (1103 of 1104 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2023-02-28 11:38:05 +01:00
Poesty Li
5d7d6568e1
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1104 of 1104 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1100 of 1100 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2023-02-28 11:38:05 +01:00
Thomas
7195f03501 bad behavior with truncated messages 2023-02-28 11:37:58 +01:00
Thomas
545fbe588c Fix cache view with large fonts 2023-02-28 10:47:09 +01:00
Thomas
97a527ab52 Allow to warn instead of blocking when there are no media description 2023-02-28 10:33:56 +01:00
Thomas
b981d2899e Release 3.19.0 2023-02-27 18:37:46 +01:00
Thomas
b9af6ee60b Fix edit media description 2023-02-27 18:08:07 +01:00
Thomas
5d5f2f9c9a Fix #800 - Add settings to don't send message if there are no media description 2023-02-27 16:14:35 +01:00
Thomas
19348ce031 #803 - Accessibility for DM 2023-02-27 14:18:23 +01:00
Thomas
ffb71f816f #803 - Accessibility for profiles 2023-02-27 14:11:14 +01:00
Thomas
c815a66be3 Fix visibility with cross replies 2023-02-27 10:51:11 +01:00
Thomas
34c81cec76 Some updates 2023-02-27 10:24:13 +01:00
Thomas
737ba73cea Merge remote-tracking branch 'origin/develop' into develop 2023-02-27 10:20:38 +01:00
Thomas
a47cbf80cd Fix issue #810 - Cache issue 2023-02-27 10:20:25 +01:00
Lukas
83fc8a46f8
Translated using Weblate (Polish)
Currently translated at 99.0% (1086 of 1096 strings)

Co-authored-by: Lukas <ox86udoo@duck.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/pl/
Translation: Fedilab/Strings
2023-02-26 00:38:42 +01:00
SevicheCC
976a27229e
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1096 of 1096 strings)

Co-authored-by: SevicheCC <me@seviche.cc>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2023-02-24 03:36:12 +01:00
josé m
a1a1a4fecb
Translated using Weblate (Galician)
Currently translated at 100.0% (1096 of 1096 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2023-02-21 20:37:57 +01:00
Poesty Li
2049f3c61d
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1096 of 1096 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2023-02-20 02:00:33 +01:00
Oğuz Ersen
dc4186aead
Translated using Weblate (Turkish)
Currently translated at 100.0% (1096 of 1096 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2023-02-20 02:00:31 +01:00
Lukas
22836dca4f
Translated using Weblate (Polish)
Currently translated at 98.7% (1082 of 1096 strings)

Co-authored-by: Lukas <ox86udoo@duck.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/pl/
Translation: Fedilab/Strings
2023-02-20 02:00:29 +01:00
Ettore Atalan
791fa4a082
Translated using Weblate (German)
Currently translated at 100.0% (1096 of 1096 strings)

Co-authored-by: Ettore Atalan <atalanttore@googlemail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2023-02-20 02:00:28 +01:00
Lukáš Jelínek
215bc11b62
Translated using Weblate (Czech)
Currently translated at 99.8% (1094 of 1096 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2023-02-20 02:00:28 +01:00
Oliebol
e008afa42f
Translated using Weblate (Dutch)
Currently translated at 98.5% (1080 of 1096 strings)

Co-authored-by: Oliebol <schrijfmedan@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/nl/
Translation: Fedilab/Strings
2023-02-20 02:00:26 +01:00
ButterflyOfFire
8efef5112b
Translated using Weblate (French)
Currently translated at 96.3% (1056 of 1096 strings)

Translated using Weblate (French)

Currently translated at 96.0% (1053 of 1096 strings)

Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/
Translation: Fedilab/Strings
2023-02-20 02:00:26 +01:00
claleb
3bdce72d58
Translated using Weblate (German)
Currently translated at 100.0% (1096 of 1096 strings)

Co-authored-by: claleb <weblate@claleb.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2023-02-20 02:00:25 +01:00
Thomas
8c49260a67 add policy for push 2023-02-19 19:10:10 +01:00
Thomas
e013dc96c0 Merge remote-tracking branch 'origin/develop' into develop 2023-02-18 18:26:01 +01:00
Thomas
89190e5b86 fix typo 2023-02-18 18:25:56 +01:00
Thomas
4ae5e73b76 Fix crashes 2023-02-18 18:24:42 +01:00
Eduardo
2757f47cc2
Translated using Weblate (Portuguese)
Currently translated at 88.9% (975 of 1096 strings)

Co-authored-by: Eduardo <edu200399lim@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/pt/
Translation: Fedilab/Strings
2023-02-18 04:38:17 +01:00
Poesty Li
a9e2576774
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1096 of 1096 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2023-02-17 04:38:53 +01:00
Ajeje Brazorf
878c4a0a92
Translated using Weblate (Sardinian)
Currently translated at 99.0% (1086 of 1096 strings)

Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/sc/
Translation: Fedilab/Strings
2023-02-17 04:38:53 +01:00
RintanBroadleaf
9edba3bbec
Translated using Weblate (Japanese)
Currently translated at 100.0% (1096 of 1096 strings)

Co-authored-by: RintanBroadleaf <rintanbroadleaf@outlook.jp>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ja/
Translation: Fedilab/Strings
2023-02-17 04:38:52 +01:00
Thomas
bc70576a63 Release 3.18.2 2023-02-16 18:57:22 +01:00
Thomas
187d20cc15 remove freezes 2023-02-16 18:41:32 +01:00
Thomas
0bd1a8e5de update libs 2023-02-16 17:29:14 +01:00
Thomas
740b42d5d3 Fix an issue with auto display hidden media 2023-02-16 17:20:50 +01:00
Thomas
c403dc14f9 Change first media size depending of the ratio (width/height) 2023-02-16 15:46:48 +01:00
Thomas
17b26de808 Merge remote-tracking branch 'origin/develop' into develop 2023-02-16 10:36:18 +01:00
Thomas
5aad4db2a4 Fix max lenght in chat + rework truncate feature 2023-02-16 10:36:11 +01:00
Poesty Li
6581caf589
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1096 of 1096 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2023-02-15 22:04:01 +01:00
Lukáš Jelínek
7f25fe3745
Translated using Weblate (Czech)
Currently translated at 99.9% (1095 of 1096 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2023-02-15 22:04:00 +01:00
Thomas
a4ec09dc61 issue with media size when opening 2023-02-15 15:54:59 +01:00
Thomas
8079a213f7 Fix media not added in chat view 2023-02-15 15:45:50 +01:00
Thomas
c72d3432ee Merge branch 'develop' of https://codeberg.org/tom79/Fedilab into develop 2023-02-15 14:59:15 +01:00
Thomas
12decaea76 Fix media not added in chat view 2023-02-15 14:59:01 +01:00
Poesty Li
10cb858a6d
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1096 of 1096 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2023-02-15 08:45:43 +01:00
Oğuz Ersen
b3f820d840
Translated using Weblate (Turkish)
Currently translated at 100.0% (1096 of 1096 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2023-02-15 08:45:43 +01:00
josé m
28d91cce16
Translated using Weblate (Galician)
Currently translated at 100.0% (1096 of 1096 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2023-02-15 08:45:42 +01:00
claleb
a9dda8f263
Translated using Weblate (German)
Currently translated at 100.0% (1096 of 1096 strings)

Co-authored-by: claleb <weblate@claleb.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2023-02-15 08:45:42 +01:00
Oğuz Ersen
777ed0a673
Translated using Weblate (Turkish)
Currently translated at 100.0% (1094 of 1094 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2023-02-14 19:11:19 +01:00
Lukáš Jelínek
4b1b48c235
Translated using Weblate (Czech)
Currently translated at 99.9% (1093 of 1094 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2023-02-14 19:11:19 +01:00
Thomas
67b9e8c7d0 Release 3.18.1 2023-02-14 19:11:07 +01:00
Thomas
dfcaed080b Some small changes 2023-02-14 19:07:05 +01:00
Thomas
e00bd1e7c3 some changes for chat 2023-02-14 17:42:27 +01:00
Thomas
3e90ac4b9e Merge pull request 'Add support for Nyastodon-style emoji reactions' (#789) from skyevg/Fedilab:develop into develop
Reviewed-on: https://codeberg.org/tom79/Fedilab/pulls/789
2023-02-14 15:34:43 +00:00
Thomas
4d92dc1f42 Merge branch 'develop' into develop 2023-02-14 15:23:55 +00:00
Thomas
c29a2bd07b some changes for chat 2023-02-14 16:21:04 +01:00
Thomas
c001b83fb4 some fixes 2023-02-14 11:26:59 +01:00
Thomas
4b8563fe7e working 2023-02-14 11:02:04 +01:00
Thomas
4eb4c6eea5 change lib 2023-02-14 10:45:23 +01:00
8e12dffb69
Add support for Nyastodon-style emoji reactions 2023-02-09 10:25:10 +09:00
1014 changed files with 37945 additions and 9560 deletions

View file

@ -36,6 +36,5 @@ Android version:
<!-- If you read our contributing advice --> <!-- If you read our contributing advice -->
[ ] - I read - [ ] I read the [contributing page](https://codeberg.org/tom79/Fedilab/src/branch/main/CONTRIBUTING.md)
the [contributing page](https://codeberg.org/tom79/Fedilab/src/branch/main/CONTRIBUTING.md)

View file

@ -22,5 +22,4 @@ labels:
<!-- If you read our contributing advice --> <!-- If you read our contributing advice -->
[ ] - I read - [ ] I read the [contributing page](https://codeberg.org/tom79/Fedilab/src/branch/main/CONTRIBUTING.md)
the [contributing page](https://codeberg.org/tom79/Fedilab/src/branch/main/CONTRIBUTING.md)

View file

@ -3,7 +3,7 @@ CONTRIBUTING
### Localizations: ### Localizations:
Fedilab works only with [Weblate]https://hosted.weblate.org/projects/fedilab), which offers nice Fedilab works only with [Weblate](https://hosted.weblate.org/projects/fedilab), which offers nice
tools for helping in translations. New translations will be automatically merged in a branch. tools for helping in translations. New translations will be automatically merged in a branch.
If you're submiting a merge request and your work adds new strings to the app, make sure they only If you're submiting a merge request and your work adds new strings to the app, make sure they only

View file

@ -1,24 +1,46 @@
[![Translation status](https://hosted.weblate.org/widgets/fedilab/-/strings/svg-badge.svg)](https://hosted.weblate.org/engage/fedilab/)
&nbsp;&nbsp;&nbsp;[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
# Fedilab is a multi-accounts client for Mastodon, Pleroma, Friendica and Pixelfed <img src="src/fdroid/fastlane/metadata/android/en/images/icon.png" width="100"/>
## Donate # Fedilab
A multi-accounts client for Mastodon, Pleroma, Friendica and Pixelfed
[<img alt="Donate using Liberapay" src="https://img.shields.io/liberapay/patrons/tom79.svg?logo=liberapay"/>](https://liberapay.com/tom79/donate)
[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg?style=for-the-badge)](https://www.gnu.org/licenses/gpl-3.0)
[![Weblate project translated](https://img.shields.io/weblate/progress/fedilab?server=https%3A%2F%2Fhosted.weblate.org&style=for-the-badge)](https://hosted.weblate.org/engage/fedilab/)
[![F-Droid Version](https://img.shields.io/f-droid/v/fr.gouv.etalab.mastodon?label=F-Droid&style=for-the-badge)](https://f-droid.org/app/fr.gouv.etalab.mastodon)
[![Liberapay patrons](https://img.shields.io/liberapay/patrons/tom79?label=Liberapay&style=for-the-badge)](https://liberapay.com/tom79/donate)
[![Open Collective backers and sponsors](https://img.shields.io/opencollective/all/fedilab?label=Open%20Collective&style=for-the-badge)](https://opencollective.com/fedilab)
## Screenshots
<img src="src/fdroid/fastlane/metadata/android/en/images/phoneScreenshots/1.png" width="150"/>
&nbsp;&nbsp;
<img src="src/fdroid/fastlane/metadata/android/en/images/phoneScreenshots/2.png" width="150"/>
&nbsp;&nbsp;
<img src="src/fdroid/fastlane/metadata/android/en/images/phoneScreenshots/4.png" width="150"/>
&nbsp;&nbsp;
<img src="src/fdroid/fastlane/metadata/android/en/images/phoneScreenshots/7.png" width="150"/>
## Download ## Download
[<img alt='Get it on Google Play' src='./images/get-it-on-play.png' height="80"/>](https://play.google.com/store/apps/details?id=app.fedilab.android) [<img alt='Get it on F-Droid' src='./images/get-it-on-fdroid.png' height="80"/>](https://f-droid.org/app/fr.gouv.etalab.mastodon)&nbsp;&nbsp;[<img alt='Get it on Google Play' src='./images/get-it-on-play.png' height="80"/>](https://play.google.com/store/apps/details?id=app.fedilab.android)
&nbsp;&nbsp;[<img alt='Get it on F-Droid' src='./images/get-it-on-fdroid.png' height="80"/>](https://f-droid.org/app/fr.gouv.etalab.mastodon)
<img src='https://img.shields.io/f-droid/v/fr.gouv.etalab.mastodon?include_prereleases' /> ## Translate
- [Weblate](https://hosted.weblate.org/engage/fedilab/)
## Donate
- [Liberapay](https://liberapay.com/tom79/donate)
- [Open Collective](https://opencollective.com/fedilab)
- [More...](https://fedilab.app/page/donations/)
## Resources ## Resources
[WIKI](https://fedilab.app/wiki/home/) - [Wiki](https://wiki.fedilab.app)
- [Releases + Changelogs](https://codeberg.org/tom79/Fedilab/releases)
[Release notes](https://codeberg.org/tom79/Fedilab/tags) ## Contact
- Fedi:<br>[toot.fedilab.app/@apps](https://toot.fedilab.app/@apps)
Lead developer: [toot.fedilab.app/@apps](https://toot.fedilab.app/@apps)

View file

@ -8,13 +8,13 @@ plugins {
} }
def flavor def flavor
android { android {
compileSdk 33 compileSdk 34
defaultConfig { defaultConfig {
minSdk 21 minSdk 21
targetSdk 33 targetSdk 34
versionCode 478 versionCode 534
versionName "3.18.0" versionName "3.33.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
flavorDimensions "default" flavorDimensions "default"
@ -25,12 +25,13 @@ android {
} }
debug { debug {
applicationIdSuffix '.debug' applicationIdSuffix '.debug'
pseudoLocalesEnabled true
} }
} }
compileOptions { compileOptions {
coreLibraryDesugaringEnabled true coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_17
} }
productFlavors { productFlavors {
fdroid { fdroid {
@ -46,16 +47,11 @@ android {
flavor = "playstore" flavor = "playstore"
} }
} }
lintOptions {
checkReleaseBuilds false
abortOnError false
}
buildFeatures { buildFeatures {
viewBinding true viewBinding true
} }
sourceSets { sourceSets {
playstore { playstore {
manifest.srcFile "src/playstore/AndroidManifest.xml"
java.srcDirs = ['src/main/java', 'src/playstore/java'] java.srcDirs = ['src/main/java', 'src/playstore/java']
res.srcDirs = ['src/main/res', 'src/playstore/res'] res.srcDirs = ['src/main/res', 'src/playstore/res']
} }
@ -77,15 +73,25 @@ android {
'src/main/res/menus/peertube', 'src/main/res/menus/peertube',
'src/main/res/menus', 'src/main/res/menus',
'src/main/res/values',
'src/main/res' 'src/main/res'
] ]
} }
} }
configurations { configurations {
cleanedAnnotations
implementation.exclude group: 'org.jetbrains', module: 'annotations'
all { all {
exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel-ktx' exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel-ktx'
} }
} }
namespace 'app.fedilab.android'
lint {
abortOnError false
checkReleaseBuilds false
}
buildToolsVersion '35.0.0'
} }
allprojects { allprojects {
repositories { repositories {
@ -94,20 +100,22 @@ allprojects {
} }
} }
dependencies { dependencies {
implementation project(':autoimageslider') implementation 'org.unifiedpush.android:connector:3.0.9'
implementation 'androidx.appcompat:appcompat:1.6.0'
implementation 'com.google.android.material:material:1.7.0' playstoreImplementation('org.unifiedpush.android:embedded-fcm-distributor:3.0.0')
implementation 'com.jaredrummler:colorpicker:1.1.0' implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'com.google.android.material:material:1.10.0'
implementation "com.google.code.gson:gson:2.9.1"
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
implementation "com.google.code.gson:gson:2.10.1"
implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:converter-simplexml:2.9.0' implementation 'com.squareup.retrofit2:converter-simplexml:2.9.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.preference:preference:1.2.0' implementation 'androidx.preference:preference:1.2.1'
implementation "org.conscrypt:conscrypt-android:2.5.2" implementation "org.conscrypt:conscrypt-android:2.5.2"
implementation 'com.vanniktech:emoji-one:0.6.0' implementation 'com.vanniktech:emoji-one:0.6.0'
implementation 'com.github.GrenderG:Toasty:1.5.2' implementation 'com.github.GrenderG:Toasty:1.5.2'
@ -118,91 +126,88 @@ dependencies {
transitive = false transitive = false
} }
implementation "org.jsoup:jsoup:1.15.1" implementation "org.jsoup:jsoup:1.18.1"
implementation 'com.github.mergehez:ArgPlayer:v3.1' implementation 'com.github.mergehez:ArgPlayer:v3.1'
implementation project(':autoimageslider')
implementation project(path: ':mytransl') implementation project(path: ':mytransl')
implementation project(path: ':ratethisapp') implementation project(path: ':ratethisapp')
implementation project(path: ':sparkbutton') implementation project(path: ':sparkbutton')
implementation project(path: ':colorPicker')
implementation project(path: ':mathjaxandroid')
implementation project(path: ':doubletapplayerview')
implementation 'com.burhanrashid52:photoeditor:1.5.1' implementation 'com.burhanrashid52:photoeditor:1.5.1'
implementation("com.vanniktech:android-image-cropper:4.3.3") implementation("com.vanniktech:android-image-cropper:4.3.3")
implementation project(path: ':mathjaxandroid')
annotationProcessor "com.github.bumptech.glide:compiler:4.12.0" annotationProcessor "com.github.bumptech.glide:compiler:4.12.0"
implementation 'jp.wasabeef:glide-transformations:4.3.0' implementation 'jp.wasabeef:glide-transformations:4.3.0'
implementation 'com.github.penfeizhou.android.animation:glide-plugin:2.23.0' implementation 'com.github.penfeizhou.android.animation:glide-plugin:3.0.5'
implementation 'com.google.android.exoplayer:exoplayer:2.18.1' implementation 'androidx.media3:media3-exoplayer-hls:1.2.1'
implementation "androidx.viewpager2:viewpager2:1.0.0" implementation "androidx.media3:media3-exoplayer:1.2.1"
implementation "androidx.media3:media3-exoplayer-dash:1.2.1"
implementation "androidx.media3:media3-ui:1.2.1"
implementation "androidx.media3:media3-session:1.2.1"
implementation "androidx.viewpager2:viewpager2:1.1.0"
implementation 'com.github.piasy:rxandroidaudio:1.7.0' implementation 'com.github.piasy:rxandroidaudio:1.7.0'
implementation 'com.github.piasy:AudioProcessor:1.7.0' implementation 'com.github.piasy:AudioProcessor:1.7.0'
implementation "androidx.work:work-runtime:2.7.1" implementation "androidx.work:work-runtime:2.9.0"
implementation 'app.futured.hauler:hauler:5.0.0' implementation 'app.futured.hauler:hauler:5.0.0'
implementation "com.github.chrisbanes:PhotoView:2.3.0" implementation "com.github.chrisbanes:PhotoView:2.3.0"
implementation "ch.acra:acra-mail:5.9.6" implementation "ch.acra:acra-mail:5.11.3"
implementation "ch.acra:acra-limiter:5.9.3" implementation "ch.acra:acra-limiter:5.11.3"
implementation "ch.acra:acra-dialog:5.9.6" implementation "ch.acra:acra-dialog:5.11.3"
implementation "com.madgag.spongycastle:bctls-jdk15on:1.58.0.0" implementation "com.madgag.spongycastle:bctls-jdk15on:1.58.0.0"
implementation 'com.github.UnifiedPush:android-connector:2.0.1'
// implementation 'com.github.UnifiedPush:android-foss_embedded_fcm_distributor:1.0.0-beta1'
playstoreImplementation('com.github.UnifiedPush:android-embedded_fcm_distributor:2.1.3') {
exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
}
implementation 'com.burhanrashid52:photoeditor:1.5.1' implementation 'com.burhanrashid52:photoeditor:1.5.1'
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
implementation 'androidx.lifecycle:lifecycle-livedata:2.5.1' implementation 'androidx.lifecycle:lifecycle-livedata:2.8.7'
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.5.1' implementation 'androidx.lifecycle:lifecycle-viewmodel:2.8.7'
implementation 'androidx.navigation:navigation-fragment:2.5.3' implementation 'androidx.navigation:navigation-fragment:2.8.7'
implementation 'androidx.navigation:navigation-ui:2.5.3' implementation 'androidx.navigation:navigation-ui:2.8.7'
testImplementation 'junit:junit:' testImplementation 'junit:junit:'
androidTestImplementation 'androidx.test.ext:junit:1.1.4' androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1' // debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1'
implementation 'com.r0adkll:slidableactivity:2.1.0' implementation 'com.r0adkll:slidableactivity:2.1.0'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'
implementation 'androidx.vectordrawable:vectordrawable:1.2.0'
implementation 'androidx.vectordrawable:vectordrawable:1.1.0' implementation "androidx.fragment:fragment:1.8.6"
implementation "androidx.fragment:fragment:1.5.5"
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.browser:browser:1.4.0' implementation 'androidx.browser:browser:1.8.0'
implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'com.github.amoskorir:avatarimagegenerator:1.5.0' implementation 'com.github.amoskorir:avatarimagegenerator:1.5.0'
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0'
implementation 'com.google.android.exoplayer:extension-mediasession:2.18.1'
implementation "com.github.mabbas007:TagsEditText:1.0.5" implementation "com.github.mabbas007:TagsEditText:1.0.5"
implementation "net.gotev:uploadservice:4.7.0" implementation "net.gotev:uploadservice:4.9.2"
implementation "net.gotev:uploadservice-okhttp:4.7.0" implementation "net.gotev:uploadservice-okhttp:4.9.2"
implementation 'androidx.media:media:1.6.0' implementation 'androidx.media:media:1.7.0'
implementation 'com.github.mancj:MaterialSearchBar:0.8.5' implementation 'com.github.mancj:MaterialSearchBar:0.8.5'
implementation 'com.github.vkay94:DoubleTapPlayerView:1.0.0' implementation 'com.github.androidmads:QRGenerator:1.0.1'
implementation 'io.noties.markwon:core:4.6.2'
implementation 'io.noties.markwon:ext-tables:4.6.2'
implementation 'io.noties.markwon:syntax-highlight:4.6.2'
implementation 'io.noties.markwon:ext-strikethrough:4.6.2'
implementation 'io.noties.markwon:inline-parser:4.6.2'
annotationProcessor 'io.noties:prism4j-bundler:2.0.0'
//************ CAST **************/// //************ CAST **************///
//---> Google libs (google_full) //---> Google libs (google_full)
playstoreImplementation "com.google.android.gms:play-services-cast-tv:19.0.1" playstoreImplementation "com.google.android.gms:play-services-cast-tv:21.1.1"
playstoreImplementation "com.google.android.gms:play-services-cast:21.0.1" playstoreImplementation "com.google.android.gms:play-services-cast:22.0.0"
playstoreImplementation "androidx.mediarouter:mediarouter:1.3.0" playstoreImplementation "androidx.mediarouter:mediarouter:1.7.0"
playstoreImplementation 'com.google.android.gms:play-services-cast-framework:21.0.1' playstoreImplementation 'com.google.android.gms:play-services-cast-framework:22.0.0'
playstoreImplementation "com.google.android.gms:play-services-cast-tv:19.0.1"
playstoreImplementation "com.google.android.gms:play-services-cast:21.0.1"
playstoreImplementation "androidx.mediarouter:mediarouter:1.3.0"
playstoreImplementation 'com.google.android.gms:play-services-cast-framework:21.0.1'
//----> Other flavors //----> Other flavors
fdroidImplementation 'su.litvak.chromecast:api-v2:0.11.3' fdroidImplementation 'su.litvak.chromecast:api-v2:0.11.3'
fdroidImplementation 'com.fasterxml.jackson.core:jackson-core:2.12.0' fdroidImplementation 'com.fasterxml.jackson.core:jackson-core:2.12.0'
fdroidImplementation 'org.slf4j:slf4j-simple:1.7.30' fdroidImplementation 'org.slf4j:slf4j-simple:1.7.30'
fdroidImplementation 'com.github.evozi:Cyanea:1.0.7'
fdroidImplementation 'su.litvak.chromecast:api-v2:0.11.3'
fdroidImplementation 'com.fasterxml.jackson.core:jackson-core:2.12.0'
fdroidImplementation 'org.slf4j:slf4j-simple:1.7.30'
} }
def getCurrentFlavor() { def getCurrentFlavor() {

View file

@ -28,9 +28,10 @@ import android.view.View;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.media3.common.Player;
import androidx.media3.exoplayer.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -38,6 +39,7 @@ import org.jetbrains.annotations.NotNull;
import java.io.IOException; import java.io.IOException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import app.fedilab.android.BuildConfig;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.databinding.ActivityPeertubeBinding; import app.fedilab.android.databinding.ActivityPeertubeBinding;
import app.fedilab.android.mastodon.activities.BaseBarActivity; import app.fedilab.android.mastodon.activities.BaseBarActivity;
@ -124,7 +126,8 @@ public class BasePeertubeActivity extends BaseBarActivity {
if (PeertubeBaseMainActivity.chromecastActivated) { if (PeertubeBaseMainActivity.chromecastActivated) {
b.putInt("displayed", 0); b.putInt("displayed", 0);
intentBC.putExtras(b); intentBC.putExtras(b);
LocalBroadcastManager.getInstance(BasePeertubeActivity.this).sendBroadcast(intentBC); intentBC.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intentBC);
Handler mainHandler = new Handler(Looper.getMainLooper()); Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> { Runnable myRunnable = () -> {
binding.doubleTapPlayerView.setVisibility(View.VISIBLE); binding.doubleTapPlayerView.setVisibility(View.VISIBLE);
@ -136,7 +139,8 @@ public class BasePeertubeActivity extends BaseBarActivity {
b.putInt("displayed", 1); b.putInt("displayed", 1);
b.putSerializable("castedTube", peertube); b.putSerializable("castedTube", peertube);
intentBC.putExtras(b); intentBC.putExtras(b);
LocalBroadcastManager.getInstance(BasePeertubeActivity.this).sendBroadcast(intentBC); intentBC.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intentBC);
try { try {
Handler mainHandler = new Handler(Looper.getMainLooper()); Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> { Runnable myRunnable = () -> {

View file

@ -19,12 +19,14 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.view.View; import android.view.View;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.core.content.ContextCompat;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
@ -34,6 +36,7 @@ import java.util.Collections;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.List; import java.util.List;
import app.fedilab.android.BuildConfig;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.databinding.ActivityMainPeertubeBinding; import app.fedilab.android.databinding.ActivityMainPeertubeBinding;
import app.fedilab.android.mastodon.activities.BaseActivity; import app.fedilab.android.mastodon.activities.BaseActivity;
@ -49,34 +52,35 @@ public abstract class PeertubeBaseMainActivity extends BaseActivity implements C
public static List<ChromeCast> chromeCasts; public static List<ChromeCast> chromeCasts;
public static ChromeCast chromeCast; public static ChromeCast chromeCast;
public static boolean chromecastActivated = false; public static boolean chromecastActivated = false;
protected ActivityMainPeertubeBinding binding; protected ActivityMainPeertubeBinding parentBinding;
private BroadcastReceiver manage_chromecast; private BroadcastReceiver manage_chromecast;
private VideoData.Video castedTube; private VideoData.Video castedTube;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
binding = ActivityMainPeertubeBinding.inflate(getLayoutInflater()); parentBinding = ActivityMainPeertubeBinding.inflate(getLayoutInflater());
View view = binding.getRoot(); View view = parentBinding.getRoot();
setContentView(view); setContentView(view);
ChromeCastsListener chromeCastsListener = this; ChromeCastsListener chromeCastsListener = this;
ChromeCasts.registerListener(chromeCastsListener); ChromeCasts.registerListener(chromeCastsListener);
binding.castClose.setOnClickListener(v -> { parentBinding.castClose.setOnClickListener(v -> {
Intent intentBC = new Intent(Helper.RECEIVE_CAST_SETTINGS); Intent intentBC = new Intent(Helper.RECEIVE_CAST_SETTINGS);
Bundle b = new Bundle(); Bundle b = new Bundle();
b.putInt("displayed", 0); b.putInt("displayed", 0);
intentBC.putExtras(b); intentBC.putExtras(b);
LocalBroadcastManager.getInstance(PeertubeBaseMainActivity.this).sendBroadcast(intentBC); intentBC.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intentBC);
}); });
binding.castTogglePlay.setOnClickListener(v -> { parentBinding.castTogglePlay.setOnClickListener(v -> {
if (chromeCast != null) { if (chromeCast != null) {
new Thread(() -> { new Thread(() -> {
try { try {
Handler mainHandler = new Handler(Looper.getMainLooper()); Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> binding.castTogglePlay.setVisibility(View.GONE); Runnable myRunnable = () -> parentBinding.castTogglePlay.setVisibility(View.GONE);
mainHandler.post(myRunnable); mainHandler.post(myRunnable);
int icon = -1; int icon = -1;
if (chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PLAYING) { if (chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PLAYING) {
@ -88,10 +92,10 @@ public abstract class PeertubeBaseMainActivity extends BaseActivity implements C
} }
if (icon != -1) { if (icon != -1) {
int finalIcon = icon; int finalIcon = icon;
myRunnable = () -> binding.castTogglePlay.setImageResource(finalIcon); myRunnable = () -> parentBinding.castTogglePlay.setImageResource(finalIcon);
mainHandler.post(myRunnable); mainHandler.post(myRunnable);
} }
myRunnable = () -> binding.castTogglePlay.setVisibility(View.VISIBLE); myRunnable = () -> parentBinding.castTogglePlay.setVisibility(View.VISIBLE);
mainHandler.post(myRunnable); mainHandler.post(myRunnable);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
@ -127,14 +131,14 @@ public abstract class PeertubeBaseMainActivity extends BaseActivity implements C
if (displayed == 1) { if (displayed == 1) {
chromecastActivated = true; chromecastActivated = true;
if (castedTube != null) { if (castedTube != null) {
binding.castInfo.setVisibility(View.VISIBLE); parentBinding.castInfo.setVisibility(View.VISIBLE);
Helper.loadGiF(PeertubeBaseMainActivity.this, castedTube.getThumbnailPath(), binding.castView); Helper.loadGiF(PeertubeBaseMainActivity.this, castedTube.getThumbnailPath(), parentBinding.castView);
binding.castTitle.setText(castedTube.getTitle()); parentBinding.castTitle.setText(castedTube.getTitle());
binding.castDescription.setText(castedTube.getDescription()); parentBinding.castDescription.setText(castedTube.getDescription());
} }
} else if (displayed == 0) { } else if (displayed == 0) {
chromecastActivated = false; chromecastActivated = false;
binding.castInfo.setVisibility(View.GONE); parentBinding.castInfo.setVisibility(View.GONE);
new Thread(() -> { new Thread(() -> {
try { try {
if (chromeCast != null) { if (chromeCast != null) {
@ -147,7 +151,7 @@ public abstract class PeertubeBaseMainActivity extends BaseActivity implements C
} }
} }
}; };
LocalBroadcastManager.getInstance(PeertubeBaseMainActivity.this).registerReceiver(manage_chromecast, new IntentFilter(Helper.RECEIVE_CAST_SETTINGS)); ContextCompat.registerReceiver(PeertubeBaseMainActivity.this, manage_chromecast, new IntentFilter(Helper.RECEIVE_CAST_SETTINGS), ContextCompat.RECEIVER_NOT_EXPORTED);
} }
@Override @Override
@ -169,8 +173,8 @@ public abstract class PeertubeBaseMainActivity extends BaseActivity implements C
} }
try { try {
if (chromeCast.isAppRunning(Helper.CAST_ID) && chromeCast.getMediaStatus() != null && chromeCast.getMediaStatus().playerState != null) { if (chromeCast.isAppRunning(Helper.CAST_ID) && chromeCast.getMediaStatus() != null && chromeCast.getMediaStatus().playerState != null) {
if (binding.castInfo.getVisibility() == View.GONE) { if (parentBinding.castInfo.getVisibility() == View.GONE) {
binding.castInfo.setVisibility(View.VISIBLE); parentBinding.castInfo.setVisibility(View.VISIBLE);
} }
} }
} catch (IOException e) { } catch (IOException e) {
@ -189,8 +193,11 @@ public abstract class PeertubeBaseMainActivity extends BaseActivity implements C
super.onDestroy(); super.onDestroy();
ChromeCasts.unregisterListener(this); ChromeCasts.unregisterListener(this);
if (manage_chromecast != null) { if (manage_chromecast != null) {
LocalBroadcastManager.getInstance(PeertubeBaseMainActivity.this).unregisterReceiver(manage_chromecast); try {
unregisterReceiver(manage_chromecast);
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
new Thread(() -> { new Thread(() -> {
if (chromeCasts != null && chromeCasts.size() > 0) { if (chromeCasts != null && chromeCasts.size() > 0) {
for (ChromeCast cast : chromeCasts) { for (ChromeCast cast : chromeCasts) {

View file

@ -1,17 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools">
package="app.fedilab.android">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission <uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" /> tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-feature <uses-feature
android:name="android.hardware.camera" android:name="android.hardware.camera"
@ -24,6 +25,8 @@
<application <application
android:name="app.fedilab.android.MainApplication" android:name="app.fedilab.android.MainApplication"
android:allowBackup="false" android:allowBackup="false"
android:dataExtractionRules="@xml/extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:icon="@mipmap/ic_launcher_bubbles" android:icon="@mipmap/ic_launcher_bubbles"
android:label="@string/app_name" android:label="@string/app_name"
@ -69,106 +72,13 @@
android:host="*" android:host="*"
android:pathPrefix="/@" android:pathPrefix="/@"
android:scheme="https" /> android:scheme="https" />
<data
android:host="*"
android:pathPrefix="/notes"
android:scheme="https" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity-alias
android:name=".activities.MainActivity.Bubbles"
android:enabled="true"
android:exported="true"
android:icon="@mipmap/ic_launcher_bubbles"
android:roundIcon="@mipmap/ic_launcher_bubbles_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.Fediverse"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_fediverse"
android:roundIcon="@mipmap/ic_launcher_fediverse_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.Hero"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_hero"
android:roundIcon="@mipmap/ic_launcher_hero_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.Atom"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_atom"
android:roundIcon="@mipmap/ic_launcher_atom_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.BrainCrash"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_crash"
android:roundIcon="@mipmap/ic_launcher_crash_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.Mastalab"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_mastalab"
android:roundIcon="@mipmap/ic_launcher_mastalab_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity <activity
android:name=".activities.LoginActivity" android:name=".activities.LoginActivity"
@ -262,7 +172,15 @@
android:configChanges="keyboardHidden|orientation|screenSize" android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/action_about" android:label="@string/action_about"
android:theme="@style/AppThemeBar" /> android:theme="@style/AppThemeBar" />
<activity
android:name=".mastodon.activities.TimelineActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@style/AppThemeBar" />
<activity
android:name=".mastodon.activities.CheckHomeCacheActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/home_cache"
android:theme="@style/AppThemeBar" />
<activity <activity
android:name=".mastodon.activities.admin.AdminDomainBlockActivity" android:name=".mastodon.activities.admin.AdminDomainBlockActivity"
android:configChanges="keyboardHidden|orientation|screenSize" android:configChanges="keyboardHidden|orientation|screenSize"
@ -316,6 +234,7 @@
android:configChanges="keyboardHidden|orientation|screenSize" /> android:configChanges="keyboardHidden|orientation|screenSize" />
<activity <activity
android:name=".mastodon.activities.MediaActivity" android:name=".mastodon.activities.MediaActivity"
android:hardwareAccelerated="true"
android:configChanges="keyboardHidden|orientation|screenSize" android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@style/Transparent" /> android:theme="@style/Transparent" />
@ -345,6 +264,16 @@
android:label="@string/action_cache" android:label="@string/action_cache"
android:theme="@style/AppThemeBar" /> android:theme="@style/AppThemeBar" />
<activity android:name=".activities.WebActivityPub"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="web+ap" />
</intent-filter>
</activity>
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileProvider" android:authorities="${applicationId}.fileProvider"
@ -363,18 +292,12 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver <service android:name=".mastodon.services.PushServiceImpl"
android:name=".mastodon.services.CustomReceiver" android:exported="false">
android:enabled="true"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="org.unifiedpush.android.connector.MESSAGE" /> <action android:name="org.unifiedpush.android.connector.PUSH_EVENT"/>
<action android:name="org.unifiedpush.android.connector.UNREGISTERED" />
<action android:name="org.unifiedpush.android.connector.NEW_ENDPOINT" />
<action android:name="org.unifiedpush.android.connector.REGISTRATION_FAILED" />
<action android:name="org.unifiedpush.android.connector.REGISTRATION_REFUSED" />
</intent-filter> </intent-filter>
</receiver> </service>
<activity <activity
@ -382,7 +305,7 @@
android:configChanges="keyboardHidden|orientation|screenSize" /> android:configChanges="keyboardHidden|orientation|screenSize" />
<activity <activity
android:name=".peertube.activities.PeertubeActivity" android:name=".peertube.activities.PeertubeActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" android:configChanges="keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
android:launchMode="singleTask" android:launchMode="singleTask"
android:resizeableActivity="true" android:resizeableActivity="true"
android:supportsPictureInPicture="true" android:supportsPictureInPicture="true"
@ -472,8 +395,366 @@
<service <service
android:name=".peertube.services.RetrieveInfoService" android:name=".peertube.services.RetrieveInfoService"
android:foregroundServiceType="dataSync"
android:exported="false" /> android:exported="false" />
<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="dataSync"
tools:node="merge" />
<!-- ============ -->
<!-- CUSTOM ICONS -->
<!-- ============ -->
<activity-alias
android:name=".activities.MainActivity.Bubbles"
android:enabled="true"
android:exported="true"
android:icon="@mipmap/ic_launcher_bubbles"
android:roundIcon="@mipmap/ic_launcher_bubbles_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.BubblesUA"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_bubbles_ua"
android:roundIcon="@mipmap/ic_launcher_bubbles_ua_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.BubblesPeaGreen"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_bubbles_pea_green"
android:roundIcon="@mipmap/ic_launcher_bubbles_pea_green_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.BubblesPride"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_bubbles_pride"
android:roundIcon="@mipmap/ic_launcher_bubbles_pride_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.BubblesPink"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_bubbles_pink"
android:roundIcon="@mipmap/ic_launcher_bubbles_pink_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.BubblesPirate"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_bubbles_pirate"
android:roundIcon="@mipmap/ic_launcher_bubbles_pirate_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.Fediverse"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_fediverse"
android:roundIcon="@mipmap/ic_launcher_fediverse_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.Hero"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_hero"
android:roundIcon="@mipmap/ic_launcher_hero_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.Atom"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_atom"
android:roundIcon="@mipmap/ic_launcher_atom_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.BrainCrash"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_crash"
android:roundIcon="@mipmap/ic_launcher_crash_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.Mastalab"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_mastalab"
android:roundIcon="@mipmap/ic_launcher_mastalab_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.Leaf"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_leaf"
android:roundIcon="@mipmap/ic_launcher_leaf_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.Offset"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_offset"
android:roundIcon="@mipmap/ic_launcher_offset_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.Jungle"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_jungle"
android:roundIcon="@mipmap/ic_launcher_jungle_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.Confetti"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_confetti"
android:roundIcon="@mipmap/ic_launcher_confetti_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.Spaghetti"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_spaghetti"
android:roundIcon="@mipmap/ic_launcher_spaghetti_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.Warm"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_warm"
android:roundIcon="@mipmap/ic_launcher_warm_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.Purple1"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_purple_1"
android:roundIcon="@mipmap/ic_launcher_purple_1_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.Purple2"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_purple_2"
android:roundIcon="@mipmap/ic_launcher_purple_2_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.YellowHeadedRedBubble"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_yellow_headed_red_bubble"
android:roundIcon="@mipmap/ic_launcher_yellow_headed_red_bubble_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
<activity-alias
android:name=".activities.MainActivity.Mosaic"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_mosaic"
android:roundIcon="@mipmap/ic_launcher_mosaic_round"
android:targetActivity=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/compose_shortcuts" />
</activity-alias>
</application> </application>
</manifest> </manifest>

View file

@ -1,4 +1,229 @@
[ [
{
"version": "3.33.1",
"code": "534",
"note": "Added:\n- Highlight bottom hashtags\n- Support Trending Links\n- Featured tags displayed in profiles\n- Add/Remove featured tags from the profile editor\n\nChanged:\n- Add confirmation dialog when long pressing the boost button\n- Open messages by tapping on Scheduled Boost\n- Improve language picker when filtered with some languages\n\nFixed:\n- Limits number of fetch for filters\n- Pleroma instances cannot select media\n- Wrong messages deleted for scheduled (messages and boosts)\n- Fix a crash with long threads\n- Fix a potential memory issue for not cropped media\n- Fix embedded quotes not displayed\n- Some crashes"
},
{
"version": "3.32.3",
"code": "532",
"note": "Fixed:\n- Polls not displayed\n- Pagination with trends\n- Push notifications not working on some devices"
},
{
"version": "3.32.2",
"code": "531",
"note": "Added:\n- An outline around media\n\nChanged:\n- Make username, display name in nav drawer clickable\n- Gif media not animated by default\n- Disable by default the mention to the booster when replying. Can be enabled in Settings > Compose (per account)\n\nFixed:\n- Wrong preview picture on share from another app\n- Crash when translating with MinT\n- Refresh and pagination broken for the Trending timeline\n- Fix lags / Crashes"
},
{
"version": "3.32.1",
"code": "530",
"note": "Fixed:\n- Fix a crash on some devices\n- Hide quote button\n- Fix a layout issue with pictures in landscape\n- Fix a crash when opening the original message from a picture"
},
{
"version": "3.32.0",
"code": "529",
"note": "Added:\n- Add option to disable auto hiding compose button\n\nChanged:\n- Add more content descriptions for buttons\n- Update some buttons\n- Update navigation drawer header\n- Squeeze action buttons when needed to prevent overlapping\n\nFixed:\n- Fix crash when media are too heavy\n- Some custom emojis in bio do not render\n- Posting messages does not work on some Friendica instances\n- Fix a crash with auto-split messages\n- Fix a crash when opening conversations\n- Fix a background color issue when displaying media"
},
{
"version": "3.31.3",
"code": "528",
"note": "Added:\n- Add new icon launchers (Settings > Interface)\n\nChanged:\n- Make logout/proxy button more visible in main menu\n- Remove permission FOREGROUND_SERVICE\n- Improve a little more media layout with translations\n\nFixed:\n- Fix status bar icons not visible in light theme with custom accent color\n- Reaction buttons not clickable for some instances"
},
{
"version": "3.31.2",
"code": "527",
"note": "Added:\n- Add support to URL scheme \"web+ap\" for opening profiles with the app\n\nChanged:\n- Layout for media descriptions\n\nFixed:\n- Fix a crash when translating media descriptions\n- Handle included twice when replying to a self user's boost\n- Fix color issues when using a custom theme"
},
{
"version": "3.31.1",
"code": "526",
"note": "Added:\n- Add MinT machine translation system support\n- Add support \"instance only\" for GoToSocial\n\nFixed:\n- GIF not displayed in timelines\n- Fix a crash when unpinning timelines\n- Top bar coloring at scroll for conversations\n- Black screen when going back from the Peertube section"
},
{
"version": "3.31.0",
"code": "524",
"note": "Added:\n- Pinned Trending Timeline (can be hidden in Manage Timelines)\n- Add a fallback to the default translator\n- New Pixelfed entry in Settings to disable fullscreen for media\n- Add tooltips for tabs in profiles\n\nChanged:\n- Increase touch area of reply buttons\n- Show a dialog after settings export\n\nFixed:\n- Fix media description not updated when there are several\n- Only a part of DeepL translations are shown\n- Fix Lingva truncated translations\n- Fix a crash when fetching remote profiles\n- Fix a crash with animated emoji\n- Fix a crash when displaying Home cache charts"
},
{
"version": "3.30.1",
"code": "523",
"note": "Added:\n- Follow Twitter tags\n\nChange:\n- Remove automatic backup (Google only)\n\nFixed:\n- Push notifications not working for some devices\n- Filters not applied to media description\n- Fix a crash with animated images in timelines\n- Fix a crash for long threads\n- Fix a crash due to some messages (happened in different timelines)"
},
{
"version": "3.30.0",
"code": "522",
"note": "Added:\n- Allow to follow the discover timelines of Pixelfed instances\n- Keep media proportions in timeline for Pixelfed\n- Add a like button in timelines for Pixelfed\n- Allow to login with a token\n\nChange:\n- Use Pixelfed layout when following Pixelfed instances\n- Allow to scroll buttons for larger screens in conversations\n- Move QR code into header\n\nFixed:\n- Not clickable URLs when Markdown is enabled\n- Nitter to follow Twitter accounts\n- Pixelfed timeline not displayed by default\n- Fix admin reports not accessible\n- Top bar coloring at scroll\n- Drafts not saved when adding/editing media descriptions\n- User search suggestions have duplicates\n- Some minor crashes"
},
{
"version": "3.29.2",
"code": "518",
"note": "Fixed:\n- Fix crash when changing the type of notifications\n- Fix issue with names not displayed fully\n- Fix a crash with notifications and Sharkey\n- Fix a crash when app is back to the foreground\n- Fix xmpp links not opening xmpp clients\n- Fix wrong muted time"
},
{
"version": "3.29.1",
"code": "517",
"note": "Added:\n- Allow to edit scheduled messages from server side\n\nChanged:\n- Order list name alphabetically in profiles\n- Remove registration for Google\n\nFixed:\n- Push notifications\n- Peertube instances picker\n- Edit scheduled threads (local)\n- Instant search of Hashtag repeats results\n- Quotes broken with Markdowns\n- Fix reports crashes after submitting\n- Fix emoji picker when there is no result\n- Fix other crashes"
},
{
"version": "3.28.2",
"code": "515",
"note": "Added:\n- Display a QR code on profiles\n\nFixed:\n- Fix tap on messages in conversations\n- Pronouns taking too much place"
},
{
"version": "3.28.1",
"code": "514",
"note": "Added:\n- Allow to disable pronouns support (default: enabled)\n- Add more support for pronouns (localization in different languages)\n\nFixed:\n- Fix a crash when reporting messages\n- Fix a crash when following tags\n- Fix some display issues\n- Several fixes from the last release (3.28.0)"
},
{
"version": "3.28.0",
"code": "513",
"note": "Added:\n- Pronouns support (Timeline/Compose/Autocomplete)\n\nChanged:\n- Use Media3 library\n\nFixed:\n- Timed mute duration too long\n- Sharing videos only download them\n- Crashes from previous release"
},
{
"version": "3.27.1",
"code": "511",
"note": "Added:\n- Tap on account banners to display them as media\n\nFixed:\n- Position lost when switching between accounts\n- Wrong profiles when enabling remote conversations\n- Peertube local timeline\n- Peertube instances search\n- Crashes of the previous release"
},
{
"version": "3.27.0",
"code": "510",
"note": "Added:\n- Fixed top bar (default: disabled)\n- Usage frequency of tags when composing\n\nChanged:\n- Markdown support disabled by default\n\nFixed:\n- Fix crashes during interactions or when opening a new screen\n- Fix color of dialogs in Settings\n- Some minor crashes"
},
{
"version": "3.26.0",
"code": "505",
"note": "Added:\n- Android 14 support\n- Automatically split long messages in threads (default: ASK)\n- Links and media are clickable when composing\n- Allow to underline clickable elements (Settings > Timelines - default: disabled)\n- Allow to disable relative date in messages\n- Add a scroll bar for timelines (default: disabled)\n- Add a search bar for custom emojis\n- Links clickable in media descriptions\n\nChanged:\n- Counters close to action buttons\n- Hide emoji picker if the instance has no emoji\n- Followed tags are ordered\n- Account picker when opening with another account\n\nFixed:\n- Avoid error 429 with NTFY\n- Fix custom colors (Android 14)\n- Fix a crash when composing\n- Display issue with followed tags\n- Crashes with profiles\n- Fix an issue with poll and Pleroma\n- Emoji not displayed in the picker\n- Several crashes are fixed"
},
{
"version": "3.25.3",
"code": "504",
"note": "Added:\n- Add a scroll bar for timelines (default: disabled)\n- Add a search bar for custom emojis\n\nFixed:\n- Fix prompt to split asked several times when refusing\n- Crashes with profiles\n- Fix an issue with poll and Pleroma\n- Emoji not displayed in the picker"
},
{
"version": "3.25.2",
"code": "503",
"note": "Added:\n- Allow to underline clickable elements (Settings > Timelines - default: disabled)\n- Allow to disable relative date in messages\n\nChanged:\n- Counters close to action buttons\n- Hide emoji picker if the instance has no emoji\n- Followed tags are ordered\n- Account picker when opening with another account\n\nFixed:\n- Fix a crash when composing\n- Fix an issue with the back button\n- Display issue with followed tags"
},
{
"version": "3.25.1",
"code": "502",
"note": "Fix a crash from release 3.25.0"
},
{
"version": "3.25.0",
"code": "501",
"note": "Added:\n- Android 14 support\n- Automatically split long messages in threads (default: ASK)\n- Links and media are clickable when composing\n\nFixed:\n- Avoid error 429 with NTFY\n- Several crashes are fixed"
},
{
"version": "3.24.1",
"code": "500",
"note": "Added:\n- Three new app icons (Pride, Pink and Pirate)\n- Keep position with remote conversations\n\nFixed:\n- Markdown: stop parsing tags and support strike text\n- Cursor more visible when composing\n- Fix custom instance max char length not working\n- Tabs in profiles\n- Fix not clickable tags for some languages\n- Bug with account having huge amount of followers\n- Crash with several gif in same message\n- Poll max chars\n- Some crashes"
},
{
"version": "3.24.0",
"code": "499",
"note": "Added:\n- Markdown support (can be disabled in Settings > Timelines)\n- Hide / Show Self boosts, self replies and your own messages (Long press the Home tab)\n\nChanged:\n- Full screen size when writing media descriptions.\n- Move media descriptions to the top\n\nFixed:\n- Holes in timelines due to a cache bug\n- Spoiler issue when composing threads\n- CamelCase tags when forwarding them in replies\n- Buttons hidden by keyboard when composing\n- Overlay with menu and buttons when playing videos\n- Clicks on card do not open Mastodon posts inside the app\n- Scrollable bio when editing profiles\n- Crash when adding a user into a list\n- Longer fields when editing bio\n- Crash with Pixelfed\n"
},
{
"version": "3.23.5",
"code": "498",
"note": "Added:\n- Hide / Show Self boosts and self replies (Long press the Home tab)\n\nChanged:\n- Full screen size when writing media descriptions."
},
{
"version": "3.23.4",
"code": "497",
"note": "Fixed:\n\n- Holes in timelines due to a cache bug\n- Fix tags issue with RTL and the markdown format\n- Scrollable bio when editing profiles\n- Crash when adding a user into a list"
},
{
"version": "3.23.3",
"code": "496",
"note": "- Longer fields when editing bio\n- Clicks on card do not open Mastodon posts inside the app\n- Crash with Pixelfed\n- Line breaks with Markdown\n- Side effects with Markdown and some links"
},
{
"version": "3.23.2",
"code": "495",
"note": "Added:\n- Markdown support (can be disabled in Settings > Timelines)"
},
{
"version": "3.23.1",
"code": "494",
"note": "Added:\n- Scrollable media description\n\nFixed:\n- Crashes with profiles"
},
{
"version": "3.23.0",
"code": "493",
"note": "Added:\n- Add preview for app icons\n- Two new app icons\n\nFixed:\n- Fix Nitter feeds\n- Crash with Pixelfed accounts\n- Lingva encoding issue\n- Avoid sleep mode for media activity\n- Videos are played simultaneously\n- Voice messages for Android 10+\n- Punycode not supported for domains"
},
{
"version": "3.22.2",
"code": "492",
"note": "Added:\n- Follow Lemmy instances (from Manage Timelines)\n- View remote conversations (default: disabled - Settings > Interface)\n\nFixed:\n- Add 50 chars max for poll options\n- Too many requests\n- Blank Home page\n- Crashes when visiting profiles\n- Some audio files cannot be uploaded\n- Multiple notifications\n- Fix some other crashes"
},
{
"version": "3.22.1",
"code": "491",
"note": "Added:\n- Follow Lemmy instance (from Manage Timelines)\nFixed:\n- Add 50 chars max for poll options"
},
{
"version": "3.22.0",
"code": "490",
"note": "Fixed:\n- Too many requests\n- Blank Home page\n- Crashes when visiting profiles\n- Some audio files cannot be uploaded"
},
{
"version": "3.21.2",
"code": "489",
"note": "Added:\n- Android 12+ : Customize accent colors for light/dark theme and per account (Settings > Theming > Custom accent color)\n"
},
{
"version": "3.21.1",
"code": "488",
"note": "Added:\n- Filter messages in profiles (hide/show boosts or replies) via a long press on the tab\n\nChanged:\n- Some layout improvements for Peertube\n- Better management of resolution with Peertube\n- Improve instance picker for Peertube\n\nFixed:\n- URL in upper cases\n- Issues with Peertube player\n- False positive error when listening to audio\n- GIF does not honor nsfw\n- Polls having html\n- Fix crashes when scrolling timeline with animated gif"
},
{
"version": "3.21.0",
"code": "487",
"note": "Added:\n- Dedicated Peertube entry in main menu (My app)\n- Select instances (Instances picker with Filters)\n- Comment/Boost/Fav Peertube videos with Mastodon accounts\n\nFixed:\n- Fix a crash when searching and with the user directory"
},
{
"version": "3.20.3",
"code": "486",
"note": "Added:\n- Display all following/followers lists from remote profiles\n- Display all accounts that boosted/fav from a remote message\n\nFixed:\n- Fix a crash with auto-fetch messages"
},
{
"version": "3.20.2",
"code": "485",
"note": "Added:\n- Visual indicator when fetching missing messages\n- Open media description when it is missing from the warning dialog\n\nChanged:\n- Maths formula aligned to the left\n- Faster access to delete all notifications\n\nFixed:\n- Fix an issue with Nitter and some URLs\n- Fix refresh issue with notifications\n- Fix an issue when entering a Peertube instance\n- Fix jumps with Akkoma/Pleroma when media preview size is not set\n- Some crashes"
},
{
"version": "3.20.1",
"code": "484",
"note": "Added:\n- Add a button to fetch remote media when it fails\n- Add a settings to automatically fetch remote media when it fails (default: disabled)\n- Display on profiles & list of accounts if users have requested to follow you\n- Warn before boosting a message having no media descriptions (default: enabled)\n\nChanged:\n- Warn when there are missing descriptions enabled by default\n\nFixed:\n- Some settings not properly restored (multiple choices)\n- Cancel a follow request\n- Media with a lot of height in landscape\n- Some crashes"
},
{
"version": "3.20.0",
"code": "483",
"note": "Added:\n- \"Follows you\" indicator in accounts list\n- Settings compose: display a dialog to warn if there are missing media description (default: disabled)\n- Settings > Cache: disable battery optimization\n- Settings > Cache - Add charts to check cache logs\n- Settings > Timelines: AutoPlay gif media (default: enabled)\n- Google: Automatic backup of data and settings\n\nChanged:\n- Improve detections of gap in timelines\n- Improve media description\n- Chat view by default\n- Chat view add an indicator for messages when not direct\n\nFixed:\n- Fix an issue with cache and fetch more\n- Cache view with large fonts\n- Bad behaviors with truncated messages"
},
{
"version": "3.19.1",
"code": "482",
"note": "Added:\n- Settings compose: display a dialog to warn if there are missing media description (default disabled)\n- Settings > Notification: disable battery optimization\n- Settings > Timelines: AutoPlay gif media (default: enabled)\n\nFixed:\n- Fix an issue with cache and fetch more\n- Cache view with large fonts\n- Bad behaviors with truncated messages"
},
{
"version": "3.19.0",
"code": "481",
"note": "Added:\n- Settings compose: don't send media if there are no description (default: disabled)\n- Settings Timelines: Enable/Disable truncate links\n- Allow to set max link length (20 - 150 chars)\n\nChanged:\n- Align media with text (left margin enabled)\n\nFixed:\n- Media previews remain the same when sharing\n- Edit media description not working\n- Accessibility (larger fonts): profiles/DM\n- Cross replies: Wrong visibility with the selected account\n- Several crashes"
},
{
"version": "3.18.2",
"code": "480",
"note": "Changed:\n- First media layout will depend of its ratio\n\nFixed:\n- Impossible to add media with the chat view\n- Chat view limited in chars\n- Freezes / bad behaviors due to the new media presentation"
},
{
"version": "3.18.1",
"code": "479",
"note": "Added:\n- Add Lingva translator (Settings > Timelines)\n- Add support for Nyastodon-style emoji reactions (skyevg)\n- Add chat view for DM (default: disable / Settings > Timelines)"
},
{ {
"version": "3.18.0", "version": "3.18.0",
"code": "478", "code": "478",
@ -68,30 +293,6 @@
"version": "3.14.3", "version": "3.14.3",
"code": "465", "code": "465",
"note": "Added:\n- Display date of the message instead of the boost (default: disabled)\n- Allow to disable release notes popup in Settings\n\nFixed:\n- Fix timelines slow down and stuttering after some scrolls\n- Fix color issues with follow buttons\n- Fix import from settings (import from login was OK)" "note": "Added:\n- Display date of the message instead of the boost (default: disabled)\n- Allow to disable release notes popup in Settings\n\nFixed:\n- Fix timelines slow down and stuttering after some scrolls\n- Fix color issues with follow buttons\n- Fix import from settings (import from login was OK)"
},
{
"version": "3.14.2",
"code": "464",
"note": "Added:\n- Display familiar followers on profiles\n- Display and filter Instance directory\n\nChanged:\n- Bot and reply icon indicators more visible\n- Single media are hidden with link previews\n\nFixed:\n- Fix a crash with Art timelines\n- Friendica: media cannot be downloaded/shared\n- Fix a crash with pinned timelines"
},
{
"version": "3.14.1",
"code": "463",
"note": "Added:\n- Search bar: display suggestions when starting by \"@\" or \"#\"\n\nChanged:\n- Preload media in timelines to avoid jumps\n- Search: Automatically switch to account tab if no results for tags\n\nFixed:\n- Fix jumps with the fetch more feature\n- Fix videos cannot be saved\n- Tags cannot be pinned when there are no custom tabs\n- PixelFed view: NSFW not honored\n- Fix crashes"
},
{
"version": "3.14.0",
"code": "462",
"note": "Added:\n\n- Add Bubble timeline support in extra-features with filters\n- Allow to display public profiles by default to get all messages (Settings > Interface)\n- Glitch: Allow to post messages locally (Can be turned off in Settings)\n- Pixelfed: Custom layout to display Media fully (Also works for other software when there are media)\n- Allow to align left action buttons in messages\n\nChanged:\n- Full rework on links in messages (also mentions and tags)\n- Add pinned tag in \"any\" to avoid to lose it when renaming timeline\n\nFixed:\n- Links to messages not handled by the app\n- CW when editing a message\n- Fix push notifications with several accounts\n- New messages or edition notifications not pushed\n- Fix quotes with tags/mentions\n- Fix notifications\n- Fix sending multiple media\n- Fix crashes"
},
{
"version": "3.13.7",
"code": "461",
"note": "Added:\n- Pixelfed: Custom layout to display Media fully \n*(Settings > Timelines > Pixelfed Presentation) - Also works for other softwares when there are media\n\nChanged:\n- Add pinned tag in \"any\" to avoid to lose it when renaming timeline\n\nFixed:\n- Fix push notifications with several accounts\n- Fix quotes with tags/mentions\n- Fix notifications\n- Fix sending multiple media\n- Some crashes"
},
{
"version": "3.13.6",
"code": "460",
"note": "Fixed:\n- Cross-compose: Wrong instance emojis\n- Custom emojis not displayed in notifications\n- Fav/Boost markers with shared messages\n- Empty notifications\n- Fix cw removed when replying\n- Fix expand media with fit preview images when sensitive\n- Fix an issue with fetch more displayed too often (cache clear will help or wait new messages)"
} }
] ]

File diff suppressed because it is too large Load diff

View file

@ -42,7 +42,9 @@ import java.util.Objects;
import app.fedilab.android.mastodon.helper.ThemeHelper; import app.fedilab.android.mastodon.helper.ThemeHelper;
import app.fedilab.android.peertube.services.GlobalUploadObserver; import app.fedilab.android.peertube.services.GlobalUploadObserver;
import es.dmoral.toasty.Toasty; import es.dmoral.toasty.Toasty;
import io.noties.prism4j.annotations.PrismBundle;
@PrismBundle(includeAll = true, grammarLocatorClassName = ".MySuperGrammerLocator")
public class MainApplication extends MultiDexApplication { public class MainApplication extends MultiDexApplication {

View file

@ -15,14 +15,17 @@ package app.fedilab.android.activities;
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Bundle; import android.os.Bundle;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.Toast;
import androidx.core.app.ActivityOptionsCompat;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import java.util.ArrayList; import java.util.ArrayList;
@ -36,10 +39,12 @@ import app.fedilab.android.mastodon.activities.BaseBarActivity;
import app.fedilab.android.mastodon.activities.ProfileActivity; import app.fedilab.android.mastodon.activities.ProfileActivity;
import app.fedilab.android.mastodon.client.entities.api.Account; import app.fedilab.android.mastodon.client.entities.api.Account;
import app.fedilab.android.mastodon.client.entities.api.Status; import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.helper.CrossActionHelper; import app.fedilab.android.mastodon.helper.CrossActionHelper;
import app.fedilab.android.mastodon.helper.Helper; import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.MastodonHelper; import app.fedilab.android.mastodon.helper.MastodonHelper;
import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM; import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM;
import es.dmoral.toasty.Toasty;
public class AboutActivity extends BaseBarActivity { public class AboutActivity extends BaseBarActivity {
@ -58,10 +63,10 @@ public class AboutActivity extends BaseBarActivity {
if (getSupportActionBar() != null) { if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} }
String version = "";
try { try {
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0); PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
String version = pInfo.versionName; version = pInfo.versionName;
binding.aboutVersion.setText(getResources().getString(R.string.about_vesrion, version)); binding.aboutVersion.setText(getResources().getString(R.string.about_vesrion, version));
} catch (PackageManager.NameNotFoundException ignored) { } catch (PackageManager.NameNotFoundException ignored) {
} }
@ -77,6 +82,20 @@ public class AboutActivity extends BaseBarActivity {
} }
binding.aboutSupportPaypal.setOnClickListener(v -> Helper.openBrowser(AboutActivity.this, "https://www.paypal.me/Mastalab")); binding.aboutSupportPaypal.setOnClickListener(v -> Helper.openBrowser(AboutActivity.this, "https://www.paypal.me/Mastalab"));
String finalVersion = version;
binding.aboutVersionCopy.setOnClickListener(v -> {
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
String content = "Fedilab v" + finalVersion + " for " + (BuildConfig.DONATIONS ? "FDroid" : "Google");
ClipData clip = ClipData.newPlainText(Helper.CLIP_BOARD, content);
if (clipboard != null) {
clipboard.setPrimaryClip(clip);
Toasty.info(AboutActivity.this, getString(R.string.clipboard_version), Toast.LENGTH_LONG).show();
}
});
if (BuildConfig.DONATIONS) { if (BuildConfig.DONATIONS) {
binding.aboutSupportPaypal.setVisibility(View.VISIBLE); binding.aboutSupportPaypal.setVisibility(View.VISIBLE);
} else { } else {
@ -100,12 +119,14 @@ public class AboutActivity extends BaseBarActivity {
binding.accountUn.setText(account.acct); binding.accountUn.setText(account.acct);
binding.accountPp.setOnClickListener(v -> { binding.accountPp.setOnClickListener(v -> {
Intent intent = new Intent(AboutActivity.this, ProfileActivity.class); Intent intent = new Intent(AboutActivity.this, ProfileActivity.class);
Bundle b = new Bundle(); Bundle args = new Bundle();
b.putSerializable(Helper.ARG_ACCOUNT, account); args.putSerializable(Helper.ARG_ACCOUNT, account);
intent.putExtras(b); new CachedBundle(AboutActivity.this).insertBundle(args, Helper.getCurrentAccount(AboutActivity.this), bundleId -> {
ActivityOptionsCompat options = ActivityOptionsCompat Bundle bundle = new Bundle();
.makeSceneTransitionAnimation(AboutActivity.this, binding.accountPp, getString(R.string.activity_porfile_pp)); bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
startActivity(intent, options.toBundle()); intent.putExtras(bundle);
startActivity(intent);
});
}); });
AccountsVM accountsVM = new ViewModelProvider(AboutActivity.this).get(AccountsVM.class); AccountsVM accountsVM = new ViewModelProvider(AboutActivity.this).get(AccountsVM.class);
List<String> ids = new ArrayList<>(); List<String> ids = new ArrayList<>();

View file

@ -150,7 +150,6 @@ public class LoginActivity extends BaseActivity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(LoginActivity.this);
setContentView(new FrameLayout(this)); setContentView(new FrameLayout(this));
FragmentLoginMain fragmentLoginMain = new FragmentLoginMain(); FragmentLoginMain fragmentLoginMain = new FragmentLoginMain();
Helper.addFragment(getSupportFragmentManager(), android.R.id.content, fragmentLoginMain, null, null, null); Helper.addFragment(getSupportFragmentManager(), android.R.id.content, fragmentLoginMain, null, null, null);

View file

@ -0,0 +1,102 @@
package app.fedilab.android.activities;
/* Copyright 2025 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import app.fedilab.android.mastodon.activities.ProfileActivity;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.helper.Helper;
public class WebActivityPub extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent appIntent = getIntent();
String acct = null;
String intent = null;
if(appIntent == null) {
finish();
return;
}
Uri uri = appIntent.getData();
if(uri == null) {
finish();
return;
}
String scheme = uri.getScheme();
String uriString = uri.toString();
if(!uriString.startsWith(scheme+"://")) {
uriString = uri.toString().replace(scheme+":",scheme+"://");
uri = Uri.parse(uriString);
if(uri == null) {
finish();
return;
}
}
String host = uri.getHost();
String path = uri.getPath();
String query = uri.getQuery();
if(path == null || path.isEmpty()) {
finish();
return;
}
if(query != null) {
String intentPatternString = "intent=(\\w+)";
final Pattern intentPattern = Pattern.compile(intentPatternString, Pattern.CASE_INSENSITIVE);
Matcher matcherIntent = intentPattern.matcher(query);
while (matcherIntent.find()) {
intent = matcherIntent.group(1);
}
}
if(path.startsWith("/@")) {
String[] params = path.split("@");
if(params.length == 2) {
acct = params[1] + "@" + host;
}
} else if(path.split("/").length > 2) {
String[] params = path.split("/");
String root = params[1].toLowerCase();
if (root.equals("users")) {
acct = params[2] + "@" + host;
}
}
if(acct != null) {
Intent intentProfile = new Intent(WebActivityPub.this, ProfileActivity.class);
Bundle args = new Bundle();
args.putString(Helper.ARG_MENTION, acct);
new CachedBundle(WebActivityPub.this).insertBundle(args, Helper.getCurrentAccount(WebActivityPub.this), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentProfile.putExtras(bundle);
intentProfile.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intentProfile);
});
}
finish();
}
}

View file

@ -27,6 +27,7 @@ import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity; import app.fedilab.android.activities.MainActivity;
@ -34,6 +35,7 @@ import app.fedilab.android.databinding.ActivityAdminReportBinding;
import app.fedilab.android.mastodon.client.entities.api.Status; import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.api.admin.AdminAccount; import app.fedilab.android.mastodon.client.entities.api.admin.AdminAccount;
import app.fedilab.android.mastodon.client.entities.api.admin.AdminReport; import app.fedilab.android.mastodon.client.entities.api.admin.AdminReport;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.helper.Helper; import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.ThemeHelper; import app.fedilab.android.mastodon.helper.ThemeHelper;
import app.fedilab.android.mastodon.ui.drawer.StatusReportAdapter; import app.fedilab.android.mastodon.ui.drawer.StatusReportAdapter;
@ -47,6 +49,7 @@ public class AccountReportActivity extends BaseBarActivity {
private AdminReport report; private AdminReport report;
private ActivityAdminReportBinding binding; private ActivityAdminReportBinding binding;
private AdminVM adminVM; private AdminVM adminVM;
private AdminAccount targeted_account;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -60,12 +63,22 @@ public class AccountReportActivity extends BaseBarActivity {
} }
report = null; report = null;
AdminAccount targeted_account = null; targeted_account = null;
Bundle b = getIntent().getExtras(); Bundle args = getIntent().getExtras();
if (b != null) { if (args != null) {
account_id = b.getString(Helper.ARG_ACCOUNT_ID, null); long bundleId = args.getLong(Helper.ARG_INTENT_ID, -1);
targeted_account = (AdminAccount) b.getSerializable(Helper.ARG_ACCOUNT); new CachedBundle(AccountReportActivity.this).getBundle(bundleId, Helper.getCurrentAccount(AccountReportActivity.this), this::initializeAfterBundle);
report = (AdminReport) b.getSerializable(Helper.ARG_REPORT); } else {
initializeAfterBundle(null);
}
}
private void initializeAfterBundle(Bundle bundle) {
if (bundle != null) {
account_id = bundle.getString(Helper.ARG_ACCOUNT_ID, null);
targeted_account = (AdminAccount) bundle.getSerializable(Helper.ARG_ACCOUNT);
report = (AdminReport) bundle.getSerializable(Helper.ARG_REPORT);
} }
binding.allow.getBackground().setColorFilter(ThemeHelper.getAttColor(this, R.attr.colorPrimary), PorterDuff.Mode.MULTIPLY); binding.allow.getBackground().setColorFilter(ThemeHelper.getAttColor(this, R.attr.colorPrimary), PorterDuff.Mode.MULTIPLY);
@ -102,7 +115,6 @@ public class AccountReportActivity extends BaseBarActivity {
account_id = targeted_account.username; account_id = targeted_account.username;
} }
} }
private void fillReport(AdminAccount accountAdmin, actionType type) { private void fillReport(AdminAccount accountAdmin, actionType type) {
@ -134,12 +146,12 @@ public class AccountReportActivity extends BaseBarActivity {
binding.reject.setOnClickListener(view -> adminVM.reject(MainActivity.currentInstance, MainActivity.currentToken, account_id).observe(this, account -> fillReport(account, actionType.REJECT))); binding.reject.setOnClickListener(view -> adminVM.reject(MainActivity.currentInstance, MainActivity.currentToken, account_id).observe(this, account -> fillReport(account, actionType.REJECT)));
binding.allow.setOnClickListener(view -> adminVM.approve(MainActivity.currentInstance, MainActivity.currentToken, account_id).observe(this, account -> fillReport(account, actionType.APPROVE))); binding.allow.setOnClickListener(view -> adminVM.approve(MainActivity.currentInstance, MainActivity.currentToken, account_id).observe(this, account -> fillReport(account, actionType.APPROVE)));
binding.warn.setOnClickListener(view -> { binding.warn.setOnClickListener(view -> {
adminVM.performAction(MainActivity.currentInstance, MainActivity.currentToken, account_id, "none", null, null, binding.comment.getText().toString().trim(), binding.emailUser.isChecked()); adminVM.performAction(MainActivity.currentInstance, MainActivity.currentToken, account_id, "none", null, null, Objects.requireNonNull(binding.comment.getText()).toString().trim(), binding.emailUser.isChecked());
fillReport(accountAdmin, actionType.NONE); fillReport(accountAdmin, actionType.NONE);
}); });
binding.silence.setOnClickListener(view -> { binding.silence.setOnClickListener(view -> {
if (!accountAdmin.silenced) { if (!accountAdmin.silenced) {
adminVM.performAction(MainActivity.currentInstance, MainActivity.currentToken, account_id, "silence", null, null, binding.comment.getText().toString().trim(), binding.emailUser.isChecked()); adminVM.performAction(MainActivity.currentInstance, MainActivity.currentToken, account_id, "silence", null, null, Objects.requireNonNull(binding.comment.getText()).toString().trim(), binding.emailUser.isChecked());
accountAdmin.silenced = true; accountAdmin.silenced = true;
fillReport(accountAdmin, actionType.SILENCE); fillReport(accountAdmin, actionType.SILENCE);
} else { } else {
@ -148,7 +160,7 @@ public class AccountReportActivity extends BaseBarActivity {
}); });
binding.disable.setOnClickListener(view -> { binding.disable.setOnClickListener(view -> {
if (!accountAdmin.disabled) { if (!accountAdmin.disabled) {
adminVM.performAction(MainActivity.currentInstance, MainActivity.currentToken, account_id, "disable", null, null, binding.comment.getText().toString().trim(), binding.emailUser.isChecked()); adminVM.performAction(MainActivity.currentInstance, MainActivity.currentToken, account_id, "disable", null, null, Objects.requireNonNull(binding.comment.getText()).toString().trim(), binding.emailUser.isChecked());
accountAdmin.disabled = true; accountAdmin.disabled = true;
fillReport(accountAdmin, actionType.DISABLE); fillReport(accountAdmin, actionType.DISABLE);
} else { } else {
@ -158,7 +170,7 @@ public class AccountReportActivity extends BaseBarActivity {
binding.suspend.setOnClickListener(view -> { binding.suspend.setOnClickListener(view -> {
if (!accountAdmin.suspended) { if (!accountAdmin.suspended) {
adminVM.performAction(MainActivity.currentInstance, MainActivity.currentToken, account_id, "suspend", null, null, binding.comment.getText().toString().trim(), binding.emailUser.isChecked()); adminVM.performAction(MainActivity.currentInstance, MainActivity.currentToken, account_id, "suspend", null, null, Objects.requireNonNull(binding.comment.getText()).toString().trim(), binding.emailUser.isChecked());
accountAdmin.suspended = true; accountAdmin.suspended = true;
fillReport(accountAdmin, actionType.SUSPEND); fillReport(accountAdmin, actionType.SUSPEND);
} else { } else {
@ -170,35 +182,21 @@ public class AccountReportActivity extends BaseBarActivity {
if (type != null) { if (type != null) {
String message = null; String message = null;
switch (type) { switch (type) {
case SILENCE: case SILENCE -> message = getString(R.string.account_silenced);
message = getString(R.string.account_silenced); case UNSILENCE -> message = getString(R.string.account_unsilenced);
break; case DISABLE -> message = getString(R.string.account_disabled);
case UNSILENCE: case ENABLE -> message = getString(R.string.account_undisabled);
message = getString(R.string.account_unsilenced); case SUSPEND -> message = getString(R.string.account_suspended);
break; case UNSUSPEND -> message = getString(R.string.account_unsuspended);
case DISABLE: case NONE -> message = getString(R.string.account_warned);
message = getString(R.string.account_disabled); case APPROVE -> {
break;
case ENABLE:
message = getString(R.string.account_undisabled);
break;
case SUSPEND:
message = getString(R.string.account_suspended);
break;
case UNSUSPEND:
message = getString(R.string.account_unsuspended);
break;
case NONE:
message = getString(R.string.account_warned);
break;
case APPROVE:
binding.allowRejectGroup.setVisibility(View.GONE); binding.allowRejectGroup.setVisibility(View.GONE);
message = getString(R.string.account_approved); message = getString(R.string.account_approved);
break; }
case REJECT: case REJECT -> {
binding.allowRejectGroup.setVisibility(View.GONE); binding.allowRejectGroup.setVisibility(View.GONE);
message = getString(R.string.account_rejected); message = getString(R.string.account_rejected);
break; }
} }
if (message != null) { if (message != null) {
Toasty.success(AccountReportActivity.this, message, Toast.LENGTH_LONG).show(); Toasty.success(AccountReportActivity.this, message, Toast.LENGTH_LONG).show();

View file

@ -60,64 +60,53 @@ public class ActionActivity extends BaseBarActivity {
private void displayTimeline(Timeline.TimeLineEnum type) { private void displayTimeline(Timeline.TimeLineEnum type) {
canGoBack = true; canGoBack = true;
if (type == Timeline.TimeLineEnum.MUTED_TIMELINE || type == Timeline.TimeLineEnum.BLOCKED_TIMELINE || type == Timeline.TimeLineEnum.MUTED_TIMELINE_HOME) { if (type == Timeline.TimeLineEnum.MUTED_TIMELINE || type == Timeline.TimeLineEnum.BLOCKED_TIMELINE || type == Timeline.TimeLineEnum.MUTED_TIMELINE_HOME) {
fragmentMastodonAccount = new FragmentMastodonAccount();
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, type);
bundle.putString(Helper.ARG_VIEW_MODEL_KEY, "FEDILAB_" + type.getValue());
fragmentMastodonAccount.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragmentMastodonAccount);
fragmentTransaction.commit();
ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> { ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> {
fragmentMastodonAccount = new FragmentMastodonAccount();
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, type);
bundle.putString(Helper.ARG_VIEW_MODEL_KEY, "FEDILAB_" + type.getValue());
fragmentMastodonAccount.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragmentMastodonAccount);
fragmentTransaction.commit();
}); });
} else if (type == Timeline.TimeLineEnum.BLOCKED_DOMAIN_TIMELINE) { } else if (type == Timeline.TimeLineEnum.BLOCKED_DOMAIN_TIMELINE) {
fragmentMastodonDomainBlock = new FragmentMastodonDomainBlock();
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragmentMastodonDomainBlock);
fragmentTransaction.commit();
ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> { ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> {
fragmentMastodonDomainBlock = new FragmentMastodonDomainBlock();
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragmentMastodonDomainBlock);
fragmentTransaction.commit();
}); });
} else { } else {
fragmentMastodonTimeline = new FragmentMastodonTimeline();
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, type);
bundle.putString(Helper.ARG_VIEW_MODEL_KEY, "FEDILAB_" + type.getValue());
fragmentMastodonTimeline.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragmentMastodonTimeline);
fragmentTransaction.commit();
ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> { ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> {
fragmentMastodonTimeline = new FragmentMastodonTimeline();
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, type);
bundle.putString(Helper.ARG_VIEW_MODEL_KEY, "FEDILAB_" + type.getValue());
fragmentMastodonTimeline.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragmentMastodonTimeline);
fragmentTransaction.commit();
}); });
} }
switch (type) { switch (type) {
case MUTED_TIMELINE: case MUTED_TIMELINE -> setTitle(R.string.muted_menu);
setTitle(R.string.muted_menu); case FAVOURITE_TIMELINE -> setTitle(R.string.favourite);
break; case BLOCKED_TIMELINE -> setTitle(R.string.blocked_menu);
case FAVOURITE_TIMELINE: case BOOKMARK_TIMELINE -> setTitle(R.string.bookmarks);
setTitle(R.string.favourite); case BLOCKED_DOMAIN_TIMELINE -> setTitle(R.string.blocked_domains);
break; case MUTED_TIMELINE_HOME -> setTitle(R.string.muted_menu_home);
case BLOCKED_TIMELINE:
setTitle(R.string.blocked_menu);
break;
case BOOKMARK_TIMELINE:
setTitle(R.string.bookmarks);
break;
case BLOCKED_DOMAIN_TIMELINE:
setTitle(R.string.blocked_domains);
break;
case MUTED_TIMELINE_HOME:
setTitle(R.string.muted_menu_home);
break;
} }
} }

View file

@ -15,6 +15,10 @@ package app.fedilab.android.mastodon.activities;
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentInstance;
import static app.fedilab.android.BaseMainActivity.currentNightMode;
import static app.fedilab.android.BaseMainActivity.currentUserID;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -22,15 +26,16 @@ import android.content.res.Configuration;
import android.graphics.Color; import android.graphics.Color;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import android.view.Window; import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.app.AppCompatDelegate;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import com.google.android.material.color.DynamicColors;
import com.vanniktech.emoji.EmojiManager; import com.vanniktech.emoji.EmojiManager;
import com.vanniktech.emoji.one.EmojiOneProvider; import com.vanniktech.emoji.one.EmojiOneProvider;
@ -52,6 +57,7 @@ public class BaseActivity extends AppCompatActivity {
EmojiManager.install(new EmojiOneProvider()); EmojiManager.install(new EmojiOneProvider());
} }
@SuppressLint("RestrictedApi")
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
@ -69,85 +75,94 @@ public class BaseActivity extends AppCompatActivity {
} }
String currentTheme = sharedpreferences.getString(getString(R.string.SET_THEME_BASE), getString(R.string.SET_DEFAULT_THEME)); String currentTheme = sharedpreferences.getString(getString(R.string.SET_THEME_BASE), getString(R.string.SET_DEFAULT_THEME));
//Default automatic switch boolean customAccentEnabled = sharedpreferences.getBoolean(getString(R.string.SET_CUSTOM_ACCENT) + currentUserID + currentInstance, false);
if (currentTheme.equals(getString(R.string.SET_DEFAULT_THEME))) {
int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; //Default automatic switch
currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
if(customAccentEnabled && currentNightMode == Configuration.UI_MODE_NIGHT_NO) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR|View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
}else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
}
if (currentTheme.equals(getString(R.string.SET_DEFAULT_THEME))) {
switch (currentNightMode) { switch (currentNightMode) {
case Configuration.UI_MODE_NIGHT_NO: case Configuration.UI_MODE_NIGHT_NO -> {
String defaultLight = sharedpreferences.getString(getString(R.string.SET_THEME_DEFAULT_LIGHT), "LIGHT"); String defaultLight = sharedpreferences.getString(getString(R.string.SET_THEME_DEFAULT_LIGHT), "LIGHT");
switch (defaultLight) { switch (defaultLight) {
case "LIGHT": case "LIGHT" -> {
setTheme(R.style.AppTheme); setTheme(R.style.AppTheme);
currentThemeId = R.style.AppTheme; currentThemeId = R.style.AppTheme;
break; }
case "SOLARIZED_LIGHT": case "SOLARIZED_LIGHT" -> {
setTheme(R.style.SolarizedAppTheme); setTheme(R.style.SolarizedAppTheme);
currentThemeId = R.style.SolarizedAppTheme; currentThemeId = R.style.SolarizedAppTheme;
break; }
} }
break; }
case Configuration.UI_MODE_NIGHT_YES: case Configuration.UI_MODE_NIGHT_YES -> {
String defaultDark = sharedpreferences.getString(getString(R.string.SET_THEME_DEFAULT_DARK), "DARK"); String defaultDark = sharedpreferences.getString(getString(R.string.SET_THEME_DEFAULT_DARK), "DARK");
switch (defaultDark) { switch (defaultDark) {
case "DARK": case "DARK" -> {
setTheme(R.style.AppTheme); setTheme(R.style.AppTheme);
currentThemeId = R.style.AppTheme; currentThemeId = R.style.AppTheme;
break; }
case "SOLARIZED_DARK": case "SOLARIZED_DARK" -> {
setTheme(R.style.SolarizedAppTheme); setTheme(R.style.SolarizedAppTheme);
currentThemeId = R.style.SolarizedAppTheme; currentThemeId = R.style.SolarizedAppTheme;
break; }
case "BLACK": case "BLACK" -> {
setTheme(R.style.BlackAppTheme); setTheme(R.style.BlackAppTheme);
currentThemeId = R.style.BlackAppTheme; currentThemeId = R.style.BlackAppTheme;
break; }
case "DRACULA": case "DRACULA" -> {
setTheme(R.style.DraculaAppTheme); setTheme(R.style.DraculaAppTheme);
currentThemeId = R.style.DraculaAppTheme; currentThemeId = R.style.DraculaAppTheme;
break; }
} }
break; }
} }
} else { } else {
switch (currentTheme) { switch (currentTheme) {
case "LIGHT": case "LIGHT" -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
setTheme(R.style.AppTheme); setTheme(R.style.AppTheme);
currentThemeId = R.style.AppTheme; currentThemeId = R.style.AppTheme;
break; }
case "DARK": case "DARK" -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
setTheme(R.style.AppTheme); setTheme(R.style.AppTheme);
currentThemeId = R.style.AppTheme; currentThemeId = R.style.AppTheme;
break; }
case "SOLARIZED_LIGHT": case "SOLARIZED_LIGHT" -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
setTheme(R.style.SolarizedAppTheme); setTheme(R.style.SolarizedAppTheme);
currentThemeId = R.style.SolarizedAppTheme; currentThemeId = R.style.SolarizedAppTheme;
break; }
case "SOLARIZED_DARK": case "SOLARIZED_DARK" -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
setTheme(R.style.SolarizedAppTheme); setTheme(R.style.SolarizedAppTheme);
currentThemeId = R.style.SolarizedAppTheme; currentThemeId = R.style.SolarizedAppTheme;
break; }
case "BLACK": case "BLACK" -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
setTheme(R.style.BlackAppTheme); setTheme(R.style.BlackAppTheme);
currentThemeId = R.style.BlackAppTheme; currentThemeId = R.style.BlackAppTheme;
break; }
case "DRACULA": case "DRACULA" -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
setTheme(R.style.DraculaAppTheme); setTheme(R.style.DraculaAppTheme);
currentThemeId = R.style.DraculaAppTheme; currentThemeId = R.style.DraculaAppTheme;
break; }
} }
} }
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
boolean dynamicColor = sharedpreferences.getBoolean(getString(R.string.SET_DYNAMICCOLOR), false);
if (dynamicColor) { ThemeHelper.applyThemeColor(this);
DynamicColors.applyToActivityIfAvailable(this);
}
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
ThemeHelper.adjustFontScale(this, getResources().getConfiguration()); ThemeHelper.adjustFontScale(this, getResources().getConfiguration());
} }
@ -163,11 +178,18 @@ public class BaseActivity extends AppCompatActivity {
@Override @Override
protected void attachBaseContext(Context newBase) { protected void attachBaseContext(Context newBase) {
if (android.os.Build.VERSION.SDK_INT > Build.VERSION_CODES.N) { if (android.os.Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
final Configuration override = new Configuration(newBase.getResources().getConfiguration()); final Configuration override = new Configuration();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(newBase); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(newBase);
override.fontScale = prefs.getFloat(newBase.getString(R.string.SET_FONT_SCALE), 1.1f); override.fontScale = prefs.getFloat(newBase.getString(R.string.SET_FONT_SCALE), 1.1f);
applyOverrideConfiguration(override); applyOverrideConfiguration(override);
} }
super.attachBaseContext(newBase); super.attachBaseContext(newBase);
} }
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.clear();
}
} }

View file

@ -15,6 +15,10 @@ package app.fedilab.android.mastodon.activities;
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentInstance;
import static app.fedilab.android.BaseMainActivity.currentNightMode;
import static app.fedilab.android.BaseMainActivity.currentUserID;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -22,15 +26,16 @@ import android.content.res.Configuration;
import android.graphics.Color; import android.graphics.Color;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import android.view.Window; import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.app.AppCompatDelegate;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import com.google.android.material.color.DynamicColors;
import com.vanniktech.emoji.EmojiManager; import com.vanniktech.emoji.EmojiManager;
import com.vanniktech.emoji.one.EmojiOneProvider; import com.vanniktech.emoji.one.EmojiOneProvider;
@ -65,74 +70,67 @@ public class BaseBarActivity extends AppCompatActivity {
} }
} }
String currentTheme = sharedpreferences.getString(getString(R.string.SET_THEME_BASE), getString(R.string.SET_DEFAULT_THEME)); String currentTheme = sharedpreferences.getString(getString(R.string.SET_THEME_BASE), getString(R.string.SET_DEFAULT_THEME));
boolean customAccentEnabled = sharedpreferences.getBoolean(getString(R.string.SET_CUSTOM_ACCENT) + currentUserID + currentInstance, false);
//Default automatic switch //Default automatic switch
currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
if(customAccentEnabled && currentNightMode == Configuration.UI_MODE_NIGHT_NO) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR|View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
}else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
}
if (currentTheme.equals(getString(R.string.SET_DEFAULT_THEME))) { if (currentTheme.equals(getString(R.string.SET_DEFAULT_THEME))) {
int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
switch (currentNightMode) { switch (currentNightMode) {
case Configuration.UI_MODE_NIGHT_NO: case Configuration.UI_MODE_NIGHT_NO -> {
String defaultLight = sharedpreferences.getString(getString(R.string.SET_THEME_DEFAULT_LIGHT), "LIGHT"); String defaultLight = sharedpreferences.getString(getString(R.string.SET_THEME_DEFAULT_LIGHT), "LIGHT");
switch (defaultLight) { switch (defaultLight) {
case "LIGHT": case "LIGHT" -> setTheme(R.style.AppThemeBar);
setTheme(R.style.AppThemeBar); case "SOLARIZED_LIGHT" -> setTheme(R.style.SolarizedAppThemeBar);
break;
case "SOLARIZED_LIGHT":
setTheme(R.style.SolarizedAppThemeBar);
break;
} }
break; }
case Configuration.UI_MODE_NIGHT_YES: case Configuration.UI_MODE_NIGHT_YES -> {
String defaultDark = sharedpreferences.getString(getString(R.string.SET_THEME_DEFAULT_DARK), "DARK"); String defaultDark = sharedpreferences.getString(getString(R.string.SET_THEME_DEFAULT_DARK), "DARK");
switch (defaultDark) { switch (defaultDark) {
case "DARK": case "DARK" -> setTheme(R.style.AppThemeBar);
setTheme(R.style.AppThemeBar); case "SOLARIZED_DARK" -> setTheme(R.style.SolarizedAppThemeBar);
break; case "BLACK" -> setTheme(R.style.BlackAppThemeBar);
case "SOLARIZED_DARK": case "DRACULA" -> setTheme(R.style.DraculaAppThemeBar);
setTheme(R.style.SolarizedAppThemeBar);
break;
case "BLACK":
setTheme(R.style.BlackAppThemeBar);
break;
case "DRACULA":
setTheme(R.style.DraculaAppThemeBar);
break;
} }
break; }
} }
} else { } else {
switch (currentTheme) { switch (currentTheme) {
case "LIGHT": case "LIGHT" -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
setTheme(R.style.AppThemeBar); setTheme(R.style.AppThemeBar);
break; }
case "DARK": case "DARK" -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
setTheme(R.style.AppThemeBar); setTheme(R.style.AppThemeBar);
break; }
case "SOLARIZED_LIGHT": case "SOLARIZED_LIGHT" -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
setTheme(R.style.SolarizedAppThemeBar); setTheme(R.style.SolarizedAppThemeBar);
break; }
case "SOLARIZED_DARK": case "SOLARIZED_DARK" -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
setTheme(R.style.SolarizedAppThemeBar); setTheme(R.style.SolarizedAppThemeBar);
break; }
case "BLACK": case "BLACK" -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
setTheme(R.style.BlackAppThemeBar); setTheme(R.style.BlackAppThemeBar);
break; }
case "DRACULA": case "DRACULA" -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
setTheme(R.style.DraculaAppThemeBar); setTheme(R.style.DraculaAppThemeBar);
break; }
} }
} }
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
boolean dynamicColor = sharedpreferences.getBoolean(getString(R.string.SET_DYNAMICCOLOR), false); ThemeHelper.applyThemeColor(this);
if (dynamicColor) {
DynamicColors.applyToActivityIfAvailable(this);
}
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
Window window = getWindow(); Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
@ -149,7 +147,7 @@ public class BaseBarActivity extends AppCompatActivity {
protected void attachBaseContext(Context newBase) { protected void attachBaseContext(Context newBase) {
if (android.os.Build.VERSION.SDK_INT > Build.VERSION_CODES.N) { if (android.os.Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
final Configuration override = new Configuration(newBase.getResources().getConfiguration()); final Configuration override = new Configuration();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(newBase); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(newBase);
override.fontScale = prefs.getFloat(newBase.getString(R.string.SET_FONT_SCALE), 1.1f); override.fontScale = prefs.getFloat(newBase.getString(R.string.SET_FONT_SCALE), 1.1f);
applyOverrideConfiguration(override); applyOverrideConfiguration(override);
@ -158,4 +156,11 @@ public class BaseBarActivity extends AppCompatActivity {
super.attachBaseContext(newBase); super.attachBaseContext(newBase);
} }
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.clear();
}
} }

View file

@ -15,6 +15,8 @@ package app.fedilab.android.mastodon.activities;
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentNightMode;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -30,7 +32,6 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.app.AppCompatDelegate;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import com.google.android.material.color.DynamicColors;
import com.vanniktech.emoji.EmojiManager; import com.vanniktech.emoji.EmojiManager;
import com.vanniktech.emoji.one.EmojiOneProvider; import com.vanniktech.emoji.one.EmojiOneProvider;
@ -66,73 +67,58 @@ public class BaseTransparentActivity extends AppCompatActivity {
} }
String currentTheme = sharedpreferences.getString(getString(R.string.SET_THEME_BASE), getString(R.string.SET_DEFAULT_THEME)); String currentTheme = sharedpreferences.getString(getString(R.string.SET_THEME_BASE), getString(R.string.SET_DEFAULT_THEME));
//Default automatic switch //Default automatic switch
currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
if (currentTheme.equals(getString(R.string.SET_DEFAULT_THEME))) { if (currentTheme.equals(getString(R.string.SET_DEFAULT_THEME))) {
int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
switch (currentNightMode) { switch (currentNightMode) {
case Configuration.UI_MODE_NIGHT_NO: case Configuration.UI_MODE_NIGHT_NO -> {
String defaultLight = sharedpreferences.getString(getString(R.string.SET_THEME_DEFAULT_LIGHT), "LIGHT"); String defaultLight = sharedpreferences.getString(getString(R.string.SET_THEME_DEFAULT_LIGHT), "LIGHT");
switch (defaultLight) { switch (defaultLight) {
case "LIGHT": case "LIGHT" -> setTheme(R.style.Transparent);
setTheme(R.style.Transparent); case "SOLARIZED_LIGHT" -> setTheme(R.style.TransparentSolarized);
break;
case "SOLARIZED_LIGHT":
setTheme(R.style.TransparentSolarized);
break;
} }
break; }
case Configuration.UI_MODE_NIGHT_YES: case Configuration.UI_MODE_NIGHT_YES -> {
String defaultDark = sharedpreferences.getString(getString(R.string.SET_THEME_DEFAULT_DARK), "DARK"); String defaultDark = sharedpreferences.getString(getString(R.string.SET_THEME_DEFAULT_DARK), "DARK");
switch (defaultDark) { switch (defaultDark) {
case "DARK": case "DARK" -> setTheme(R.style.Transparent);
setTheme(R.style.Transparent); case "SOLARIZED_DARK" -> setTheme(R.style.TransparentSolarized);
break; case "BLACK" -> setTheme(R.style.TransparentBlack);
case "SOLARIZED_DARK": case "DRACULA" -> setTheme(R.style.TransparentDracula);
setTheme(R.style.TransparentSolarized);
break;
case "BLACK":
setTheme(R.style.TransparentBlack);
break;
case "DRACULA":
setTheme(R.style.TransparentDracula);
break;
} }
break; }
} }
} else { } else {
switch (currentTheme) { switch (currentTheme) {
case "LIGHT": case "LIGHT" -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
setTheme(R.style.Transparent); setTheme(R.style.Transparent);
break; }
case "DARK": case "DARK" -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
setTheme(R.style.Transparent); setTheme(R.style.Transparent);
break; }
case "SOLARIZED_LIGHT": case "SOLARIZED_LIGHT" -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
setTheme(R.style.TransparentSolarized); setTheme(R.style.TransparentSolarized);
break; }
case "SOLARIZED_DARK": case "SOLARIZED_DARK" -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
setTheme(R.style.TransparentSolarized); setTheme(R.style.TransparentSolarized);
break; }
case "BLACK": case "BLACK" -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
setTheme(R.style.TransparentBlack); setTheme(R.style.TransparentBlack);
break; }
case "DRACULA": case "DRACULA" -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
setTheme(R.style.TransparentDracula); setTheme(R.style.TransparentDracula);
break; }
} }
} }
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
boolean dynamicColor = sharedpreferences.getBoolean(getString(R.string.SET_DYNAMICCOLOR), false); ThemeHelper.applyThemeColor(this);
if (dynamicColor) {
DynamicColors.applyToActivityIfAvailable(this);
}
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
Window window = getWindow(); Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
@ -149,7 +135,7 @@ public class BaseTransparentActivity extends AppCompatActivity {
protected void attachBaseContext(Context newBase) { protected void attachBaseContext(Context newBase) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
final Configuration override = new Configuration(newBase.getResources().getConfiguration()); final Configuration override = new Configuration();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(newBase); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(newBase);
override.fontScale = prefs.getFloat(newBase.getString(R.string.SET_FONT_SCALE), 1.1f); override.fontScale = prefs.getFloat(newBase.getString(R.string.SET_FONT_SCALE), 1.1f);
applyOverrideConfiguration(override); applyOverrideConfiguration(override);

View file

@ -0,0 +1,518 @@
package app.fedilab.android.mastodon.activities;
/* Copyright 2023 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.mastodon.helper.Helper.dateDiffFull;
import static app.fedilab.android.mastodon.helper.Helper.dateDiffFullShort;
import static app.fedilab.android.mastodon.viewmodel.mastodon.TimelinesVM.sortAsc;
import android.content.Context;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import com.github.mikephil.charting.components.Description;
import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.components.MarkerView;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.data.CombinedData;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.formatter.IndexAxisValueFormatter;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import com.github.mikephil.charting.utils.MPPointF;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import app.fedilab.android.R;
import app.fedilab.android.databinding.ActivityCheckHomeCachetBinding;
import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.app.StatusCache;
import app.fedilab.android.mastodon.client.entities.app.TimelineCacheLogs;
import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.ThemeHelper;
import es.dmoral.toasty.Toasty;
public class CheckHomeCacheActivity extends BaseBarActivity {
private ActivityCheckHomeCachetBinding binding;
private List<Status> statuses;
private List<TimelineCacheLogs> timelineCacheLogsList;
private List<Status> statusesDay;
private List<TimelineCacheLogs> timelineCacheLogsDayList;
private ArrayList<String> xVals;
private ArrayList<String> xVals2;
private List<TimelineCacheLogs> timelineCacheLogsListToAnalyse;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityCheckHomeCachetBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
if (Helper.getCurrentAccount(CheckHomeCacheActivity.this) == null || Helper.getCurrentAccount(CheckHomeCacheActivity.this).mastodon_account == null) {
finish();
return;
}
drawCacheGraph(range.ALL);
drawCacheLogsGraph(range.ALL);
binding.chartToggle.setOnCheckedChangeListener((compoundButton, checked) -> {
drawCacheGraph(checked ? range.DAY : range.ALL);
drawCacheLogsGraph(checked ? range.DAY : range.ALL);
});
}
private void drawCacheGraph(range myRange) {
binding.progress.setVisibility(View.VISIBLE);
binding.chartToggle.setEnabled(false);
new Thread(() -> {
try {
if (myRange == range.ALL) {
if (statuses == null) {
statuses = new StatusCache(this).getHome(Helper.getCurrentAccount(CheckHomeCacheActivity.this));
sortAsc(statuses);
}
} else if (myRange == range.DAY) {
if (statusesDay == null && statuses != null) {
statusesDay = new ArrayList<>();
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_YEAR, -1);
for (Status status : statuses) {
if (status.created_at.after(calendar.getTime())) {
statusesDay.add(status);
}
}
}
}
if ((statuses == null || statuses.size() < 2) && myRange == range.ALL) {
runOnUiThread(() -> binding.chartToggle.setEnabled(true));
return;
}
if ((statusesDay == null || statusesDay.size() < 2) && myRange == range.DAY) {
runOnUiThread(() -> binding.chartToggle.setEnabled(true));
return;
}
List<Status> statusToAnalyse = new ArrayList<>();
if (myRange == range.ALL) {
statusToAnalyse.addAll(statuses);
} else {
statusToAnalyse.addAll(statusesDay);
}
Date firstMessageDate = statusToAnalyse.get(0).created_at;
Date lastMessageDate = statusToAnalyse.get(statusToAnalyse.size() - 1).created_at;
long diff = lastMessageDate.getTime() - firstMessageDate.getTime();
int numberOfHour = (int) Math.ceil((double) diff / (1000 * 60 * 60));
List<GraphElement> graphElements = new ArrayList<>();
xVals = new ArrayList<>();
String xDateH;
SimpleDateFormat df;
String xDateD;
String xDate;
for (int i = 0; i < numberOfHour; i++) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(firstMessageDate);
calendar.add(Calendar.HOUR, i);
xDateH = new SimpleDateFormat("HH", Locale.getDefault()).format(calendar.getTime());
df = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault());
xDateD = df.format(calendar.getTime());
xDate = xDateD + " " + String.format(Locale.getDefault(), "%sh", xDateH);
xVals.add(xDate);
GraphElement graphElement = new GraphElement();
graphElement.dateLabel = xDate;
int count = 0;
for (Status status : statusToAnalyse) {
xDateH = new SimpleDateFormat("HH", Locale.getDefault()).format(status.created_at);
df = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault());
xDateD = df.format(status.created_at.getTime());
xDate = xDateD + " " + String.format(Locale.getDefault(), "%sh", xDateH);
if (xDate.equalsIgnoreCase(graphElement.dateLabel)) {
count++;
}
}
graphElement.count = count;
graphElements.add(graphElement);
}
runOnUiThread(() -> {
binding.chartToggle.setEnabled(true);
binding.progress.setVisibility(View.GONE);
//We loop through cache
List<Entry> statusEntry = new ArrayList<>();
int inc = 0;
for (GraphElement ge : graphElements) {
statusEntry.add(new Entry(inc, ge.count));
inc++;
}
List<ILineDataSet> dataSets = new ArrayList<>();
LineDataSet dataStatus = new LineDataSet(statusEntry, getString(R.string.cached_messages));
dataStatus.setColor(ThemeHelper.getAttColor(this, R.attr.colorPrimary));
dataStatus.setFillColor(ThemeHelper.getAttColor(this, R.attr.colorPrimary));
dataStatus.setDrawValues(false);
dataStatus.setDrawFilled(true);
dataStatus.setDrawCircles(false);
dataStatus.setDrawCircleHole(false);
dataStatus.setMode(LineDataSet.Mode.CUBIC_BEZIER);
dataSets.add(dataStatus);
LineData data = new LineData(dataSets);
binding.chart.setData(data);
IndexAxisValueFormatter formatter = new IndexAxisValueFormatter() {
@Override
public String getFormattedValue(float value) {
if (value >= 0 && value < xVals.size()) {
return xVals.get((int) value);
} else
return "";
}
};
binding.chart.setExtraBottomOffset(80);
// binding.chart.getXAxis().setGranularity(1f);
binding.chart.getXAxis().setPosition(XAxis.XAxisPosition.BOTTOM);
binding.chart.getXAxis().setLabelRotationAngle(-45f);
binding.chart.getXAxis().setValueFormatter(formatter);
binding.chart.getXAxis().setEnabled(true);
binding.chart.getXAxis().setTextColor(ThemeHelper.getAttColor(CheckHomeCacheActivity.this, R.attr.colorOnBackground));
binding.chart.getAxisLeft().setTextColor(ThemeHelper.getAttColor(CheckHomeCacheActivity.this, R.attr.colorOnBackground));
binding.chart.getAxisRight().setTextColor(ThemeHelper.getAttColor(CheckHomeCacheActivity.this, R.attr.colorOnBackground));
binding.chart.getLegend().setTextColor(ThemeHelper.getAttColor(CheckHomeCacheActivity.this, R.attr.colorOnBackground));
binding.chart.getAxisLeft().setAxisMinimum(0f);
binding.chart.getAxisRight().setAxisMinimum(0f);
binding.chart.getXAxis().setLabelCount(10, true);
binding.chart.getLegend().setEnabled(false);
binding.chart.setTouchEnabled(true);
Description description = binding.chart.getDescription();
description.setEnabled(false);
CustomMarkerView mv = new CustomMarkerView(CheckHomeCacheActivity.this, R.layout.custom_marker_view_layout);
binding.chart.setMarker(mv);
binding.chart.invalidate();
});
} catch (DBException | NegativeArraySizeException e) {
Toasty.error(this, getString(R.string.toast_error), Toasty.LENGTH_SHORT).show();
throw new RuntimeException(e);
}
}).start();
}
private void drawCacheLogsGraph(range myRange) {
binding.progress2.setVisibility(View.VISIBLE);
new Thread(() -> {
xVals2 = new ArrayList<>();
try {
if (myRange == range.ALL) {
if (timelineCacheLogsList == null) {
timelineCacheLogsList = new TimelineCacheLogs(this).getHome(Helper.getCurrentAccount(CheckHomeCacheActivity.this));
}
} else if (myRange == range.DAY) {
if (timelineCacheLogsDayList == null && timelineCacheLogsList != null) {
timelineCacheLogsDayList = new ArrayList<>();
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_YEAR, -1);
for (TimelineCacheLogs timelineCacheLogs : timelineCacheLogsList) {
if (timelineCacheLogs.created_at.after(calendar.getTime())) {
timelineCacheLogsDayList.add(timelineCacheLogs);
}
}
}
}
if ((timelineCacheLogsList == null || timelineCacheLogsList.size() < 2) && myRange == range.ALL) {
return;
}
if ((timelineCacheLogsDayList == null || timelineCacheLogsDayList.size() < 2) && myRange == range.DAY) {
return;
}
timelineCacheLogsListToAnalyse = new ArrayList<>();
if (myRange == range.ALL) {
timelineCacheLogsListToAnalyse.addAll(timelineCacheLogsList);
} else {
timelineCacheLogsListToAnalyse.addAll(timelineCacheLogsDayList);
}
List<BarEntry> failEntry = new ArrayList<>();
List<Entry> updateEntry = new ArrayList<>();
List<Entry> insertEntry = new ArrayList<>();
List<Entry> frequencyEntry = new ArrayList<>();
List<Entry> fetchedEntry = new ArrayList<>();
int inc = 0;
for (TimelineCacheLogs timelineCacheLogs : timelineCacheLogsListToAnalyse) {
//X-Axis
//X-Axis
String xDate = dateDiffFullShort(timelineCacheLogs.created_at);
xVals2.add(xDate);
//Entries
failEntry.add(new BarEntry(inc, timelineCacheLogs.failed));
updateEntry.add(new Entry(inc, timelineCacheLogs.updated));
insertEntry.add(new Entry(inc, timelineCacheLogs.inserted));
frequencyEntry.add(new Entry(inc, timelineCacheLogs.frequency));
fetchedEntry.add(new Entry(inc, timelineCacheLogs.fetched));
inc++;
}
runOnUiThread(() -> {
binding.progress2.setVisibility(View.GONE);
LineData lineData = new LineData();
BarData barDataFailed = new BarData();
BarDataSet dataFailed = new BarDataSet(failEntry, getString(R.string.fails));
LineDataSet dataNewMessage = new LineDataSet(insertEntry, getString(R.string.new_messages));
LineDataSet dataUpdatedMessage = new LineDataSet(updateEntry, getString(R.string.updated_messages));
LineDataSet dataFrequency = new LineDataSet(frequencyEntry, getString(R.string.frequency_minutes));
LineDataSet dataFetched = new LineDataSet(fetchedEntry, getString(R.string.total_fetched));
dataFailed.setColor(ContextCompat.getColor(CheckHomeCacheActivity.this, R.color.errorColor));
dataFailed.setDrawValues(false);
dataFailed.setAxisDependency(YAxis.AxisDependency.RIGHT);
barDataFailed.addDataSet(dataFailed);
dataNewMessage.setColor(ContextCompat.getColor(CheckHomeCacheActivity.this, R.color.decoration_1));
dataNewMessage.setFillColor(ContextCompat.getColor(CheckHomeCacheActivity.this, R.color.transparent));
dataNewMessage.setDrawValues(false);
dataNewMessage.setDrawFilled(true);
dataNewMessage.setDrawCircles(false);
dataNewMessage.setDrawCircleHole(false);
dataNewMessage.setAxisDependency(YAxis.AxisDependency.LEFT);
dataNewMessage.setMode(LineDataSet.Mode.CUBIC_BEZIER);
lineData.addDataSet(dataNewMessage);
dataUpdatedMessage.setColor(ContextCompat.getColor(CheckHomeCacheActivity.this, R.color.decoration_2));
dataUpdatedMessage.setFillColor(ContextCompat.getColor(CheckHomeCacheActivity.this, R.color.transparent));
dataUpdatedMessage.setDrawValues(false);
dataUpdatedMessage.setDrawFilled(true);
dataUpdatedMessage.setDrawCircles(false);
dataUpdatedMessage.setDrawCircleHole(false);
dataUpdatedMessage.setAxisDependency(YAxis.AxisDependency.LEFT);
dataUpdatedMessage.setMode(LineDataSet.Mode.CUBIC_BEZIER);
lineData.addDataSet(dataUpdatedMessage);
dataFrequency.setColor(ContextCompat.getColor(CheckHomeCacheActivity.this, R.color.decoration_3));
dataFrequency.setFillColor(ContextCompat.getColor(CheckHomeCacheActivity.this, R.color.transparent));
dataFrequency.setDrawValues(false);
dataFrequency.setDrawFilled(true);
dataFrequency.setDrawCircles(false);
dataFrequency.setDrawCircleHole(false);
dataFrequency.setAxisDependency(YAxis.AxisDependency.LEFT);
dataFrequency.setMode(LineDataSet.Mode.CUBIC_BEZIER);
lineData.addDataSet(dataFrequency);
dataFetched.setColor(ContextCompat.getColor(CheckHomeCacheActivity.this, R.color.decoration_4));
dataFetched.setFillColor(ContextCompat.getColor(CheckHomeCacheActivity.this, R.color.transparent));
dataFetched.setDrawValues(false);
dataFetched.setDrawFilled(true);
dataFetched.setDrawCircles(false);
dataFetched.setDrawCircleHole(false);
dataFetched.setAxisDependency(YAxis.AxisDependency.LEFT);
dataFetched.setMode(LineDataSet.Mode.CUBIC_BEZIER);
lineData.addDataSet(dataFetched);
CombinedData data = new CombinedData();
data.setData(barDataFailed);
data.setData(lineData);
binding.chart2.setData(data);
IndexAxisValueFormatter formatter = new IndexAxisValueFormatter() {
@Override
public String getFormattedValue(float value) {
if (value >= 0 && value < xVals2.size()) {
return xVals2.get((int) value);
} else
return "";
}
};
binding.chart2.setExtraBottomOffset(80);
// binding.chart.getXAxis().setGranularity(1f);
binding.chart2.getXAxis().setPosition(XAxis.XAxisPosition.BOTTOM);
binding.chart2.getXAxis().setLabelRotationAngle(-45f);
binding.chart2.getXAxis().setValueFormatter(formatter);
binding.chart2.getXAxis().setEnabled(true);
binding.chart2.getXAxis().setTextColor(ThemeHelper.getAttColor(CheckHomeCacheActivity.this, R.attr.colorOnBackground));
binding.chart2.getAxisLeft().setTextColor(ThemeHelper.getAttColor(CheckHomeCacheActivity.this, R.attr.colorOnBackground));
binding.chart2.getAxisRight().setTextColor(ThemeHelper.getAttColor(CheckHomeCacheActivity.this, R.attr.colorOnBackground));
binding.chart2.getLegend().setTextColor(ThemeHelper.getAttColor(CheckHomeCacheActivity.this, R.attr.colorOnBackground));
binding.chart2.getAxisLeft().setAxisMinimum(0f);
binding.chart2.getAxisRight().setAxisMinimum(0f);
binding.chart2.getXAxis().setLabelCount(10, true);
binding.chart2.getLegend().setEnabled(true);
binding.chart2.getLegend().setOrientation(Legend.LegendOrientation.HORIZONTAL);
binding.chart2.getLegend().setVerticalAlignment(Legend.LegendVerticalAlignment.BOTTOM);
binding.chart2.getLegend().setDrawInside(false);
binding.chart2.getLegend().setWordWrapEnabled(true);
binding.chart2.setTouchEnabled(true);
Description description = binding.chart2.getDescription();
description.setEnabled(true);
CustomMarkerView2 mv = new CustomMarkerView2(CheckHomeCacheActivity.this, R.layout.custom_marker_view_layout);
binding.chart2.setMarker(mv);
binding.chart2.invalidate();
});
} catch (DBException | NegativeArraySizeException e) {
Toasty.error(this, getString(R.string.toast_error), Toasty.LENGTH_SHORT).show();
throw new RuntimeException(e);
}
}).start();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
public enum range {
ALL,
DAY
}
public static class GraphElement {
String dateLabel;
int count;
@Override
public boolean equals(@Nullable Object obj) {
boolean same = false;
if (obj instanceof GraphElement) {
same = this.dateLabel.equals(((GraphElement) obj).dateLabel);
}
return same;
}
}
public class CustomMarkerView extends MarkerView {
private final TextView tvContent;
private MPPointF mOffset;
public CustomMarkerView(Context context, int layoutResource) {
super(context, layoutResource);
// find your layout components
tvContent = findViewById(R.id.tvContent);
}
// callbacks everytime the MarkerView is redrawn, can be used to update the
// content (user-interface)
@Override
public void refreshContent(Entry e, Highlight highlight) {
if (xVals.size() > (int) e.getX()) {
tvContent.setText(getString(R.string.messages, (int) e.getY()));
}
// this will perform necessary layouting
super.refreshContent(e, highlight);
}
@Override
public MPPointF getOffset() {
if (mOffset == null) {
// center the marker horizontally and vertically
mOffset = new MPPointF(-(int) (getWidth() / 2), -getHeight());
}
return mOffset;
}
}
public class CustomMarkerView2 extends MarkerView {
private final TextView tvContent;
private MPPointF mOffset;
public CustomMarkerView2(Context context, int layoutResource) {
super(context, layoutResource);
// find your layout components
tvContent = findViewById(R.id.tvContent);
}
// callbacks everytime the MarkerView is redrawn, can be used to update the
// content (user-interface)
@Override
public void refreshContent(Entry e, Highlight highlight) {
if (xVals2.size() > (int) e.getX()) {
if (timelineCacheLogsListToAnalyse != null && (int) e.getX() < timelineCacheLogsListToAnalyse.size()) {
String text = getString(R.string.fail_count, timelineCacheLogsListToAnalyse.get((int) e.getX()).failed) + "\r\n";
text += getString(R.string.fetched_count, timelineCacheLogsListToAnalyse.get((int) e.getX()).fetched) + "\r\n";
text += getString(R.string.inserted_count, timelineCacheLogsListToAnalyse.get((int) e.getX()).inserted) + "\r\n";
text += getString(R.string.updated_count, timelineCacheLogsListToAnalyse.get((int) e.getX()).updated) + "\r\n";
text += getString(R.string.frequency_count_minutes, timelineCacheLogsListToAnalyse.get((int) e.getX()).frequency) + "\r\n\r\n";
text += dateDiffFull(timelineCacheLogsListToAnalyse.get((int) e.getX()).created_at);
tvContent.setText(text);
} else {
tvContent.setText(getString(R.string.messages, (int) e.getY()));
}
}
// this will perform necessary layouting
super.refreshContent(e, highlight);
}
@Override
public MPPointF getOffset() {
if (mOffset == null) {
// center the marker horizontally and vertically
mOffset = new MPPointF(-(int) (getWidth() / 2), -getHeight());
}
return mOffset;
}
}
}

View file

@ -15,7 +15,7 @@ package app.fedilab.android.mastodon.activities;
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentAccount; import static app.fedilab.android.BaseMainActivity.currentInstance;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -29,10 +29,13 @@ import android.view.MenuItem;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import com.google.android.material.appbar.AppBarLayout;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -43,6 +46,7 @@ import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity; import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.databinding.ActivityConversationBinding; import app.fedilab.android.databinding.ActivityConversationBinding;
import app.fedilab.android.mastodon.client.entities.api.Status; import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.client.entities.app.StatusCache; import app.fedilab.android.mastodon.client.entities.app.StatusCache;
import app.fedilab.android.mastodon.exception.DBException; import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.helper.Helper; import app.fedilab.android.mastodon.helper.Helper;
@ -60,12 +64,16 @@ public class ContextActivity extends BaseActivity implements FragmentMastodonCon
Fragment currentFragment; Fragment currentFragment;
private Status firstMessage; private Status firstMessage;
private String remote_instance; private String remote_instance;
private Status focusedStatus;
private String focusedStatusURI;
private boolean checkRemotely;
private ActivityConversationBinding binding;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
ActivityConversationBinding binding = ActivityConversationBinding.inflate(getLayoutInflater()); binding = ActivityConversationBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot()); setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar); setSupportActionBar(binding.toolbar);
ActionBar actionBar = getSupportActionBar(); ActionBar actionBar = getSupportActionBar();
@ -82,50 +90,101 @@ public class ContextActivity extends BaseActivity implements FragmentMastodonCon
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true);
} }
Bundle b = getIntent().getExtras(); manageTopBarScrolling(binding.toolbar, sharedpreferences);
displayCW = sharedpreferences.getBoolean(getString(R.string.SET_EXPAND_CW), false); displayCW = sharedpreferences.getBoolean(getString(R.string.SET_EXPAND_CW), false);
Status focusedStatus = null; // or other values focusedStatus = null; // or other values
if (b != null) {
focusedStatus = (Status) b.getSerializable(Helper.ARG_STATUS);
remote_instance = b.getString(Helper.ARG_REMOTE_INSTANCE, null); Bundle args = getIntent().getExtras();
} if (args != null) {
if (focusedStatus == null || currentAccount == null || currentAccount.mastodon_account == null) { long bundleId = args.getLong(Helper.ARG_INTENT_ID, -1);
finish(); new CachedBundle(ContextActivity.this).getBundle(bundleId, Helper.getCurrentAccount(ContextActivity.this), this::initializeAfterBundle);
return; } else {
} initializeAfterBundle(null);
MastodonHelper.loadPPMastodon(binding.profilePicture, currentAccount.mastodon_account);
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_STATUS, focusedStatus);
bundle.putString(Helper.ARG_REMOTE_INSTANCE, remote_instance);
FragmentMastodonContext fragmentMastodonContext = new FragmentMastodonContext();
fragmentMastodonContext.firstMessage = this;
currentFragment = Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_content_main, fragmentMastodonContext, bundle, null, null);
if (remote_instance == null) {
StatusesVM timelinesVM = new ViewModelProvider(ContextActivity.this).get(StatusesVM.class);
timelinesVM.getStatus(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, focusedStatus.id).observe(ContextActivity.this, status -> {
if (status != null) {
StatusCache statusCache = new StatusCache();
statusCache.instance = BaseMainActivity.currentInstance;
statusCache.user_id = BaseMainActivity.currentUserID;
statusCache.status = status;
statusCache.status_id = status.id;
//Update cache
new Thread(() -> {
try {
new StatusCache(getApplication()).updateIfExists(statusCache);
Handler mainHandler = new Handler(Looper.getMainLooper());
//Update UI
Runnable myRunnable = () -> StatusAdapter.sendAction(ContextActivity.this, Helper.ARG_STATUS_ACTION, status, null);
mainHandler.post(myRunnable);
} catch (DBException e) {
e.printStackTrace();
}
}).start();
}
});
} }
} }
private void initializeAfterBundle(Bundle bundle) {
if (bundle != null) {
focusedStatus = (Status) bundle.getSerializable(Helper.ARG_STATUS);
remote_instance = bundle.getString(Helper.ARG_REMOTE_INSTANCE, null);
focusedStatusURI = bundle.getString(Helper.ARG_FOCUSED_STATUS_URI, null);
}
if (focusedStatus == null || Helper.getCurrentAccount(ContextActivity.this) == null || Helper.getCurrentAccount(ContextActivity.this).mastodon_account == null) {
finish();
return;
}
if (focusedStatusURI == null && remote_instance == null) {
focusedStatusURI = focusedStatus.uri;
}
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(this);
checkRemotely = sharedpreferences.getBoolean(getString(R.string.SET_CONVERSATION_REMOTELY), false);
if (!checkRemotely) {
loadLocalConversation();
} else {
loadRemotelyConversation(true);
invalidateOptionsMenu();
}
if (Helper.getCurrentAccount(ContextActivity.this) != null) {
MastodonHelper.loadPPMastodon(binding.profilePicture, Helper.getCurrentAccount(ContextActivity.this).mastodon_account);
}
}
private void manageTopBarScrolling(Toolbar toolbar, SharedPreferences sharedpreferences) {
final boolean topBarScrolling = !sharedpreferences.getBoolean(getString(R.string.SET_DISABLE_TOPBAR_SCROLLING), false);
final AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
int scrollFlags = toolbarLayoutParams.getScrollFlags();
if (topBarScrolling) {
scrollFlags |= AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL;
} else {
scrollFlags &= ~AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL;
}
toolbarLayoutParams.setScrollFlags(scrollFlags);
}
private void loadLocalConversation() {
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_STATUS, focusedStatus);
args.putString(Helper.ARG_REMOTE_INSTANCE, remote_instance);
new CachedBundle(ContextActivity.this).insertBundle(args, Helper.getCurrentAccount(ContextActivity.this), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
FragmentMastodonContext fragmentMastodonContext = new FragmentMastodonContext();
fragmentMastodonContext.firstMessage = this;
currentFragment = Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_content_main, fragmentMastodonContext, bundle, null, null);
//Update the status
if (remote_instance == null) {
StatusesVM timelinesVM = new ViewModelProvider(ContextActivity.this).get(StatusesVM.class);
timelinesVM.getStatus(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, focusedStatus.id).observe(ContextActivity.this, status -> {
if (status != null) {
StatusCache statusCache = new StatusCache();
statusCache.instance = BaseMainActivity.currentInstance;
statusCache.user_id = BaseMainActivity.currentUserID;
statusCache.status = status;
statusCache.status_id = status.id;
//Update cache
new Thread(() -> {
try {
new StatusCache(getApplication()).updateIfExists(statusCache);
Handler mainHandler = new Handler(Looper.getMainLooper());
//Update UI
Runnable myRunnable = () -> StatusAdapter.sendAction(ContextActivity.this, Helper.ARG_STATUS_ACTION, status, null);
mainHandler.post(myRunnable);
} catch (DBException e) {
e.printStackTrace();
}
}).start();
}
});
}
});
}
@Override @Override
public boolean onCreateOptionsMenu(@NonNull Menu menu) { public boolean onCreateOptionsMenu(@NonNull Menu menu) {
@ -144,7 +203,7 @@ public class ContextActivity extends BaseActivity implements FragmentMastodonCon
itemDisplayCW.setIcon(R.drawable.ic_outline_remove_red_eye_24); itemDisplayCW.setIcon(R.drawable.ic_outline_remove_red_eye_24);
} }
MenuItem action_remote = menu.findItem(R.id.action_remote); MenuItem action_remote = menu.findItem(R.id.action_remote);
if (remote_instance != null) { if (remote_instance != null || checkRemotely) {
action_remote.setVisible(false); action_remote.setVisible(false);
} else { } else {
if (firstMessage != null && !firstMessage.visibility.equalsIgnoreCase("direct") && !firstMessage.visibility.equalsIgnoreCase("private")) { if (firstMessage != null && !firstMessage.visibility.equalsIgnoreCase("direct") && !firstMessage.visibility.equalsIgnoreCase("private")) {
@ -181,10 +240,67 @@ public class ContextActivity extends BaseActivity implements FragmentMastodonCon
} }
invalidateOptionsMenu(); invalidateOptionsMenu();
} else if (item.getItemId() == R.id.action_remote) { } else if (item.getItemId() == R.id.action_remote) {
loadRemotelyConversation(false);
}
return true;
}
private void loadRemotelyConversation(boolean fallback) {
if (fallback) {
StatusesVM statusesVM;
statusesVM = new ViewModelProvider(this).get(StatusesVM.class);
statusesVM.getContext(currentInstance, null, focusedStatus.id)
.observe(this, result -> {
if (result != null && result.ancestors != null && result.ancestors.size() > 0) {
firstMessage = result.ancestors.get(0);
String instance = null;
try {
URL url = new URL(firstMessage.uri);
instance = url.getHost();
} catch (MalformedURLException e) {
e.printStackTrace();
}
if (instance == null) {
loadLocalConversation();
return;
}
Pattern pattern = Helper.statusIdInUrl;
Matcher matcher = pattern.matcher(firstMessage.uri);
String remoteId = null;
if (matcher.find()) {
remoteId = matcher.group(1);
}
if (remoteId == null) {
loadLocalConversation();
return;
}
String finalInstance = instance;
statusesVM.getStatus(instance, null, remoteId).observe(ContextActivity.this, status -> {
if (status != null) {
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_STATUS, status);
args.putString(Helper.ARG_REMOTE_INSTANCE, finalInstance);
args.putString(Helper.ARG_FOCUSED_STATUS_URI, focusedStatusURI);
new CachedBundle(ContextActivity.this).insertBundle(args, Helper.getCurrentAccount(ContextActivity.this), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
FragmentMastodonContext fragmentMastodonContext = new FragmentMastodonContext();
fragmentMastodonContext.firstMessage = ContextActivity.this;
currentFragment = Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_content_main, fragmentMastodonContext, bundle, null, null);
});
} else {
loadLocalConversation();
}
});
} else {
loadLocalConversation();
}
});
} else {
if (firstMessage == null) { if (firstMessage == null) {
Toasty.warning(ContextActivity.this, getString(R.string.toast_try_later), Toasty.LENGTH_SHORT).show(); Toasty.warning(ContextActivity.this, getString(R.string.toast_try_later), Toasty.LENGTH_SHORT).show();
return true; return;
} }
if (firstMessage.account.acct != null) { if (firstMessage.account.acct != null) {
String instance = null; String instance = null;
@ -196,11 +312,11 @@ public class ContextActivity extends BaseActivity implements FragmentMastodonCon
} }
if (instance == null) { if (instance == null) {
Toasty.info(ContextActivity.this, getString(R.string.toast_error_fetch_message), Toasty.LENGTH_SHORT).show(); Toasty.info(ContextActivity.this, getString(R.string.toast_error_fetch_message), Toasty.LENGTH_SHORT).show();
return true; return;
} }
if (instance.equalsIgnoreCase(MainActivity.currentInstance)) { if (instance.equalsIgnoreCase(MainActivity.currentInstance)) {
Toasty.info(ContextActivity.this, getString(R.string.toast_on_your_instance), Toasty.LENGTH_SHORT).show(); Toasty.info(ContextActivity.this, getString(R.string.toast_on_your_instance), Toasty.LENGTH_SHORT).show();
return true; return;
} }
Pattern pattern = Helper.statusIdInUrl; Pattern pattern = Helper.statusIdInUrl;
Matcher matcher = pattern.matcher(firstMessage.uri); Matcher matcher = pattern.matcher(firstMessage.uri);
@ -214,10 +330,17 @@ public class ContextActivity extends BaseActivity implements FragmentMastodonCon
statusesVM.getStatus(instance, null, remoteId).observe(ContextActivity.this, status -> { statusesVM.getStatus(instance, null, remoteId).observe(ContextActivity.this, status -> {
if (status != null) { if (status != null) {
Intent intentContext = new Intent(ContextActivity.this, ContextActivity.class); Intent intentContext = new Intent(ContextActivity.this, ContextActivity.class);
intentContext.putExtra(Helper.ARG_STATUS, status); Bundle args = new Bundle();
intentContext.putExtra(Helper.ARG_REMOTE_INSTANCE, finalInstance); args.putSerializable(Helper.ARG_STATUS, status);
intentContext.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); args.putString(Helper.ARG_FOCUSED_STATUS_URI, focusedStatusURI);
startActivity(intentContext); args.putString(Helper.ARG_REMOTE_INSTANCE, finalInstance);
new CachedBundle(ContextActivity.this).insertBundle(args, Helper.getCurrentAccount(ContextActivity.this), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentContext.putExtras(bundle);
intentContext.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intentContext);
});
} else { } else {
Toasty.warning(ContextActivity.this, getString(R.string.toast_error_fetch_message), Toasty.LENGTH_SHORT).show(); Toasty.warning(ContextActivity.this, getString(R.string.toast_error_fetch_message), Toasty.LENGTH_SHORT).show();
} }
@ -229,7 +352,6 @@ public class ContextActivity extends BaseActivity implements FragmentMastodonCon
Toasty.warning(ContextActivity.this, getString(R.string.toast_error_fetch_message), Toasty.LENGTH_SHORT).show(); Toasty.warning(ContextActivity.this, getString(R.string.toast_error_fetch_message), Toasty.LENGTH_SHORT).show();
} }
} }
return true;
} }
@Override @Override

View file

@ -14,7 +14,6 @@ package app.fedilab.android.mastodon.activities;
* You should have received a copy of the GNU General Public License along with Fedilab; if not, * You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentAccount;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
@ -28,6 +27,7 @@ import android.widget.Toast;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import app.fedilab.android.R; import app.fedilab.android.R;
@ -36,6 +36,7 @@ import app.fedilab.android.mastodon.client.entities.api.Attachment;
import app.fedilab.android.mastodon.client.entities.api.Emoji; import app.fedilab.android.mastodon.client.entities.api.Emoji;
import app.fedilab.android.mastodon.client.entities.api.Status; import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.api.Tag; import app.fedilab.android.mastodon.client.entities.api.Tag;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.helper.Helper; import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.customsharing.CustomSharingAsyncTask; import app.fedilab.android.mastodon.helper.customsharing.CustomSharingAsyncTask;
import app.fedilab.android.mastodon.helper.customsharing.CustomSharingResponse; import app.fedilab.android.mastodon.helper.customsharing.CustomSharingResponse;
@ -64,23 +65,34 @@ public class CustomSharingActivity extends BaseBarActivity implements OnCustomSh
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(CustomSharingActivity.this);
binding = ActivityCustomSharingBinding.inflate(getLayoutInflater()); binding = ActivityCustomSharingBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot()); setContentView(binding.getRoot());
if (getSupportActionBar() != null) { if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true);
} }
Bundle b = getIntent().getExtras(); Bundle args = getIntent().getExtras();
status = null; status = null;
if (b != null) { if (args != null) {
status = (Status) b.getSerializable(Helper.ARG_STATUS); long bundleId = args.getLong(Helper.ARG_INTENT_ID, -1);
new CachedBundle(CustomSharingActivity.this).getBundle(bundleId, Helper.getCurrentAccount(CustomSharingActivity.this), this::initializeAfterBundle);
} else {
initializeAfterBundle(null);
}
}
private void initializeAfterBundle(Bundle bundle) {
if (bundle != null) {
status = (Status) bundle.getSerializable(Helper.ARG_STATUS);
} }
if (status == null) { if (status == null) {
finish(); finish();
return; return;
} }
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(CustomSharingActivity.this);
bundle_creator = status.account.acct; bundle_creator = status.account.acct;
bundle_url = status.url; bundle_url = status.url;
bundle_id = status.uri; bundle_id = status.uri;
@ -97,7 +109,7 @@ public class CustomSharingActivity extends BaseBarActivity implements OnCustomSh
bundle_thumbnailurl = status.account.avatar; bundle_thumbnailurl = status.account.avatar;
} }
if (!bundle_creator.contains("@")) { if (!bundle_creator.contains("@")) {
bundle_creator = bundle_creator + "@" + currentAccount.instance; bundle_creator = bundle_creator + "@" + Helper.getCurrentAccount(CustomSharingActivity.this).instance;
} }
binding.setCustomSharingTitle.setEllipsize(TextUtils.TruncateAt.END); binding.setCustomSharingTitle.setEllipsize(TextUtils.TruncateAt.END);
@ -126,7 +138,7 @@ public class CustomSharingActivity extends BaseBarActivity implements OnCustomSh
binding.setCustomSharingSave.setOnClickListener(v -> { binding.setCustomSharingSave.setOnClickListener(v -> {
// obtain title, description, keywords // obtain title, description, keywords
title = binding.setCustomSharingTitle.getText().toString(); title = binding.setCustomSharingTitle.getText().toString();
keywords = binding.setCustomSharingKeywords.getText().toString(); keywords = Objects.requireNonNull(binding.setCustomSharingKeywords.getText()).toString();
CharSequence comma_only = ","; CharSequence comma_only = ",";
CharSequence space_only = " "; CharSequence space_only = " ";
CharSequence double_space = " "; CharSequence double_space = " ";
@ -194,38 +206,38 @@ public class CustomSharingActivity extends BaseBarActivity implements OnCustomSh
String param_value = uri.getQueryParameter(param_name); String param_value = uri.getQueryParameter(param_name);
if (param_value != null) if (param_value != null)
switch (param_value) { switch (param_value) {
case "${url}": case "${url}" -> {
paramFound = true; paramFound = true;
builder.appendQueryParameter(param_name, bundle_url); builder.appendQueryParameter(param_name, bundle_url);
break; }
case "${title}": case "${title}" -> {
paramFound = true; paramFound = true;
builder.appendQueryParameter(param_name, title); builder.appendQueryParameter(param_name, title);
break; }
case "${source}": case "${source}" -> {
paramFound = true; paramFound = true;
builder.appendQueryParameter(param_name, bundle_source); builder.appendQueryParameter(param_name, bundle_source);
break; }
case "${id}": case "${id}" -> {
paramFound = true; paramFound = true;
builder.appendQueryParameter(param_name, bundle_id); builder.appendQueryParameter(param_name, bundle_id);
break; }
case "${description}": case "${description}" -> {
paramFound = true; paramFound = true;
builder.appendQueryParameter(param_name, bundle_content); builder.appendQueryParameter(param_name, bundle_content);
break; }
case "${keywords}": case "${keywords}" -> {
paramFound = true; paramFound = true;
builder.appendQueryParameter(param_name, keywords); builder.appendQueryParameter(param_name, keywords);
break; }
case "${creator}": case "${creator}" -> {
paramFound = true; paramFound = true;
builder.appendQueryParameter(param_name, bundle_creator); builder.appendQueryParameter(param_name, bundle_creator);
break; }
case "${thumbnailurl}": case "${thumbnailurl}" -> {
paramFound = true; paramFound = true;
builder.appendQueryParameter(param_name, bundle_thumbnailurl); builder.appendQueryParameter(param_name, bundle_thumbnailurl);
break; }
} }
if (!paramFound) { if (!paramFound) {
builder.appendQueryParameter(param_name, param_value); builder.appendQueryParameter(param_name, param_value);

View file

@ -15,7 +15,6 @@ package app.fedilab.android.mastodon.activities;
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentAccount;
import static app.fedilab.android.mastodon.activities.ComposeActivity.PICK_MEDIA; import static app.fedilab.android.mastodon.activities.ComposeActivity.PICK_MEDIA;
import android.content.ClipData; import android.content.ClipData;
@ -39,6 +38,7 @@ import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.databinding.ActivityDirectMessageBinding; import app.fedilab.android.databinding.ActivityDirectMessageBinding;
import app.fedilab.android.mastodon.client.entities.api.Status; import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.client.entities.app.StatusCache; import app.fedilab.android.mastodon.client.entities.app.StatusCache;
import app.fedilab.android.mastodon.exception.DBException; import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.helper.Helper; import app.fedilab.android.mastodon.helper.Helper;
@ -53,7 +53,6 @@ public class DirectMessageActivity extends BaseActivity implements FragmentMasto
public static boolean displayCW; public static boolean displayCW;
FragmentMastodonDirectMessage currentFragment; FragmentMastodonDirectMessage currentFragment;
private Status firstMessage;
private String remote_instance; private String remote_instance;
@Override @Override
@ -72,53 +71,71 @@ public class DirectMessageActivity extends BaseActivity implements FragmentMasto
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(this);
float scale = sharedpreferences.getFloat(getString(R.string.SET_FONT_SCALE), 1.1f); float scale = sharedpreferences.getFloat(getString(R.string.SET_FONT_SCALE), 1.1f);
binding.title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18 * 1.1f / scale); binding.title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18 * 1.1f / scale);
MastodonHelper.loadPPMastodon(binding.profilePicture, Helper.getCurrentAccount(DirectMessageActivity.this).mastodon_account);
if (getSupportActionBar() != null) { if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true);
} }
Bundle b = getIntent().getExtras(); Bundle args = getIntent().getExtras();
displayCW = sharedpreferences.getBoolean(getString(R.string.SET_EXPAND_CW), false); displayCW = sharedpreferences.getBoolean(getString(R.string.SET_EXPAND_CW), false);
Status focusedStatus = null; // or other values
if (b != null) { if (args != null) {
focusedStatus = (Status) b.getSerializable(Helper.ARG_STATUS); long bundleId = args.getLong(Helper.ARG_INTENT_ID, -1);
remote_instance = b.getString(Helper.ARG_REMOTE_INSTANCE, null); new CachedBundle(DirectMessageActivity.this).getBundle(bundleId, Helper.getCurrentAccount(DirectMessageActivity.this), this::initializeAfterBundle);
} else {
initializeAfterBundle(null);
} }
if (focusedStatus == null || currentAccount == null || currentAccount.mastodon_account == null) {
}
private void initializeAfterBundle(Bundle bundle) {
Status focusedStatus = null; // or other values
if (bundle != null) {
focusedStatus = (Status) bundle.getSerializable(Helper.ARG_STATUS);
remote_instance = bundle.getString(Helper.ARG_REMOTE_INSTANCE, null);
}
if (focusedStatus == null || Helper.getCurrentAccount(DirectMessageActivity.this) == null || Helper.getCurrentAccount(DirectMessageActivity.this).mastodon_account == null) {
finish(); finish();
return; return;
} }
MastodonHelper.loadPPMastodon(binding.profilePicture, currentAccount.mastodon_account);
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_STATUS, focusedStatus);
bundle.putString(Helper.ARG_REMOTE_INSTANCE, remote_instance);
FragmentMastodonDirectMessage FragmentMastodonDirectMessage = new FragmentMastodonDirectMessage();
FragmentMastodonDirectMessage.firstMessage = this;
currentFragment = (FragmentMastodonDirectMessage) Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_content_main, FragmentMastodonDirectMessage, bundle, null, null);
StatusesVM timelinesVM = new ViewModelProvider(DirectMessageActivity.this).get(StatusesVM.class);
timelinesVM.getStatus(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, focusedStatus.id).observe(DirectMessageActivity.this, status -> {
if (status != null) {
StatusCache statusCache = new StatusCache();
statusCache.instance = BaseMainActivity.currentInstance;
statusCache.user_id = BaseMainActivity.currentUserID;
statusCache.status = status;
statusCache.status_id = status.id;
//Update cache
new Thread(() -> {
try {
new StatusCache(getApplication()).updateIfExists(statusCache);
Handler mainHandler = new Handler(Looper.getMainLooper());
//Update UI
Runnable myRunnable = () -> StatusAdapter.sendAction(DirectMessageActivity.this, Helper.ARG_STATUS_ACTION, status, null);
mainHandler.post(myRunnable);
} catch (DBException e) {
e.printStackTrace();
}
}).start();
}
});
}
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_STATUS, focusedStatus);
args.putString(Helper.ARG_REMOTE_INSTANCE, remote_instance);
Status finalFocusedStatus = focusedStatus;
new CachedBundle(DirectMessageActivity.this).insertBundle(args, Helper.getCurrentAccount(DirectMessageActivity.this), bundleId -> {
Bundle args2 = new Bundle();
args2.putLong(Helper.ARG_INTENT_ID, bundleId);
FragmentMastodonDirectMessage FragmentMastodonDirectMessage = new FragmentMastodonDirectMessage();
FragmentMastodonDirectMessage.firstMessage = this;
currentFragment = (FragmentMastodonDirectMessage) Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_content_main, FragmentMastodonDirectMessage, args2, null, null);
StatusesVM timelinesVM = new ViewModelProvider(DirectMessageActivity.this).get(StatusesVM.class);
timelinesVM.getStatus(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, finalFocusedStatus.id).observe(DirectMessageActivity.this, status -> {
if (status != null) {
StatusCache statusCache = new StatusCache();
statusCache.instance = BaseMainActivity.currentInstance;
statusCache.user_id = BaseMainActivity.currentUserID;
statusCache.status = status;
statusCache.status_id = status.id;
//Update cache
new Thread(() -> {
try {
new StatusCache(getApplication()).updateIfExists(statusCache);
Handler mainHandler = new Handler(Looper.getMainLooper());
//Update UI
Runnable myRunnable = () -> StatusAdapter.sendAction(DirectMessageActivity.this, Helper.ARG_STATUS_ACTION, status, null);
mainHandler.post(myRunnable);
} catch (DBException e) {
e.printStackTrace();
}
}).start();
}
});
});
}
@Override @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
@ -149,7 +166,6 @@ public class DirectMessageActivity extends BaseActivity implements FragmentMasto
@Override @Override
public void get(Status status) { public void get(Status status) {
firstMessage = status;
invalidateOptionsMenu(); invalidateOptionsMenu();
} }
} }

View file

@ -14,7 +14,6 @@ package app.fedilab.android.mastodon.activities;
* You should have received a copy of the GNU General Public License along with Fedilab; if not, * You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentAccount;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
@ -45,6 +44,7 @@ import app.fedilab.android.mastodon.client.entities.api.Attachment;
import app.fedilab.android.mastodon.client.entities.api.Status; import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.app.StatusDraft; import app.fedilab.android.mastodon.client.entities.app.StatusDraft;
import app.fedilab.android.mastodon.exception.DBException; import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.ui.drawer.StatusDraftAdapter; import app.fedilab.android.mastodon.ui.drawer.StatusDraftAdapter;
import app.fedilab.android.mastodon.viewmodel.mastodon.TimelinesVM; import app.fedilab.android.mastodon.viewmodel.mastodon.TimelinesVM;
@ -82,7 +82,7 @@ public class DraftActivity extends BaseActivity implements StatusDraftAdapter.Dr
getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true);
} }
timelinesVM = new ViewModelProvider(DraftActivity.this).get(TimelinesVM.class); timelinesVM = new ViewModelProvider(DraftActivity.this).get(TimelinesVM.class);
timelinesVM.getDrafts(currentAccount) timelinesVM.getDrafts(Helper.getCurrentAccount(DraftActivity.this))
.observe(DraftActivity.this, this::initializeDraftView); .observe(DraftActivity.this, this::initializeDraftView);
} }
@ -178,7 +178,7 @@ public class DraftActivity extends BaseActivity implements StatusDraftAdapter.Dr
super.onResume(); super.onResume();
//We need to check if drafts changed (ie when coming back from the compose activity) //We need to check if drafts changed (ie when coming back from the compose activity)
if (statusDrafts != null && timelinesVM != null) { if (statusDrafts != null && timelinesVM != null) {
timelinesVM.getDrafts(currentAccount) timelinesVM.getDrafts(Helper.getCurrentAccount(DraftActivity.this))
.observe(DraftActivity.this, this::updateDrafts); .observe(DraftActivity.this, this::updateDrafts);
} }
} }

View file

@ -14,9 +14,10 @@ package app.fedilab.android.mastodon.activities;
* You should have received a copy of the GNU General Public License along with Fedilab; if not, * You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentAccount;
import static app.fedilab.android.BaseMainActivity.instanceInfo; import static app.fedilab.android.BaseMainActivity.instanceInfo;
import android.annotation.SuppressLint;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
@ -26,27 +27,34 @@ import android.text.Html;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textfield.TextInputEditText;
import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects;
import app.fedilab.android.BaseMainActivity; import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.BuildConfig;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.databinding.AccountFeaturedHashtagItemBinding;
import app.fedilab.android.databinding.AccountFieldItemBinding; import app.fedilab.android.databinding.AccountFieldItemBinding;
import app.fedilab.android.databinding.ActivityEditProfileBinding; import app.fedilab.android.databinding.ActivityEditProfileBinding;
import app.fedilab.android.mastodon.client.entities.api.Account; import app.fedilab.android.mastodon.client.entities.api.Account;
import app.fedilab.android.mastodon.client.entities.api.FeaturedTag;
import app.fedilab.android.mastodon.client.entities.api.Field; import app.fedilab.android.mastodon.client.entities.api.Field;
import app.fedilab.android.mastodon.client.entities.api.Tag;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.exception.DBException; import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.helper.Helper; import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.MastodonHelper; import app.fedilab.android.mastodon.helper.MastodonHelper;
@ -59,7 +67,10 @@ public class EditProfileActivity extends BaseBarActivity {
public static final int PICK_MEDIA_HEADER = 5706; public static final int PICK_MEDIA_HEADER = 5706;
private ActivityEditProfileBinding binding; private ActivityEditProfileBinding binding;
private AccountsVM accountsVM; private AccountsVM accountsVM;
private static final int MAX_FIELDS = 4;
private static final int MAX_FEATURED_HASHTAGS = 10;
@SuppressLint("ClickableViewAccessibility")
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -70,62 +81,132 @@ public class EditProfileActivity extends BaseBarActivity {
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} }
new ViewModelProvider(EditProfileActivity.this).get(AccountsVM.class).getConnectedAccount(BaseMainActivity.currentInstance, BaseMainActivity.currentToken) binding.scrollContainer.setOnTouchListener((v, event) -> {
binding.bio.getParent().requestDisallowInterceptTouchEvent(false);
return false;
});
binding.bio.setOnTouchListener((v, event) -> {
binding.bio.getParent().requestDisallowInterceptTouchEvent(true);
return false;
});
accountsVM = new ViewModelProvider(EditProfileActivity.this).get(AccountsVM.class);
accountsVM.getConnectedAccount(BaseMainActivity.currentInstance, BaseMainActivity.currentToken)
.observe(EditProfileActivity.this, account -> { .observe(EditProfileActivity.this, account -> {
if (account != null) { if (account != null) {
currentAccount.mastodon_account = account; Helper.setCurrentAccountMastodonAccount(EditProfileActivity.this, account);
initializeView(); initializeView();
} else { } else {
Helper.sendToastMessage(getApplication(), Helper.RECEIVE_TOAST_TYPE_ERROR, getString(R.string.toast_error)); Helper.sendToastMessage(getApplication(), Helper.RECEIVE_TOAST_TYPE_ERROR, getString(R.string.toast_error));
} }
}); });
}
}
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
private void initializeView() { private void initializeView() {
//Hydrate values //Hydrate values
MastodonHelper.loadProfileMediaMastodon(EditProfileActivity.this, binding.bannerPp, currentAccount.mastodon_account, MastodonHelper.MediaAccountType.HEADER); MastodonHelper.loadProfileMediaMastodon(EditProfileActivity.this, binding.bannerPp, Helper.getCurrentAccount(EditProfileActivity.this).mastodon_account, MastodonHelper.MediaAccountType.HEADER);
MastodonHelper.loadPPMastodon(binding.accountPp, currentAccount.mastodon_account); MastodonHelper.loadPPMastodon(binding.accountPp, Helper.getCurrentAccount(EditProfileActivity.this).mastodon_account);
binding.displayName.setText(currentAccount.mastodon_account.display_name); binding.displayName.setText(Helper.getCurrentAccount(EditProfileActivity.this).mastodon_account.display_name);
binding.acct.setText(String.format(Locale.getDefault(), "%s@%s", currentAccount.mastodon_account.acct, BaseMainActivity.currentInstance)); binding.acct.setText(String.format(Locale.getDefault(), "%s@%s", Helper.getCurrentAccount(EditProfileActivity.this).mastodon_account.acct, BaseMainActivity.currentInstance));
String bio; String bio;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
bio = Html.fromHtml(currentAccount.mastodon_account.note, Html.FROM_HTML_MODE_LEGACY).toString(); bio = Html.fromHtml(Helper.getCurrentAccount(EditProfileActivity.this).mastodon_account.note, Html.FROM_HTML_MODE_LEGACY).toString();
else else
bio = Html.fromHtml(currentAccount.mastodon_account.note).toString(); bio = Html.fromHtml(Helper.getCurrentAccount(EditProfileActivity.this).mastodon_account.note).toString();
binding.bio.setText(bio); binding.bio.setText(bio);
if (currentAccount.mastodon_account.source != null) {
binding.sensitive.setChecked(currentAccount.mastodon_account.source.sensitive); accountsVM.getFeaturedTagsSuggestions(BaseMainActivity.currentInstance, BaseMainActivity.currentToken).observe(this, featuredTags -> {
switch (currentAccount.mastodon_account.source.privacy) { StringBuilder text = new StringBuilder(getString(R.string.no_feature_hashtag_suggestion));
case "public": if(featuredTags != null && !featuredTags.isEmpty()) {
binding.visibilityPublic.setChecked(true); text = new StringBuilder();
break; for (Tag tag : featuredTags) {
case "unlisted": text.append(String.format("#%s ", tag.name));
binding.visibilityUnlisted.setChecked(true); }
break; }
case "private": binding.featuredHashtagsSuggestions.setText(text);
binding.visibilityPrivate.setChecked(true); });
break; accountsVM.getAccountFeaturedTags(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, Helper.getCurrentAccount(EditProfileActivity.this).mastodon_account.id).observe(this, featuredTags -> {
case "direct": if(featuredTags != null && !featuredTags.isEmpty()) {
binding.visibilityDirect.setChecked(true); for (FeaturedTag featuredTag : featuredTags) {
break; AccountFeaturedHashtagItemBinding featuredHashtagItemBinding = AccountFeaturedHashtagItemBinding.inflate(getLayoutInflater());
featuredHashtagItemBinding.name.setText(featuredTag.name);
featuredHashtagItemBinding.remove.setOnClickListener(v -> {
AlertDialog.Builder deleteConfirm = new MaterialAlertDialogBuilder(EditProfileActivity.this);
deleteConfirm.setTitle(getString(R.string.delete_featured_hashtag));
deleteConfirm.setMessage(getString(R.string.delete_featured_hashtag_confirm));
deleteConfirm.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
deleteConfirm.setPositiveButton(R.string.delete, (dialog, which) -> {
((ViewGroup)featuredHashtagItemBinding.getRoot().getParent()).removeView(featuredHashtagItemBinding.getRoot());
if (binding.featuredHashtagsContainer.getChildCount() >= MAX_FEATURED_HASHTAGS) {
binding.addFeaturedHashtags.setVisibility(View.GONE);
} else {
binding.addFeaturedHashtags.setVisibility(View.VISIBLE);
}
dialog.dismiss();
});
deleteConfirm.create().show();
});
binding.featuredHashtagsContainer.addView(featuredHashtagItemBinding.getRoot());
}
}
binding.addFeaturedHashtags.setOnClickListener(view -> {
AccountFeaturedHashtagItemBinding featuredHashtagItemBinding = AccountFeaturedHashtagItemBinding.inflate(getLayoutInflater());
featuredHashtagItemBinding.remove.setOnClickListener(v -> {
AlertDialog.Builder deleteConfirm = new MaterialAlertDialogBuilder(EditProfileActivity.this);
deleteConfirm.setTitle(getString(R.string.delete_featured_hashtag));
deleteConfirm.setMessage(getString(R.string.delete_featured_hashtag_confirm));
deleteConfirm.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
deleteConfirm.setPositiveButton(R.string.delete, (dialog, which) -> {
((ViewGroup)featuredHashtagItemBinding.getRoot().getParent()).removeView(featuredHashtagItemBinding.getRoot());
if (binding.featuredHashtagsContainer.getChildCount() >= MAX_FEATURED_HASHTAGS) {
binding.addFeaturedHashtags.setVisibility(View.GONE);
} else {
binding.addFeaturedHashtags.setVisibility(View.VISIBLE);
}
dialog.dismiss();
});
deleteConfirm.create().show();
});
binding.featuredHashtagsContainer.addView(featuredHashtagItemBinding.getRoot());
if (binding.featuredHashtagsContainer.getChildCount() >= MAX_FEATURED_HASHTAGS) {
binding.addFeaturedHashtags.setVisibility(View.GONE);
} else {
binding.addFeaturedHashtags.setVisibility(View.VISIBLE);
}
});
});
if (Helper.getCurrentAccount(EditProfileActivity.this).mastodon_account.source != null) {
binding.sensitive.setChecked(Helper.getCurrentAccount(EditProfileActivity.this).mastodon_account.source.sensitive);
switch (Helper.getCurrentAccount(EditProfileActivity.this).mastodon_account.source.privacy) {
case "public" -> binding.visibilityPublic.setChecked(true);
case "unlisted" -> binding.visibilityUnlisted.setChecked(true);
case "private" -> binding.visibilityPrivate.setChecked(true);
case "direct" -> binding.visibilityDirect.setChecked(true);
} }
} else { } else {
binding.sensitive.setVisibility(View.GONE); binding.sensitive.setVisibility(View.GONE);
binding.visibilityGroup.setVisibility(View.GONE); binding.visibilityGroup.setVisibility(View.GONE);
} }
binding.bot.setChecked(currentAccount.mastodon_account.bot); binding.bot.setChecked(Helper.getCurrentAccount(EditProfileActivity.this).mastodon_account.bot);
binding.discoverable.setChecked(currentAccount.mastodon_account.discoverable); binding.discoverable.setChecked(Helper.getCurrentAccount(EditProfileActivity.this).mastodon_account.discoverable);
if (currentAccount.mastodon_account.locked) { if (Helper.getCurrentAccount(EditProfileActivity.this).mastodon_account.locked) {
binding.locked.setChecked(true); binding.locked.setChecked(true);
} else { } else {
binding.unlocked.setChecked(true); binding.unlocked.setChecked(true);
} }
List<Field> fields = currentAccount.mastodon_account.fields; List<Field> fields = Helper.getCurrentAccount(EditProfileActivity.this).mastodon_account.fields;
if (fields != null && fields.size() > 0) { if (fields != null && !fields.isEmpty()) {
for (Field field : fields) { for (Field field : fields) {
AccountFieldItemBinding fieldItemBinding = AccountFieldItemBinding.inflate(getLayoutInflater()); AccountFieldItemBinding fieldItemBinding = AccountFieldItemBinding.inflate(getLayoutInflater());
fieldItemBinding.name.setText(field.name); fieldItemBinding.name.setText(field.name);
@ -141,11 +222,11 @@ public class EditProfileActivity extends BaseBarActivity {
deleteConfirm.setMessage(getString(R.string.delete_field_confirm)); deleteConfirm.setMessage(getString(R.string.delete_field_confirm));
deleteConfirm.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); deleteConfirm.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
deleteConfirm.setPositiveButton(R.string.delete, (dialog, which) -> { deleteConfirm.setPositiveButton(R.string.delete, (dialog, which) -> {
binding.fieldsContainer.removeView(fieldItemBinding.getRoot()); ((ViewGroup)fieldItemBinding.getRoot().getParent()).removeView(fieldItemBinding.getRoot());
if (binding.fieldsContainer.getChildCount() < 4) { if (binding.fieldsContainer.getChildCount() >= MAX_FIELDS) {
binding.fieldsContainer.setVisibility(View.VISIBLE); binding.addField.setVisibility(View.GONE);
} else { } else {
binding.fieldsContainer.setVisibility(View.GONE); binding.addField.setVisibility(View.VISIBLE);
} }
dialog.dismiss(); dialog.dismiss();
}); });
@ -153,7 +234,11 @@ public class EditProfileActivity extends BaseBarActivity {
}); });
binding.fieldsContainer.addView(fieldItemBinding.getRoot()); binding.fieldsContainer.addView(fieldItemBinding.getRoot());
} }
if (binding.fieldsContainer.getChildCount() >= MAX_FIELDS) {
binding.addField.setVisibility(View.GONE);
} else {
binding.addField.setVisibility(View.VISIBLE);
}
} }
binding.addField.setOnClickListener(view -> { binding.addField.setOnClickListener(view -> {
AccountFieldItemBinding fieldItemBinding = AccountFieldItemBinding.inflate(getLayoutInflater()); AccountFieldItemBinding fieldItemBinding = AccountFieldItemBinding.inflate(getLayoutInflater());
@ -163,25 +248,24 @@ public class EditProfileActivity extends BaseBarActivity {
deleteConfirm.setMessage(getString(R.string.delete_field_confirm)); deleteConfirm.setMessage(getString(R.string.delete_field_confirm));
deleteConfirm.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); deleteConfirm.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
deleteConfirm.setPositiveButton(R.string.delete, (dialog, which) -> { deleteConfirm.setPositiveButton(R.string.delete, (dialog, which) -> {
binding.fieldsContainer.removeView(fieldItemBinding.getRoot()); ((ViewGroup)fieldItemBinding.getRoot().getParent()).removeView(fieldItemBinding.getRoot());
if (binding.fieldsContainer.getChildCount() < 4) { if (binding.fieldsContainer.getChildCount() >= MAX_FIELDS) {
binding.fieldsContainer.setVisibility(View.VISIBLE); binding.addField.setVisibility(View.GONE);
} else { } else {
binding.fieldsContainer.setVisibility(View.GONE); binding.addField.setVisibility(View.VISIBLE);
} }
dialog.dismiss(); dialog.dismiss();
}); });
deleteConfirm.create().show(); deleteConfirm.create().show();
}); });
binding.fieldsContainer.addView(fieldItemBinding.getRoot()); binding.fieldsContainer.addView(fieldItemBinding.getRoot());
if (binding.fieldsContainer.getChildCount() >= 4) {
if (binding.fieldsContainer.getChildCount() >= MAX_FIELDS) {
binding.addField.setVisibility(View.GONE); binding.addField.setVisibility(View.GONE);
} else {
binding.addField.setVisibility(View.VISIBLE);
} }
}); });
//Actions with the activity
accountsVM = new ViewModelProvider(EditProfileActivity.this).get(AccountsVM.class);
binding.headerSelect.setOnClickListener(view -> startActivityForResult(prepareIntent(), EditProfileActivity.PICK_MEDIA_HEADER)); binding.headerSelect.setOnClickListener(view -> startActivityForResult(prepareIntent(), EditProfileActivity.PICK_MEDIA_HEADER));
binding.avatarSelect.setOnClickListener(view -> startActivityForResult(prepareIntent(), EditProfileActivity.PICK_MEDIA_AVATAR)); binding.avatarSelect.setOnClickListener(view -> startActivityForResult(prepareIntent(), EditProfileActivity.PICK_MEDIA_AVATAR));
@ -204,11 +288,11 @@ public class EditProfileActivity extends BaseBarActivity {
if (account != null) { if (account != null) {
sendBroadCast(account); sendBroadCast(account);
binding.avatarProgress.setVisibility(View.GONE); binding.avatarProgress.setVisibility(View.GONE);
currentAccount.mastodon_account = account; Helper.setCurrentAccountMastodonAccount(EditProfileActivity.this, account);
Helper.recreateMainActivity(EditProfileActivity.this); Helper.recreateMainActivity(EditProfileActivity.this);
new Thread(() -> { new Thread(() -> {
try { try {
new app.fedilab.android.mastodon.client.entities.app.Account(EditProfileActivity.this).insertOrUpdate(currentAccount); new app.fedilab.android.mastodon.client.entities.app.Account(EditProfileActivity.this).insertOrUpdate(Helper.getCurrentAccount(EditProfileActivity.this));
} catch (DBException e) { } catch (DBException e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -232,10 +316,10 @@ public class EditProfileActivity extends BaseBarActivity {
if (account != null) { if (account != null) {
sendBroadCast(account); sendBroadCast(account);
binding.headerProgress.setVisibility(View.GONE); binding.headerProgress.setVisibility(View.GONE);
currentAccount.mastodon_account = account; Helper.setCurrentAccountMastodonAccount(EditProfileActivity.this, account);
new Thread(() -> { new Thread(() -> {
try { try {
new app.fedilab.android.mastodon.client.entities.app.Account(EditProfileActivity.this).insertOrUpdate(currentAccount); new app.fedilab.android.mastodon.client.entities.app.Account(EditProfileActivity.this).insertOrUpdate(Helper.getCurrentAccount(EditProfileActivity.this));
} catch (DBException e) { } catch (DBException e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -249,12 +333,18 @@ public class EditProfileActivity extends BaseBarActivity {
} }
private void sendBroadCast(Account account) { private void sendBroadCast(Account account) {
Bundle b = new Bundle(); Bundle args = new Bundle();
b.putBoolean(Helper.RECEIVE_REDRAW_PROFILE, true); args.putBoolean(Helper.RECEIVE_REDRAW_PROFILE, true);
b.putSerializable(Helper.ARG_ACCOUNT, account); args.putSerializable(Helper.ARG_ACCOUNT, account);
Intent intentBD = new Intent(Helper.BROADCAST_DATA); new CachedBundle(EditProfileActivity.this).insertBundle(args, Helper.getCurrentAccount(EditProfileActivity.this), bundleId -> {
intentBD.putExtras(b); Bundle bundle = new Bundle();
LocalBroadcastManager.getInstance(EditProfileActivity.this).sendBroadcast(intentBD); bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
Intent intentBD = new Intent(Helper.BROADCAST_DATA);
intentBD.putExtras(bundle);
intentBD.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intentBD);
});
} }
private Intent prepareIntent() { private Intent prepareIntent() {
@ -264,7 +354,7 @@ public class EditProfileActivity extends BaseBarActivity {
intent.setType("*/*"); intent.setType("*/*");
String[] mimetypes; String[] mimetypes;
long max_size = -1; long max_size = -1;
if (instanceInfo.getMimeTypeImage().size() > 0) { if (!instanceInfo.getMimeTypeImage().isEmpty()) {
mimetypes = instanceInfo.getMimeTypeImage().toArray(new String[0]); mimetypes = instanceInfo.getMimeTypeImage().toArray(new String[0]);
max_size = instanceInfo.configuration.media_attachments.image_size_limit; max_size = instanceInfo.configuration.media_attachments.image_size_limit;
} else { } else {
@ -300,13 +390,23 @@ public class EditProfileActivity extends BaseBarActivity {
LinkedHashMap<Integer, Field.FieldParams> fields = new LinkedHashMap<>(); LinkedHashMap<Integer, Field.FieldParams> fields = new LinkedHashMap<>();
for (int i = 0; i < binding.fieldsContainer.getChildCount(); i++) { for (int i = 0; i < binding.fieldsContainer.getChildCount(); i++) {
Field.FieldParams field = new Field.FieldParams(); Field.FieldParams field = new Field.FieldParams();
field.name = ((TextInputEditText) binding.fieldsContainer.getChildAt(i).findViewById(R.id.name)).getText().toString().trim(); field.name = Objects.requireNonNull(((TextInputEditText) binding.fieldsContainer.getChildAt(i).findViewById(R.id.name)).getText()).toString().trim();
field.value = ((TextInputEditText) binding.fieldsContainer.getChildAt(i).findViewById(R.id.value)).getText().toString().trim(); field.value = Objects.requireNonNull(((TextInputEditText) binding.fieldsContainer.getChildAt(i).findViewById(R.id.value)).getText()).toString().trim();
fields.put(i, field); fields.put(i, field);
} }
return fields; return fields;
} }
List<String> getFeaturedHashtags() {
List<String> featuredHashtags = new ArrayList<>();
for (int i = 0; i < binding.featuredHashtagsContainer.getChildCount(); i++) {
String name = Objects.requireNonNull(((TextInputEditText) binding.featuredHashtagsContainer.getChildAt(i).findViewById(R.id.name)).getText()).toString().trim();
featuredHashtags.add(name);
}
return featuredHashtags;
}
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) { if (item.getItemId() == android.R.id.home) {
@ -316,20 +416,21 @@ public class EditProfileActivity extends BaseBarActivity {
accountsVM.updateCredentials(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, accountsVM.updateCredentials(BaseMainActivity.currentInstance, BaseMainActivity.currentToken,
binding.discoverable.isChecked(), binding.discoverable.isChecked(),
binding.bot.isChecked(), binding.bot.isChecked(),
binding.displayName.getText().toString().trim(), Objects.requireNonNull(binding.displayName.getText()).toString().trim(),
binding.bio.getText().toString(), Objects.requireNonNull(binding.bio.getText()).toString(),
binding.locked.isChecked(), binding.locked.isChecked(),
getPrivacy(), getPrivacy(),
binding.sensitive.isChecked(), binding.sensitive.isChecked(),
null, null,
getFields() getFields(),
getFeaturedHashtags()
) )
.observe(EditProfileActivity.this, account -> { .observe(EditProfileActivity.this, account -> {
if (account != null) { if (account != null) {
currentAccount.mastodon_account = account; Helper.setCurrentAccountMastodonAccount(EditProfileActivity.this, account);
new Thread(() -> { new Thread(() -> {
try { try {
new app.fedilab.android.mastodon.client.entities.app.Account(EditProfileActivity.this).insertOrUpdate(currentAccount); new app.fedilab.android.mastodon.client.entities.app.Account(EditProfileActivity.this).insertOrUpdate(Helper.getCurrentAccount(EditProfileActivity.this));
sendBroadCast(account); sendBroadCast(account);
} catch (DBException e) { } catch (DBException e) {
e.printStackTrace(); e.printStackTrace();

View file

@ -75,27 +75,13 @@ public class FilterActivity extends BaseBarActivity implements FilterAdapter.Del
@Override @Override
public void onItemSelected(AdapterView<?> parent1, View view, int position1, long id) { public void onItemSelected(AdapterView<?> parent1, View view, int position1, long id) {
switch (position1) { switch (position1) {
case 0: case 0 -> expire[0] = -1;
expire[0] = -1; case 1 -> expire[0] = 1800;
break; case 2 -> expire[0] = 3600;
case 1: case 3 -> expire[0] = 21600;
expire[0] = 1800; case 4 -> expire[0] = 43200;
break; case 5 -> expire[0] = 86400;
case 2: case 6 -> expire[0] = 604800;
expire[0] = 3600;
break;
case 3:
expire[0] = 21600;
break;
case 4:
expire[0] = 43200;
break;
case 5:
expire[0] = 86400;
break;
case 6:
expire[0] = 604800;
break;
} }
} }
@ -128,21 +114,12 @@ public class FilterActivity extends BaseBarActivity implements FilterAdapter.Del
if (filter.context != null) { if (filter.context != null) {
for (String val : filter.context) { for (String val : filter.context) {
switch (val) { switch (val) {
case "home": case "home" -> popupAddFilterBinding.contextHome.setChecked(true);
popupAddFilterBinding.contextHome.setChecked(true); case "public" -> popupAddFilterBinding.contextPublic.setChecked(true);
break; case "notifications" ->
case "public": popupAddFilterBinding.contextNotification.setChecked(true);
popupAddFilterBinding.contextPublic.setChecked(true); case "thread" -> popupAddFilterBinding.contextConversation.setChecked(true);
break; case "account" -> popupAddFilterBinding.contextProfiles.setChecked(true);
case "notifications":
popupAddFilterBinding.contextNotification.setChecked(true);
break;
case "thread":
popupAddFilterBinding.contextConversation.setChecked(true);
break;
case "account":
popupAddFilterBinding.contextProfiles.setChecked(true);
break;
} }
} }
} }
@ -193,7 +170,7 @@ public class FilterActivity extends BaseBarActivity implements FilterAdapter.Del
canBeSent = false; canBeSent = false;
} }
} }
if (popupAddFilterBinding.addTitle.getText().toString().trim().isEmpty()) { if (Objects.requireNonNull(popupAddFilterBinding.addTitle.getText()).toString().trim().isEmpty()) {
popupAddFilterBinding.addTitle.setError(context.getString(R.string.cannot_be_empty)); popupAddFilterBinding.addTitle.setError(context.getString(R.string.cannot_be_empty));
canBeSent = false; canBeSent = false;
} }

View file

@ -94,11 +94,8 @@ public class FollowRequestActivity extends BaseActivity {
binding.loadingNextAccounts.setVisibility(View.GONE); binding.loadingNextAccounts.setVisibility(View.GONE);
if (accountList != null && accounts != null && accounts.accounts != null && accounts.accounts.size() > 0) { if (accountList != null && accounts != null && accounts.accounts != null && accounts.accounts.size() > 0) {
int startId = 0;
//There are some statuses present in the timeline //There are some statuses present in the timeline
if (accountList.size() > 0) { int startId = accountList.size();
startId = accountList.size();
}
flagLoading = accounts.pagination.max_id == null; flagLoading = accounts.pagination.max_id == null;
accountList.addAll(accounts.accounts); accountList.addAll(accounts.accounts);
max_id = accounts.pagination.max_id; max_id = accounts.pagination.max_id;

View file

@ -21,6 +21,7 @@ import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
@ -31,6 +32,9 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import app.fedilab.android.BaseMainActivity; import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R; import app.fedilab.android.R;
@ -73,6 +77,7 @@ public class FollowedTagActivity extends BaseBarActivity implements FollowedTagA
.observe(FollowedTagActivity.this, tags -> { .observe(FollowedTagActivity.this, tags -> {
if (tags != null && tags.tags != null && tags.tags.size() > 0) { if (tags != null && tags.tags != null && tags.tags.size() > 0) {
tagList = new ArrayList<>(tags.tags); tagList = new ArrayList<>(tags.tags);
sortAsc(tagList);
followedTagAdapter = new FollowedTagAdapter(tagList); followedTagAdapter = new FollowedTagAdapter(tagList);
followedTagAdapter.actionOnTag = this; followedTagAdapter.actionOnTag = this;
binding.notContent.setVisibility(View.GONE); binding.notContent.setVisibility(View.GONE);
@ -82,12 +87,29 @@ public class FollowedTagActivity extends BaseBarActivity implements FollowedTagA
binding.notContent.setVisibility(View.VISIBLE); binding.notContent.setVisibility(View.VISIBLE);
} }
}); });
getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (canGoBack) {
canGoBack = false;
ThemeHelper.slideViewsToRight(binding.fragmentContainer, binding.recyclerView, () -> {
if (fragmentMastodonTimeline != null) {
fragmentMastodonTimeline.onDestroyView();
}
});
setTitle(R.string.followed_tags);
invalidateOptionsMenu();
} else {
finish();
}
}
});
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) { if (item.getItemId() == android.R.id.home) {
onBackPressed(); getOnBackPressedDispatcher().onBackPressed();
return true; return true;
} else if (item.getItemId() == R.id.action_unfollow && tag != null) { } else if (item.getItemId() == R.id.action_unfollow && tag != null) {
AlertDialog.Builder alt_bld = new MaterialAlertDialogBuilder(FollowedTagActivity.this); AlertDialog.Builder alt_bld = new MaterialAlertDialogBuilder(FollowedTagActivity.this);
@ -127,6 +149,11 @@ public class FollowedTagActivity extends BaseBarActivity implements FollowedTagA
dialogBuilder.setView(popupAddFollowedTagtBinding.getRoot()); dialogBuilder.setView(popupAddFollowedTagtBinding.getRoot());
popupAddFollowedTagtBinding.addTag.setFilters(new InputFilter[]{new InputFilter.LengthFilter(255)}); popupAddFollowedTagtBinding.addTag.setFilters(new InputFilter[]{new InputFilter.LengthFilter(255)});
dialogBuilder.setPositiveButton(R.string.validate, (dialog, id) -> { dialogBuilder.setPositiveButton(R.string.validate, (dialog, id) -> {
String name = Objects.requireNonNull(popupAddFollowedTagtBinding.addTag.getText()).toString().trim();
if (tagList != null && tagList.contains(new Tag(name))) {
Toasty.error(FollowedTagActivity.this, getString(R.string.tag_already_followed), Toasty.LENGTH_LONG).show();
return;
}
if (popupAddFollowedTagtBinding.addTag.getText() != null && popupAddFollowedTagtBinding.addTag.getText().toString().trim().length() > 0) { if (popupAddFollowedTagtBinding.addTag.getText() != null && popupAddFollowedTagtBinding.addTag.getText().toString().trim().length() > 0) {
tagVM.follow(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, popupAddFollowedTagtBinding.addTag.getText().toString().trim()) tagVM.follow(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, popupAddFollowedTagtBinding.addTag.getText().toString().trim())
.observe(FollowedTagActivity.this, newTag -> { .observe(FollowedTagActivity.this, newTag -> {
@ -143,8 +170,10 @@ public class FollowedTagActivity extends BaseBarActivity implements FollowedTagA
if (newTag != null) { if (newTag != null) {
tagList.add(0, newTag); tagList.add(0, newTag);
followedTagAdapter.notifyItemInserted(0); followedTagAdapter.notifyItemInserted(0);
sortAsc(tagList);
followedTagAdapter.notifyItemRangeChanged(0, tagList.size());
} else { } else {
Toasty.error(FollowedTagActivity.this, getString(R.string.toast_feature_not_supported), Toasty.LENGTH_LONG).show(); Toasty.error(FollowedTagActivity.this, getString(R.string.not_valid_tag_name), Toasty.LENGTH_LONG).show();
} }
}); });
dialog.dismiss(); dialog.dismiss();
@ -159,6 +188,9 @@ public class FollowedTagActivity extends BaseBarActivity implements FollowedTagA
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
private void sortAsc(List<Tag> tagList) {
Collections.sort(tagList, (obj1, obj2) -> obj1.name.compareToIgnoreCase(obj2.name));
}
@Override @Override
public boolean onCreateOptionsMenu(@NonNull Menu menu) { public boolean onCreateOptionsMenu(@NonNull Menu menu) {
@ -170,39 +202,25 @@ public class FollowedTagActivity extends BaseBarActivity implements FollowedTagA
return true; return true;
} }
@Override
public void onBackPressed() {
if (canGoBack) {
canGoBack = false;
ThemeHelper.slideViewsToRight(binding.fragmentContainer, binding.recyclerView, () -> {
if (fragmentMastodonTimeline != null) {
fragmentMastodonTimeline.onDestroyView();
}
});
setTitle(R.string.followed_tags);
invalidateOptionsMenu();
} else {
super.onBackPressed();
}
}
@Override @Override
public void click(Tag tag) { public void click(Tag tag) {
this.tag = tag; this.tag = tag;
canGoBack = true; canGoBack = true;
fragmentMastodonTimeline = new FragmentMastodonTimeline();
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_SEARCH_KEYWORD, tag.name);
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.TAG);
setTitle(tag.name);
fragmentMastodonTimeline.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragmentMastodonTimeline);
fragmentTransaction.commit();
invalidateOptionsMenu();
ThemeHelper.slideViewsToLeft(binding.recyclerView, binding.fragmentContainer, () -> { ThemeHelper.slideViewsToLeft(binding.recyclerView, binding.fragmentContainer, () -> {
fragmentMastodonTimeline = new FragmentMastodonTimeline();
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_SEARCH_KEYWORD, tag.name);
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.TAG);
setTitle(tag.name);
fragmentMastodonTimeline.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragmentMastodonTimeline);
fragmentTransaction.commit();
invalidateOptionsMenu();
}); });
} }
} }

View file

@ -15,8 +15,6 @@ package app.fedilab.android.mastodon.activities;
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentAccount;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
@ -28,7 +26,6 @@ import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
@ -36,11 +33,13 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import app.fedilab.android.BaseMainActivity; import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.BuildConfig;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity; import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.databinding.ActivityHashtagBinding; import app.fedilab.android.databinding.ActivityHashtagBinding;
import app.fedilab.android.mastodon.client.entities.api.Filter; import app.fedilab.android.mastodon.client.entities.api.Filter;
import app.fedilab.android.mastodon.client.entities.api.Status; import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.client.entities.app.Pinned; import app.fedilab.android.mastodon.client.entities.app.Pinned;
import app.fedilab.android.mastodon.client.entities.app.PinnedTimeline; import app.fedilab.android.mastodon.client.entities.app.PinnedTimeline;
import app.fedilab.android.mastodon.client.entities.app.StatusDraft; import app.fedilab.android.mastodon.client.entities.app.StatusDraft;
@ -69,20 +68,31 @@ public class HashTagActivity extends BaseActivity {
private Filter.KeywordsAttributes keyword; private Filter.KeywordsAttributes keyword;
private PinnedTimeline pinnedTimeline; private PinnedTimeline pinnedTimeline;
private Pinned pinned; private Pinned pinned;
private ActivityHashtagBinding binding;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
ActivityHashtagBinding binding = ActivityHashtagBinding.inflate(getLayoutInflater()); binding = ActivityHashtagBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot()); setContentView(binding.getRoot());
Bundle b = getIntent().getExtras(); Bundle args = getIntent().getExtras();
if (b != null) { if (args != null) {
tag = b.getString(Helper.ARG_SEARCH_KEYWORD, null); long bundleId = args.getLong(Helper.ARG_INTENT_ID, -1);
new CachedBundle(HashTagActivity.this).getBundle(bundleId, Helper.getCurrentAccount(HashTagActivity.this), this::initializeAfterBundle);
} else {
initializeAfterBundle(null);
} }
if (tag == null) }
private void initializeAfterBundle(Bundle bundle) {
if (bundle != null) {
tag = bundle.getString(Helper.ARG_SEARCH_KEYWORD, null);
}
if (tag == null) {
finish(); finish();
return;
}
pinnedTag = null; pinnedTag = null;
followedTag = null; followedTag = null;
mutedTag = null; mutedTag = null;
@ -146,10 +156,10 @@ public class HashTagActivity extends BaseActivity {
invalidateOptionsMenu(); invalidateOptionsMenu();
} }
Bundle bundle = new Bundle(); Bundle bundleFragment = new Bundle();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.TAG); bundleFragment.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.TAG);
bundle.putString(Helper.ARG_SEARCH_KEYWORD, tag); bundleFragment.putString(Helper.ARG_SEARCH_KEYWORD, tag);
Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_tags, new FragmentMastodonTimeline(), bundle, null, null); Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_tags, new FragmentMastodonTimeline(), bundleFragment, null, null);
binding.compose.setOnClickListener(v -> { binding.compose.setOnClickListener(v -> {
Intent intentToot = new Intent(HashTagActivity.this, ComposeActivity.class); Intent intentToot = new Intent(HashTagActivity.this, ComposeActivity.class);
StatusDraft statusDraft = new StatusDraft(); StatusDraft statusDraft = new StatusDraft();
@ -158,10 +168,14 @@ public class HashTagActivity extends BaseActivity {
List<Status> statuses = new ArrayList<>(); List<Status> statuses = new ArrayList<>();
statuses.add(status); statuses.add(status);
statusDraft.statusDraftList = statuses; statusDraft.statusDraftList = statuses;
Bundle _b = new Bundle(); Bundle args = new Bundle();
_b.putSerializable(Helper.ARG_STATUS_DRAFT, statusDraft); args.putSerializable(Helper.ARG_STATUS_DRAFT, statusDraft);
intentToot.putExtras(_b); new CachedBundle(HashTagActivity.this).insertBundle(args, Helper.getCurrentAccount(HashTagActivity.this), bundleId -> {
startActivity(intentToot); Bundle bundleCached = new Bundle();
bundleCached.putLong(Helper.ARG_INTENT_ID, bundleId);
intentToot.putExtras(bundleCached);
startActivity(intentToot);
});
}); });
} }
@ -188,18 +202,24 @@ public class HashTagActivity extends BaseActivity {
} }
pinnedTag = false; pinnedTag = false;
invalidateOptionsMenu(); invalidateOptionsMenu();
Bundle b = new Bundle(); Bundle args = new Bundle();
b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true); args.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Intent intentBD = new Intent(Helper.BROADCAST_DATA); Intent intentBD = new Intent(Helper.BROADCAST_DATA);
intentBD.putExtras(b); new CachedBundle(HashTagActivity.this).insertBundle(args, Helper.getCurrentAccount(HashTagActivity.this), bundleId -> {
LocalBroadcastManager.getInstance(HashTagActivity.this).sendBroadcast(intentBD); Bundle bundle = new Bundle();
dialog.dismiss(); bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentBD.putExtras(bundle);
intentBD.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intentBD);
dialog.dismiss();
});
}); });
unpinConfirm.show(); unpinConfirm.show();
} else { } else {
new Thread(() -> { new Thread(() -> {
try { try {
Pinned pinned = new Pinned(HashTagActivity.this).getPinned(currentAccount); Pinned pinned = new Pinned(HashTagActivity.this).getPinned(Helper.getCurrentAccount(HashTagActivity.this));
boolean canBeAdded = true; boolean canBeAdded = true;
boolean update = true; boolean update = true;
if (pinned == null) { if (pinned == null) {
@ -242,11 +262,16 @@ public class HashTagActivity extends BaseActivity {
} else { } else {
new Pinned(HashTagActivity.this).insertPinned(pinned); new Pinned(HashTagActivity.this).insertPinned(pinned);
} }
Bundle b = new Bundle(); Bundle args = new Bundle();
b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true); args.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Intent intentBD = new Intent(Helper.BROADCAST_DATA); Intent intentBD = new Intent(Helper.BROADCAST_DATA);
intentBD.putExtras(b); new CachedBundle(HashTagActivity.this).insertBundle(args, Helper.getCurrentAccount(HashTagActivity.this), bundleId -> {
LocalBroadcastManager.getInstance(HashTagActivity.this).sendBroadcast(intentBD); Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentBD.putExtras(bundle);
intentBD.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intentBD);
});
pinnedTag = true; pinnedTag = true;
invalidateOptionsMenu(); invalidateOptionsMenu();
} catch (DBException e) { } catch (DBException e) {

View file

@ -37,6 +37,8 @@ import androidx.preference.PreferenceManager;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.Objects;
import app.fedilab.android.BaseMainActivity; import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity; import app.fedilab.android.activities.MainActivity;
@ -50,7 +52,6 @@ import app.fedilab.android.mastodon.viewmodel.mastodon.InstancesVM;
public class InstanceActivity extends DialogFragment { public class InstanceActivity extends DialogFragment {
ActivityInstanceBinding binding; ActivityInstanceBinding binding;
private boolean applyMaxChar = false;
@NonNull @NonNull
@Override @Override
@ -78,19 +79,23 @@ public class InstanceActivity extends DialogFragment {
binding.about.setOnClickListener(v -> Helper.openBrowser(requireActivity(), "https://" + MainActivity.currentInstance + "/about")); binding.about.setOnClickListener(v -> Helper.openBrowser(requireActivity(), "https://" + MainActivity.currentInstance + "/about"));
binding.privacy.setOnClickListener(v -> Helper.openBrowser(requireActivity(), "https://" + MainActivity.currentInstance + "/privacy-policy")); binding.privacy.setOnClickListener(v -> Helper.openBrowser(requireActivity(), "https://" + MainActivity.currentInstance + "/privacy-policy"));
int maxCharCustom = sharedpreferences.getInt(getString(R.string.SET_MAX_INSTANCE_CHAR) + MainActivity.currentInstance, -1);
if (maxCharCustom != -1) {
binding.maxChar.setText(String.valueOf(maxCharCustom));
}
binding.close.setOnClickListener(view -> { binding.close.setOnClickListener(view -> {
if (applyMaxChar) { String max_char = Objects.requireNonNull(binding.maxChar.getText()).toString();
String max_char = binding.maxChar.getText().toString(); SharedPreferences.Editor editor = sharedpreferences.edit();
if (!max_char.isEmpty()) {
SharedPreferences.Editor editor = sharedpreferences.edit(); try {
if (!max_char.isEmpty()) { editor.putInt(getString(R.string.SET_MAX_INSTANCE_CHAR) + MainActivity.currentInstance, Integer.parseInt(max_char));
try { editor.apply();
editor.putInt(getString(R.string.SET_MAX_INSTANCE_CHAR) + MainActivity.currentInstance, Integer.parseInt(max_char)); } catch (Exception ignored) {
}
} else {
editor.putInt(getString(R.string.SET_MAX_INSTANCE_CHAR) + MainActivity.currentInstance, -1);
editor.apply(); editor.apply();
} catch (Exception ignored) {
} }
}
}
requireDialog().dismiss(); requireDialog().dismiss();
} }
@ -107,8 +112,6 @@ public class InstanceActivity extends DialogFragment {
if (val != -1) { if (val != -1) {
binding.maxChar.setText(String.valueOf(val)); binding.maxChar.setText(String.valueOf(val));
} }
applyMaxChar = true;
} else { } else {
Instance instance = instanceInfo.info; Instance instance = instanceInfo.info;

View file

@ -79,34 +79,34 @@ class InstanceHealthActivity : DialogFragment() {
} }
binding.name.text = instance.name binding.name.text = instance.name
if (instance.up) { if (instance.up) {
binding.up.setText(app.fedilab.android.R.string.is_up) binding.up.setText(R.string.is_up)
binding.up.setTextColor( binding.up.setTextColor(
ThemeHelper.getAttColor( ThemeHelper.getAttColor(
requireContext(), requireContext(),
app.fedilab.android.R.attr.colorPrimary R.attr.colorPrimary
) )
) )
} else { } else {
binding.up.setText(app.fedilab.android.R.string.is_down) binding.up.setText(R.string.is_down)
binding.up.setTextColor( binding.up.setTextColor(
ThemeHelper.getAttColor( ThemeHelper.getAttColor(
requireContext(), requireContext(),
app.fedilab.android.R.attr.colorError R.attr.colorError
) )
) )
} }
binding.uptime.text = getString( binding.uptime.text = getString(
app.fedilab.android.R.string.instance_health_uptime, R.string.instance_health_uptime,
instance.uptime * 100 instance.uptime * 100
) )
if (instance.checked_at != null) if (instance.checked_at != null)
binding.checkedAt.text = binding.checkedAt.text =
getString( getString(
app.fedilab.android.R.string.instance_health_checkedat, R.string.instance_health_checkedat,
Helper.dateToString(instance.checked_at) Helper.dateToString(instance.checked_at)
) )
binding.values.text = getString( binding.values.text = getString(
app.fedilab.android.R.string.instance_health_indication, R.string.instance_health_indication,
instance.version, instance.version,
Helper.withSuffix(instance.active_users.toLong()), Helper.withSuffix(instance.active_users.toLong()),
Helper.withSuffix(instance.statuses.toLong()) Helper.withSuffix(instance.statuses.toLong())

View file

@ -15,6 +15,7 @@ package app.fedilab.android.mastodon.activities;
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import android.annotation.SuppressLint;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
@ -25,12 +26,12 @@ import android.view.MenuItem;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -39,15 +40,17 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
import app.fedilab.android.BaseMainActivity; import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.BuildConfig;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.databinding.ActivityListBinding; import app.fedilab.android.databinding.ActivityListBinding;
import app.fedilab.android.databinding.PopupAddListBinding; import app.fedilab.android.databinding.PopupAddListBinding;
import app.fedilab.android.databinding.PopupManageAccountsListBinding; import app.fedilab.android.databinding.PopupManageAccountsListBinding;
import app.fedilab.android.mastodon.client.entities.api.Account; import app.fedilab.android.mastodon.client.entities.api.Account;
import app.fedilab.android.mastodon.client.entities.api.MastodonList; import app.fedilab.android.mastodon.client.entities.api.MastodonList;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.client.entities.app.Pinned; import app.fedilab.android.mastodon.client.entities.app.Pinned;
import app.fedilab.android.mastodon.client.entities.app.PinnedTimeline; import app.fedilab.android.mastodon.client.entities.app.PinnedTimeline;
import app.fedilab.android.mastodon.client.entities.app.Timeline; import app.fedilab.android.mastodon.client.entities.app.Timeline;
@ -126,6 +129,24 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
} }
}); });
}); });
getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (canGoBack) {
canGoBack = false;
ThemeHelper.slideViewsToRight(binding.fragmentContainer, binding.recyclerView, () -> {
if (fragmentMastodonTimeline != null) {
fragmentMastodonTimeline.onDestroyView();
}
});
setTitle(R.string.action_lists);
invalidateOptionsMenu();
} else {
finish();
}
}
});
} }
@ -141,10 +162,11 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
invalidateOptionsMenu(); invalidateOptionsMenu();
} }
@SuppressLint("ClickableViewAccessibility")
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) { if (item.getItemId() == android.R.id.home) {
onBackPressed(); getOnBackPressedDispatcher().onBackPressed();
return true; return true;
} else if (item.getItemId() == R.id.action_user_mute_home) { } else if (item.getItemId() == R.id.action_user_mute_home) {
AlertDialog.Builder dialogBuilder = new MaterialAlertDialogBuilder(MastodonListActivity.this); AlertDialog.Builder dialogBuilder = new MaterialAlertDialogBuilder(MastodonListActivity.this);
@ -153,7 +175,7 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
timelinesVM.getAccountsInList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, mastodonList.id, null, null, 0) timelinesVM.getAccountsInList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, mastodonList.id, null, null, 0)
.observe(MastodonListActivity.this, accounts -> { .observe(MastodonListActivity.this, accounts -> {
if (accounts != null && accounts.size() > 0) { if (accounts != null && accounts.size() > 0) {
accountsVM.muteAccountsHome(MainActivity.currentAccount, accounts); accountsVM.muteAccountsHome(Helper.getCurrentAccount(MastodonListActivity.this), accounts);
} }
}); });
dialog.dismiss(); dialog.dismiss();
@ -285,12 +307,17 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
} else { } else {
binding.notContent.setVisibility(View.GONE); binding.notContent.setVisibility(View.GONE);
} }
Bundle b = new Bundle(); Bundle args = new Bundle();
b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true); args.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Intent intentBD = new Intent(Helper.BROADCAST_DATA); Intent intentBD = new Intent(Helper.BROADCAST_DATA);
b.putSerializable(Helper.RECEIVE_MASTODON_LIST, mastodonListList); args.putSerializable(Helper.RECEIVE_MASTODON_LIST, mastodonListList);
intentBD.putExtras(b); new CachedBundle(MastodonListActivity.this).insertBundle(args, Helper.getCurrentAccount(MastodonListActivity.this), bundleId -> {
LocalBroadcastManager.getInstance(MastodonListActivity.this).sendBroadcast(intentBD); Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentBD.putExtras(bundle);
intentBD.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intentBD);
});
}); });
alt_bld.setNegativeButton(R.string.cancel, (dialog, id) -> dialog.dismiss()); alt_bld.setNegativeButton(R.string.cancel, (dialog, id) -> dialog.dismiss());
AlertDialog alert = alt_bld.create(); AlertDialog alert = alt_bld.create();
@ -320,12 +347,17 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
} else { } else {
Toasty.error(MastodonListActivity.this, getString(R.string.toast_error), Toasty.LENGTH_LONG).show(); Toasty.error(MastodonListActivity.this, getString(R.string.toast_error), Toasty.LENGTH_LONG).show();
} }
Bundle b = new Bundle(); Bundle args = new Bundle();
b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true); args.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Intent intentBD = new Intent(Helper.BROADCAST_DATA); Intent intentBD = new Intent(Helper.BROADCAST_DATA);
b.putSerializable(Helper.RECEIVE_MASTODON_LIST, mastodonListList); args.putSerializable(Helper.RECEIVE_MASTODON_LIST, mastodonListList);
intentBD.putExtras(b); new CachedBundle(MastodonListActivity.this).insertBundle(args, Helper.getCurrentAccount(MastodonListActivity.this), bundleId -> {
LocalBroadcastManager.getInstance(MastodonListActivity.this).sendBroadcast(intentBD); Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentBD.putExtras(bundle);
intentBD.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intentBD);
});
}); });
dialog.dismiss(); dialog.dismiss();
} else { } else {
@ -341,7 +373,7 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
dialogBuilder.setView(popupAddListBinding.getRoot()); dialogBuilder.setView(popupAddListBinding.getRoot());
popupAddListBinding.addList.setFilters(new InputFilter[]{new InputFilter.LengthFilter(255)}); popupAddListBinding.addList.setFilters(new InputFilter[]{new InputFilter.LengthFilter(255)});
popupAddListBinding.addList.setText(mastodonList.title); popupAddListBinding.addList.setText(mastodonList.title);
popupAddListBinding.addList.setSelection(popupAddListBinding.addList.getText().length()); popupAddListBinding.addList.setSelection(Objects.requireNonNull(popupAddListBinding.addList.getText()).length());
dialogBuilder.setPositiveButton(R.string.validate, (dialog, id) -> { dialogBuilder.setPositiveButton(R.string.validate, (dialog, id) -> {
if (popupAddListBinding.addList.getText() != null && popupAddListBinding.addList.getText().toString().trim().length() > 0) { if (popupAddListBinding.addList.getText() != null && popupAddListBinding.addList.getText().toString().trim().length() > 0) {
timelinesVM.updateList( timelinesVM.updateList(
@ -368,12 +400,18 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
new Thread(() -> { new Thread(() -> {
try { try {
new Pinned(MastodonListActivity.this).updatePinned(pinned); new Pinned(MastodonListActivity.this).updatePinned(pinned);
Bundle b = new Bundle(); Bundle args = new Bundle();
b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true); args.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Intent intentBD = new Intent(Helper.BROADCAST_DATA); Intent intentBD = new Intent(Helper.BROADCAST_DATA);
intentBD.putExtras(b); new CachedBundle(MastodonListActivity.this).insertBundle(args, Helper.getCurrentAccount(MastodonListActivity.this), bundleId -> {
LocalBroadcastManager.getInstance(MastodonListActivity.this).sendBroadcast(intentBD); Bundle bundle = new Bundle();
} catch (DBException e) { bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentBD.putExtras(bundle);
intentBD.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intentBD);
});
} catch (
DBException e) {
e.printStackTrace(); e.printStackTrace();
} }
}).start(); }).start();
@ -421,19 +459,20 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
this.mastodonList = mastodonList; this.mastodonList = mastodonList;
canGoBack = true; canGoBack = true;
fragmentMastodonTimeline = new FragmentMastodonTimeline();
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_LIST_ID, mastodonList.id);
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.LIST);
setTitle(mastodonList.title);
fragmentMastodonTimeline.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragmentMastodonTimeline);
fragmentTransaction.commit();
invalidateOptionsMenu();
ThemeHelper.slideViewsToLeft(binding.recyclerView, binding.fragmentContainer, () -> { ThemeHelper.slideViewsToLeft(binding.recyclerView, binding.fragmentContainer, () -> {
fragmentMastodonTimeline = new FragmentMastodonTimeline();
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_LIST_ID, mastodonList.id);
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.LIST);
setTitle(mastodonList.title);
fragmentMastodonTimeline.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragmentMastodonTimeline);
fragmentTransaction.commit();
invalidateOptionsMenu();
}); });
} }
@ -454,20 +493,4 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
} }
return true; return true;
} }
@Override
public void onBackPressed() {
if (canGoBack) {
canGoBack = false;
ThemeHelper.slideViewsToRight(binding.fragmentContainer, binding.recyclerView, () -> {
if (fragmentMastodonTimeline != null) {
fragmentMastodonTimeline.onDestroyView();
}
});
setTitle(R.string.action_lists);
invalidateOptionsMenu();
} else {
super.onBackPressed();
}
}
} }

View file

@ -14,6 +14,8 @@ package app.fedilab.android.mastodon.activities;
* You should have received a copy of the GNU General Public License along with Fedilab; if not, * You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static android.util.Patterns.WEB_URL;
import android.Manifest; import android.Manifest;
import android.app.DownloadManager; import android.app.DownloadManager;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
@ -21,17 +23,26 @@ import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
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.os.Handler; import android.os.Handler;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.Window; import android.view.Window;
import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -40,16 +51,19 @@ import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter; import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.preference.PreferenceManager;
import androidx.viewpager.widget.ViewPager; import androidx.viewpager.widget.ViewPager;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.regex.Matcher;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.databinding.ActivityMediaPagerBinding; import app.fedilab.android.databinding.ActivityMediaPagerBinding;
import app.fedilab.android.mastodon.client.entities.api.Attachment; import app.fedilab.android.mastodon.client.entities.api.Attachment;
import app.fedilab.android.mastodon.client.entities.api.Status; import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.helper.Helper; import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.MediaHelper; import app.fedilab.android.mastodon.helper.MediaHelper;
import app.fedilab.android.mastodon.helper.TranslateHelper; import app.fedilab.android.mastodon.helper.TranslateHelper;
@ -59,7 +73,7 @@ import app.fedilab.android.mastodon.ui.fragment.media.FragmentMediaProfile;
import es.dmoral.toasty.Toasty; import es.dmoral.toasty.Toasty;
public class MediaActivity extends BaseTransparentActivity implements OnDownloadInterface { public class MediaActivity extends BaseBarActivity implements OnDownloadInterface {
int flags; int flags;
private ArrayList<Attachment> attachments; private ArrayList<Attachment> attachments;
@ -69,10 +83,10 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
private final BroadcastReceiver onDownloadComplete = new BroadcastReceiver() { private final BroadcastReceiver onDownloadComplete = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
if (downloadID == id) { if (downloadID == id) {
DownloadManager manager = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE); DownloadManager manager = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE);
assert manager != null;
Uri uri = manager.getUriForDownloadedFile(downloadID); Uri uri = manager.getUriForDownloadedFile(downloadID);
Intent shareIntent = new Intent(Intent.ACTION_SEND); Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, uri); shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
@ -81,7 +95,8 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
shareIntent.setType(cR.getType(uri)); shareIntent.setType(cR.getType(uri));
try { try {
startActivity(shareIntent); startActivity(shareIntent);
} catch (Exception ignored) { } catch (Exception e) {
e.printStackTrace();
} }
} else { } else {
Toasty.error(context, context.getString(R.string.toast_error), Toasty.LENGTH_LONG).show(); Toasty.error(context, context.getString(R.string.toast_error), Toasty.LENGTH_LONG).show();
@ -103,6 +118,7 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY); getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
ActivityCompat.postponeEnterTransition(MediaActivity.this); ActivityCompat.postponeEnterTransition(MediaActivity.this);
binding = ActivityMediaPagerBinding.inflate(getLayoutInflater()); binding = ActivityMediaPagerBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot()); setContentView(binding.getRoot());
@ -110,13 +126,29 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
fullscreen = false; fullscreen = false;
flags = getWindow().getDecorView().getSystemUiVisibility(); flags = getWindow().getDecorView().getSystemUiVisibility();
Bundle b = getIntent().getExtras(); Bundle args = getIntent().getExtras();
if (b != null) { if (args != null) {
mediaPosition = b.getInt(Helper.ARG_MEDIA_POSITION, 1); long bundleId = args.getLong(Helper.ARG_INTENT_ID, -1);
attachments = (ArrayList<Attachment>) b.getSerializable(Helper.ARG_MEDIA_ARRAY); new CachedBundle(MediaActivity.this).getBundle(bundleId, Helper.getCurrentAccount(MediaActivity.this), this::initializeAfterBundle);
mediaFromProfile = b.getBoolean(Helper.ARG_MEDIA_ARRAY_PROFILE, false); } else {
status = (Status) b.getSerializable(Helper.ARG_STATUS); initializeAfterBundle(null);
} }
}
private void initializeAfterBundle(Bundle bundle) {
if (bundle != null) {
mediaPosition = bundle.getInt(Helper.ARG_MEDIA_POSITION, 1);
if(mediaPosition < 1 ) {
mediaPosition = 1;
}
attachments = (ArrayList<Attachment>) bundle.getSerializable(Helper.ARG_MEDIA_ARRAY);
mediaFromProfile = bundle.getBoolean(Helper.ARG_MEDIA_ARRAY_PROFILE, false);
status = (Status) bundle.getSerializable(Helper.ARG_STATUS);
}
if (mediaFromProfile && FragmentMediaProfile.mediaAttachmentProfile != null) { if (mediaFromProfile && FragmentMediaProfile.mediaAttachmentProfile != null) {
attachments = new ArrayList<>(); attachments = new ArrayList<>();
attachments.addAll(FragmentMediaProfile.mediaAttachmentProfile); attachments.addAll(FragmentMediaProfile.mediaAttachmentProfile);
@ -132,32 +164,45 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
} }
setTitle(""); setTitle("");
ScreenSlidePagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager()); ScreenSlidePagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
binding.mediaViewpager.setAdapter(mPagerAdapter); binding.mediaViewpager.setAdapter(mPagerAdapter);
binding.mediaViewpager.setSaveEnabled(false); binding.mediaViewpager.setSaveEnabled(false);
binding.mediaViewpager.setCurrentItem(mediaPosition - 1); binding.mediaViewpager.setCurrentItem(mediaPosition - 1);
registerReceiver(onDownloadComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); ContextCompat.registerReceiver(MediaActivity.this, onDownloadComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), ContextCompat.RECEIVER_EXPORTED);
String description = attachments.get(mediaPosition - 1).description; String description = attachments.get(mediaPosition - 1).description;
handler = new Handler(); handler = new Handler();
if (attachments.get(mediaPosition - 1).status != null) { if (attachments.get(mediaPosition - 1).status != null) {
binding.originalMessage.setOnClickListener(v -> { binding.originalMessage.setOnClickListener(v -> {
Intent intentContext = new Intent(MediaActivity.this, ContextActivity.class); Intent intentContext = new Intent(MediaActivity.this, ContextActivity.class);
intentContext.putExtra(Helper.ARG_STATUS, attachments.get(mediaPosition - 1).status); Bundle args = new Bundle();
intentContext.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); args.putSerializable(Helper.ARG_STATUS, attachments.get(mediaPosition - 1).status);
startActivity(intentContext); new CachedBundle(MediaActivity.this).insertBundle(args, Helper.getCurrentAccount(MediaActivity.this), bundleId -> {
Bundle bundleCached = new Bundle();
bundleCached.putLong(Helper.ARG_INTENT_ID, bundleId);
intentContext.putExtras(bundleCached);
intentContext.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intentContext);
});
}); });
} }
if (description != null && description.trim().length() > 0 && description.trim().compareTo("null") != 0) {
binding.mediaDescription.setMovementMethod(LinkMovementMethod.getInstance());
binding.mediaDescriptionTranslated.setMovementMethod(LinkMovementMethod.getInstance());
if (description != null && !description.trim().isEmpty() && description.trim().compareTo("null") != 0) {
binding.mediaDescription.setText(description); binding.mediaDescription.setText(description);
binding.translate.setOnClickListener(v -> { binding.translate.setOnClickListener(v -> {
String descriptionToTranslate = attachments.get(mediaPosition - 1).description; String descriptionToTranslate = attachments.get(mediaPosition - 1).description;
TranslateHelper.translate(MediaActivity.this, descriptionToTranslate, translated -> { TranslateHelper.translate(MediaActivity.this, descriptionToTranslate, status!=null?status.language:"en", translated -> {
if (translated != null) { if (translated != null) {
attachments.get(mediaPosition - 1).translation = translated; attachments.get(mediaPosition - 1).translation = translated;
binding.mediaDescriptionTranslated.setText(translated); binding.mediaDescriptionTranslated.setText(translated);
binding.mediaDescriptionTranslated.setVisibility(View.VISIBLE); binding.mediaDescriptionTranslated.setVisibility(View.VISIBLE);
binding.mediaDescription.setVisibility(View.GONE); binding.mediaDescription.setVisibility(View.GONE);
if (mCurrentFragment != null) {
mCurrentFragment.toggleController(false);
}
binding.translate.setVisibility(View.GONE);
} else { } else {
Toasty.error(MediaActivity.this, getString(R.string.toast_error_translate), Toast.LENGTH_LONG).show(); Toasty.error(MediaActivity.this, getString(R.string.toast_error_translate), Toast.LENGTH_LONG).show();
} }
@ -165,10 +210,16 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
}); });
if (attachments.get(mediaPosition - 1).translation != null) { if (attachments.get(mediaPosition - 1).translation != null) {
binding.mediaDescription.setVisibility(View.GONE); binding.mediaDescription.setVisibility(View.GONE);
if (mCurrentFragment != null) {
mCurrentFragment.toggleController(false);
}
binding.mediaDescriptionTranslated.setText(attachments.get(mediaPosition - 1).translation); binding.mediaDescriptionTranslated.setText(attachments.get(mediaPosition - 1).translation);
binding.mediaDescriptionTranslated.setVisibility(View.VISIBLE); binding.mediaDescriptionTranslated.setVisibility(View.VISIBLE);
} else { } else {
binding.mediaDescription.setVisibility(View.VISIBLE); binding.mediaDescription.setVisibility(View.VISIBLE);
if (mCurrentFragment != null) {
mCurrentFragment.toggleController(true);
}
binding.mediaDescriptionTranslated.setVisibility(View.GONE); binding.mediaDescriptionTranslated.setVisibility(View.GONE);
} }
} }
@ -182,22 +233,29 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
public void onPageSelected(int position) { public void onPageSelected(int position) {
mediaPosition = position; mediaPosition = position;
if(mediaPosition < 1 ) {
mediaPosition = 1;
}
String description = attachments.get(position).description; String description = attachments.get(position).description;
if (handler != null) { if (handler != null) {
handler.removeCallbacksAndMessages(null); handler.removeCallbacksAndMessages(null);
} }
handler = new Handler(); handler = new Handler();
if (description != null && description.trim().length() > 0 && description.trim().compareTo("null") != 0) { if (description != null && !description.trim().isEmpty() && description.trim().compareTo("null") != 0) {
binding.mediaDescription.setText(description); binding.mediaDescription.setText(linkify(MediaActivity.this, description), TextView.BufferType.SPANNABLE);
} }
binding.translate.setOnClickListener(v -> { binding.translate.setOnClickListener(v -> {
String descriptionToTranslate = attachments.get(position).description; String descriptionToTranslate = attachments.get(position).description;
TranslateHelper.translate(MediaActivity.this, descriptionToTranslate, translated -> { TranslateHelper.translate(MediaActivity.this, descriptionToTranslate, status!=null?status.language:"en", translated -> {
if (translated != null) { if (translated != null) {
attachments.get(position).translation = translated; attachments.get(position).translation = translated;
binding.mediaDescriptionTranslated.setText(translated); binding.mediaDescriptionTranslated.setText(translated);
binding.mediaDescriptionTranslated.setVisibility(View.VISIBLE); binding.mediaDescriptionTranslated.setVisibility(View.VISIBLE);
binding.mediaDescription.setVisibility(View.GONE); binding.mediaDescription.setVisibility(View.GONE);
if (mCurrentFragment != null) {
mCurrentFragment.toggleController(true);
}
binding.translate.setVisibility(View.GONE);
} else { } else {
Toasty.error(MediaActivity.this, getString(R.string.toast_error_translate), Toast.LENGTH_LONG).show(); Toasty.error(MediaActivity.this, getString(R.string.toast_error_translate), Toast.LENGTH_LONG).show();
} }
@ -206,14 +264,23 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
if (!fullscreen) { if (!fullscreen) {
if (attachments.get(position).translation != null) { if (attachments.get(position).translation != null) {
binding.mediaDescription.setVisibility(View.GONE); binding.mediaDescription.setVisibility(View.GONE);
if (mCurrentFragment != null) {
mCurrentFragment.toggleController(false);
}
binding.mediaDescriptionTranslated.setText(attachments.get(position).translation); binding.mediaDescriptionTranslated.setText(attachments.get(position).translation);
binding.mediaDescriptionTranslated.setVisibility(View.VISIBLE); binding.mediaDescriptionTranslated.setVisibility(View.VISIBLE);
} else { } else {
binding.mediaDescription.setVisibility(View.VISIBLE); binding.mediaDescription.setVisibility(View.VISIBLE);
if (mCurrentFragment != null) {
mCurrentFragment.toggleController(true);
}
binding.mediaDescriptionTranslated.setVisibility(View.GONE); binding.mediaDescriptionTranslated.setVisibility(View.GONE);
} }
} else { } else {
binding.mediaDescription.setVisibility(View.GONE); binding.mediaDescription.setVisibility(View.GONE);
if (mCurrentFragment != null) {
mCurrentFragment.toggleController(false);
}
binding.mediaDescriptionTranslated.setVisibility(View.GONE); binding.mediaDescriptionTranslated.setVisibility(View.GONE);
} }
} }
@ -221,6 +288,41 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
setFullscreen(true); setFullscreen(true);
} }
private Spannable linkify(Context context, String content) {
if (content == null) {
return new SpannableString("");
}
Matcher matcher = WEB_URL.matcher(content);
Spannable contentSpan = new SpannableString(content);
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
boolean underlineLinks = sharedpreferences.getBoolean(context.getString(R.string.SET_UNDERLINE_CLICKABLE), false);
while (matcher.find()) {
int matchStart = matcher.start();
int matchEnd = matcher.end();
String url = content.substring(matchStart, matchEnd);
if (matchStart >= 0 && matchEnd <= content.length() && matchEnd >= matchStart) {
contentSpan.setSpan(new ClickableSpan() {
@Override
public void onClick(@NonNull View textView) {
Helper.openBrowser(context, url);
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds);
if (!underlineLinks) {
ds.setUnderlineText(status != null && status.underlined);
}
}
}, matchStart, matchEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
}
return contentSpan;
}
@Override @Override
public boolean dispatchTouchEvent(MotionEvent event) { public boolean dispatchTouchEvent(MotionEvent event) {
try { try {
@ -246,7 +348,10 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) { if (item.getItemId() == android.R.id.home) {
finish(); finish();
ActivityCompat.finishAfterTransition(MediaActivity.this); try {
ActivityCompat.finishAfterTransition(MediaActivity.this);
} catch (Exception ignored) {
}
return true; return true;
} else if (item.getItemId() == R.id.action_save) { } else if (item.getItemId() == R.id.action_save) {
int position = binding.mediaViewpager.getCurrentItem(); int position = binding.mediaViewpager.getCurrentItem();
@ -328,7 +433,7 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
if (!fullscreen) { if (!fullscreen) {
String description = attachments.get(binding.mediaViewpager.getCurrentItem()).description; String description = attachments.get(binding.mediaViewpager.getCurrentItem()).description;
if (description != null && description.trim().length() > 0 && description.trim().compareTo("null") != 0) { if (description != null && description.trim().length() > 0 && description.trim().compareTo("null") != 0) {
binding.mediaDescription.setText(description); binding.mediaDescription.setText(linkify(MediaActivity.this, description), TextView.BufferType.SPANNABLE);
if (attachments.get(binding.mediaViewpager.getCurrentItem()).translation != null) { if (attachments.get(binding.mediaViewpager.getCurrentItem()).translation != null) {
binding.mediaDescription.setVisibility(View.GONE); binding.mediaDescription.setVisibility(View.GONE);
binding.mediaDescriptionTranslated.setText(attachments.get(binding.mediaViewpager.getCurrentItem()).translation); binding.mediaDescriptionTranslated.setText(attachments.get(binding.mediaViewpager.getCurrentItem()).translation);
@ -358,13 +463,16 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
@Override @Override
public void onDestroy() { public void onDestroy() {
unregisterReceiver(onDownloadComplete); try {
unregisterReceiver(onDownloadComplete);
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
super.onDestroy(); super.onDestroy();
} }
@Override @Override
public void onDownloaded(String saveFilePath, String downloadUrl, Error error) { public void onDownloaded(String saveFilePath, String downloadUrl, Error error) {
} }
@Override @Override
@ -395,14 +503,22 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
this.fullscreen = fullscreen; this.fullscreen = fullscreen;
if (!fullscreen) { if (!fullscreen) {
showSystemUI(); showSystemUI();
binding.descriptionContainer.setVisibility(View.VISIBLE);
binding.mediaDescription.setVisibility(View.VISIBLE); binding.mediaDescription.setVisibility(View.VISIBLE);
if (mCurrentFragment != null) {
mCurrentFragment.toggleController(true);
}
binding.translate.setVisibility(View.VISIBLE); binding.translate.setVisibility(View.VISIBLE);
if (mediaFromProfile) { if (mediaFromProfile) {
binding.originalMessage.setVisibility(View.VISIBLE); binding.originalMessage.setVisibility(View.VISIBLE);
} }
} else { } else {
hideSystemUI(); hideSystemUI();
binding.descriptionContainer.setVisibility(View.GONE);
binding.mediaDescription.setVisibility(View.GONE); binding.mediaDescription.setVisibility(View.GONE);
if (mCurrentFragment != null) {
mCurrentFragment.toggleController(false);
}
binding.translate.setVisibility(View.GONE); binding.translate.setVisibility(View.GONE);
binding.originalMessage.setVisibility(View.INVISIBLE); binding.originalMessage.setVisibility(View.INVISIBLE);
} }

View file

@ -23,7 +23,6 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import androidx.core.app.ActivityOptionsCompat;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import java.util.ArrayList; import java.util.ArrayList;
@ -34,6 +33,7 @@ import app.fedilab.android.R;
import app.fedilab.android.databinding.ActivityPartnershipBinding; import app.fedilab.android.databinding.ActivityPartnershipBinding;
import app.fedilab.android.mastodon.client.entities.api.Account; import app.fedilab.android.mastodon.client.entities.api.Account;
import app.fedilab.android.mastodon.client.entities.api.Status; import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.helper.CrossActionHelper; import app.fedilab.android.mastodon.helper.CrossActionHelper;
import app.fedilab.android.mastodon.helper.Helper; import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.MastodonHelper; import app.fedilab.android.mastodon.helper.MastodonHelper;
@ -78,12 +78,14 @@ public class PartnerShipActivity extends BaseBarActivity {
binding.accountUn.setText(account.acct); binding.accountUn.setText(account.acct);
binding.accountPp.setOnClickListener(v -> { binding.accountPp.setOnClickListener(v -> {
Intent intent = new Intent(PartnerShipActivity.this, ProfileActivity.class); Intent intent = new Intent(PartnerShipActivity.this, ProfileActivity.class);
Bundle b = new Bundle(); Bundle args = new Bundle();
b.putSerializable(Helper.ARG_ACCOUNT, account); args.putSerializable(Helper.ARG_ACCOUNT, account);
intent.putExtras(b); new CachedBundle(PartnerShipActivity.this).insertBundle(args, Helper.getCurrentAccount(PartnerShipActivity.this), bundleId -> {
ActivityOptionsCompat options = ActivityOptionsCompat Bundle bundle = new Bundle();
.makeSceneTransitionAnimation(PartnerShipActivity.this, binding.accountPp, getString(R.string.activity_porfile_pp)); bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
startActivity(intent, options.toBundle()); intent.putExtras(bundle);
startActivity(intent);
});
}); });
AccountsVM accountsVM = new ViewModelProvider(PartnerShipActivity.this).get(AccountsVM.class); AccountsVM accountsVM = new ViewModelProvider(PartnerShipActivity.this).get(AccountsVM.class);
List<String> ids = new ArrayList<>(); List<String> ids = new ArrayList<>();

View file

@ -15,7 +15,9 @@ package app.fedilab.android.mastodon.activities;
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentAccount;
import static app.fedilab.android.mastodon.helper.LogoHelper.getMainLogo;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ClipData; import android.content.ClipData;
@ -25,20 +27,28 @@ import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
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.os.Bundle; import android.os.Bundle;
import android.os.Environment;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.Spanned; import android.text.Spanned;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.text.style.UnderlineSpan; import android.text.style.UnderlineSpan;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.webkit.URLUtil;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
@ -49,11 +59,10 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.core.app.ActivityCompat; import androidx.appcompat.widget.TooltipCompat;
import androidx.core.app.ActivityOptionsCompat; import androidx.core.app.ActivityOptionsCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -61,31 +70,41 @@ import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget; import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition; import com.bumptech.glide.request.transition.Transition;
import com.google.android.material.chip.Chip;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayout;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import androidmads.library.qrgenearator.QRGContents;
import androidmads.library.qrgenearator.QRGEncoder;
import app.fedilab.android.BaseMainActivity; import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.BuildConfig;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity; import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.databinding.ActivityProfileBinding; import app.fedilab.android.databinding.ActivityProfileBinding;
import app.fedilab.android.databinding.NotificationsRelatedAccountsBinding; import app.fedilab.android.databinding.NotificationsRelatedAccountsBinding;
import app.fedilab.android.databinding.PopupQrcodeBinding;
import app.fedilab.android.databinding.TabProfileCustomViewBinding;
import app.fedilab.android.mastodon.client.entities.api.Account; import app.fedilab.android.mastodon.client.entities.api.Account;
import app.fedilab.android.mastodon.client.entities.api.Attachment; import app.fedilab.android.mastodon.client.entities.api.Attachment;
import app.fedilab.android.mastodon.client.entities.api.FamiliarFollowers; import app.fedilab.android.mastodon.client.entities.api.FamiliarFollowers;
import app.fedilab.android.mastodon.client.entities.api.FeaturedTag;
import app.fedilab.android.mastodon.client.entities.api.Field; import app.fedilab.android.mastodon.client.entities.api.Field;
import app.fedilab.android.mastodon.client.entities.api.IdentityProof; import app.fedilab.android.mastodon.client.entities.api.IdentityProof;
import app.fedilab.android.mastodon.client.entities.api.MastodonList; import app.fedilab.android.mastodon.client.entities.api.MastodonList;
import app.fedilab.android.mastodon.client.entities.api.RelationShip; import app.fedilab.android.mastodon.client.entities.api.RelationShip;
import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.client.entities.app.Languages; import app.fedilab.android.mastodon.client.entities.app.Languages;
import app.fedilab.android.mastodon.client.entities.app.Pinned; import app.fedilab.android.mastodon.client.entities.app.Pinned;
import app.fedilab.android.mastodon.client.entities.app.PinnedTimeline; import app.fedilab.android.mastodon.client.entities.app.PinnedTimeline;
@ -100,6 +119,7 @@ import app.fedilab.android.mastodon.helper.SpannableHelper;
import app.fedilab.android.mastodon.helper.ThemeHelper; import app.fedilab.android.mastodon.helper.ThemeHelper;
import app.fedilab.android.mastodon.ui.drawer.FieldAdapter; import app.fedilab.android.mastodon.ui.drawer.FieldAdapter;
import app.fedilab.android.mastodon.ui.drawer.IdentityProofsAdapter; import app.fedilab.android.mastodon.ui.drawer.IdentityProofsAdapter;
import app.fedilab.android.mastodon.ui.drawer.StatusAdapter;
import app.fedilab.android.mastodon.ui.pageadapter.FedilabProfileTLPageAdapter; import app.fedilab.android.mastodon.ui.pageadapter.FedilabProfileTLPageAdapter;
import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM; import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.mastodon.viewmodel.mastodon.NodeInfoVM; import app.fedilab.android.mastodon.viewmodel.mastodon.NodeInfoVM;
@ -114,7 +134,6 @@ public class ProfileActivity extends BaseActivity {
private RelationShip relationship; private RelationShip relationship;
private FamiliarFollowers familiarFollowers; private FamiliarFollowers familiarFollowers;
private Account account; private Account account;
private ScheduledExecutorService scheduledExecutorService;
private action doAction; private action doAction;
private AccountsVM accountsVM; private AccountsVM accountsVM;
private RecyclerView identityProofsRecycler; private RecyclerView identityProofsRecycler;
@ -123,18 +142,23 @@ public class ProfileActivity extends BaseActivity {
private String account_id; private String account_id;
private String mention_str; private String mention_str;
private WellKnownNodeinfo.NodeInfo nodeInfo; private WellKnownNodeinfo.NodeInfo nodeInfo;
private boolean checkRemotely; private boolean checkRemotely;
private final BroadcastReceiver broadcast_data = new BroadcastReceiver() { private final BroadcastReceiver broadcast_data = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
Bundle b = intent.getExtras(); Bundle args = intent.getExtras();
if (b != null) { if (args != null) {
Account accountReceived = (Account) b.getSerializable(Helper.ARG_ACCOUNT); long bundleId = args.getLong(Helper.ARG_INTENT_ID, -1);
if (b.getBoolean(Helper.RECEIVE_REDRAW_PROFILE, false) && accountReceived != null) { new CachedBundle(ProfileActivity.this).getBundle(bundleId, Helper.getCurrentAccount(ProfileActivity.this), bundle -> {
if (account != null && accountReceived.id != null && account.id != null && accountReceived.id.equalsIgnoreCase(account.id)) { Account accountReceived = (Account) bundle.getSerializable(Helper.ARG_ACCOUNT);
initializeView(accountReceived); if (bundle.getBoolean(Helper.RECEIVE_REDRAW_PROFILE, false) && accountReceived != null) {
if (account != null && accountReceived.id != null && account.id != null && accountReceived.id.equalsIgnoreCase(account.id)) {
initializeView(accountReceived);
}
} }
} });
} }
} }
}; };
@ -148,21 +172,14 @@ public class ProfileActivity extends BaseActivity {
setContentView(binding.getRoot()); setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar); setSupportActionBar(binding.toolbar);
ActionBar actionBar = getSupportActionBar(); ActionBar actionBar = getSupportActionBar();
Bundle b = getIntent().getExtras(); Bundle args = getIntent().getExtras();
binding.accountFollow.setEnabled(false); binding.accountFollow.setEnabled(false);
checkRemotely = false; checkRemotely = false;
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(this);
homeMuted = false;
if (b != null) {
account = (Account) b.getSerializable(Helper.ARG_ACCOUNT);
account_id = b.getString(Helper.ARG_USER_ID, null);
mention_str = b.getString(Helper.ARG_MENTION, null);
checkRemotely = b.getBoolean(Helper.ARG_CHECK_REMOTELY, false);
}
if (!checkRemotely) { if (!checkRemotely) {
checkRemotely = sharedpreferences.getBoolean(getString(R.string.SET_PROFILE_REMOTELY), false); checkRemotely = sharedpreferences.getBoolean(getString(R.string.SET_PROFILE_REMOTELY), false);
} }
ActivityCompat.postponeEnterTransition(ProfileActivity.this);
//Remove title //Remove title
if (actionBar != null) { if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(false); actionBar.setDisplayShowTitleEnabled(false);
@ -176,6 +193,22 @@ public class ProfileActivity extends BaseActivity {
float scale = sharedpreferences.getFloat(getString(R.string.SET_FONT_SCALE), 1.1f); float scale = sharedpreferences.getFloat(getString(R.string.SET_FONT_SCALE), 1.1f);
binding.title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18 * 1.1f / scale); binding.title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18 * 1.1f / scale);
accountsVM = new ViewModelProvider(ProfileActivity.this).get(AccountsVM.class); accountsVM = new ViewModelProvider(ProfileActivity.this).get(AccountsVM.class);
homeMuted = false;
if (args != null) {
long bundleId = args.getLong(Helper.ARG_INTENT_ID, -1);
new CachedBundle(ProfileActivity.this).getBundle(bundleId, Helper.getCurrentAccount(ProfileActivity.this), this::initializeAfterBundle);
} else {
initializeAfterBundle(null);
}
}
private void initializeAfterBundle(Bundle bundle) {
if (bundle != null) {
account = (Account) bundle.getSerializable(Helper.ARG_ACCOUNT);
account_id = bundle.getString(Helper.ARG_USER_ID, null);
mention_str = bundle.getString(Helper.ARG_MENTION, null);
checkRemotely = bundle.getBoolean(Helper.ARG_CHECK_REMOTELY, false);
}
if (account != null) { if (account != null) {
initializeView(account); initializeView(account);
} else if (account_id != null) { } else if (account_id != null) {
@ -185,7 +218,7 @@ public class ProfileActivity extends BaseActivity {
}); });
} else if (mention_str != null) { } else if (mention_str != null) {
accountsVM.searchAccounts(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, mention_str, 1, true, false).observe(ProfileActivity.this, accounts -> { accountsVM.searchAccounts(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, mention_str, 1, true, false).observe(ProfileActivity.this, accounts -> {
if (accounts != null && accounts.size() > 0) { if (accounts != null && !accounts.isEmpty()) {
account = accounts.get(0); account = accounts.get(0);
initializeView(account); initializeView(account);
} else { } else {
@ -198,8 +231,49 @@ public class ProfileActivity extends BaseActivity {
finish(); finish();
} }
//Check if account is homeMuted //Check if account is homeMuted
accountsVM.isMuted(currentAccount, account).observe(this, result -> homeMuted = result != null && result); accountsVM.isMuted(Helper.getCurrentAccount(ProfileActivity.this), account).observe(this, result -> homeMuted = result != null && result);
LocalBroadcastManager.getInstance(ProfileActivity.this).registerReceiver(broadcast_data, new IntentFilter(Helper.BROADCAST_DATA)); ContextCompat.registerReceiver(ProfileActivity.this, broadcast_data, new IntentFilter(Helper.BROADCAST_DATA), ContextCompat.RECEIVER_NOT_EXPORTED);
//Search for featured tags
accountsVM.getAccountFeaturedTags(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account!=null?account.id:account_id).observe(this, featuredTags -> {
if(featuredTags != null && !featuredTags.isEmpty()) {
binding.featuredHashtagsContainer.setVisibility(View.VISIBLE);
binding.featuredHashtags.removeAllViews();
for(FeaturedTag featuredTag: featuredTags) {
if(featuredTag.statuses_count > 0 ) {
Chip chip = new Chip(ProfileActivity.this);
chip.setClickable(true);
chip.setEnsureMinTouchTargetSize(false);
chip.setText(String.format("#%s", featuredTag.name));
chip.setTextColor(ThemeHelper.getAttColor(ProfileActivity.this, R.attr.colorPrimary));
chip.setOnClickListener(v -> {
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.ACCOUNT_TIMELINE);
if (account != null) {
args.putSerializable(Helper.ARG_CACHED_ACCOUNT_ID, account.id);
}
args.putString(Helper.ARG_TAGGED, featuredTag.name);
args.putBoolean(Helper.ARG_SHOW_PINNED, false);
args.putBoolean(Helper.ARG_SHOW_REPLIES, false);
args.putBoolean(Helper.ARG_SHOW_REBLOGS, false);
args.putBoolean(Helper.ARG_CHECK_REMOTELY, checkRemotely);
Intent intent = new Intent(ProfileActivity.this, TimelineActivity.class);
new CachedBundle(ProfileActivity.this).insertBundle(args, Helper.getCurrentAccount(ProfileActivity.this), bundleId -> {
Bundle _bundle = new Bundle();
_bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(_bundle);
startActivity(intent);
});
});
binding.featuredHashtags.addView(chip);
}
}
} else {
binding.featuredHashtagsContainer.setVisibility(View.GONE);
}
});
} }
@ -215,14 +289,18 @@ public class ProfileActivity extends BaseActivity {
TabLayout.Tab followerTab = binding.accountTabLayout.getTabAt(2); TabLayout.Tab followerTab = binding.accountTabLayout.getTabAt(2);
if (statusTab != null) { if (statusTab != null) {
statusTab.setText(getString(R.string.status_cnt, Helper.withSuffix(account.statuses_count))); statusTab.setText(getString(R.string.status_cnt, Helper.withSuffix(account.statuses_count)));
TooltipCompat.setTooltipText(statusTab.view, String.valueOf(account.statuses_count));
} }
if (followingTab != null) { if (followingTab != null) {
followingTab.setText(getString(R.string.following_cnt, Helper.withSuffix(account.following_count))); followingTab.setText(getString(R.string.following_cnt, Helper.withSuffix(account.following_count)));
TooltipCompat.setTooltipText(followingTab.view, String.valueOf(account.following_count));
} }
if (followerTab != null) { if (followerTab != null) {
followerTab.setText(getString(R.string.followers_cnt, Helper.withSuffix(account.followers_count))); followerTab.setText(getString(R.string.followers_cnt, Helper.withSuffix(account.followers_count)));
TooltipCompat.setTooltipText(followerTab.view, String.valueOf(account.followers_count));
} }
} }
} }
} }
@ -251,19 +329,64 @@ public class ProfileActivity extends BaseActivity {
} }
}); });
binding.qrCodeGenerator.setVisibility(View.VISIBLE);
binding.qrCodeGenerator.setOnClickListener(v->{
QRGEncoder qrgEncoder = new QRGEncoder(account.url, null, QRGContents.Type.TEXT, 400);
Drawable logoDrawable = ContextCompat.getDrawable(ProfileActivity.this, R.drawable.fedilab_logo_bubbles);
if (logoDrawable != null) {
Bitmap bitmap = qrgEncoder.getBitmap();
MaterialAlertDialogBuilder alertadd = new MaterialAlertDialogBuilder(ProfileActivity.this);
PopupQrcodeBinding popupQrcodeBinding = PopupQrcodeBinding.inflate(getLayoutInflater());
popupQrcodeBinding.qrcodeImage.setImageBitmap(bitmap);
alertadd.setView(popupQrcodeBinding.getRoot());
alertadd.setNeutralButton(R.string.close, (dialog, which) -> dialog.dismiss());
alertadd.setPositiveButton(R.string.save, (dlg, which) -> {
File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
File targeted_folder = new File(path, getString(R.string.app_name));
if (!targeted_folder.exists()) {
boolean created = targeted_folder.mkdir();
if (!created) {
Toasty.error(ProfileActivity.this, getString(R.string.toast_error), Toasty.LENGTH_SHORT).show();
return;
}
}
String fileName = URLUtil.guessFileName(account.url, null, null);
if (fileName.endsWith(".bin")) {
fileName = fileName.replace(".bin", ".png");
}
fileName = fileName.replaceAll("@","");
File backupFile = new File(targeted_folder.getAbsolutePath() + "/" + fileName);
try (FileOutputStream out = new FileOutputStream(backupFile)) {
final Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
Uri uri = Uri.fromFile(backupFile);
intent.setDataAndType(uri, "image/jpeg");
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
Helper.notify_user(ProfileActivity.this, Helper.getCurrentAccount(ProfileActivity.this), intent, BitmapFactory.decodeResource(getResources(),
getMainLogo(ProfileActivity.this)), Helper.NotifType.STORE, getString(R.string.save_over), getString(R.string.download_from, fileName));
Toasty.success(ProfileActivity.this, getString(R.string.save_over), Toasty.LENGTH_LONG).show();
} catch (IOException e) {
e.printStackTrace();
}
});
alertadd.show();
}
});
//Retrieve relationship with the connected account //Retrieve relationship with the connected account
List<String> accountListToCheck = new ArrayList<>(); List<String> accountListToCheck = new ArrayList<>();
accountListToCheck.add(account.id); accountListToCheck.add(account.id);
//Retrieve relation ship //Retrieve relation ship
accountsVM.getRelationships(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, accountListToCheck).observe(ProfileActivity.this, relationShips -> { accountsVM.getRelationships(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, accountListToCheck).observe(ProfileActivity.this, relationShips -> {
if (relationShips != null && relationShips.size() > 0) { if (relationShips != null && !relationShips.isEmpty()) {
this.relationship = relationShips.get(0); this.relationship = relationShips.get(0);
updateAccount(); updateAccount();
} }
}); });
accountsVM.getFamiliarFollowers(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, accountListToCheck).observe(ProfileActivity.this, familiarFollowersList -> { accountsVM.getFamiliarFollowers(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, accountListToCheck).observe(ProfileActivity.this, familiarFollowersList -> {
if (familiarFollowersList != null && familiarFollowersList.size() > 0) { if (familiarFollowersList != null && !familiarFollowersList.isEmpty()) {
this.familiarFollowers = familiarFollowersList.get(0); this.familiarFollowers = familiarFollowersList.get(0);
updateAccount(); updateAccount();
} }
@ -274,24 +397,48 @@ public class ProfileActivity extends BaseActivity {
this.identityProofList = identityProofs; this.identityProofList = identityProofs;
updateAccount(); updateAccount();
}); });
//Animate emojis
if (account.emojis != null && account.emojis.size() > 0) {
boolean disableAnimatedEmoji = sharedpreferences.getBoolean(getString(R.string.SET_DISABLE_ANIMATED_EMOJI), false);
if (!disableAnimatedEmoji) {
scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService.scheduleAtFixedRate(() -> binding.accountDn.invalidate(), 0, 130, TimeUnit.MILLISECONDS);
}
}
binding.accountTabLayout.clearOnTabSelectedListeners(); binding.accountTabLayout.clearOnTabSelectedListeners();
binding.accountTabLayout.removeAllTabs(); binding.accountTabLayout.removeAllTabs();
//Tablayout for timelines/following/followers //Tablayout for timelines/following/followers
FedilabProfileTLPageAdapter fedilabProfileTLPageAdapter = new FedilabProfileTLPageAdapter(getSupportFragmentManager(), account, checkRemotely); FedilabProfileTLPageAdapter fedilabProfileTLPageAdapter = new FedilabProfileTLPageAdapter(getSupportFragmentManager(), account, checkRemotely);
binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.status_cnt, Helper.withSuffix(account.statuses_count)))); TabProfileCustomViewBinding tabMessagesView = TabProfileCustomViewBinding.inflate(getLayoutInflater());
binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.following_cnt, Helper.withSuffix(account.following_count)))); TabProfileCustomViewBinding tabFollowingView = TabProfileCustomViewBinding.inflate(getLayoutInflater());
binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.followers_cnt, Helper.withSuffix(account.followers_count)))); TabProfileCustomViewBinding tabFollowersView = TabProfileCustomViewBinding.inflate(getLayoutInflater());
tabMessagesView.title.setText(getString(R.string.toots));
tabMessagesView.count.setText(Helper.withSuffix(account.statuses_count));
tabFollowingView.title.setText(getString(R.string.following));
tabFollowingView.count.setText(Helper.withSuffix(account.following_count));
tabFollowersView.title.setText(getString(R.string.followers));
tabFollowersView.count.setText(Helper.withSuffix(account.followers_count));
TabLayout.Tab tabMessages = binding.accountTabLayout.newTab();
TabLayout.Tab tabFollowing = binding.accountTabLayout.newTab();
TabLayout.Tab tabFollowers = binding.accountTabLayout.newTab();
tabMessages.setCustomView(tabMessagesView.getRoot());
tabFollowing.setCustomView(tabFollowingView.getRoot());
tabFollowers.setCustomView(tabFollowersView.getRoot());
binding.accountTabLayout.addTab(tabMessages);
binding.accountTabLayout.addTab(tabFollowing);
binding.accountTabLayout.addTab(tabFollowers);
binding.accountViewpager.setAdapter(fedilabProfileTLPageAdapter); binding.accountViewpager.setAdapter(fedilabProfileTLPageAdapter);
binding.accountViewpager.setOffscreenPageLimit(3); binding.accountViewpager.setOffscreenPageLimit(3);
binding.accountViewpager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(binding.accountTabLayout)); binding.accountViewpager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(binding.accountTabLayout));
float scale = sharedpreferences.getFloat(getString(R.string.SET_FONT_SCALE), 1.1f);
binding.accountTabLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
binding.accountTabLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
ViewGroup.LayoutParams params = binding.accountTabLayout.getLayoutParams();
params.height = (int) (binding.accountTabLayout.getHeight() * scale);
binding.accountTabLayout.setLayoutParams(params);
}
});
binding.accountTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { binding.accountTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override @Override
public void onTabSelected(TabLayout.Tab tab) { public void onTabSelected(TabLayout.Tab tab) {
@ -323,32 +470,28 @@ public class ProfileActivity extends BaseActivity {
binding.accountPp.animate(); binding.accountPp.animate();
((Animatable) resource).start(); ((Animatable) resource).start();
} }
ActivityCompat.startPostponedEnterTransition(ProfileActivity.this);
} }
@Override @Override
public void onLoadFailed(@Nullable Drawable errorDrawable) { public void onLoadFailed(@Nullable Drawable errorDrawable) {
binding.profilePicture.setImageResource(R.drawable.ic_person); binding.profilePicture.setImageResource(R.drawable.ic_person);
binding.accountPp.setImageResource(R.drawable.ic_person); binding.accountPp.setImageResource(R.drawable.ic_person);
ActivityCompat.startPostponedEnterTransition(ProfileActivity.this);
} }
@Override @Override
public void onLoadCleared(@Nullable Drawable placeholder) { public void onLoadCleared(@Nullable Drawable placeholder) {
binding.profilePicture.setImageResource(R.drawable.ic_person); binding.profilePicture.setImageResource(R.drawable.ic_person);
binding.accountPp.setImageResource(R.drawable.ic_person); binding.accountPp.setImageResource(R.drawable.ic_person);
ActivityCompat.startPostponedEnterTransition(ProfileActivity.this);
} }
} }
); );
//Load header //Load header
MastodonHelper.loadProfileMediaMastodon(ProfileActivity.this, binding.bannerPp, account, MastodonHelper.MediaAccountType.HEADER); MastodonHelper.loadProfileMediaMastodon(ProfileActivity.this, binding.bannerPp, account, MastodonHelper.MediaAccountType.HEADER);
//Redraws icon for locked accounts //Redraws icon for locked accounts
final float scale = getResources().getDisplayMetrics().density;
if (account.locked) { if (account.locked) {
Drawable img = ContextCompat.getDrawable(ProfileActivity.this, R.drawable.ic_baseline_lock_24); Drawable img = ContextCompat.getDrawable(ProfileActivity.this, R.drawable.ic_baseline_lock_24);
assert img != null; assert img != null;
img.setBounds(0, 0, (int) (16 * scale + 0.5f), (int) (16 * scale + 0.5f)); img.setBounds(0, 0, (int) (Helper.convertDpToPixel(14, this) * scale + 0.5f), (int) (Helper.convertDpToPixel(14, this) * scale + 0.5f));
binding.accountUn.setCompoundDrawables(null, null, img, null); binding.accountUn.setCompoundDrawables(null, null, img, null);
} else { } else {
binding.accountUn.setCompoundDrawables(null, null, null, null); binding.accountUn.setCompoundDrawables(null, null, null, null);
@ -385,7 +528,7 @@ public class ProfileActivity extends BaseActivity {
binding.accountMoved.setVisibility(View.VISIBLE); binding.accountMoved.setVisibility(View.VISIBLE);
Drawable imgTravel = ContextCompat.getDrawable(ProfileActivity.this, R.drawable.ic_baseline_card_travel_24); Drawable imgTravel = ContextCompat.getDrawable(ProfileActivity.this, R.drawable.ic_baseline_card_travel_24);
assert imgTravel != null; assert imgTravel != null;
imgTravel.setBounds(0, 0, (int) (20 * scale + 0.5f), (int) (20 * scale + 0.5f)); imgTravel.setBounds(0, 0, (int) (Helper.convertDpToPixel(20, this) * scale + 0.5f), (int) (Helper.convertDpToPixel(20, this) * scale + 0.5f));
binding.accountMoved.setCompoundDrawables(imgTravel, null, null, null); binding.accountMoved.setCompoundDrawables(imgTravel, null, null, null);
//Retrieves content and make account names clickable //Retrieves content and make account names clickable
SpannableString spannableString = SpannableHelper.moveToText(ProfileActivity.this, account); SpannableString spannableString = SpannableHelper.moveToText(ProfileActivity.this, account);
@ -402,25 +545,27 @@ public class ProfileActivity extends BaseActivity {
} }
binding.openRemoteProfile.setOnClickListener(v -> { binding.openRemoteProfile.setOnClickListener(v -> {
Intent intent = new Intent(ProfileActivity.this, ProfileActivity.class); Intent intent = new Intent(ProfileActivity.this, ProfileActivity.class);
Bundle b = new Bundle(); Bundle args = new Bundle();
b.putSerializable(Helper.ARG_ACCOUNT, account); args.putSerializable(Helper.ARG_ACCOUNT, account);
b.putSerializable(Helper.ARG_CHECK_REMOTELY, true); args.putSerializable(Helper.ARG_CHECK_REMOTELY, true);
intent.putExtras(b); new CachedBundle(ProfileActivity.this).insertBundle(args, Helper.getCurrentAccount(ProfileActivity.this), bundleId -> {
ActivityOptionsCompat options = ActivityOptionsCompat Bundle bundle = new Bundle();
.makeSceneTransitionAnimation(ProfileActivity.this, binding.profilePicture, getString(R.string.activity_porfile_pp)); bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
startActivity(intent, options.toBundle()); intent.putExtras(bundle);
finish(); startActivity(intent);
finish();
});
}); });
//Fields for profile //Fields for profile
List<Field> fields = account.fields; List<Field> fields = account.fields;
if (fields != null && fields.size() > 0) { if (fields != null && !fields.isEmpty()) {
FieldAdapter fieldAdapter = new FieldAdapter(fields, account); FieldAdapter fieldAdapter = new FieldAdapter(fields, account);
binding.fieldsContainer.setAdapter(fieldAdapter); binding.fieldsContainer.setAdapter(fieldAdapter);
binding.fieldsContainer.setLayoutManager(new LinearLayoutManager(ProfileActivity.this)); binding.fieldsContainer.setLayoutManager(new LinearLayoutManager(ProfileActivity.this));
} }
binding.accountDn.setText( binding.accountDn.setText(
account.getSpanDisplayName(ProfileActivity.this, account.getSpanDisplayNameEmoji(ProfileActivity.this,
new WeakReference<>(binding.accountDn)), new WeakReference<>(binding.accountDn)),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
@ -438,14 +583,45 @@ public class ProfileActivity extends BaseActivity {
}); });
binding.accountNote.setText( binding.accountNote.setText(
account.getSpanNote(ProfileActivity.this, account.getSpanNote(ProfileActivity.this,
new WeakReference<>(binding.accountNote)), new WeakReference<>(binding.accountNote), () -> {
//TODO: replace this hack
binding.accountNote.setText(
account.getSpanNote(ProfileActivity.this,
new WeakReference<>(binding.accountNote)), TextView.BufferType.SPANNABLE);
}),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
binding.accountNote.setMovementMethod(LinkMovementMethod.getInstance()); binding.accountNote.setMovementMethod(LinkMovementMethod.getInstance());
binding.bannerPp.setOnClickListener(v -> {
Intent intent = new Intent(ProfileActivity.this, MediaActivity.class);
Bundle args = new Bundle();
Attachment attachment = new Attachment();
attachment.description = account.acct;
attachment.preview_url = account.header;
attachment.url = account.header;
attachment.remote_url = account.header;
attachment.type = "image";
ArrayList<Attachment> attachments = new ArrayList<>();
attachments.add(attachment);
args.putSerializable(Helper.ARG_MEDIA_ARRAY, attachments);
args.putInt(Helper.ARG_MEDIA_POSITION, 1);
new CachedBundle(ProfileActivity.this).insertBundle(args, Helper.getCurrentAccount(ProfileActivity.this), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
ActivityOptionsCompat options = ActivityOptionsCompat
.makeSceneTransitionAnimation(ProfileActivity.this, binding.accountPp, attachment.url);
// start the new activity
startActivity(intent, options.toBundle());
});
});
binding.accountPp.setOnClickListener(v -> { binding.accountPp.setOnClickListener(v -> {
Intent intent = new Intent(ProfileActivity.this, MediaActivity.class); Intent intent = new Intent(ProfileActivity.this, MediaActivity.class);
Bundle b = new Bundle(); Bundle args = new Bundle();
Attachment attachment = new Attachment(); Attachment attachment = new Attachment();
attachment.description = account.acct; attachment.description = account.acct;
attachment.preview_url = account.avatar; attachment.preview_url = account.avatar;
@ -454,13 +630,17 @@ public class ProfileActivity extends BaseActivity {
attachment.type = "image"; attachment.type = "image";
ArrayList<Attachment> attachments = new ArrayList<>(); ArrayList<Attachment> attachments = new ArrayList<>();
attachments.add(attachment); attachments.add(attachment);
b.putSerializable(Helper.ARG_MEDIA_ARRAY, attachments); args.putSerializable(Helper.ARG_MEDIA_ARRAY, attachments);
b.putInt(Helper.ARG_MEDIA_POSITION, 1); args.putInt(Helper.ARG_MEDIA_POSITION, 1);
intent.putExtras(b); new CachedBundle(ProfileActivity.this).insertBundle(args, Helper.getCurrentAccount(ProfileActivity.this), bundleId -> {
ActivityOptionsCompat options = ActivityOptionsCompat Bundle bundle = new Bundle();
.makeSceneTransitionAnimation(ProfileActivity.this, binding.accountPp, attachment.url); bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
// start the new activity intent.putExtras(bundle);
startActivity(intent, options.toBundle()); ActivityOptionsCompat options = ActivityOptionsCompat
.makeSceneTransitionAnimation(ProfileActivity.this, binding.accountPp, attachment.url);
// start the new activity
startActivity(intent, options.toBundle());
});
}); });
@ -553,13 +733,13 @@ public class ProfileActivity extends BaseActivity {
* This methode is called to update the view once an action has been performed * This methode is called to update the view once an action has been performed
*/ */
private void updateAccount() { private void updateAccount() {
if (currentAccount == null || account == null) { if (Helper.getCurrentAccount(ProfileActivity.this) == null || account == null) {
return; return;
} }
//Manage indentity proofs if not yet displayed //Manage indentity proofs if not yet displayed
if (identityProofList != null && identityProofList.size() > 0) { if (identityProofList != null && !identityProofList.isEmpty()) {
ImageView identity_proofs_indicator = findViewById(R.id.identity_proofs_indicator); ImageView identity_proofs_indicator = findViewById(R.id.identity_proofs_indicator);
identity_proofs_indicator.setVisibility(View.VISIBLE); identity_proofs_indicator.setVisibility(View.VISIBLE);
//Recyclerview for identity proof has not been inflated yet //Recyclerview for identity proof has not been inflated yet
@ -580,7 +760,7 @@ public class ProfileActivity extends BaseActivity {
} }
} }
if (familiarFollowers != null && familiarFollowers.accounts != null && familiarFollowers.accounts.size() > 0) { if (familiarFollowers != null && familiarFollowers.accounts != null && !familiarFollowers.accounts.isEmpty()) {
binding.relatedAccounts.removeAllViews(); binding.relatedAccounts.removeAllViews();
for (Account account : familiarFollowers.accounts) { for (Account account : familiarFollowers.accounts) {
NotificationsRelatedAccountsBinding notificationsRelatedAccountsBinding = NotificationsRelatedAccountsBinding.inflate(LayoutInflater.from(ProfileActivity.this)); NotificationsRelatedAccountsBinding notificationsRelatedAccountsBinding = NotificationsRelatedAccountsBinding.inflate(LayoutInflater.from(ProfileActivity.this));
@ -588,13 +768,15 @@ public class ProfileActivity extends BaseActivity {
notificationsRelatedAccountsBinding.acc.setText(account.username); notificationsRelatedAccountsBinding.acc.setText(account.username);
notificationsRelatedAccountsBinding.relatedAccountContainer.setOnClickListener(v -> { notificationsRelatedAccountsBinding.relatedAccountContainer.setOnClickListener(v -> {
Intent intent = new Intent(ProfileActivity.this, ProfileActivity.class); Intent intent = new Intent(ProfileActivity.this, ProfileActivity.class);
Bundle b = new Bundle(); Bundle args = new Bundle();
b.putSerializable(Helper.ARG_ACCOUNT, account); args.putSerializable(Helper.ARG_ACCOUNT, account);
intent.putExtras(b); new CachedBundle(ProfileActivity.this).insertBundle(args, Helper.getCurrentAccount(ProfileActivity.this), bundleId -> {
ActivityOptionsCompat options = ActivityOptionsCompat Bundle bundle = new Bundle();
.makeSceneTransitionAnimation(ProfileActivity.this, notificationsRelatedAccountsBinding.profilePicture, getString(R.string.activity_porfile_pp)); bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
// start the new activity intent.putExtras(bundle);
startActivity(intent, options.toBundle()); startActivity(intent);
});
}); });
binding.relatedAccounts.addView(notificationsRelatedAccountsBinding.getRoot()); binding.relatedAccounts.addView(notificationsRelatedAccountsBinding.getRoot());
} }
@ -611,18 +793,18 @@ public class ProfileActivity extends BaseActivity {
binding.accountFollow.setContentDescription(getString(R.string.action_disabled)); binding.accountFollow.setContentDescription(getString(R.string.action_disabled));
} }
if (relationship.requested) {
binding.accountFollowRequest.setVisibility(View.VISIBLE);
binding.accountFollow.setIconResource(R.drawable.ic_baseline_hourglass_full_24);
binding.accountFollow.setVisibility(View.VISIBLE);
binding.accountFollow.setContentDescription(getString(R.string.follow_request));
doAction = action.UNFOLLOW;
}
if (relationship.followed_by) { if (relationship.followed_by) {
binding.accountFollowedBy.setVisibility(View.VISIBLE); binding.accountFollowedBy.setVisibility(View.VISIBLE);
} else { } else {
binding.accountFollowedBy.setVisibility(View.GONE); binding.accountFollowedBy.setVisibility(View.GONE);
} }
if (relationship.requested_by) {
binding.accountRequestedBy.setVisibility(View.VISIBLE);
} else {
binding.accountRequestedBy.setVisibility(View.GONE);
}
binding.accountFollowRequest.setVisibility(View.GONE);
if (relationship.following) { if (relationship.following) {
binding.accountFollow.setIconResource(R.drawable.ic_baseline_person_remove_24); binding.accountFollow.setIconResource(R.drawable.ic_baseline_person_remove_24);
binding.accountFollow.setBackgroundTintList(ColorStateList.valueOf(ThemeHelper.getAttColor(this, R.attr.colorError))); binding.accountFollow.setBackgroundTintList(ColorStateList.valueOf(ThemeHelper.getAttColor(this, R.attr.colorError)));
@ -635,15 +817,23 @@ public class ProfileActivity extends BaseActivity {
doAction = action.UNBLOCK; doAction = action.UNBLOCK;
binding.accountFollow.setVisibility(View.VISIBLE); binding.accountFollow.setVisibility(View.VISIBLE);
binding.accountFollow.setContentDescription(getString(R.string.action_unblock)); binding.accountFollow.setContentDescription(getString(R.string.action_unblock));
} else if (relationship.requested) {
binding.accountFollowRequest.setVisibility(View.VISIBLE);
binding.accountFollow.setIconResource(R.drawable.ic_baseline_hourglass_full_24);
binding.accountFollow.setVisibility(View.VISIBLE);
binding.accountFollow.setContentDescription(getString(R.string.follow_request));
doAction = action.UNFOLLOW;
} else { } else {
binding.accountFollow.setIconResource(R.drawable.ic_baseline_person_add_24); binding.accountFollow.setIconResource(R.drawable.ic_baseline_person_add_24);
doAction = action.FOLLOW; doAction = action.FOLLOW;
binding.accountFollow.setVisibility(View.VISIBLE); binding.accountFollow.setVisibility(View.VISIBLE);
binding.accountFollow.setContentDescription(getString(R.string.action_follow)); binding.accountFollow.setContentDescription(getString(R.string.action_follow));
} }
//The value for account is from same server so id can be used //The value for account is from same server so id can be used
if (account.id.equals(currentAccount.user_id)) { if (account.id.equals(Helper.getCurrentAccount(ProfileActivity.this).user_id)) {
binding.accountFollow.setVisibility(View.GONE); binding.accountFollow.setVisibility(View.GONE);
binding.headerEditProfile.setVisibility(View.VISIBLE); binding.headerEditProfile.setVisibility(View.VISIBLE);
binding.headerEditProfile.bringToFront(); binding.headerEditProfile.bringToFront();
@ -819,6 +1009,8 @@ public class ProfileActivity extends BaseActivity {
instanceType = RemoteInstance.InstanceType.PIXELFED; instanceType = RemoteInstance.InstanceType.PIXELFED;
} else if (nodeInfo.software.name.compareToIgnoreCase("misskey") == 0) { } else if (nodeInfo.software.name.compareToIgnoreCase("misskey") == 0) {
instanceType = RemoteInstance.InstanceType.MISSKEY; instanceType = RemoteInstance.InstanceType.MISSKEY;
} else if (nodeInfo.software.name.compareToIgnoreCase("lemmy") == 0) {
instanceType = RemoteInstance.InstanceType.LEMMY;
} else if (nodeInfo.software.name.compareToIgnoreCase("gnu") == 0) { } else if (nodeInfo.software.name.compareToIgnoreCase("gnu") == 0) {
instanceType = RemoteInstance.InstanceType.GNU; instanceType = RemoteInstance.InstanceType.GNU;
} else { } else {
@ -850,11 +1042,16 @@ public class ProfileActivity extends BaseActivity {
new Pinned(ProfileActivity.this).insertPinned(finalPinned); new Pinned(ProfileActivity.this).insertPinned(finalPinned);
} }
runOnUiThread(() -> { runOnUiThread(() -> {
Bundle b = new Bundle(); Bundle args = new Bundle();
b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true); args.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Intent intentBD = new Intent(Helper.BROADCAST_DATA); Intent intentBD = new Intent(Helper.BROADCAST_DATA);
intentBD.putExtras(b); new CachedBundle(ProfileActivity.this).insertBundle(args, Helper.getCurrentAccount(ProfileActivity.this), bundleId -> {
LocalBroadcastManager.getInstance(ProfileActivity.this).sendBroadcast(intentBD); Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentBD.putExtras(bundle);
intentBD.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intentBD);
});
}); });
} catch (DBException e) { } catch (DBException e) {
e.printStackTrace(); e.printStackTrace();
@ -916,7 +1113,7 @@ public class ProfileActivity extends BaseActivity {
String[] codesArr; String[] codesArr;
String[] languagesArr; String[] languagesArr;
boolean[] presentArr; boolean[] presentArr;
if (storedLanguages != null && storedLanguages.size() > 0) { if (storedLanguages != null && !storedLanguages.isEmpty()) {
int i = 0; int i = 0;
codesArr = new String[storedLanguages.size()]; codesArr = new String[storedLanguages.size()];
languagesArr = new String[storedLanguages.size()]; languagesArr = new String[storedLanguages.size()];
@ -974,22 +1171,30 @@ public class ProfileActivity extends BaseActivity {
return true; return true;
} else if (itemId == R.id.action_direct_message) { } else if (itemId == R.id.action_direct_message) {
Intent intent = new Intent(ProfileActivity.this, ComposeActivity.class); Intent intent = new Intent(ProfileActivity.this, ComposeActivity.class);
Bundle b = new Bundle(); Bundle args = new Bundle();
b.putSerializable(Helper.ARG_ACCOUNT_MENTION, account); args.putSerializable(Helper.ARG_ACCOUNT_MENTION, account);
b.putString(Helper.ARG_VISIBILITY, "direct"); args.putString(Helper.ARG_VISIBILITY, "direct");
intent.putExtras(b); new CachedBundle(ProfileActivity.this).insertBundle(args, Helper.getCurrentAccount(ProfileActivity.this), bundleId -> {
startActivity(intent); Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
startActivity(intent);
});
return true; return true;
} else if (itemId == R.id.action_add_to_list) { } else if (itemId == R.id.action_add_to_list) {
TimelinesVM timelinesVM = new ViewModelProvider(ProfileActivity.this).get(TimelinesVM.class); TimelinesVM timelinesVM = new ViewModelProvider(ProfileActivity.this).get(TimelinesVM.class);
timelinesVM.getLists(BaseMainActivity.currentInstance, BaseMainActivity.currentToken) timelinesVM.getLists(BaseMainActivity.currentInstance, BaseMainActivity.currentToken)
.observe(ProfileActivity.this, mastodonLists -> { .observe(ProfileActivity.this, mastodonLists -> {
if (mastodonLists == null || mastodonLists.size() == 0) { if (mastodonLists == null || mastodonLists.isEmpty()) {
Toasty.info(ProfileActivity.this, getString(R.string.action_lists_empty), Toast.LENGTH_SHORT).show(); Toasty.info(ProfileActivity.this, getString(R.string.action_lists_empty), Toast.LENGTH_SHORT).show();
return; return;
} }
accountsVM.getListContainingAccount(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id) accountsVM.getListContainingAccount(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id)
.observe(ProfileActivity.this, mastodonListUserIs -> { .observe(ProfileActivity.this, mastodonListUserIs -> {
if (mastodonListUserIs == null) {
mastodonListUserIs = new ArrayList<>();
}
Collections.sort(mastodonLists, (obj1, obj2) -> obj1.title.compareToIgnoreCase(obj2.title));
AlertDialog.Builder builderSingle = new MaterialAlertDialogBuilder(ProfileActivity.this); AlertDialog.Builder builderSingle = new MaterialAlertDialogBuilder(ProfileActivity.this);
builderSingle.setTitle(getString(R.string.action_lists_add_to)); builderSingle.setTitle(getString(R.string.action_lists_add_to));
builderSingle.setPositiveButton(R.string.close, (dialog, which) -> dialog.dismiss()); builderSingle.setPositiveButton(R.string.close, (dialog, which) -> dialog.dismiss());
@ -1058,12 +1263,15 @@ public class ProfileActivity extends BaseActivity {
return true; return true;
} else if (itemId == R.id.action_mention) { } else if (itemId == R.id.action_mention) {
Intent intent; Intent intent;
Bundle b;
intent = new Intent(ProfileActivity.this, ComposeActivity.class); intent = new Intent(ProfileActivity.this, ComposeActivity.class);
b = new Bundle(); Bundle args = new Bundle();
b.putSerializable(Helper.ARG_ACCOUNT_MENTION, account); args.putSerializable(Helper.ARG_ACCOUNT_MENTION, account);
intent.putExtras(b); new CachedBundle(ProfileActivity.this).insertBundle(args, Helper.getCurrentAccount(ProfileActivity.this), bundleId -> {
startActivity(intent); Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
startActivity(intent);
});
return true; return true;
} else if (itemId == R.id.action_mute) { } else if (itemId == R.id.action_mute) {
AlertDialog.Builder builderInner; AlertDialog.Builder builderInner;
@ -1107,7 +1315,7 @@ public class ProfileActivity extends BaseActivity {
builderInner.setNeutralButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); builderInner.setNeutralButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
if (homeMuted) { if (homeMuted) {
builderInner.setTitle(R.string.unmute_home); builderInner.setTitle(R.string.unmute_home);
builderInner.setPositiveButton(R.string.action_unmute, (dialog, which) -> accountsVM.unmuteHome(currentAccount, account) builderInner.setPositiveButton(R.string.action_unmute, (dialog, which) -> accountsVM.unmuteHome(Helper.getCurrentAccount(ProfileActivity.this), account)
.observe(ProfileActivity.this, account -> { .observe(ProfileActivity.this, account -> {
homeMuted = false; homeMuted = false;
invalidateOptionsMenu(); invalidateOptionsMenu();
@ -1115,7 +1323,7 @@ public class ProfileActivity extends BaseActivity {
})); }));
} else { } else {
builderInner.setTitle(R.string.mute_home); builderInner.setTitle(R.string.mute_home);
builderInner.setPositiveButton(R.string.action_mute, (dialog, which) -> accountsVM.muteHome(currentAccount, account) builderInner.setPositiveButton(R.string.action_mute, (dialog, which) -> accountsVM.muteHome(Helper.getCurrentAccount(ProfileActivity.this), account)
.observe(ProfileActivity.this, account -> { .observe(ProfileActivity.this, account -> {
homeMuted = true; homeMuted = true;
invalidateOptionsMenu(); invalidateOptionsMenu();
@ -1130,24 +1338,15 @@ public class ProfileActivity extends BaseActivity {
}); });
return true; return true;
} else if (itemId == R.id.action_report) { } else if (itemId == R.id.action_report) {
AlertDialog.Builder builderInner = new MaterialAlertDialogBuilder(ProfileActivity.this); Intent intent = new Intent(ProfileActivity.this, ReportActivity.class);
builderInner.setTitle(R.string.report_account); Bundle args = new Bundle();
//Text for report args.putSerializable(Helper.ARG_ACCOUNT, account);
EditText input = new EditText(ProfileActivity.this); new CachedBundle(ProfileActivity.this).insertBundle(args, Helper.getCurrentAccount(ProfileActivity.this), bundleId -> {
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( Bundle bundle = new Bundle();
LinearLayout.LayoutParams.MATCH_PARENT, bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
LinearLayout.LayoutParams.WRAP_CONTENT); intent.putExtras(bundle);
input.setLayoutParams(lp); startActivity(intent);
builderInner.setView(input);
builderInner.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
builderInner.setPositiveButton(R.string.yes, (dialog, which) -> {
String comment = null;
if (input.getText() != null)
comment = input.getText().toString();
accountsVM.report(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, null, null, null, comment, false);
dialog.dismiss();
}); });
builderInner.show();
return true; return true;
} else if (itemId == R.id.action_block) { } else if (itemId == R.id.action_block) {
AlertDialog.Builder builderInner = new MaterialAlertDialogBuilder(ProfileActivity.this); AlertDialog.Builder builderInner = new MaterialAlertDialogBuilder(ProfileActivity.this);
@ -1171,20 +1370,18 @@ public class ProfileActivity extends BaseActivity {
target = account.id; target = account.id;
} }
switch (doActionAccount) { switch (doActionAccount) {
case BLOCK: case BLOCK ->
accountsVM.block(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, target) accountsVM.block(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, target)
.observe(ProfileActivity.this, relationShip -> { .observe(ProfileActivity.this, relationShip -> {
this.relationship = relationShip; this.relationship = relationShip;
updateAccount(); updateAccount();
}); });
break; case UNBLOCK ->
case UNBLOCK: accountsVM.unblock(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, target)
accountsVM.unblock(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, target) .observe(ProfileActivity.this, relationShip -> {
.observe(ProfileActivity.this, relationShip -> { this.relationship = relationShip;
this.relationship = relationShip; updateAccount();
updateAccount(); });
});
break;
} }
dialog.dismiss(); dialog.dismiss();
}); });
@ -1213,11 +1410,11 @@ public class ProfileActivity extends BaseActivity {
@Override @Override
public void onDestroy() { public void onDestroy() {
if (scheduledExecutorService != null) { try {
scheduledExecutorService.shutdownNow(); unregisterReceiver(broadcast_data);
scheduledExecutorService = null; } catch (IllegalArgumentException e) {
e.printStackTrace();
} }
LocalBroadcastManager.getInstance(ProfileActivity.this).unregisterReceiver(broadcast_data);
super.onDestroy(); super.onDestroy();
} }

View file

@ -31,7 +31,6 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
@ -43,11 +42,13 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import app.fedilab.android.BuildConfig;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity; import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.databinding.ActivityReorderTabsBinding; import app.fedilab.android.databinding.ActivityReorderTabsBinding;
import app.fedilab.android.databinding.PopupSearchInstanceBinding; import app.fedilab.android.databinding.PopupSearchInstanceBinding;
import app.fedilab.android.mastodon.client.entities.app.BottomMenu; import app.fedilab.android.mastodon.client.entities.app.BottomMenu;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.client.entities.app.InstanceSocial; import app.fedilab.android.mastodon.client.entities.app.InstanceSocial;
import app.fedilab.android.mastodon.client.entities.app.Pinned; import app.fedilab.android.mastodon.client.entities.app.Pinned;
import app.fedilab.android.mastodon.client.entities.app.PinnedTimeline; import app.fedilab.android.mastodon.client.entities.app.PinnedTimeline;
@ -199,16 +200,22 @@ public class ReorderTimelinesActivity extends BaseBarActivity implements OnStart
} else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.peertube_instance) { } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.peertube_instance) {
url = "https://" + instanceName + "/api/v1/videos/"; url = "https://" + instanceName + "/api/v1/videos/";
} else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.pixelfed_instance) { } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.pixelfed_instance) {
url = "https://" + instanceName + "/api/v1/timelines/public"; url = "https://" + instanceName + "/api/pixelfed/v2/discover/posts/trending?range=daily";
} else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.misskey_instance) { } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.misskey_instance) {
url = "https://" + instanceName + "/api/notes/local-timeline"; url = "https://" + instanceName + "/api/notes/local-timeline";
getCall = false; getCall = false;
} else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.lemmy_instance) {
url = "https://" + instanceName + "/api/v3/post/list";
} else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.gnu_instance) { } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.gnu_instance) {
url = "https://" + instanceName + "/api/statuses/public_timeline.json"; url = "https://" + instanceName + "/api/statuses/public_timeline.json";
} else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.twitter_accounts) { } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.twitter_accounts) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(ReorderTimelinesActivity.this); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(ReorderTimelinesActivity.this);
String nitterHost = sharedpreferences.getString(getString(R.string.SET_NITTER_HOST), getString(R.string.DEFAULT_NITTER_HOST)).toLowerCase(); String nitterHost = sharedpreferences.getString(getString(R.string.SET_NITTER_HOST), getString(R.string.DEFAULT_NITTER_HOST)).toLowerCase();
url = "https://" + nitterHost + "/" + instanceName.replaceAll("[ ]+", ",").replaceAll("\\s", "") + "/rss"; url = "https://" + nitterHost + "/" + instanceName.replaceAll("[ ]+", ",").replaceAll("\\s", "") + "/with_replies/rss";
}else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.twitter_tags) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(ReorderTimelinesActivity.this);
String nitterHost = sharedpreferences.getString(getString(R.string.SET_NITTER_HOST), getString(R.string.DEFAULT_NITTER_HOST)).toLowerCase();
url = "https://" + nitterHost + "/search?f=tweets&q=" + instanceName.replaceAll("[ ]+", "+or+").replaceAll("\\s", "") + "&e-nativeretweets=on";
} }
OkHttpClient client = new OkHttpClient.Builder() OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS) .connectTimeout(10, TimeUnit.SECONDS)
@ -248,10 +255,14 @@ public class ReorderTimelinesActivity extends BaseBarActivity implements OnStart
instanceType = RemoteInstance.InstanceType.PIXELFED; instanceType = RemoteInstance.InstanceType.PIXELFED;
} else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.misskey_instance) { } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.misskey_instance) {
instanceType = RemoteInstance.InstanceType.MISSKEY; instanceType = RemoteInstance.InstanceType.MISSKEY;
} else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.lemmy_instance) {
instanceType = RemoteInstance.InstanceType.LEMMY;
} else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.gnu_instance) { } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.gnu_instance) {
instanceType = RemoteInstance.InstanceType.GNU; instanceType = RemoteInstance.InstanceType.GNU;
} else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.twitter_accounts) { } else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.twitter_accounts) {
instanceType = RemoteInstance.InstanceType.NITTER; instanceType = RemoteInstance.InstanceType.NITTER;
} else if (popupSearchInstanceBinding.setAttachmentGroup.getCheckedRadioButtonId() == R.id.twitter_tags) {
instanceType = RemoteInstance.InstanceType.NITTER_TAG;
} }
RemoteInstance remoteInstance = new RemoteInstance(); RemoteInstance remoteInstance = new RemoteInstance();
remoteInstance.type = instanceType; remoteInstance.type = instanceType;
@ -280,11 +291,16 @@ public class ReorderTimelinesActivity extends BaseBarActivity implements OnStart
} }
} }
reorderTabAdapter.notifyItemInserted(pinned.pinnedTimelines.size()); reorderTabAdapter.notifyItemInserted(pinned.pinnedTimelines.size());
Bundle b = new Bundle(); Bundle args = new Bundle();
b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true); args.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Intent intentBD = new Intent(Helper.BROADCAST_DATA); Intent intentBD = new Intent(Helper.BROADCAST_DATA);
intentBD.putExtras(b); new CachedBundle(ReorderTimelinesActivity.this).insertBundle(args, Helper.getCurrentAccount(ReorderTimelinesActivity.this), bundleId -> {
LocalBroadcastManager.getInstance(ReorderTimelinesActivity.this).sendBroadcast(intentBD); Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentBD.putExtras(bundle);
intentBD.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intentBD);
});
}); });
} else { } else {
runOnUiThread(() -> Toasty.warning(ReorderTimelinesActivity.this, getString(R.string.toast_instance_unavailable), Toast.LENGTH_LONG).show()); runOnUiThread(() -> Toasty.warning(ReorderTimelinesActivity.this, getString(R.string.toast_instance_unavailable), Toast.LENGTH_LONG).show());
@ -368,18 +384,29 @@ public class ReorderTimelinesActivity extends BaseBarActivity implements OnStart
super.onPause(); super.onPause();
if (changes) { if (changes) {
//Update menu //Update menu
Bundle b = new Bundle(); Bundle args = new Bundle();
b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true); args.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Intent intentBD = new Intent(Helper.BROADCAST_DATA); Intent intentBD = new Intent(Helper.BROADCAST_DATA);
intentBD.putExtras(b); new CachedBundle(ReorderTimelinesActivity.this).insertBundle(args, Helper.getCurrentAccount(ReorderTimelinesActivity.this), bundleId -> {
LocalBroadcastManager.getInstance(ReorderTimelinesActivity.this).sendBroadcast(intentBD); Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentBD.putExtras(bundle);
intentBD.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intentBD);
});
} }
if (bottomChanges) { if (bottomChanges) {
Bundle b = new Bundle(); Bundle args = new Bundle();
b.putBoolean(Helper.RECEIVE_REDRAW_BOTTOM, true); args.putBoolean(Helper.RECEIVE_REDRAW_BOTTOM, true);
Intent intentBD = new Intent(Helper.BROADCAST_DATA); Intent intentBD = new Intent(Helper.BROADCAST_DATA);
intentBD.putExtras(b); new CachedBundle(ReorderTimelinesActivity.this).insertBundle(args, Helper.getCurrentAccount(ReorderTimelinesActivity.this), bundleId -> {
LocalBroadcastManager.getInstance(ReorderTimelinesActivity.this).sendBroadcast(intentBD); Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentBD.putExtras(bundle);
intentBD.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intentBD);
});
} }
} }

View file

@ -14,6 +14,7 @@ package app.fedilab.android.mastodon.activities;
* You should have received a copy of the GNU General Public License along with Fedilab; if not, * You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import android.os.Bundle; import android.os.Bundle;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -21,13 +22,12 @@ import android.widget.RadioButton;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.widget.LinearLayoutCompat; import androidx.appcompat.widget.LinearLayoutCompat;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import app.fedilab.android.BaseMainActivity; import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R; import app.fedilab.android.R;
@ -35,6 +35,7 @@ import app.fedilab.android.databinding.ActivityReportBinding;
import app.fedilab.android.mastodon.client.entities.api.Account; import app.fedilab.android.mastodon.client.entities.api.Account;
import app.fedilab.android.mastodon.client.entities.api.RelationShip; import app.fedilab.android.mastodon.client.entities.api.RelationShip;
import app.fedilab.android.mastodon.client.entities.api.Status; import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.client.entities.app.Timeline; import app.fedilab.android.mastodon.client.entities.app.Timeline;
import app.fedilab.android.mastodon.helper.Helper; import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.ui.drawer.RulesAdapter; import app.fedilab.android.mastodon.ui.drawer.RulesAdapter;
@ -69,11 +70,21 @@ public class ReportActivity extends BaseBarActivity {
if (getSupportActionBar() != null) { if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} }
Bundle args = getIntent().getExtras();
if (args != null) {
long bundleId = args.getLong(Helper.ARG_INTENT_ID, -1);
if (bundleId != -1) {
new CachedBundle(ReportActivity.this).getBundle(bundleId, Helper.getCurrentAccount(ReportActivity.this), this::initializeAfterBundle);
} else {
initializeAfterBundle(args);
}
}
}
Bundle b = getIntent().getExtras(); private void initializeAfterBundle(Bundle bundle) {
if (b != null) { if (bundle != null) {
status = (Status) b.getSerializable(Helper.ARG_STATUS); status = (Status) bundle.getSerializable(Helper.ARG_STATUS);
account = (Account) b.getSerializable(Helper.ARG_ACCOUNT); account = (Account) bundle.getSerializable(Helper.ARG_ACCOUNT);
} }
if (account == null && status != null) { if (account == null && status != null) {
account = status.account; account = status.account;
@ -138,7 +149,6 @@ public class ReportActivity extends BaseBarActivity {
}); });
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) { if (item.getItemId() == android.R.id.home) {
@ -221,21 +231,27 @@ public class ReportActivity extends BaseBarActivity {
private void switchToSpam() { private void switchToSpam() {
fragment = new FragmentMastodonTimeline(); fragment = new FragmentMastodonTimeline();
Bundle bundle = new Bundle(); Bundle args = new Bundle();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.ACCOUNT_TIMELINE); args.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.ACCOUNT_TIMELINE);
bundle.putSerializable(Helper.ARG_ACCOUNT, account); args.putBoolean(Helper.ARG_SHOW_PINNED, false);
args.putBoolean(Helper.ARG_SHOW_REPLIES, true);
args.putBoolean(Helper.ARG_SHOW_REBLOGS, false);
args.putBoolean(Helper.ARG_CHECK_REMOTELY, false);
args.putString(Helper.ARG_VIEW_MODEL_KEY, "FEDILAB_REPORT_" + account.acct);
if (account != null) {
args.putSerializable(Helper.ARG_CACHED_ACCOUNT_ID, account.id);
}
//Set to display statuses with less options //Set to display statuses with less options
bundle.putBoolean(Helper.ARG_MINIFIED, true); args.putBoolean(Helper.ARG_MINIFIED, true);
if (status != null) { if (status != null) {
status.isChecked = true; status.isChecked = true;
bundle.putSerializable(Helper.ARG_STATUS_REPORT, status); args.putSerializable(Helper.ARG_STATUS_REPORT, status);
} }
fragment.setArguments(bundle); new CachedBundle(ReportActivity.this).insertBundle(args, Helper.getCurrentAccount(ReportActivity.this), bundleId -> {
FragmentManager fragmentManager = getSupportFragmentManager(); Bundle bundle = new Bundle();
FragmentTransaction fragmentTransaction = bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
fragmentManager.beginTransaction(); Helper.addFragment(getSupportFragmentManager(), R.id.fram_spam_container, fragment, bundle, null, null);
fragmentTransaction.replace(R.id.fram_spam_container, fragment); });
fragmentTransaction.commit();
binding.actionButton.setText(R.string.next); binding.actionButton.setText(R.string.next);
binding.actionButton.setOnClickListener(v -> { binding.actionButton.setOnClickListener(v -> {
@ -247,22 +263,27 @@ public class ReportActivity extends BaseBarActivity {
private void switchToSomethingElse() { private void switchToSomethingElse() {
fragment = new FragmentMastodonTimeline(); fragment = new FragmentMastodonTimeline();
Bundle bundle = new Bundle(); Bundle args = new Bundle();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.ACCOUNT_TIMELINE); args.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.ACCOUNT_TIMELINE);
bundle.putSerializable(Helper.ARG_ACCOUNT, account); args.putBoolean(Helper.ARG_SHOW_PINNED, false);
args.putBoolean(Helper.ARG_SHOW_REPLIES, true);
args.putBoolean(Helper.ARG_SHOW_REBLOGS, false);
args.putBoolean(Helper.ARG_CHECK_REMOTELY, false);
args.putString(Helper.ARG_VIEW_MODEL_KEY, "FEDILAB_REPORT_" + account.acct);
if (account != null) {
args.putSerializable(Helper.ARG_CACHED_ACCOUNT_ID, account.id);
}
//Set to display statuses with less options //Set to display statuses with less options
bundle.putBoolean(Helper.ARG_MINIFIED, true); args.putBoolean(Helper.ARG_MINIFIED, true);
if (status != null) { if (status != null) {
status.isChecked = true; status.isChecked = true;
bundle.putSerializable(Helper.ARG_STATUS_REPORT, status); args.putSerializable(Helper.ARG_STATUS_REPORT, status);
} }
fragment.setArguments(bundle); new CachedBundle(ReportActivity.this).insertBundle(args, Helper.getCurrentAccount(ReportActivity.this), bundleId -> {
FragmentManager fragmentManager = getSupportFragmentManager(); Bundle bundle = new Bundle();
FragmentTransaction fragmentTransaction = bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
fragmentManager.beginTransaction(); Helper.addFragment(getSupportFragmentManager(), R.id.fram_se_container, fragment, bundle, null, null);
fragmentTransaction.replace(R.id.fram_se_container, fragment); });
fragmentTransaction.commit();
binding.actionButton.setText(R.string.next); binding.actionButton.setText(R.string.next);
binding.actionButton.setOnClickListener(v -> { binding.actionButton.setOnClickListener(v -> {
if (category == null) { if (category == null) {
@ -291,7 +312,7 @@ public class ReportActivity extends BaseBarActivity {
if (fragment != null) { if (fragment != null) {
statusIds = fragment.getCheckedStatusesId(); statusIds = fragment.getCheckedStatusesId();
} }
comment = binding.reportMessage.getText().toString(); comment = Objects.requireNonNull(binding.reportMessage.getText()).toString();
binding.actionButton.setEnabled(false); binding.actionButton.setEnabled(false);
accountsVM.report(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, category, statusIds, ruleIds, comment, forward) accountsVM.report(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, category, statusIds, ruleIds, comment, forward)
.observe(ReportActivity.this, report -> { .observe(ReportActivity.this, report -> {

View file

@ -14,7 +14,6 @@ package app.fedilab.android.mastodon.activities;
* You should have received a copy of the GNU General Public License along with Fedilab; if not, * You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentAccount;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
@ -29,6 +28,7 @@ import com.google.android.material.tabs.TabLayout;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.databinding.ActivityScheduledBinding; import app.fedilab.android.databinding.ActivityScheduledBinding;
import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.MastodonHelper; import app.fedilab.android.mastodon.helper.MastodonHelper;
import app.fedilab.android.mastodon.ui.pageadapter.FedilabScheduledPageAdapter; import app.fedilab.android.mastodon.ui.pageadapter.FedilabScheduledPageAdapter;
@ -55,7 +55,7 @@ public class ScheduledActivity extends BaseActivity {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(this);
float scale = sharedpreferences.getFloat(getString(R.string.SET_FONT_SCALE), 1.1f); float scale = sharedpreferences.getFloat(getString(R.string.SET_FONT_SCALE), 1.1f);
binding.title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18 * 1.1f / scale); binding.title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18 * 1.1f / scale);
MastodonHelper.loadPPMastodon(binding.profilePicture, currentAccount.mastodon_account); MastodonHelper.loadPPMastodon(binding.profilePicture, Helper.getCurrentAccount(ScheduledActivity.this).mastodon_account);
binding.title.setText(R.string.scheduled); binding.title.setText(R.string.scheduled);
binding.scheduleTablayout.addTab(binding.scheduleTablayout.newTab().setText(getString(R.string.toots_server))); binding.scheduleTablayout.addTab(binding.scheduleTablayout.newTab().setText(getString(R.string.toots_server)));
binding.scheduleTablayout.addTab(binding.scheduleTablayout.newTab().setText(getString(R.string.toots_client))); binding.scheduleTablayout.addTab(binding.scheduleTablayout.newTab().setText(getString(R.string.toots_client)));

View file

@ -67,10 +67,10 @@ import es.dmoral.toasty.Toasty;
public class SearchResultTabActivity extends BaseBarActivity { public class SearchResultTabActivity extends BaseBarActivity {
public Boolean tagEmpty, accountEmpty;
private String search; private String search;
private ActivitySearchResultTabsBinding binding; private ActivitySearchResultTabsBinding binding;
private TabLayout.Tab initial; private TabLayout.Tab initial;
public Boolean tagEmpty, accountEmpty;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -82,7 +82,6 @@ public class SearchResultTabActivity extends BaseBarActivity {
Bundle b = getIntent().getExtras(); Bundle b = getIntent().getExtras();
if (b != null) { if (b != null) {
search = b.getString(Helper.ARG_SEARCH_KEYWORD, null); search = b.getString(Helper.ARG_SEARCH_KEYWORD, null);
} }
if (search == null) { if (search == null) {
Toasty.error(SearchResultTabActivity.this, getString(R.string.toast_error_search), Toast.LENGTH_LONG).show(); Toasty.error(SearchResultTabActivity.this, getString(R.string.toast_error_search), Toast.LENGTH_LONG).show();
@ -114,14 +113,11 @@ public class SearchResultTabActivity extends BaseBarActivity {
Fragment fragment; Fragment fragment;
if (binding.searchViewpager.getAdapter() != null) { if (binding.searchViewpager.getAdapter() != null) {
fragment = (Fragment) binding.searchViewpager.getAdapter().instantiateItem(binding.searchViewpager, tab.getPosition()); fragment = (Fragment) binding.searchViewpager.getAdapter().instantiateItem(binding.searchViewpager, tab.getPosition());
if (fragment instanceof FragmentMastodonAccount) { if (fragment instanceof FragmentMastodonAccount fragmentMastodonAccount) {
FragmentMastodonAccount fragmentMastodonAccount = ((FragmentMastodonAccount) fragment);
fragmentMastodonAccount.scrollToTop(); fragmentMastodonAccount.scrollToTop();
} else if (fragment instanceof FragmentMastodonTimeline) { } else if (fragment instanceof FragmentMastodonTimeline fragmentMastodonTimeline) {
FragmentMastodonTimeline fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
fragmentMastodonTimeline.scrollToTop(); fragmentMastodonTimeline.scrollToTop();
} else if (fragment instanceof FragmentMastodonTag) { } else if (fragment instanceof FragmentMastodonTag fragmentMastodonTag) {
FragmentMastodonTag fragmentMastodonTag = ((FragmentMastodonTag) fragment);
fragmentMastodonTag.scrollToTop(); fragmentMastodonTag.scrollToTop();
} }
} }
@ -136,6 +132,9 @@ public class SearchResultTabActivity extends BaseBarActivity {
inflater.inflate(R.menu.menu_search, menu); inflater.inflate(R.menu.menu_search, menu);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView(); SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
if (searchView == null) {
return true;
}
if (search != null) { if (search != null) {
searchView.setQuery(search, false); searchView.setQuery(search, false);
} }
@ -280,13 +279,13 @@ public class SearchResultTabActivity extends BaseBarActivity {
public void moveToAccount() { public void moveToAccount() {
tagEmpty = null; tagEmpty = null;
accountEmpty = null; accountEmpty = null;
binding.searchViewpager.setCurrentItem(1); binding.searchViewpager.post(() -> binding.searchViewpager.setCurrentItem(1));
} }
public void moveToMessage() { public void moveToMessage() {
tagEmpty = null; tagEmpty = null;
accountEmpty = null; accountEmpty = null;
binding.searchViewpager.setCurrentItem(2); binding.searchViewpager.post(() -> binding.searchViewpager.setCurrentItem(2));
} }
/** /**
@ -302,27 +301,32 @@ public class SearchResultTabActivity extends BaseBarActivity {
@Override @Override
public Fragment getItem(int position) { public Fragment getItem(int position) {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
FragmentMastodonTimeline fragmentMastodonTimeline;
switch (position) { switch (position) {
case 0: case 0 -> {
FragmentMastodonTag fragmentMastodonTag = new FragmentMastodonTag(); FragmentMastodonTag fragmentMastodonTag = new FragmentMastodonTag();
bundle.putString(Helper.ARG_SEARCH_KEYWORD, search); bundle.putString(Helper.ARG_SEARCH_KEYWORD, search);
fragmentMastodonTag.setArguments(bundle); fragmentMastodonTag.setArguments(bundle);
return fragmentMastodonTag; return fragmentMastodonTag;
case 1: }
case 1 -> {
FragmentMastodonAccount fragmentMastodonAccount = new FragmentMastodonAccount(); FragmentMastodonAccount fragmentMastodonAccount = new FragmentMastodonAccount();
bundle.putString(Helper.ARG_SEARCH_KEYWORD, search); bundle.putString(Helper.ARG_SEARCH_KEYWORD, search);
fragmentMastodonAccount.setArguments(bundle); fragmentMastodonAccount.setArguments(bundle);
return fragmentMastodonAccount; return fragmentMastodonAccount;
case 2: }
FragmentMastodonTimeline fragmentMastodonTimeline = new FragmentMastodonTimeline(); case 2 -> {
fragmentMastodonTimeline = new FragmentMastodonTimeline();
bundle.putString(Helper.ARG_SEARCH_KEYWORD, search); bundle.putString(Helper.ARG_SEARCH_KEYWORD, search);
fragmentMastodonTimeline.setArguments(bundle); fragmentMastodonTimeline.setArguments(bundle);
return fragmentMastodonTimeline; return fragmentMastodonTimeline;
default: }
default -> {
fragmentMastodonTimeline = new FragmentMastodonTimeline(); fragmentMastodonTimeline = new FragmentMastodonTimeline();
bundle.putString(Helper.ARG_SEARCH_KEYWORD_CACHE, search); bundle.putString(Helper.ARG_SEARCH_KEYWORD_CACHE, search);
fragmentMastodonTimeline.setArguments(bundle); fragmentMastodonTimeline.setArguments(bundle);
return fragmentMastodonTimeline; return fragmentMastodonTimeline;
}
} }
} }

View file

@ -15,6 +15,9 @@ package app.fedilab.android.mastodon.activities;
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentInstance;
import static app.fedilab.android.BaseMainActivity.currentToken;
import android.os.Bundle; import android.os.Bundle;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -24,16 +27,18 @@ import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.databinding.ActivityStatusInfoBinding; import app.fedilab.android.databinding.ActivityStatusInfoBinding;
import app.fedilab.android.mastodon.client.entities.api.Account; import app.fedilab.android.mastodon.client.entities.api.Account;
import app.fedilab.android.mastodon.client.entities.api.Accounts; import app.fedilab.android.mastodon.client.entities.api.Accounts;
import app.fedilab.android.mastodon.client.entities.api.RelationShip; import app.fedilab.android.mastodon.client.entities.api.RelationShip;
import app.fedilab.android.mastodon.client.entities.api.Status; import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.helper.Helper; import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.ui.drawer.AccountAdapter; import app.fedilab.android.mastodon.ui.drawer.AccountAdapter;
import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM; import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM;
@ -50,6 +55,9 @@ public class StatusInfoActivity extends BaseActivity {
private boolean flagLoading; private boolean flagLoading;
private Status status; private Status status;
private boolean checkRemotely;
private String instance, token;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -62,20 +70,52 @@ public class StatusInfoActivity extends BaseActivity {
getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true);
} }
accountList = new ArrayList<>(); accountList = new ArrayList<>();
Bundle b = getIntent().getExtras(); checkRemotely = false;
if (b != null) { Bundle args = getIntent().getExtras();
type = (typeOfInfo) b.getSerializable(Helper.ARG_TYPE_OF_INFO); if (args != null) {
status = (Status) b.getSerializable(Helper.ARG_STATUS); long bundleId = args.getLong(Helper.ARG_INTENT_ID, -1);
new CachedBundle(StatusInfoActivity.this).getBundle(bundleId, Helper.getCurrentAccount(StatusInfoActivity.this), this::initializeAfterBundle);
} else {
initializeAfterBundle(null);
} }
}
private void initializeAfterBundle(Bundle bundle) {
if (bundle != null) {
type = (typeOfInfo) bundle.getSerializable(Helper.ARG_TYPE_OF_INFO);
status = (Status) bundle.getSerializable(Helper.ARG_STATUS);
checkRemotely = bundle.getBoolean(Helper.ARG_CHECK_REMOTELY, false);
}
if (type == null || status == null) { if (type == null || status == null) {
finish(); finish();
return; return;
} }
token = currentToken;
instance = currentInstance;
if (checkRemotely) {
try {
URL url = new URL(status.uri);
instance = url.getHost();
} catch (MalformedURLException e) {
e.printStackTrace();
}
token = null;
if (instance != null && instance.equalsIgnoreCase(currentInstance)) {
checkRemotely = false;
instance = currentInstance;
token = currentToken;
}
}
flagLoading = false; flagLoading = false;
max_id = null; max_id = null;
setTitle("");
binding.title.setText(type == typeOfInfo.BOOSTED_BY ? R.string.boosted_by : R.string.favourited_by); binding.title.setText(type == typeOfInfo.BOOSTED_BY ? R.string.boosted_by : R.string.favourited_by);
StatusesVM statusesVM = new ViewModelProvider(StatusInfoActivity.this).get(StatusesVM.class); StatusesVM statusesVM = new ViewModelProvider(StatusInfoActivity.this).get(StatusesVM.class);
accountAdapter = new AccountAdapter(accountList); accountAdapter = new AccountAdapter(accountList, false, checkRemotely ? instance : null);
LinearLayoutManager mLayoutManager = new LinearLayoutManager(StatusInfoActivity.this); LinearLayoutManager mLayoutManager = new LinearLayoutManager(StatusInfoActivity.this);
binding.lvAccounts.setLayoutManager(mLayoutManager); binding.lvAccounts.setLayoutManager(mLayoutManager);
binding.lvAccounts.setAdapter(accountAdapter); binding.lvAccounts.setAdapter(accountAdapter);
@ -92,9 +132,9 @@ public class StatusInfoActivity extends BaseActivity {
flagLoading = true; flagLoading = true;
binding.loadingNextAccounts.setVisibility(View.VISIBLE); binding.loadingNextAccounts.setVisibility(View.VISIBLE);
if (type == typeOfInfo.BOOSTED_BY) { if (type == typeOfInfo.BOOSTED_BY) {
statusesVM.rebloggedBy(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, status.id, max_id, null, null).observe(StatusInfoActivity.this, accounts -> manageView(accounts)); statusesVM.rebloggedBy(instance, token, status.id, max_id, null, null).observe(StatusInfoActivity.this, accounts -> manageView(accounts));
} else if (type == typeOfInfo.LIKED_BY) { } else if (type == typeOfInfo.LIKED_BY) {
statusesVM.favouritedBy(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, status.id, max_id, null, null).observe(StatusInfoActivity.this, accounts -> manageView(accounts)); statusesVM.favouritedBy(instance, token, status.id, max_id, null, null).observe(StatusInfoActivity.this, accounts -> manageView(accounts));
} }
} }
} else { } else {
@ -104,22 +144,22 @@ public class StatusInfoActivity extends BaseActivity {
} }
}); });
if (type == typeOfInfo.BOOSTED_BY) { if (type == typeOfInfo.BOOSTED_BY) {
statusesVM.rebloggedBy(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, status.id, null, null, null).observe(StatusInfoActivity.this, this::manageView); statusesVM.rebloggedBy(instance, token, status.id, null, null, null).observe(StatusInfoActivity.this, this::manageView);
} else if (type == typeOfInfo.LIKED_BY) { } else if (type == typeOfInfo.LIKED_BY) {
statusesVM.favouritedBy(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, status.id, null, null, null).observe(StatusInfoActivity.this, this::manageView); statusesVM.favouritedBy(instance, token, status.id, null, null, null).observe(StatusInfoActivity.this, this::manageView);
} }
} }
private void manageView(Accounts accounts) { private void manageView(Accounts accounts) {
binding.loadingNextAccounts.setVisibility(View.GONE); binding.loadingNextAccounts.setVisibility(View.GONE);
if (accountList != null && accounts != null && accounts.accounts != null) { if (accountList != null && accounts != null && accounts.accounts != null) {
int position = this.accountList.size(); int position = this.accountList.size();
fetchRelationShip(accounts.accounts, position); if (!checkRemotely) {
int startId = 0; fetchRelationShip(accounts.accounts, position);
//There are some statuses present in the timeline
if (accountList.size() > 0) {
startId = accountList.size();
} }
//There are some statuses present in the timeline
int startId = accountList.size();
accountList.addAll(accounts.accounts); accountList.addAll(accounts.accounts);
max_id = accounts.pagination.max_id; max_id = accounts.pagination.max_id;
flagLoading = accounts.pagination.max_id == null; flagLoading = accounts.pagination.max_id == null;
@ -133,7 +173,7 @@ public class StatusInfoActivity extends BaseActivity {
ids.add(account.id); ids.add(account.id);
} }
AccountsVM accountsVM = new ViewModelProvider(StatusInfoActivity.this).get(AccountsVM.class); AccountsVM accountsVM = new ViewModelProvider(StatusInfoActivity.this).get(AccountsVM.class);
accountsVM.getRelationships(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, ids) accountsVM.getRelationships(instance, token, ids)
.observe(StatusInfoActivity.this, relationShips -> { .observe(StatusInfoActivity.this, relationShips -> {
if (relationShips != null) { if (relationShips != null) {
for (RelationShip relationShip : relationShips) { for (RelationShip relationShip : relationShips) {

View file

@ -0,0 +1,106 @@
package app.fedilab.android.mastodon.activities;
/* Copyright 2023 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import android.os.Bundle;
import android.view.MenuItem;
import org.jetbrains.annotations.NotNull;
import app.fedilab.android.R;
import app.fedilab.android.databinding.ActivityTimelineBinding;
import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.client.entities.app.PinnedTimeline;
import app.fedilab.android.mastodon.client.entities.app.Timeline;
import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.ui.fragment.timeline.FragmentMastodonTimeline;
public class TimelineActivity extends BaseBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
app.fedilab.android.databinding.ActivityTimelineBinding binding = ActivityTimelineBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
Bundle args = getIntent().getExtras();
if (args != null) {
long bundleId = args.getLong(Helper.ARG_INTENT_ID, -1);
new CachedBundle(TimelineActivity.this).getBundle(bundleId, Helper.getCurrentAccount(TimelineActivity.this), this::initializeAfterBundle);
} else {
initializeAfterBundle(null);
}
}
private void initializeAfterBundle(Bundle bundle) {
Timeline.TimeLineEnum timelineType = null;
String lemmy_post_id = null;
PinnedTimeline pinnedTimeline = null;
String tagged = null;
String timelineAccountId = null;
Status status = null;
if (bundle != null) {
timelineType = (Timeline.TimeLineEnum) bundle.get(Helper.ARG_TIMELINE_TYPE);
lemmy_post_id = bundle.getString(Helper.ARG_LEMMY_POST_ID, null);
pinnedTimeline = (PinnedTimeline) bundle.getSerializable(Helper.ARG_REMOTE_INSTANCE);
status = (Status) bundle.getSerializable(Helper.ARG_STATUS);
tagged = bundle.getString(Helper.ARG_TAGGED, null);
timelineAccountId = bundle.getString(Helper.ARG_CACHED_ACCOUNT_ID, null);
}
if (pinnedTimeline != null && pinnedTimeline.remoteInstance != null) {
setTitle(pinnedTimeline.remoteInstance.host);
}
if(tagged != null) {
setTitle(String.format("#%s",tagged));
}
FragmentMastodonTimeline fragmentMastodonTimeline = new FragmentMastodonTimeline();
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_TIMELINE_TYPE, timelineType);
args.putSerializable(Helper.ARG_REMOTE_INSTANCE, pinnedTimeline);
args.putSerializable(Helper.ARG_LEMMY_POST_ID, lemmy_post_id);
args.putSerializable(Helper.ARG_TAGGED, tagged);
args.putSerializable(Helper.ARG_CACHED_ACCOUNT_ID, timelineAccountId);
if (status != null) {
args.putSerializable(Helper.ARG_STATUS, status);
}
new CachedBundle(TimelineActivity.this).insertBundle(args, Helper.getCurrentAccount(TimelineActivity.this), bundleId -> {
Bundle bundle1 = new Bundle();
bundle1.putLong(Helper.ARG_INTENT_ID, bundleId);
fragmentMastodonTimeline.setArguments(bundle1);
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container_view, fragmentMastodonTimeline).commit();
});
}
@Override
public boolean onOptionsItemSelected(@NotNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View file

@ -33,6 +33,7 @@ import app.fedilab.android.R;
import app.fedilab.android.databinding.ActivityTrendsBinding; import app.fedilab.android.databinding.ActivityTrendsBinding;
import app.fedilab.android.mastodon.client.entities.app.Timeline; import app.fedilab.android.mastodon.client.entities.app.Timeline;
import app.fedilab.android.mastodon.helper.Helper; import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.ui.fragment.timeline.FragmentMastodonLink;
import app.fedilab.android.mastodon.ui.fragment.timeline.FragmentMastodonTag; import app.fedilab.android.mastodon.ui.fragment.timeline.FragmentMastodonTag;
import app.fedilab.android.mastodon.ui.fragment.timeline.FragmentMastodonTimeline; import app.fedilab.android.mastodon.ui.fragment.timeline.FragmentMastodonTimeline;
@ -56,6 +57,7 @@ public class TrendsActivity extends BaseBarActivity {
binding.searchTabLayout.addTab(binding.searchTabLayout.newTab().setText(getString(R.string.tags))); binding.searchTabLayout.addTab(binding.searchTabLayout.newTab().setText(getString(R.string.tags)));
binding.searchTabLayout.addTab(binding.searchTabLayout.newTab().setText(getString(R.string.toots))); binding.searchTabLayout.addTab(binding.searchTabLayout.newTab().setText(getString(R.string.toots)));
binding.searchTabLayout.addTab(binding.searchTabLayout.newTab().setText(getString(R.string.links)));
binding.searchTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { binding.searchTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override @Override
public void onTabSelected(TabLayout.Tab tab) { public void onTabSelected(TabLayout.Tab tab) {
@ -71,12 +73,12 @@ public class TrendsActivity extends BaseBarActivity {
Fragment fragment; Fragment fragment;
if (binding.trendsViewpager.getAdapter() != null) { if (binding.trendsViewpager.getAdapter() != null) {
fragment = (Fragment) binding.trendsViewpager.getAdapter().instantiateItem(binding.trendsViewpager, tab.getPosition()); fragment = (Fragment) binding.trendsViewpager.getAdapter().instantiateItem(binding.trendsViewpager, tab.getPosition());
if (fragment instanceof FragmentMastodonTimeline) { if (fragment instanceof FragmentMastodonTimeline fragmentMastodonTimeline) {
FragmentMastodonTimeline fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
fragmentMastodonTimeline.scrollToTop(); fragmentMastodonTimeline.scrollToTop();
} else if (fragment instanceof FragmentMastodonTag) { } else if (fragment instanceof FragmentMastodonTag fragmentMastodonTag) {
FragmentMastodonTag fragmentMastodonTag = ((FragmentMastodonTag) fragment);
fragmentMastodonTag.scrollToTop(); fragmentMastodonTag.scrollToTop();
}else if (fragment instanceof FragmentMastodonLink fragmentMastodonLink) {
fragmentMastodonLink.scrollToTop();
} }
} }
} }
@ -131,11 +133,17 @@ public class TrendsActivity extends BaseBarActivity {
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.TREND_TAG); bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.TREND_TAG);
fragmentMastodonTag.setArguments(bundle); fragmentMastodonTag.setArguments(bundle);
return fragmentMastodonTag; return fragmentMastodonTag;
} else if(position == 1) {
FragmentMastodonTimeline fragmentMastodonTimeline = new FragmentMastodonTimeline();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.TREND_MESSAGE);
fragmentMastodonTimeline.setArguments(bundle);
return fragmentMastodonTimeline;
} else {
FragmentMastodonLink fragmentMastodonLink = new FragmentMastodonLink();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.TREND_LINK);
fragmentMastodonLink.setArguments(bundle);
return fragmentMastodonLink;
} }
FragmentMastodonTimeline fragmentMastodonTimeline = new FragmentMastodonTimeline();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.TREND_MESSAGE);
fragmentMastodonTimeline.setArguments(bundle);
return fragmentMastodonTimeline;
} }
@Override @Override
@ -144,7 +152,7 @@ public class TrendsActivity extends BaseBarActivity {
@Override @Override
public int getCount() { public int getCount() {
return 2; return 3;
} }
} }
} }

View file

@ -61,6 +61,7 @@ import app.fedilab.android.mastodon.activities.MediaActivity;
import app.fedilab.android.mastodon.client.entities.api.Attachment; import app.fedilab.android.mastodon.client.entities.api.Attachment;
import app.fedilab.android.mastodon.client.entities.api.admin.AdminAccount; import app.fedilab.android.mastodon.client.entities.api.admin.AdminAccount;
import app.fedilab.android.mastodon.client.entities.api.admin.AdminIp; import app.fedilab.android.mastodon.client.entities.api.admin.AdminIp;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.helper.Helper; import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.MastodonHelper; import app.fedilab.android.mastodon.helper.MastodonHelper;
import app.fedilab.android.mastodon.helper.SpannableHelper; import app.fedilab.android.mastodon.helper.SpannableHelper;
@ -87,18 +88,32 @@ public class AdminAccountActivity extends BaseActivity {
setContentView(binding.getRoot()); setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar); setSupportActionBar(binding.toolbar);
ActionBar actionBar = getSupportActionBar(); ActionBar actionBar = getSupportActionBar();
Bundle b = getIntent().getExtras(); Bundle args = getIntent().getExtras();
adminAccount = null; adminAccount = null;
if (b != null) {
adminAccount = (AdminAccount) b.getSerializable(Helper.ARG_ACCOUNT);
account_id = b.getString(Helper.ARG_ACCOUNT_ID, null);
}
postponeEnterTransition(); postponeEnterTransition();
//Remove title //Remove title
if (actionBar != null) { if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(false); actionBar.setDisplayShowTitleEnabled(false);
} }
if (args != null) {
long bundleId = args.getLong(Helper.ARG_INTENT_ID, -1);
new CachedBundle(AdminAccountActivity.this).getBundle(bundleId, Helper.getCurrentAccount(AdminAccountActivity.this), this::initializeAfterBundle);
} else {
initializeAfterBundle(null);
}
}
private void initializeAfterBundle(Bundle bundle) {
if (bundle != null) {
adminAccount = (AdminAccount) bundle.getSerializable(Helper.ARG_ACCOUNT);
account_id = bundle.getString(Helper.ARG_ACCOUNT_ID, null);
}
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(this);
float scale = sharedpreferences.getFloat(getString(R.string.SET_FONT_SCALE), 1.1f); float scale = sharedpreferences.getFloat(getString(R.string.SET_FONT_SCALE), 1.1f);
binding.title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18 * 1.1f / scale); binding.title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18 * 1.1f / scale);
@ -175,8 +190,6 @@ public class AdminAccountActivity extends BaseActivity {
initializeView(adminAccount); initializeView(adminAccount);
} }
}); });
} }
private void initializeView(AdminAccount adminAccount) { private void initializeView(AdminAccount adminAccount) {
@ -310,7 +323,7 @@ public class AdminAccountActivity extends BaseActivity {
binding.accountDn.setText( binding.accountDn.setText(
adminAccount.account.getSpanDisplayName(AdminAccountActivity.this, adminAccount.account.getSpanDisplayNameEmoji(AdminAccountActivity.this,
new WeakReference<>(binding.accountDn)), new WeakReference<>(binding.accountDn)),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
binding.accountUn.setText(String.format("@%s", adminAccount.account.acct)); binding.accountUn.setText(String.format("@%s", adminAccount.account.acct));
@ -329,7 +342,7 @@ public class AdminAccountActivity extends BaseActivity {
MastodonHelper.loadPPMastodon(binding.accountPp, adminAccount.account); MastodonHelper.loadPPMastodon(binding.accountPp, adminAccount.account);
binding.accountPp.setOnClickListener(v -> { binding.accountPp.setOnClickListener(v -> {
Intent intent = new Intent(AdminAccountActivity.this, MediaActivity.class); Intent intent = new Intent(AdminAccountActivity.this, MediaActivity.class);
Bundle b = new Bundle(); Bundle args = new Bundle();
Attachment attachment = new Attachment(); Attachment attachment = new Attachment();
attachment.description = adminAccount.account.acct; attachment.description = adminAccount.account.acct;
attachment.preview_url = adminAccount.account.avatar; attachment.preview_url = adminAccount.account.avatar;
@ -338,13 +351,17 @@ public class AdminAccountActivity extends BaseActivity {
attachment.type = "image"; attachment.type = "image";
ArrayList<Attachment> attachments = new ArrayList<>(); ArrayList<Attachment> attachments = new ArrayList<>();
attachments.add(attachment); attachments.add(attachment);
b.putSerializable(Helper.ARG_MEDIA_ARRAY, attachments); args.putSerializable(Helper.ARG_MEDIA_ARRAY, attachments);
b.putInt(Helper.ARG_MEDIA_POSITION, 1); args.putInt(Helper.ARG_MEDIA_POSITION, 1);
intent.putExtras(b); new CachedBundle(AdminAccountActivity.this).insertBundle(args, Helper.getCurrentAccount(AdminAccountActivity.this), bundleId -> {
ActivityOptionsCompat options = ActivityOptionsCompat Bundle bundle = new Bundle();
.makeSceneTransitionAnimation(AdminAccountActivity.this, binding.accountPp, attachment.url); bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
// start the new activity intent.putExtras(bundle);
startActivity(intent, options.toBundle()); ActivityOptionsCompat options = ActivityOptionsCompat
.makeSceneTransitionAnimation(AdminAccountActivity.this, binding.accountPp, attachment.url);
// start the new activity
startActivity(intent, options.toBundle());
});
}); });

View file

@ -14,6 +14,7 @@ package app.fedilab.android.mastodon.activities.admin;
* You should have received a copy of the GNU General Public License along with Fedilab; if not, * You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.mastodon.activities.admin.AdminActionActivity.AdminEnum.ACCOUNT; import static app.fedilab.android.mastodon.activities.admin.AdminActionActivity.AdminEnum.ACCOUNT;
import static app.fedilab.android.mastodon.activities.admin.AdminActionActivity.AdminEnum.DOMAIN; import static app.fedilab.android.mastodon.activities.admin.AdminActionActivity.AdminEnum.DOMAIN;
import static app.fedilab.android.mastodon.activities.admin.AdminActionActivity.AdminEnum.REPORT; import static app.fedilab.android.mastodon.activities.admin.AdminActionActivity.AdminEnum.REPORT;
@ -26,12 +27,13 @@ import android.os.Bundle;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
@ -42,6 +44,7 @@ import app.fedilab.android.databinding.PopupAdminFilterAccountsBinding;
import app.fedilab.android.databinding.PopupAdminFilterReportsBinding; import app.fedilab.android.databinding.PopupAdminFilterReportsBinding;
import app.fedilab.android.mastodon.activities.BaseBarActivity; import app.fedilab.android.mastodon.activities.BaseBarActivity;
import app.fedilab.android.mastodon.client.entities.api.admin.AdminDomainBlock; import app.fedilab.android.mastodon.client.entities.api.admin.AdminDomainBlock;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.helper.Helper; import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.ThemeHelper; import app.fedilab.android.mastodon.helper.ThemeHelper;
import app.fedilab.android.mastodon.ui.fragment.admin.FragmentAdminAccount; import app.fedilab.android.mastodon.ui.fragment.admin.FragmentAdminAccount;
@ -62,18 +65,21 @@ public class AdminActionActivity extends BaseBarActivity {
private final BroadcastReceiver mReceiver = new BroadcastReceiver() { private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
Bundle b = intent.getExtras(); Bundle args = intent.getExtras();
if (b != null) { if (args != null) {
AdminDomainBlock adminDomainBlock = (AdminDomainBlock) b.getSerializable(Helper.ARG_ADMIN_DOMAINBLOCK); long bundleId = args.getLong(Helper.ARG_INTENT_ID, -1);
AdminDomainBlock adminDomainBlockDelete = (AdminDomainBlock) b.getSerializable(Helper.ARG_ADMIN_DOMAINBLOCK_DELETE); new CachedBundle(AdminActionActivity.this).getBundle(bundleId, Helper.getCurrentAccount(AdminActionActivity.this), bundle -> {
if (adminDomainBlock != null && adminDomainBlock.domain != null && fragmentAdminDomain != null) { AdminDomainBlock adminDomainBlock = (AdminDomainBlock) bundle.getSerializable(Helper.ARG_ADMIN_DOMAINBLOCK);
fragmentAdminDomain.update(adminDomainBlock); AdminDomainBlock adminDomainBlockDelete = (AdminDomainBlock) bundle.getSerializable(Helper.ARG_ADMIN_DOMAINBLOCK_DELETE);
} if (adminDomainBlock != null && adminDomainBlock.domain != null && fragmentAdminDomain != null) {
if (adminDomainBlockDelete != null && fragmentAdminDomain != null) { fragmentAdminDomain.update(adminDomainBlock);
fragmentAdminDomain.delete(adminDomainBlockDelete); }
} if (adminDomainBlockDelete != null && fragmentAdminDomain != null) {
} fragmentAdminDomain.delete(adminDomainBlockDelete);
}
});
}
} }
}; };
@ -83,7 +89,9 @@ public class AdminActionActivity extends BaseBarActivity {
binding = ActivityAdminActionsBinding.inflate(getLayoutInflater()); binding = ActivityAdminActionsBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot()); setContentView(binding.getRoot());
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, new IntentFilter(Helper.BROADCAST_DATA));
ContextCompat.registerReceiver(AdminActionActivity.this, mReceiver, new IntentFilter(Helper.BROADCAST_DATA), ContextCompat.RECEIVER_NOT_EXPORTED);
if (getSupportActionBar() != null) { if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} }
@ -92,60 +100,84 @@ public class AdminActionActivity extends BaseBarActivity {
binding.accounts.setOnClickListener(v -> displayTimeline(ACCOUNT)); binding.accounts.setOnClickListener(v -> displayTimeline(ACCOUNT));
binding.domains.setOnClickListener(v -> displayTimeline(DOMAIN)); binding.domains.setOnClickListener(v -> displayTimeline(DOMAIN));
getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (canGoBack) {
canGoBack = false;
ThemeHelper.slideViewsToRight(binding.fragmentContainer, binding.buttonContainer, () -> {
if (fragmentAdminReport != null) {
fragmentAdminReport.onDestroyView();
fragmentAdminReport = null;
}
if (fragmentAdminAccount != null) {
fragmentAdminAccount.onDestroyView();
fragmentAdminAccount = null;
}
if (fragmentAdminDomain != null) {
fragmentAdminDomain.onDestroyView();
fragmentAdminDomain = null;
}
setTitle(R.string.administration);
invalidateOptionsMenu();
});
} else {
finish();
}
}
});
} }
private void displayTimeline(AdminEnum type) { private void displayTimeline(AdminEnum type) {
canGoBack = true; canGoBack = true;
if (type == REPORT) { if (type == REPORT) {
fragmentAdminReport = new FragmentAdminReport();
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, type);
bundle.putString(Helper.ARG_VIEW_MODEL_KEY, "FEDILAB_" + type.getValue());
fragmentAdminReport.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragmentAdminReport);
fragmentTransaction.commit();
ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> { ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> {
fragmentAdminReport = new FragmentAdminReport();
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, type);
bundle.putString(Helper.ARG_VIEW_MODEL_KEY, "FEDILAB_" + type.getValue());
fragmentAdminReport.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragmentAdminReport);
fragmentTransaction.commit();
}); });
} else if (type == ACCOUNT) { } else if (type == ACCOUNT) {
fragmentAdminAccount = new FragmentAdminAccount();
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, type);
bundle.putString(Helper.ARG_VIEW_MODEL_KEY, "FEDILAB_" + type.getValue());
fragmentAdminAccount.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragmentAdminAccount);
fragmentTransaction.commit();
ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> { ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> {
fragmentAdminAccount = new FragmentAdminAccount();
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, type);
bundle.putString(Helper.ARG_VIEW_MODEL_KEY, "FEDILAB_" + type.getValue());
fragmentAdminAccount.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragmentAdminAccount);
fragmentTransaction.commit();
}); });
} else if (type == DOMAIN) { } else if (type == DOMAIN) {
fragmentAdminDomain = new FragmentAdminDomain();
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, type);
bundle.putString(Helper.ARG_VIEW_MODEL_KEY, "FEDILAB_" + type.getValue());
fragmentAdminDomain.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragmentAdminDomain);
fragmentTransaction.commit();
ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> { ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> {
fragmentAdminDomain = new FragmentAdminDomain();
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, type);
bundle.putString(Helper.ARG_VIEW_MODEL_KEY, "FEDILAB_" + type.getValue());
fragmentAdminDomain.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragmentAdminDomain);
fragmentTransaction.commit();
}); });
} }
switch (type) { switch (type) {
case REPORT: case REPORT -> setTitle(R.string.reports);
setTitle(R.string.reports); case ACCOUNT -> setTitle(R.string.accounts);
break; case DOMAIN -> setTitle(R.string.domains);
case ACCOUNT:
setTitle(R.string.accounts);
break;
case DOMAIN:
setTitle(R.string.domains);
break;
} }
invalidateOptionsMenu(); invalidateOptionsMenu();
} }
@ -161,7 +193,7 @@ public class AdminActionActivity extends BaseBarActivity {
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) { if (item.getItemId() == android.R.id.home) {
onBackPressed(); getOnBackPressedDispatcher().onBackPressed();
return true; return true;
} else if (item.getItemId() == R.id.action_filter) { } else if (item.getItemId() == R.id.action_filter) {
if (getTitle().toString().equalsIgnoreCase(getString(R.string.accounts))) { if (getTitle().toString().equalsIgnoreCase(getString(R.string.accounts))) {
@ -318,35 +350,14 @@ public class AdminActionActivity extends BaseBarActivity {
protected void onDestroy() { protected void onDestroy() {
super.onDestroy(); super.onDestroy();
if (mReceiver != null) { if (mReceiver != null) {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver); try {
unregisterReceiver(mReceiver);
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
} }
} }
@Override
public void onBackPressed() {
if (canGoBack) {
canGoBack = false;
ThemeHelper.slideViewsToRight(binding.fragmentContainer, binding.buttonContainer, () -> {
if (fragmentAdminReport != null) {
fragmentAdminReport.onDestroyView();
fragmentAdminReport = null;
}
if (fragmentAdminAccount != null) {
fragmentAdminAccount.onDestroyView();
fragmentAdminAccount = null;
}
if (fragmentAdminDomain != null) {
fragmentAdminDomain.onDestroyView();
fragmentAdminDomain = null;
}
setTitle(R.string.administration);
invalidateOptionsMenu();
});
} else {
super.onBackPressed();
}
}
public enum AdminEnum { public enum AdminEnum {
@SerializedName("REPORT") @SerializedName("REPORT")

View file

@ -26,15 +26,18 @@ import android.widget.ArrayAdapter;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.Objects;
import app.fedilab.android.BuildConfig;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity; import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.databinding.ActivityAdminDomainblockBinding; import app.fedilab.android.databinding.ActivityAdminDomainblockBinding;
import app.fedilab.android.mastodon.activities.BaseBarActivity; import app.fedilab.android.mastodon.activities.BaseBarActivity;
import app.fedilab.android.mastodon.client.entities.api.admin.AdminDomainBlock; import app.fedilab.android.mastodon.client.entities.api.admin.AdminDomainBlock;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.helper.Helper; import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.viewmodel.mastodon.AdminVM; import app.fedilab.android.mastodon.viewmodel.mastodon.AdminVM;
import es.dmoral.toasty.Toasty; import es.dmoral.toasty.Toasty;
@ -97,9 +100,9 @@ public class AdminDomainBlockActivity extends BaseBarActivity {
binding.rejectReports.setOnCheckedChangeListener((compoundButton, checked) -> adminDomainBlock.reject_reports = checked); binding.rejectReports.setOnCheckedChangeListener((compoundButton, checked) -> adminDomainBlock.reject_reports = checked);
adminVM = new ViewModelProvider(AdminDomainBlockActivity.this).get(AdminVM.class); adminVM = new ViewModelProvider(AdminDomainBlockActivity.this).get(AdminVM.class);
binding.saveChanges.setOnClickListener(v -> { binding.saveChanges.setOnClickListener(v -> {
adminDomainBlock.domain = binding.domain.getText().toString().trim(); adminDomainBlock.domain = Objects.requireNonNull(binding.domain.getText()).toString().trim();
adminDomainBlock.public_comment = binding.publicComment.getText().toString().trim(); adminDomainBlock.public_comment = Objects.requireNonNull(binding.publicComment.getText()).toString().trim();
adminDomainBlock.private_comment = binding.privateComment.getText().toString().trim(); adminDomainBlock.private_comment = Objects.requireNonNull(binding.privateComment.getText()).toString().trim();
adminVM.createOrUpdateDomainBlock(MainActivity.currentInstance, MainActivity.currentToken, adminDomainBlock) adminVM.createOrUpdateDomainBlock(MainActivity.currentInstance, MainActivity.currentToken, adminDomainBlock)
.observe(AdminDomainBlockActivity.this, adminDomainBlockResult -> { .observe(AdminDomainBlockActivity.this, adminDomainBlockResult -> {
if (adminDomainBlockResult != null) { if (adminDomainBlockResult != null) {
@ -107,9 +110,17 @@ public class AdminDomainBlockActivity extends BaseBarActivity {
} else { } else {
Toasty.error(AdminDomainBlockActivity.this, getString(R.string.toast_error), Toasty.LENGTH_SHORT).show(); Toasty.error(AdminDomainBlockActivity.this, getString(R.string.toast_error), Toasty.LENGTH_SHORT).show();
} }
Intent intent = new Intent(Helper.BROADCAST_DATA).putExtra(Helper.ARG_ADMIN_DOMAINBLOCK, adminDomainBlockResult); Intent intent = new Intent(Helper.BROADCAST_DATA);
LocalBroadcastManager.getInstance(AdminDomainBlockActivity.this).sendBroadcast(intent); Bundle args = new Bundle();
finish(); args.putSerializable(Helper.ARG_ADMIN_DOMAINBLOCK, adminDomainBlockResult);
new CachedBundle(AdminDomainBlockActivity.this).insertBundle(args, Helper.getCurrentAccount(AdminDomainBlockActivity.this), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
intent.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intent);
finish();
});
} }
); );
}); });
@ -135,9 +146,18 @@ public class AdminDomainBlockActivity extends BaseBarActivity {
.setPositiveButton(R.string.unblock_domain, (dialog, which) -> { .setPositiveButton(R.string.unblock_domain, (dialog, which) -> {
adminVM.deleteDomain(MainActivity.currentInstance, MainActivity.currentToken, adminDomainBlock.id) adminVM.deleteDomain(MainActivity.currentInstance, MainActivity.currentToken, adminDomainBlock.id)
.observe(AdminDomainBlockActivity.this, adminDomainBlockResult -> { .observe(AdminDomainBlockActivity.this, adminDomainBlockResult -> {
Intent intent = new Intent(Helper.BROADCAST_DATA).putExtra(Helper.ARG_ADMIN_DOMAINBLOCK_DELETE, adminDomainBlock); Intent intent = new Intent(Helper.BROADCAST_DATA);
LocalBroadcastManager.getInstance(AdminDomainBlockActivity.this).sendBroadcast(intent); Bundle args = new Bundle();
finish(); args.putSerializable(Helper.ARG_ADMIN_DOMAINBLOCK_DELETE, adminDomainBlock);
new CachedBundle(AdminDomainBlockActivity.this).insertBundle(args, Helper.getCurrentAccount(AdminDomainBlockActivity.this), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
intent.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intent);
finish();
});
} }
); );
dialog.dismiss(); dialog.dismiss();

View file

@ -46,7 +46,6 @@ import com.bumptech.glide.request.transition.Transition;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
@ -62,6 +61,7 @@ import app.fedilab.android.mastodon.client.entities.api.Account;
import app.fedilab.android.mastodon.client.entities.api.Attachment; import app.fedilab.android.mastodon.client.entities.api.Attachment;
import app.fedilab.android.mastodon.client.entities.api.admin.AdminAccount; import app.fedilab.android.mastodon.client.entities.api.admin.AdminAccount;
import app.fedilab.android.mastodon.client.entities.api.admin.AdminIp; import app.fedilab.android.mastodon.client.entities.api.admin.AdminIp;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.helper.Helper; import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.MastodonHelper; import app.fedilab.android.mastodon.helper.MastodonHelper;
import app.fedilab.android.mastodon.helper.SpannableHelper; import app.fedilab.android.mastodon.helper.SpannableHelper;
@ -87,19 +87,8 @@ public class AdminReportActivity extends BaseBarActivity {
setContentView(binding.getRoot()); setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar); setSupportActionBar(binding.toolbar);
ActionBar actionBar = getSupportActionBar(); ActionBar actionBar = getSupportActionBar();
Bundle b = getIntent().getExtras(); Bundle args = getIntent().getExtras();
if (b != null) {
adminAccount = (AdminAccount) b.getSerializable(Helper.ARG_ACCOUNT);
if (adminAccount != null) {
account = adminAccount.account;
}
}
postponeEnterTransition(); postponeEnterTransition();
//Remove title
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(false);
}
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(this);
float scale = sharedpreferences.getFloat(getString(R.string.SET_FONT_SCALE), 1.1f); float scale = sharedpreferences.getFloat(getString(R.string.SET_FONT_SCALE), 1.1f);
binding.title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18 * 1.1f / scale); binding.title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18 * 1.1f / scale);
@ -107,6 +96,28 @@ public class AdminReportActivity extends BaseBarActivity {
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true);
} }
//Remove title
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(false);
}
if (args != null) {
long bundleId = args.getLong(Helper.ARG_INTENT_ID, -1);
new CachedBundle(AdminReportActivity.this).getBundle(bundleId, Helper.getCurrentAccount(AdminReportActivity.this), this::initializeAfterBundle);
} else {
initializeAfterBundle(null);
}
}
private void initializeAfterBundle(Bundle bundle) {
if (bundle != null) {
adminAccount = (AdminAccount) bundle.getSerializable(Helper.ARG_ACCOUNT);
if (adminAccount != null) {
account = adminAccount.account;
}
}
if (account != null) { if (account != null) {
initializeView(account); initializeView(account);
} else { } else {
@ -146,10 +157,10 @@ public class AdminReportActivity extends BaseBarActivity {
lastActive.append(Helper.shortDateToString(ip.used_at)).append(" - ").append(ip.ip).append("\r\n"); lastActive.append(Helper.shortDateToString(ip.used_at)).append(" - ").append(ip.ip).append("\r\n");
} }
} }
if (lastActive.toString().trim().length() == 0) { if (lastActive.toString().trim().isEmpty()) {
binding.lastActiveContainer.setVisibility(View.GONE); binding.lastActiveContainer.setVisibility(View.GONE);
} }
if (adminAccount.email == null || adminAccount.email.trim().length() == 0) { if (adminAccount.email == null || adminAccount.email.trim().isEmpty()) {
binding.emailContainer.setVisibility(View.GONE); binding.emailContainer.setVisibility(View.GONE);
} }
binding.lastActive.setText(lastActive.toString()); binding.lastActive.setText(lastActive.toString());
@ -248,11 +259,8 @@ public class AdminReportActivity extends BaseBarActivity {
}); });
//Retrieve relationship with the connected account
List<String> accountListToCheck = new ArrayList<>();
accountListToCheck.add(account.id);
//Animate emojis //Animate emojis
if (account.emojis != null && account.emojis.size() > 0) { if (account.emojis != null && !account.emojis.isEmpty()) {
boolean disableAnimatedEmoji = sharedpreferences.getBoolean(getString(R.string.SET_DISABLE_ANIMATED_EMOJI), false); boolean disableAnimatedEmoji = sharedpreferences.getBoolean(getString(R.string.SET_DISABLE_ANIMATED_EMOJI), false);
if (!disableAnimatedEmoji) { if (!disableAnimatedEmoji) {
scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
@ -328,7 +336,7 @@ public class AdminReportActivity extends BaseBarActivity {
} }
binding.accountDn.setText( binding.accountDn.setText(
account.getSpanDisplayName(AdminReportActivity.this, account.getSpanDisplayNameEmoji(AdminReportActivity.this,
new WeakReference<>(binding.accountDn)), new WeakReference<>(binding.accountDn)),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
binding.accountUn.setText(String.format("@%s", account.acct)); binding.accountUn.setText(String.format("@%s", account.acct));
@ -347,7 +355,7 @@ public class AdminReportActivity extends BaseBarActivity {
MastodonHelper.loadPPMastodon(binding.accountPp, account); MastodonHelper.loadPPMastodon(binding.accountPp, account);
binding.accountPp.setOnClickListener(v -> { binding.accountPp.setOnClickListener(v -> {
Intent intent = new Intent(AdminReportActivity.this, MediaActivity.class); Intent intent = new Intent(AdminReportActivity.this, MediaActivity.class);
Bundle b = new Bundle(); Bundle args = new Bundle();
Attachment attachment = new Attachment(); Attachment attachment = new Attachment();
attachment.description = account.acct; attachment.description = account.acct;
attachment.preview_url = account.avatar; attachment.preview_url = account.avatar;
@ -356,13 +364,16 @@ public class AdminReportActivity extends BaseBarActivity {
attachment.type = "image"; attachment.type = "image";
ArrayList<Attachment> attachments = new ArrayList<>(); ArrayList<Attachment> attachments = new ArrayList<>();
attachments.add(attachment); attachments.add(attachment);
b.putSerializable(Helper.ARG_MEDIA_ARRAY, attachments); args.putSerializable(Helper.ARG_MEDIA_ARRAY, attachments);
b.putInt(Helper.ARG_MEDIA_POSITION, 1); args.putInt(Helper.ARG_MEDIA_POSITION, 1);
intent.putExtras(b); new CachedBundle(AdminReportActivity.this).insertBundle(args, Helper.getCurrentAccount(AdminReportActivity.this), bundleId -> {
ActivityOptionsCompat options = ActivityOptionsCompat Bundle bundle = new Bundle();
.makeSceneTransitionAnimation(AdminReportActivity.this, binding.accountPp, attachment.url); bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
// start the new activity intent.putExtras(bundle);
startActivity(intent, options.toBundle()); ActivityOptionsCompat options = ActivityOptionsCompat
.makeSceneTransitionAnimation(AdminReportActivity.this, binding.accountPp, attachment.url);
startActivity(intent, options.toBundle());
});
}); });

View file

@ -25,7 +25,7 @@ import java.util.Set;
/** /**
* Original work from https://stackoverflow.com/a/25873554 * Original work from <a href="https://stackoverflow.com/a/25873554">stackoverflow</a>
*/ */
public class NetworkStateReceiver extends BroadcastReceiver { public class NetworkStateReceiver extends BroadcastReceiver {

View file

@ -31,18 +31,14 @@ public class ToastMessage extends BroadcastReceiver {
String content = b.getString(Helper.RECEIVE_TOAST_CONTENT, null); String content = b.getString(Helper.RECEIVE_TOAST_CONTENT, null);
if (type != null && content != null) { if (type != null && content != null) {
switch (type) { switch (type) {
case Helper.RECEIVE_TOAST_TYPE_ERROR: case Helper.RECEIVE_TOAST_TYPE_ERROR ->
Toasty.error(context, content, Toasty.LENGTH_SHORT).show(); Toasty.error(context, content, Toasty.LENGTH_SHORT).show();
break; case Helper.RECEIVE_TOAST_TYPE_WARNING ->
case Helper.RECEIVE_TOAST_TYPE_WARNING: Toasty.warning(context, content, Toasty.LENGTH_SHORT).show();
Toasty.warning(context, content, Toasty.LENGTH_SHORT).show(); case Helper.RECEIVE_TOAST_TYPE_INFO ->
break; Toasty.info(context, content, Toasty.LENGTH_SHORT).show();
case Helper.RECEIVE_TOAST_TYPE_INFO: case Helper.RECEIVE_TOAST_TYPE_SUCCESS ->
Toasty.info(context, content, Toasty.LENGTH_SHORT).show(); Toasty.success(context, content, Toasty.LENGTH_SHORT).show();
break;
case Helper.RECEIVE_TOAST_TYPE_SUCCESS:
Toasty.success(context, content, Toasty.LENGTH_SHORT).show();
break;
} }
} }
} }

View file

@ -124,6 +124,7 @@ public interface MastodonAccountsService {
@Query("exclude_reblogs") Boolean exclude_reblogs, @Query("exclude_reblogs") Boolean exclude_reblogs,
@Query("only_media") Boolean only_media, @Query("only_media") Boolean only_media,
@Query("pinned") Boolean pinned, @Query("pinned") Boolean pinned,
@Query("tagged") String tagged,
@Query("limit") int limit @Query("limit") int limit
); );

View file

@ -21,6 +21,7 @@ import app.fedilab.android.mastodon.client.entities.api.Account;
import app.fedilab.android.mastodon.client.entities.api.Activity; import app.fedilab.android.mastodon.client.entities.api.Activity;
import app.fedilab.android.mastodon.client.entities.api.Emoji; import app.fedilab.android.mastodon.client.entities.api.Emoji;
import app.fedilab.android.mastodon.client.entities.api.Instance; import app.fedilab.android.mastodon.client.entities.api.Instance;
import app.fedilab.android.mastodon.client.entities.api.InstanceV2;
import app.fedilab.android.mastodon.client.entities.api.Tag; import app.fedilab.android.mastodon.client.entities.api.Tag;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.http.GET; import retrofit2.http.GET;
@ -32,6 +33,9 @@ public interface MastodonInstanceService {
@GET("instance") @GET("instance")
Call<Instance> instance(); Call<Instance> instance();
@GET("instance")
Call<InstanceV2> instanceV2();
@GET("instance/peers") @GET("instance/peers")
Call<List<String>> connectedInstance(); Call<List<String>> connectedInstance();

View file

@ -67,6 +67,7 @@ public interface MastodonNotificationsService {
@Field("subscription[endpoint]") String endpoint, @Field("subscription[endpoint]") String endpoint,
@Field("subscription[keys][p256dh]") String keys_p256dh, @Field("subscription[keys][p256dh]") String keys_p256dh,
@Field("subscription[keys][auth]") String keys_auth, @Field("subscription[keys][auth]") String keys_auth,
@Field("subscription[standard]") boolean standard,
@Field("data[alerts][follow]") boolean follow, @Field("data[alerts][follow]") boolean follow,
@Field("data[alerts][favourite]") boolean favourite, @Field("data[alerts][favourite]") boolean favourite,
@Field("data[alerts][reblog]") boolean reblog, @Field("data[alerts][reblog]") boolean reblog,
@ -75,7 +76,8 @@ public interface MastodonNotificationsService {
@Field("data[alerts][status]") boolean status, @Field("data[alerts][status]") boolean status,
@Field("data[alerts][update]") boolean update, @Field("data[alerts][update]") boolean update,
@Field("data[alerts][admin.sign_up]") boolean admin_sign_up, @Field("data[alerts][admin.sign_up]") boolean admin_sign_up,
@Field("data[alerts][admin.report]") boolean admin_report @Field("data[alerts][admin.report]") boolean admin_report,
@Field("data[policy]") String policy
); );
@ -93,7 +95,8 @@ public interface MastodonNotificationsService {
@Field("data[alerts][favourite]") boolean favourite, @Field("data[alerts][favourite]") boolean favourite,
@Field("data[alerts][reblog]") boolean reblog, @Field("data[alerts][reblog]") boolean reblog,
@Field("data[alerts][mention]") boolean mention, @Field("data[alerts][mention]") boolean mention,
@Field("data[alerts][poll]") boolean poll @Field("data[alerts][poll]") boolean poll,
@Field("data[policy]") String policy
); );
@DELETE("push/subscription") @DELETE("push/subscription")

View file

@ -15,6 +15,7 @@ package app.fedilab.android.mastodon.client.endpoints;
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import java.util.Date; import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import app.fedilab.android.mastodon.client.entities.api.Account; import app.fedilab.android.mastodon.client.entities.api.Account;
@ -25,14 +26,17 @@ import app.fedilab.android.mastodon.client.entities.api.Poll;
import app.fedilab.android.mastodon.client.entities.api.ScheduledStatus; import app.fedilab.android.mastodon.client.entities.api.ScheduledStatus;
import app.fedilab.android.mastodon.client.entities.api.Status; import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.api.StatusSource; import app.fedilab.android.mastodon.client.entities.api.StatusSource;
import app.fedilab.android.mastodon.client.entities.api.params.StatusParams;
import okhttp3.MultipartBody; import okhttp3.MultipartBody;
import okhttp3.RequestBody; import okhttp3.RequestBody;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.DELETE; import retrofit2.http.DELETE;
import retrofit2.http.Field; import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded; import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET; import retrofit2.http.GET;
import retrofit2.http.Header; import retrofit2.http.Header;
import retrofit2.http.Headers;
import retrofit2.http.Multipart; import retrofit2.http.Multipart;
import retrofit2.http.POST; import retrofit2.http.POST;
import retrofit2.http.PUT; import retrofit2.http.PUT;
@ -61,7 +65,8 @@ public interface MastodonStatusesService {
@Field("visibility") String visibility, @Field("visibility") String visibility,
@Field("language") String language, @Field("language") String language,
@Field("quote_id") String quote_id, @Field("quote_id") String quote_id,
@Field("content_type") String content_type @Field("content_type") String content_type,
@Field("local_only") Boolean local_only
); );
@GET("statuses/{id}/source") @GET("statuses/{id}/source")
@ -74,6 +79,16 @@ public interface MastodonStatusesService {
@Header("Authorization") String token, @Header("Authorization") String token,
@Path("id") String id); @Path("id") String id);
@Headers({"Accept: application/json"})
@PUT("statuses/{id}")
Call<Status> updateStatus(
@Header("Idempotency-Key") String idempotency_Key,
@Header("Authorization") String token,
@Path("id") String id,
@Body StatusParams statusParams
);
//Post a status //Post a status
@FormUrlEncoded @FormUrlEncoded
@PUT("statuses/{id}") @PUT("statuses/{id}")
@ -92,9 +107,9 @@ public interface MastodonStatusesService {
@Field("spoiler_text") String spoiler_text, @Field("spoiler_text") String spoiler_text,
@Field("visibility") String visibility, @Field("visibility") String visibility,
@Field("language") String language, @Field("language") String language,
@Field("media_attributes[id][]") List<String> media_id, @Field("media_attributes[]") LinkedHashMap<String, String> media_id,
@Field("media_attributes[description][]") List<String> media_description, @Field("media_attributes[]") LinkedHashMap<String, String> media_description,
@Field("media_attributes[focus][]") List<String> focus @Field("media_attributes[]") LinkedHashMap<String, String> focus
); );
//Post a scheduled status //Post a scheduled status
@ -318,4 +333,18 @@ public interface MastodonStatusesService {
@Header("Authorization") String token, @Header("Authorization") String token,
@Path("id") String id @Path("id") String id
); );
@POST("statuses/{id}/react/{name}")
Call<Void> addReaction(
@Header("Authorization") String app_token,
@Path("id") String id,
@Path("name") String name
);
@POST("statuses/{id}/unreact/{name}")
Call<Void> removeReaction(
@Header("Authorization") String app_token,
@Path("id") String id,
@Path("name") String name
);
} }

View file

@ -18,10 +18,12 @@ import java.util.List;
import app.fedilab.android.mastodon.client.entities.api.Account; import app.fedilab.android.mastodon.client.entities.api.Account;
import app.fedilab.android.mastodon.client.entities.api.Conversation; import app.fedilab.android.mastodon.client.entities.api.Conversation;
import app.fedilab.android.mastodon.client.entities.api.Link;
import app.fedilab.android.mastodon.client.entities.api.Marker; import app.fedilab.android.mastodon.client.entities.api.Marker;
import app.fedilab.android.mastodon.client.entities.api.MastodonList; import app.fedilab.android.mastodon.client.entities.api.MastodonList;
import app.fedilab.android.mastodon.client.entities.api.Status; import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.api.Tag; import app.fedilab.android.mastodon.client.entities.api.Tag;
import app.fedilab.android.mastodon.client.entities.lemmy.LemmyPost;
import app.fedilab.android.mastodon.client.entities.misskey.MisskeyNote; import app.fedilab.android.mastodon.client.entities.misskey.MisskeyNote;
import app.fedilab.android.mastodon.client.entities.nitter.Nitter; import app.fedilab.android.mastodon.client.entities.nitter.Nitter;
import app.fedilab.android.mastodon.client.entities.peertube.PeertubeVideo; import app.fedilab.android.mastodon.client.entities.peertube.PeertubeVideo;
@ -79,6 +81,11 @@ public interface MastodonTimelinesService {
@Query("offset") Integer offset, @Query("offset") Integer offset,
@Query("limit") Integer limit); @Query("limit") Integer limit);
@GET("trends/links")
Call<List<Link>> getLinkTrends(@Header("Authorization") String token,
@Query("offset") Integer offset,
@Query("limit") Integer limit);
//Public Tags timelines //Public Tags timelines
@GET("timelines/tag/{hashtag}") @GET("timelines/tag/{hashtag}")
Call<List<Status>> getHashTag( Call<List<Status>> getHashTag(
@ -230,6 +237,20 @@ public interface MastodonTimelinesService {
Call<List<MisskeyNote>> getMisskey(@Body MisskeyNote.MisskeyParams params); Call<List<MisskeyNote>> getMisskey(@Body MisskeyNote.MisskeyParams params);
@GET("discover/posts/trending")
Call<List<Status>> getPixelDiscoverTrending(
@Query("range") String range
);
@GET("api/v3/post/list?sort=New")
Call<LemmyPost.LemmyPosts> getLemmyMain(@Query("limit") Integer limit,
@Query("page") String page);
@GET("api/v3/comment/list")
Call<LemmyPost.LemmyComments> getLemmyThread(@Query("post_id") String post_id,
@Query("limit") Integer limit,
@Query("page") String page);
//Public timelines for Misskey //Public timelines for Misskey
@FormUrlEncoded @FormUrlEncoded
@POST("api/notes") @POST("api/notes")
@ -253,14 +274,14 @@ public interface MastodonTimelinesService {
); );
@Headers({"Accept: text/html,application/xhtml+xml,application/xml"}) @Headers({"Accept: text/html,application/xhtml+xml,application/xml"})
@GET("{names}/rss") @GET("{names}/with_replies/rss")
Call<Nitter> getNitter( Call<Nitter> getNitter(
@Path("names") String id, @Path("names") String id,
@Query("max_position") String max_position @Query("max_position") String max_position
); );
@Headers({"Accept: text/html,application/xhtml+xml,application/xml"}) @Headers({"Accept: text/html,application/xhtml+xml,application/xml"})
@GET("{account}/rss") @GET("{account}/with_replies/rss")
Call<Nitter> getNitterAccount( Call<Nitter> getNitterAccount(
@Path("account") String account @Path("account") String account
); );

View file

@ -0,0 +1,32 @@
package app.fedilab.android.mastodon.client.endpoints;
/* Copyright 2025 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import java.util.List;
import app.fedilab.android.mastodon.client.entities.api.Status;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
public interface PixelfedTimelinesService {
//Public timelines
@GET("discover/posts/trending")
Call<List<Status>> getTrending(
@Query("range") String range
);
}

View file

@ -60,11 +60,11 @@ public class Account implements Serializable {
@SerializedName("header_static") @SerializedName("header_static")
public String header_static; public String header_static;
@SerializedName("followers_count") @SerializedName("followers_count")
public int followers_count; public long followers_count;
@SerializedName("following_count") @SerializedName("following_count")
public int following_count; public long following_count;
@SerializedName("statuses_count") @SerializedName("statuses_count")
public int statuses_count; public long statuses_count;
@SerializedName("last_status_at") @SerializedName("last_status_at")
public Date last_status_at; public Date last_status_at;
@SerializedName("source") @SerializedName("source")
@ -88,15 +88,17 @@ public class Account implements Serializable {
@SerializedName("role") @SerializedName("role")
public Role role; public Role role;
public transient RelationShip relationShip; public transient RelationShip relationShip;
public transient String pronouns = null;
public synchronized Spannable getSpanDisplayName(Context context, WeakReference<View> viewWeakReference) { public synchronized Spannable getSpanDisplayName(Context context, WeakReference<View> viewWeakReference) {
if (display_name == null || display_name.isEmpty()) { if (display_name == null || display_name.isEmpty()) {
display_name = username; display_name = username;
} }
return SpannableHelper.convert(context, display_name, null, this, null, viewWeakReference); return SpannableHelper.convert(context, display_name, null, this, null, viewWeakReference, null, true, false);
} }
public synchronized Spannable getSpanDisplayName(Activity activity, WeakReference<View> viewWeakReference) { public synchronized Spannable getSpanDisplayNameEmoji(Activity activity, WeakReference<View> viewWeakReference) {
if (display_name == null || display_name.isEmpty()) { if (display_name == null || display_name.isEmpty()) {
display_name = username; display_name = username;
} }
@ -104,11 +106,14 @@ public class Account implements Serializable {
} }
public synchronized Spannable getSpanDisplayNameTitle(Context context, WeakReference<View> viewWeakReference, String title) { public synchronized Spannable getSpanDisplayNameTitle(Context context, WeakReference<View> viewWeakReference, String title) {
return SpannableHelper.convert(context, title, null, this, null, viewWeakReference); return SpannableHelper.convert(context, title, null, this, null, viewWeakReference, null, true, false);
} }
public synchronized Spannable getSpanNote(Context context, WeakReference<View> viewWeakReference) { public synchronized Spannable getSpanNote(Context context, WeakReference<View> viewWeakReference) {
return SpannableHelper.convert(context, note, null, this, null, viewWeakReference); return SpannableHelper.convert(context, note, null, this, null, viewWeakReference, null, true, false);
}
public synchronized Spannable getSpanNote(Context context, WeakReference<View> viewWeakReference, Status.Callback callback) {
return SpannableHelper.convert(context, note, null, this, null, viewWeakReference, callback, true, false);
} }
@Override @Override

View file

@ -56,7 +56,7 @@ public class Announcement {
public synchronized Spannable getSpanContent(Context context, WeakReference<View> viewWeakReference) { public synchronized Spannable getSpanContent(Context context, WeakReference<View> viewWeakReference) {
return SpannableHelper.convert(context, content, null, null, this, viewWeakReference); return SpannableHelper.convert(context, content, null, null, this, viewWeakReference, null, true, false);
} }
} }

View file

@ -73,6 +73,16 @@ public class Attachment implements Serializable {
public MediaData original; public MediaData original;
@SerializedName("small") @SerializedName("small")
public MediaData small; public MediaData small;
public MediaData getSmall() {
if (small != null) {
return small;
} else if (original != null) {
return original;
} else {
return null;
}
}
} }
public static class Focus implements Serializable { public static class Focus implements Serializable {

View file

@ -14,21 +14,36 @@ package app.fedilab.android.mastodon.client.entities.api;
* You should have received a copy of the GNU General Public License along with Fedilab; if not, * You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.emojis;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import java.io.Serializable; import java.io.Serializable;
import java.net.IDN;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.mastodon.client.endpoints.MastodonInstanceService;
import app.fedilab.android.mastodon.exception.DBException; import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.sqlite.Sqlite; import app.fedilab.android.sqlite.Sqlite;
import okhttp3.OkHttpClient;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class EmojiInstance implements Serializable { public class EmojiInstance implements Serializable {
@ -170,6 +185,17 @@ public class EmojiInstance implements Serializable {
} }
} }
private MastodonInstanceService init(String instance) {
final OkHttpClient okHttpClient = Helper.myOkHttpClient(context);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + (instance != null ? IDN.toASCII(instance, IDN.ALLOW_UNASSIGNED) : null) + "/api/v1/")
.addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder()))
.client(okHttpClient)
.build();
return retrofit.create(MastodonInstanceService.class);
}
/** /**
* Returns the emojis for an instance * Returns the emojis for an instance
* *
@ -184,10 +210,72 @@ public class EmojiInstance implements Serializable {
Cursor c = db.query(Sqlite.TABLE_EMOJI_INSTANCE, null, Sqlite.COL_INSTANCE + " = '" + instance + "'", null, null, null, null, "1"); Cursor c = db.query(Sqlite.TABLE_EMOJI_INSTANCE, null, Sqlite.COL_INSTANCE + " = '" + instance + "'", null, null, null, null, "1");
return cursorToEmojiList(c); return cursorToEmojiList(c);
} catch (Exception e) { } catch (Exception e) {
MastodonInstanceService mastodonInstanceService = init(instance);
Call<List<Emoji>> emojiCall = mastodonInstanceService.customEmoji();
if (emojiCall != null) {
try {
Response<List<Emoji>> emojiResponse = emojiCall.execute();
if (emojiResponse.isSuccessful()) {
return emojiResponse.body();
}
} catch (Exception err) {
err.printStackTrace();
}
}
return null; return null;
} }
} }
/**
* Returns the emojis for an instance
*
* @param instance String
* @param filter String
* @param callBack EmojiFilteredCallBack - Get filtered emojis
* @return List<Emoji> - List of {@link Emoji}
*/
public void getEmojiListFiltered(@NonNull String instance, @NonNull String filter, EmojiFilteredCallBack callBack) throws DBException {
if (db == null) {
throw new DBException("db is null. Wrong initialization.");
}
new Thread(() -> {
List<Emoji> emojiArrayList = new ArrayList<>();
List<Emoji> emojiFiltered = new ArrayList<>();
if (emojis == null || !emojis.containsKey(BaseMainActivity.currentInstance) || emojis.get(BaseMainActivity.currentInstance) == null) {
try {
Cursor c = db.query(Sqlite.TABLE_EMOJI_INSTANCE, null, Sqlite.COL_INSTANCE + " = '" + instance + "'", null, null, null, null, "1");
emojiArrayList = cursorToEmojiList(c);
} catch (Exception e) {
MastodonInstanceService mastodonInstanceService = init(instance);
Call<List<Emoji>> emojiCall = mastodonInstanceService.customEmoji();
if (emojiCall != null) {
try {
Response<List<Emoji>> emojiResponse = emojiCall.execute();
if (emojiResponse.isSuccessful()) {
emojiArrayList = emojiResponse.body();
}
} catch (Exception err) {
err.printStackTrace();
}
}
}
} else {
emojiArrayList = emojis.get(instance);
}
if (emojiArrayList != null ) {
for (Emoji emoji : emojiArrayList) {
if (emoji.shortcode.contains(filter)) {
emojiFiltered.add(emoji);
}
}
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> callBack.get(emojiFiltered);
mainHandler.post(myRunnable);
}).start();
}
/** /**
* Restore emoji list from db * Restore emoji list from db
* *
@ -214,4 +302,8 @@ public class EmojiInstance implements Serializable {
} }
return filteredEmojis; return filteredEmojis;
} }
public interface EmojiFilteredCallBack {
void get(List<Emoji> emojiList);
}
} }

View file

@ -47,7 +47,7 @@ public class Field implements Serializable {
if (verified_at != null && value != null) { if (verified_at != null && value != null) {
value_span = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.verified_text)); value_span = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.verified_text));
} }
Spannable spannable = SpannableHelper.convert(context, value, null, account, null, viewWeakReference); Spannable spannable = SpannableHelper.convert(context, value, null, account, null, viewWeakReference, null, true, false);
if (value_span != null && spannable != null) { if (value_span != null && spannable != null) {
spannable.setSpan(value_span, 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); spannable.setSpan(value_span, 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
@ -57,7 +57,7 @@ public class Field implements Serializable {
public synchronized Spannable getLabelSpan(Context context, Account account, WeakReference<View> viewWeakReference) { public synchronized Spannable getLabelSpan(Context context, Account account, WeakReference<View> viewWeakReference) {
Spannable spannable = SpannableHelper.convert(context, name, null, account, null, viewWeakReference); Spannable spannable = SpannableHelper.convert(context, name, null, account, null, viewWeakReference, null, true, false);
if (name_span != null && spannable != null) { if (name_span != null && spannable != null) {
spannable.setSpan(name_span, 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); spannable.setSpan(name_span, 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} }

View file

@ -2,6 +2,8 @@ package app.fedilab.android.mastodon.client.entities.api;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
/* Copyright 2021 Thomas Schneider /* Copyright 2021 Thomas Schneider
* *
* This file is a part of Fedilab * This file is a part of Fedilab
@ -16,7 +18,7 @@ import com.google.gson.annotations.SerializedName;
* *
* You should have received a copy of the GNU General Public License along with Fedilab; if not, * You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
public class History { public class History implements Serializable {
@SerializedName("day") @SerializedName("day")
public String day; public String day;
@SerializedName("uses") @SerializedName("uses")

View file

@ -169,8 +169,8 @@ public class Instance implements Serializable {
public int min_expiration; public int min_expiration;
@SerializedName("max_options") @SerializedName("max_options")
public int max_options = 4; public int max_options = 4;
@SerializedName("max_option_chars") @SerializedName("max_characters_per_option")
public int max_option_chars = 25; public int max_option_chars = 50;
@SerializedName("max_expiration") @SerializedName("max_expiration")
public int max_expiration; public int max_expiration;
} }

View file

@ -0,0 +1,70 @@
package app.fedilab.android.mastodon.client.entities.api;
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/* Copyright 2025 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
public class InstanceV2 implements Serializable {
@SerializedName("domain")
public String domain;
@SerializedName("title")
public String title;
@SerializedName("version")
public String version;
@SerializedName("source_url")
public String sourceUrl;
@SerializedName("description")
public String description;
@SerializedName("configuration")
public Configuration configuration;
public static String serialize(InstanceV2 instance) {
Gson gson = new Gson();
try {
return gson.toJson(instance);
} catch (Exception e) {
return null;
}
}
public static InstanceV2 restore(String serialized) {
Gson gson = new Gson();
try {
return gson.fromJson(serialized, InstanceV2.class);
} catch (Exception e) {
return null;
}
}
public static class Configuration implements Serializable {
@SerializedName("vapid")
public VapId vapId;
}
public static class VapId implements Serializable {
@SerializedName("public_key")
public String publicKey;
}
}

View file

@ -0,0 +1,52 @@
package app.fedilab.android.mastodon.client.entities.api;
/* Copyright 2025 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.util.List;
public class Link implements Serializable {
@SerializedName("url")
public String url;
@SerializedName("title")
public String title;
@SerializedName("description")
public String description;
@SerializedName("type")
public String type;
@SerializedName("author_name")
public String author_name;
@SerializedName("author_url")
public String author_url;
@SerializedName("provider_name")
public String provider_name;
@SerializedName("provider_url")
public String provider_url;
@SerializedName("html")
public String html;
@SerializedName("width")
public int width;
@SerializedName("height")
public int height;
@SerializedName("image")
public String image;
@SerializedName("embed_url")
public String embed_url;
@SerializedName("blurhash")
public String blurhash;
@SerializedName("history")
public List<History> history;
}

View file

@ -41,8 +41,8 @@ public class Notification {
public boolean cached; public boolean cached;
public Filter filteredByApp; public Filter filteredByApp;
public PositionFetchMore positionFetchMore = PositionFetchMore.BOTTOM; public PositionFetchMore positionFetchMore = PositionFetchMore.BOTTOM;
public transient List<Notification> relatedNotifications; public List<Notification> relatedNotifications;
public boolean isFetchMore; public transient boolean isFetchMore;
/** /**
* Serialized a list of Notification class * Serialized a list of Notification class

View file

@ -61,7 +61,7 @@ public class Poll implements Serializable {
public transient Spannable span_title; public transient Spannable span_title;
public Spannable getSpanTitle(Context context, Status status, WeakReference<View> viewWeakReference) { public Spannable getSpanTitle(Context context, Status status, WeakReference<View> viewWeakReference) {
span_title = SpannableHelper.convert(context, title, status, null, null, viewWeakReference); span_title = SpannableHelper.convert(context, title, status, null, null, viewWeakReference, null, false, false);
return span_title; return span_title;
} }
} }

View file

@ -22,6 +22,10 @@ public class PushSubscription {
public String id; public String id;
@SerializedName("endpoint") @SerializedName("endpoint")
public String endpoint; public String endpoint;
@SerializedName("standard")
public String standard;
@SerializedName("policy")
public String policy;
@SerializedName("alerts") @SerializedName("alerts")
public Alerts alerts; public Alerts alerts;
@SerializedName("server_key") @SerializedName("server_key")

View file

@ -32,6 +32,9 @@ public class RelationShip {
public boolean followed_by; public boolean followed_by;
@SerializedName("blocking") @SerializedName("blocking")
public boolean blocking; public boolean blocking;
@SerializedName("requested_by")
public boolean requested_by;
@SerializedName("blocked_by") @SerializedName("blocked_by")
public boolean blocked_by; public boolean blocked_by;
@SerializedName("muting") @SerializedName("muting")

View file

@ -16,6 +16,7 @@ package app.fedilab.android.mastodon.client.entities.api;
import android.content.Context; import android.content.Context;
import android.text.Spannable; import android.text.Spannable;
import android.util.Log;
import android.view.View; import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -28,6 +29,7 @@ import java.lang.ref.WeakReference;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.SpannableHelper; import app.fedilab.android.mastodon.helper.SpannableHelper;
import de.timfreiheit.mathjax.android.MathJaxView; import de.timfreiheit.mathjax.android.MathJaxView;
@ -62,11 +64,11 @@ public class Status implements Serializable, Cloneable {
@SerializedName("url") @SerializedName("url")
public String url; public String url;
@SerializedName("replies_count") @SerializedName("replies_count")
public int replies_count; public long replies_count;
@SerializedName("reblogs_count") @SerializedName("reblogs_count")
public int reblogs_count; public long reblogs_count;
@SerializedName("favourites_count") @SerializedName("favourites_count")
public int favourites_count; public long favourites_count;
@SerializedName("favourited") @SerializedName("favourited")
public boolean favourited; public boolean favourited;
@SerializedName("reblogged") @SerializedName("reblogged")
@ -109,12 +111,21 @@ public class Status implements Serializable, Cloneable {
public boolean cached = false; public boolean cached = false;
@SerializedName("is_maths") @SerializedName("is_maths")
public Boolean isMaths; public Boolean isMaths;
@SerializedName("reactions")
public List<Reaction> reactions;
public String attachedNotification = null;
public int gifPosition = 0;
public transient boolean isFetchMore = false;
public transient boolean isFetching = false;
public transient PositionFetchMore positionFetchMore = PositionFetchMore.BOTTOM;
public Attachment art_attachment; public Attachment art_attachment;
public boolean isExpended = false; public boolean isExpended = false;
public boolean isTruncated = true; public boolean isTruncated = true;
public transient boolean isFetchMore = false;
public transient PositionFetchMore positionFetchMore = PositionFetchMore.BOTTOM;
public boolean isChecked = false; public boolean isChecked = false;
//When forwarding tags //When forwarding tags
public boolean tagAdded = false; public boolean tagAdded = false;
@ -126,12 +137,19 @@ public class Status implements Serializable, Cloneable {
public transient boolean setCursorToEnd = false; public transient boolean setCursorToEnd = false;
public transient int cursorPosition = 0; public transient int cursorPosition = 0;
public transient boolean submitted = false; public transient boolean submitted = false;
public transient boolean underlined = false;
public boolean spoilerChecked = false; public boolean spoilerChecked = false;
public Filter filteredByApp; public Filter filteredByApp;
public transient Spannable contentSpan; public transient Spannable contentSpan;
public transient String[] bottomTags;
public transient Spannable contentSpoilerSpan; public transient Spannable contentSpoilerSpan;
public transient Spannable contentTranslateSpan; public transient Spannable contentTranslateSpan;
public transient MathJaxView mathJaxView; public transient MathJaxView mathJaxView;
public String lemmy_post_id;
public transient String pronouns = null;
@Override @Override
public boolean equals(@Nullable Object obj) { public boolean equals(@Nullable Object obj) {
@ -142,23 +160,30 @@ public class Status implements Serializable, Cloneable {
return same; return same;
} }
public synchronized Spannable getSpanContent(Context context, WeakReference<View> viewWeakReference, Callback callback) { public synchronized Spannable getSpanContent(Context context, boolean checkRemotely, WeakReference<View> viewWeakReference, Callback callback) {
if (contentSpan == null) { if (contentSpan == null) {
contentSpan = SpannableHelper.convert(context, content, this, null, null, viewWeakReference, callback); contentSpan = SpannableHelper.convert(context, content, this, null, null, checkRemotely, viewWeakReference, callback, true, true);
} }
return contentSpan; return contentSpan;
} }
public synchronized String[] getBottomTags() {
if(bottomTags == null) {
bottomTags = SpannableHelper.hasBottomTags(content);
}
return bottomTags;
}
public synchronized Spannable getSpanSpoiler(Context context, WeakReference<View> viewWeakReference, Callback callback) { public synchronized Spannable getSpanSpoiler(Context context, WeakReference<View> viewWeakReference, Callback callback) {
if (contentSpoilerSpan == null) { if (contentSpoilerSpan == null) {
contentSpoilerSpan = SpannableHelper.convert(context, spoiler_text, this, null, null, viewWeakReference, callback); contentSpoilerSpan = SpannableHelper.convert(context, spoiler_text, this, null, null, viewWeakReference, callback, true, false);
} }
return contentSpoilerSpan; return contentSpoilerSpan;
} }
public synchronized Spannable getSpanTranslate(Context context, WeakReference<View> viewWeakReference, Callback callback) { public synchronized Spannable getSpanTranslate(Context context, WeakReference<View> viewWeakReference, Callback callback) {
if (contentTranslateSpan == null) { if (contentTranslateSpan == null) {
contentTranslateSpan = SpannableHelper.convert(context, translationContent, this, null, null, viewWeakReference, callback); contentTranslateSpan = SpannableHelper.convert(context, translationContent, this, null, null, viewWeakReference, callback, true, true);
} }
return contentTranslateSpan; return contentTranslateSpan;
} }

View file

@ -32,6 +32,13 @@ public class Tag implements Serializable {
@SerializedName("following") @SerializedName("following")
public boolean following = false; public boolean following = false;
public Tag() {
}
public Tag(String name) {
this.name = name;
}
public int getWeight() { public int getWeight() {
int weight = 0; int weight = 0;
if (history != null && history.size() > 0) { if (history != null && history.size() > 0) {

View file

@ -0,0 +1,65 @@
package app.fedilab.android.mastodon.client.entities.api.params;
/* Copyright 2025 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.util.List;
public class StatusParams implements Serializable {
@SerializedName("id")
public String id;
@SerializedName("status")
public String status;
@SerializedName("media_ids")
public List<String> media_ids;
@SerializedName("poll")
public PollParams pollParams;
@SerializedName("in_reply_to_id")
public String in_reply_to_id;
@SerializedName("sensitive")
public Boolean sensitive;
@SerializedName("spoiler_text")
public String spoiler_text;
@SerializedName("visibility")
public String visibility;
@SerializedName("language")
public String language;
@SerializedName("media_attributes")
public List<MediaParams> media_attributes;
public static class PollParams implements Serializable{
@SerializedName("options")
public List<String> poll_options;
@SerializedName("expires_in")
public Integer poll_expire_in;
@SerializedName("multiple")
public Boolean poll_multiple;
@SerializedName("hide_totals")
public Boolean poll_hide_totals;
}
public static class MediaParams implements Serializable {
@SerializedName("id")
public String id;
@SerializedName("description")
public String description;
@SerializedName("focus")
public String focus;
}
}

View file

@ -0,0 +1,433 @@
package app.fedilab.android.mastodon.client.entities.app;
/* Copyright 2024 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.mastodon.helper.Helper.PREF_USER_ID;
import static app.fedilab.android.mastodon.helper.Helper.PREF_USER_INSTANCE;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
import android.util.Base64;
import androidx.preference.PreferenceManager;
import com.google.gson.annotations.SerializedName;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import app.fedilab.android.mastodon.client.entities.api.Account;
import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.sqlite.Sqlite;
/**
* Class that manages Bundle of Intent from database
*/
public class CachedBundle {
public String id;
public Bundle bundle;
public CacheType cacheType;
public String instance;
public String user_id;
public String target_id;
public Date created_at;
private SQLiteDatabase db;
private transient Context context;
public CachedBundle() {
}
public CachedBundle(Context context) {
//Creation of the DB with tables
this.context = context;
this.db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
}
public void insertAccountBundle(Account account, BaseAccount currentUser) throws DBException {
if (db == null) {
throw new DBException("db is null. Wrong initialization.");
}
ContentValues valuesAccount = new ContentValues();
Bundle bundleAccount = new Bundle();
if (account != null) {
bundleAccount.putSerializable(Helper.ARG_ACCOUNT, account);
valuesAccount.put(Sqlite.COL_BUNDLE, serializeBundle(bundleAccount));
valuesAccount.put(Sqlite.COL_CREATED_AT, Helper.dateToString(new Date()));
valuesAccount.put(Sqlite.COL_TARGET_ID, account.id);
valuesAccount.put(Sqlite.COL_USER_ID, currentUser.user_id);
valuesAccount.put(Sqlite.COL_INSTANCE, currentUser.instance);
valuesAccount.put(Sqlite.COL_TYPE, CacheType.ACCOUNT.getValue());
removeIntent(currentUser, account.id);
db.insertOrThrow(Sqlite.TABLE_INTENT, null, valuesAccount);
}
}
/**
* Insert a bundle in db
*
* @param bundle {@link Bundle}
* @return long - db id
* @throws DBException exception with database
*/
private long insertIntent(Bundle bundle, BaseAccount currentUser) throws DBException {
if (db == null) {
throw new DBException("db is null. Wrong initialization.");
}
ContentValues values = new ContentValues();
values.put(Sqlite.COL_BUNDLE, serializeBundle(bundle));
values.put(Sqlite.COL_CREATED_AT, Helper.dateToString(new Date()));
values.put(Sqlite.COL_TYPE, CacheType.ARGS.getValue());
if (bundle.containsKey(Helper.ARG_ACCOUNT) && currentUser != null) {
ContentValues valuesAccount = new ContentValues();
Bundle bundleAccount = new Bundle();
Account account = null;
try {
account = (Account) bundle.getSerializable(Helper.ARG_ACCOUNT);
} catch (ClassCastException ignored) {
}
if (account != null) {
bundleAccount.putSerializable(Helper.ARG_ACCOUNT, account);
valuesAccount.put(Sqlite.COL_BUNDLE, serializeBundle(bundleAccount));
valuesAccount.put(Sqlite.COL_CREATED_AT, Helper.dateToString(new Date()));
valuesAccount.put(Sqlite.COL_TARGET_ID, account.id);
valuesAccount.put(Sqlite.COL_USER_ID, currentUser.user_id);
valuesAccount.put(Sqlite.COL_INSTANCE, currentUser.instance);
valuesAccount.put(Sqlite.COL_TYPE, CacheType.ACCOUNT.getValue());
removeIntent(currentUser, account.id);
db.insertOrThrow(Sqlite.TABLE_INTENT, null, valuesAccount);
}
}
if (bundle.containsKey(Helper.ARG_STATUS) && currentUser != null) {
ContentValues valuesAccount = new ContentValues();
Bundle bundleStatus = new Bundle();
Status status = null;
try {
status = (Status) bundle.getSerializable(Helper.ARG_STATUS);
} catch (ClassCastException ignored) {
}
if (status != null) {
bundleStatus.putSerializable(Helper.ARG_STATUS, status);
valuesAccount.put(Sqlite.COL_BUNDLE, serializeBundle(bundleStatus));
valuesAccount.put(Sqlite.COL_CREATED_AT, Helper.dateToString(new Date()));
valuesAccount.put(Sqlite.COL_TARGET_ID, status.id);
valuesAccount.put(Sqlite.COL_USER_ID, currentUser.user_id);
valuesAccount.put(Sqlite.COL_INSTANCE, currentUser.instance);
valuesAccount.put(Sqlite.COL_TYPE, CacheType.STATUS.getValue());
removeIntent(currentUser, status.id);
db.insertOrThrow(Sqlite.TABLE_INTENT, null, valuesAccount);
}
}
try {
return db.insertOrThrow(Sqlite.TABLE_INTENT, null, values);
} catch (Exception e) {
e.printStackTrace();
return -1;
}
}
public void getBundle(long id, BaseAccount Account, BundleCallback callback) {
new Thread(() -> {
Bundle bundle = null;
try {
CachedBundle cachedBundle = getCachedBundle(String.valueOf(id));
if (cachedBundle != null) {
bundle = cachedBundle.bundle;
if (bundle != null && bundle.containsKey(Helper.ARG_CACHED_ACCOUNT_ID)) {
Account cachedAccount = getCachedAccount(Account, bundle.getString(Helper.ARG_CACHED_ACCOUNT_ID));
if (cachedAccount != null) {
bundle.putSerializable(Helper.ARG_ACCOUNT, cachedAccount);
}
}
if (bundle != null && bundle.containsKey(Helper.ARG_CACHED_STATUS_ID)) {
Status cachedStatus = getCachedStatus(Account, bundle.getString(Helper.ARG_CACHED_STATUS_ID));
if (cachedStatus != null) {
bundle.putSerializable(Helper.ARG_STATUS, cachedStatus);
}
}
}
} catch (DBException ignored) {
}
if (bundle == null) {
bundle = new Bundle();
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Bundle finalBundle = bundle;
Runnable myRunnable = () -> callback.get(finalBundle);
mainHandler.post(myRunnable);
}).start();
}
public void insertBundle(Bundle bundle, BaseAccount Account, BundleInsertCallback callback) {
new Thread(() -> {
long dbBundleId = -1;
try {
dbBundleId = insertIntent(bundle, Account);
} catch (DBException ignored) {
}
Handler mainHandler = new Handler(Looper.getMainLooper());
long finalDbBundleId = dbBundleId;
Runnable myRunnable = () -> callback.inserted(finalDbBundleId);
mainHandler.post(myRunnable);
}).start();
}
/**
* Returns a bundle by targeted account id
*
* @param target_id String
* @return Account {@link Account}
*/
public Account getCachedAccount(BaseAccount account, String target_id) throws DBException {
if (db == null) {
throw new DBException("db is null. Wrong initialization.");
}
if (target_id == null) {
return null;
}
if (account == null) {
account = new BaseAccount();
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
account.user_id = sharedpreferences.getString(PREF_USER_ID, null);
account.instance = sharedpreferences.getString(PREF_USER_INSTANCE, null);
}
try {
Cursor c = db.query(Sqlite.TABLE_INTENT, null, Sqlite.COL_USER_ID + " = '" + account.user_id + "' AND "
+ Sqlite.COL_INSTANCE + " = '" + account.instance + "' AND "
+ Sqlite.COL_TYPE + " = '" + CacheType.ACCOUNT.getValue() + "' AND "
+ Sqlite.COL_TARGET_ID + " = '" + target_id + "'", null, null, null, null, "1");
CachedBundle cachedBundle = cursorToCachedBundle(c);
if (cachedBundle != null && cachedBundle.bundle.containsKey(Helper.ARG_ACCOUNT)) {
return (Account) cachedBundle.bundle.getSerializable(Helper.ARG_ACCOUNT);
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return null;
}
/**
* Returns a bundle by targeted status id
*
* @param target_id String
* @return Status {@link Status}
*/
private Status getCachedStatus(BaseAccount account, String target_id) throws DBException {
if (db == null) {
throw new DBException("db is null. Wrong initialization.");
}
if (target_id == null) {
return null;
}
if (account == null) {
account = new BaseAccount();
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
account.user_id = sharedpreferences.getString(PREF_USER_ID, null);
account.instance = sharedpreferences.getString(PREF_USER_INSTANCE, null);
}
try {
Cursor c = db.query(Sqlite.TABLE_INTENT, null, Sqlite.COL_USER_ID + " = '" + account.user_id + "' AND "
+ Sqlite.COL_INSTANCE + " = '" + account.instance + "' AND "
+ Sqlite.COL_TYPE + " = '" + CacheType.STATUS.getValue() + "' AND "
+ Sqlite.COL_TARGET_ID + " = '" + target_id + "'", null, null, null, null, "1");
CachedBundle cachedBundle = cursorToCachedBundle(c);
if (cachedBundle != null && cachedBundle.bundle.containsKey(Helper.ARG_STATUS)) {
return (Status) cachedBundle.bundle.getSerializable(Helper.ARG_STATUS);
}
} catch (Exception e) {
return null;
}
return null;
}
/**
* Returns a bundle by its ID
*
* @param id String
* @return CachedBundle {@link CachedBundle}
*/
private CachedBundle getCachedBundle(String id) throws DBException {
if (db == null) {
throw new DBException("db is null. Wrong initialization.");
}
try {
Cursor c = db.query(Sqlite.TABLE_INTENT, null, Sqlite.COL_ID + " = \"" + id + "\"", null, null, null, null, "1");
return cursorToCachedBundle(c);
} catch (Exception e) {
return null;
}
}
/**
* Remove a bundle from db
*/
public void deleteOldIntent() throws DBException {
if (db == null) {
throw new DBException("db is null. Wrong initialization.");
}
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
cal.add(Calendar.DATE, -1);
Date date = cal.getTime();
String dateStr = Helper.dateToString(date);
try {
db.delete(Sqlite.TABLE_INTENT, Sqlite.COL_CREATED_AT + " < ?", new String[]{dateStr});
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Remove a bundle from db
*/
private void removeIntent(BaseAccount account, String target_id) throws DBException {
if (db == null) {
throw new DBException("db is null. Wrong initialization.");
}
if (account == null || target_id == null) {
return;
}
db.delete(Sqlite.TABLE_INTENT, Sqlite.COL_USER_ID + " = '" + account.user_id + "' AND "
+ Sqlite.COL_INSTANCE + " = '" + account.instance + "' AND "
+ Sqlite.COL_TARGET_ID + " = '" + target_id + "'", null);
}
/***
* Method to hydrate an CachedBundle from database
* @param c Cursor
* @return CachedBundle {@link CachedBundle}
*/
private CachedBundle cursorToCachedBundle(Cursor c) {
//No element found
if (c.getCount() == 0) {
c.close();
return null;
}
//Take the first element
c.moveToFirst();
//New user
CachedBundle account = convertCursorToCachedBundle(c);
//Close the cursor
c.close();
return account;
}
/**
* Read cursor and hydrate without closing it
*
* @param c - Cursor
* @return Account
*/
private CachedBundle convertCursorToCachedBundle(Cursor c) {
CachedBundle cachedBundle = new CachedBundle();
cachedBundle.id = c.getString(c.getColumnIndexOrThrow(Sqlite.COL_ID));
cachedBundle.bundle = deserializeBundle(c.getString(c.getColumnIndexOrThrow(Sqlite.COL_BUNDLE)));
cachedBundle.user_id = c.getString(c.getColumnIndexOrThrow(Sqlite.COL_USER_ID));
cachedBundle.instance = c.getString(c.getColumnIndexOrThrow(Sqlite.COL_INSTANCE));
cachedBundle.target_id = c.getString(c.getColumnIndexOrThrow(Sqlite.COL_TARGET_ID));
cachedBundle.cacheType = CacheType.valueOf(c.getString(c.getColumnIndexOrThrow(Sqlite.COL_TYPE)));
cachedBundle.created_at = Helper.stringToDate(context, c.getString(c.getColumnIndexOrThrow(Sqlite.COL_CREATED_AT)));
return cachedBundle;
}
private String serializeBundle(final Bundle bundle) {
String base64 = null;
final Parcel parcel = Parcel.obtain();
try {
parcel.writeBundle(bundle);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final GZIPOutputStream zos = new GZIPOutputStream(new BufferedOutputStream(bos));
zos.write(parcel.marshall());
zos.close();
base64 = Base64.encodeToString(bos.toByteArray(), 0);
} catch (IOException e) {
e.printStackTrace();
} finally {
parcel.recycle();
}
return base64;
}
private Bundle deserializeBundle(final String base64) {
Bundle bundle = null;
final Parcel parcel = Parcel.obtain();
try {
final ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
final byte[] buffer = new byte[1024];
final GZIPInputStream zis = new GZIPInputStream(new ByteArrayInputStream(Base64.decode(base64, 0)));
int len;
while ((len = zis.read(buffer)) != -1) {
byteBuffer.write(buffer, 0, len);
}
zis.close();
parcel.unmarshall(byteBuffer.toByteArray(), 0, byteBuffer.size());
parcel.setDataPosition(0);
bundle = parcel.readBundle(getClass().getClassLoader());
} catch (IOException e) {
e.printStackTrace();
} finally {
parcel.recycle();
}
return bundle;
}
public enum CacheType {
@SerializedName("ARGS")
ARGS("ARGS"),
@SerializedName("ACCOUNT")
ACCOUNT("ACCOUNT"),
@SerializedName("STATUS")
STATUS("STATUS");
private final String value;
CacheType(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
public interface BundleCallback {
void get(Bundle bundle);
}
public interface BundleInsertCallback {
void inserted(long bundleId);
}
}

View file

@ -0,0 +1,53 @@
package app.fedilab.android.mastodon.client.entities.app;
/* Copyright 2023 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import android.text.style.URLSpan;
import java.util.List;
public class MarkdownConverter {
public List<MarkdownItem> markdownItems;
public MarkdownItem getByPosition(int position) {
if (markdownItems != null && !markdownItems.isEmpty() && position < markdownItems.size()) {
for (MarkdownItem markdownItem : markdownItems) {
if (markdownItem.position == position) {
return markdownItem;
}
}
}
return null;
}
public static class MarkdownItem {
public String code;
public int position;
public URLSpan urlSpan;
public int regexPosition(List<MarkdownItem> markdownItems) {
int position = 0;
int loopedPosition = 0;
for (MarkdownItem markdownItem : markdownItems) {
if (markdownItem.code.equals(code) && loopedPosition <= this.position) {
position++;
}
loopedPosition++;
}
return position;
}
}
}

View file

@ -46,8 +46,12 @@ public class RemoteInstance implements Serializable {
PEERTUBE("PEERTUBE"), PEERTUBE("PEERTUBE"),
@SerializedName("NITTER") @SerializedName("NITTER")
NITTER("NITTER"), NITTER("NITTER"),
@SerializedName("NITTER_TAG")
NITTER_TAG("NITTER_TAG"),
@SerializedName("MISSKEY") @SerializedName("MISSKEY")
MISSKEY("MISSKEY"), MISSKEY("MISSKEY"),
@SerializedName("LEMMY")
LEMMY("LEMMY"),
@SerializedName("GNU") @SerializedName("GNU")
GNU("GNU"); GNU("GNU");

View file

@ -14,6 +14,8 @@ package app.fedilab.android.mastodon.client.entities.app;
* You should have received a copy of the GNU General Public License along with Fedilab; if not, * You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.mastodon.helper.Helper.TAG;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
@ -229,6 +231,28 @@ public class StatusCache {
return count; return count;
} }
/**
* get all cache messages for home
*
* @param baseAccount Status {@link BaseAccount}
* @return List<Status>
* @throws DBException Exception
*/
public List<Status> getHome(BaseAccount baseAccount) throws DBException {
if (db == null) {
throw new DBException("db is null. Wrong initialization.");
}
String selection = Sqlite.COL_INSTANCE + "='" + baseAccount.instance + "' AND " + Sqlite.COL_USER_ID + "= '" + baseAccount.user_id + "' AND " + Sqlite.COL_SLUG + "= '" + Timeline.TimeLineEnum.HOME.getValue() + "' ";
try {
Cursor c = db.query(Sqlite.TABLE_STATUS_CACHE, null, selection, null, Sqlite.COL_STATUS_ID, null, Sqlite.COL_STATUS_ID + " ASC", null);
return cursorToListOfStatuses(c);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/** /**
* count messages for other timelines * count messages for other timelines
* *
@ -267,7 +291,6 @@ public class StatusCache {
if (statusCache.type != null) { if (statusCache.type != null) {
query += " AND " + Sqlite.COL_TYPE + " = '" + statusCache.type.getValue() + "'"; query += " AND " + Sqlite.COL_TYPE + " = '" + statusCache.type.getValue() + "'";
} }
Cursor mCount = db.rawQuery(query, null); Cursor mCount = db.rawQuery(query, null);
mCount.moveToFirst(); mCount.moveToFirst();
int count = mCount.getInt(0); int count = mCount.getInt(0);
@ -330,7 +353,11 @@ public class StatusCache {
values.put(Sqlite.COL_STATUS, mastodonStatusToStringStorage(statusCache.status)); values.put(Sqlite.COL_STATUS, mastodonStatusToStringStorage(statusCache.status));
} }
if (statusCache.notification != null) { if (statusCache.notification != null) {
values.put(Sqlite.COL_STATUS, mastodonNotificationToStringStorage(statusCache.notification)); Notification currentNotification = getCachedNotification(statusCache);
if(currentNotification != null && currentNotification.status != null) {
currentNotification.status = statusCache.notification.status;
values.put(Sqlite.COL_STATUS, mastodonNotificationToStringStorage(currentNotification));
}
} }
if (statusCache.conversation != null) { if (statusCache.conversation != null) {
values.put(Sqlite.COL_STATUS, mastodonConversationToStringStorage(statusCache.conversation)); values.put(Sqlite.COL_STATUS, mastodonConversationToStringStorage(statusCache.conversation));
@ -533,15 +560,28 @@ public class StatusCache {
String selection = Sqlite.COL_INSTANCE + "='" + instance + "' AND " + Sqlite.COL_USER_ID + "= '" + user_id + "' AND " + Sqlite.COL_TYPE + "= '" + Timeline.TimeLineEnum.NOTIFICATION.getValue() + "' "; String selection = Sqlite.COL_INSTANCE + "='" + instance + "' AND " + Sqlite.COL_USER_ID + "= '" + user_id + "' AND " + Sqlite.COL_TYPE + "= '" + Timeline.TimeLineEnum.NOTIFICATION.getValue() + "' ";
String limit = String.valueOf(MastodonHelper.statusesPerCall(context)); String limit = String.valueOf(MastodonHelper.statusesPerCall(context));
if (min_id != null) { if (min_id != null) {
selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + min_id + "' "; if (Helper.isNumeric(min_id)) {
selection += "AND " + Sqlite.COL_STATUS_ID + " > cast(" + min_id + " as int) ";
} else {
selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + min_id + "' ";
}
order = " ASC"; order = " ASC";
} else if (max_id != null) { } else if (max_id != null) {
selection += "AND " + Sqlite.COL_STATUS_ID + " < '" + max_id + "' "; if (Helper.isNumeric(max_id)) {
selection += "AND " + Sqlite.COL_STATUS_ID + " < cast(" + max_id + " as int) ";
} else {
selection += "AND " + Sqlite.COL_STATUS_ID + " < '" + max_id + "' ";
}
} else if (since_id != null) { } else if (since_id != null) {
selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + since_id + "' "; if (Helper.isNumeric(since_id)) {
selection += "AND " + Sqlite.COL_STATUS_ID + " > cast(" + since_id + " as int) ";
} else {
selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + since_id + "' ";
}
limit = null; limit = null;
} }
if (exclude_type != null && exclude_type.size() > 0) { if (exclude_type != null && exclude_type.size() > 0) {
StringBuilder exclude = new StringBuilder(); StringBuilder exclude = new StringBuilder();
for (String excluded : exclude_type) { for (String excluded : exclude_type) {
@ -559,6 +599,15 @@ public class StatusCache {
} }
} }
public Notification getCachedNotification(StatusCache statusCache) throws DBException {
if (db == null) {
throw new DBException("db is null. Wrong initialization.");
}
Cursor c = db.query(Sqlite.TABLE_STATUS_CACHE, null, Sqlite.COL_STATUS_ID + " = ? AND " + Sqlite.COL_USER_ID + " = ? AND " + Sqlite.COL_INSTANCE + " =?",
new String[]{statusCache.status_id, statusCache.user_id, statusCache.instance}, null, null, null, "1");
c.moveToFirst();
return convertCursorToNotification(c);
}
/** /**
* Get paginated conversations from db * Get paginated conversations from db
@ -578,12 +627,25 @@ public class StatusCache {
String selection = Sqlite.COL_INSTANCE + "='" + instance + "' AND " + Sqlite.COL_USER_ID + "= '" + user_id + "' AND " + Sqlite.COL_TYPE + "= '" + Timeline.TimeLineEnum.CONVERSATION.getValue() + "' "; String selection = Sqlite.COL_INSTANCE + "='" + instance + "' AND " + Sqlite.COL_USER_ID + "= '" + user_id + "' AND " + Sqlite.COL_TYPE + "= '" + Timeline.TimeLineEnum.CONVERSATION.getValue() + "' ";
String limit = String.valueOf(MastodonHelper.statusesPerCall(context)); String limit = String.valueOf(MastodonHelper.statusesPerCall(context));
if (min_id != null) { if (min_id != null) {
selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + min_id + "' "; if (Helper.isNumeric(min_id)) {
selection += "AND " + Sqlite.COL_STATUS_ID + " > cast(" + min_id + " as int) ";
} else {
selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + min_id + "' ";
}
order = " ASC"; order = " ASC";
} else if (max_id != null) { } else if (max_id != null) {
selection += "AND " + Sqlite.COL_STATUS_ID + " < '" + max_id + "' ";
if (Helper.isNumeric(max_id)) {
selection += "AND " + Sqlite.COL_STATUS_ID + " < cast(" + max_id + " as int) ";
} else {
selection += "AND " + Sqlite.COL_STATUS_ID + " < '" + max_id + "' ";
}
} else if (since_id != null) { } else if (since_id != null) {
selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + since_id + "' "; if (Helper.isNumeric(since_id)) {
selection += "AND " + Sqlite.COL_STATUS_ID + " > cast(" + since_id + " as int) ";
} else {
selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + since_id + "' ";
}
limit = null; limit = null;
} }
try { try {
@ -614,12 +676,25 @@ public class StatusCache {
String selection = Sqlite.COL_INSTANCE + "='" + instance + "' AND " + Sqlite.COL_USER_ID + "= '" + user_id + "' AND " + Sqlite.COL_SLUG + "= '" + slug + "' "; String selection = Sqlite.COL_INSTANCE + "='" + instance + "' AND " + Sqlite.COL_USER_ID + "= '" + user_id + "' AND " + Sqlite.COL_SLUG + "= '" + slug + "' ";
String limit = String.valueOf(MastodonHelper.statusesPerCall(context)); String limit = String.valueOf(MastodonHelper.statusesPerCall(context));
if (min_id != null) { if (min_id != null) {
selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + min_id + "' ";
if (Helper.isNumeric(min_id)) {
selection += "AND " + Sqlite.COL_STATUS_ID + " > cast(" + min_id + " as int) ";
} else {
selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + min_id + "' ";
}
order = " ASC"; order = " ASC";
} else if (max_id != null) { } else if (max_id != null) {
selection += "AND " + Sqlite.COL_STATUS_ID + " < '" + max_id + "' "; if (Helper.isNumeric(max_id)) {
selection += "AND " + Sqlite.COL_STATUS_ID + " < cast(" + max_id + " as int) ";
} else {
selection += "AND " + Sqlite.COL_STATUS_ID + " < '" + max_id + "' ";
}
} else if (since_id != null) { } else if (since_id != null) {
selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + since_id + "' "; if (Helper.isNumeric(since_id)) {
selection += "AND " + Sqlite.COL_STATUS_ID + " > cast(" + since_id + " as int) ";
} else {
selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + since_id + "' ";
}
limit = null; limit = null;
} }
try { try {

View file

@ -378,6 +378,8 @@ public class Timeline {
REMOTE("REMOTE"), REMOTE("REMOTE"),
@SerializedName("TREND_TAG") @SerializedName("TREND_TAG")
TREND_TAG("TREND_TAG"), TREND_TAG("TREND_TAG"),
@SerializedName("TREND_LINK")
TREND_LINK("TREND_LINK"),
@SerializedName("TREND_MESSAGE") @SerializedName("TREND_MESSAGE")
TREND_MESSAGE("TREND_MESSAGE"), TREND_MESSAGE("TREND_MESSAGE"),
@SerializedName("ACCOUNT_SUGGESTION") @SerializedName("ACCOUNT_SUGGESTION")

View file

@ -0,0 +1,246 @@
package app.fedilab.android.mastodon.client.entities.app;
/* Copyright 2023 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.google.gson.annotations.SerializedName;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.sqlite.Sqlite;
public class TimelineCacheLogs {
private final SQLiteDatabase db;
@SerializedName("id")
public long id;
@SerializedName("user_id")
public String user_id;
@SerializedName("instance")
public String instance;
@SerializedName("slug")
public String slug;
@SerializedName("type")
public Timeline.TimeLineEnum type;
@SerializedName("created_at")
public Date created_at;
@SerializedName("fetched")
public int fetched;
@SerializedName("failed")
public int failed;
@SerializedName("inserted")
public int inserted;
@SerializedName("updated")
public int updated;
@SerializedName("frequency")
public int frequency;
private Context context;
public TimelineCacheLogs() {
db = null;
}
public TimelineCacheLogs(Context context) {
//Creation of the DB with tables
this.context = context;
this.db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
}
/**
* get all cache timelineCacheLogs for home
*
* @param baseAccount Status {@link BaseAccount}
* @return List<Status>
* @throws DBException Exception
*/
public List<TimelineCacheLogs> getHome(BaseAccount baseAccount) throws DBException {
if (db == null) {
throw new DBException("db is null. Wrong initialization.");
}
String selection = Sqlite.COL_INSTANCE + "='" + baseAccount.instance + "' AND " + Sqlite.COL_USER_ID + "= '" + baseAccount.user_id + "' AND " + Sqlite.COL_SLUG + "= '" + Timeline.TimeLineEnum.HOME.getValue() + "' ";
try {
Cursor c = db.query(Sqlite.TABLE_TIMELINE_CACHE_LOGS, null, selection, null, null, null, Sqlite.COL_ID + " ASC", null);
return cursorToListOfStatuses(c);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Insert a status in db
*
* @param timelineCacheLogs {@link TimelineCacheLogs}
* @return long - db id
* @throws DBException exception with database
*/
public long insert(TimelineCacheLogs timelineCacheLogs) throws DBException {
if (db == null) {
throw new DBException("db is null. Wrong initialization.");
}
ContentValues values = new ContentValues();
values.put(Sqlite.COL_USER_ID, timelineCacheLogs.user_id);
values.put(Sqlite.COL_INSTANCE, timelineCacheLogs.instance);
values.put(Sqlite.COL_SLUG, timelineCacheLogs.slug);
values.put(Sqlite.COL_TYPE, timelineCacheLogs.type.getValue());
values.put(Sqlite.COL_CREATED_AT, Helper.dateToString(new Date()));
values.put(Sqlite.COL_FAILED, timelineCacheLogs.failed);
values.put(Sqlite.COL_FETCHED, timelineCacheLogs.fetched);
values.put(Sqlite.COL_FREQUENCY, timelineCacheLogs.frequency);
values.put(Sqlite.COL_INSERTED, timelineCacheLogs.inserted);
values.put(Sqlite.COL_UPDATED, timelineCacheLogs.updated);
//Inserts token
try {
return db.insertOrThrow(Sqlite.TABLE_TIMELINE_CACHE_LOGS, null, values);
} catch (Exception e) {
e.printStackTrace();
return -1;
}
}
/**
* delete all cache for all account
*
* @return long - db id
* @throws DBException exception with database
*/
public long deleteForAllAccount() throws DBException {
if (db == null) {
throw new DBException("db is null. Wrong initialization.");
}
try {
return db.delete(Sqlite.TABLE_TIMELINE_CACHE_LOGS, null, null);
} catch (Exception e) {
e.printStackTrace();
return -1;
}
}
/**
* delete all cache for all account after 7 days
*
* @return long - db id
* @throws DBException exception with database
*/
public long deleteForAllAccountAfter7Days() throws DBException {
if (db == null) {
throw new DBException("db is null. Wrong initialization.");
}
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
cal.add(Calendar.DATE, -7);
Date date = cal.getTime();
String dateStr = Helper.dateToString(date);
try {
return db.delete(Sqlite.TABLE_TIMELINE_CACHE_LOGS, Sqlite.COL_CREATED_AT + " < ?", new String[]{dateStr});
} catch (Exception e) {
e.printStackTrace();
return -1;
}
}
/**
* delete all cache for an slug
*
* @return long - db id
* @throws DBException exception with database
*/
public long deleteForSlug(String slug) throws DBException {
if (db == null) {
throw new DBException("db is null. Wrong initialization.");
}
try {
return db.delete(Sqlite.TABLE_TIMELINE_CACHE_LOGS,
Sqlite.COL_SLUG + " = ? AND " + Sqlite.COL_USER_ID + " = ? AND " + Sqlite.COL_INSTANCE + " =?",
new String[]{slug, MainActivity.currentUserID, MainActivity.currentInstance});
} catch (Exception e) {
e.printStackTrace();
return -1;
}
}
public int count(BaseAccount account) throws DBException {
if (db == null) {
throw new DBException("db is null. Wrong initialization.");
}
Cursor mCount = db.rawQuery("select count(*) from " + Sqlite.TABLE_TIMELINE_CACHE_LOGS
+ " where " + Sqlite.COL_INSTANCE + " = '" + account.instance + "' AND " + Sqlite.COL_USER_ID + " = '" + account.user_id + "'", null);
mCount.moveToFirst();
int count = mCount.getInt(0);
mCount.close();
return count;
}
/**
* Convert a cursor to list of TimelineCacheLogs
*
* @param c Cursor
* @return List<TimelineCacheLogs>
*/
private List<TimelineCacheLogs> cursorToListOfStatuses(Cursor c) {
//No element found
if (c.getCount() == 0) {
c.close();
return null;
}
List<TimelineCacheLogs> timelineCacheLogsList = new ArrayList<>();
while (c.moveToNext()) {
TimelineCacheLogs timelineCacheLogs = convertCursorToTimelineCacheLogs(c);
timelineCacheLogsList.add(timelineCacheLogs);
}
//Close the cursor
c.close();
return timelineCacheLogsList;
}
/**
* Read cursor and hydrate without closing it
*
* @param c - Cursor
* @return Timeline
*/
private TimelineCacheLogs convertCursorToTimelineCacheLogs(Cursor c) {
TimelineCacheLogs timelineCacheLogs = new TimelineCacheLogs();
timelineCacheLogs.id = c.getInt(c.getColumnIndexOrThrow(Sqlite.COL_ID));
timelineCacheLogs.type = Timeline.TimeLineEnum.valueOf(c.getString(c.getColumnIndexOrThrow(Sqlite.COL_TYPE)));
timelineCacheLogs.instance = c.getString(c.getColumnIndexOrThrow(Sqlite.COL_INSTANCE));
timelineCacheLogs.user_id = c.getString(c.getColumnIndexOrThrow(Sqlite.COL_USER_ID));
timelineCacheLogs.slug = c.getString(c.getColumnIndexOrThrow(Sqlite.COL_SLUG));
timelineCacheLogs.created_at = Helper.stringToDate(context, c.getString(c.getColumnIndexOrThrow(Sqlite.COL_CREATED_AT)));
timelineCacheLogs.failed = c.getInt(c.getColumnIndexOrThrow(Sqlite.COL_FAILED));
timelineCacheLogs.fetched = c.getInt(c.getColumnIndexOrThrow(Sqlite.COL_FETCHED));
timelineCacheLogs.inserted = c.getInt(c.getColumnIndexOrThrow(Sqlite.COL_INSERTED));
timelineCacheLogs.updated = c.getInt(c.getColumnIndexOrThrow(Sqlite.COL_UPDATED));
timelineCacheLogs.frequency = c.getInt(c.getColumnIndexOrThrow(Sqlite.COL_FREQUENCY));
return timelineCacheLogs;
}
}

View file

@ -0,0 +1,268 @@
package app.fedilab.android.mastodon.client.entities.lemmy;
/* Copyright 2023 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import app.fedilab.android.mastodon.client.entities.api.Account;
import app.fedilab.android.mastodon.client.entities.api.Attachment;
import app.fedilab.android.mastodon.client.entities.api.Status;
public class LemmyPost implements Serializable {
@SerializedName("post")
public Post post;
@SerializedName("comment")
public Comment comment;
@SerializedName("creator")
public Creator creator;
/*@SerializedName("community")
public Community community;*/
@SerializedName("counts")
public Counts counts;
@SerializedName("creator_banned_from_community")
public boolean creator_banned_from_community;
@SerializedName("saved")
public boolean saved;
@SerializedName("read")
public boolean read;
@SerializedName("creator_blocked")
public boolean creator_blocked;
@SerializedName("unread_comments")
public int unread_comments;
public static Status convert(LemmyPost lemmyPost, String instance) {
Status status = new Status();
status.id = lemmyPost.comment == null ? lemmyPost.post.id : lemmyPost.comment.id;
if (lemmyPost.comment != null) {
status.in_reply_to_id = lemmyPost.comment.post_id;
status.lemmy_post_id = null;
} else {
status.lemmy_post_id = lemmyPost.post.id;
}
status.content = lemmyPost.comment == null ? lemmyPost.post.name : lemmyPost.comment.content;
status.visibility = "public";
status.created_at = lemmyPost.comment == null ? lemmyPost.post.published : lemmyPost.comment.published;
status.url = lemmyPost.comment == null ? lemmyPost.post.ap_id : lemmyPost.comment.ap_id;
status.uri = lemmyPost.comment == null ? lemmyPost.post.ap_id : lemmyPost.comment.ap_id;
Account account = new Account();
account.id = lemmyPost.creator.id;
account.acct = lemmyPost.creator.name + "@" + instance;
account.username = "@" + lemmyPost.creator.name;
account.display_name = lemmyPost.creator.name;
account.avatar = lemmyPost.creator.avatar;
account.avatar_static = lemmyPost.creator.avatar;
status.account = account;
if (lemmyPost.comment == null && lemmyPost.post.thumbnail_url != null) {
List<Attachment> attachmentList = new ArrayList<>();
Attachment attachment = new Attachment();
attachment.type = "image";
attachment.url = lemmyPost.post.thumbnail_url;
attachment.preview_url = lemmyPost.post.thumbnail_url;
if (lemmyPost.post.nsfw) {
status.sensitive = true;
}
attachmentList.add(attachment);
status.media_attachments = attachmentList;
} else if (lemmyPost.comment != null && lemmyPost.comment.thumbnail_url != null) {
List<Attachment> attachmentList = new ArrayList<>();
Attachment attachment = new Attachment();
attachment.type = "image";
attachment.url = lemmyPost.comment.thumbnail_url;
attachment.preview_url = lemmyPost.comment.thumbnail_url;
if (lemmyPost.post.nsfw) {
status.sensitive = true;
}
attachmentList.add(attachment);
status.media_attachments = attachmentList;
}
return status;
}
public static class LemmyPosts {
@SerializedName("posts")
public List<LemmyPost> posts;
}
public static class LemmyComments {
@SerializedName("comments")
public List<LemmyPost> comments;
}
public static class Post implements Serializable {
@SerializedName("id")
public String id;
@SerializedName("name")
public String name;
@SerializedName("body")
public String body;
@SerializedName("creator_id")
public String creator_id;
@SerializedName("community_id")
public String community_id;
@SerializedName("removed")
public boolean removed;
@SerializedName("locked")
public boolean locked;
@SerializedName("published")
public Date published;
@SerializedName("updated")
public Date updated;
@SerializedName("deleted")
public boolean deleted;
@SerializedName("nsfw")
public boolean nsfw;
@SerializedName("thumbnail_url")
public String thumbnail_url;
@SerializedName("ap_id")
public String ap_id;
@SerializedName("local")
public boolean local;
@SerializedName("language_id")
public String language_id;
@SerializedName("featured_community")
public boolean featured_community;
@SerializedName("featured_local")
public boolean featured_local;
}
public static class Comment implements Serializable {
@SerializedName("id")
public String id;
@SerializedName("creator_id")
public String creator_id;
@SerializedName("post_id")
public String post_id;
@SerializedName("content")
public String content;
@SerializedName("removed")
public boolean removed;
@SerializedName("published")
public Date published;
@SerializedName("thumbnail_url")
public String thumbnail_url;
@SerializedName("deleted")
public boolean deleted;
@SerializedName("ap_id")
public String ap_id;
@SerializedName("local")
public boolean local;
@SerializedName("path")
public String path;
@SerializedName("distinguished")
public boolean distinguished;
@SerializedName("language_id")
public String language_id;
}
public static class Community implements Serializable {
@SerializedName("id")
public String id;
@SerializedName("name")
public String name;
@SerializedName("title")
public String title;
@SerializedName("description")
public String description;
@SerializedName("removed")
public boolean removed;
@SerializedName("published")
public Date published;
@SerializedName("updated")
public Date updated;
@SerializedName("deleted")
public boolean deleted;
@SerializedName("nsfw")
public boolean nsfw;
@SerializedName("actor_id")
public String actor_id;
@SerializedName("local")
public boolean local;
@SerializedName("icon")
public String icon;
@SerializedName("hidden")
public boolean hidden;
@SerializedName("posting_restricted_to_mods")
public boolean posting_restricted_to_mods;
@SerializedName("instance_id")
public String instance_id;
}
public static class Creator implements Serializable {
@SerializedName("id")
public String id;
@SerializedName("name")
public String name;
@SerializedName("avatar")
public String avatar;
@SerializedName("banned")
public boolean banned;
@SerializedName("published")
public Date published;
@SerializedName("actor_id")
public String actor_id;
@SerializedName("bio")
public String bio;
@SerializedName("local")
public boolean local;
@SerializedName("deleted")
public boolean deleted;
@SerializedName("matrix_user_id")
public String matrix_user_id;
@SerializedName("admin")
public boolean admin;
@SerializedName("bot_account")
public boolean bot_account;
@SerializedName("instance_id")
public String instance_id;
}
public static class Counts implements Serializable {
@SerializedName("id")
public String id;
@SerializedName("post_id")
public String post_id;
@SerializedName("comments")
public int comments;
@SerializedName("score")
public int score;
@SerializedName("upvotes")
public int upvotes;
@SerializedName("downvotes")
public int downvotes;
@SerializedName("published")
public Date published;
@SerializedName("newest_comment_time_necro")
public Date newest_comment_time_necro;
@SerializedName("newest_comment_time")
public Date newest_comment_time;
@SerializedName("featured_local")
public boolean featured_local;
@SerializedName("hot_rank")
public int hot_rank;
@SerializedName("hot_rank_active")
public int hot_rank_active;
}
}

View file

@ -14,10 +14,12 @@ package app.fedilab.android.mastodon.client.entities.nitter;
* You should have received a copy of the GNU General Public License along with Fedilab; if not, * You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.jsoup.select.Elements;
import org.simpleframework.xml.Element; import org.simpleframework.xml.Element;
import org.simpleframework.xml.ElementList; import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.Namespace; import org.simpleframework.xml.Namespace;
@ -25,10 +27,11 @@ import org.simpleframework.xml.Path;
import org.simpleframework.xml.Root; import org.simpleframework.xml.Root;
import java.io.Serializable; import java.io.Serializable;
import java.net.IDN;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.ThreadLocalRandom;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -61,14 +64,9 @@ public class Nitter implements Serializable {
public static MastodonTimelinesService initInstanceXMLOnly(Context context, String instance) { public static MastodonTimelinesService initInstanceXMLOnly(Context context, String instance) {
OkHttpClient okHttpClient = new OkHttpClient.Builder() OkHttpClient okHttpClient = Helper.myOkHttpClient(context);
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.callTimeout(60, TimeUnit.SECONDS)
.proxy(Helper.getProxy(context))
.build();
Retrofit retrofit = new Retrofit.Builder() Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + instance) .baseUrl("https://" + (instance != null ? IDN.toASCII(instance, IDN.ALLOW_UNASSIGNED) : null))
.addConverterFactory(SimpleXmlConverterFactory.create()) .addConverterFactory(SimpleXmlConverterFactory.create())
.client(okHttpClient) .client(okHttpClient)
.build(); .build();
@ -87,7 +85,8 @@ public class Nitter implements Serializable {
status.text = feedItem.title; status.text = feedItem.title;
status.content = status.content.replaceAll("<img [^>]*src=\"[^\"]+\"[^>]*>", ""); status.content = status.content.replaceAll("<img [^>]*src=\"[^\"]+\"[^>]*>", "");
status.visibility = "public"; status.visibility = "public";
status.created_at = Helper.stringToDateWithFormat(context, feedItem.pubDate, "EEE, dd MMM yyyy HH:mm:ss zzz"); String dateFormat = "E', 'dd' 'MMM' 'yyyy' 'hh:m:s' GMT'";
status.created_at = Helper.stringToDateWithFormat(context, feedItem.pubDate, dateFormat);
status.uri = feedItem.guid; status.uri = feedItem.guid;
status.url = feedItem.link; status.url = feedItem.link;
if (!accounts.containsKey(feedItem.creator)) { if (!accounts.containsKey(feedItem.creator)) {
@ -182,4 +181,83 @@ public class Nitter implements Serializable {
} }
public static Status nitterHTMLParser(Context context, org.jsoup.nodes.Element timelineItem, String nitterInstance) {
if(timelineItem == null) {
return null;
}
Status status = new Status();
Account account = new Account();
String fedilabInstance = "nitter.fedilab.app";
org.jsoup.nodes.Element messageLink;
if(timelineItem.select(".quote-text").html().isEmpty()) {
status.content = timelineItem.select(".tweet-content").html();
status.text = timelineItem.select(".tweet-content").text();
status.url = "https://"+ nitterInstance +timelineItem.select(".tweet-link").attr("href");
messageLink = timelineItem.select(".tweet-link").first();
} else {
status.content = timelineItem.select(".quote-text").html();
status.text = timelineItem.select(".quote-text").text();
status.url = "https://"+ nitterInstance +timelineItem.select(".quote-link").attr("href");
messageLink = timelineItem.select(".quote-link").first();
}
status.uri = status.url;
String status_id = String.valueOf(ThreadLocalRandom.current().nextLong(10,10000000));;
if(messageLink != null){
String[] splitLink = messageLink.attr("href").split("/");
status_id = splitLink[splitLink.length-1];
}
String pubDate = timelineItem.select(".tweet-date").select("a").attr("title");
org.jsoup.nodes.Element nameElement = timelineItem.select(".fullname").first();
String name = nameElement!= null?nameElement.text():"";
org.jsoup.nodes.Element userNameElement = timelineItem.select(".username").first();
String userName = userNameElement!= null?userNameElement.text().replace("@",""):"";
String avatar = "https://" + fedilabInstance + timelineItem.select(".avatar").attr("src");
account.id = userName;
account.acct = userName;
if(timelineItem.select(".replying-to").html().isEmpty()) {
account.username = userName;
account.display_name = name;
} else {
account.display_name = timelineItem.select(".fullname").text() +"&nbsp;" +timelineItem.select(".replying-to").text();
}
account.avatar = avatar;
account.avatar_static = avatar;
account.url = "https://"+ nitterInstance +"/" + userName;
status.id = status_id;
status.account = account;
Elements imageElements = timelineItem.select(".attachments").select("img");
Elements videoElements = timelineItem.select(".attachments").select("video");
ArrayList<Attachment> attachmentList = new ArrayList<>();
for(org.jsoup.nodes.Element imageElement: imageElements) {
Attachment attachment = new Attachment();
attachment.type = "image";
attachment.url = "https://"+fedilabInstance+imageElement.attr("src");
attachment.preview_url = "https://"+fedilabInstance+imageElement.attr("src");
attachment.id = imageElement.attr("src");
attachmentList.add(attachment);
}
for(org.jsoup.nodes.Element videoElement: videoElements) {
Attachment attachment = new Attachment();
attachment.type = "video";
attachment.url = "https://"+fedilabInstance+videoElement.child(0).attr("src");
attachment.preview_url = "https://"+fedilabInstance+videoElement.attr("poster");
attachment.id = videoElement.attr("poster");
attachmentList.add(attachment);
}
status.visibility = "public";
status.media_attachments = attachmentList;
String dateFormat = "MMM d', 'yyyy' · 'h:m a' UTC'";
status.created_at = Helper.stringToDateWithFormat(context, pubDate, dateFormat);
return status;
}
} }

View file

@ -18,24 +18,23 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.media3.database.ExoDatabaseProvider;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DefaultDataSourceFactory;
import androidx.media3.datasource.DefaultHttpDataSource;
import androidx.media3.datasource.FileDataSource;
import androidx.media3.datasource.cache.CacheDataSink;
import androidx.media3.datasource.cache.CacheDataSource;
import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor;
import androidx.media3.datasource.cache.SimpleCache;
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import com.google.android.exoplayer2.database.ExoDatabaseProvider;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.FileDataSource;
import com.google.android.exoplayer2.upstream.cache.CacheDataSink;
import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import java.io.File; import java.io.File;
import app.fedilab.android.R; import app.fedilab.android.R;
@androidx.annotation.OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
public class CacheDataSourceFactory implements DataSource.Factory { public class CacheDataSourceFactory implements DataSource.Factory {
private static SimpleCache sDownloadCache; private static SimpleCache sDownloadCache;

View file

@ -0,0 +1,121 @@
package app.fedilab.android.mastodon.helper;
/* Copyright 2023 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.mastodon.helper.Helper.mentionPatternALL;
import android.util.Patterns;
import java.util.ArrayList;
import java.util.regex.Matcher;
public class ComposeHelper {
/**
* Allows to split the toot by dot "." for sentences - adds number at the end automatically
*
* @param content String initial content
* @param maxChars int the max chars per toot
* @return ArrayList<String> split toot
*/
public static ArrayList<String> splitToots(String content, int maxChars) {
String[] splitContent = content.split("\\s");
ArrayList<String> mentions = new ArrayList<>();
int mentionLength;
StringBuilder mentionString = new StringBuilder();
Matcher matcher = mentionPatternALL.matcher(content);
while (matcher.find()) {
String mentionLong = matcher.group(1);
if (mentionLong != null) {
if (!mentions.contains(mentionLong)) {
mentions.add(mentionLong);
}
}
String mentionShort = matcher.group(2);
if (mentionShort != null) {
if (!mentions.contains(mentionShort)) {
mentions.add(mentionShort);
}
}
}
for (String mention : mentions) {
mentionString.append(mention).append(" ");
}
mentionLength = countLength(mentionString.toString()) + 1;
int maxCharsPerMessage = maxChars - mentionLength;
int totalCurrent = 0;
ArrayList<String> reply = new ArrayList<>();
int index = 0;
for (String s : splitContent) {
if ((totalCurrent + s.length() + 1) < maxCharsPerMessage) {
totalCurrent += (s.length() + 1);
} else {
if (content.length() > totalCurrent && totalCurrent > 0) {
String tempContent = content.substring(0, (totalCurrent));
content = content.substring(totalCurrent);
reply.add(index, tempContent);
index++;
totalCurrent = s.length() + 1;
}
}
}
if (totalCurrent > 0) {
reply.add(index, content);
}
if (reply.size() > 1) {
int i = 0;
for (String r : reply) {
if (mentions.size() > 0) {
String tmpMention = mentionString.toString();
for (String mention : mentions) {
if (r.contains(mention)) {
tmpMention = tmpMention.replace(mention, "");
}
}
reply.set(i, r + " " + tmpMention);
} else {
reply.set(i, r);
}
i++;
}
}
return reply;
}
/***
* Returns the length used when composing a toot
* @param mentions String containing mentions
* @return int - characters used
*/
public static int countLength(String mentions) {
String contentCount = mentions;
contentCount = contentCount.replaceAll("(?i)(^|[^/\\w])@(([a-z0-9_]+)@[a-z0-9.-]+[a-z0-9]+)", "$1@$3");
Matcher matcherALink = Patterns.WEB_URL.matcher(contentCount);
while (matcherALink.find()) {
final String url = matcherALink.group(1);
if (url != null) {
contentCount = contentCount.replace(url, "abcdefghijklmnopkrstuvw");
}
}
return contentCount.length();
}
}

View file

@ -14,6 +14,7 @@ package app.fedilab.android.mastodon.helper;
* You should have received a copy of the GNU General Public License along with Fedilab; if not, * You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -31,9 +32,9 @@ import androidx.preference.PreferenceManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.io.IOException; import java.io.IOException;
import java.net.IDN;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
import app.fedilab.android.BaseMainActivity; import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R; import app.fedilab.android.R;
@ -44,6 +45,7 @@ import app.fedilab.android.mastodon.client.entities.api.Results;
import app.fedilab.android.mastodon.client.entities.api.Status; import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.app.Account; import app.fedilab.android.mastodon.client.entities.app.Account;
import app.fedilab.android.mastodon.client.entities.app.BaseAccount; import app.fedilab.android.mastodon.client.entities.app.BaseAccount;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.exception.DBException; import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.ui.drawer.AccountsSearchAdapter; import app.fedilab.android.mastodon.ui.drawer.AccountsSearchAdapter;
import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM; import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM;
@ -71,10 +73,30 @@ public class CrossActionHelper {
final SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); final SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
new Thread(() -> { new Thread(() -> {
try { try {
boolean confirmFav = sharedpreferences.getBoolean(context.getString(R.string.SET_NOTIF_VALIDATION_FAV), false);
boolean confirmBoost = sharedpreferences.getBoolean(context.getString(R.string.SET_NOTIF_VALIDATION), true);
List<BaseAccount> accounts = new Account(context).getCrossAccounts(); List<BaseAccount> accounts = new Account(context).getCrossAccounts();
if (accounts.size() == 1) { if (accounts.size() == 1) {
Handler mainHandler = new Handler(Looper.getMainLooper()); Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> fetchRemote(context, actionType, accounts.get(0), targetedAccount, targetedStatus); Runnable myRunnable = () -> {
if ((actionType == TypeOfCrossAction.REBLOG_ACTION && confirmBoost) || (actionType == TypeOfCrossAction.FAVOURITE_ACTION && confirmFav)) {
AlertDialog.Builder alt_bld = new MaterialAlertDialogBuilder(context);
if (actionType == TypeOfCrossAction.REBLOG_ACTION) {
alt_bld.setMessage(context.getString(R.string.reblog_add));
} else {
alt_bld.setMessage(context.getString(R.string.favourite_add));
}
alt_bld.setPositiveButton(R.string.yes, (dia, id) -> {
fetchRemote(context, actionType, accounts.get(0), targetedAccount, targetedStatus);
dia.dismiss();
});
alt_bld.setNegativeButton(R.string.cancel, (dia, id) -> dia.dismiss());
AlertDialog alert = alt_bld.create();
alert.show();
} else {
fetchRemote(context, actionType, accounts.get(0), targetedAccount, targetedStatus);
}
};
mainHandler.post(myRunnable); mainHandler.post(myRunnable);
} else { } else {
List<app.fedilab.android.mastodon.client.entities.api.Account> accountList = new ArrayList<>(); List<app.fedilab.android.mastodon.client.entities.api.Account> accountList = new ArrayList<>();
@ -95,8 +117,7 @@ public class CrossActionHelper {
} }
builderSingle.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); builderSingle.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
builderSingle.setAdapter(accountsSearchAdapter, (dialog, which) -> { builderSingle.setAdapter(accountsSearchAdapter, (dialog, which) -> {
boolean confirmFav = sharedpreferences.getBoolean(context.getString(R.string.SET_NOTIF_VALIDATION_FAV), false);
boolean confirmBoost = sharedpreferences.getBoolean(context.getString(R.string.SET_NOTIF_VALIDATION), true);
BaseAccount selectedAccount = accountArray[which]; BaseAccount selectedAccount = accountArray[which];
if ((actionType == TypeOfCrossAction.REBLOG_ACTION && confirmBoost) || (actionType == TypeOfCrossAction.FAVOURITE_ACTION && confirmFav)) { if ((actionType == TypeOfCrossAction.REBLOG_ACTION && confirmBoost) || (actionType == TypeOfCrossAction.FAVOURITE_ACTION && confirmFav)) {
AlertDialog.Builder alt_bld = new MaterialAlertDialogBuilder(context); AlertDialog.Builder alt_bld = new MaterialAlertDialogBuilder(context);
@ -107,9 +128,9 @@ public class CrossActionHelper {
} }
alt_bld.setPositiveButton(R.string.yes, (dia, id) -> { alt_bld.setPositiveButton(R.string.yes, (dia, id) -> {
fetchRemote(context, actionType, selectedAccount, targetedAccount, targetedStatus); fetchRemote(context, actionType, selectedAccount, targetedAccount, targetedStatus);
dialog.dismiss(); dia.dismiss();
}); });
alt_bld.setNegativeButton(R.string.cancel, (dia, id) -> dialog.dismiss()); alt_bld.setNegativeButton(R.string.cancel, (dia, id) -> dia.dismiss());
AlertDialog alert = alt_bld.create(); AlertDialog alert = alt_bld.create();
alert.show(); alert.show();
} else { } else {
@ -146,7 +167,7 @@ public class CrossActionHelper {
} }
searchVM.search(ownerAccount.instance, ownerAccount.token, search, null, "accounts", false, true, false, 0, null, null, 1) searchVM.search(ownerAccount.instance, ownerAccount.token, search, null, "accounts", false, true, false, 0, null, null, 1)
.observe((LifecycleOwner) context, results -> { .observe((LifecycleOwner) context, results -> {
if (results.accounts != null && results.accounts.size() > 0) { if (results != null && results.accounts != null && !results.accounts.isEmpty()) {
app.fedilab.android.mastodon.client.entities.api.Account account = results.accounts.get(0); app.fedilab.android.mastodon.client.entities.api.Account account = results.accounts.get(0);
applyAction(context, actionType, ownerAccount, account, null); applyAction(context, actionType, ownerAccount, account, null);
} else { } else {
@ -156,7 +177,7 @@ public class CrossActionHelper {
} else if (targetedStatus != null) { } else if (targetedStatus != null) {
searchVM.search(ownerAccount.instance, ownerAccount.token, targetedStatus.uri, null, "statuses", false, true, false, 0, null, null, 1) searchVM.search(ownerAccount.instance, ownerAccount.token, targetedStatus.uri, null, "statuses", false, true, false, 0, null, null, 1)
.observe((LifecycleOwner) context, results -> { .observe((LifecycleOwner) context, results -> {
if (results != null && results.statuses != null && results.statuses.size() > 0) { if (results != null && results.statuses != null && !results.statuses.isEmpty()) {
Status status = results.statuses.get(0); Status status = results.statuses.get(0);
applyAction(context, actionType, ownerAccount, null, status); applyAction(context, actionType, ownerAccount, null, status);
} else { } else {
@ -183,89 +204,97 @@ public class CrossActionHelper {
statusesVM = new ViewModelProvider((ViewModelStoreOwner) context).get("crossactions", StatusesVM.class); statusesVM = new ViewModelProvider((ViewModelStoreOwner) context).get("crossactions", StatusesVM.class);
} }
switch (actionType) { switch (actionType) {
case MUTE_ACTION: case MUTE_ACTION -> {
assert accountsVM != null; assert accountsVM != null;
accountsVM.mute(ownerAccount.instance, ownerAccount.token, targetedAccount.id, true, 0) accountsVM.mute(ownerAccount.instance, ownerAccount.token, targetedAccount.id, true, 0)
.observe((LifecycleOwner) context, relationShip -> Toasty.info(context, context.getString(R.string.toast_mute), Toasty.LENGTH_SHORT).show()); .observe((LifecycleOwner) context, relationShip -> Toasty.info(context, context.getString(R.string.toast_mute), Toasty.LENGTH_SHORT).show());
break; }
case UNMUTE_ACTION: case UNMUTE_ACTION -> {
assert accountsVM != null; assert accountsVM != null;
accountsVM.unmute(ownerAccount.instance, ownerAccount.token, targetedAccount.id) accountsVM.unmute(ownerAccount.instance, ownerAccount.token, targetedAccount.id)
.observe((LifecycleOwner) context, relationShip -> Toasty.info(context, context.getString(R.string.toast_unmute), Toasty.LENGTH_SHORT).show()); .observe((LifecycleOwner) context, relationShip -> Toasty.info(context, context.getString(R.string.toast_unmute), Toasty.LENGTH_SHORT).show());
break; }
case BLOCK_ACTION: case BLOCK_ACTION -> {
assert accountsVM != null; assert accountsVM != null;
accountsVM.block(ownerAccount.instance, ownerAccount.token, targetedAccount.id) accountsVM.block(ownerAccount.instance, ownerAccount.token, targetedAccount.id)
.observe((LifecycleOwner) context, relationShip -> Toasty.info(context, context.getString(R.string.toast_block), Toasty.LENGTH_SHORT).show()); .observe((LifecycleOwner) context, relationShip -> Toasty.info(context, context.getString(R.string.toast_block), Toasty.LENGTH_SHORT).show());
break; }
case UNBLOCK_ACTION: case UNBLOCK_ACTION -> {
assert accountsVM != null; assert accountsVM != null;
accountsVM.unblock(ownerAccount.instance, ownerAccount.token, targetedAccount.id) accountsVM.unblock(ownerAccount.instance, ownerAccount.token, targetedAccount.id)
.observe((LifecycleOwner) context, relationShip -> Toasty.info(context, context.getString(R.string.toast_unblock), Toasty.LENGTH_SHORT).show()); .observe((LifecycleOwner) context, relationShip -> Toasty.info(context, context.getString(R.string.toast_unblock), Toasty.LENGTH_SHORT).show());
break; }
case FOLLOW_ACTION: case FOLLOW_ACTION -> {
assert accountsVM != null; assert accountsVM != null;
accountsVM.follow(ownerAccount.instance, ownerAccount.token, targetedAccount.id, true, false, null) accountsVM.follow(ownerAccount.instance, ownerAccount.token, targetedAccount.id, true, false, null)
.observe((LifecycleOwner) context, relationShip -> Toasty.info(context, context.getString(R.string.toast_follow), Toasty.LENGTH_SHORT).show()); .observe((LifecycleOwner) context, relationShip -> Toasty.info(context, context.getString(R.string.toast_follow), Toasty.LENGTH_SHORT).show());
break; }
case UNFOLLOW_ACTION: case UNFOLLOW_ACTION -> {
assert accountsVM != null; assert accountsVM != null;
accountsVM.unfollow(ownerAccount.instance, ownerAccount.token, targetedAccount.id) accountsVM.unfollow(ownerAccount.instance, ownerAccount.token, targetedAccount.id)
.observe((LifecycleOwner) context, relationShip -> Toasty.info(context, context.getString(R.string.toast_unfollow), Toasty.LENGTH_SHORT).show()); .observe((LifecycleOwner) context, relationShip -> Toasty.info(context, context.getString(R.string.toast_unfollow), Toasty.LENGTH_SHORT).show());
break; }
case FAVOURITE_ACTION: case FAVOURITE_ACTION -> {
assert statusesVM != null; assert statusesVM != null;
statusesVM.favourite(ownerAccount.instance, ownerAccount.token, targetedStatus.id) statusesVM.favourite(ownerAccount.instance, ownerAccount.token, targetedStatus.id)
.observe((LifecycleOwner) context, status -> Toasty.info(context, context.getString(R.string.toast_favourite), Toasty.LENGTH_SHORT).show()); .observe((LifecycleOwner) context, status -> Toasty.info(context, context.getString(R.string.toast_favourite), Toasty.LENGTH_SHORT).show());
break; }
case UNFAVOURITE_ACTION: case UNFAVOURITE_ACTION -> {
assert statusesVM != null; assert statusesVM != null;
statusesVM.unFavourite(ownerAccount.instance, ownerAccount.token, targetedStatus.id) statusesVM.unFavourite(ownerAccount.instance, ownerAccount.token, targetedStatus.id)
.observe((LifecycleOwner) context, status -> Toasty.info(context, context.getString(R.string.toast_unfavourite), Toasty.LENGTH_SHORT).show()); .observe((LifecycleOwner) context, status -> Toasty.info(context, context.getString(R.string.toast_unfavourite), Toasty.LENGTH_SHORT).show());
break; }
case BOOKMARK_ACTION: case BOOKMARK_ACTION -> {
assert statusesVM != null; assert statusesVM != null;
statusesVM.bookmark(ownerAccount.instance, ownerAccount.token, targetedStatus.id) statusesVM.bookmark(ownerAccount.instance, ownerAccount.token, targetedStatus.id)
.observe((LifecycleOwner) context, status -> Toasty.info(context, context.getString(R.string.toast_bookmark), Toasty.LENGTH_SHORT).show()); .observe((LifecycleOwner) context, status -> Toasty.info(context, context.getString(R.string.toast_bookmark), Toasty.LENGTH_SHORT).show());
break; }
case UNBOOKMARK_ACTION: case UNBOOKMARK_ACTION -> {
assert statusesVM != null; assert statusesVM != null;
statusesVM.unBookmark(ownerAccount.instance, ownerAccount.token, targetedStatus.id) statusesVM.unBookmark(ownerAccount.instance, ownerAccount.token, targetedStatus.id)
.observe((LifecycleOwner) context, status -> Toasty.info(context, context.getString(R.string.toast_unbookmark), Toasty.LENGTH_SHORT).show()); .observe((LifecycleOwner) context, status -> Toasty.info(context, context.getString(R.string.toast_unbookmark), Toasty.LENGTH_SHORT).show());
break; }
case REBLOG_ACTION: case REBLOG_ACTION -> {
assert statusesVM != null; assert statusesVM != null;
statusesVM.reblog(ownerAccount.instance, ownerAccount.token, targetedStatus.id, null) statusesVM.reblog(ownerAccount.instance, ownerAccount.token, targetedStatus.id, null)
.observe((LifecycleOwner) context, status -> Toasty.info(context, context.getString(R.string.toast_reblog), Toasty.LENGTH_SHORT).show()); .observe((LifecycleOwner) context, status -> Toasty.info(context, context.getString(R.string.toast_reblog), Toasty.LENGTH_SHORT).show());
break; }
case UNREBLOG_ACTION: case UNREBLOG_ACTION -> {
assert statusesVM != null; assert statusesVM != null;
statusesVM.unReblog(ownerAccount.instance, ownerAccount.token, targetedStatus.id) statusesVM.unReblog(ownerAccount.instance, ownerAccount.token, targetedStatus.id)
.observe((LifecycleOwner) context, status -> Toasty.info(context, context.getString(R.string.toast_unreblog), Toasty.LENGTH_SHORT).show()); .observe((LifecycleOwner) context, status -> Toasty.info(context, context.getString(R.string.toast_unreblog), Toasty.LENGTH_SHORT).show());
break; }
case REPLY_ACTION: case REPLY_ACTION -> {
Intent intent = new Intent(context, ComposeActivity.class); Intent intent = new Intent(context, ComposeActivity.class);
intent.putExtra(Helper.ARG_STATUS_REPLY, targetedStatus); Bundle args = new Bundle();
intent.putExtra(Helper.ARG_ACCOUNT, ownerAccount); args.putSerializable(Helper.ARG_STATUS_REPLY, targetedStatus);
context.startActivity(intent); args.putSerializable(Helper.ARG_ACCOUNT, ownerAccount);
break; new CachedBundle(context).insertBundle(args, Helper.getCurrentAccount(context), bundleId -> {
case COMPOSE: Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
context.startActivity(intent);
});
}
case COMPOSE -> {
Intent intentCompose = new Intent(context, ComposeActivity.class); Intent intentCompose = new Intent(context, ComposeActivity.class);
intentCompose.putExtra(Helper.ARG_ACCOUNT, ownerAccount); Bundle args = new Bundle();
context.startActivity(intentCompose); args.putSerializable(Helper.ARG_ACCOUNT, ownerAccount);
break; new CachedBundle(context).insertBundle(args, Helper.getCurrentAccount(context), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentCompose.putExtras(bundle);
context.startActivity(intentCompose);
});
}
} }
} }
private static MastodonSearchService init(Context context, @NonNull String instance) { private static MastodonSearchService init(Context context, String instance) {
final OkHttpClient okHttpClient = new OkHttpClient.Builder() final OkHttpClient okHttpClient = Helper.myOkHttpClient(context);
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.proxy(Helper.getProxy(context))
.build();
Retrofit retrofit = new Retrofit.Builder() Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + instance + "/api/v2/") .baseUrl("https://" + (instance != null ? IDN.toASCII(instance, IDN.ALLOW_UNASSIGNED) : null) + "/api/v2/")
.addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder())) .addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder()))
.client(okHttpClient) .client(okHttpClient)
.build(); .build();
@ -434,10 +463,15 @@ public class CrossActionHelper {
final BaseAccount account = accountArray[which]; final BaseAccount account = accountArray[which];
Intent intentToot = new Intent(context, ComposeActivity.class); Intent intentToot = new Intent(context, ComposeActivity.class);
bundle.putSerializable(Helper.ARG_ACCOUNT, account); bundle.putSerializable(Helper.ARG_ACCOUNT, account);
intentToot.putExtras(bundle); new CachedBundle(context).insertBundle(bundle, Helper.getCurrentAccount(context), bundleId -> {
context.startActivity(intentToot); Bundle bundleCached = new Bundle();
((BaseActivity) context).finish(); bundleCached.putLong(Helper.ARG_INTENT_ID, bundleId);
dialog.dismiss(); intentToot.putExtras(bundleCached);
context.startActivity(intentToot);
((BaseActivity) context).finish();
dialog.dismiss();
});
}); });
builderSingle.show(); builderSingle.show();
} }
@ -508,7 +542,7 @@ public class CrossActionHelper {
} }
} }
} }
} catch (IOException e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} }

View file

@ -30,8 +30,8 @@ import app.fedilab.android.mastodon.client.entities.api.Status;
public class CustomEmoji extends ReplacementSpan { public class CustomEmoji extends ReplacementSpan {
private float scale;
private final WeakReference<View> viewWeakReference; private final WeakReference<View> viewWeakReference;
private float scale;
private Drawable imageDrawable; private Drawable imageDrawable;
private boolean callbackCalled; private boolean callbackCalled;
@ -47,7 +47,7 @@ public class CustomEmoji extends ReplacementSpan {
} }
public SpannableStringBuilder makeEmoji(SpannableStringBuilder content, List<Emoji> emojiList, boolean animate, Status.Callback callback) { public SpannableStringBuilder makeEmoji(SpannableStringBuilder content, List<Emoji> emojiList, boolean animate, Status.Callback callback) {
if (emojiList != null && emojiList.size() > 0) { if (emojiList != null && !emojiList.isEmpty()) {
int count = 1; int count = 1;
for (Emoji emoji : emojiList) { for (Emoji emoji : emojiList) {
Matcher matcher = Pattern.compile(":" + emoji.shortcode + ":", Pattern.LITERAL) Matcher matcher = Pattern.compile(":" + emoji.shortcode + ":", Pattern.LITERAL)
@ -68,13 +68,6 @@ public class CustomEmoji extends ReplacementSpan {
@Override @Override
public int getSize(@NonNull Paint paint, CharSequence charSequence, int i, int i1, @Nullable Paint.FontMetricsInt fontMetricsInt) { public int getSize(@NonNull Paint paint, CharSequence charSequence, int i, int i1, @Nullable Paint.FontMetricsInt fontMetricsInt) {
if (fontMetricsInt != null) {
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
fontMetricsInt.top = (int) fontMetrics.top;
fontMetricsInt.ascent = (int) fontMetrics.ascent;
fontMetricsInt.descent = (int) fontMetrics.descent;
fontMetricsInt.bottom = (int) fontMetrics.bottom;
}
return (int) (paint.getTextSize() * scale); return (int) (paint.getTextSize() * scale);
} }
@ -85,7 +78,7 @@ public class CustomEmoji extends ReplacementSpan {
int emojiSize = (int) (paint.getTextSize() * scale); int emojiSize = (int) (paint.getTextSize() * scale);
imageDrawable.setBounds(0, 0, emojiSize, emojiSize); imageDrawable.setBounds(0, 0, emojiSize, emojiSize);
int transY = bottom - imageDrawable.getBounds().bottom; int transY = bottom - imageDrawable.getBounds().bottom;
transY -= paint.getFontMetrics().descent / 2; transY -= (int) (paint.getFontMetrics().descent / 2);
canvas.translate(x, (float) transY); canvas.translate(x, (float) transY);
imageDrawable.draw(canvas); imageDrawable.draw(canvas);
canvas.restore(); canvas.restore();
@ -93,40 +86,50 @@ public class CustomEmoji extends ReplacementSpan {
} }
public Target<Drawable> getTarget(boolean animate, Status.Callback callback) { public Target<Drawable> getTarget(boolean animate, Status.Callback callback) {
return new CustomTarget<Drawable>() { return new CustomTarget<>() {
@Override
public void onStart() {
if (imageDrawable instanceof Animatable) {
((Animatable) imageDrawable).start();
}
}
@Override
public void onStop() {
if (imageDrawable instanceof Animatable) {
((Animatable) imageDrawable).stop();
}
}
@Override @Override
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) { public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
View view = viewWeakReference.get(); View view = viewWeakReference.get();
if (animate && resource instanceof Animatable) { if (animate && resource instanceof Animatable) {
Drawable.Callback drawableCallBack = resource.getCallback();
resource.setCallback(new Drawable.Callback() { resource.setCallback(new Drawable.Callback() {
@Override @Override
public void invalidateDrawable(@NonNull Drawable drawable) { public void invalidateDrawable(@NonNull Drawable drawable) {
if (drawableCallBack != null) { if(view != null) {
drawableCallBack.invalidateDrawable(drawable); view.invalidate();
} }
view.invalidate();
} }
@Override @Override
public void scheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable, long l) { public void scheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable, long l) {
if (drawableCallBack != null) { view.postDelayed(runnable, l);
drawableCallBack.scheduleDrawable(drawable, runnable, l);
}
} }
@Override @Override
public void unscheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable) { public void unscheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable) {
if (drawableCallBack != null) { view.removeCallbacks(runnable);
drawableCallBack.unscheduleDrawable(drawable, runnable);
}
} }
}); });
((Animatable) resource).start(); ((Animatable) resource).start();
} }
imageDrawable = resource; imageDrawable = resource;
if (view != null) { if(view != null) {
view.invalidate(); view.invalidate();
} }
if (callback != null && !callbackCalled) { if (callback != null && !callbackCalled) {
@ -137,6 +140,17 @@ public class CustomEmoji extends ReplacementSpan {
@Override @Override
public void onLoadCleared(@Nullable Drawable placeholder) { public void onLoadCleared(@Nullable Drawable placeholder) {
View view = viewWeakReference.get();
if (imageDrawable != null) {
if (imageDrawable instanceof Animatable) {
((Animatable) imageDrawable).stop();
imageDrawable.setCallback(null);
}
}
imageDrawable = null;
if(view != null) {
view.invalidate();
}
} }
}; };
} }

View file

@ -69,7 +69,6 @@ public class DividerDecoration extends RecyclerView.ItemDecoration {
StatusAdapter statusAdapter = ((StatusAdapter) parent.getAdapter()); StatusAdapter statusAdapter = ((StatusAdapter) parent.getAdapter());
if (statusAdapter != null && statusAdapter.getItemCount() > position && position > 0) { if (statusAdapter != null && statusAdapter.getItemCount() > position && position > 0) {
Status status = statusAdapter.getItem(position); Status status = statusAdapter.getItem(position);
int start = (int) Helper.convertDpToPixel( int start = (int) Helper.convertDpToPixel(
6 * fontScale * CommentDecorationHelper.getIndentation(status.in_reply_to_id, statusList, indentationMax), 6 * fontScale * CommentDecorationHelper.getIndentation(status.in_reply_to_id, statusList, indentationMax),
_mContext); _mContext);
@ -95,7 +94,6 @@ public class DividerDecoration extends RecyclerView.ItemDecoration {
int indentation = Math.min( int indentation = Math.min(
CommentDecorationHelper.getIndentation(status.in_reply_to_id, statusList, indentationMax), CommentDecorationHelper.getIndentation(status.in_reply_to_id, statusList, indentationMax),
indentationMax); indentationMax);
if (indentation > 0) { if (indentation > 0) {
Paint paint = new Paint(); Paint paint = new Paint();
paint.setDither(false); paint.setDither(false);
@ -109,12 +107,7 @@ public class DividerDecoration extends RecyclerView.ItemDecoration {
startPx = c.getWidth() - startPx; startPx = c.getWidth() - startPx;
float bottomPx = view.getBottom(); float bottomPx = view.getBottom();
int color; int color = colorList.get(j%(colorList.size()-1));
if (j >= colorList.size()) {
color = colorList.get(j - colorList.size());
} else {
color = colorList.get(j);
}
paint.setColor(ResourcesCompat.getColor(_mContext.getResources(), color, _mContext.getTheme())); paint.setColor(ResourcesCompat.getColor(_mContext.getResources(), color, _mContext.getTheme()));
if (j == indentationMax - 1) { if (j == indentationMax - 1) {
paint.setPathEffect(new DashPathEffect( paint.setPathEffect(new DashPathEffect(
@ -125,12 +118,7 @@ public class DividerDecoration extends RecyclerView.ItemDecoration {
c.drawLine(startPx, view.getTop() - margin, startPx, bottomPx, paint); c.drawLine(startPx, view.getTop() - margin, startPx, bottomPx, paint);
} }
int color; int color = colorList.get(indentation%colorList.size()-1);
if (indentation - 1 >= colorList.size()) {
color = colorList.get(indentation - 1 - colorList.size());
} else {
color = colorList.get(indentation - 1);
}
paint.setColor(ResourcesCompat.getColor(_mContext.getResources(), color, _mContext.getTheme())); paint.setColor(ResourcesCompat.getColor(_mContext.getResources(), color, _mContext.getTheme()));
float startDp = 6 * fontScale * (indentation - 1) + 6 * fontScale; float startDp = 6 * fontScale * (indentation - 1) + 6 * fontScale;

View file

@ -127,12 +127,7 @@ public class DividerDecorationSimple extends RecyclerView.ItemDecoration {
c.drawLine(startPx, view.getTop() - margin, startPx, bottomPx, paint); c.drawLine(startPx, view.getTop() - margin, startPx, bottomPx, paint);
} }
int color; int color = colorList.get(indentation%colorList.size()-1);
if (indentation - 1 >= colorList.size()) {
color = colorList.get(indentation - 1 - colorList.size());
} else {
color = colorList.get(indentation - 1);
}
paint.setColor(ResourcesCompat.getColor(_mContext.getResources(), color, _mContext.getTheme())); paint.setColor(ResourcesCompat.getColor(_mContext.getResources(), color, _mContext.getTheme()));
float startDp = 6 * fontScale * (indentation - 1) + 6 * fontScale; float startDp = 6 * fontScale * (indentation - 1) + 6 * fontScale;

View file

@ -1,289 +0,0 @@
package app.fedilab.android.mastodon.helper;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Base64;
import androidx.preference.PreferenceManager;
import org.spongycastle.asn1.ASN1ObjectIdentifier;
import org.spongycastle.asn1.x9.ECNamedCurveTable;
import org.spongycastle.asn1.x9.X9ECParameters;
import org.spongycastle.crypto.params.ECNamedDomainParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.jce.spec.ECNamedCurveSpec;
import org.spongycastle.jce.spec.ECParameterSpec;
import org.spongycastle.jce.spec.ECPrivateKeySpec;
import org.spongycastle.jce.spec.ECPublicKeySpec;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECPoint;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
public class ECDH {
public static final String kp_public = "kp_public";
public static final String peer_public = "peer_public";
public static final String PROVIDER = org.spongycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME;
public static final String kp_private = "kp_private";
public static final String KEGEN_ALG = "ECDH";
public static final String name = "prime256v1";
private static final String kp_public_affine_x = "kp_public_affine_x";
private static final String kp_public_affine_y = "kp_public_affine_y";
private static ECDH instance;
static {
Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider());
}
public final KeyFactory kf;
private final KeyPairGenerator kpg;
private final String slug;
public ECDH(String slug) throws Exception {
if (slug == null) {
throw new Exception("slug cannot be null");
}
try {
kf = KeyFactory.getInstance(KEGEN_ALG, PROVIDER);
kpg = KeyPairGenerator.getInstance(KEGEN_ALG, PROVIDER);
this.slug = slug;
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static synchronized ECDH getInstance(String slug) throws Exception {
if (instance == null) {
instance = new ECDH(slug);
}
return instance;
}
public static String base64Encode(byte[] b) {
return Base64.encodeToString(
b, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
}
static byte[] base64Decode(String str) {
return Base64.decode(str, Base64.URL_SAFE);
}
synchronized KeyPair generateKeyPair()
throws Exception {
ECGenParameterSpec ecParamSpec = new ECGenParameterSpec(name);
kpg.initialize(ecParamSpec);
return kpg.generateKeyPair();
}
private byte[] generateSecret(PrivateKey myPrivKey, PublicKey otherPubKey) throws Exception {
KeyAgreement keyAgreement = KeyAgreement.getInstance(KEGEN_ALG);
keyAgreement.init(myPrivKey);
keyAgreement.doPhase(otherPubKey, true);
return keyAgreement.generateSecret();
}
synchronized KeyPair readKeyPair(Context context)
throws Exception {
return new KeyPair(readMyPublicKey(context), readMyPrivateKey(context));
}
@SuppressLint("ApplySharedPref")
public KeyPair newPair(Context context) {
SharedPreferences.Editor prefsEditor = PreferenceManager
.getDefaultSharedPreferences(context).edit();
KeyPair kp;
try {
kp = generateKeyPair();
} catch (Exception e) {
e.printStackTrace();
return null;
}
ECPublicKey key = (ECPublicKey) kp.getPublic();
byte[] x = key.getW().getAffineX().toByteArray();
byte[] y = key.getW().getAffineY().toByteArray();
BigInteger xbi = new BigInteger(1, x);
BigInteger ybi = new BigInteger(1, y);
X9ECParameters x9 = ECNamedCurveTable.getByName(name);
ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID(name);
ECCurve curve = x9.getCurve();
ECPoint point = curve.createPoint(xbi, ybi);
ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid,
x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed());
ECPublicKeyParameters pubKey = new ECPublicKeyParameters(point, dParams);
ECPrivateKeyParameters privateKey = new ECPrivateKeyParameters(new BigInteger(kp.getPrivate().getEncoded()), pubKey.getParameters());
byte[] privateKeyBytes = privateKey.getD().toByteArray();
String keyString = base64Encode(pubKey.getQ().getEncoded(false));
String keypString = base64Encode(privateKeyBytes);
prefsEditor.putString(kp_public + slug, keyString);
prefsEditor.putString(kp_public_affine_x + slug, key.getW().getAffineX().toString());
prefsEditor.putString(kp_public_affine_y + slug, key.getW().getAffineY().toString());
prefsEditor.putString(kp_private + slug, keypString);
prefsEditor.commit();
return kp;
}
synchronized PublicKey readMyPublicKey(Context context) throws Exception {
X9ECParameters x9 = ECNamedCurveTable.getByName(name);
ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID(name);
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
BigInteger xbi = new BigInteger(prefs.getString(kp_public_affine_x + slug, "0"));
BigInteger ybi = new BigInteger(prefs.getString(kp_public_affine_y + slug, "0"));
ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid,
x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed());
ECNamedCurveSpec ecNamedCurveSpec = new ECNamedCurveSpec(name, dParams.getCurve(), dParams.getG(), dParams.getN());
java.security.spec.ECPoint w = new java.security.spec.ECPoint(xbi, ybi);
return kf.generatePublic(new java.security.spec.ECPublicKeySpec(w, ecNamedCurveSpec));
}
public String uncryptMessage(Context context, String cyphered) {
byte[] privateKey = getSharedSecret(context);
try {
Cipher outCipher = Cipher.getInstance("ECIES", PROVIDER);
PrivateKey ddd = readPrivateKey(privateKey);
outCipher.init(Cipher.DECRYPT_MODE, readPrivateKey(privateKey));
byte[] plaintext = outCipher.doFinal(base64Decode(cyphered));
return new String(plaintext);
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
public PublicKey readPublicKey(String keyStr) throws Exception {
ECParameterSpec parameterSpec = org.spongycastle.jce.ECNamedCurveTable.getParameterSpec(name);
ECCurve curve = parameterSpec.getCurve();
ECPoint point = curve.decodePoint(base64Decode(keyStr));
ECPublicKeySpec pubSpec = new ECPublicKeySpec(point, parameterSpec);
return kf.generatePublic(pubSpec);
}
public PrivateKey readPrivateKey(byte[] key) throws Exception {
ECParameterSpec parameterSpec = org.spongycastle.jce.ECNamedCurveTable.getParameterSpec(name);
ECPrivateKeySpec pubSpec = new ECPrivateKeySpec(new BigInteger(1, key), parameterSpec);
return kf.generatePrivate(pubSpec);
}
synchronized PrivateKey readMyPrivateKey(Context context) throws Exception {
X9ECParameters x9 = ECNamedCurveTable.getByName(name);
ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID(name);
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
BigInteger ybi = new BigInteger(prefs.getString(kp_public_affine_y + slug, "0"));
ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid,
x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed());
ECNamedCurveSpec ecNamedCurveSpec = new ECNamedCurveSpec(name, dParams.getCurve(), dParams.getG(), dParams.getN());
return kf.generatePrivate(new java.security.spec.ECPrivateKeySpec(ybi, ecNamedCurveSpec));
}
private synchronized KeyPair getPair(Context context) {
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
String strPub = prefs.getString(kp_public + slug, "");
String strPriv = prefs.getString(kp_private + slug, "");
if (strPub.trim().isEmpty() || strPriv.trim().isEmpty()) {
return newPair(context);
}
try {
return readKeyPair(context);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
PublicKey getServerKey(Context context) throws Exception {
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
String serverKey = prefs.getString(peer_public + slug, "");
return readPublicKey(serverKey);
}
@SuppressWarnings({"unused", "RedundantSuppression"})
public byte[] getSharedSecret(Context context) {
try {
KeyPair keyPair = getPair(context);
if (keyPair != null) {
return generateSecret(keyPair.getPrivate(), getServerKey(context));
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return null;
}
public String getPublicKey(Context context) {
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
return prefs.getString(kp_public + slug, "");
}
@SuppressLint("ApplySharedPref")
public void saveServerKey(Context context, String strPeerPublic) {
SharedPreferences.Editor prefsEditor = PreferenceManager
.getDefaultSharedPreferences(context).edit();
prefsEditor.putString(peer_public + slug, strPeerPublic);
prefsEditor.commit();
}
}

View file

@ -1,237 +0,0 @@
package app.fedilab.android.mastodon.helper;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.mastodon.client.entities.app.StatusCache.restoreNotificationFromString;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Base64;
import androidx.preference.PreferenceManager;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyAgreement;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import app.fedilab.android.mastodon.client.entities.api.Notification;
public class ECDHFedilab {
public static final String kp_public = "kp_public";
public static final String peer_public = "peer_public";
public static final String name = "prime256v1";
private static final byte[] P256_HEAD = new byte[]{(byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a,
(byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06, (byte) 0x08, (byte) 0x2a, (byte) 0x86,
(byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x03, (byte) 0x01, (byte) 0x07, (byte) 0x03, (byte) 0x42, (byte) 0x00};
static {
Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider());
}
private final KeyPairGenerator kpg;
private final PublicKey publicKey;
private final String encodedPublicKey;
private final byte[] authKey;
private final String slug;
private final String pushPublicKey;
private final String encodedAuthKey;
private final String pushAccountID;
private final String pushPrivateKey;
PrivateKey privateKey;
private String pushPrivateKe;
public ECDHFedilab(Context context, String slug) throws Exception {
if (slug == null) {
throw new Exception("slug cannot be null");
}
try {
kpg = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec spec = new ECGenParameterSpec("prime256v1");
kpg.initialize(spec);
KeyPair keyPair = kpg.generateKeyPair();
publicKey = keyPair.getPublic();
privateKey = keyPair.getPrivate();
encodedPublicKey = Base64.encodeToString(serializeRawPublicKey(publicKey), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
authKey = new byte[16];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(authKey);
byte[] randomAccountID = new byte[16];
secureRandom.nextBytes(randomAccountID);
pushPrivateKey = Base64.encodeToString(privateKey.getEncoded(), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
pushPublicKey = Base64.encodeToString(publicKey.getEncoded(), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
encodedAuthKey = Base64.encodeToString(authKey, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
pushAccountID = Base64.encodeToString(randomAccountID, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
SharedPreferences.Editor prefsEditor = PreferenceManager
.getDefaultSharedPreferences(context).edit();
prefsEditor.putString("pushPrivateKey" + slug, pushPrivateKey);
prefsEditor.putString("pushPublicKey" + slug, pushPublicKey);
prefsEditor.putString("encodedAuthKey" + slug, encodedAuthKey);
prefsEditor.putString("pushAccountID" + slug, pushAccountID);
prefsEditor.apply();
this.slug = slug;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static String getServerKey(Context context, String slug) {
SharedPreferences sharedPreferences = PreferenceManager
.getDefaultSharedPreferences(context);
return sharedPreferences.getString("server_key" + slug, null);
}
private static byte[] serializeRawPublicKey(PublicKey key) {
ECPoint point = ((ECPublicKey) key).getW();
byte[] x = point.getAffineX().toByteArray();
byte[] y = point.getAffineY().toByteArray();
if (x.length > 32)
x = Arrays.copyOfRange(x, x.length - 32, x.length);
if (y.length > 32)
y = Arrays.copyOfRange(y, y.length - 32, y.length);
byte[] result = new byte[65];
result[0] = 4;
System.arraycopy(x, 0, result, 1 + (32 - x.length), x.length);
System.arraycopy(y, 0, result, result.length - y.length, y.length);
return result;
}
public static Notification decryptNotification(Context context, String slug, byte[] messageEncrypted) {
SharedPreferences sharedPreferences = PreferenceManager
.getDefaultSharedPreferences(context);
String pushPrivateKey = sharedPreferences.getString("pushPrivateKey" + slug, null);
String pushPublicKey = sharedPreferences.getString("pushPublicKey" + slug, null);
String encodedAuthKey = sharedPreferences.getString("encodedAuthKey" + slug, null);
sharedPreferences.getString("pushAccountID" + slug, null);
PublicKey serverKey = null;
serverKey = deserializeRawPublicKey(Base64.decode(getServerKey(context, slug), Base64.URL_SAFE));
PrivateKey privateKey;
PublicKey publicKey;
byte[] authKey;
try {
KeyFactory kf = KeyFactory.getInstance("EC");
privateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.decode(pushPrivateKey, Base64.URL_SAFE)));
publicKey = kf.generatePublic(new X509EncodedKeySpec(Base64.decode(pushPublicKey, Base64.URL_SAFE)));
authKey = Base64.decode(encodedAuthKey, Base64.URL_SAFE);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
e.printStackTrace();
return null;
}
byte[] sharedSecret;
try {
KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH");
keyAgreement.init(privateKey);
keyAgreement.doPhase(serverKey, true);
sharedSecret = keyAgreement.generateSecret();
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
e.printStackTrace();
return null;
}
byte[] secondSaltInfo = "Content-Encoding: auth\0".getBytes(StandardCharsets.UTF_8);
byte[] deriveKey;
try {
deriveKey = deriveKey(authKey, sharedSecret, secondSaltInfo, 32);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
e.printStackTrace();
return null;
}
String decryptedStr;
try {
SecretKeySpec aesKey = new SecretKeySpec(deriveKey, "AES");
byte[] iv = Arrays.copyOfRange(messageEncrypted, 0, 12);
byte[] ciphertext = Arrays.copyOfRange(messageEncrypted, 12, messageEncrypted.length); // Separate ciphertext (the MAC is implicitly separated from the ciphertext)
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec gCMParameterSpec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, aesKey, gCMParameterSpec);
byte[] decrypted = cipher.doFinal(ciphertext);
decryptedStr = new String(decrypted, 2, decrypted.length - 2, StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException | InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
e.printStackTrace();
return null;
}
return restoreNotificationFromString(decryptedStr);
}
protected static PublicKey deserializeRawPublicKey(byte[] rawBytes) {
if (rawBytes.length != 65 && rawBytes.length != 64)
return null;
try {
KeyFactory kf = KeyFactory.getInstance("EC");
ByteArrayOutputStream os = new ByteArrayOutputStream();
os.write(P256_HEAD);
if (rawBytes.length == 64)
os.write(4);
os.write(rawBytes);
return kf.generatePublic(new X509EncodedKeySpec(os.toByteArray()));
} catch (NoSuchAlgorithmException | InvalidKeySpecException | IOException e) {
e.printStackTrace();
}
return null;
}
private static byte[] deriveKey(byte[] firstSalt, byte[] secondSalt, byte[] info, int length) throws NoSuchAlgorithmException, InvalidKeyException {
Mac hmacContext = Mac.getInstance("HmacSHA256");
hmacContext.init(new SecretKeySpec(firstSalt, "HmacSHA256"));
byte[] hmac = hmacContext.doFinal(secondSalt);
hmacContext.init(new SecretKeySpec(hmac, "HmacSHA256"));
hmacContext.update(info);
byte[] result = hmacContext.doFinal(new byte[]{1});
return result.length <= length ? result : Arrays.copyOfRange(result, 0, length);
}
public String getPublicKey() {
return this.encodedPublicKey;
}
public String getAuthKey() {
return this.encodedAuthKey;
}
}

View file

@ -15,7 +15,7 @@ package app.fedilab.android.mastodon.helper;
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static android.content.Context.DOWNLOAD_SERVICE; import static android.content.Context.DOWNLOAD_SERVICE;
import static app.fedilab.android.BaseMainActivity.currentAccount; import static app.fedilab.android.BaseMainActivity.networkAvailable;
import static app.fedilab.android.mastodon.activities.BaseActivity.currentThemeId; import static app.fedilab.android.mastodon.activities.BaseActivity.currentThemeId;
import static app.fedilab.android.mastodon.helper.LogoHelper.getNotificationIcon; import static app.fedilab.android.mastodon.helper.LogoHelper.getNotificationIcon;
import static app.fedilab.android.mastodon.helper.ThemeHelper.fetchAccentColor; import static app.fedilab.android.mastodon.helper.ThemeHelper.fetchAccentColor;
@ -34,6 +34,7 @@ import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.content.res.ColorStateList;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.database.Cursor; import android.database.Cursor;
@ -58,6 +59,7 @@ import android.os.Looper;
import android.os.Parcelable; import android.os.Parcelable;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.provider.OpenableColumns; import android.provider.OpenableColumns;
import android.text.Html;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.TypedValue; import android.util.TypedValue;
@ -78,7 +80,6 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.browser.customtabs.CustomTabColorSchemeParams; import androidx.browser.customtabs.CustomTabColorSchemeParams;
import androidx.browser.customtabs.CustomTabsIntent; import androidx.browser.customtabs.CustomTabsIntent;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat; import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
@ -89,7 +90,6 @@ import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner; import androidx.lifecycle.ViewModelStoreOwner;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
@ -100,6 +100,7 @@ import com.bumptech.glide.load.resource.bitmap.CenterCrop;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners; import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.load.resource.gif.GifDrawable; import com.bumptech.glide.load.resource.gif.GifDrawable;
import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.RequestOptions;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
@ -140,6 +141,7 @@ import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -156,6 +158,7 @@ import app.fedilab.android.mastodon.client.entities.api.Attachment;
import app.fedilab.android.mastodon.client.entities.api.Status; import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.app.Account; import app.fedilab.android.mastodon.client.entities.app.Account;
import app.fedilab.android.mastodon.client.entities.app.BaseAccount; import app.fedilab.android.mastodon.client.entities.app.BaseAccount;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.client.entities.app.ReleaseNote; import app.fedilab.android.mastodon.client.entities.app.ReleaseNote;
import app.fedilab.android.mastodon.client.entities.app.Timeline; import app.fedilab.android.mastodon.client.entities.app.Timeline;
import app.fedilab.android.mastodon.exception.DBException; import app.fedilab.android.mastodon.exception.DBException;
@ -165,9 +168,12 @@ import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.mastodon.viewmodel.mastodon.OauthVM; import app.fedilab.android.mastodon.viewmodel.mastodon.OauthVM;
import app.fedilab.android.mastodon.watermark.androidwm.WatermarkBuilder; import app.fedilab.android.mastodon.watermark.androidwm.WatermarkBuilder;
import app.fedilab.android.mastodon.watermark.androidwm.bean.WatermarkText; import app.fedilab.android.mastodon.watermark.androidwm.bean.WatermarkText;
import app.fedilab.android.peertube.client.data.AccountData;
import es.dmoral.toasty.Toasty; import es.dmoral.toasty.Toasty;
import okhttp3.MediaType; import okhttp3.MediaType;
import okhttp3.MultipartBody; import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody; import okhttp3.RequestBody;
public class Helper { public class Helper {
@ -194,20 +200,30 @@ public class Helper {
public static final String RECEIVE_REDRAW_BOTTOM = "RECEIVE_REDRAW_BOTTOM"; public static final String RECEIVE_REDRAW_BOTTOM = "RECEIVE_REDRAW_BOTTOM";
public static final String RECEIVE_STATUS_ACTION = "RECEIVE_STATUS_ACTION"; public static final String RECEIVE_STATUS_ACTION = "RECEIVE_STATUS_ACTION";
public static final String RECEIVE_REFRESH_NOTIFICATIONS_ACTION = "RECEIVE_REFRESH_NOTIFICATIONS_ACTION";
public static final String RECEIVE_ERROR_MESSAGE = "RECEIVE_ERROR_MESSAGE"; public static final String RECEIVE_ERROR_MESSAGE = "RECEIVE_ERROR_MESSAGE";
public static final String RECEIVE_RECREATE_ACTIVITY = "RECEIVE_RECREATE_ACTIVITY"; public static final String RECEIVE_RECREATE_ACTIVITY = "RECEIVE_RECREATE_ACTIVITY";
public static final String RECEIVE_RECREATE_PEERTUBE_ACTIVITY = "RECEIVE_RECREATE_PEERTUBE_ACTIVITY";
public static final String RECEIVE_NEW_MESSAGE = "RECEIVE_NEW_MESSAGE"; public static final String RECEIVE_NEW_MESSAGE = "RECEIVE_NEW_MESSAGE";
public static final String RECEIVE_COMPOSE_ERROR_MESSAGE = "RECEIVE_COMPOSE_ERROR_MESSAGE"; public static final String RECEIVE_COMPOSE_ERROR_MESSAGE = "RECEIVE_COMPOSE_ERROR_MESSAGE";
public static final String RECEIVE_MASTODON_LIST = "RECEIVE_MASTODON_LIST"; public static final String RECEIVE_MASTODON_LIST = "RECEIVE_MASTODON_LIST";
public static final String RECEIVE_REDRAW_PROFILE = "RECEIVE_REDRAW_PROFILE"; public static final String RECEIVE_REDRAW_PROFILE = "RECEIVE_REDRAW_PROFILE";
public static final String ARG_TIMELINE_TYPE = "ARG_TIMELINE_TYPE"; public static final String ARG_TIMELINE_TYPE = "ARG_TIMELINE_TYPE";
public static final String ARG_INTENT_ID = "ARG_INTENT_ID";
public static final String ARG_PEERTUBE_NAV_REMOTE = "ARG_PEERTUBE_NAV_REMOTE";
public static final String ARG_REMOTE_INSTANCE_STRING = "ARG_REMOTE_INSTANCE_STRING"; public static final String ARG_REMOTE_INSTANCE_STRING = "ARG_REMOTE_INSTANCE_STRING";
public static final String ARG_NOTIFICATION_TYPE = "ARG_NOTIFICATION_TYPE"; public static final String ARG_NOTIFICATION_TYPE = "ARG_NOTIFICATION_TYPE";
public static final String ARG_EXCLUDED_NOTIFICATION_TYPE = "ARG_EXCLUDED_NOTIFICATION_TYPE"; public static final String ARG_EXCLUDED_NOTIFICATION_TYPE = "ARG_EXCLUDED_NOTIFICATION_TYPE";
public static final String ARG_STATUS = "ARG_STATUS"; public static final String ARG_STATUS = "ARG_STATUS";
public static final String ARG_QR_CODE_URL = "ARG_QR_CODE_URL";
public static final String ARG_FOCUSED_STATUS_URI = "ARG_FOCUSED_STATUS_URI";
public static final String ARG_TIMELINE_REFRESH_ALL = "ARG_TIMELINE_REFRESH_ALL"; public static final String ARG_TIMELINE_REFRESH_ALL = "ARG_TIMELINE_REFRESH_ALL";
public static final String ARG_REFRESH_NOTFICATION = "ARG_REFRESH_NOTFICATION"; public static final String ARG_REFRESH_NOTFICATION = "ARG_REFRESH_NOTFICATION";
public static final String ARG_STATUS_DELETED = "ARG_STATUS_DELETED"; public static final String ARG_STATUS_DELETED = "ARG_STATUS_DELETED";
@ -231,6 +247,8 @@ public class Helper {
public static final String ARG_STATUS_REPLY_ID = "ARG_STATUS_REPLY_ID"; public static final String ARG_STATUS_REPLY_ID = "ARG_STATUS_REPLY_ID";
public static final String ARG_ACCOUNT = "ARG_ACCOUNT"; public static final String ARG_ACCOUNT = "ARG_ACCOUNT";
public static final String ARG_ACCOUNT_ID = "ARG_ACCOUNT_ID"; public static final String ARG_ACCOUNT_ID = "ARG_ACCOUNT_ID";
public static final String ARG_CACHED_ACCOUNT_ID = "ARG_CACHED_ACCOUNT_ID";
public static final String ARG_CACHED_STATUS_ID = "ARG_CACHED_STATUS_ID";
public static final String ARG_ADMIN_DOMAINBLOCK = "ARG_ADMIN_DOMAINBLOCK"; public static final String ARG_ADMIN_DOMAINBLOCK = "ARG_ADMIN_DOMAINBLOCK";
public static final String ARG_ADMIN_DOMAINBLOCK_DELETE = "ARG_ADMIN_DOMAINBLOCK_DELETE"; public static final String ARG_ADMIN_DOMAINBLOCK_DELETE = "ARG_ADMIN_DOMAINBLOCK_DELETE";
public static final String FEDILAB_MUTED_HASHTAGS = "Fedilab muted hashtags"; public static final String FEDILAB_MUTED_HASHTAGS = "Fedilab muted hashtags";
@ -253,6 +271,8 @@ public class Helper {
public static final String ARG_STATUS_ID = "ARG_STATUS_ID"; public static final String ARG_STATUS_ID = "ARG_STATUS_ID";
public static final String ARG_WORK_ID = "ARG_WORK_ID"; public static final String ARG_WORK_ID = "ARG_WORK_ID";
public static final String ARG_LIST_ID = "ARG_LIST_ID"; public static final String ARG_LIST_ID = "ARG_LIST_ID";
public static final String ARG_LEMMY_POST_ID = "ARG_LEMMY_POST_ID";
public static final String ARG_SEARCH_KEYWORD = "ARG_SEARCH_KEYWORD"; public static final String ARG_SEARCH_KEYWORD = "ARG_SEARCH_KEYWORD";
public static final String ARG_DIRECTORY_ORDER = "ARG_DIRECTORY_ORDER"; public static final String ARG_DIRECTORY_ORDER = "ARG_DIRECTORY_ORDER";
public static final String ARG_DIRECTORY_LOCAL = "ARG_DIRECTORY_LOCAL"; public static final String ARG_DIRECTORY_LOCAL = "ARG_DIRECTORY_LOCAL";
@ -268,6 +288,7 @@ public class Helper {
public static final String ARG_SHOW_REBLOGS = "ARG_SHOW_REBLOGS"; public static final String ARG_SHOW_REBLOGS = "ARG_SHOW_REBLOGS";
public static final String ARG_INITIALIZE_VIEW = "ARG_INITIALIZE_VIEW"; public static final String ARG_INITIALIZE_VIEW = "ARG_INITIALIZE_VIEW";
public static final String ARG_SHOW_PINNED = "ARG_SHOW_PINNED"; public static final String ARG_SHOW_PINNED = "ARG_SHOW_PINNED";
public static final String ARG_TAGGED = "ARG_TAGGED";
public static final String ARG_SHOW_MEDIA_ONY = "ARG_SHOW_MEDIA_ONY"; public static final String ARG_SHOW_MEDIA_ONY = "ARG_SHOW_MEDIA_ONY";
public static final String ARG_MENTION = "ARG_MENTION"; public static final String ARG_MENTION = "ARG_MENTION";
public static final String ARG_CHECK_REMOTELY = "ARG_CHECK_REMOTELY"; public static final String ARG_CHECK_REMOTELY = "ARG_CHECK_REMOTELY";
@ -276,6 +297,7 @@ public class Helper {
public static final String ARG_MEDIA_ARRAY_PROFILE = "ARG_MEDIA_ARRAY_PROFILE"; public static final String ARG_MEDIA_ARRAY_PROFILE = "ARG_MEDIA_ARRAY_PROFILE";
public static final String ARG_VISIBILITY = "ARG_VISIBILITY"; public static final String ARG_VISIBILITY = "ARG_VISIBILITY";
public static final String ARG_SCHEDULED_DATE = "ARG_SCHEDULED_DATE"; public static final String ARG_SCHEDULED_DATE = "ARG_SCHEDULED_DATE";
public static final String ARG_SCHEDULED_ID = "ARG_SCHEDULED_ID";
public static final String WORKER_REFRESH_NOTIFICATION = "WORKER_REFRESH_NOTIFICATION"; public static final String WORKER_REFRESH_NOTIFICATION = "WORKER_REFRESH_NOTIFICATION";
public static final String WORKER_REFRESH_HOME = "WORKER_REFRESH_HOME"; public static final String WORKER_REFRESH_HOME = "WORKER_REFRESH_HOME";
@ -300,6 +322,9 @@ public class Helper {
public static final String PREF_USER_TOKEN = "PREF_USER_TOKEN"; public static final String PREF_USER_TOKEN = "PREF_USER_TOKEN";
public static final String PREF_USER_ID = "PREF_USER_ID"; public static final String PREF_USER_ID = "PREF_USER_ID";
public static final String PREF_USER_INSTANCE = "PREF_USER_INSTANCE"; public static final String PREF_USER_INSTANCE = "PREF_USER_INSTANCE";
public static final String PREF_USER_INSTANCE_PEERTUBE_BROWSING = "PREF_USER_INSTANCE_PEERTUBE_BROWSING";
public static final String PREF_USER_SOFTWARE = "PREF_USER_SOFTWARE"; public static final String PREF_USER_SOFTWARE = "PREF_USER_SOFTWARE";
public static final String PREF_IS_MODERATOR = "PREF_IS_MODERATOR"; public static final String PREF_IS_MODERATOR = "PREF_IS_MODERATOR";
public static final String PREF_IS_ADMINISTRATOR = "PREF_IS_ADMINISTRATOR"; public static final String PREF_IS_ADMINISTRATOR = "PREF_IS_ADMINISTRATOR";
@ -312,6 +337,7 @@ public class Helper {
public static final int OPEN_NOTIFICATION = 2; public static final int OPEN_NOTIFICATION = 2;
public static final int OPEN_WITH_ANOTHER_ACCOUNT = 3; public static final int OPEN_WITH_ANOTHER_ACCOUNT = 3;
public static final String INTENT_TARGETED_ACCOUNT = "INTENT_TARGETED_ACCOUNT"; public static final String INTENT_TARGETED_ACCOUNT = "INTENT_TARGETED_ACCOUNT";
public static final String INTENT_TARGETED_STATUS = "INTENT_TARGETED_STATUS";
public static final String INTENT_SEND_MODIFIED_IMAGE = "INTENT_SEND_MODIFIED_IMAGE"; public static final String INTENT_SEND_MODIFIED_IMAGE = "INTENT_SEND_MODIFIED_IMAGE";
public static final String INTENT_COMPOSE_ERROR_MESSAGE = "INTENT_COMPOSE_ERROR_MESSAGE"; public static final String INTENT_COMPOSE_ERROR_MESSAGE = "INTENT_COMPOSE_ERROR_MESSAGE";
public static final String TEMP_MEDIA_DIRECTORY = "TEMP_MEDIA_DIRECTORY"; public static final String TEMP_MEDIA_DIRECTORY = "TEMP_MEDIA_DIRECTORY";
@ -330,11 +356,14 @@ public class Helper {
public static final Pattern groupPattern = Pattern.compile("(![\\w_]+)"); public static final Pattern groupPattern = Pattern.compile("(![\\w_]+)");
public static final Pattern mentionPattern = Pattern.compile("(@[\\w_.-]?[\\w]+)"); public static final Pattern mentionPattern = Pattern.compile("(@[\\w_.-]?[\\w]+)");
public static final Pattern mentionLongPattern = Pattern.compile("(@[\\w_.-]+@[a-zA-Z0-9][a-zA-Z0-9.-]{1,61}[a-zA-Z0-9](?:\\.[a-zA-Z]{2,})+)"); public static final Pattern mentionLongPattern = Pattern.compile("(@[\\w_.-]+@[a-zA-Z0-9][a-zA-Z0-9.-]{1,61}[a-zA-Z0-9](?:\\.[a-zA-Z]{2,})+)");
public static final Pattern mentionPatternALL = Pattern.compile("(@[\\w_.-]+@[a-zA-Z0-9][a-zA-Z0-9.-]{1,61}[a-zA-Z0-9](?:\\.[a-zA-Z]{2,})+)|(@[\\w_.-]?[\\w]+)");
public static final Pattern mathsPattern = Pattern.compile("\\\\\\(|\\\\\\["); public static final Pattern mathsPattern = Pattern.compile("\\\\\\(|\\\\\\[");
public static final Pattern mathsComposePattern = Pattern.compile("\\\\\\(.*\\\\\\)|\\\\\\[.*\\\\\\]"); public static final Pattern mathsComposePattern = Pattern.compile("\\\\\\(.*\\\\\\)|\\\\\\[.*\\\\\\]");
public static final Pattern twitterPattern = Pattern.compile("((@[\\w]+)@twitter\\.com)"); public static final Pattern twitterPattern = Pattern.compile("((@[\\w]+)@twitter\\.com)");
public static final Pattern youtubePattern = Pattern.compile("(www\\.|m\\.)?(youtube\\.com|youtu\\.be|youtube-nocookie\\.com)/(((?!([\"'<])).)*)"); public static final Pattern youtubePattern = Pattern.compile("(www\\.|m\\.)?(youtube\\.com|youtu\\.be|youtube-nocookie\\.com)/(((?!([\"'<])).)*)");
public static final Pattern nitterPattern = Pattern.compile("(mobile\\.|www\\.)?twitter.com([\\w-/]+)"); public static final Pattern nitterPattern = Pattern.compile("(mobile\\.|www\\.)?twitter\\.com([\\w/-]+)");
public static final Pattern bibliogramPattern = Pattern.compile("(m\\.|www\\.)?instagram.com(/p/[\\w-/]+)"); public static final Pattern bibliogramPattern = Pattern.compile("(m\\.|www\\.)?instagram.com(/p/[\\w-/]+)");
public static final Pattern libredditPattern = Pattern.compile("(www\\.|m\\.)?(reddit\\.com|preview\\.redd\\.it|i\\.redd\\.it|redd\\.it)/(((?!([\"'<])).)*)"); public static final Pattern libredditPattern = Pattern.compile("(www\\.|m\\.)?(reddit\\.com|preview\\.redd\\.it|i\\.redd\\.it|redd\\.it)/(((?!([\"'<])).)*)");
public static final Pattern ouichesPattern = Pattern.compile("https?://ouich\\.es/tag/(\\w+)"); public static final Pattern ouichesPattern = Pattern.compile("https?://ouich\\.es/tag/(\\w+)");
@ -401,6 +430,7 @@ public class Helper {
private static int notificationId = 1; private static int notificationId = 1;
//Allow to store in shared preference first visible fragment when the app starts //Allow to store in shared preference first visible fragment when the app starts
private static String slugOfFirstFragment; private static String slugOfFirstFragment;
private static BaseAccount baseAccount;
static { static {
LinkedHashMap<PatternType, Pattern> aMap = new LinkedHashMap<>(); LinkedHashMap<PatternType, Pattern> aMap = new LinkedHashMap<>();
@ -411,7 +441,6 @@ public class Helper {
patternHashMap = Collections.unmodifiableMap(aMap); patternHashMap = Collections.unmodifiableMap(aMap);
} }
/** /**
* Manage downloads with URLs * Manage downloads with URLs
* *
@ -455,8 +484,6 @@ public class Helper {
} }
} }
/*** /***
* Check if the user is connected to Internet * Check if the user is connected to Internet
* @return boolean * @return boolean
@ -561,6 +588,20 @@ public class Helper {
return df.format(date); return df.format(date);
} }
/**
* Convert a date in String
*
* @param date Date
* @return String
*/
public static String mediumDateToString(Date date) {
if (date == null) {
date = new Date();
}
DateFormat df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault());
return df.format(date);
}
/** /**
* Convert a date in String -> format yyyy-MM-dd HH:mm:ss * Convert a date in String -> format yyyy-MM-dd HH:mm:ss
* *
@ -608,19 +649,13 @@ public class Helper {
*/ */
public static Date stringToDateWithFormat(Context context, String stringDate, String format) { public static Date stringToDateWithFormat(Context context, String stringDate, String format) {
if (stringDate == null) if (stringDate == null)
return null; return new Date();
Locale userLocale; SimpleDateFormat dateFormat = new SimpleDateFormat(format, Locale.US);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { Date date = new Date();
userLocale = context.getResources().getConfiguration().getLocales().get(0);
} else {
userLocale = context.getResources().getConfiguration().locale;
}
SimpleDateFormat dateFormat = new SimpleDateFormat(format, userLocale);
Date date = null;
try { try {
date = dateFormat.parse(stringDate); date = dateFormat.parse(stringDate);
} catch (java.text.ParseException ignored) { } catch (java.text.ParseException ignored) {
ignored.printStackTrace();
} }
return date; return date;
} }
@ -648,6 +683,10 @@ public class Helper {
if (url == null) { if (url == null) {
return; return;
} }
/*if(networkAvailable == BaseMainActivity.status.DISCONNECTED){
Toasty.warning(context, context.getString(R.string.toast_error_internet), Toast.LENGTH_LONG).show();
return;
}*/
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
boolean customTab = sharedpreferences.getBoolean(context.getString(R.string.SET_CUSTOM_TABS), true); boolean customTab = sharedpreferences.getBoolean(context.getString(R.string.SET_CUSTOM_TABS), true);
if (customTab) { if (customTab) {
@ -658,18 +697,35 @@ public class Helper {
.build(); .build();
builder.setDefaultColorSchemeParams(defaultColors); builder.setDefaultColorSchemeParams(defaultColors);
CustomTabsIntent customTabsIntent = builder.build(); CustomTabsIntent customTabsIntent = builder.build();
customTabsIntent.launchUrl(context, Uri.parse(url)); try {
customTabsIntent.launchUrl(context, Uri.parse(url).normalizeScheme());
} catch (Exception e) {
if(url.toLowerCase().startsWith("xmpp:")) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setData(Uri.parse("https://xmpp.link/#" + url.replace("xmpp:","")).normalizeScheme());
context.startActivity(intent);
} else {
Toasty.error(context, context.getString(R.string.toast_error), Toast.LENGTH_LONG).show();
}
}
} else { } else {
Intent intent = new Intent(Intent.ACTION_VIEW); Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (!url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://") && !url.toLowerCase().startsWith("gemini://")) { if (!url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://") && !url.toLowerCase().startsWith("gemini://") && !url.toLowerCase().startsWith("xmpp:")) {
url = "http://" + url; url = "http://" + url;
} }
intent.setData(Uri.parse(url)); intent.setData(Uri.parse(url).normalizeScheme());
try { try {
context.startActivity(intent); context.startActivity(intent);
} catch (Exception e) { } catch (Exception e) {
Toasty.error(context, context.getString(R.string.toast_error), Toast.LENGTH_LONG).show(); if(url.toLowerCase().startsWith("xmpp:")) {
intent.setData(Uri.parse("https://xmpp.link/#" + url.replace("xmpp:","")).normalizeScheme());
context.startActivity(intent);
} else {
Toasty.error(context, context.getString(R.string.toast_error), Toast.LENGTH_LONG).show();
}
} }
} }
} }
@ -806,7 +862,7 @@ public class Helper {
@SuppressLint("DefaultLocale") @SuppressLint("DefaultLocale")
public static String withSuffix(long count) { public static String withSuffix(long count) {
if (count < 1000) return "" + count; if (count < 1000) return String.valueOf(count);
int exp = (int) (Math.log(count) / Math.log(1000)); int exp = (int) (Math.log(count) / Math.log(1000));
Locale locale = null; Locale locale = null;
try { try {
@ -832,12 +888,17 @@ public class Helper {
*/ */
public static void sendToastMessage(Context context, String type, String content) { public static void sendToastMessage(Context context, String type, String content) {
Intent intentBC = new Intent(context, ToastMessage.class); Intent intentBC = new Intent(context, ToastMessage.class);
Bundle b = new Bundle(); Bundle args = new Bundle();
b.putString(RECEIVE_TOAST_TYPE, type); args.putString(RECEIVE_TOAST_TYPE, type);
b.putString(RECEIVE_TOAST_CONTENT, content); args.putString(RECEIVE_TOAST_CONTENT, content);
intentBC.setAction(Helper.RECEIVE_TOAST_MESSAGE); intentBC.setAction(Helper.RECEIVE_TOAST_MESSAGE);
intentBC.putExtras(b); new CachedBundle(context).insertBundle(args, Helper.getCurrentAccount(context), bundleId -> {
context.sendBroadcast(intentBC); Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentBC.putExtras(bundle);
intentBC.setPackage(BuildConfig.APPLICATION_ID);
context.sendBroadcast(intentBC);
});
} }
/** /**
@ -858,19 +919,25 @@ public class Helper {
ft.setCustomAnimations(R.anim.enter, R.anim.exit, R.anim.pop_enter, R.anim.pop_exit); ft.setCustomAnimations(R.anim.enter, R.anim.exit, R.anim.pop_enter, R.anim.pop_exit);
Fragment _fragment = fragmentManager.findFragmentByTag(tag); Fragment _fragment = fragmentManager.findFragmentByTag(tag);
if (_fragment != null && _fragment.isAdded()) { if (_fragment != null && _fragment.isAdded()) {
ft.show(_fragment).commit(); ft.show(_fragment).commitAllowingStateLoss();
fragment = _fragment; fragment = _fragment;
} else { } else {
if (args != null) fragment.setArguments(args); if (args != null) fragment.setArguments(args);
ft = fragmentManager.beginTransaction();
ft.add(containerViewId, fragment, tag); ft.add(containerViewId, fragment, tag);
if (backStackName != null) ft.addToBackStack(backStackName); if (backStackName != null) {
ft.commit(); try {
ft.addToBackStack(backStackName);
}catch (Exception ignored){}
}
if (!fragmentManager.isDestroyed()) {
ft.commitAllowingStateLoss();
}
} }
fragmentManager.executePendingTransactions(); fragmentManager.executePendingTransactions();
return fragment; return fragment;
} }
/** /**
* Load a media into a view * Load a media into a view
* *
@ -913,11 +980,11 @@ public class Helper {
OauthVM oauthVM = new ViewModelProvider((ViewModelStoreOwner) activity).get(OauthVM.class); OauthVM oauthVM = new ViewModelProvider((ViewModelStoreOwner) activity).get(OauthVM.class);
if (currentAccount != null) { if (Helper.getCurrentAccount(activity) != null) {
//Revoke the token //Revoke the token
oauthVM.revokeToken(currentAccount.instance, currentAccount.token, currentAccount.client_id, currentAccount.client_secret); oauthVM.revokeToken(Helper.getCurrentAccount(activity).instance, Helper.getCurrentAccount(activity).token, Helper.getCurrentAccount(activity).client_id, Helper.getCurrentAccount(activity).client_secret);
//Log out the current user //Log out the current user
accountDB.removeUser(currentAccount); accountDB.removeUser(Helper.getCurrentAccount(activity));
} }
BaseAccount newAccount = accountDB.getLastUsedAccount(); BaseAccount newAccount = accountDB.getLastUsedAccount();
SharedPreferences.Editor editor = sharedpreferences.edit(); SharedPreferences.Editor editor = sharedpreferences.edit();
@ -931,7 +998,7 @@ public class Helper {
activity.startActivity(loginActivity); activity.startActivity(loginActivity);
activity.finish(); activity.finish();
} else { } else {
currentAccount = newAccount; Helper.setCurrentAccount(newAccount);
editor.putString(PREF_USER_TOKEN, newAccount.token); editor.putString(PREF_USER_TOKEN, newAccount.token);
editor.putString(PREF_USER_SOFTWARE, newAccount.software); editor.putString(PREF_USER_SOFTWARE, newAccount.software);
editor.putString(PREF_USER_INSTANCE, newAccount.instance); editor.putString(PREF_USER_INSTANCE, newAccount.instance);
@ -957,8 +1024,7 @@ public class Helper {
if (context == null) { if (context == null) {
return false; return false;
} }
if (context instanceof Activity) { if (context instanceof Activity activity) {
final Activity activity = (Activity) context;
return !activity.isDestroyed() && !activity.isFinishing(); return !activity.isDestroyed() && !activity.isFinishing();
} }
return true; return true;
@ -1094,11 +1160,13 @@ public class Helper {
.toSquare() .toSquare()
.setBackgroundColor(fetchAccentColor(activity)) .setBackgroundColor(fetchAccentColor(activity))
.build(); .build();
Glide.with(activity) if (Helper.isValidContextForGlide(activity)) {
.asDrawable() Glide.with(activity)
.load(avatar) .asDrawable()
.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10))) .load(avatar)
.into(view); .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10)))
.into(view);
}
return; return;
} }
} }
@ -1264,9 +1332,9 @@ public class Helper {
MimeTypeMap mime = MimeTypeMap.getSingleton(); MimeTypeMap mime = MimeTypeMap.getSingleton();
String extension = mime.getExtensionFromMimeType(cR.getType(uri)); String extension = mime.getExtensionFromMimeType(cR.getType(uri));
if (uri.toString().endsWith("fedilab_recorded_audio.m4a")) { if (uri.toString().endsWith("fedilab_recorded_audio.ogg")) {
extension = ".m4a"; extension = "ogg";
attachment.mimeType = "audio/mp4"; attachment.mimeType = "audio/ogg";
} }
SimpleDateFormat formatter = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_" + counter, Locale.getDefault()); SimpleDateFormat formatter = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_" + counter, Locale.getDefault());
counter++; counter++;
@ -1318,7 +1386,6 @@ public class Helper {
}).start(); }).start();
} }
public static void createFileFromUri(Context context, Uri uri, OnFileCopied callBack) { public static void createFileFromUri(Context context, Uri uri, OnFileCopied callBack) {
new Thread(() -> { new Thread(() -> {
InputStream selectedFileInputStream; InputStream selectedFileInputStream;
@ -1397,6 +1464,24 @@ public class Helper {
imageView.setColorFilter(color); imageView.setColorFilter(color);
} }
/**
* change color of a drawable
*
* @param materialButton {@link MaterialButton}
* @param hexaColor example 0xffff00
*/
public static void changeDrawableColor(Context context, MaterialButton materialButton, int hexaColor) {
if (materialButton == null)
return;
int color;
try {
color = context.getResources().getColor(hexaColor);
} catch (Resources.NotFoundException e) {
color = hexaColor;
}
materialButton.setIconTint(ColorStateList.valueOf(color));
}
/** /**
* change color of a drawable * change color of a drawable
* *
@ -1474,11 +1559,16 @@ public class Helper {
* @param activity - Activity * @param activity - Activity
*/ */
public static void recreateMainActivity(Activity activity) { public static void recreateMainActivity(Activity activity) {
Bundle b = new Bundle(); Bundle args = new Bundle();
b.putBoolean(Helper.RECEIVE_RECREATE_ACTIVITY, true); args.putBoolean(Helper.RECEIVE_RECREATE_ACTIVITY, true);
Intent intentBD = new Intent(Helper.BROADCAST_DATA); Intent intentBD = new Intent(Helper.BROADCAST_DATA);
intentBD.putExtras(b); new CachedBundle(activity).insertBundle(args, Helper.getCurrentAccount(activity), bundleId -> {
LocalBroadcastManager.getInstance(activity).sendBroadcast(intentBD); Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentBD.putExtras(bundle);
intentBD.setPackage(BuildConfig.APPLICATION_ID);
activity.sendBroadcast(intentBD);
});
} }
public static void showKeyboard(Context context, View view) { public static void showKeyboard(Context context, View view) {
@ -1515,49 +1605,50 @@ public class Helper {
String channelTitle; String channelTitle;
switch (notifType) { switch (notifType) {
case FAV: case FAV -> {
channelId = "channel_favourite"; channelId = "channel_favourite";
channelTitle = context.getString(R.string.channel_notif_fav); channelTitle = context.getString(R.string.channel_notif_fav);
break; }
case FOLLLOW: case FOLLLOW -> {
channelId = "channel_follow"; channelId = "channel_follow";
channelTitle = context.getString(R.string.channel_notif_follow); channelTitle = context.getString(R.string.channel_notif_follow);
break; }
case MENTION: case MENTION -> {
channelId = "channel_mention"; channelId = "channel_mention";
channelTitle = context.getString(R.string.channel_notif_mention); channelTitle = context.getString(R.string.channel_notif_mention);
break; }
case POLL: case POLL -> {
channelId = "channel_poll"; channelId = "channel_poll";
channelTitle = context.getString(R.string.channel_notif_poll); channelTitle = context.getString(R.string.channel_notif_poll);
break; }
case BACKUP: case BACKUP -> {
channelId = "channel_backup"; channelId = "channel_backup";
channelTitle = context.getString(R.string.channel_notif_backup); channelTitle = context.getString(R.string.channel_notif_backup);
break; }
case STORE: case STORE -> {
channelId = "channel_media"; channelId = "channel_media";
channelTitle = context.getString(R.string.channel_notif_media); channelTitle = context.getString(R.string.channel_notif_media);
break; }
case TOOT: case TOOT -> {
channelId = "channel_status"; channelId = "channel_status";
channelTitle = context.getString(R.string.channel_notif_status); channelTitle = context.getString(R.string.channel_notif_status);
break; }
case UPDATE: case UPDATE -> {
channelId = "channel_update"; channelId = "channel_update";
channelTitle = context.getString(R.string.channel_notif_update); channelTitle = context.getString(R.string.channel_notif_update);
break; }
case SIGN_UP: case SIGN_UP -> {
channelId = "channel_signup"; channelId = "channel_signup";
channelTitle = context.getString(R.string.channel_notif_signup); channelTitle = context.getString(R.string.channel_notif_signup);
break; }
case REPORT: case REPORT -> {
channelId = "channel_report"; channelId = "channel_report";
channelTitle = context.getString(R.string.channel_notif_report); channelTitle = context.getString(R.string.channel_notif_report);
break; }
default: default -> {
channelId = "channel_boost"; channelId = "channel_boost";
channelTitle = context.getString(R.string.channel_notif_boost); channelTitle = context.getString(R.string.channel_notif_boost);
}
} }
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, channelId) NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, channelId)
.setSmallIcon(getNotificationIcon(context)).setTicker(message); .setSmallIcon(getNotificationIcon(context)).setTicker(message);
@ -1566,31 +1657,25 @@ public class Helper {
message = message.substring(0, 499) + ""; message = message.substring(0, 499) + "";
} }
}*/ }*/
notificationBuilder.setGroup(account.mastodon_account != null ? account.mastodon_account.username + "@" + account.instance : "" + "@" + account.instance) notificationBuilder.setGroup(account.mastodon_account != null ? account.mastodon_account.username + "@" + account.instance : "@" + account.instance)
.setContentIntent(pIntent) .setContentIntent(pIntent)
.setContentText(message); .setContentText(message);
int ledColour = Color.BLUE; int ledColour = Color.BLUE;
int prefColor; int prefColor;
prefColor = Integer.parseInt(sharedpreferences.getString(context.getString(R.string.SET_LED_COLOUR_VAL_N), String.valueOf(LED_COLOUR))); prefColor = Integer.parseInt(sharedpreferences.getString(context.getString(R.string.SET_LED_COLOUR_VAL_N), String.valueOf(LED_COLOUR)));
switch (prefColor) { switch (prefColor) {
case 1: // CYAN case 1 -> // CYAN
ledColour = Color.CYAN; ledColour = Color.CYAN;
break; case 2 -> // MAGENTA
case 2: // MAGENTA ledColour = Color.MAGENTA;
ledColour = Color.MAGENTA; case 3 -> // GREEN
break; ledColour = Color.GREEN;
case 3: // GREEN case 4 -> // RED
ledColour = Color.GREEN; ledColour = Color.RED;
break; case 5 -> // YELLOW
case 4: // RED ledColour = Color.YELLOW;
ledColour = Color.RED; case 6 -> // WHITE
break; ledColour = Color.WHITE;
case 5: // YELLOW
ledColour = Color.YELLOW;
break;
case 6: // WHITE
ledColour = Color.WHITE;
break;
} }
@ -1634,7 +1719,7 @@ public class Helper {
.setLargeIcon(icon) .setLargeIcon(icon)
.setSmallIcon(getNotificationIcon(context)) .setSmallIcon(getNotificationIcon(context))
.setStyle(new NotificationCompat.BigTextStyle().bigText(message)) .setStyle(new NotificationCompat.BigTextStyle().bigText(message))
.setGroup(account.mastodon_account != null ? account.mastodon_account.username + "@" + account.instance : "" + "@" + account.instance) .setGroup(account.mastodon_account != null ? account.mastodon_account.username + "@" + account.instance : "@" + account.instance)
.setGroupSummary(true) .setGroupSummary(true)
.build(); .build();
@ -1644,7 +1729,6 @@ public class Helper {
} }
} }
public static String dateDiffFull(Date dateToot) { public static String dateDiffFull(Date dateToot) {
SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.MEDIUM, Locale.getDefault()); SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.MEDIUM, Locale.getDefault());
try { try {
@ -1654,6 +1738,15 @@ public class Helper {
} }
} }
public static String dateDiffFullShort(Date dateToot) {
SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault());
try {
return df.format(dateToot);
} catch (Exception e) {
return "";
}
}
/** /**
* Makes the tvDate TextView field clickable, and displays the absolute date & time of a toot * Makes the tvDate TextView field clickable, and displays the absolute date & time of a toot
* for 5 seconds. * for 5 seconds.
@ -1756,7 +1849,6 @@ public class Helper {
if (responseCode == HttpURLConnection.HTTP_OK) { if (responseCode == HttpURLConnection.HTTP_OK) {
String fileName = ""; String fileName = "";
String disposition = httpURLConnection.getHeaderField("Content-Disposition"); String disposition = httpURLConnection.getHeaderField("Content-Disposition");
if (disposition != null) { if (disposition != null) {
// extracts file name from header field // extracts file name from header field
int index = disposition.indexOf("filename="); int index = disposition.indexOf("filename=");
@ -1766,7 +1858,12 @@ public class Helper {
} }
} else { } else {
// extracts file name from URL // extracts file name from URL
fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/") + 1); try {
URL downLoadUrlTmp = new URL(downloadUrl);
fileName = downLoadUrlTmp.getPath().replace("/","_");
}catch (Exception exception) {
fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/") + 1);
}
} }
fileName = FileNameCleaner.cleanFileName(fileName); fileName = FileNameCleaner.cleanFileName(fileName);
// opens input stream from the HTTP connection // opens input stream from the HTTP connection
@ -1873,12 +1970,14 @@ public class Helper {
binding.accountUn.setText(account.acct); binding.accountUn.setText(account.acct);
binding.accountPp.setOnClickListener(v -> { binding.accountPp.setOnClickListener(v -> {
Intent intent = new Intent(activity, ProfileActivity.class); Intent intent = new Intent(activity, ProfileActivity.class);
Bundle b = new Bundle(); Bundle args = new Bundle();
b.putSerializable(Helper.ARG_ACCOUNT, account); args.putSerializable(Helper.ARG_ACCOUNT, account);
intent.putExtras(b); new CachedBundle(activity).insertBundle(args, Helper.getCurrentAccount(activity), bundleId -> {
ActivityOptionsCompat options = ActivityOptionsCompat Bundle bundle = new Bundle();
.makeSceneTransitionAnimation(activity, binding.accountPp, activity.getString(R.string.activity_porfile_pp)); bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
activity.startActivity(intent, options.toBundle()); intent.putExtras(bundle);
activity.startActivity(intent);
});
}); });
AccountsVM accountsVM = new ViewModelProvider((ViewModelStoreOwner) activity).get(AccountsVM.class); AccountsVM accountsVM = new ViewModelProvider((ViewModelStoreOwner) activity).get(AccountsVM.class);
@ -1961,7 +2060,6 @@ public class Helper {
Runtime.getRuntime().exit(0); Runtime.getRuntime().exit(0);
} }
public static void forwardToBrowser(Activity activity, Intent i) { public static void forwardToBrowser(Activity activity, Intent i) {
Intent intent = new Intent(); Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW); intent.setAction(android.content.Intent.ACTION_VIEW);
@ -1986,7 +2084,6 @@ public class Helper {
} }
} }
public static int dialogStyle() { public static int dialogStyle() {
if (R.style.AppThemeBar == currentThemeId || R.style.AppTheme == currentThemeId) { if (R.style.AppThemeBar == currentThemeId || R.style.AppTheme == currentThemeId) {
return R.style.AppThemeAlertDialog; return R.style.AppThemeAlertDialog;
@ -2015,6 +2112,83 @@ public class Helper {
} }
} }
public static BaseAccount getCurrentAccount(Context context) {
if (baseAccount == null && context != null) {
baseAccount = new BaseAccount();
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
baseAccount.user_id = sharedpreferences.getString(PREF_USER_ID, null);
baseAccount.instance = sharedpreferences.getString(PREF_USER_INSTANCE, null);
baseAccount.token = sharedpreferences.getString(PREF_USER_TOKEN, null);
}
return baseAccount;
}
public static void setCurrentAccount(BaseAccount newBaseAccount) {
baseAccount = newBaseAccount;
}
public static void setCurrentAccountMastodonAccount(Context context, app.fedilab.android.mastodon.client.entities.api.Account newAccount) {
BaseAccount tempBaseAccount = getCurrentAccount(context);
tempBaseAccount.mastodon_account = newAccount;
setCurrentAccount(tempBaseAccount);
}
public static void setCurrentAccountPeertubeAccount(Context context, AccountData.PeertubeAccount newAccount) {
BaseAccount tempBaseAccount = getCurrentAccount(context);
tempBaseAccount.peertube_account = newAccount;
setCurrentAccount(tempBaseAccount);
}
public static boolean isNumeric(String str) {
try {
Double.parseDouble(str);
return true;
} catch (NumberFormatException e) {
return false;
}
}
public static OkHttpClient myOkHttpClient(Context context) {
return new OkHttpClient.Builder()
.addInterceptor(chain -> {
Request originalRequest = chain.request();
Request requestWithUserAgent = originalRequest.newBuilder()
.header("User-Agent", context.getString(R.string.app_name) + "/" + BuildConfig.VERSION_NAME + "/" + BuildConfig.VERSION_CODE)
.build();
return chain.proceed(requestWithUserAgent);
})
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.callTimeout(60, TimeUnit.SECONDS)
.proxy(Helper.getProxy(context))
.build();
}
public static OkHttpClient myPostOkHttpClient(Context context) {
return new OkHttpClient.Builder()
.addInterceptor(chain -> {
Request originalRequest = chain.request();
Request requestWithUserAgent = originalRequest.newBuilder()
.header("User-Agent", context.getString(R.string.app_name) + "/" + BuildConfig.VERSION_NAME + "/" + BuildConfig.VERSION_CODE)
.build();
return chain.proceed(requestWithUserAgent);
})
.readTimeout(120, TimeUnit.SECONDS)
.connectTimeout(120, TimeUnit.SECONDS)
.callTimeout(120, TimeUnit.SECONDS)
.proxy(Helper.getProxy(context))
.build();
}
public static String parseHtml(String html) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY).toString();
} else {
//noinspection deprecation
return Html.fromHtml(html).toString();
}
}
//Enum that described actions to replace inside a toot content //Enum that described actions to replace inside a toot content
public enum PatternType { public enum PatternType {
MENTION, MENTION,

View file

@ -0,0 +1,209 @@
package app.fedilab.android.mastodon.helper;
/* Copyright 2023 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import android.app.AlertDialog;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.RadioButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.ListPreference;
import java.util.ArrayList;
import java.util.List;
import app.fedilab.android.R;
public class ImageListPreference extends ListPreference {
private static final int DEFAULT_TINT = 0;
private static final int DEFAULT_BACKGROUND_TINT = 0xFFFFFFFF;
private final List<Integer> mImages;
private int mErrorResource;
private int mTintColor;
private int mBackgroundColor;
private boolean mUseCard;
private int mCustomItemLayout;
public ImageListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
mImages = new ArrayList<>();
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ImageListPreference);
try {
int entryImagesArrayResource = array.getResourceId(R.styleable.ImageListPreference_ilp_entryImages, 0);
String tintKey = array.getNonResourceString(R.styleable.ImageListPreference_ilp_tintKey);
String backgroundKey = array.getNonResourceString(R.styleable.ImageListPreference_ilp_backgroundTint);
mTintColor = array.getColor(R.styleable.ImageListPreference_ilp_tint, DEFAULT_TINT);
mBackgroundColor = array.getColor(R.styleable.ImageListPreference_ilp_backgroundTint, 0);
mErrorResource = array.getResourceId(R.styleable.ImageListPreference_ilp_errorImage, 0);
mUseCard = array.getBoolean(R.styleable.ImageListPreference_ilp_useCard, false);
mCustomItemLayout = array.getResourceId(R.styleable.ImageListPreference_ilp_itemLayout, 0);
if (tintKey != null) {
mTintColor = sharedPreferences.getInt(tintKey, mTintColor);
}
if (backgroundKey != null) {
mBackgroundColor = sharedPreferences.getInt(backgroundKey, mBackgroundColor);
}
TypedArray images = context.getResources().obtainTypedArray(entryImagesArrayResource);
for (int i = 0; i < images.length(); i++) {
mImages.add(images.getResourceId(i, 0));
}
images.recycle();
} catch (Exception e) {
e.printStackTrace();
} finally {
array.recycle();
}
}
@Override
protected void onClick() {
List<ImageListItem> items = new ArrayList<>();
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
String launcher = sharedpreferences.getString(getContext().getString(R.string.SET_LOGO_LAUNCHER), "Bubbles");
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
int length = getEntries().length;
for (int i = 0; i < length; i++) {
int resource = 0;
if (mImages.size() > i) {
resource = mImages.get(i);
}
items.add(new ImageListItem(getEntries()[i], resource, String.valueOf(getEntryValues()[i]).equalsIgnoreCase(launcher)));
}
int layout = R.layout.imagelistpreference_item;
if (mUseCard) {
layout = R.layout.imagelistpreference_item_card;
}
if (mCustomItemLayout != 0) {
layout = mCustomItemLayout;
}
ListAdapter adapter = new ImageListPreferenceAdapter(getContext(), layout, items);
builder.setAdapter(adapter, (dialogInterface, which) -> {
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(getContext().getString(R.string.SET_LOGO_LAUNCHER), String.valueOf(getEntryValues()[which]));
editor.commit();
});
builder.create().show();
}
private static class ImageListItem {
private final int resource;
private final boolean isChecked;
private final String name;
ImageListItem(CharSequence name, int resource, boolean isChecked) {
this(name.toString(), resource, isChecked);
}
ImageListItem(String name, int resource, boolean isChecked) {
this.name = name;
this.resource = resource;
this.isChecked = isChecked;
}
}
private static class ViewHolder {
ImageView iconImage;
TextView iconName;
RadioButton radioButton;
}
private class ImageListPreferenceAdapter extends ArrayAdapter<ImageListItem> {
private final List<ImageListItem> mItems;
private final int mLayoutResource;
ImageListPreferenceAdapter(Context context, int layoutResource, List<ImageListItem> items) {
super(context, layoutResource, items);
mLayoutResource = layoutResource;
mItems = items;
}
@Override
public @NonNull
View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) getContext().
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
try {
assert inflater != null;
convertView = inflater.inflate(mLayoutResource, parent, false);
holder = new ViewHolder();
holder.iconName = convertView.findViewById(R.id.imagelistpreference_text);
holder.iconImage = convertView.findViewById(R.id.imagelistpreference_image);
holder.radioButton = convertView.findViewById(R.id.imagelistpreference_radio);
convertView.setTag(holder);
} catch (Exception e) {
e.printStackTrace();
return super.getView(position, null, parent);
}
} else {
holder = (ViewHolder) convertView.getTag();
}
if (holder == null) {
return super.getView(position, convertView, parent);
}
ImageListItem item = mItems.get(position);
holder.iconName.setText(item.name);
if (item.resource != 0) {
holder.iconImage.setImageResource(item.resource);
} else {
holder.iconImage.setImageResource(mErrorResource);
}
if (mTintColor != 0) {
holder.iconImage.setColorFilter(mTintColor);
}
if (mBackgroundColor != 0) {
holder.iconImage.setBackgroundColor(mBackgroundColor);
}
holder.radioButton.setChecked(item.isChecked);
return convertView;
}
}
}

View file

@ -60,6 +60,16 @@ public class LogoHelper {
return R.drawable.fedilab_logo_crash; return R.drawable.fedilab_logo_crash;
case "Mastalab": case "Mastalab":
return R.drawable.fedilab_logo_mastalab; return R.drawable.fedilab_logo_mastalab;
case "BubblesUA":
return R.drawable.fedilab_logo_bubbles_ua;
case "BubblesPeaGreen":
return R.drawable.fedilab_logo_bubbles_pea_green;
case "BubblesPride":
return R.drawable.fedilab_logo_bubbles_pride;
case "BubblesPink":
return R.drawable.fedilab_logo_bubbles_pink;
case "BubblesPirate":
return R.drawable.fedilab_logo_bubbles_pirate;
default: default:
return R.drawable.fedilab_logo_bubbles; return R.drawable.fedilab_logo_bubbles;
} }
@ -78,6 +88,16 @@ public class LogoHelper {
return R.drawable.fedilab_logo_crash; return R.drawable.fedilab_logo_crash;
case "Mastalab": case "Mastalab":
return R.drawable.fedilab_logo_mastalab; return R.drawable.fedilab_logo_mastalab;
case "BubblesUA":
return R.drawable.fedilab_logo_bubbles_ua;
case "BubblesPeaGreen":
return R.drawable.fedilab_logo_bubbles_pea_green;
case "BubblesPride":
return R.drawable.fedilab_logo_bubbles_pride;
case "BubblesPink":
return R.drawable.fedilab_logo_bubbles_pink;
case "BubblesPirate":
return R.drawable.fedilab_logo_bubbles_pirate;
default: default:
return R.drawable.fedilab_logo_bubbles; return R.drawable.fedilab_logo_bubbles;
} }
@ -100,6 +120,21 @@ public class LogoHelper {
case "Mastalab": case "Mastalab":
mLauncher = BaseMainActivity.iconLauncher.MASTALAB; mLauncher = BaseMainActivity.iconLauncher.MASTALAB;
break; break;
case "BubblesUA":
mLauncher = BaseMainActivity.iconLauncher.BUBBLESUA;
break;
case "BubblesPeaGreen":
mLauncher = BaseMainActivity.iconLauncher.BUBBLESPEAGREEN;
break;
case "BubblesPride":
mLauncher = BaseMainActivity.iconLauncher.BUBBLESPRIDE;
break;
case "BubblesPink":
mLauncher = BaseMainActivity.iconLauncher.BUBBLESPINK;
break;
case "BubblesPirate":
mLauncher = BaseMainActivity.iconLauncher.BUBBLESPIRATE;
break;
default: default:
mLauncher = BaseMainActivity.iconLauncher.BUBBLES; mLauncher = BaseMainActivity.iconLauncher.BUBBLES;
} }

View file

@ -239,9 +239,9 @@ public class MastodonHelper {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
boolean disableGif = sharedpreferences.getBoolean(context.getString(R.string.SET_DISABLE_GIF), false); boolean disableGif = sharedpreferences.getBoolean(context.getString(R.string.SET_DISABLE_GIF), false);
@DrawableRes int placeholder = type == MediaAccountType.AVATAR ? R.drawable.ic_person : R.drawable.default_banner; @DrawableRes int placeholder = type == MediaAccountType.AVATAR ? R.drawable.ic_person : R.drawable.default_banner;
if (Helper.isValidContextForGlide(activity != null ? activity : context)) { if (Helper.isValidContextForGlide(activity != null ? activity.getApplicationContext() : context.getApplicationContext())) {
if (account == null) { if (account == null) {
Glide.with(activity != null ? activity : context) Glide.with(activity != null ? activity.getApplicationContext() : context.getApplicationContext())
.asDrawable() .asDrawable()
.load(placeholder) .load(placeholder)
.thumbnail(0.1f) .thumbnail(0.1f)
@ -253,7 +253,7 @@ public class MastodonHelper {
if (targetedUrl != null) { if (targetedUrl != null) {
if (disableGif || (!targetedUrl.endsWith(".gif"))) { if (disableGif || (!targetedUrl.endsWith(".gif"))) {
try { try {
Glide.with(activity != null ? activity : context) Glide.with(activity != null ? activity.getApplicationContext() : context.getApplicationContext())
.asDrawable() .asDrawable()
.load(targetedUrl) .load(targetedUrl)
.thumbnail(0.1f) .thumbnail(0.1f)
@ -263,7 +263,7 @@ public class MastodonHelper {
e.printStackTrace(); e.printStackTrace();
} }
} else { } else {
Glide.with(activity != null ? activity : context) Glide.with(activity != null ? activity.getApplicationContext() : context.getApplicationContext())
.asGif() .asGif()
.load(targetedUrl) .load(targetedUrl)
.thumbnail(0.1f) .thumbnail(0.1f)
@ -271,7 +271,7 @@ public class MastodonHelper {
.into(view); .into(view);
} }
} else { } else {
Glide.with(activity != null ? activity : context) Glide.with(activity != null ? activity.getApplicationContext() : context.getApplicationContext())
.asDrawable() .asDrawable()
.load(placeholder) .load(placeholder)
.thumbnail(0.1f) .thumbnail(0.1f)
@ -397,7 +397,7 @@ public class MastodonHelper {
* @param text String - The current text * @param text String - The current text
* @return int - Number of characters used by emoji * @return int - Number of characters used by emoji
*/ */
private static int countWithEmoji(String text) { static int countWithEmoji(String text) {
int emojiCount = 0; int emojiCount = 0;
for (int i = 0; i < text.length(); i++) { for (int i = 0; i < text.length(); i++) {
int type = Character.getType(text.charAt(i)); int type = Character.getType(text.charAt(i));
@ -499,7 +499,7 @@ public class MastodonHelper {
accountId = account.id; accountId = account.id;
acct = account.acct; acct = account.acct;
} }
accountsVM.mute(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, accountId, true, (int) delayToPass) accountsVM.mute(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, accountId, true, (int) delayToPass / 1000)
.observe((LifecycleOwner) context, relationShip -> { .observe((LifecycleOwner) context, relationShip -> {
if (listener != null) { if (listener != null) {
listener.onTimedMute(relationShip); listener.onTimedMute(relationShip);
@ -553,13 +553,13 @@ public class MastodonHelper {
public static int getInstanceMaxChars(Context context) { public static int getInstanceMaxChars(Context context) {
int max_car; int max_car;
if (instanceInfo != null) { SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
max_car = instanceInfo.max_toot_chars != null ? Integer.parseInt(instanceInfo.max_toot_chars) : instanceInfo.configuration.statusesConf.max_characters; int val = sharedpreferences.getInt(context.getString(R.string.SET_MAX_INSTANCE_CHAR) + MainActivity.currentInstance, -1);
if (val != -1) {
return val;
} else { } else {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); if (instanceInfo != null) {
int val = sharedpreferences.getInt(context.getString(R.string.SET_MAX_INSTANCE_CHAR) + MainActivity.currentInstance, -1); max_car = instanceInfo.max_toot_chars != null && instanceInfo.max_toot_chars.matches("\\d+") ? Integer.parseInt(instanceInfo.max_toot_chars) : instanceInfo.configuration.statusesConf.max_characters;
if (val != -1) {
return val;
} else { } else {
max_car = 500; max_car = 500;
} }

View file

@ -15,7 +15,6 @@ package app.fedilab.android.mastodon.helper;
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static android.content.Context.DOWNLOAD_SERVICE; import static android.content.Context.DOWNLOAD_SERVICE;
import static app.fedilab.android.BaseMainActivity.currentAccount;
import static app.fedilab.android.mastodon.helper.LogoHelper.getMainLogo; import static app.fedilab.android.mastodon.helper.LogoHelper.getMainLogo;
import android.app.Activity; import android.app.Activity;
@ -26,6 +25,7 @@ import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.media.MediaRecorder; import android.media.MediaRecorder;
import android.media.MediaScannerConnection; import android.media.MediaScannerConnection;
@ -172,7 +172,7 @@ public class MediaHelper {
intent.setDataAndType(uri, mime); intent.setDataAndType(uri, mime);
MediaScannerConnection.scanFile(context, new String[]{backupFile.getAbsolutePath()}, null, null); MediaScannerConnection.scanFile(context, new String[]{backupFile.getAbsolutePath()}, null, null);
if (!share) { if (!share) {
Helper.notify_user(context, currentAccount, intent, BitmapFactory.decodeResource(context.getResources(), Helper.notify_user(context, Helper.getCurrentAccount(context), intent, BitmapFactory.decodeResource(context.getResources(),
getMainLogo(context)), Helper.NotifType.STORE, context.getString(R.string.save_over), context.getString(R.string.download_from, fileName)); getMainLogo(context)), Helper.NotifType.STORE, context.getString(R.string.save_over), context.getString(R.string.download_from, fileName));
Toasty.success(context, context.getString(R.string.save_over), Toasty.LENGTH_LONG).show(); Toasty.success(context, context.getString(R.string.save_over), Toasty.LENGTH_LONG).show();
} else { } else {
@ -267,6 +267,32 @@ public class MediaHelper {
); );
} }
public static Drawable rescaleImageIfNeeded(Activity activity, Drawable image) {
if (!(image instanceof BitmapDrawable)) {
return image;
}
int maxSize = 2000;
int width = image.getIntrinsicWidth();
int height = image.getIntrinsicHeight();
float scaleFactor;
if(width > maxSize || height > maxSize) {
if(width >= height) {
scaleFactor = (float) maxSize / width;
} else {
scaleFactor = (float) maxSize / height;
}
} else {
return image;
}
Bitmap b = ((BitmapDrawable)image).getBitmap();
int sizeX = Math.round(image.getIntrinsicWidth() * scaleFactor);
int sizeY = Math.round(image.getIntrinsicHeight() * scaleFactor);
Bitmap bitmapResized = Bitmap.createScaledBitmap(b, sizeX, sizeY, false);
image = new BitmapDrawable(activity.getResources(), bitmapResized);
return image;
}
/** /**
* Record media * Record media
* *
@ -274,7 +300,8 @@ public class MediaHelper {
* @param listener ActionRecord * @param listener ActionRecord
*/ */
public static void recordAudio(Activity activity, ActionRecord listener) { public static void recordAudio(Activity activity, ActionRecord listener) {
String filePath = activity.getCacheDir() + "/fedilab_recorded_audio.m4a"; //String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() + "/fedilab_recorded_audio.ogg";
String filePath = activity.getCacheDir() + "/fedilab_recorded_audio.ogg";
AudioRecorder mAudioRecorder = AudioRecorder.getInstance(); AudioRecorder mAudioRecorder = AudioRecorder.getInstance();
File mAudioFile = new File(filePath); File mAudioFile = new File(filePath);
PopupRecordBinding binding = PopupRecordBinding.inflate(activity.getLayoutInflater()); PopupRecordBinding binding = PopupRecordBinding.inflate(activity.getLayoutInflater());
@ -306,16 +333,18 @@ public class MediaHelper {
binding.counter.setText(String.format(Locale.getDefault(), "%s:%s", minutes, seconds)); binding.counter.setText(String.format(Locale.getDefault(), "%s:%s", minutes, seconds));
}); });
} }
}, 1000, 1000); }, 0, 1000);
binding.record.setOnClickListener(v -> { binding.record.setOnClickListener(v -> {
mAudioRecorder.stopRecord(); mAudioRecorder.stopRecord();
timer.cancel(); timer.cancel();
alert.dismiss(); alert.dismiss();
listener.onRecorded(filePath); listener.onRecorded(filePath);
}); });
mAudioRecorder.prepareRecord(MediaRecorder.AudioSource.MIC, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaRecorder.OutputFormat.MPEG_4, MediaRecorder.AudioEncoder.AAC, mAudioRecorder.prepareRecord(MediaRecorder.AudioSource.MIC,
mAudioFile); MediaRecorder.OutputFormat.OGG, MediaRecorder.AudioEncoder.OPUS, 48000, 384000,
mAudioFile);
}
mAudioRecorder.startRecord(); mAudioRecorder.startRecord();
} }
@ -390,8 +419,8 @@ public class MediaHelper {
int maxHeight = RelativeLayout.LayoutParams.WRAP_CONTENT; int maxHeight = RelativeLayout.LayoutParams.WRAP_CONTENT;
if (attachmentList != null && attachmentList.size() > 0) { if (attachmentList != null && attachmentList.size() > 0) {
for (Attachment attachment : attachmentList) { for (Attachment attachment : attachmentList) {
if (attachment.meta != null && attachment.meta.small != null && attachment.meta.small.height > maxHeight) { if (attachment.meta != null && attachment.meta.getSmall() != null && attachment.meta.getSmall().height > maxHeight) {
maxHeight = (int) Helper.convertDpToPixel(attachment.meta.small.height, context); maxHeight = (int) Helper.convertDpToPixel(attachment.meta.getSmall().height, context);
} }
} }
} }

View file

@ -24,6 +24,7 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
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.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.text.Html; import android.text.Html;
@ -34,17 +35,15 @@ import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.CustomTarget; import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.transition.Transition; import com.bumptech.glide.request.transition.Transition;
import java.net.IDN;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import app.fedilab.android.BaseMainActivity; import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R; import app.fedilab.android.R;
@ -52,8 +51,10 @@ import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.mastodon.client.endpoints.MastodonNotificationsService; import app.fedilab.android.mastodon.client.endpoints.MastodonNotificationsService;
import app.fedilab.android.mastodon.client.entities.api.Notification; import app.fedilab.android.mastodon.client.entities.api.Notification;
import app.fedilab.android.mastodon.client.entities.api.Notifications; import app.fedilab.android.mastodon.client.entities.api.Notifications;
import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.app.Account; import app.fedilab.android.mastodon.client.entities.app.Account;
import app.fedilab.android.mastodon.client.entities.app.BaseAccount; import app.fedilab.android.mastodon.client.entities.app.BaseAccount;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.exception.DBException; import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.ui.drawer.StatusAdapter; import app.fedilab.android.mastodon.ui.drawer.StatusAdapter;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@ -65,9 +66,21 @@ import retrofit2.converter.gson.GsonConverterFactory;
public class NotificationsHelper { public class NotificationsHelper {
public static HashMap<String, String> since_ids = new HashMap<>(); private static final HashMap<String, ReentrantLock> lockMap = new HashMap<>();
public static HashMap<String, List<String>> pushed_notifications = new HashMap<>();
public static void task(Context context, String slug) throws DBException { private static ReentrantLock getLock(String slug) {
synchronized (lockMap) {
if (lockMap.containsKey(slug)) {
return lockMap.get(slug);
}
ReentrantLock lock = new ReentrantLock();
lockMap.put(slug, lock);
return lock;
}
}
public static synchronized void task(Context context, String slug) throws DBException {
SharedPreferences prefs = PreferenceManager SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context); .getDefaultSharedPreferences(context);
@ -76,13 +89,6 @@ public class NotificationsHelper {
if (accountDb == null) { if (accountDb == null) {
return; return;
} }
String last_notifid;
if (since_ids.containsKey(slug)) {
last_notifid = since_ids.get(slug);
} else {
last_notifid = prefs.getString(context.getString(R.string.LAST_NOTIFICATION_ID) + slug, null);
since_ids.put(slug, last_notifid);
}
//Check which notifications the user wants to see //Check which notifications the user wants to see
boolean notif_follow = prefs.getBoolean(context.getString(R.string.SET_NOTIF_FOLLOW), true); boolean notif_follow = prefs.getBoolean(context.getString(R.string.SET_NOTIF_FOLLOW), true);
@ -96,44 +102,54 @@ public class NotificationsHelper {
boolean notif_report = prefs.getBoolean(context.getString(R.string.SET_NOTIF_ADMIN_REPORT), true); boolean notif_report = prefs.getBoolean(context.getString(R.string.SET_NOTIF_ADMIN_REPORT), true);
//User disagree with all notifications //User disagree with all notifications
if (!notif_follow && !notif_fav && !notif_mention && !notif_share && !notif_poll && !notif_status && !notif_updates && !notif_signup && !notif_report) if (!notif_follow && !notif_fav && !notif_mention && !notif_share && !notif_poll && !notif_status && !notif_updates && !notif_signup && !notif_report) {
return; //Nothing is done return; //Nothing is done
}
MastodonNotificationsService mastodonNotificationsService = init(context, slugArray[1]);
String finalLast_notifid = last_notifid;
new Thread(() -> { new Thread(() -> {
Notifications notifications = new Notifications(); ReentrantLock lock = getLock(slug);
Call<List<Notification>> notificationsCall; // fetch if we get the lock, or ignore, another thread is doing the job
if (finalLast_notifid != null) { if (lock.tryLock()) {
notificationsCall = mastodonNotificationsService.getNotifications(accountDb.token, null, null, null, finalLast_notifid, null, 30);
} else {
notificationsCall = mastodonNotificationsService.getNotifications(accountDb.token, null, null, null, null, null, 5);
}
if (notificationsCall != null) {
try { try {
Response<List<Notification>> notificationsResponse = notificationsCall.execute(); MastodonNotificationsService mastodonNotificationsService = init(context, slugArray[1]);
if (notificationsResponse.isSuccessful()) { Notifications notifications = new Notifications();
notifications.notifications = notificationsResponse.body(); Call<List<Notification>> notificationsCall;
if (notifications.notifications != null) { String last_notif_id = prefs.getString(context.getString(R.string.LAST_NOTIFICATION_ID) + slug, null);
if (notifications.notifications.size() > 0) { notificationsCall = mastodonNotificationsService.getNotifications(accountDb.token, null, null, null, last_notif_id, null, 30);
since_ids.put(slug, notifications.notifications.get(0).id); if (notificationsCall != null) {
try {
Response<List<Notification>> notificationsResponse = notificationsCall.execute();
if (notificationsResponse.isSuccessful()) {
notifications.notifications = notificationsResponse.body();
if (notifications.notifications != null) {
if (notifications.notifications.size() > 0) {
prefs.edit().putString(
context.getString(R.string.LAST_NOTIFICATION_ID) + slug,
notifications.notifications.get(0).id
).apply();
}
}
notifications.pagination = MastodonHelper.getPagination(notificationsResponse.headers());
} }
} catch (Exception e) {
e.printStackTrace();
} }
notifications.pagination = MastodonHelper.getPagination(notificationsResponse.headers()); Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> onRetrieveNotifications(context, notifications, accountDb, last_notif_id);
mainHandler.post(myRunnable);
}
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
} }
} catch (Exception e) {
e.printStackTrace();
} }
} }
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> onRetrieveNotifications(context, notifications, accountDb);
mainHandler.post(myRunnable);
}).start(); }).start();
} }
private static MastodonNotificationsService init(Context context, @NonNull String instance) { private static MastodonNotificationsService init(Context context, String instance) {
final OkHttpClient okHttpClient = new OkHttpClient.Builder() final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS)
@ -141,14 +157,14 @@ public class NotificationsHelper {
.proxy(Helper.getProxy(context)) .proxy(Helper.getProxy(context))
.build(); .build();
Retrofit retrofit = new Retrofit.Builder() Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + instance + "/api/v1/") .baseUrl("https://" + (instance != null ? IDN.toASCII(instance, IDN.ALLOW_UNASSIGNED) : null) + "/api/v1/")
.addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder())) .addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder()))
.client(okHttpClient) .client(okHttpClient)
.build(); .build();
return retrofit.create(MastodonNotificationsService.class); return retrofit.create(MastodonNotificationsService.class);
} }
public static void onRetrieveNotifications(Context context, Notifications newNotifications, final BaseAccount account) { public static void onRetrieveNotifications(Context context, Notifications newNotifications, final BaseAccount account, String max_id) {
if (newNotifications == null || newNotifications.notifications == null || newNotifications.notifications.size() == 0 || account == null) { if (newNotifications == null || newNotifications.notifications == null || newNotifications.notifications.size() == 0 || account == null) {
return; return;
} }
@ -167,8 +183,6 @@ public class NotificationsHelper {
boolean notif_signup = prefs.getBoolean(context.getString(R.string.SET_NOTIF_ADMIN_SIGNUP), true); boolean notif_signup = prefs.getBoolean(context.getString(R.string.SET_NOTIF_ADMIN_SIGNUP), true);
boolean notif_report = prefs.getBoolean(context.getString(R.string.SET_NOTIF_ADMIN_REPORT), true); boolean notif_report = prefs.getBoolean(context.getString(R.string.SET_NOTIF_ADMIN_REPORT), true);
final String max_id = prefs.getString(context.getString(R.string.LAST_NOTIFICATION_ID) + key, null);
final List<Notification> notifications = new ArrayList<>(); final List<Notification> notifications = new ArrayList<>();
int pos = 0; int pos = 0;
for (Notification notif : notificationsReceived) { for (Notification notif : notificationsReceived) {
@ -184,10 +198,24 @@ public class NotificationsHelper {
//No previous notifications in cache, so no notification will be sent //No previous notifications in cache, so no notification will be sent
for (Notification notification : notifications) { for (Notification notification : notifications) {
List<String> notificationIDList;
if (pushed_notifications.containsKey(key)) {
notificationIDList = pushed_notifications.get(key);
if (notificationIDList != null && notificationIDList.contains(notification.id)) {
continue;
}
}
notificationIDList = pushed_notifications.get(key);
if (notificationIDList == null) {
notificationIDList = new ArrayList<>();
}
notificationIDList.add(notification.id);
pushed_notifications.put(key, notificationIDList);
String notificationUrl; String notificationUrl;
String title = null; String title = null;
String message = null; String message = null;
String targeted_account = null; app.fedilab.android.mastodon.client.entities.api.Account targeted_account = null;
Status targeted_status = null;
Helper.NotifType notifType = Helper.NotifType.MENTION; Helper.NotifType notifType = Helper.NotifType.MENTION;
switch (notification.type) { switch (notification.type) {
case "mention": case "mention":
@ -208,6 +236,7 @@ public class NotificationsHelper {
else else
message = new SpannableString(Html.fromHtml(notification.status.content)).toString(); message = new SpannableString(Html.fromHtml(notification.status.content)).toString();
} }
targeted_status = notification.status;
} }
} }
break; break;
@ -230,6 +259,7 @@ public class NotificationsHelper {
else else
message = new SpannableString(Html.fromHtml(notification.status.content)).toString(); message = new SpannableString(Html.fromHtml(notification.status.content)).toString();
} }
targeted_status = notification.status;
} }
} }
break; break;
@ -285,7 +315,7 @@ public class NotificationsHelper {
message = String.format("%s %s", notification.account.display_name, context.getString(R.string.notif_follow_request)); message = String.format("%s %s", notification.account.display_name, context.getString(R.string.notif_follow_request));
else else
message = String.format("@%s %s", notification.account.acct, context.getString(R.string.notif_follow_request)); message = String.format("@%s %s", notification.account.acct, context.getString(R.string.notif_follow_request));
targeted_account = notification.account.id; targeted_account = notification.account;
} }
break; break;
case "follow": case "follow":
@ -296,7 +326,7 @@ public class NotificationsHelper {
message = String.format("%s %s", notification.account.display_name, context.getString(R.string.notif_follow)); message = String.format("%s %s", notification.account.display_name, context.getString(R.string.notif_follow));
else else
message = String.format("@%s %s", notification.account.acct, context.getString(R.string.notif_follow)); message = String.format("@%s %s", notification.account.acct, context.getString(R.string.notif_follow));
targeted_account = notification.account.id; targeted_account = notification.account;
} }
break; break;
case "poll": case "poll":
@ -337,6 +367,7 @@ public class NotificationsHelper {
else else
message = new SpannableString(Html.fromHtml(notification.status.content)).toString(); message = new SpannableString(Html.fromHtml(notification.status.content)).toString();
} }
targeted_status = notification.status;
} }
} }
break; break;
@ -348,7 +379,7 @@ public class NotificationsHelper {
message = String.format("%s %s", notification.account.display_name, context.getString(R.string.notif_signed_up)); message = String.format("%s %s", notification.account.display_name, context.getString(R.string.notif_signed_up));
else else
message = String.format("@%s %s", notification.account.acct, context.getString(R.string.notif_signed_up)); message = String.format("@%s %s", notification.account.acct, context.getString(R.string.notif_signed_up));
targeted_account = notification.account.id; targeted_account = notification.account;
} }
break; break;
case "admin.report": case "admin.report":
@ -359,7 +390,7 @@ public class NotificationsHelper {
message = String.format("%s %s", notification.account.display_name, context.getString(R.string.notif_reported)); message = String.format("%s %s", notification.account.display_name, context.getString(R.string.notif_reported));
else else
message = String.format("@%s %s", notification.account.acct, context.getString(R.string.notif_reported)); message = String.format("@%s %s", notification.account.acct, context.getString(R.string.notif_reported));
targeted_account = notification.account.id; targeted_account = notification.account;
} }
break; break;
default: default:
@ -368,62 +399,48 @@ public class NotificationsHelper {
//Some others notification //Some others notification
final Intent intent = new Intent(context, MainActivity.class); final Intent intent = new Intent(context, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
Bundle args = new Bundle();
intent.putExtra(Helper.INTENT_ACTION, Helper.NOTIFICATION_INTENT); intent.putExtra(Helper.INTENT_ACTION, Helper.NOTIFICATION_INTENT);
intent.putExtra(Helper.PREF_USER_ID, account.user_id); intent.putExtra(Helper.PREF_USER_ID, account.user_id);
if (targeted_account != null) if (targeted_account != null) {
intent.putExtra(Helper.INTENT_TARGETED_ACCOUNT, targeted_account); args.putSerializable(Helper.INTENT_TARGETED_ACCOUNT, targeted_account);
intent.putExtra(Helper.PREF_USER_INSTANCE, account.instance); } else if (targeted_status != null) {
notificationUrl = notification.account.avatar; args.putSerializable(Helper.INTENT_TARGETED_STATUS, targeted_status);
Handler mainHandler = new Handler(Looper.getMainLooper()); }
final String finalNotificationUrl = notificationUrl; String finalMessage1 = message;
Helper.NotifType finalNotifType = notifType; String finalTitle1 = title;
String finalMessage = message; Helper.NotifType finalNotifType1 = notifType;
String finalTitle = title; new CachedBundle(context).insertBundle(args, account, bundleId -> {
StatusAdapter.sendAction(context, Helper.ARG_REFRESH_NOTFICATION, null, null); Bundle bundle = new Bundle();
Runnable myRunnable = () -> Glide.with(context) bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
.asBitmap() intent.putExtras(bundle);
.load(finalNotificationUrl != null ? finalNotificationUrl : R.drawable.fedilab_logo_bubbles) intent.putExtra(Helper.PREF_USER_INSTANCE, account.instance);
.listener(new RequestListener<Bitmap>() { Handler mainHandler = new Handler(Looper.getMainLooper());
final String finalNotificationUrl = notification.account.avatar;
StatusAdapter.sendAction(context, Helper.RECEIVE_REFRESH_NOTIFICATIONS_ACTION, null, null);
Runnable myRunnable = () -> Glide.with(context)
.asBitmap()
.load(finalNotificationUrl != null ? finalNotificationUrl : R.drawable.fedilab_logo_bubbles)
.into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
Helper.notify_user(context, account, intent, resource, finalNotifType1, finalTitle1, finalMessage1);
}
@Override @Override
public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) { public void onLoadFailed(@Nullable Drawable errorDrawable) {
return false; super.onLoadFailed(errorDrawable);
}
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) {
String lastNotif = prefs.getString(context.getString(R.string.LAST_NOTIFICATION_ID) + account.user_id + "@" + account.instance, null);
if (lastNotif == null || Helper.compareTo(notification.id, lastNotif) > 0) {
SharedPreferences.Editor editor = prefs.edit();
since_ids.put(account.user_id + "@" + account.instance, lastNotif);
editor.putString(context.getString(R.string.LAST_NOTIFICATION_ID) + account.user_id + "@" + account.instance, notifications.get(0).id);
editor.apply();
Helper.notify_user(context, account, intent, BitmapFactory.decodeResource(context.getResources(), Helper.notify_user(context, account, intent, BitmapFactory.decodeResource(context.getResources(),
getMainLogo(context)), finalNotifType, finalTitle, finalMessage); getMainLogo(context)), finalNotifType1, finalTitle1, finalMessage1);
} }
return false;
} @Override
}) public void onLoadCleared(@Nullable Drawable placeholder) {
.into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
String lastNotif = prefs.getString(context.getString(R.string.LAST_NOTIFICATION_ID) + account.user_id + "@" + account.instance, null);
if (lastNotif == null || Helper.compareTo(notification.id, lastNotif) > 0) {
SharedPreferences.Editor editor = prefs.edit();
editor.putString(context.getString(R.string.LAST_NOTIFICATION_ID) + account.user_id + "@" + account.instance, notifications.get(0).id);
editor.apply();
since_ids.put(account.user_id + "@" + account.instance, lastNotif);
Helper.notify_user(context, account, intent, resource, finalNotifType, finalTitle, finalMessage);
} }
} });
mainHandler.post(myRunnable);
@Override });
public void onLoadCleared(@Nullable Drawable placeholder) {
}
});
mainHandler.post(myRunnable);
} }
} }

View file

@ -15,12 +15,14 @@ package app.fedilab.android.mastodon.helper;
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentAccount;
import static app.fedilab.android.BaseMainActivity.currentInstance; import static app.fedilab.android.BaseMainActivity.currentInstance;
import static app.fedilab.android.BaseMainActivity.currentUserID; import static app.fedilab.android.BaseMainActivity.currentUserID;
import static app.fedilab.android.BaseMainActivity.show_boosts; import static app.fedilab.android.BaseMainActivity.show_boosts;
import static app.fedilab.android.BaseMainActivity.show_dms; import static app.fedilab.android.BaseMainActivity.show_dms;
import static app.fedilab.android.BaseMainActivity.show_my_messages;
import static app.fedilab.android.BaseMainActivity.show_replies; import static app.fedilab.android.BaseMainActivity.show_replies;
import static app.fedilab.android.BaseMainActivity.show_self_boosts;
import static app.fedilab.android.BaseMainActivity.show_self_replies;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -54,6 +56,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import app.fedilab.android.BaseMainActivity; import app.fedilab.android.BaseMainActivity;
@ -181,15 +184,18 @@ public class PinnedTimelineHelper {
} }
} }
} }
if (extraFeatures) {
try { //Create other default timelines
Pinned pinnedAll = new Pinned(activity).getAllPinned(currentAccount); try {
if (pinnedAll == null) { Pinned pinnedAll = new Pinned(activity).getAllPinned(Helper.getCurrentAccount(activity));
pinnedAll = new Pinned(); if (pinnedAll == null) {
pinnedAll.user_id = currentUserID; pinnedAll = new Pinned();
pinnedAll.instance = currentInstance; pinnedAll.user_id = currentUserID;
pinnedAll.pinnedTimelines = new ArrayList<>(); pinnedAll.instance = currentInstance;
} pinnedAll.pinnedTimelines = new ArrayList<>();
}
if (extraFeatures) {
//Bubble timeline
boolean createDefaultBubbleAtTop = true; boolean createDefaultBubbleAtTop = true;
for (PinnedTimeline pinnedTimeline : pinnedAll.pinnedTimelines) { for (PinnedTimeline pinnedTimeline : pinnedAll.pinnedTimelines) {
if (pinnedTimeline.type == Timeline.TimeLineEnum.BUBBLE) { if (pinnedTimeline.type == Timeline.TimeLineEnum.BUBBLE) {
@ -209,11 +215,34 @@ public class PinnedTimelineHelper {
new Pinned(activity).insertPinned(pinned); new Pinned(activity).insertPinned(pinned);
} }
} }
} catch (DBException e) {
e.printStackTrace();
} }
//Trend timeline
boolean createDefaultTrendAtTop = true;
for (PinnedTimeline pinnedTimeline : pinnedAll.pinnedTimelines) {
if (pinnedTimeline.type == Timeline.TimeLineEnum.TREND_MESSAGE) {
createDefaultTrendAtTop = false;
break;
}
}
if (createDefaultTrendAtTop) {
PinnedTimeline pinnedTimelineBubble = new PinnedTimeline();
pinnedTimelineBubble.type = Timeline.TimeLineEnum.TREND_MESSAGE;
pinnedTimelineBubble.position = pinnedAll.pinnedTimelines != null ? pinnedAll.pinnedTimelines.size() : 0;
pinned.pinnedTimelines.add(pinnedTimelineBubble);
boolean exist = new Pinned(activity).pinnedExist(pinned);
if (exist) {
new Pinned(activity).updatePinned(pinned);
} else {
new Pinned(activity).insertPinned(pinned);
}
}
} catch (DBException e) {
e.printStackTrace();
} }
sortPositionAsc(pinnedTimelines); sortPositionAsc(pinnedTimelines);
//Check if changes occurred, if mastodonLists is null it does need, because it is the first call to draw pinned //Check if changes occurred, if mastodonLists is null it does need, because it is the first call to draw pinned
boolean needRedraw = mastodonLists == null; boolean needRedraw = mastodonLists == null;
@ -259,7 +288,7 @@ public class PinnedTimelineHelper {
for (MastodonList mastodonList : mastodonLists) { for (MastodonList mastodonList : mastodonLists) {
boolean present = false; boolean present = false;
try { try {
Pinned pinnedAll = new Pinned(activity).getAllPinned(currentAccount); Pinned pinnedAll = new Pinned(activity).getAllPinned(Helper.getCurrentAccount(activity));
if (pinnedAll == null) { if (pinnedAll == null) {
pinnedAll = pinned; pinnedAll = pinned;
} }
@ -300,12 +329,18 @@ public class PinnedTimelineHelper {
} }
//Pinned tab position will start after BOTTOM_TIMELINE_COUNT (ie 5) //Pinned tab position will start after BOTTOM_TIMELINE_COUNT (ie 5)
activityMainBinding.tabLayout.removeAllTabs(); activityMainBinding.tabLayout.removeAllTabs();
if(activityMainBinding.viewPager.getAdapter() != null) {
activityMainBinding.viewPager.getAdapter().notifyDataSetChanged();
}
int toRemove = FedilabPageAdapter.BOTTOM_TIMELINE_COUNT; int toRemove = FedilabPageAdapter.BOTTOM_TIMELINE_COUNT;
if (!singleBar) { if (!singleBar) {
//Small hack to hide first tabs (they represent the item of the bottom menu) //Small hack to hide first tabs (they represent the item of the bottom menu)
toRemove = itemToRemoveInBottomMenu(activity); toRemove = itemToRemoveInBottomMenu(activity);
for (int i = 0; i < (FedilabPageAdapter.BOTTOM_TIMELINE_COUNT - toRemove); i++) { for (int i = 0; i < (FedilabPageAdapter.BOTTOM_TIMELINE_COUNT - toRemove); i++) {
activityMainBinding.tabLayout.addTab(activityMainBinding.tabLayout.newTab(), false); activityMainBinding.tabLayout.addTab(activityMainBinding.tabLayout.newTab(), false);
if(activityMainBinding.viewPager.getAdapter() != null) {
activityMainBinding.viewPager.getAdapter().notifyDataSetChanged();
}
((ViewGroup) activityMainBinding.tabLayout.getChildAt(0)).getChildAt(i).setVisibility(View.GONE); ((ViewGroup) activityMainBinding.tabLayout.getChildAt(0)).getChildAt(i).setVisibility(View.GONE);
} }
} }
@ -339,10 +374,10 @@ public class PinnedTimelineHelper {
break; break;
case REMOTE: case REMOTE:
name = pinnedTimeline.remoteInstance.host; name = pinnedTimeline.remoteInstance.host;
if (pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.NITTER) { if (pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.NITTER || pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.NITTER_TAG) {
String remoteInstance = sharedpreferences.getString(activity.getString(R.string.SET_NITTER_HOST), activity.getString(R.string.DEFAULT_NITTER_HOST)).toLowerCase(); String remoteInstance = sharedpreferences.getString(activity.getString(R.string.SET_NITTER_HOST), activity.getString(R.string.DEFAULT_NITTER_HOST)).toLowerCase();
//Custom name for Nitter instances //Custom name for Nitter instances
if (pinnedTimeline.remoteInstance.displayName != null && pinnedTimeline.remoteInstance.displayName.trim().length() > 0) { if (pinnedTimeline.remoteInstance.displayName != null && !pinnedTimeline.remoteInstance.displayName.trim().isEmpty()) {
name = pinnedTimeline.remoteInstance.displayName; name = pinnedTimeline.remoteInstance.displayName;
} }
ident = "@R@" + remoteInstance; ident = "@R@" + remoteInstance;
@ -370,10 +405,13 @@ public class PinnedTimelineHelper {
case MASTODON: case MASTODON:
tabCustomViewBinding.icon.setImageResource(R.drawable.mastodon_icon_item); tabCustomViewBinding.icon.setImageResource(R.drawable.mastodon_icon_item);
break; break;
case LEMMY:
tabCustomViewBinding.icon.setImageResource(R.drawable.lemmy);
break;
case MISSKEY: case MISSKEY:
tabCustomViewBinding.icon.setImageResource(R.drawable.misskey); tabCustomViewBinding.icon.setImageResource(R.drawable.misskey);
break; break;
case NITTER_TAG:
case NITTER: case NITTER:
tabCustomViewBinding.icon.setImageResource(R.drawable.nitter); tabCustomViewBinding.icon.setImageResource(R.drawable.nitter);
break; break;
@ -408,6 +446,9 @@ public class PinnedTimelineHelper {
case BUBBLE: case BUBBLE:
tabCustomDefaultViewBinding.icon.setImageResource(R.drawable.ic_baseline_bubble_chart_24); tabCustomDefaultViewBinding.icon.setImageResource(R.drawable.ic_baseline_bubble_chart_24);
break; break;
case TREND_MESSAGE:
tabCustomDefaultViewBinding.icon.setImageResource(R.drawable.baseline_moving_24);
break;
} }
tab.setCustomView(tabCustomDefaultViewBinding.getRoot()); tab.setCustomView(tabCustomDefaultViewBinding.getRoot());
} }
@ -415,6 +456,9 @@ public class PinnedTimelineHelper {
String slug = pinnedTimeline.type.getValue() + (ident != null ? "|" + ident : ""); String slug = pinnedTimeline.type.getValue() + (ident != null ? "|" + ident : "");
tab.setTag(slug); tab.setTag(slug);
activityMainBinding.tabLayout.addTab(tab, false); activityMainBinding.tabLayout.addTab(tab, false);
if(activityMainBinding.viewPager.getAdapter() != null){
activityMainBinding.viewPager.getAdapter().notifyDataSetChanged();
}
pinnedTimelineVisibleList.add(pinnedTimeline); pinnedTimelineVisibleList.add(pinnedTimeline);
} }
} }
@ -461,12 +505,16 @@ public class PinnedTimelineHelper {
case MISSKEY: case MISSKEY:
item.setIcon(R.drawable.misskey); item.setIcon(R.drawable.misskey);
break; break;
case LEMMY:
item.setIcon(R.drawable.lemmy);
break;
case PIXELFED: case PIXELFED:
item.setIcon(R.drawable.pixelfed); item.setIcon(R.drawable.pixelfed);
break; break;
case NITTER_TAG:
case NITTER: case NITTER:
item.setIcon(R.drawable.nitter); item.setIcon(R.drawable.nitter);
if (pinnedTimeline.remoteInstance.displayName != null && pinnedTimeline.remoteInstance.displayName.trim().length() > 0) { if (pinnedTimeline.remoteInstance.displayName != null && !pinnedTimeline.remoteInstance.displayName.trim().isEmpty()) {
item.setTitle(pinnedTimeline.remoteInstance.displayName); item.setTitle(pinnedTimeline.remoteInstance.displayName);
} else { } else {
item.setTitle(pinnedTimeline.remoteInstance.host); item.setTitle(pinnedTimeline.remoteInstance.host);
@ -518,7 +566,7 @@ public class PinnedTimelineHelper {
bubbleClick(activity, finalPinned, v, activityMainBinding, finalI, activityMainBinding.tabLayout.getTabAt(finalI).getTag().toString()); bubbleClick(activity, finalPinned, v, activityMainBinding, finalI, activityMainBinding.tabLayout.getTabAt(finalI).getTag().toString());
break; break;
case REMOTE: case REMOTE:
if (pinnedTimelineVisibleList.get(position).remoteInstance.type != RemoteInstance.InstanceType.NITTER) { if (pinnedTimelineVisibleList.get(position).remoteInstance.type != RemoteInstance.InstanceType.NITTER && pinnedTimelineVisibleList.get(position).remoteInstance.type != RemoteInstance.InstanceType.NITTER_TAG) {
instanceClick(activity, finalPinned, v, activityMainBinding, finalI, activityMainBinding.tabLayout.getTabAt(finalI).getTag().toString()); instanceClick(activity, finalPinned, v, activityMainBinding, finalI, activityMainBinding.tabLayout.getTabAt(finalI).getTag().toString());
} else { } else {
nitterClick(activity, finalPinned, v, activityMainBinding, finalI, activityMainBinding.tabLayout.getTabAt(finalI).getTag().toString()); nitterClick(activity, finalPinned, v, activityMainBinding, finalI, activityMainBinding.tabLayout.getTabAt(finalI).getTag().toString());
@ -526,6 +574,7 @@ public class PinnedTimelineHelper {
break; break;
case HOME: case HOME:
case LOCAL: case LOCAL:
case TREND_MESSAGE:
case PUBLIC: case PUBLIC:
defaultClick(activity, pinnedTimelineVisibleList.get(position).type, v, activityMainBinding, finalI); defaultClick(activity, pinnedTimelineVisibleList.get(position).type, v, activityMainBinding, finalI);
break; break;
@ -539,6 +588,7 @@ public class PinnedTimelineHelper {
activityMainBinding.tabLayout.clearOnTabSelectedListeners(); activityMainBinding.tabLayout.clearOnTabSelectedListeners();
FedilabPageAdapter fedilabPageAdapter = new FedilabPageAdapter(activity, activity.getSupportFragmentManager(), pinned, bottomMenu); FedilabPageAdapter fedilabPageAdapter = new FedilabPageAdapter(activity, activity.getSupportFragmentManager(), pinned, bottomMenu);
activityMainBinding.viewPager.setAdapter(fedilabPageAdapter); activityMainBinding.viewPager.setAdapter(fedilabPageAdapter);
activityMainBinding.viewPager.setOffscreenPageLimit(tabStrip.getChildCount()); activityMainBinding.viewPager.setOffscreenPageLimit(tabStrip.getChildCount());
activityMainBinding.viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(activityMainBinding.tabLayout)); activityMainBinding.viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(activityMainBinding.tabLayout));
if (!singleBar) { if (!singleBar) {
@ -628,16 +678,25 @@ public class PinnedTimelineHelper {
.inflate(R.menu.option_filter_toots, popup.getMenu()); .inflate(R.menu.option_filter_toots, popup.getMenu());
Menu menu = popup.getMenu(); Menu menu = popup.getMenu();
final MenuItem itemShowBoosts = menu.findItem(R.id.action_show_boosts); final MenuItem itemShowBoosts = menu.findItem(R.id.action_show_boosts);
final MenuItem itemShowMyMessages = menu.findItem(R.id.action_show_my_messages);
final MenuItem itemShowSelfBoosts = menu.findItem(R.id.action_show_self_boosts);
final MenuItem itemShowReplies = menu.findItem(R.id.action_show_replies); final MenuItem itemShowReplies = menu.findItem(R.id.action_show_replies);
final MenuItem itemShowSelfReplies = menu.findItem(R.id.action_show_self_replies);
final MenuItem itemShowDMs = menu.findItem(R.id.action_show_dms); final MenuItem itemShowDMs = menu.findItem(R.id.action_show_dms);
final MenuItem itemFilter = menu.findItem(R.id.action_filter); final MenuItem itemFilter = menu.findItem(R.id.action_filter);
if (!showExtendedFilter) { if (!showExtendedFilter) {
itemShowBoosts.setVisible(false); itemShowBoosts.setVisible(false);
itemShowReplies.setVisible(false); itemShowReplies.setVisible(false);
itemShowSelfBoosts.setVisible(false);
itemShowSelfReplies.setVisible(false);
itemShowMyMessages.setVisible(false);
itemShowDMs.setVisible(false); itemShowDMs.setVisible(false);
} else { } else {
itemShowBoosts.setVisible(true); itemShowBoosts.setVisible(true);
itemShowReplies.setVisible(true); itemShowReplies.setVisible(true);
itemShowSelfBoosts.setVisible(true);
itemShowSelfReplies.setVisible(true);
itemShowMyMessages.setVisible(true);
itemShowDMs.setVisible(true); itemShowDMs.setVisible(true);
} }
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(activity); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(activity);
@ -651,16 +710,18 @@ public class PinnedTimelineHelper {
} }
itemShowBoosts.setChecked(show_boosts); itemShowBoosts.setChecked(show_boosts);
itemShowMyMessages.setChecked(show_my_messages);
itemShowReplies.setChecked(show_replies); itemShowReplies.setChecked(show_replies);
itemShowSelfBoosts.setChecked(show_self_boosts);
itemShowSelfReplies.setChecked(show_self_replies);
itemShowDMs.setChecked(show_dms); itemShowDMs.setChecked(show_dms);
if (show_filtered != null && show_filtered.length() > 0) { if (show_filtered != null && show_filtered.length() > 0) {
itemFilter.setTitle(show_filtered); itemFilter.setTitle(show_filtered);
} }
popup.setOnDismissListener(menu1 -> { popup.setOnDismissListener(menu1 -> {
if (activityMainBinding.viewPager.getAdapter() != null) { if (activityMainBinding.viewPager.getAdapter() != null && activityMainBinding.tabLayout.getSelectedTabPosition() != -1) {
Fragment fragment = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition()); Fragment fragment = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition());
if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) { if (fragment instanceof FragmentMastodonTimeline fragmentMastodonTimeline && fragment.isVisible()) {
FragmentMastodonTimeline fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
fragmentMastodonTimeline.refreshAllAdapters(); fragmentMastodonTimeline.refreshAllAdapters();
} }
} }
@ -687,11 +748,26 @@ public class PinnedTimelineHelper {
editor.putBoolean(activity.getString(R.string.SET_SHOW_BOOSTS) + currentUserID + currentInstance, show_boosts); editor.putBoolean(activity.getString(R.string.SET_SHOW_BOOSTS) + currentUserID + currentInstance, show_boosts);
itemShowBoosts.setChecked(show_boosts); itemShowBoosts.setChecked(show_boosts);
editor.apply(); editor.apply();
} else if (itemId == R.id.action_show_my_messages) {
show_my_messages = !show_my_messages;
editor.putBoolean(activity.getString(R.string.SET_SHOW_MY_MESSAGES) + currentUserID + currentInstance, show_my_messages);
itemShowMyMessages.setChecked(show_my_messages);
editor.apply();
} else if (itemId == R.id.action_show_self_boosts) {
show_self_boosts = !show_self_boosts;
editor.putBoolean(activity.getString(R.string.SET_SHOW_SELF_BOOSTS) + currentUserID + currentInstance, show_self_boosts);
itemShowSelfBoosts.setChecked(show_self_boosts);
editor.apply();
} else if (itemId == R.id.action_show_replies) { } else if (itemId == R.id.action_show_replies) {
show_replies = !show_replies; show_replies = !show_replies;
editor.putBoolean(activity.getString(R.string.SET_SHOW_REPLIES) + currentUserID + currentInstance, show_replies); editor.putBoolean(activity.getString(R.string.SET_SHOW_REPLIES) + currentUserID + currentInstance, show_replies);
itemShowReplies.setChecked(show_replies); itemShowReplies.setChecked(show_replies);
editor.apply(); editor.apply();
} else if (itemId == R.id.action_show_self_replies) {
show_self_replies = !show_self_replies;
editor.putBoolean(activity.getString(R.string.SET_SHOW_SELF_REPLIES) + currentUserID + currentInstance, show_self_replies);
itemShowSelfReplies.setChecked(show_self_replies);
editor.apply();
} else if (itemId == R.id.action_show_dms) { } else if (itemId == R.id.action_show_dms) {
show_dms = !show_dms; show_dms = !show_dms;
editor.putBoolean(activity.getString(R.string.SET_SHOW_DMS) + currentUserID + currentInstance, show_dms); editor.putBoolean(activity.getString(R.string.SET_SHOW_DMS) + currentUserID + currentInstance, show_dms);
@ -759,7 +835,7 @@ public class PinnedTimelineHelper {
int toRemove = 0; int toRemove = 0;
try { try {
//If some menu items have been hidden we should not create tab for them //If some menu items have been hidden we should not create tab for them
bottomMenuDb = new BottomMenu(activity).getAllBottomMenu(currentAccount); bottomMenuDb = new BottomMenu(activity).getAllBottomMenu(Helper.getCurrentAccount(activity));
if (bottomMenuDb != null) { if (bottomMenuDb != null) {
List<BottomMenu.MenuItem> menuItemList = bottomMenuDb.bottom_menu; List<BottomMenu.MenuItem> menuItemList = bottomMenuDb.bottom_menu;
if (menuItemList != null) { if (menuItemList != null) {
@ -830,19 +906,21 @@ public class PinnedTimelineHelper {
SharedPreferences.Editor editor = sharedpreferences.edit(); SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(activity.getString(R.string.SET_INNER_MARKER) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance + slug, null); editor.putString(activity.getString(R.string.SET_INNER_MARKER) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance + slug, null);
editor.commit(); editor.commit();
Fragment fragmentMastodonTimeline = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition()); if (activityMainBinding.tabLayout.getSelectedTabPosition() != -1) {
if (fragmentMastodonTimeline instanceof FragmentMastodonTimeline && fragmentMastodonTimeline.isVisible()) { Fragment fragmentMastodonTimeline = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition());
FragmentTransaction fragTransaction = activity.getSupportFragmentManager().beginTransaction(); if (fragmentMastodonTimeline instanceof FragmentMastodonTimeline && fragmentMastodonTimeline.isVisible()) {
fragTransaction.detach(fragmentMastodonTimeline).commit(); FragmentTransaction fragTransaction = activity.getSupportFragmentManager().beginTransaction();
Bundle bundle = new Bundle(); fragTransaction.detach(fragmentMastodonTimeline).commit();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.TAG); Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_TAG_TIMELINE, tagTimeline); bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.TAG);
bundle.putSerializable(Helper.ARG_INITIALIZE_VIEW, false); bundle.putSerializable(Helper.ARG_TAG_TIMELINE, tagTimeline);
fragmentMastodonTimeline.setArguments(bundle); bundle.putSerializable(Helper.ARG_INITIALIZE_VIEW, false);
FragmentTransaction fragTransaction2 = activity.getSupportFragmentManager().beginTransaction(); fragmentMastodonTimeline.setArguments(bundle);
fragTransaction2.attach(fragmentMastodonTimeline); FragmentTransaction fragTransaction2 = activity.getSupportFragmentManager().beginTransaction();
fragTransaction2.commit(); fragTransaction2.attach(fragmentMastodonTimeline);
((FragmentMastodonTimeline) fragmentMastodonTimeline).recreate(); fragTransaction2.commit();
((FragmentMastodonTimeline) fragmentMastodonTimeline).recreate();
}
} }
} }
} }
@ -1058,19 +1136,21 @@ public class PinnedTimelineHelper {
SharedPreferences.Editor editor = sharedpreferences.edit(); SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(activity.getString(R.string.SET_INNER_MARKER) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance + slug, null); editor.putString(activity.getString(R.string.SET_INNER_MARKER) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance + slug, null);
editor.commit(); editor.commit();
Fragment fragmentMastodonTimeline = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition()); if (activityMainBinding.tabLayout.getSelectedTabPosition() != -1) {
if (fragmentMastodonTimeline instanceof FragmentMastodonTimeline && fragmentMastodonTimeline.isVisible()) { Fragment fragmentMastodonTimeline = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition());
FragmentTransaction fragTransaction = activity.getSupportFragmentManager().beginTransaction(); if (fragmentMastodonTimeline instanceof FragmentMastodonTimeline && fragmentMastodonTimeline.isVisible()) {
fragTransaction.detach(fragmentMastodonTimeline).commit(); FragmentTransaction fragTransaction = activity.getSupportFragmentManager().beginTransaction();
Bundle bundle = new Bundle(); fragTransaction.detach(fragmentMastodonTimeline).commit();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.BUBBLE); Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_BUBBLE_TIMELINE, bubbleTimeline); bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.BUBBLE);
bundle.putSerializable(Helper.ARG_INITIALIZE_VIEW, false); bundle.putSerializable(Helper.ARG_BUBBLE_TIMELINE, bubbleTimeline);
fragmentMastodonTimeline.setArguments(bundle); bundle.putSerializable(Helper.ARG_INITIALIZE_VIEW, false);
FragmentTransaction fragTransaction2 = activity.getSupportFragmentManager().beginTransaction(); fragmentMastodonTimeline.setArguments(bundle);
fragTransaction2.attach(fragmentMastodonTimeline); FragmentTransaction fragTransaction2 = activity.getSupportFragmentManager().beginTransaction();
fragTransaction2.commit(); fragTransaction2.attach(fragmentMastodonTimeline);
((FragmentMastodonTimeline) fragmentMastodonTimeline).recreate(); fragTransaction2.commit();
((FragmentMastodonTimeline) fragmentMastodonTimeline).recreate();
}
} }
} }
} }
@ -1301,7 +1381,7 @@ public class PinnedTimelineHelper {
} catch (DBException e) { } catch (DBException e) {
e.printStackTrace(); e.printStackTrace();
} }
if (activityMainBinding.viewPager.getAdapter() != null) { if (activityMainBinding.viewPager.getAdapter() != null && activityMainBinding.tabLayout.getSelectedTabPosition() != -1) {
Fragment fragment = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition()); Fragment fragment = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition());
if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) { if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) {
fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment); fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
@ -1349,7 +1429,7 @@ public class PinnedTimelineHelper {
int finalOffSetPosition1 = offSetPosition; int finalOffSetPosition1 = offSetPosition;
item.setOnMenuItemClickListener(item1 -> { item.setOnMenuItemClickListener(item1 -> {
FragmentMastodonTimeline fragmentMastodonTimeline = null; FragmentMastodonTimeline fragmentMastodonTimeline = null;
if (activityMainBinding.viewPager.getAdapter() != null) { if (activityMainBinding.viewPager.getAdapter() != null && activityMainBinding.tabLayout.getSelectedTabPosition() != -1) {
Fragment fragment = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition()); Fragment fragment = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition());
if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) { if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) {
fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment); fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
@ -1442,7 +1522,7 @@ public class PinnedTimelineHelper {
} catch (DBException e) { } catch (DBException e) {
e.printStackTrace(); e.printStackTrace();
} }
if (activityMainBinding.viewPager.getAdapter() != null) { if (activityMainBinding.viewPager.getAdapter() != null && activityMainBinding.tabLayout.getSelectedTabPosition() != -1) {
Fragment fragment = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition()); Fragment fragment = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition());
if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) { if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) {
fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment); fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
@ -1490,6 +1570,12 @@ public class PinnedTimelineHelper {
PopupMenu popup = new PopupMenu(activity, view); PopupMenu popup = new PopupMenu(activity, view);
popup.getMenuInflater() popup.getMenuInflater()
.inflate(R.menu.option_nitter_timeline, popup.getMenu()); .inflate(R.menu.option_nitter_timeline, popup.getMenu());
if(remoteInstance.type == RemoteInstance.InstanceType.NITTER_TAG) {
MenuItem item = popup.getMenu().findItem(R.id.action_nitter_manage_accounts);
if(item != null) {
item.setTitle(R.string.manage_tags);
}
}
int finalOffSetPosition = offSetPosition; int finalOffSetPosition = offSetPosition;
popup.setOnMenuItemClickListener(item -> { popup.setOnMenuItemClickListener(item -> {
int itemId = item.getItemId(); int itemId = item.getItemId();
@ -1509,7 +1595,7 @@ public class PinnedTimelineHelper {
} }
dialogBuilder.setPositiveButton(R.string.validate, (dialog, id) -> { dialogBuilder.setPositiveButton(R.string.validate, (dialog, id) -> {
String values = editTextName.getText().toString(); String values = editTextName.getText().toString();
if (values.trim().length() == 0) { if (values.trim().isEmpty()) {
values = remoteInstance.displayName; values = remoteInstance.displayName;
} }
remoteInstance.displayName = values; remoteInstance.displayName = values;
@ -1551,7 +1637,7 @@ public class PinnedTimelineHelper {
} catch (DBException e) { } catch (DBException e) {
e.printStackTrace(); e.printStackTrace();
} }
if (activityMainBinding.viewPager.getAdapter() != null) { if (activityMainBinding.viewPager.getAdapter() != null && activityMainBinding.tabLayout.getSelectedTabPosition() != -1) {
Fragment fragment = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition()); Fragment fragment = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition());
if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) { if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) {
fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment); fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);

View file

@ -0,0 +1,46 @@
package app.fedilab.android.mastodon.helper;
/* Copyright 2024 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import java.util.ArrayList;
import java.util.List;
public class PronounsHelper {
public static List<String> pronouns = new ArrayList<>() {
{
add("pronoun");
add("pronouns");
add("pronoms");
add("pronom");
add("pronombres");
add("לשון פנייה");
add("כינויי גוף");
add("pronomen");
add("pronomina");
add("称谓");
add("pronomes");
add("pronome");
add("pronomi");
add("pronomoj");
add("zaimki");
add("voornaamwoorden");
add("riochdairean");
add("כינויי גוף");
add("personlig pronomen");
add("人称");
add("人稱");
}
};
}

View file

@ -35,20 +35,26 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.unifiedpush.android.connector.UnifiedPush; import org.unifiedpush.android.connector.UnifiedPush;
import java.util.ArrayList; import java.net.IDN;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.mastodon.client.endpoints.MastodonInstanceService;
import app.fedilab.android.mastodon.client.entities.api.InstanceV2;
import app.fedilab.android.mastodon.client.entities.app.Account; import app.fedilab.android.mastodon.client.entities.app.Account;
import app.fedilab.android.mastodon.client.entities.app.BaseAccount; import app.fedilab.android.mastodon.client.entities.app.BaseAccount;
import app.fedilab.android.mastodon.jobs.NotificationsWorker; import app.fedilab.android.mastodon.jobs.NotificationsWorker;
import okhttp3.OkHttpClient;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class PushHelper { public class PushHelper {
public static void startStreaming(Context context) { public static void startStreaming(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
String typeOfNotification = prefs.getString(context.getString(R.string.SET_NOTIFICATION_TYPE), "PUSH_NOTIFICATIONS"); String typeOfNotification = prefs.getString(context.getString(R.string.SET_NOTIFICATION_TYPE), "PUSH_NOTIFICATIONS");
switch (typeOfNotification) { switch (typeOfNotification) {
@ -57,12 +63,12 @@ public class PushHelper {
List<BaseAccount> accounts = new Account(context).getPushNotificationAccounts(); List<BaseAccount> accounts = new Account(context).getPushNotificationAccounts();
Handler mainHandler = new Handler(Looper.getMainLooper()); Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> { Runnable myRunnable = () -> {
List<String> distributors = UnifiedPush.getDistributors(context, new ArrayList<>()); List<String> distributors = UnifiedPush.getDistributors(context);
if (distributors.size() == 0) { if (distributors.isEmpty()) {
AlertDialog.Builder alert = new MaterialAlertDialogBuilder(context); AlertDialog.Builder alert = new MaterialAlertDialogBuilder(context);
alert.setTitle(R.string.no_distributors_found); alert.setTitle(R.string.no_distributors_found);
final TextView message = new TextView(context); final TextView message = new TextView(context);
String link = "https://fedilab.app/wiki/features/push-notifications/"; String link = "https://fedilab.app/docs/fedilab/faq/";
final SpannableString s = final SpannableString s =
new SpannableString(context.getString(R.string.no_distributors_explanation, link)); new SpannableString(context.getString(R.string.no_distributors_explanation, link));
Linkify.addLinks(s, Linkify.WEB_URLS); Linkify.addLinks(s, Linkify.WEB_URLS);
@ -94,8 +100,10 @@ public class PushHelper {
WorkManager.getInstance(context).cancelAllWorkByTag(Helper.WORKER_REFRESH_NOTIFICATION); WorkManager.getInstance(context).cancelAllWorkByTag(Helper.WORKER_REFRESH_NOTIFICATION);
new Thread(() -> { new Thread(() -> {
List<BaseAccount> accounts = new Account(context).getPushNotificationAccounts(); List<BaseAccount> accounts = new Account(context).getPushNotificationAccounts();
for (BaseAccount account : accounts) { if(accounts != null) {
((Activity) context).runOnUiThread(() -> UnifiedPush.unregisterApp(context, account.user_id + "@" + account.instance)); for (BaseAccount account : accounts) {
((Activity) context).runOnUiThread(() -> UnifiedPush.unregister(context, account.user_id + "@" + account.instance));
}
} }
}).start(); }).start();
break; break;
@ -108,7 +116,7 @@ public class PushHelper {
List<BaseAccount> accounts = new Account(context).getPushNotificationAccounts(); List<BaseAccount> accounts = new Account(context).getPushNotificationAccounts();
if (accounts != null) { if (accounts != null) {
for (BaseAccount account : accounts) { for (BaseAccount account : accounts) {
((Activity) context).runOnUiThread(() -> UnifiedPush.unregisterApp(context, account.user_id + "@" + account.instance)); ((Activity) context).runOnUiThread(() -> UnifiedPush.unregister(context, account.user_id + "@" + account.instance));
} }
} }
}).start(); }).start();
@ -125,28 +133,49 @@ public class PushHelper {
if (accounts == null) { if (accounts == null) {
return; return;
} }
List<String> distributors = UnifiedPush.getDistributors(context, new ArrayList<>()); List<String> distributors = UnifiedPush.getDistributors(context);
if (distributors.size() == 1 || !UnifiedPush.getDistributor(context).isEmpty()) { if (!distributors.isEmpty()) {
if (distributors.size() == 1) { if (distributors.size() == 1) {
UnifiedPush.saveDistributor(context, distributors.get(0)); UnifiedPush.saveDistributor(context, distributors.get(0));
} }
final OkHttpClient okHttpClient = Helper.myOkHttpClient(context.getApplicationContext());
for (BaseAccount account : accounts) { for (BaseAccount account : accounts) {
UnifiedPush.registerApp(context, account.user_id + "@" + account.instance, new ArrayList<>(), "");
}
return;
}
AlertDialog.Builder alert = new MaterialAlertDialogBuilder(context); new Thread(()->{
alert.setTitle(R.string.select_distributors); Retrofit retrofit = new Retrofit.Builder()
String[] distributorsStr = distributors.toArray(new String[0]); .baseUrl("https://" + (account.instance != null ? IDN.toASCII(account.instance, IDN.ALLOW_UNASSIGNED) : null) + "/api/v2/")
alert.setSingleChoiceItems(distributorsStr, -1, (dialog, item) -> { .addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder()))
String distributor = distributorsStr[item]; .client(okHttpClient)
UnifiedPush.saveDistributor(context, distributor); .build();
for (BaseAccount account : accounts) { MastodonInstanceService mastodonInstanceService = retrofit.create(MastodonInstanceService.class);
UnifiedPush.registerApp(context, account.user_id + "@" + account.instance, new ArrayList<>(), ""); Call<InstanceV2> instanceV2Call = mastodonInstanceService.instanceV2();
String vapid = null;
if (instanceV2Call != null) {
try {
Response<InstanceV2> instanceResponse = instanceV2Call.execute();
if (instanceResponse.isSuccessful()) {
InstanceV2 instanceV2 = instanceResponse.body();
if (instanceV2 != null && instanceV2.configuration.vapId != null) {
vapid = instanceV2.configuration.vapId.publicKey;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
Handler mainHandler = new Handler(Looper.getMainLooper());
String finalVapid = vapid!=null?vapid.replaceAll("=",""):null;
Runnable myRunnable = () -> {
try {
UnifiedPush.register(context, account.user_id + "@" + account.instance, null, finalVapid);
}catch (Exception e){
e.printStackTrace();
}
};
mainHandler.post(myRunnable);
}).start();
} }
dialog.dismiss(); }
});
alert.show();
} }
} }

View file

@ -15,15 +15,19 @@ package app.fedilab.android.mastodon.helper;
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import java.util.concurrent.TimeUnit; import org.unifiedpush.android.connector.data.PushEndpoint;
import java.net.IDN;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.mastodon.client.endpoints.MastodonNotificationsService; import app.fedilab.android.mastodon.client.endpoints.MastodonNotificationsService;
@ -40,23 +44,31 @@ import retrofit2.converter.gson.GsonConverterFactory;
public class PushNotifications { public class PushNotifications {
public static void unregisterPushNotifications(Context context, String slug) {
new Thread(() -> {
String[] slugArray = slug.split("@");
BaseAccount accountDb = null;
try {
accountDb = new Account(context).getUniqAccount(slugArray[0], slugArray[1]);
} catch (DBException e) {
e.printStackTrace();
}
public static void registerPushNotifications(Context context, String endpoint, String slug) { if (accountDb == null) {
return;
}
MastodonNotificationsService mastodonNotificationsService = init(context, accountDb.instance);
mastodonNotificationsService.deletePushsubscription(accountDb.token);
});
}
public static void registerPushNotifications(Context context, PushEndpoint pushEndpoint, String slug) {
SharedPreferences prefs = PreferenceManager SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context); .getDefaultSharedPreferences(context);
ECDHFedilab ecdh = null;
try {
ecdh = new ECDHFedilab(context, slug);
} catch (Exception e) {
e.printStackTrace();
}
if (ecdh == null) {
return;
}
String pubKey = ecdh.getPublicKey(); String pubKey = pushEndpoint.getPubKeySet().getPubKey();
String auth = ecdh.getAuthKey(); String auth =pushEndpoint.getPubKeySet().getAuth();
boolean notif_follow = prefs.getBoolean(context.getString(R.string.SET_NOTIF_FOLLOW), true); boolean notif_follow = prefs.getBoolean(context.getString(R.string.SET_NOTIF_FOLLOW), true);
@ -84,9 +96,10 @@ public class PushNotifications {
PushSubscription pushSubscription; PushSubscription pushSubscription;
Call<PushSubscription> pushSubscriptionCall = mastodonNotificationsService.pushSubscription( Call<PushSubscription> pushSubscriptionCall = mastodonNotificationsService.pushSubscription(
accountDb.token, accountDb.token,
endpoint, pushEndpoint.getUrl(),
pubKey, pubKey,
auth, auth,
true,
notif_follow, notif_follow,
notif_fav, notif_fav,
notif_share, notif_share,
@ -95,12 +108,13 @@ public class PushNotifications {
notif_status, notif_status,
notif_updates, notif_updates,
notif_signup, notif_signup,
notif_report); notif_report, "all");
if (pushSubscriptionCall != null) { if (pushSubscriptionCall != null) {
try { try {
Response<PushSubscription> pushSubscriptionResponse = pushSubscriptionCall.execute(); Response<PushSubscription> pushSubscriptionResponse = pushSubscriptionCall.execute();
if (pushSubscriptionResponse.isSuccessful()) { if (pushSubscriptionResponse.isSuccessful()) {
pushSubscription = pushSubscriptionResponse.body(); pushSubscription = pushSubscriptionResponse.body();
if (pushSubscription != null) { if (pushSubscription != null) {
pushSubscription.server_key = pushSubscription.server_key.replace('/', '_'); pushSubscription.server_key = pushSubscription.server_key.replace('/', '_');
pushSubscription.server_key = pushSubscription.server_key.replace('+', '-'); pushSubscription.server_key = pushSubscription.server_key.replace('+', '-');
@ -130,14 +144,10 @@ public class PushNotifications {
slug + "/unifiedpush.connector", null); slug + "/unifiedpush.connector", null);
} }
private static MastodonNotificationsService init(@NonNull Context context, @NonNull String instance) { private static MastodonNotificationsService init(@NonNull Context context, String instance) {
final OkHttpClient okHttpClient = new OkHttpClient.Builder() final OkHttpClient okHttpClient = Helper.myOkHttpClient(context);
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.proxy(Helper.getProxy(context.getApplicationContext()))
.build();
Retrofit retrofit = new Retrofit.Builder() Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + instance + "/api/v1/") .baseUrl("https://" + (instance != null ? IDN.toASCII(instance, IDN.ALLOW_UNASSIGNED) : null) + "/api/v1/")
.addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder())) .addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder()))
.client(okHttpClient) .client(okHttpClient)
.build(); .build();

View file

@ -15,7 +15,7 @@ package app.fedilab.android.mastodon.helper;
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentAccount; import static app.fedilab.android.BaseMainActivity.currentNightMode;
import android.app.Activity; import android.app.Activity;
import android.content.ClipData; import android.content.ClipData;
@ -71,6 +71,7 @@ import java.util.regex.Pattern;
import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.HttpsURLConnection;
import app.fedilab.android.BaseMainActivity; import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.MySuperGrammerLocator;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity; import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.databinding.PopupLinksBinding; import app.fedilab.android.databinding.PopupLinksBinding;
@ -84,30 +85,66 @@ import app.fedilab.android.mastodon.client.entities.api.Emoji;
import app.fedilab.android.mastodon.client.entities.api.Filter; import app.fedilab.android.mastodon.client.entities.api.Filter;
import app.fedilab.android.mastodon.client.entities.api.Mention; import app.fedilab.android.mastodon.client.entities.api.Mention;
import app.fedilab.android.mastodon.client.entities.api.Status; import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.client.entities.app.MarkdownConverter;
import app.fedilab.android.mastodon.ui.drawer.StatusAdapter; import app.fedilab.android.mastodon.ui.drawer.StatusAdapter;
import app.fedilab.android.mastodon.viewmodel.mastodon.FiltersVM; import app.fedilab.android.mastodon.viewmodel.mastodon.FiltersVM;
import es.dmoral.toasty.Toasty; import es.dmoral.toasty.Toasty;
import io.noties.markwon.AbstractMarkwonPlugin;
import io.noties.markwon.Markwon;
import io.noties.markwon.SoftBreakAddsNewLinePlugin;
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
import io.noties.markwon.ext.tables.TablePlugin;
import io.noties.markwon.inlineparser.HtmlInlineProcessor;
import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin;
import io.noties.markwon.syntax.Prism4jThemeDefault;
import io.noties.markwon.syntax.SyntaxHighlightPlugin;
import io.noties.prism4j.Prism4j;
public class SpannableHelper { public class SpannableHelper {
public static final String CLICKABLE_SPAN = "CLICKABLE_SPAN"; public static final String CLICKABLE_SPAN = "CLICKABLE_SPAN";
private static int linkColor; private static int linkColor;
private static boolean underlineLinks;
public static Spannable convert(Context context, String text, private static final String patternBottomTags = "\\n{2,}((#[\\w_À-ú-]+)(\\s*| *))+$";
Status status, Account account, Announcement announcement, WeakReference<View> viewWeakReference) {
return convert(context, text, status, account, announcement, viewWeakReference, null); public static String[] hasBottomTags(String text) {
if (text == null) {
return new String[]{};
}
SpannableString initialContent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
initialContent = new SpannableString(Html.fromHtml(text, Html.FROM_HTML_MODE_LEGACY));
} else {
initialContent = new SpannableString(Html.fromHtml(text));
}
final Pattern bottomTagsPattern = Pattern.compile(patternBottomTags, Pattern.CASE_INSENSITIVE);
Matcher matcherBottomTags = bottomTagsPattern.matcher(initialContent);
String[] tags = new String[]{};
while (matcherBottomTags.find()) {
String stringTags = Objects.requireNonNull(matcherBottomTags.group()).trim();
tags = stringTags.split("\\s");
}
return tags;
} }
public static Spannable convert(Context context, String text, public static Spannable convert(Context context, String text,
Status status, Account account, Announcement announcement, Status status, Account account, Announcement announcement,
WeakReference<View> viewWeakReference, Status.Callback callback) { WeakReference<View> viewWeakReference, Status.Callback callback, boolean convertHtml, boolean convertMarkdown) {
return convert(context, text, status, account, announcement, false, viewWeakReference, callback, convertHtml, convertMarkdown);
}
public static Spannable convert(Context context, String text,
Status status, Account account, Announcement announcement, boolean checkRemotely,
WeakReference<View> viewWeakReference, Status.Callback callback, boolean convertHtml, boolean convertMarkdown) {
if (text == null) { if (text == null) {
return null; return null;
} }
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
int currentNightMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
boolean customLight = sharedpreferences.getBoolean(context.getString(R.string.SET_CUSTOMIZE_LIGHT_COLORS), false); boolean customLight = sharedpreferences.getBoolean(context.getString(R.string.SET_CUSTOMIZE_LIGHT_COLORS), false);
boolean customDark = sharedpreferences.getBoolean(context.getString(R.string.SET_CUSTOMIZE_DARK_COLORS), false); boolean customDark = sharedpreferences.getBoolean(context.getString(R.string.SET_CUSTOMIZE_DARK_COLORS), false);
underlineLinks = sharedpreferences.getBoolean(context.getString(R.string.SET_UNDERLINE_CLICKABLE), false);
int link_color; int link_color;
if (currentNightMode == Configuration.UI_MODE_NIGHT_NO && customLight) { if (currentNightMode == Configuration.UI_MODE_NIGHT_NO && customLight) {
link_color = sharedpreferences.getInt(context.getString(R.string.SET_LIGHT_LINK), -1); link_color = sharedpreferences.getInt(context.getString(R.string.SET_LIGHT_LINK), -1);
@ -125,21 +162,97 @@ public class SpannableHelper {
if (linkColor == 0) { if (linkColor == 0) {
linkColor = -1; linkColor = -1;
} }
if (status != null && status.underlined) {
linkColor = -1;
}
List<Mention> mentions = new ArrayList<>(); List<Mention> mentions = new ArrayList<>();
if (status != null && status.mentions != null) { if (status != null && status.mentions != null) {
mentions.addAll(status.mentions); mentions.addAll(status.mentions);
} }
text = text.replaceAll("((<\\s?p\\s?>|<\\s?br\\s?/?>)&gt;(((?!(<\\s?br\\s?/?>|<\\s?/s?p\\s?>)).)*))", "$2<blockquote>$3</blockquote>"); boolean markdownSupport = sharedpreferences.getBoolean(context.getString(R.string.SET_MARKDOWN_SUPPORT), false);
if(!markdownSupport) {
text = text.replaceAll("((<\\s?p\\s?>|<\\s?br\\s?/?>)&gt;(((?!(<\\s?br\\s?/?>|<\\s?/s?p\\s?>)).)*))", "$2<blockquote>$3</blockquote>");
}
text = text.trim().replaceAll("\\s{3}", "&nbsp;&nbsp;&nbsp;"); text = text.trim().replaceAll("\\s{3}", "&nbsp;&nbsp;&nbsp;");
text = text.trim().replaceAll("\\s{2}", "&nbsp;&nbsp;"); text = text.trim().replaceAll("\\s{2}", "&nbsp;&nbsp;");
SpannableString initialContent; SpannableString initialContent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) if (convertHtml) {
initialContent = new SpannableString(Html.fromHtml(text, Html.FROM_HTML_MODE_LEGACY)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
else initialContent = new SpannableString(Html.fromHtml(text, Html.FROM_HTML_MODE_LEGACY));
initialContent = new SpannableString(Html.fromHtml(text)); else
initialContent = new SpannableString(Html.fromHtml(text));
} else {
initialContent = new SpannableString(text);
}
//Get all links //Get all links
SpannableStringBuilder content = new SpannableStringBuilder(initialContent); SpannableStringBuilder content;
if (markdownSupport && convertMarkdown) {
MarkdownConverter markdownConverter = new MarkdownConverter();
markdownConverter.markdownItems = new ArrayList<>();
int next;
int position = 0;
for (int i = 0; i < initialContent.length(); i = next) {
// find the next span transition
next = initialContent.nextSpanTransition(i, initialContent.length(), URLSpan.class);
MarkdownConverter.MarkdownItem markdownItem = new MarkdownConverter.MarkdownItem();
markdownItem.code = initialContent.subSequence(i, next).toString();
markdownItem.position = position;
// get all spans in this range
URLSpan[] spans = initialContent.getSpans(i, next, URLSpan.class);
if (spans != null && spans.length > 0) {
markdownItem.urlSpan = spans[0];
}
if (!markdownItem.code.trim().isEmpty()) {
markdownConverter.markdownItems.add(markdownItem);
position++;
}
}
final Markwon markwon = Markwon.builder(context)
.usePlugin(TablePlugin.create(context))
.usePlugin(SoftBreakAddsNewLinePlugin.create())
.usePlugin(SyntaxHighlightPlugin.create(new Prism4j(new MySuperGrammerLocator()), Prism4jThemeDefault.create()))
.usePlugin(StrikethroughPlugin.create())
.usePlugin(MarkwonInlineParserPlugin.create())
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configure(@NonNull Registry registry) {
registry.require(MarkwonInlineParserPlugin.class, plugin -> plugin.factoryBuilder()
.excludeInlineProcessor(HtmlInlineProcessor.class));
}
})
.build();
final Spanned markdown = markwon.toMarkdown(initialContent.toString());
content = new SpannableStringBuilder(markdown);
position = 0;
for (MarkdownConverter.MarkdownItem markdownItem : markdownConverter.markdownItems) {
String sb = Pattern.compile("\\A[\\p{L}0-9_]").matcher(markdownItem.code.trim()).find() ? "\\b" : "";
String eb = Pattern.compile("[\\p{L}0-9_]\\z").matcher(markdownItem.code.trim()).find() ? "\\b" : "\\B";
Pattern p = Pattern.compile(sb + "(" + Pattern.quote(markdownItem.code.trim()) + ")" + eb, Pattern.UNICODE_CASE);
Matcher m = p.matcher(content);
int fetchPosition = 1;
while (m.find()) {
int regexPosition = markdownItem.regexPosition(markdownConverter.markdownItems);
if (regexPosition == fetchPosition) {
content.setSpan(markdownItem.urlSpan, m.start(), m.end(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
fetchPosition++;
}
position++;
}
} else {
content = new SpannableStringBuilder(initialContent);
}
URLSpan[] urls = content.getSpans(0, (content.length() - 1), URLSpan.class); URLSpan[] urls = content.getSpans(0, (content.length() - 1), URLSpan.class);
//Loop through links //Loop through links
for (URLSpan span : urls) { for (URLSpan span : urls) {
@ -149,6 +262,7 @@ public class SpannableHelper {
if (start < 0 || end > content.length()) { if (start < 0 || end > content.length()) {
continue; continue;
} }
boolean sameContent = (content.subSequence(start,end).toString().trim().equals(url.trim()));
content.removeSpan(span); content.removeSpan(span);
//Get the matching word associated to the URL //Get the matching word associated to the URL
String word = content.subSequence(start, end).toString(); String word = content.subSequence(start, end).toString();
@ -198,40 +312,59 @@ public class SpannableHelper {
public void onClick(@NonNull View textView) { public void onClick(@NonNull View textView) {
textView.setTag(CLICKABLE_SPAN); textView.setTag(CLICKABLE_SPAN);
Intent intent; Intent intent;
Bundle b; Bundle args;
if (word.startsWith("#")) { if (word.startsWith("#")) {
intent = new Intent(context, HashTagActivity.class); intent = new Intent(context, HashTagActivity.class);
b = new Bundle(); args = new Bundle();
b.putString(Helper.ARG_SEARCH_KEYWORD, word.trim()); args.putString(Helper.ARG_SEARCH_KEYWORD, word.trim());
intent.putExtras(b); new CachedBundle(context).insertBundle(args, Helper.getCurrentAccount(context), bundleId -> {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); Bundle bundle = new Bundle();
context.startActivity(intent); bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
});
} else if (word.startsWith("@")) { } else if (word.startsWith("@")) {
intent = new Intent(context, ProfileActivity.class); intent = new Intent(context, ProfileActivity.class);
b = new Bundle(); args = new Bundle();
Mention targetedMention = null; Mention targetedMention = null;
String acct = null;
for (Mention mention : mentions) { for (Mention mention : mentions) {
if (word.compareToIgnoreCase("@" + mention.username) == 0) { if (word.compareToIgnoreCase("@" + mention.username) == 0) {
targetedMention = mention; if (!checkRemotely) {
targetedMention = mention;
} else {
acct = mention.acct;
}
break; break;
} }
} }
if (targetedMention != null) { if (targetedMention != null) {
b.putString(Helper.ARG_USER_ID, targetedMention.id); args.putString(Helper.ARG_USER_ID, targetedMention.id);
} else if (acct != null) {
args.putString(Helper.ARG_MENTION, acct);
} else { } else {
b.putString(Helper.ARG_MENTION, word); args.putString(Helper.ARG_MENTION, word);
} }
intent.putExtras(b); new CachedBundle(context).insertBundle(args, Helper.getCurrentAccount(context), bundleId -> {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); Bundle bundle = new Bundle();
context.startActivity(intent); bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
});
} }
} }
@Override @Override
public void updateDrawState(@NonNull TextPaint ds) { public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds); super.updateDrawState(ds);
ds.setUnderlineText(false); if (!underlineLinks) {
ds.setUnderlineText(status != null && status.underlined);
}
if (linkColor != -1) { if (linkColor != -1) {
ds.setColor(linkColor); ds.setColor(linkColor);
} }
@ -239,11 +372,11 @@ public class SpannableHelper {
}, start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); }, start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
} else { } else {
makeLinks(context, content, url, start, end); makeLinks(context, status, content, url, start, end, sameContent);
} }
replaceQuoteSpans(context, content);
emails(context, content);
} }
replaceQuoteSpans(context, content);
emails(context, content, status);
Pattern imgPattern = Pattern.compile("<img [^>]*src=\"([^\"]+)\"[^>]*>"); Pattern imgPattern = Pattern.compile("<img [^>]*src=\"([^\"]+)\"[^>]*>");
Matcher matcherImg = imgPattern.matcher(text); Matcher matcherImg = imgPattern.matcher(text);
@ -269,7 +402,7 @@ public class SpannableHelper {
CustomEmoji customEmoji = new CustomEmoji(new WeakReference<>(view)); CustomEmoji customEmoji = new CustomEmoji(new WeakReference<>(view));
content = customEmoji.makeEmoji(content, emojiList, animate, callback); content = customEmoji.makeEmoji(content, emojiList, animate, callback);
if (imagesToReplace.size() > 0) { if (!imagesToReplace.isEmpty()) {
for (Map.Entry<String, String> entry : imagesToReplace.entrySet()) { for (Map.Entry<String, String> entry : imagesToReplace.entrySet()) {
String key = entry.getKey(); String key = entry.getKey();
String url = entry.getValue(); String url = entry.getValue();
@ -284,29 +417,48 @@ public class SpannableHelper {
} }
} }
}
boolean underlineBottomHashTags = sharedpreferences.getBoolean(context.getString(R.string.SET_UNDERLINE_BOTTOM_HASHTAGS), true);
if(underlineBottomHashTags) {
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
final Pattern bottomTagsPattern = Pattern.compile(patternBottomTags, Pattern.CASE_INSENSITIVE);
Matcher matcherBottomTags = bottomTagsPattern.matcher(content);
int length = 0;
while (matcherBottomTags.find()) {
length = Objects.requireNonNull(matcherBottomTags.group()).length();
}
spannableStringBuilder.append(content,0, content.length()-length);
return trimSpannable(spannableStringBuilder);
} }
return trimSpannable(new SpannableStringBuilder(content)); return trimSpannable(new SpannableStringBuilder(content));
} }
private static void makeLinks(Context context, SpannableStringBuilder content, String url, int start, int end) { private static void makeLinks(Context context, Status status, SpannableStringBuilder content, String url, int start, int end, boolean sameContent) {
String newUrl = url; String newUrl = url;
boolean validUrl = URLUtil.isValidUrl(url) && url.length() == (end - start); boolean validUrl = URLUtil.isValidUrl(url) && sameContent;
if (validUrl) { if (validUrl) {
newUrl = Helper.transformURL(context, url); newUrl = Helper.transformURL(context, url);
} }
//If URL has been transformed //If URL has been transformed
if (validUrl && newUrl.compareTo(url) != 0) { if (validUrl && newUrl.compareTo(url) != 0) {
content.replace(start, end, newUrl); content.replace(start, end, newUrl);
end = start + newUrl.length(); end = start + newUrl.length();
url = newUrl; url = newUrl;
} }
if (url.length() > 30 && (validUrl || url.startsWith("gimini://"))) { SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
newUrl = url.substring(0, 30); boolean truncate = sharedpreferences.getBoolean(context.getString(R.string.SET_TRUNCATE_LINKS), true);
newUrl += ""; if (truncate) {
content.replace(start, end, newUrl); int truncateValue = sharedpreferences.getInt(context.getString(R.string.SET_TRUNCATE_LINKS_MAX), 30);
if (url.length() > truncateValue && (validUrl || url.startsWith("gimini://"))) {
newUrl = url.substring(0, truncateValue);
newUrl += "";
content.replace(start, end, newUrl);
}
} }
int matchEnd = validUrl ? start + newUrl.length() : end; int matchEnd = validUrl ? start + newUrl.length() : end;
@ -510,117 +662,15 @@ public class SpannableHelper {
public void onClick(@NonNull View textView) { public void onClick(@NonNull View textView) {
textView.setTag(CLICKABLE_SPAN); textView.setTag(CLICKABLE_SPAN);
Pattern link = Pattern.compile("https?://([\\da-z.-]+\\.[a-z.]{2,10})/(@[\\w._-]*[0-9]*)(/[0-9]+)?$"); linkClickAction(context, finalUrl);
Matcher matcherLink = link.matcher(finalUrl);
Pattern linkLong = Pattern.compile("https?://([\\da-z.-]+\\.[a-z.]{2,10})/(@[\\w_.-]+@[a-zA-Z0-9][a-zA-Z0-9.-]{1,61}[a-zA-Z0-9](?:\\.[a-zA-Z]{2,})+)(/[0-9]+)?$");
Matcher matcherLinkLong = linkLong.matcher(finalUrl);
Pattern userWithoutAt = Pattern.compile("https?://([\\da-z.-]+\\.[a-z.]{2,10})/(users/([\\w._-]*[0-9]*))/statuses/([0-9]+)");
Matcher matcherUserWithoutAt = userWithoutAt.matcher(finalUrl);
if (matcherLink.find() && !finalUrl.contains("medium.com")) {
if (matcherLink.group(3) != null && Objects.requireNonNull(matcherLink.group(3)).length() > 0) { //It's a toot
CrossActionHelper.fetchRemoteStatus(context, currentAccount, finalUrl, new CrossActionHelper.Callback() {
@Override
public void federatedStatus(Status status) {
Intent intent = new Intent(context, ContextActivity.class);
intent.putExtra(Helper.ARG_STATUS, status);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
@Override
public void federatedAccount(Account account) {
}
});
} else {//It's an account
CrossActionHelper.fetchRemoteAccount(context, currentAccount, matcherLink.group(2) + "@" + matcherLink.group(1), new CrossActionHelper.Callback() {
@Override
public void federatedStatus(Status status) {
}
@Override
public void federatedAccount(Account account) {
Intent intent = new Intent(context, ProfileActivity.class);
Bundle b = new Bundle();
b.putSerializable(Helper.ARG_ACCOUNT, account);
intent.putExtras(b);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
});
}
} else if (matcherLinkLong.find() && !finalUrl.contains("medium.com")) {
if (matcherLinkLong.group(3) != null && Objects.requireNonNull(matcherLinkLong.group(3)).length() > 0) { //It's a toot
CrossActionHelper.fetchRemoteStatus(context, currentAccount, finalUrl, new CrossActionHelper.Callback() {
@Override
public void federatedStatus(Status status) {
Intent intent = new Intent(context, ContextActivity.class);
intent.putExtra(Helper.ARG_STATUS, status);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
@Override
public void federatedAccount(Account account) {
}
});
} else if (matcherLinkLong.group(2) != null) {//It's an account
CrossActionHelper.fetchRemoteAccount(context, currentAccount, matcherLinkLong.group(2), new CrossActionHelper.Callback() {
@Override
public void federatedStatus(Status status) {
}
@Override
public void federatedAccount(Account account) {
Intent intent = new Intent(context, ProfileActivity.class);
Bundle b = new Bundle();
b.putSerializable(Helper.ARG_ACCOUNT, account);
intent.putExtras(b);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
});
}
} else if (matcherUserWithoutAt.find() && !finalUrl.contains("medium.com")) {
if (matcherUserWithoutAt.group(4) != null && Objects.requireNonNull(matcherUserWithoutAt.group(4)).length() > 0) { //It's a toot
CrossActionHelper.fetchRemoteStatus(context, currentAccount, finalUrl, new CrossActionHelper.Callback() {
@Override
public void federatedStatus(Status status) {
Intent intent = new Intent(context, ContextActivity.class);
intent.putExtra(Helper.ARG_STATUS, status);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
@Override
public void federatedAccount(Account account) {
}
});
} else {//It's an account
CrossActionHelper.fetchRemoteAccount(context, currentAccount, matcherUserWithoutAt.group(3) + "@" + matcherUserWithoutAt.group(1), new CrossActionHelper.Callback() {
@Override
public void federatedStatus(Status status) {
}
@Override
public void federatedAccount(Account account) {
Intent intent = new Intent(context, ProfileActivity.class);
Bundle b = new Bundle();
b.putSerializable(Helper.ARG_ACCOUNT, account);
intent.putExtras(b);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
});
}
} else {
Helper.openBrowser(context, finalUrl);
}
} }
@Override @Override
public void updateDrawState(@NonNull TextPaint ds) { public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds); super.updateDrawState(ds);
ds.setUnderlineText(false); if (!underlineLinks) {
ds.setUnderlineText(status != null && status.underlined);
}
if (linkColor != -1) { if (linkColor != -1) {
ds.setColor(linkColor); ds.setColor(linkColor);
} }
@ -628,8 +678,145 @@ public class SpannableHelper {
}, start, matchEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); }, start, matchEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
} }
public static void linkClickAction(Context context, String finalUrl) {
Pattern link = Pattern.compile("https?://([\\da-z.-]+\\.[a-z.]{2,10})/(@[\\w._-]*[0-9]*)(/[0-9]+)?$");
Matcher matcherLink = link.matcher(finalUrl);
Pattern linkLong = Pattern.compile("https?://([\\da-z.-]+\\.[a-z.]{2,10})/(@[\\w_.-]+@[a-zA-Z0-9][a-zA-Z0-9.-]{1,61}[a-zA-Z0-9](?:\\.[a-zA-Z]{2,})+)(/[0-9]+)?$");
Matcher matcherLinkLong = linkLong.matcher(finalUrl);
Pattern userWithoutAt = Pattern.compile("https?://([\\da-z.-]+\\.[a-z.]{2,10})/(users/([\\w._-]*[0-9]*))/statuses/([0-9]+)");
Matcher matcherUserWithoutAt = userWithoutAt.matcher(finalUrl);
if (matcherLink.find() && !finalUrl.contains("medium.com")) {
if (matcherLink.group(3) != null && !Objects.requireNonNull(matcherLink.group(3)).isEmpty()) { //It's a toot
CrossActionHelper.fetchRemoteStatus(context, Helper.getCurrentAccount(context), finalUrl, new CrossActionHelper.Callback() {
@Override
public void federatedStatus(Status status) {
Intent intent = new Intent(context, ContextActivity.class);
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_STATUS, status);
new CachedBundle(context).insertBundle(args, Helper.getCurrentAccount(context), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
});
}
private static void emails(Context context, Spannable content) { @Override
public void federatedAccount(Account account) {
}
});
} else {//It's an account
CrossActionHelper.fetchRemoteAccount(context, Helper.getCurrentAccount(context), matcherLink.group(2) + "@" + matcherLink.group(1), new CrossActionHelper.Callback() {
@Override
public void federatedStatus(Status status) {
}
@Override
public void federatedAccount(Account account) {
Intent intent = new Intent(context, ProfileActivity.class);
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_ACCOUNT, account);
new CachedBundle(context).insertBundle(args, Helper.getCurrentAccount(context), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
});
}
});
}
} else if (matcherLinkLong.find() && !finalUrl.contains("medium.com")) {
if (matcherLinkLong.group(3) != null && !Objects.requireNonNull(matcherLinkLong.group(3)).isEmpty()) { //It's a toot
CrossActionHelper.fetchRemoteStatus(context, Helper.getCurrentAccount(context), finalUrl, new CrossActionHelper.Callback() {
@Override
public void federatedStatus(Status status) {
Intent intent = new Intent(context, ContextActivity.class);
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_STATUS, status);
new CachedBundle(context).insertBundle(args, Helper.getCurrentAccount(context), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
});
}
@Override
public void federatedAccount(Account account) {
}
});
} else if (matcherLinkLong.group(2) != null) {//It's an account
CrossActionHelper.fetchRemoteAccount(context, Helper.getCurrentAccount(context), matcherLinkLong.group(2), new CrossActionHelper.Callback() {
@Override
public void federatedStatus(Status status) {
}
@Override
public void federatedAccount(Account account) {
Intent intent = new Intent(context, ProfileActivity.class);
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_ACCOUNT, account);
new CachedBundle(context).insertBundle(args, Helper.getCurrentAccount(context), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
});
}
});
}
} else if (matcherUserWithoutAt.find() && !finalUrl.contains("medium.com")) {
if (matcherUserWithoutAt.group(4) != null && !Objects.requireNonNull(matcherUserWithoutAt.group(4)).isEmpty()) { //It's a toot
CrossActionHelper.fetchRemoteStatus(context, Helper.getCurrentAccount(context), finalUrl, new CrossActionHelper.Callback() {
@Override
public void federatedStatus(Status status) {
Intent intent = new Intent(context, ContextActivity.class);
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_STATUS, status);
new CachedBundle(context).insertBundle(args, Helper.getCurrentAccount(context), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
});
}
@Override
public void federatedAccount(Account account) {
}
});
} else {//It's an account
CrossActionHelper.fetchRemoteAccount(context, Helper.getCurrentAccount(context), matcherUserWithoutAt.group(3) + "@" + matcherUserWithoutAt.group(1), new CrossActionHelper.Callback() {
@Override
public void federatedStatus(Status status) {
}
@Override
public void federatedAccount(Account account) {
Intent intent = new Intent(context, ProfileActivity.class);
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_ACCOUNT, account);
new CachedBundle(context).insertBundle(args, Helper.getCurrentAccount(context), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
});
}
});
}
} else {
Helper.openBrowser(context, finalUrl);
}
}
private static void emails(Context context, Spannable content, Status status) {
// --- For all patterns defined in Helper class --- // --- For all patterns defined in Helper class ---
Pattern pattern = Helper.emailPattern; Pattern pattern = Helper.emailPattern;
Matcher matcher = pattern.matcher(content); Matcher matcher = pattern.matcher(content);
@ -658,7 +845,9 @@ public class SpannableHelper {
@Override @Override
public void updateDrawState(@NonNull TextPaint ds) { public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds); super.updateDrawState(ds);
ds.setUnderlineText(false); if (!underlineLinks) {
ds.setUnderlineText(status != null && status.underlined);
}
if (linkColor != -1) { if (linkColor != -1) {
ds.setColor(linkColor); ds.setColor(linkColor);
} }
@ -796,16 +985,22 @@ public class SpannableHelper {
@Override @Override
public void onClick(@NonNull View textView) { public void onClick(@NonNull View textView) {
Intent intent = new Intent(context, ProfileActivity.class); Intent intent = new Intent(context, ProfileActivity.class);
Bundle b = new Bundle(); Bundle args = new Bundle();
b.putSerializable(Helper.ARG_ACCOUNT, account.moved); args.putSerializable(Helper.ARG_ACCOUNT, account.moved);
intent.putExtras(b); new CachedBundle(context).insertBundle(args, Helper.getCurrentAccount(context), bundleId -> {
context.startActivity(intent); Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
context.startActivity(intent);
});
} }
@Override @Override
public void updateDrawState(@NonNull TextPaint ds) { public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds); super.updateDrawState(ds);
ds.setUnderlineText(false); if (!underlineLinks) {
ds.setUnderlineText(false);
}
if (linkColor != -1) { if (linkColor != -1) {
ds.setColor(linkColor); ds.setColor(linkColor);
} }

View file

@ -15,6 +15,9 @@ package app.fedilab.android.mastodon.helper;
* see <http://www.gnu.org/licenses>. */ * see <http://www.gnu.org/licenses>. */
import static android.content.Context.WINDOW_SERVICE; import static android.content.Context.WINDOW_SERVICE;
import static app.fedilab.android.BaseMainActivity.currentInstance;
import static app.fedilab.android.BaseMainActivity.currentNightMode;
import static app.fedilab.android.BaseMainActivity.currentUserID;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
@ -23,6 +26,8 @@ import android.content.res.ColorStateList;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.os.Build; import android.os.Build;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.TypedValue; import android.util.TypedValue;
@ -37,6 +42,9 @@ import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import com.google.android.material.color.DynamicColors;
import com.google.android.material.color.DynamicColorsOptions;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -168,12 +176,10 @@ public class ThemeHelper {
String[] list; String[] list;
try { try {
list = context.getAssets().list("themes/contributors"); list = context.getAssets().list("themes/contributors");
if (list.length > 0) { for (String file : list) {
for (String file : list) { InputStream is = context.getAssets().open("themes/contributors/" + file);
InputStream is = context.getAssets().open("themes/contributors/" + file); LinkedHashMap<String, String> data = readCSVFile(is);
LinkedHashMap<String, String> data = readCSVFile(is); linkedHashMaps.add(data);
linkedHashMaps.add(data);
}
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
@ -267,7 +273,7 @@ public class ThemeHelper {
public static void switchTo(String themePref) { public static void switchTo(String themePref) {
if (themes.LIGHT.name().equals(themePref) || themes.SOLARIZED_LIGHT.name().equals(themePref)) { if (themes.LIGHT.name().equals(themePref) || themes.SOLARIZED_LIGHT.name().equals(themePref)) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
} else if (themes.DARK.name().equals(themePref) || themes.SOLARIZED_DARK.name().equals(themePref)) { } else if (themes.DARK.name().equals(themePref) || themes.SOLARIZED_DARK.name().equals(themePref) || themes.DRACULA.name().equals(themePref) || themes.BLACK.name().equals(themePref)) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
} else { } else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
@ -278,9 +284,36 @@ public class ThemeHelper {
} }
} }
public static void applyThemeColor(Activity activity) {
final SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(activity);
boolean dynamicColor = sharedpreferences.getBoolean(activity.getString(R.string.SET_DYNAMICCOLOR), false);
boolean customAccentEnabled = sharedpreferences.getBoolean(activity.getString(R.string.SET_CUSTOM_ACCENT) + currentUserID + currentInstance, false);
int customAccentLight = sharedpreferences.getInt(activity.getString(R.string.SET_CUSTOM_ACCENT_LIGHT_VALUE) + currentUserID + currentInstance, -1);
int customAccentDark = sharedpreferences.getInt(activity.getString(R.string.SET_CUSTOM_ACCENT_DARK_VALUE) + currentUserID + currentInstance, -1);
if (customAccentEnabled) {
Bitmap bmp = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
//Light theme enabled
if (currentNightMode == Configuration.UI_MODE_NIGHT_NO && customAccentLight != -1) {
canvas.drawColor(customAccentLight);
} else if (customAccentDark != -1) {
canvas.drawColor(customAccentDark);
}
DynamicColorsOptions.Builder builder = new DynamicColorsOptions.Builder();
builder.setContentBasedSource(bmp);
builder.setThemeOverlay(R.style.ThemeOverlay_Material3_DynamicColors_DayNight);
DynamicColorsOptions dynamicColorsOptions = builder.build();
DynamicColors.applyToActivityIfAvailable(activity, dynamicColorsOptions);
} else if (dynamicColor) {
DynamicColors.applyToActivityIfAvailable(activity);
}
}
public enum themes { public enum themes {
LIGHT, LIGHT,
DARK, DARK,
BLACK,
DRACULA,
SYSTEM, SYSTEM,
SOLARIZED_LIGHT, SOLARIZED_LIGHT,
SOLARIZED_DARK SOLARIZED_DARK

View file

@ -29,10 +29,10 @@ import androidx.lifecycle.ViewModelStoreOwner;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import java.io.IOException; import java.io.IOException;
import java.net.IDN;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -41,6 +41,7 @@ import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity; import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.mastodon.client.endpoints.MastodonFiltersService; import app.fedilab.android.mastodon.client.endpoints.MastodonFiltersService;
import app.fedilab.android.mastodon.client.entities.api.Account; import app.fedilab.android.mastodon.client.entities.api.Account;
import app.fedilab.android.mastodon.client.entities.api.Attachment;
import app.fedilab.android.mastodon.client.entities.api.Filter; import app.fedilab.android.mastodon.client.entities.api.Filter;
import app.fedilab.android.mastodon.client.entities.api.Notification; import app.fedilab.android.mastodon.client.entities.api.Notification;
import app.fedilab.android.mastodon.client.entities.api.Status; import app.fedilab.android.mastodon.client.entities.api.Status;
@ -55,13 +56,9 @@ import retrofit2.converter.gson.GsonConverterFactory;
public class TimelineHelper { public class TimelineHelper {
private static MastodonFiltersService initv2(Context context) { private static MastodonFiltersService initv2(Context context) {
OkHttpClient okHttpClient = new OkHttpClient.Builder() OkHttpClient okHttpClient = Helper.myOkHttpClient(context);
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.proxy(Helper.getProxy(context))
.build();
Retrofit retrofit = new Retrofit.Builder() Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + BaseMainActivity.currentInstance + "/api/v2/") .baseUrl("https://" + (MainActivity.currentInstance != null ? IDN.toASCII(MainActivity.currentInstance, IDN.ALLOW_UNASSIGNED) : null) + "/api/v2/")
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient) .client(okHttpClient)
.build(); .build();
@ -79,7 +76,7 @@ public class TimelineHelper {
*/ */
public static List<Status> filterStatus(Context context, List<Status> statuses, Timeline.TimeLineEnum filterTimeLineType) { public static List<Status> filterStatus(Context context, List<Status> statuses, Timeline.TimeLineEnum filterTimeLineType) {
//A security to make sure filters have been fetched before displaying messages //A security to make sure filters have been fetched before displaying messages
if (!BaseMainActivity.filterFetched) { if (!BaseMainActivity.filterFetched && BaseMainActivity.filterFetchedRetry < 3) {
MastodonFiltersService mastodonFiltersService = initv2(context); MastodonFiltersService mastodonFiltersService = initv2(context);
List<Filter> filterList; List<Filter> filterList;
Call<List<Filter>> getFiltersCall = mastodonFiltersService.getFilters(BaseMainActivity.currentToken); Call<List<Filter>> getFiltersCall = mastodonFiltersService.getFilters(BaseMainActivity.currentToken);
@ -95,10 +92,11 @@ public class TimelineHelper {
e.printStackTrace(); e.printStackTrace();
} }
} }
BaseMainActivity.filterFetchedRetry++;
} }
//If there are filters: //If there are filters:
if (BaseMainActivity.mainFilters != null && BaseMainActivity.mainFilters.size() > 0 && statuses != null && statuses.size() > 0) { if (BaseMainActivity.mainFilters != null && !BaseMainActivity.mainFilters.isEmpty() && statuses != null && !statuses.isEmpty()) {
//Loop through filters //Loop through filters
for (Filter filter : BaseMainActivity.mainFilters) { for (Filter filter : BaseMainActivity.mainFilters) {
@ -117,7 +115,7 @@ public class TimelineHelper {
} else { } else {
if (!filter.context.contains("public")) continue; if (!filter.context.contains("public")) continue;
} }
if (filter.keywords != null && filter.keywords.size() > 0) { if (filter.keywords != null && !filter.keywords.isEmpty()) {
for (Filter.KeywordsAttributes filterKeyword : filter.keywords) { for (Filter.KeywordsAttributes filterKeyword : filter.keywords) {
String sb = Pattern.compile("\\A[A-Za-z0-9_]").matcher(filterKeyword.keyword).find() ? "\\b" : ""; String sb = Pattern.compile("\\A[A-Za-z0-9_]").matcher(filterKeyword.keyword).find() ? "\\b" : "";
String eb = Pattern.compile("[A-Za-z0-9_]\\z").matcher(filterKeyword.keyword).find() ? "\\b" : ""; String eb = Pattern.compile("[A-Za-z0-9_]\\z").matcher(filterKeyword.keyword).find() ? "\\b" : "";
@ -132,10 +130,14 @@ public class TimelineHelper {
continue; continue;
} }
String content; String content;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) try {
content = Html.fromHtml(status.reblog != null ? status.reblog.content : status.content, Html.FROM_HTML_MODE_LEGACY).toString(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
else content = Html.fromHtml(status.reblog != null ? status.reblog.content : status.content, Html.FROM_HTML_MODE_LEGACY).toString();
content = Html.fromHtml(status.reblog != null ? status.reblog.content : status.content).toString(); else
content = Html.fromHtml(status.reblog != null ? status.reblog.content : status.content).toString();
} catch (Exception e) {
content = status.reblog != null ? status.reblog.content : status.content;
}
Matcher m = p.matcher(content); Matcher m = p.matcher(content);
if (m.find()) { if (m.find()) {
status.filteredByApp = filter; status.filteredByApp = filter;
@ -150,6 +152,19 @@ public class TimelineHelper {
Matcher ms = p.matcher(spoilerText); Matcher ms = p.matcher(spoilerText);
if (ms.find()) { if (ms.find()) {
status.filteredByApp = filter; status.filteredByApp = filter;
continue;
}
}
List<Attachment> mediaAttachments = status.reblog != null ? status.reblog.media_attachments : status.media_attachments;
if(mediaAttachments != null && !mediaAttachments.isEmpty()) {
for(Attachment attachment : mediaAttachments) {
if(attachment.description != null) {
Matcher ms = p.matcher(attachment.description );
if (ms.find()) {
status.filteredByApp = filter;
break;
}
}
} }
} }
} }
@ -157,13 +172,13 @@ public class TimelineHelper {
} }
} }
} }
if (statuses != null && statuses.size() > 0) { if (statuses != null && !statuses.isEmpty()) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
boolean groupReblogs = sharedpreferences.getBoolean(context.getString(R.string.SET_GROUP_REBLOGS), true); boolean groupReblogs = sharedpreferences.getBoolean(context.getString(R.string.SET_GROUP_REBLOGS), true);
if (filterTimeLineType == Timeline.TimeLineEnum.HOME) { if (filterTimeLineType == Timeline.TimeLineEnum.HOME) {
for (int i = 0; i < statuses.size(); i++) { for (int i = 0; i < statuses.size(); i++) {
if (filteredAccounts != null && filteredAccounts.size() > 0) { if (filteredAccounts != null && !filteredAccounts.isEmpty()) {
for (Account account : filteredAccounts) { for (Account account : filteredAccounts) {
if (account.acct.equals(statuses.get(i).account.acct) || (statuses.get(i).reblog != null && account.acct.equals(statuses.get(i).reblog.account.acct))) { if (account.acct.equals(statuses.get(i).account.acct) || (statuses.get(i).reblog != null && account.acct.equals(statuses.get(i).reblog.account.acct))) {
Filter filterCustom = new Filter(); Filter filterCustom = new Filter();
@ -208,7 +223,7 @@ public class TimelineHelper {
public static List<Notification> filterNotification(Context context, List<Notification> notifications) { public static List<Notification> filterNotification(Context context, List<Notification> notifications) {
//A security to make sure filters have been fetched before displaying messages //A security to make sure filters have been fetched before displaying messages
List<Notification> notificationToRemove = new ArrayList<>(); List<Notification> notificationToRemove = new ArrayList<>();
if (!BaseMainActivity.filterFetched) { if (!BaseMainActivity.filterFetched && BaseMainActivity.filterFetchedRetry < 3) {
try { try {
FiltersVM filtersVM = new ViewModelProvider((ViewModelStoreOwner) context).get(FiltersVM.class); FiltersVM filtersVM = new ViewModelProvider((ViewModelStoreOwner) context).get(FiltersVM.class);
filtersVM.getFilters(BaseMainActivity.currentInstance, BaseMainActivity.currentToken).observe((LifecycleOwner) context, filters -> { filtersVM.getFilters(BaseMainActivity.currentInstance, BaseMainActivity.currentToken).observe((LifecycleOwner) context, filters -> {
@ -218,9 +233,10 @@ public class TimelineHelper {
} catch (Exception e) { } catch (Exception e) {
return notifications; return notifications;
} }
BaseMainActivity.filterFetchedRetry++;
} }
//If there are filters: //If there are filters:
if (BaseMainActivity.mainFilters != null && BaseMainActivity.mainFilters.size() > 0 && notifications != null && notifications.size() > 0) { if (BaseMainActivity.mainFilters != null && !BaseMainActivity.mainFilters.isEmpty() && notifications != null && !notifications.isEmpty()) {
//Loop through filters //Loop through filters
for (Filter filter : BaseMainActivity.mainFilters) { for (Filter filter : BaseMainActivity.mainFilters) {
@ -231,7 +247,7 @@ public class TimelineHelper {
} }
if (!filter.context.contains("notifications")) continue; if (!filter.context.contains("notifications")) continue;
if (filter.keywords != null && filter.keywords.size() > 0) { if (filter.keywords != null && !filter.keywords.isEmpty()) {
for (Filter.KeywordsAttributes filterKeyword : filter.keywords) { for (Filter.KeywordsAttributes filterKeyword : filter.keywords) {
String sb = Pattern.compile("\\A[A-Za-z0-9_]").matcher(filterKeyword.keyword).find() ? "\\b" : ""; String sb = Pattern.compile("\\A[A-Za-z0-9_]").matcher(filterKeyword.keyword).find() ? "\\b" : "";
String eb = Pattern.compile("[A-Za-z0-9_]\\z").matcher(filterKeyword.keyword).find() ? "\\b" : ""; String eb = Pattern.compile("[A-Za-z0-9_]\\z").matcher(filterKeyword.keyword).find() ? "\\b" : "";

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