Compare commits

..

No commits in common. "main" and "3.18.0" have entirely different histories.
main ... 3.18.0

1014 changed files with 9527 additions and 37912 deletions

View file

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

View file

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

View file

@ -3,7 +3,7 @@ CONTRIBUTING
### 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.
If you're submiting a merge request and your work adds new strings to the app, make sure they only
@ -18,4 +18,4 @@ important part is how to reproduce the bug and steps to reproduce it.
### Pull Requests
Please target the develop branch and not the main branch.
Please target the develop branch and not the main branch.

View file

@ -1,46 +1,24 @@
[![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)
<img src="src/fdroid/fastlane/metadata/android/en/images/icon.png" width="100"/>
# Fedilab
A multi-accounts client for Mastodon, Pleroma, Friendica and Pixelfed
[![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
[<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)
## Translate
- [Weblate](https://hosted.weblate.org/engage/fedilab/)
# Fedilab is a multi-accounts client for Mastodon, Pleroma, Friendica and Pixelfed
## Donate
- [Liberapay](https://liberapay.com/tom79/donate)
- [Open Collective](https://opencollective.com/fedilab)
- [More...](https://fedilab.app/page/donations/)
[<img alt="Donate using Liberapay" src="https://img.shields.io/liberapay/patrons/tom79.svg?logo=liberapay"/>](https://liberapay.com/tom79/donate)
## 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)
&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' />
## Resources
- [Wiki](https://wiki.fedilab.app)
- [Releases + Changelogs](https://codeberg.org/tom79/Fedilab/releases)
[WIKI](https://fedilab.app/wiki/home/)
## Contact
- Fedi:<br>[toot.fedilab.app/@apps](https://toot.fedilab.app/@apps)
[Release notes](https://codeberg.org/tom79/Fedilab/tags)
Lead developer: [toot.fedilab.app/@apps](https://toot.fedilab.app/@apps)

View file

@ -8,13 +8,13 @@ plugins {
}
def flavor
android {
compileSdk 34
compileSdk 33
defaultConfig {
minSdk 21
targetSdk 34
versionCode 534
versionName "3.33.1"
targetSdk 33
versionCode 478
versionName "3.18.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
flavorDimensions "default"
@ -25,13 +25,12 @@ android {
}
debug {
applicationIdSuffix '.debug'
pseudoLocalesEnabled true
}
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
productFlavors {
fdroid {
@ -47,11 +46,16 @@ android {
flavor = "playstore"
}
}
lintOptions {
checkReleaseBuilds false
abortOnError false
}
buildFeatures {
viewBinding true
}
sourceSets {
playstore {
manifest.srcFile "src/playstore/AndroidManifest.xml"
java.srcDirs = ['src/main/java', 'src/playstore/java']
res.srcDirs = ['src/main/res', 'src/playstore/res']
}
@ -73,25 +77,15 @@ android {
'src/main/res/menus/peertube',
'src/main/res/menus',
'src/main/res/values',
'src/main/res'
]
}
}
configurations {
cleanedAnnotations
implementation.exclude group: 'org.jetbrains', module: 'annotations'
all {
exclude group: 'androidx.lifecycle', module: 'lifecycle-viewmodel-ktx'
}
}
namespace 'app.fedilab.android'
lint {
abortOnError false
checkReleaseBuilds false
}
buildToolsVersion '35.0.0'
}
allprojects {
repositories {
@ -100,22 +94,20 @@ allprojects {
}
}
dependencies {
implementation 'org.unifiedpush.android:connector:3.0.9'
implementation project(':autoimageslider')
implementation 'androidx.appcompat:appcompat:1.6.0'
playstoreImplementation('org.unifiedpush.android:embedded-fcm-distributor:3.0.0')
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'com.jaredrummler:colorpicker:1.1.0'
implementation 'com.google.android.material:material:1.10.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
implementation "com.google.code.gson:gson:2.10.1"
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation "com.google.code.gson:gson:2.9.1"
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:converter-simplexml:2.9.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.preference:preference:1.2.1'
implementation 'androidx.preference:preference:1.2.0'
implementation "org.conscrypt:conscrypt-android:2.5.2"
implementation 'com.vanniktech:emoji-one:0.6.0'
implementation 'com.github.GrenderG:Toasty:1.5.2'
@ -126,88 +118,91 @@ dependencies {
transitive = false
}
implementation "org.jsoup:jsoup:1.18.1"
implementation 'com.github.mergehez:ArgPlayer:v3.1'
implementation "org.jsoup:jsoup:1.15.1"
implementation project(':autoimageslider')
implementation 'com.github.mergehez:ArgPlayer:v3.1'
implementation project(path: ':mytransl')
implementation project(path: ':ratethisapp')
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.vanniktech:android-image-cropper:4.3.3")
implementation project(path: ':mathjaxandroid')
annotationProcessor "com.github.bumptech.glide:compiler:4.12.0"
implementation 'jp.wasabeef:glide-transformations:4.3.0'
implementation 'com.github.penfeizhou.android.animation:glide-plugin:3.0.5'
implementation 'androidx.media3:media3-exoplayer-hls:1.2.1'
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.penfeizhou.android.animation:glide-plugin:2.23.0'
implementation 'com.google.android.exoplayer:exoplayer:2.18.1'
implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation 'com.github.piasy:rxandroidaudio:1.7.0'
implementation 'com.github.piasy:AudioProcessor:1.7.0'
implementation "androidx.work:work-runtime:2.9.0"
implementation "androidx.work:work-runtime:2.7.1"
implementation 'app.futured.hauler:hauler:5.0.0'
implementation "com.github.chrisbanes:PhotoView:2.3.0"
implementation "ch.acra:acra-mail:5.11.3"
implementation "ch.acra:acra-limiter:5.11.3"
implementation "ch.acra:acra-dialog:5.11.3"
implementation "ch.acra:acra-mail:5.9.6"
implementation "ch.acra:acra-limiter:5.9.3"
implementation "ch.acra:acra-dialog:5.9.6"
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 'androidx.multidex:multidex:2.0.1'
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
implementation 'androidx.lifecycle:lifecycle-livedata:2.8.7'
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.8.7'
implementation 'androidx.navigation:navigation-fragment:2.8.7'
implementation 'androidx.navigation:navigation-ui:2.8.7'
implementation 'androidx.lifecycle:lifecycle-livedata:2.5.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.5.1'
implementation 'androidx.navigation:navigation-fragment:2.5.3'
implementation 'androidx.navigation:navigation-ui:2.5.3'
testImplementation 'junit:junit:'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1'
implementation 'com.r0adkll:slidableactivity:2.1.0'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation 'androidx.vectordrawable:vectordrawable:1.2.0'
implementation "androidx.fragment:fragment:1.8.6"
implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
implementation "androidx.fragment:fragment:1.5.5"
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.browser:browser:1.8.0'
implementation 'androidx.browser:browser:1.4.0'
implementation 'androidx.documentfile:documentfile:1.0.1'
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 "net.gotev:uploadservice:4.9.2"
implementation "net.gotev:uploadservice-okhttp:4.9.2"
implementation 'androidx.media:media:1.7.0'
implementation "net.gotev:uploadservice:4.7.0"
implementation "net.gotev:uploadservice-okhttp:4.7.0"
implementation 'androidx.media:media:1.6.0'
implementation 'com.github.mancj:MaterialSearchBar:0.8.5'
implementation 'com.github.androidmads:QRGenerator:1.0.1'
implementation 'com.github.vkay94:DoubleTapPlayerView:1.0.0'
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 **************///
//---> Google libs (google_full)
playstoreImplementation "com.google.android.gms:play-services-cast-tv:21.1.1"
playstoreImplementation "com.google.android.gms:play-services-cast:22.0.0"
playstoreImplementation "androidx.mediarouter:mediarouter:1.7.0"
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'
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
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'
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() {

View file

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

View file

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

View file

@ -1,18 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<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.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<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_DATA_SYNC" />
<uses-feature
android:name="android.hardware.camera"
@ -25,8 +24,6 @@
<application
android:name="app.fedilab.android.MainApplication"
android:allowBackup="false"
android:dataExtractionRules="@xml/extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:configChanges="orientation|screenSize"
android:icon="@mipmap/ic_launcher_bubbles"
android:label="@string/app_name"
@ -72,13 +69,106 @@
android:host="*"
android:pathPrefix="/@"
android:scheme="https" />
<data
android:host="*"
android:pathPrefix="/notes"
android:scheme="https" />
</intent-filter>
</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
android:name=".activities.LoginActivity"
@ -172,15 +262,7 @@
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/action_about"
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
android:name=".mastodon.activities.admin.AdminDomainBlockActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
@ -234,7 +316,6 @@
android:configChanges="keyboardHidden|orientation|screenSize" />
<activity
android:name=".mastodon.activities.MediaActivity"
android:hardwareAccelerated="true"
android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@style/Transparent" />
@ -264,16 +345,6 @@
android:label="@string/action_cache"
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
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileProvider"
@ -292,12 +363,18 @@
</intent-filter>
</receiver>
<service android:name=".mastodon.services.PushServiceImpl"
android:exported="false">
<receiver
android:name=".mastodon.services.CustomReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="org.unifiedpush.android.connector.PUSH_EVENT"/>
<action android:name="org.unifiedpush.android.connector.MESSAGE" />
<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>
</service>
</receiver>
<activity
@ -305,7 +382,7 @@
android:configChanges="keyboardHidden|orientation|screenSize" />
<activity
android:name=".peertube.activities.PeertubeActivity"
android:configChanges="keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:launchMode="singleTask"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
@ -395,366 +472,8 @@
<service
android:name=".peertube.services.RetrieveInfoService"
android:foregroundServiceType="dataSync"
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>
</manifest>
</manifest>

View file

@ -1,229 +1,4 @@
[
{
"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",
"code": "478",
@ -293,6 +68,30 @@
"version": "3.14.3",
"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)"
},
{
"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,9 +42,7 @@ import java.util.Objects;
import app.fedilab.android.mastodon.helper.ThemeHelper;
import app.fedilab.android.peertube.services.GlobalUploadObserver;
import es.dmoral.toasty.Toasty;
import io.noties.prism4j.annotations.PrismBundle;
@PrismBundle(includeAll = true, grammarLocatorClassName = ".MySuperGrammerLocator")
public class MainApplication extends MultiDexApplication {

View file

@ -15,17 +15,14 @@ package app.fedilab.android.activities;
* 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.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.core.app.ActivityOptionsCompat;
import androidx.lifecycle.ViewModelProvider;
import java.util.ArrayList;
@ -39,12 +36,10 @@ import app.fedilab.android.mastodon.activities.BaseBarActivity;
import app.fedilab.android.mastodon.activities.ProfileActivity;
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.app.CachedBundle;
import app.fedilab.android.mastodon.helper.CrossActionHelper;
import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.MastodonHelper;
import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM;
import es.dmoral.toasty.Toasty;
public class AboutActivity extends BaseBarActivity {
@ -63,10 +58,10 @@ public class AboutActivity extends BaseBarActivity {
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
String version = "";
try {
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
version = pInfo.versionName;
String version = pInfo.versionName;
binding.aboutVersion.setText(getResources().getString(R.string.about_vesrion, version));
} catch (PackageManager.NameNotFoundException ignored) {
}
@ -82,20 +77,6 @@ public class AboutActivity extends BaseBarActivity {
}
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) {
binding.aboutSupportPaypal.setVisibility(View.VISIBLE);
} else {
@ -119,14 +100,12 @@ public class AboutActivity extends BaseBarActivity {
binding.accountUn.setText(account.acct);
binding.accountPp.setOnClickListener(v -> {
Intent intent = new Intent(AboutActivity.this, ProfileActivity.class);
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_ACCOUNT, account);
new CachedBundle(AboutActivity.this).insertBundle(args, Helper.getCurrentAccount(AboutActivity.this), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
startActivity(intent);
});
Bundle b = new Bundle();
b.putSerializable(Helper.ARG_ACCOUNT, account);
intent.putExtras(b);
ActivityOptionsCompat options = ActivityOptionsCompat
.makeSceneTransitionAnimation(AboutActivity.this, binding.accountPp, getString(R.string.activity_porfile_pp));
startActivity(intent, options.toBundle());
});
AccountsVM accountsVM = new ViewModelProvider(AboutActivity.this).get(AccountsVM.class);
List<String> ids = new ArrayList<>();

View file

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

View file

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

View file

@ -60,53 +60,64 @@ public class ActionActivity extends BaseBarActivity {
private void displayTimeline(Timeline.TimeLineEnum type) {
canGoBack = true;
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) {
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, () -> {
fragmentMastodonDomainBlock = new FragmentMastodonDomainBlock();
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragmentMastodonDomainBlock);
fragmentTransaction.commit();
});
} 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) {
case MUTED_TIMELINE -> setTitle(R.string.muted_menu);
case FAVOURITE_TIMELINE -> setTitle(R.string.favourite);
case BLOCKED_TIMELINE -> setTitle(R.string.blocked_menu);
case BOOKMARK_TIMELINE -> setTitle(R.string.bookmarks);
case BLOCKED_DOMAIN_TIMELINE -> setTitle(R.string.blocked_domains);
case MUTED_TIMELINE_HOME -> setTitle(R.string.muted_menu_home);
case MUTED_TIMELINE:
setTitle(R.string.muted_menu);
break;
case FAVOURITE_TIMELINE:
setTitle(R.string.favourite);
break;
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,10 +15,6 @@ package app.fedilab.android.mastodon.activities;
* 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.content.Context;
import android.content.SharedPreferences;
@ -26,16 +22,15 @@ import android.content.res.Configuration;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.preference.PreferenceManager;
import com.google.android.material.color.DynamicColors;
import com.vanniktech.emoji.EmojiManager;
import com.vanniktech.emoji.one.EmojiOneProvider;
@ -57,7 +52,6 @@ public class BaseActivity extends AppCompatActivity {
EmojiManager.install(new EmojiOneProvider());
}
@SuppressLint("RestrictedApi")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@ -75,94 +69,85 @@ public class BaseActivity extends AppCompatActivity {
}
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
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))) {
int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
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");
switch (defaultLight) {
case "LIGHT" -> {
case "LIGHT":
setTheme(R.style.AppTheme);
currentThemeId = R.style.AppTheme;
}
case "SOLARIZED_LIGHT" -> {
break;
case "SOLARIZED_LIGHT":
setTheme(R.style.SolarizedAppTheme);
currentThemeId = R.style.SolarizedAppTheme;
}
break;
}
}
case Configuration.UI_MODE_NIGHT_YES -> {
break;
case Configuration.UI_MODE_NIGHT_YES:
String defaultDark = sharedpreferences.getString(getString(R.string.SET_THEME_DEFAULT_DARK), "DARK");
switch (defaultDark) {
case "DARK" -> {
case "DARK":
setTheme(R.style.AppTheme);
currentThemeId = R.style.AppTheme;
}
case "SOLARIZED_DARK" -> {
break;
case "SOLARIZED_DARK":
setTheme(R.style.SolarizedAppTheme);
currentThemeId = R.style.SolarizedAppTheme;
}
case "BLACK" -> {
break;
case "BLACK":
setTheme(R.style.BlackAppTheme);
currentThemeId = R.style.BlackAppTheme;
}
case "DRACULA" -> {
break;
case "DRACULA":
setTheme(R.style.DraculaAppTheme);
currentThemeId = R.style.DraculaAppTheme;
}
break;
}
}
break;
}
} else {
switch (currentTheme) {
case "LIGHT" -> {
case "LIGHT":
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
setTheme(R.style.AppTheme);
currentThemeId = R.style.AppTheme;
}
case "DARK" -> {
break;
case "DARK":
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
setTheme(R.style.AppTheme);
currentThemeId = R.style.AppTheme;
}
case "SOLARIZED_LIGHT" -> {
break;
case "SOLARIZED_LIGHT":
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
setTheme(R.style.SolarizedAppTheme);
currentThemeId = R.style.SolarizedAppTheme;
}
case "SOLARIZED_DARK" -> {
break;
case "SOLARIZED_DARK":
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
setTheme(R.style.SolarizedAppTheme);
currentThemeId = R.style.SolarizedAppTheme;
}
case "BLACK" -> {
break;
case "BLACK":
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
setTheme(R.style.BlackAppTheme);
currentThemeId = R.style.BlackAppTheme;
}
case "DRACULA" -> {
break;
case "DRACULA":
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
setTheme(R.style.DraculaAppTheme);
currentThemeId = R.style.DraculaAppTheme;
}
break;
}
}
super.onCreate(savedInstanceState);
ThemeHelper.applyThemeColor(this);
boolean dynamicColor = sharedpreferences.getBoolean(getString(R.string.SET_DYNAMICCOLOR), false);
if (dynamicColor) {
DynamicColors.applyToActivityIfAvailable(this);
}
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
ThemeHelper.adjustFontScale(this, getResources().getConfiguration());
}
@ -178,18 +163,11 @@ public class BaseActivity extends AppCompatActivity {
@Override
protected void attachBaseContext(Context newBase) {
if (android.os.Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
final Configuration override = new Configuration();
final Configuration override = new Configuration(newBase.getResources().getConfiguration());
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(newBase);
override.fontScale = prefs.getFloat(newBase.getString(R.string.SET_FONT_SCALE), 1.1f);
applyOverrideConfiguration(override);
}
super.attachBaseContext(newBase);
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.clear();
}
}

View file

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

View file

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

View file

@ -1,518 +0,0 @@
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>. */
import static app.fedilab.android.BaseMainActivity.currentInstance;
import static app.fedilab.android.BaseMainActivity.currentAccount;
import android.content.Intent;
import android.content.SharedPreferences;
@ -29,13 +29,10 @@ import android.view.MenuItem;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.PreferenceManager;
import com.google.android.material.appbar.AppBarLayout;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.regex.Matcher;
@ -46,7 +43,6 @@ import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.databinding.ActivityConversationBinding;
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.exception.DBException;
import app.fedilab.android.mastodon.helper.Helper;
@ -64,16 +60,12 @@ public class ContextActivity extends BaseActivity implements FragmentMastodonCon
Fragment currentFragment;
private Status firstMessage;
private String remote_instance;
private Status focusedStatus;
private String focusedStatusURI;
private boolean checkRemotely;
private ActivityConversationBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityConversationBinding.inflate(getLayoutInflater());
ActivityConversationBinding binding = ActivityConversationBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
ActionBar actionBar = getSupportActionBar();
@ -90,102 +82,51 @@ public class ContextActivity extends BaseActivity implements FragmentMastodonCon
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
manageTopBarScrolling(binding.toolbar, sharedpreferences);
Bundle b = getIntent().getExtras();
displayCW = sharedpreferences.getBoolean(getString(R.string.SET_EXPAND_CW), false);
focusedStatus = null; // or other values
Bundle args = getIntent().getExtras();
if (args != null) {
long bundleId = args.getLong(Helper.ARG_INTENT_ID, -1);
new CachedBundle(ContextActivity.this).getBundle(bundleId, Helper.getCurrentAccount(ContextActivity.this), this::initializeAfterBundle);
} else {
initializeAfterBundle(null);
Status focusedStatus = null; // or other values
if (b != null) {
focusedStatus = (Status) b.getSerializable(Helper.ARG_STATUS);
remote_instance = b.getString(Helper.ARG_REMOTE_INSTANCE, null);
}
}
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) {
if (focusedStatus == null || currentAccount == null || currentAccount.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);
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 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
public boolean onCreateOptionsMenu(@NonNull Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
@ -203,7 +144,7 @@ public class ContextActivity extends BaseActivity implements FragmentMastodonCon
itemDisplayCW.setIcon(R.drawable.ic_outline_remove_red_eye_24);
}
MenuItem action_remote = menu.findItem(R.id.action_remote);
if (remote_instance != null || checkRemotely) {
if (remote_instance != null) {
action_remote.setVisible(false);
} else {
if (firstMessage != null && !firstMessage.visibility.equalsIgnoreCase("direct") && !firstMessage.visibility.equalsIgnoreCase("private")) {
@ -240,67 +181,10 @@ public class ContextActivity extends BaseActivity implements FragmentMastodonCon
}
invalidateOptionsMenu();
} 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) {
Toasty.warning(ContextActivity.this, getString(R.string.toast_try_later), Toasty.LENGTH_SHORT).show();
return;
return true;
}
if (firstMessage.account.acct != null) {
String instance = null;
@ -312,11 +196,11 @@ public class ContextActivity extends BaseActivity implements FragmentMastodonCon
}
if (instance == null) {
Toasty.info(ContextActivity.this, getString(R.string.toast_error_fetch_message), Toasty.LENGTH_SHORT).show();
return;
return true;
}
if (instance.equalsIgnoreCase(MainActivity.currentInstance)) {
Toasty.info(ContextActivity.this, getString(R.string.toast_on_your_instance), Toasty.LENGTH_SHORT).show();
return;
return true;
}
Pattern pattern = Helper.statusIdInUrl;
Matcher matcher = pattern.matcher(firstMessage.uri);
@ -330,17 +214,10 @@ public class ContextActivity extends BaseActivity implements FragmentMastodonCon
statusesVM.getStatus(instance, null, remoteId).observe(ContextActivity.this, status -> {
if (status != null) {
Intent intentContext = new Intent(ContextActivity.this, ContextActivity.class);
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_STATUS, status);
args.putString(Helper.ARG_FOCUSED_STATUS_URI, focusedStatusURI);
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);
});
intentContext.putExtra(Helper.ARG_STATUS, status);
intentContext.putExtra(Helper.ARG_REMOTE_INSTANCE, finalInstance);
intentContext.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intentContext);
} else {
Toasty.warning(ContextActivity.this, getString(R.string.toast_error_fetch_message), Toasty.LENGTH_SHORT).show();
}
@ -352,6 +229,7 @@ public class ContextActivity extends BaseActivity implements FragmentMastodonCon
Toasty.warning(ContextActivity.this, getString(R.string.toast_error_fetch_message), Toasty.LENGTH_SHORT).show();
}
}
return true;
}
@Override

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,
* see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentAccount;
import android.content.SharedPreferences;
import android.net.Uri;
@ -27,7 +28,6 @@ import android.widget.Toast;
import androidx.preference.PreferenceManager;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import app.fedilab.android.R;
@ -36,7 +36,6 @@ 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.Status;
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.customsharing.CustomSharingAsyncTask;
import app.fedilab.android.mastodon.helper.customsharing.CustomSharingResponse;
@ -65,34 +64,23 @@ public class CustomSharingActivity extends BaseBarActivity implements OnCustomSh
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(CustomSharingActivity.this);
binding = ActivityCustomSharingBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
Bundle args = getIntent().getExtras();
Bundle b = getIntent().getExtras();
status = null;
if (args != null) {
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 (b != null) {
status = (Status) b.getSerializable(Helper.ARG_STATUS);
}
if (status == null) {
finish();
return;
}
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(CustomSharingActivity.this);
bundle_creator = status.account.acct;
bundle_url = status.url;
bundle_id = status.uri;
@ -109,7 +97,7 @@ public class CustomSharingActivity extends BaseBarActivity implements OnCustomSh
bundle_thumbnailurl = status.account.avatar;
}
if (!bundle_creator.contains("@")) {
bundle_creator = bundle_creator + "@" + Helper.getCurrentAccount(CustomSharingActivity.this).instance;
bundle_creator = bundle_creator + "@" + currentAccount.instance;
}
binding.setCustomSharingTitle.setEllipsize(TextUtils.TruncateAt.END);
@ -138,7 +126,7 @@ public class CustomSharingActivity extends BaseBarActivity implements OnCustomSh
binding.setCustomSharingSave.setOnClickListener(v -> {
// obtain title, description, keywords
title = binding.setCustomSharingTitle.getText().toString();
keywords = Objects.requireNonNull(binding.setCustomSharingKeywords.getText()).toString();
keywords = binding.setCustomSharingKeywords.getText().toString();
CharSequence comma_only = ",";
CharSequence space_only = " ";
CharSequence double_space = " ";
@ -206,38 +194,38 @@ public class CustomSharingActivity extends BaseBarActivity implements OnCustomSh
String param_value = uri.getQueryParameter(param_name);
if (param_value != null)
switch (param_value) {
case "${url}" -> {
case "${url}":
paramFound = true;
builder.appendQueryParameter(param_name, bundle_url);
}
case "${title}" -> {
break;
case "${title}":
paramFound = true;
builder.appendQueryParameter(param_name, title);
}
case "${source}" -> {
break;
case "${source}":
paramFound = true;
builder.appendQueryParameter(param_name, bundle_source);
}
case "${id}" -> {
break;
case "${id}":
paramFound = true;
builder.appendQueryParameter(param_name, bundle_id);
}
case "${description}" -> {
break;
case "${description}":
paramFound = true;
builder.appendQueryParameter(param_name, bundle_content);
}
case "${keywords}" -> {
break;
case "${keywords}":
paramFound = true;
builder.appendQueryParameter(param_name, keywords);
}
case "${creator}" -> {
break;
case "${creator}":
paramFound = true;
builder.appendQueryParameter(param_name, bundle_creator);
}
case "${thumbnailurl}" -> {
break;
case "${thumbnailurl}":
paramFound = true;
builder.appendQueryParameter(param_name, bundle_thumbnailurl);
}
break;
}
if (!paramFound) {
builder.appendQueryParameter(param_name, param_value);

View file

@ -15,6 +15,7 @@ package app.fedilab.android.mastodon.activities;
* see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentAccount;
import static app.fedilab.android.mastodon.activities.ComposeActivity.PICK_MEDIA;
import android.content.ClipData;
@ -38,7 +39,6 @@ import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.databinding.ActivityDirectMessageBinding;
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.exception.DBException;
import app.fedilab.android.mastodon.helper.Helper;
@ -53,6 +53,7 @@ public class DirectMessageActivity extends BaseActivity implements FragmentMasto
public static boolean displayCW;
FragmentMastodonDirectMessage currentFragment;
private Status firstMessage;
private String remote_instance;
@Override
@ -71,72 +72,54 @@ public class DirectMessageActivity extends BaseActivity implements FragmentMasto
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(this);
float scale = sharedpreferences.getFloat(getString(R.string.SET_FONT_SCALE), 1.1f);
binding.title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18 * 1.1f / scale);
MastodonHelper.loadPPMastodon(binding.profilePicture, Helper.getCurrentAccount(DirectMessageActivity.this).mastodon_account);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
Bundle args = getIntent().getExtras();
Bundle b = getIntent().getExtras();
displayCW = sharedpreferences.getBoolean(getString(R.string.SET_EXPAND_CW), false);
if (args != null) {
long bundleId = args.getLong(Helper.ARG_INTENT_ID, -1);
new CachedBundle(DirectMessageActivity.this).getBundle(bundleId, Helper.getCurrentAccount(DirectMessageActivity.this), this::initializeAfterBundle);
} else {
initializeAfterBundle(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 (b != null) {
focusedStatus = (Status) b.getSerializable(Helper.ARG_STATUS);
remote_instance = b.getString(Helper.ARG_REMOTE_INSTANCE, null);
}
if (focusedStatus == null || Helper.getCurrentAccount(DirectMessageActivity.this) == null || Helper.getCurrentAccount(DirectMessageActivity.this).mastodon_account == null) {
if (focusedStatus == null || currentAccount == null || currentAccount.mastodon_account == null) {
finish();
return;
}
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();
}
});
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();
}
});
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
@ -166,6 +149,7 @@ public class DirectMessageActivity extends BaseActivity implements FragmentMasto
@Override
public void get(Status status) {
firstMessage = status;
invalidateOptionsMenu();
}
}

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,
* see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentAccount;
import android.content.SharedPreferences;
import android.os.Bundle;
@ -44,7 +45,6 @@ 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.app.StatusDraft;
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.viewmodel.mastodon.TimelinesVM;
@ -82,7 +82,7 @@ public class DraftActivity extends BaseActivity implements StatusDraftAdapter.Dr
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
timelinesVM = new ViewModelProvider(DraftActivity.this).get(TimelinesVM.class);
timelinesVM.getDrafts(Helper.getCurrentAccount(DraftActivity.this))
timelinesVM.getDrafts(currentAccount)
.observe(DraftActivity.this, this::initializeDraftView);
}
@ -178,7 +178,7 @@ public class DraftActivity extends BaseActivity implements StatusDraftAdapter.Dr
super.onResume();
//We need to check if drafts changed (ie when coming back from the compose activity)
if (statusDrafts != null && timelinesVM != null) {
timelinesVM.getDrafts(Helper.getCurrentAccount(DraftActivity.this))
timelinesVM.getDrafts(currentAccount)
.observe(DraftActivity.this, this::updateDrafts);
}
}

View file

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

View file

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

View file

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

View file

@ -21,7 +21,6 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentManager;
@ -32,9 +31,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
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.R;
@ -77,7 +73,6 @@ public class FollowedTagActivity extends BaseBarActivity implements FollowedTagA
.observe(FollowedTagActivity.this, tags -> {
if (tags != null && tags.tags != null && tags.tags.size() > 0) {
tagList = new ArrayList<>(tags.tags);
sortAsc(tagList);
followedTagAdapter = new FollowedTagAdapter(tagList);
followedTagAdapter.actionOnTag = this;
binding.notContent.setVisibility(View.GONE);
@ -87,29 +82,12 @@ public class FollowedTagActivity extends BaseBarActivity implements FollowedTagA
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
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
getOnBackPressedDispatcher().onBackPressed();
onBackPressed();
return true;
} else if (item.getItemId() == R.id.action_unfollow && tag != null) {
AlertDialog.Builder alt_bld = new MaterialAlertDialogBuilder(FollowedTagActivity.this);
@ -149,11 +127,6 @@ public class FollowedTagActivity extends BaseBarActivity implements FollowedTagA
dialogBuilder.setView(popupAddFollowedTagtBinding.getRoot());
popupAddFollowedTagtBinding.addTag.setFilters(new InputFilter[]{new InputFilter.LengthFilter(255)});
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) {
tagVM.follow(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, popupAddFollowedTagtBinding.addTag.getText().toString().trim())
.observe(FollowedTagActivity.this, newTag -> {
@ -170,10 +143,8 @@ public class FollowedTagActivity extends BaseBarActivity implements FollowedTagA
if (newTag != null) {
tagList.add(0, newTag);
followedTagAdapter.notifyItemInserted(0);
sortAsc(tagList);
followedTagAdapter.notifyItemRangeChanged(0, tagList.size());
} else {
Toasty.error(FollowedTagActivity.this, getString(R.string.not_valid_tag_name), Toasty.LENGTH_LONG).show();
Toasty.error(FollowedTagActivity.this, getString(R.string.toast_feature_not_supported), Toasty.LENGTH_LONG).show();
}
});
dialog.dismiss();
@ -188,9 +159,6 @@ public class FollowedTagActivity extends BaseBarActivity implements FollowedTagA
return super.onOptionsItemSelected(item);
}
private void sortAsc(List<Tag> tagList) {
Collections.sort(tagList, (obj1, obj2) -> obj1.name.compareToIgnoreCase(obj2.name));
}
@Override
public boolean onCreateOptionsMenu(@NonNull Menu menu) {
@ -202,25 +170,39 @@ public class FollowedTagActivity extends BaseBarActivity implements FollowedTagA
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
public void click(Tag tag) {
this.tag = tag;
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, () -> {
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,6 +15,8 @@ package app.fedilab.android.mastodon.activities;
* see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentAccount;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
@ -26,6 +28,7 @@ import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.ViewModelProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
@ -33,13 +36,11 @@ import java.util.ArrayList;
import java.util.List;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.BuildConfig;
import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.databinding.ActivityHashtagBinding;
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.app.CachedBundle;
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.StatusDraft;
@ -68,31 +69,20 @@ public class HashTagActivity extends BaseActivity {
private Filter.KeywordsAttributes keyword;
private PinnedTimeline pinnedTimeline;
private Pinned pinned;
private ActivityHashtagBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityHashtagBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
Bundle args = getIntent().getExtras();
if (args != 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);
}
}
ActivityHashtagBinding binding = ActivityHashtagBinding.inflate(getLayoutInflater());
private void initializeAfterBundle(Bundle bundle) {
if (bundle != null) {
tag = bundle.getString(Helper.ARG_SEARCH_KEYWORD, null);
setContentView(binding.getRoot());
Bundle b = getIntent().getExtras();
if (b != null) {
tag = b.getString(Helper.ARG_SEARCH_KEYWORD, null);
}
if (tag == null) {
if (tag == null)
finish();
return;
}
pinnedTag = null;
followedTag = null;
mutedTag = null;
@ -156,10 +146,10 @@ public class HashTagActivity extends BaseActivity {
invalidateOptionsMenu();
}
Bundle bundleFragment = new Bundle();
bundleFragment.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.TAG);
bundleFragment.putString(Helper.ARG_SEARCH_KEYWORD, tag);
Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_tags, new FragmentMastodonTimeline(), bundleFragment, null, null);
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.TAG);
bundle.putString(Helper.ARG_SEARCH_KEYWORD, tag);
Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_tags, new FragmentMastodonTimeline(), bundle, null, null);
binding.compose.setOnClickListener(v -> {
Intent intentToot = new Intent(HashTagActivity.this, ComposeActivity.class);
StatusDraft statusDraft = new StatusDraft();
@ -168,14 +158,10 @@ public class HashTagActivity extends BaseActivity {
List<Status> statuses = new ArrayList<>();
statuses.add(status);
statusDraft.statusDraftList = statuses;
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_STATUS_DRAFT, statusDraft);
new CachedBundle(HashTagActivity.this).insertBundle(args, Helper.getCurrentAccount(HashTagActivity.this), bundleId -> {
Bundle bundleCached = new Bundle();
bundleCached.putLong(Helper.ARG_INTENT_ID, bundleId);
intentToot.putExtras(bundleCached);
startActivity(intentToot);
});
Bundle _b = new Bundle();
_b.putSerializable(Helper.ARG_STATUS_DRAFT, statusDraft);
intentToot.putExtras(_b);
startActivity(intentToot);
});
}
@ -202,24 +188,18 @@ public class HashTagActivity extends BaseActivity {
}
pinnedTag = false;
invalidateOptionsMenu();
Bundle args = new Bundle();
args.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Bundle b = new Bundle();
b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Intent intentBD = new Intent(Helper.BROADCAST_DATA);
new CachedBundle(HashTagActivity.this).insertBundle(args, Helper.getCurrentAccount(HashTagActivity.this), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentBD.putExtras(bundle);
intentBD.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intentBD);
dialog.dismiss();
});
intentBD.putExtras(b);
LocalBroadcastManager.getInstance(HashTagActivity.this).sendBroadcast(intentBD);
dialog.dismiss();
});
unpinConfirm.show();
} else {
new Thread(() -> {
try {
Pinned pinned = new Pinned(HashTagActivity.this).getPinned(Helper.getCurrentAccount(HashTagActivity.this));
Pinned pinned = new Pinned(HashTagActivity.this).getPinned(currentAccount);
boolean canBeAdded = true;
boolean update = true;
if (pinned == null) {
@ -262,16 +242,11 @@ public class HashTagActivity extends BaseActivity {
} else {
new Pinned(HashTagActivity.this).insertPinned(pinned);
}
Bundle args = new Bundle();
args.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Bundle b = new Bundle();
b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Intent intentBD = new Intent(Helper.BROADCAST_DATA);
new CachedBundle(HashTagActivity.this).insertBundle(args, Helper.getCurrentAccount(HashTagActivity.this), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentBD.putExtras(bundle);
intentBD.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intentBD);
});
intentBD.putExtras(b);
LocalBroadcastManager.getInstance(HashTagActivity.this).sendBroadcast(intentBD);
pinnedTag = true;
invalidateOptionsMenu();
} catch (DBException e) {

View file

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

View file

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

View file

@ -15,7 +15,6 @@ package app.fedilab.android.mastodon.activities;
* see <http://www.gnu.org/licenses>. */
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
@ -26,12 +25,12 @@ import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -40,17 +39,15 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
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.BuildConfig;
import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.databinding.ActivityListBinding;
import app.fedilab.android.databinding.PopupAddListBinding;
import app.fedilab.android.databinding.PopupManageAccountsListBinding;
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.app.CachedBundle;
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.Timeline;
@ -129,24 +126,6 @@ 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();
}
}
});
}
@ -162,11 +141,10 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
invalidateOptionsMenu();
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
getOnBackPressedDispatcher().onBackPressed();
onBackPressed();
return true;
} else if (item.getItemId() == R.id.action_user_mute_home) {
AlertDialog.Builder dialogBuilder = new MaterialAlertDialogBuilder(MastodonListActivity.this);
@ -175,7 +153,7 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
timelinesVM.getAccountsInList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, mastodonList.id, null, null, 0)
.observe(MastodonListActivity.this, accounts -> {
if (accounts != null && accounts.size() > 0) {
accountsVM.muteAccountsHome(Helper.getCurrentAccount(MastodonListActivity.this), accounts);
accountsVM.muteAccountsHome(MainActivity.currentAccount, accounts);
}
});
dialog.dismiss();
@ -307,17 +285,12 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
} else {
binding.notContent.setVisibility(View.GONE);
}
Bundle args = new Bundle();
args.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Bundle b = new Bundle();
b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Intent intentBD = new Intent(Helper.BROADCAST_DATA);
args.putSerializable(Helper.RECEIVE_MASTODON_LIST, mastodonListList);
new CachedBundle(MastodonListActivity.this).insertBundle(args, Helper.getCurrentAccount(MastodonListActivity.this), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentBD.putExtras(bundle);
intentBD.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intentBD);
});
b.putSerializable(Helper.RECEIVE_MASTODON_LIST, mastodonListList);
intentBD.putExtras(b);
LocalBroadcastManager.getInstance(MastodonListActivity.this).sendBroadcast(intentBD);
});
alt_bld.setNegativeButton(R.string.cancel, (dialog, id) -> dialog.dismiss());
AlertDialog alert = alt_bld.create();
@ -347,17 +320,12 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
} else {
Toasty.error(MastodonListActivity.this, getString(R.string.toast_error), Toasty.LENGTH_LONG).show();
}
Bundle args = new Bundle();
args.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Bundle b = new Bundle();
b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Intent intentBD = new Intent(Helper.BROADCAST_DATA);
args.putSerializable(Helper.RECEIVE_MASTODON_LIST, mastodonListList);
new CachedBundle(MastodonListActivity.this).insertBundle(args, Helper.getCurrentAccount(MastodonListActivity.this), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentBD.putExtras(bundle);
intentBD.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intentBD);
});
b.putSerializable(Helper.RECEIVE_MASTODON_LIST, mastodonListList);
intentBD.putExtras(b);
LocalBroadcastManager.getInstance(MastodonListActivity.this).sendBroadcast(intentBD);
});
dialog.dismiss();
} else {
@ -373,7 +341,7 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
dialogBuilder.setView(popupAddListBinding.getRoot());
popupAddListBinding.addList.setFilters(new InputFilter[]{new InputFilter.LengthFilter(255)});
popupAddListBinding.addList.setText(mastodonList.title);
popupAddListBinding.addList.setSelection(Objects.requireNonNull(popupAddListBinding.addList.getText()).length());
popupAddListBinding.addList.setSelection(popupAddListBinding.addList.getText().length());
dialogBuilder.setPositiveButton(R.string.validate, (dialog, id) -> {
if (popupAddListBinding.addList.getText() != null && popupAddListBinding.addList.getText().toString().trim().length() > 0) {
timelinesVM.updateList(
@ -400,18 +368,12 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
new Thread(() -> {
try {
new Pinned(MastodonListActivity.this).updatePinned(pinned);
Bundle args = new Bundle();
args.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Bundle b = new Bundle();
b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Intent intentBD = new Intent(Helper.BROADCAST_DATA);
new CachedBundle(MastodonListActivity.this).insertBundle(args, Helper.getCurrentAccount(MastodonListActivity.this), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentBD.putExtras(bundle);
intentBD.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intentBD);
});
} catch (
DBException e) {
intentBD.putExtras(b);
LocalBroadcastManager.getInstance(MastodonListActivity.this).sendBroadcast(intentBD);
} catch (DBException e) {
e.printStackTrace();
}
}).start();
@ -459,20 +421,19 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
this.mastodonList = mastodonList;
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, () -> {
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();
});
}
@ -493,4 +454,20 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
}
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,8 +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,
* see <http://www.gnu.org/licenses>. */
import static android.util.Patterns.WEB_URL;
import android.Manifest;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
@ -23,26 +21,17 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
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.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
@ -51,19 +40,16 @@ import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.preference.PreferenceManager;
import androidx.viewpager.widget.ViewPager;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.regex.Matcher;
import app.fedilab.android.R;
import app.fedilab.android.databinding.ActivityMediaPagerBinding;
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.app.CachedBundle;
import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.MediaHelper;
import app.fedilab.android.mastodon.helper.TranslateHelper;
@ -73,7 +59,7 @@ import app.fedilab.android.mastodon.ui.fragment.media.FragmentMediaProfile;
import es.dmoral.toasty.Toasty;
public class MediaActivity extends BaseBarActivity implements OnDownloadInterface {
public class MediaActivity extends BaseTransparentActivity implements OnDownloadInterface {
int flags;
private ArrayList<Attachment> attachments;
@ -83,10 +69,10 @@ public class MediaActivity extends BaseBarActivity implements OnDownloadInterfac
private final BroadcastReceiver onDownloadComplete = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
if (downloadID == id) {
DownloadManager manager = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE);
assert manager != null;
Uri uri = manager.getUriForDownloadedFile(downloadID);
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
@ -95,8 +81,7 @@ public class MediaActivity extends BaseBarActivity implements OnDownloadInterfac
shareIntent.setType(cR.getType(uri));
try {
startActivity(shareIntent);
} catch (Exception e) {
e.printStackTrace();
} catch (Exception ignored) {
}
} else {
Toasty.error(context, context.getString(R.string.toast_error), Toasty.LENGTH_LONG).show();
@ -118,7 +103,6 @@ public class MediaActivity extends BaseBarActivity implements OnDownloadInterfac
getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
ActivityCompat.postponeEnterTransition(MediaActivity.this);
binding = ActivityMediaPagerBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
@ -126,29 +110,13 @@ public class MediaActivity extends BaseBarActivity implements OnDownloadInterfac
fullscreen = false;
flags = getWindow().getDecorView().getSystemUiVisibility();
Bundle args = getIntent().getExtras();
if (args != null) {
long bundleId = args.getLong(Helper.ARG_INTENT_ID, -1);
new CachedBundle(MediaActivity.this).getBundle(bundleId, Helper.getCurrentAccount(MediaActivity.this), this::initializeAfterBundle);
} else {
initializeAfterBundle(null);
Bundle b = getIntent().getExtras();
if (b != null) {
mediaPosition = b.getInt(Helper.ARG_MEDIA_POSITION, 1);
attachments = (ArrayList<Attachment>) b.getSerializable(Helper.ARG_MEDIA_ARRAY);
mediaFromProfile = b.getBoolean(Helper.ARG_MEDIA_ARRAY_PROFILE, false);
status = (Status) b.getSerializable(Helper.ARG_STATUS);
}
}
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) {
attachments = new ArrayList<>();
attachments.addAll(FragmentMediaProfile.mediaAttachmentProfile);
@ -164,45 +132,32 @@ public class MediaActivity extends BaseBarActivity implements OnDownloadInterfac
}
setTitle("");
ScreenSlidePagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
binding.mediaViewpager.setAdapter(mPagerAdapter);
binding.mediaViewpager.setSaveEnabled(false);
binding.mediaViewpager.setCurrentItem(mediaPosition - 1);
ContextCompat.registerReceiver(MediaActivity.this, onDownloadComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), ContextCompat.RECEIVER_EXPORTED);
registerReceiver(onDownloadComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
String description = attachments.get(mediaPosition - 1).description;
handler = new Handler();
if (attachments.get(mediaPosition - 1).status != null) {
binding.originalMessage.setOnClickListener(v -> {
Intent intentContext = new Intent(MediaActivity.this, ContextActivity.class);
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_STATUS, attachments.get(mediaPosition - 1).status);
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);
});
intentContext.putExtra(Helper.ARG_STATUS, attachments.get(mediaPosition - 1).status);
intentContext.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intentContext);
});
}
binding.mediaDescription.setMovementMethod(LinkMovementMethod.getInstance());
binding.mediaDescriptionTranslated.setMovementMethod(LinkMovementMethod.getInstance());
if (description != null && !description.trim().isEmpty() && description.trim().compareTo("null") != 0) {
if (description != null && description.trim().length() > 0 && description.trim().compareTo("null") != 0) {
binding.mediaDescription.setText(description);
binding.translate.setOnClickListener(v -> {
String descriptionToTranslate = attachments.get(mediaPosition - 1).description;
TranslateHelper.translate(MediaActivity.this, descriptionToTranslate, status!=null?status.language:"en", translated -> {
TranslateHelper.translate(MediaActivity.this, descriptionToTranslate, translated -> {
if (translated != null) {
attachments.get(mediaPosition - 1).translation = translated;
binding.mediaDescriptionTranslated.setText(translated);
binding.mediaDescriptionTranslated.setVisibility(View.VISIBLE);
binding.mediaDescription.setVisibility(View.GONE);
if (mCurrentFragment != null) {
mCurrentFragment.toggleController(false);
}
binding.translate.setVisibility(View.GONE);
} else {
Toasty.error(MediaActivity.this, getString(R.string.toast_error_translate), Toast.LENGTH_LONG).show();
}
@ -210,16 +165,10 @@ public class MediaActivity extends BaseBarActivity implements OnDownloadInterfac
});
if (attachments.get(mediaPosition - 1).translation != null) {
binding.mediaDescription.setVisibility(View.GONE);
if (mCurrentFragment != null) {
mCurrentFragment.toggleController(false);
}
binding.mediaDescriptionTranslated.setText(attachments.get(mediaPosition - 1).translation);
binding.mediaDescriptionTranslated.setVisibility(View.VISIBLE);
} else {
binding.mediaDescription.setVisibility(View.VISIBLE);
if (mCurrentFragment != null) {
mCurrentFragment.toggleController(true);
}
binding.mediaDescriptionTranslated.setVisibility(View.GONE);
}
}
@ -233,29 +182,22 @@ public class MediaActivity extends BaseBarActivity implements OnDownloadInterfac
public void onPageSelected(int position) {
mediaPosition = position;
if(mediaPosition < 1 ) {
mediaPosition = 1;
}
String description = attachments.get(position).description;
if (handler != null) {
handler.removeCallbacksAndMessages(null);
}
handler = new Handler();
if (description != null && !description.trim().isEmpty() && description.trim().compareTo("null") != 0) {
binding.mediaDescription.setText(linkify(MediaActivity.this, description), TextView.BufferType.SPANNABLE);
if (description != null && description.trim().length() > 0 && description.trim().compareTo("null") != 0) {
binding.mediaDescription.setText(description);
}
binding.translate.setOnClickListener(v -> {
String descriptionToTranslate = attachments.get(position).description;
TranslateHelper.translate(MediaActivity.this, descriptionToTranslate, status!=null?status.language:"en", translated -> {
TranslateHelper.translate(MediaActivity.this, descriptionToTranslate, translated -> {
if (translated != null) {
attachments.get(position).translation = translated;
binding.mediaDescriptionTranslated.setText(translated);
binding.mediaDescriptionTranslated.setVisibility(View.VISIBLE);
binding.mediaDescription.setVisibility(View.GONE);
if (mCurrentFragment != null) {
mCurrentFragment.toggleController(true);
}
binding.translate.setVisibility(View.GONE);
} else {
Toasty.error(MediaActivity.this, getString(R.string.toast_error_translate), Toast.LENGTH_LONG).show();
}
@ -264,23 +206,14 @@ public class MediaActivity extends BaseBarActivity implements OnDownloadInterfac
if (!fullscreen) {
if (attachments.get(position).translation != null) {
binding.mediaDescription.setVisibility(View.GONE);
if (mCurrentFragment != null) {
mCurrentFragment.toggleController(false);
}
binding.mediaDescriptionTranslated.setText(attachments.get(position).translation);
binding.mediaDescriptionTranslated.setVisibility(View.VISIBLE);
} else {
binding.mediaDescription.setVisibility(View.VISIBLE);
if (mCurrentFragment != null) {
mCurrentFragment.toggleController(true);
}
binding.mediaDescriptionTranslated.setVisibility(View.GONE);
}
} else {
binding.mediaDescription.setVisibility(View.GONE);
if (mCurrentFragment != null) {
mCurrentFragment.toggleController(false);
}
binding.mediaDescriptionTranslated.setVisibility(View.GONE);
}
}
@ -288,41 +221,6 @@ public class MediaActivity extends BaseBarActivity implements OnDownloadInterfac
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
public boolean dispatchTouchEvent(MotionEvent event) {
try {
@ -348,10 +246,7 @@ public class MediaActivity extends BaseBarActivity implements OnDownloadInterfac
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
try {
ActivityCompat.finishAfterTransition(MediaActivity.this);
} catch (Exception ignored) {
}
ActivityCompat.finishAfterTransition(MediaActivity.this);
return true;
} else if (item.getItemId() == R.id.action_save) {
int position = binding.mediaViewpager.getCurrentItem();
@ -433,7 +328,7 @@ public class MediaActivity extends BaseBarActivity implements OnDownloadInterfac
if (!fullscreen) {
String description = attachments.get(binding.mediaViewpager.getCurrentItem()).description;
if (description != null && description.trim().length() > 0 && description.trim().compareTo("null") != 0) {
binding.mediaDescription.setText(linkify(MediaActivity.this, description), TextView.BufferType.SPANNABLE);
binding.mediaDescription.setText(description);
if (attachments.get(binding.mediaViewpager.getCurrentItem()).translation != null) {
binding.mediaDescription.setVisibility(View.GONE);
binding.mediaDescriptionTranslated.setText(attachments.get(binding.mediaViewpager.getCurrentItem()).translation);
@ -463,16 +358,13 @@ public class MediaActivity extends BaseBarActivity implements OnDownloadInterfac
@Override
public void onDestroy() {
try {
unregisterReceiver(onDownloadComplete);
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
unregisterReceiver(onDownloadComplete);
super.onDestroy();
}
@Override
public void onDownloaded(String saveFilePath, String downloadUrl, Error error) {
}
@Override
@ -503,22 +395,14 @@ public class MediaActivity extends BaseBarActivity implements OnDownloadInterfac
this.fullscreen = fullscreen;
if (!fullscreen) {
showSystemUI();
binding.descriptionContainer.setVisibility(View.VISIBLE);
binding.mediaDescription.setVisibility(View.VISIBLE);
if (mCurrentFragment != null) {
mCurrentFragment.toggleController(true);
}
binding.translate.setVisibility(View.VISIBLE);
if (mediaFromProfile) {
binding.originalMessage.setVisibility(View.VISIBLE);
}
} else {
hideSystemUI();
binding.descriptionContainer.setVisibility(View.GONE);
binding.mediaDescription.setVisibility(View.GONE);
if (mCurrentFragment != null) {
mCurrentFragment.toggleController(false);
}
binding.translate.setVisibility(View.GONE);
binding.originalMessage.setVisibility(View.INVISIBLE);
}

View file

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

View file

@ -15,9 +15,7 @@ package app.fedilab.android.mastodon.activities;
* see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.mastodon.helper.LogoHelper.getMainLogo;
import static app.fedilab.android.BaseMainActivity.currentAccount;
import android.content.BroadcastReceiver;
import android.content.ClipData;
@ -27,28 +25,20 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.text.style.ForegroundColorSpan;
import android.text.style.UnderlineSpan;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.inputmethod.EditorInfo;
import android.webkit.URLUtil;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
@ -59,10 +49,11 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.TooltipCompat;
import androidx.core.app.ActivityCompat;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -70,41 +61,31 @@ import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
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.tabs.TabLayout;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
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.BuildConfig;
import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.databinding.ActivityProfileBinding;
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.Attachment;
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.IdentityProof;
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.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.Pinned;
import app.fedilab.android.mastodon.client.entities.app.PinnedTimeline;
@ -119,7 +100,6 @@ import app.fedilab.android.mastodon.helper.SpannableHelper;
import app.fedilab.android.mastodon.helper.ThemeHelper;
import app.fedilab.android.mastodon.ui.drawer.FieldAdapter;
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.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.mastodon.viewmodel.mastodon.NodeInfoVM;
@ -134,6 +114,7 @@ public class ProfileActivity extends BaseActivity {
private RelationShip relationship;
private FamiliarFollowers familiarFollowers;
private Account account;
private ScheduledExecutorService scheduledExecutorService;
private action doAction;
private AccountsVM accountsVM;
private RecyclerView identityProofsRecycler;
@ -142,23 +123,18 @@ public class ProfileActivity extends BaseActivity {
private String account_id;
private String mention_str;
private WellKnownNodeinfo.NodeInfo nodeInfo;
private boolean checkRemotely;
private final BroadcastReceiver broadcast_data = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Bundle args = intent.getExtras();
if (args != null) {
long bundleId = args.getLong(Helper.ARG_INTENT_ID, -1);
new CachedBundle(ProfileActivity.this).getBundle(bundleId, Helper.getCurrentAccount(ProfileActivity.this), bundle -> {
Account accountReceived = (Account) bundle.getSerializable(Helper.ARG_ACCOUNT);
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);
}
Bundle b = intent.getExtras();
if (b != null) {
Account accountReceived = (Account) b.getSerializable(Helper.ARG_ACCOUNT);
if (b.getBoolean(Helper.RECEIVE_REDRAW_PROFILE, false) && accountReceived != null) {
if (account != null && accountReceived.id != null && account.id != null && accountReceived.id.equalsIgnoreCase(account.id)) {
initializeView(accountReceived);
}
});
}
}
}
};
@ -172,14 +148,21 @@ public class ProfileActivity extends BaseActivity {
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
ActionBar actionBar = getSupportActionBar();
Bundle args = getIntent().getExtras();
Bundle b = getIntent().getExtras();
binding.accountFollow.setEnabled(false);
checkRemotely = false;
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) {
checkRemotely = sharedpreferences.getBoolean(getString(R.string.SET_PROFILE_REMOTELY), false);
}
ActivityCompat.postponeEnterTransition(ProfileActivity.this);
//Remove title
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(false);
@ -193,22 +176,6 @@ public class ProfileActivity extends BaseActivity {
float scale = sharedpreferences.getFloat(getString(R.string.SET_FONT_SCALE), 1.1f);
binding.title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18 * 1.1f / scale);
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) {
initializeView(account);
} else if (account_id != null) {
@ -218,7 +185,7 @@ public class ProfileActivity extends BaseActivity {
});
} else if (mention_str != null) {
accountsVM.searchAccounts(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, mention_str, 1, true, false).observe(ProfileActivity.this, accounts -> {
if (accounts != null && !accounts.isEmpty()) {
if (accounts != null && accounts.size() > 0) {
account = accounts.get(0);
initializeView(account);
} else {
@ -231,49 +198,8 @@ public class ProfileActivity extends BaseActivity {
finish();
}
//Check if account is homeMuted
accountsVM.isMuted(Helper.getCurrentAccount(ProfileActivity.this), account).observe(this, result -> homeMuted = result != null && result);
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);
}
});
accountsVM.isMuted(currentAccount, account).observe(this, result -> homeMuted = result != null && result);
LocalBroadcastManager.getInstance(ProfileActivity.this).registerReceiver(broadcast_data, new IntentFilter(Helper.BROADCAST_DATA));
}
@ -289,18 +215,14 @@ public class ProfileActivity extends BaseActivity {
TabLayout.Tab followerTab = binding.accountTabLayout.getTabAt(2);
if (statusTab != null) {
statusTab.setText(getString(R.string.status_cnt, Helper.withSuffix(account.statuses_count)));
TooltipCompat.setTooltipText(statusTab.view, String.valueOf(account.statuses_count));
}
if (followingTab != null) {
followingTab.setText(getString(R.string.following_cnt, Helper.withSuffix(account.following_count)));
TooltipCompat.setTooltipText(followingTab.view, String.valueOf(account.following_count));
}
if (followerTab != null) {
followerTab.setText(getString(R.string.followers_cnt, Helper.withSuffix(account.followers_count)));
TooltipCompat.setTooltipText(followerTab.view, String.valueOf(account.followers_count));
}
}
}
}
@ -329,64 +251,19 @@ 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
List<String> accountListToCheck = new ArrayList<>();
accountListToCheck.add(account.id);
//Retrieve relation ship
accountsVM.getRelationships(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, accountListToCheck).observe(ProfileActivity.this, relationShips -> {
if (relationShips != null && !relationShips.isEmpty()) {
if (relationShips != null && relationShips.size() > 0) {
this.relationship = relationShips.get(0);
updateAccount();
}
});
accountsVM.getFamiliarFollowers(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, accountListToCheck).observe(ProfileActivity.this, familiarFollowersList -> {
if (familiarFollowersList != null && !familiarFollowersList.isEmpty()) {
if (familiarFollowersList != null && familiarFollowersList.size() > 0) {
this.familiarFollowers = familiarFollowersList.get(0);
updateAccount();
}
@ -397,48 +274,24 @@ public class ProfileActivity extends BaseActivity {
this.identityProofList = identityProofs;
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.removeAllTabs();
//Tablayout for timelines/following/followers
FedilabProfileTLPageAdapter fedilabProfileTLPageAdapter = new FedilabProfileTLPageAdapter(getSupportFragmentManager(), account, checkRemotely);
TabProfileCustomViewBinding tabMessagesView = TabProfileCustomViewBinding.inflate(getLayoutInflater());
TabProfileCustomViewBinding tabFollowingView = TabProfileCustomViewBinding.inflate(getLayoutInflater());
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.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.status_cnt, Helper.withSuffix(account.statuses_count))));
binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.following_cnt, Helper.withSuffix(account.following_count))));
binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.followers_cnt, Helper.withSuffix(account.followers_count))));
binding.accountViewpager.setAdapter(fedilabProfileTLPageAdapter);
binding.accountViewpager.setOffscreenPageLimit(3);
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() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
@ -470,28 +323,32 @@ public class ProfileActivity extends BaseActivity {
binding.accountPp.animate();
((Animatable) resource).start();
}
ActivityCompat.startPostponedEnterTransition(ProfileActivity.this);
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
binding.profilePicture.setImageResource(R.drawable.ic_person);
binding.accountPp.setImageResource(R.drawable.ic_person);
ActivityCompat.startPostponedEnterTransition(ProfileActivity.this);
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
binding.profilePicture.setImageResource(R.drawable.ic_person);
binding.accountPp.setImageResource(R.drawable.ic_person);
ActivityCompat.startPostponedEnterTransition(ProfileActivity.this);
}
}
);
//Load header
MastodonHelper.loadProfileMediaMastodon(ProfileActivity.this, binding.bannerPp, account, MastodonHelper.MediaAccountType.HEADER);
//Redraws icon for locked accounts
final float scale = getResources().getDisplayMetrics().density;
if (account.locked) {
Drawable img = ContextCompat.getDrawable(ProfileActivity.this, R.drawable.ic_baseline_lock_24);
assert img != null;
img.setBounds(0, 0, (int) (Helper.convertDpToPixel(14, this) * scale + 0.5f), (int) (Helper.convertDpToPixel(14, this) * scale + 0.5f));
img.setBounds(0, 0, (int) (16 * scale + 0.5f), (int) (16 * scale + 0.5f));
binding.accountUn.setCompoundDrawables(null, null, img, null);
} else {
binding.accountUn.setCompoundDrawables(null, null, null, null);
@ -528,7 +385,7 @@ public class ProfileActivity extends BaseActivity {
binding.accountMoved.setVisibility(View.VISIBLE);
Drawable imgTravel = ContextCompat.getDrawable(ProfileActivity.this, R.drawable.ic_baseline_card_travel_24);
assert imgTravel != null;
imgTravel.setBounds(0, 0, (int) (Helper.convertDpToPixel(20, this) * scale + 0.5f), (int) (Helper.convertDpToPixel(20, this) * scale + 0.5f));
imgTravel.setBounds(0, 0, (int) (20 * scale + 0.5f), (int) (20 * scale + 0.5f));
binding.accountMoved.setCompoundDrawables(imgTravel, null, null, null);
//Retrieves content and make account names clickable
SpannableString spannableString = SpannableHelper.moveToText(ProfileActivity.this, account);
@ -545,27 +402,25 @@ public class ProfileActivity extends BaseActivity {
}
binding.openRemoteProfile.setOnClickListener(v -> {
Intent intent = new Intent(ProfileActivity.this, ProfileActivity.class);
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_ACCOUNT, account);
args.putSerializable(Helper.ARG_CHECK_REMOTELY, true);
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);
finish();
});
Bundle b = new Bundle();
b.putSerializable(Helper.ARG_ACCOUNT, account);
b.putSerializable(Helper.ARG_CHECK_REMOTELY, true);
intent.putExtras(b);
ActivityOptionsCompat options = ActivityOptionsCompat
.makeSceneTransitionAnimation(ProfileActivity.this, binding.profilePicture, getString(R.string.activity_porfile_pp));
startActivity(intent, options.toBundle());
finish();
});
//Fields for profile
List<Field> fields = account.fields;
if (fields != null && !fields.isEmpty()) {
if (fields != null && fields.size() > 0) {
FieldAdapter fieldAdapter = new FieldAdapter(fields, account);
binding.fieldsContainer.setAdapter(fieldAdapter);
binding.fieldsContainer.setLayoutManager(new LinearLayoutManager(ProfileActivity.this));
}
binding.accountDn.setText(
account.getSpanDisplayNameEmoji(ProfileActivity.this,
account.getSpanDisplayName(ProfileActivity.this,
new WeakReference<>(binding.accountDn)),
TextView.BufferType.SPANNABLE);
@ -583,45 +438,14 @@ public class ProfileActivity extends BaseActivity {
});
binding.accountNote.setText(
account.getSpanNote(ProfileActivity.this,
new WeakReference<>(binding.accountNote), () -> {
//TODO: replace this hack
binding.accountNote.setText(
account.getSpanNote(ProfileActivity.this,
new WeakReference<>(binding.accountNote)), TextView.BufferType.SPANNABLE);
}),
new WeakReference<>(binding.accountNote)),
TextView.BufferType.SPANNABLE);
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 -> {
Intent intent = new Intent(ProfileActivity.this, MediaActivity.class);
Bundle args = new Bundle();
Bundle b = new Bundle();
Attachment attachment = new Attachment();
attachment.description = account.acct;
attachment.preview_url = account.avatar;
@ -630,17 +454,13 @@ public class ProfileActivity extends BaseActivity {
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());
});
b.putSerializable(Helper.ARG_MEDIA_ARRAY, attachments);
b.putInt(Helper.ARG_MEDIA_POSITION, 1);
intent.putExtras(b);
ActivityOptionsCompat options = ActivityOptionsCompat
.makeSceneTransitionAnimation(ProfileActivity.this, binding.accountPp, attachment.url);
// start the new activity
startActivity(intent, options.toBundle());
});
@ -733,13 +553,13 @@ public class ProfileActivity extends BaseActivity {
* This methode is called to update the view once an action has been performed
*/
private void updateAccount() {
if (Helper.getCurrentAccount(ProfileActivity.this) == null || account == null) {
if (currentAccount == null || account == null) {
return;
}
//Manage indentity proofs if not yet displayed
if (identityProofList != null && !identityProofList.isEmpty()) {
if (identityProofList != null && identityProofList.size() > 0) {
ImageView identity_proofs_indicator = findViewById(R.id.identity_proofs_indicator);
identity_proofs_indicator.setVisibility(View.VISIBLE);
//Recyclerview for identity proof has not been inflated yet
@ -760,7 +580,7 @@ public class ProfileActivity extends BaseActivity {
}
}
if (familiarFollowers != null && familiarFollowers.accounts != null && !familiarFollowers.accounts.isEmpty()) {
if (familiarFollowers != null && familiarFollowers.accounts != null && familiarFollowers.accounts.size() > 0) {
binding.relatedAccounts.removeAllViews();
for (Account account : familiarFollowers.accounts) {
NotificationsRelatedAccountsBinding notificationsRelatedAccountsBinding = NotificationsRelatedAccountsBinding.inflate(LayoutInflater.from(ProfileActivity.this));
@ -768,15 +588,13 @@ public class ProfileActivity extends BaseActivity {
notificationsRelatedAccountsBinding.acc.setText(account.username);
notificationsRelatedAccountsBinding.relatedAccountContainer.setOnClickListener(v -> {
Intent intent = new Intent(ProfileActivity.this, ProfileActivity.class);
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_ACCOUNT, account);
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);
});
Bundle b = new Bundle();
b.putSerializable(Helper.ARG_ACCOUNT, account);
intent.putExtras(b);
ActivityOptionsCompat options = ActivityOptionsCompat
.makeSceneTransitionAnimation(ProfileActivity.this, notificationsRelatedAccountsBinding.profilePicture, getString(R.string.activity_porfile_pp));
// start the new activity
startActivity(intent, options.toBundle());
});
binding.relatedAccounts.addView(notificationsRelatedAccountsBinding.getRoot());
}
@ -793,18 +611,18 @@ public class ProfileActivity extends BaseActivity {
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) {
binding.accountFollowedBy.setVisibility(View.VISIBLE);
} else {
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) {
binding.accountFollow.setIconResource(R.drawable.ic_baseline_person_remove_24);
binding.accountFollow.setBackgroundTintList(ColorStateList.valueOf(ThemeHelper.getAttColor(this, R.attr.colorError)));
@ -817,23 +635,15 @@ public class ProfileActivity extends BaseActivity {
doAction = action.UNBLOCK;
binding.accountFollow.setVisibility(View.VISIBLE);
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 {
binding.accountFollow.setIconResource(R.drawable.ic_baseline_person_add_24);
doAction = action.FOLLOW;
binding.accountFollow.setVisibility(View.VISIBLE);
binding.accountFollow.setContentDescription(getString(R.string.action_follow));
}
//The value for account is from same server so id can be used
if (account.id.equals(Helper.getCurrentAccount(ProfileActivity.this).user_id)) {
if (account.id.equals(currentAccount.user_id)) {
binding.accountFollow.setVisibility(View.GONE);
binding.headerEditProfile.setVisibility(View.VISIBLE);
binding.headerEditProfile.bringToFront();
@ -1009,8 +819,6 @@ public class ProfileActivity extends BaseActivity {
instanceType = RemoteInstance.InstanceType.PIXELFED;
} else if (nodeInfo.software.name.compareToIgnoreCase("misskey") == 0) {
instanceType = RemoteInstance.InstanceType.MISSKEY;
} else if (nodeInfo.software.name.compareToIgnoreCase("lemmy") == 0) {
instanceType = RemoteInstance.InstanceType.LEMMY;
} else if (nodeInfo.software.name.compareToIgnoreCase("gnu") == 0) {
instanceType = RemoteInstance.InstanceType.GNU;
} else {
@ -1042,16 +850,11 @@ public class ProfileActivity extends BaseActivity {
new Pinned(ProfileActivity.this).insertPinned(finalPinned);
}
runOnUiThread(() -> {
Bundle args = new Bundle();
args.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Bundle b = new Bundle();
b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Intent intentBD = new Intent(Helper.BROADCAST_DATA);
new CachedBundle(ProfileActivity.this).insertBundle(args, Helper.getCurrentAccount(ProfileActivity.this), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentBD.putExtras(bundle);
intentBD.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intentBD);
});
intentBD.putExtras(b);
LocalBroadcastManager.getInstance(ProfileActivity.this).sendBroadcast(intentBD);
});
} catch (DBException e) {
e.printStackTrace();
@ -1113,7 +916,7 @@ public class ProfileActivity extends BaseActivity {
String[] codesArr;
String[] languagesArr;
boolean[] presentArr;
if (storedLanguages != null && !storedLanguages.isEmpty()) {
if (storedLanguages != null && storedLanguages.size() > 0) {
int i = 0;
codesArr = new String[storedLanguages.size()];
languagesArr = new String[storedLanguages.size()];
@ -1171,30 +974,22 @@ public class ProfileActivity extends BaseActivity {
return true;
} else if (itemId == R.id.action_direct_message) {
Intent intent = new Intent(ProfileActivity.this, ComposeActivity.class);
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_ACCOUNT_MENTION, account);
args.putString(Helper.ARG_VISIBILITY, "direct");
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);
});
Bundle b = new Bundle();
b.putSerializable(Helper.ARG_ACCOUNT_MENTION, account);
b.putString(Helper.ARG_VISIBILITY, "direct");
intent.putExtras(b);
startActivity(intent);
return true;
} else if (itemId == R.id.action_add_to_list) {
TimelinesVM timelinesVM = new ViewModelProvider(ProfileActivity.this).get(TimelinesVM.class);
timelinesVM.getLists(BaseMainActivity.currentInstance, BaseMainActivity.currentToken)
.observe(ProfileActivity.this, mastodonLists -> {
if (mastodonLists == null || mastodonLists.isEmpty()) {
if (mastodonLists == null || mastodonLists.size() == 0) {
Toasty.info(ProfileActivity.this, getString(R.string.action_lists_empty), Toast.LENGTH_SHORT).show();
return;
}
accountsVM.getListContainingAccount(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id)
.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);
builderSingle.setTitle(getString(R.string.action_lists_add_to));
builderSingle.setPositiveButton(R.string.close, (dialog, which) -> dialog.dismiss());
@ -1263,15 +1058,12 @@ public class ProfileActivity extends BaseActivity {
return true;
} else if (itemId == R.id.action_mention) {
Intent intent;
Bundle b;
intent = new Intent(ProfileActivity.this, ComposeActivity.class);
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_ACCOUNT_MENTION, account);
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);
});
b = new Bundle();
b.putSerializable(Helper.ARG_ACCOUNT_MENTION, account);
intent.putExtras(b);
startActivity(intent);
return true;
} else if (itemId == R.id.action_mute) {
AlertDialog.Builder builderInner;
@ -1315,7 +1107,7 @@ public class ProfileActivity extends BaseActivity {
builderInner.setNeutralButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
if (homeMuted) {
builderInner.setTitle(R.string.unmute_home);
builderInner.setPositiveButton(R.string.action_unmute, (dialog, which) -> accountsVM.unmuteHome(Helper.getCurrentAccount(ProfileActivity.this), account)
builderInner.setPositiveButton(R.string.action_unmute, (dialog, which) -> accountsVM.unmuteHome(currentAccount, account)
.observe(ProfileActivity.this, account -> {
homeMuted = false;
invalidateOptionsMenu();
@ -1323,7 +1115,7 @@ public class ProfileActivity extends BaseActivity {
}));
} else {
builderInner.setTitle(R.string.mute_home);
builderInner.setPositiveButton(R.string.action_mute, (dialog, which) -> accountsVM.muteHome(Helper.getCurrentAccount(ProfileActivity.this), account)
builderInner.setPositiveButton(R.string.action_mute, (dialog, which) -> accountsVM.muteHome(currentAccount, account)
.observe(ProfileActivity.this, account -> {
homeMuted = true;
invalidateOptionsMenu();
@ -1338,15 +1130,24 @@ public class ProfileActivity extends BaseActivity {
});
return true;
} else if (itemId == R.id.action_report) {
Intent intent = new Intent(ProfileActivity.this, ReportActivity.class);
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_ACCOUNT, account);
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);
AlertDialog.Builder builderInner = new MaterialAlertDialogBuilder(ProfileActivity.this);
builderInner.setTitle(R.string.report_account);
//Text for report
EditText input = new EditText(ProfileActivity.this);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
input.setLayoutParams(lp);
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;
} else if (itemId == R.id.action_block) {
AlertDialog.Builder builderInner = new MaterialAlertDialogBuilder(ProfileActivity.this);
@ -1370,18 +1171,20 @@ public class ProfileActivity extends BaseActivity {
target = account.id;
}
switch (doActionAccount) {
case BLOCK ->
accountsVM.block(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, target)
.observe(ProfileActivity.this, relationShip -> {
this.relationship = relationShip;
updateAccount();
});
case UNBLOCK ->
accountsVM.unblock(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, target)
.observe(ProfileActivity.this, relationShip -> {
this.relationship = relationShip;
updateAccount();
});
case BLOCK:
accountsVM.block(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, target)
.observe(ProfileActivity.this, relationShip -> {
this.relationship = relationShip;
updateAccount();
});
break;
case UNBLOCK:
accountsVM.unblock(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, target)
.observe(ProfileActivity.this, relationShip -> {
this.relationship = relationShip;
updateAccount();
});
break;
}
dialog.dismiss();
});
@ -1410,11 +1213,11 @@ public class ProfileActivity extends BaseActivity {
@Override
public void onDestroy() {
try {
unregisterReceiver(broadcast_data);
} catch (IllegalArgumentException e) {
e.printStackTrace();
if (scheduledExecutorService != null) {
scheduledExecutorService.shutdownNow();
scheduledExecutorService = null;
}
LocalBroadcastManager.getInstance(ProfileActivity.this).unregisterReceiver(broadcast_data);
super.onDestroy();
}

View file

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

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

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,
* see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentAccount;
import android.content.SharedPreferences;
import android.os.Bundle;
@ -28,7 +29,6 @@ import com.google.android.material.tabs.TabLayout;
import app.fedilab.android.R;
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.ui.pageadapter.FedilabScheduledPageAdapter;
@ -55,7 +55,7 @@ public class ScheduledActivity extends BaseActivity {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(this);
float scale = sharedpreferences.getFloat(getString(R.string.SET_FONT_SCALE), 1.1f);
binding.title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18 * 1.1f / scale);
MastodonHelper.loadPPMastodon(binding.profilePicture, Helper.getCurrentAccount(ScheduledActivity.this).mastodon_account);
MastodonHelper.loadPPMastodon(binding.profilePicture, currentAccount.mastodon_account);
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_client)));

View file

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

View file

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

View file

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

View file

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

View file

@ -14,7 +14,6 @@ package app.fedilab.android.mastodon.activities.admin;
* 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.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.REPORT;
@ -27,13 +26,12 @@ import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.gson.annotations.SerializedName;
@ -44,7 +42,6 @@ import app.fedilab.android.databinding.PopupAdminFilterAccountsBinding;
import app.fedilab.android.databinding.PopupAdminFilterReportsBinding;
import app.fedilab.android.mastodon.activities.BaseBarActivity;
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.ThemeHelper;
import app.fedilab.android.mastodon.ui.fragment.admin.FragmentAdminAccount;
@ -65,21 +62,18 @@ public class AdminActionActivity extends BaseBarActivity {
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Bundle args = intent.getExtras();
if (args != null) {
long bundleId = args.getLong(Helper.ARG_INTENT_ID, -1);
new CachedBundle(AdminActionActivity.this).getBundle(bundleId, Helper.getCurrentAccount(AdminActionActivity.this), bundle -> {
AdminDomainBlock adminDomainBlock = (AdminDomainBlock) bundle.getSerializable(Helper.ARG_ADMIN_DOMAINBLOCK);
AdminDomainBlock adminDomainBlockDelete = (AdminDomainBlock) bundle.getSerializable(Helper.ARG_ADMIN_DOMAINBLOCK_DELETE);
if (adminDomainBlock != null && adminDomainBlock.domain != null && fragmentAdminDomain != null) {
fragmentAdminDomain.update(adminDomainBlock);
}
if (adminDomainBlockDelete != null && fragmentAdminDomain != null) {
fragmentAdminDomain.delete(adminDomainBlockDelete);
}
});
Bundle b = intent.getExtras();
if (b != null) {
AdminDomainBlock adminDomainBlock = (AdminDomainBlock) b.getSerializable(Helper.ARG_ADMIN_DOMAINBLOCK);
AdminDomainBlock adminDomainBlockDelete = (AdminDomainBlock) b.getSerializable(Helper.ARG_ADMIN_DOMAINBLOCK_DELETE);
if (adminDomainBlock != null && adminDomainBlock.domain != null && fragmentAdminDomain != null) {
fragmentAdminDomain.update(adminDomainBlock);
}
if (adminDomainBlockDelete != null && fragmentAdminDomain != null) {
fragmentAdminDomain.delete(adminDomainBlockDelete);
}
}
}
};
@ -89,9 +83,7 @@ public class AdminActionActivity extends BaseBarActivity {
binding = ActivityAdminActionsBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
ContextCompat.registerReceiver(AdminActionActivity.this, mReceiver, new IntentFilter(Helper.BROADCAST_DATA), ContextCompat.RECEIVER_NOT_EXPORTED);
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, new IntentFilter(Helper.BROADCAST_DATA));
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@ -100,84 +92,60 @@ public class AdminActionActivity extends BaseBarActivity {
binding.accounts.setOnClickListener(v -> displayTimeline(ACCOUNT));
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) {
canGoBack = true;
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, () -> {
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) {
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, () -> {
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) {
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, () -> {
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) {
case REPORT -> setTitle(R.string.reports);
case ACCOUNT -> setTitle(R.string.accounts);
case DOMAIN -> setTitle(R.string.domains);
case REPORT:
setTitle(R.string.reports);
break;
case ACCOUNT:
setTitle(R.string.accounts);
break;
case DOMAIN:
setTitle(R.string.domains);
break;
}
invalidateOptionsMenu();
}
@ -193,7 +161,7 @@ public class AdminActionActivity extends BaseBarActivity {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
getOnBackPressedDispatcher().onBackPressed();
onBackPressed();
return true;
} else if (item.getItemId() == R.id.action_filter) {
if (getTitle().toString().equalsIgnoreCase(getString(R.string.accounts))) {
@ -350,14 +318,35 @@ public class AdminActionActivity extends BaseBarActivity {
protected void onDestroy() {
super.onDestroy();
if (mReceiver != null) {
try {
unregisterReceiver(mReceiver);
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
}
}
@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 {
@SerializedName("REPORT")

View file

@ -26,18 +26,15 @@ import android.widget.ArrayAdapter;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.ViewModelProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
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.activities.MainActivity;
import app.fedilab.android.databinding.ActivityAdminDomainblockBinding;
import app.fedilab.android.mastodon.activities.BaseBarActivity;
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.viewmodel.mastodon.AdminVM;
import es.dmoral.toasty.Toasty;
@ -100,9 +97,9 @@ public class AdminDomainBlockActivity extends BaseBarActivity {
binding.rejectReports.setOnCheckedChangeListener((compoundButton, checked) -> adminDomainBlock.reject_reports = checked);
adminVM = new ViewModelProvider(AdminDomainBlockActivity.this).get(AdminVM.class);
binding.saveChanges.setOnClickListener(v -> {
adminDomainBlock.domain = Objects.requireNonNull(binding.domain.getText()).toString().trim();
adminDomainBlock.public_comment = Objects.requireNonNull(binding.publicComment.getText()).toString().trim();
adminDomainBlock.private_comment = Objects.requireNonNull(binding.privateComment.getText()).toString().trim();
adminDomainBlock.domain = binding.domain.getText().toString().trim();
adminDomainBlock.public_comment = binding.publicComment.getText().toString().trim();
adminDomainBlock.private_comment = binding.privateComment.getText().toString().trim();
adminVM.createOrUpdateDomainBlock(MainActivity.currentInstance, MainActivity.currentToken, adminDomainBlock)
.observe(AdminDomainBlockActivity.this, adminDomainBlockResult -> {
if (adminDomainBlockResult != null) {
@ -110,17 +107,9 @@ public class AdminDomainBlockActivity extends BaseBarActivity {
} else {
Toasty.error(AdminDomainBlockActivity.this, getString(R.string.toast_error), Toasty.LENGTH_SHORT).show();
}
Intent intent = new Intent(Helper.BROADCAST_DATA);
Bundle args = new Bundle();
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();
});
Intent intent = new Intent(Helper.BROADCAST_DATA).putExtra(Helper.ARG_ADMIN_DOMAINBLOCK, adminDomainBlockResult);
LocalBroadcastManager.getInstance(AdminDomainBlockActivity.this).sendBroadcast(intent);
finish();
}
);
});
@ -146,18 +135,9 @@ public class AdminDomainBlockActivity extends BaseBarActivity {
.setPositiveButton(R.string.unblock_domain, (dialog, which) -> {
adminVM.deleteDomain(MainActivity.currentInstance, MainActivity.currentToken, adminDomainBlock.id)
.observe(AdminDomainBlockActivity.this, adminDomainBlockResult -> {
Intent intent = new Intent(Helper.BROADCAST_DATA);
Bundle args = new Bundle();
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();
});
Intent intent = new Intent(Helper.BROADCAST_DATA).putExtra(Helper.ARG_ADMIN_DOMAINBLOCK_DELETE, adminDomainBlock);
LocalBroadcastManager.getInstance(AdminDomainBlockActivity.this).sendBroadcast(intent);
finish();
}
);
dialog.dismiss();

View file

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

View file

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

View file

@ -31,14 +31,18 @@ public class ToastMessage extends BroadcastReceiver {
String content = b.getString(Helper.RECEIVE_TOAST_CONTENT, null);
if (type != null && content != null) {
switch (type) {
case Helper.RECEIVE_TOAST_TYPE_ERROR ->
Toasty.error(context, content, Toasty.LENGTH_SHORT).show();
case Helper.RECEIVE_TOAST_TYPE_WARNING ->
Toasty.warning(context, content, Toasty.LENGTH_SHORT).show();
case Helper.RECEIVE_TOAST_TYPE_INFO ->
Toasty.info(context, content, Toasty.LENGTH_SHORT).show();
case Helper.RECEIVE_TOAST_TYPE_SUCCESS ->
Toasty.success(context, content, Toasty.LENGTH_SHORT).show();
case Helper.RECEIVE_TOAST_TYPE_ERROR:
Toasty.error(context, content, Toasty.LENGTH_SHORT).show();
break;
case Helper.RECEIVE_TOAST_TYPE_WARNING:
Toasty.warning(context, content, Toasty.LENGTH_SHORT).show();
break;
case Helper.RECEIVE_TOAST_TYPE_INFO:
Toasty.info(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,7 +124,6 @@ public interface MastodonAccountsService {
@Query("exclude_reblogs") Boolean exclude_reblogs,
@Query("only_media") Boolean only_media,
@Query("pinned") Boolean pinned,
@Query("tagged") String tagged,
@Query("limit") int limit
);

View file

@ -21,7 +21,6 @@ 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.Emoji;
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 retrofit2.Call;
import retrofit2.http.GET;
@ -33,9 +32,6 @@ public interface MastodonInstanceService {
@GET("instance")
Call<Instance> instance();
@GET("instance")
Call<InstanceV2> instanceV2();
@GET("instance/peers")
Call<List<String>> connectedInstance();

View file

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

View file

@ -15,7 +15,6 @@ package app.fedilab.android.mastodon.client.endpoints;
* see <http://www.gnu.org/licenses>. */
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import app.fedilab.android.mastodon.client.entities.api.Account;
@ -26,17 +25,14 @@ 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.Status;
import app.fedilab.android.mastodon.client.entities.api.StatusSource;
import app.fedilab.android.mastodon.client.entities.api.params.StatusParams;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.Headers;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.PUT;
@ -65,8 +61,7 @@ public interface MastodonStatusesService {
@Field("visibility") String visibility,
@Field("language") String language,
@Field("quote_id") String quote_id,
@Field("content_type") String content_type,
@Field("local_only") Boolean local_only
@Field("content_type") String content_type
);
@GET("statuses/{id}/source")
@ -79,16 +74,6 @@ public interface MastodonStatusesService {
@Header("Authorization") String token,
@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
@FormUrlEncoded
@PUT("statuses/{id}")
@ -107,9 +92,9 @@ public interface MastodonStatusesService {
@Field("spoiler_text") String spoiler_text,
@Field("visibility") String visibility,
@Field("language") String language,
@Field("media_attributes[]") LinkedHashMap<String, String> media_id,
@Field("media_attributes[]") LinkedHashMap<String, String> media_description,
@Field("media_attributes[]") LinkedHashMap<String, String> focus
@Field("media_attributes[id][]") List<String> media_id,
@Field("media_attributes[description][]") List<String> media_description,
@Field("media_attributes[focus][]") List<String> focus
);
//Post a scheduled status
@ -333,18 +318,4 @@ public interface MastodonStatusesService {
@Header("Authorization") String token,
@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,12 +18,10 @@ import java.util.List;
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.Link;
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.Status;
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.nitter.Nitter;
import app.fedilab.android.mastodon.client.entities.peertube.PeertubeVideo;
@ -81,11 +79,6 @@ public interface MastodonTimelinesService {
@Query("offset") Integer offset,
@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
@GET("timelines/tag/{hashtag}")
Call<List<Status>> getHashTag(
@ -237,20 +230,6 @@ public interface MastodonTimelinesService {
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
@FormUrlEncoded
@POST("api/notes")
@ -274,14 +253,14 @@ public interface MastodonTimelinesService {
);
@Headers({"Accept: text/html,application/xhtml+xml,application/xml"})
@GET("{names}/with_replies/rss")
@GET("{names}/rss")
Call<Nitter> getNitter(
@Path("names") String id,
@Query("max_position") String max_position
);
@Headers({"Accept: text/html,application/xhtml+xml,application/xml"})
@GET("{account}/with_replies/rss")
@GET("{account}/rss")
Call<Nitter> getNitterAccount(
@Path("account") String account
);

View file

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

View file

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

View file

@ -73,16 +73,6 @@ public class Attachment implements Serializable {
public MediaData original;
@SerializedName("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 {

View file

@ -14,36 +14,21 @@ 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,
* see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.emojis;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
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.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import java.io.Serializable;
import java.net.IDN;
import java.util.ArrayList;
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.helper.Helper;
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 {
@ -185,17 +170,6 @@ 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
*
@ -210,72 +184,10 @@ public class EmojiInstance implements Serializable {
Cursor c = db.query(Sqlite.TABLE_EMOJI_INSTANCE, null, Sqlite.COL_INSTANCE + " = '" + instance + "'", null, null, null, null, "1");
return 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()) {
return emojiResponse.body();
}
} catch (Exception err) {
err.printStackTrace();
}
}
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
*
@ -302,8 +214,4 @@ public class EmojiInstance implements Serializable {
}
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) {
value_span = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.verified_text));
}
Spannable spannable = SpannableHelper.convert(context, value, null, account, null, viewWeakReference, null, true, false);
Spannable spannable = SpannableHelper.convert(context, value, null, account, null, viewWeakReference);
if (value_span != null && spannable != null) {
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) {
Spannable spannable = SpannableHelper.convert(context, name, null, account, null, viewWeakReference, null, true, false);
Spannable spannable = SpannableHelper.convert(context, name, null, account, null, viewWeakReference);
if (name_span != null && spannable != null) {
spannable.setSpan(name_span, 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}

View file

@ -2,8 +2,6 @@ package app.fedilab.android.mastodon.client.entities.api;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -18,7 +16,7 @@ import java.io.Serializable;
*
* 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 History implements Serializable {
public class History {
@SerializedName("day")
public String day;
@SerializedName("uses")

View file

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

View file

@ -1,70 +0,0 @@
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

@ -1,52 +0,0 @@
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 Filter filteredByApp;
public PositionFetchMore positionFetchMore = PositionFetchMore.BOTTOM;
public List<Notification> relatedNotifications;
public transient boolean isFetchMore;
public transient List<Notification> relatedNotifications;
public boolean isFetchMore;
/**
* Serialized a list of Notification class

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,65 +0,0 @@
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

@ -1,433 +0,0 @@
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

@ -1,53 +0,0 @@
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,12 +46,8 @@ public class RemoteInstance implements Serializable {
PEERTUBE("PEERTUBE"),
@SerializedName("NITTER")
NITTER("NITTER"),
@SerializedName("NITTER_TAG")
NITTER_TAG("NITTER_TAG"),
@SerializedName("MISSKEY")
MISSKEY("MISSKEY"),
@SerializedName("LEMMY")
LEMMY("LEMMY"),
@SerializedName("GNU")
GNU("GNU");

View file

@ -14,8 +14,6 @@ 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,
* see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.mastodon.helper.Helper.TAG;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@ -231,28 +229,6 @@ public class StatusCache {
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
*
@ -291,6 +267,7 @@ public class StatusCache {
if (statusCache.type != null) {
query += " AND " + Sqlite.COL_TYPE + " = '" + statusCache.type.getValue() + "'";
}
Cursor mCount = db.rawQuery(query, null);
mCount.moveToFirst();
int count = mCount.getInt(0);
@ -353,11 +330,7 @@ public class StatusCache {
values.put(Sqlite.COL_STATUS, mastodonStatusToStringStorage(statusCache.status));
}
if (statusCache.notification != null) {
Notification currentNotification = getCachedNotification(statusCache);
if(currentNotification != null && currentNotification.status != null) {
currentNotification.status = statusCache.notification.status;
values.put(Sqlite.COL_STATUS, mastodonNotificationToStringStorage(currentNotification));
}
values.put(Sqlite.COL_STATUS, mastodonNotificationToStringStorage(statusCache.notification));
}
if (statusCache.conversation != null) {
values.put(Sqlite.COL_STATUS, mastodonConversationToStringStorage(statusCache.conversation));
@ -560,28 +533,15 @@ 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 limit = String.valueOf(MastodonHelper.statusesPerCall(context));
if (min_id != null) {
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 + "' ";
}
selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + min_id + "' ";
order = " ASC";
} else if (max_id != null) {
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 + "' ";
}
selection += "AND " + Sqlite.COL_STATUS_ID + " < '" + max_id + "' ";
} else if (since_id != null) {
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 + "' ";
}
selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + since_id + "' ";
limit = null;
}
if (exclude_type != null && exclude_type.size() > 0) {
StringBuilder exclude = new StringBuilder();
for (String excluded : exclude_type) {
@ -599,15 +559,6 @@ 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
@ -627,25 +578,12 @@ 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 limit = String.valueOf(MastodonHelper.statusesPerCall(context));
if (min_id != null) {
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 + "' ";
}
selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + min_id + "' ";
order = " ASC";
} else if (max_id != null) {
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 + "' ";
}
selection += "AND " + Sqlite.COL_STATUS_ID + " < '" + max_id + "' ";
} else if (since_id != null) {
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 + "' ";
}
selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + since_id + "' ";
limit = null;
}
try {
@ -676,25 +614,12 @@ public class StatusCache {
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));
if (min_id != null) {
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 + "' ";
}
selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + min_id + "' ";
order = " ASC";
} else if (max_id != null) {
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 + "' ";
}
selection += "AND " + Sqlite.COL_STATUS_ID + " < '" + max_id + "' ";
} else if (since_id != null) {
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 + "' ";
}
selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + since_id + "' ";
limit = null;
}
try {

View file

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

View file

@ -1,246 +0,0 @@
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

@ -1,268 +0,0 @@
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,12 +14,10 @@ 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,
* see <http://www.gnu.org/licenses>. */
import android.content.Context;
import androidx.annotation.NonNull;
import org.jsoup.select.Elements;
import org.simpleframework.xml.Element;
import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.Namespace;
@ -27,11 +25,10 @@ import org.simpleframework.xml.Path;
import org.simpleframework.xml.Root;
import java.io.Serializable;
import java.net.IDN;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -64,9 +61,14 @@ public class Nitter implements Serializable {
public static MastodonTimelinesService initInstanceXMLOnly(Context context, String instance) {
OkHttpClient okHttpClient = Helper.myOkHttpClient(context);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.callTimeout(60, TimeUnit.SECONDS)
.proxy(Helper.getProxy(context))
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + (instance != null ? IDN.toASCII(instance, IDN.ALLOW_UNASSIGNED) : null))
.baseUrl("https://" + instance)
.addConverterFactory(SimpleXmlConverterFactory.create())
.client(okHttpClient)
.build();
@ -85,8 +87,7 @@ public class Nitter implements Serializable {
status.text = feedItem.title;
status.content = status.content.replaceAll("<img [^>]*src=\"[^\"]+\"[^>]*>", "");
status.visibility = "public";
String dateFormat = "E', 'dd' 'MMM' 'yyyy' 'hh:m:s' GMT'";
status.created_at = Helper.stringToDateWithFormat(context, feedItem.pubDate, dateFormat);
status.created_at = Helper.stringToDateWithFormat(context, feedItem.pubDate, "EEE, dd MMM yyyy HH:mm:ss zzz");
status.uri = feedItem.guid;
status.url = feedItem.link;
if (!accounts.containsKey(feedItem.creator)) {
@ -181,83 +182,4 @@ 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,23 +18,24 @@ import android.content.Context;
import android.content.SharedPreferences;
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 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 app.fedilab.android.R;
@androidx.annotation.OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
public class CacheDataSourceFactory implements DataSource.Factory {
private static SimpleCache sDownloadCache;

View file

@ -1,121 +0,0 @@
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,7 +14,6 @@ package app.fedilab.android.mastodon.helper;
* 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.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@ -32,9 +31,9 @@ import androidx.preference.PreferenceManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.io.IOException;
import java.net.IDN;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
@ -45,7 +44,6 @@ 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.app.Account;
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.ui.drawer.AccountsSearchAdapter;
import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM;
@ -73,30 +71,10 @@ public class CrossActionHelper {
final SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
new Thread(() -> {
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();
if (accounts.size() == 1) {
Handler mainHandler = new Handler(Looper.getMainLooper());
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);
}
};
Runnable myRunnable = () -> fetchRemote(context, actionType, accounts.get(0), targetedAccount, targetedStatus);
mainHandler.post(myRunnable);
} else {
List<app.fedilab.android.mastodon.client.entities.api.Account> accountList = new ArrayList<>();
@ -117,7 +95,8 @@ public class CrossActionHelper {
}
builderSingle.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
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];
if ((actionType == TypeOfCrossAction.REBLOG_ACTION && confirmBoost) || (actionType == TypeOfCrossAction.FAVOURITE_ACTION && confirmFav)) {
AlertDialog.Builder alt_bld = new MaterialAlertDialogBuilder(context);
@ -128,9 +107,9 @@ public class CrossActionHelper {
}
alt_bld.setPositiveButton(R.string.yes, (dia, id) -> {
fetchRemote(context, actionType, selectedAccount, targetedAccount, targetedStatus);
dia.dismiss();
dialog.dismiss();
});
alt_bld.setNegativeButton(R.string.cancel, (dia, id) -> dia.dismiss());
alt_bld.setNegativeButton(R.string.cancel, (dia, id) -> dialog.dismiss());
AlertDialog alert = alt_bld.create();
alert.show();
} else {
@ -167,7 +146,7 @@ public class CrossActionHelper {
}
searchVM.search(ownerAccount.instance, ownerAccount.token, search, null, "accounts", false, true, false, 0, null, null, 1)
.observe((LifecycleOwner) context, results -> {
if (results != null && results.accounts != null && !results.accounts.isEmpty()) {
if (results.accounts != null && results.accounts.size() > 0) {
app.fedilab.android.mastodon.client.entities.api.Account account = results.accounts.get(0);
applyAction(context, actionType, ownerAccount, account, null);
} else {
@ -177,7 +156,7 @@ public class CrossActionHelper {
} else if (targetedStatus != null) {
searchVM.search(ownerAccount.instance, ownerAccount.token, targetedStatus.uri, null, "statuses", false, true, false, 0, null, null, 1)
.observe((LifecycleOwner) context, results -> {
if (results != null && results.statuses != null && !results.statuses.isEmpty()) {
if (results != null && results.statuses != null && results.statuses.size() > 0) {
Status status = results.statuses.get(0);
applyAction(context, actionType, ownerAccount, null, status);
} else {
@ -204,97 +183,89 @@ public class CrossActionHelper {
statusesVM = new ViewModelProvider((ViewModelStoreOwner) context).get("crossactions", StatusesVM.class);
}
switch (actionType) {
case MUTE_ACTION -> {
case MUTE_ACTION:
assert accountsVM != null;
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());
}
case UNMUTE_ACTION -> {
break;
case UNMUTE_ACTION:
assert accountsVM != null;
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());
}
case BLOCK_ACTION -> {
break;
case BLOCK_ACTION:
assert accountsVM != null;
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());
}
case UNBLOCK_ACTION -> {
break;
case UNBLOCK_ACTION:
assert accountsVM != null;
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());
}
case FOLLOW_ACTION -> {
break;
case FOLLOW_ACTION:
assert accountsVM != 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());
}
case UNFOLLOW_ACTION -> {
break;
case UNFOLLOW_ACTION:
assert accountsVM != null;
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());
}
case FAVOURITE_ACTION -> {
break;
case FAVOURITE_ACTION:
assert statusesVM != null;
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());
}
case UNFAVOURITE_ACTION -> {
break;
case UNFAVOURITE_ACTION:
assert statusesVM != null;
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());
}
case BOOKMARK_ACTION -> {
break;
case BOOKMARK_ACTION:
assert statusesVM != null;
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());
}
case UNBOOKMARK_ACTION -> {
break;
case UNBOOKMARK_ACTION:
assert statusesVM != null;
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());
}
case REBLOG_ACTION -> {
break;
case REBLOG_ACTION:
assert statusesVM != 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());
}
case UNREBLOG_ACTION -> {
break;
case UNREBLOG_ACTION:
assert statusesVM != null;
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());
}
case REPLY_ACTION -> {
break;
case REPLY_ACTION:
Intent intent = new Intent(context, ComposeActivity.class);
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_STATUS_REPLY, targetedStatus);
args.putSerializable(Helper.ARG_ACCOUNT, ownerAccount);
new CachedBundle(context).insertBundle(args, Helper.getCurrentAccount(context), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
context.startActivity(intent);
});
}
case COMPOSE -> {
intent.putExtra(Helper.ARG_STATUS_REPLY, targetedStatus);
intent.putExtra(Helper.ARG_ACCOUNT, ownerAccount);
context.startActivity(intent);
break;
case COMPOSE:
Intent intentCompose = new Intent(context, ComposeActivity.class);
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_ACCOUNT, ownerAccount);
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);
});
}
intentCompose.putExtra(Helper.ARG_ACCOUNT, ownerAccount);
context.startActivity(intentCompose);
break;
}
}
private static MastodonSearchService init(Context context, String instance) {
final OkHttpClient okHttpClient = Helper.myOkHttpClient(context);
private static MastodonSearchService init(Context context, @NonNull String instance) {
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.proxy(Helper.getProxy(context))
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + (instance != null ? IDN.toASCII(instance, IDN.ALLOW_UNASSIGNED) : null) + "/api/v2/")
.baseUrl("https://" + instance + "/api/v2/")
.addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder()))
.client(okHttpClient)
.build();
@ -463,15 +434,10 @@ public class CrossActionHelper {
final BaseAccount account = accountArray[which];
Intent intentToot = new Intent(context, ComposeActivity.class);
bundle.putSerializable(Helper.ARG_ACCOUNT, account);
new CachedBundle(context).insertBundle(bundle, Helper.getCurrentAccount(context), bundleId -> {
Bundle bundleCached = new Bundle();
bundleCached.putLong(Helper.ARG_INTENT_ID, bundleId);
intentToot.putExtras(bundleCached);
context.startActivity(intentToot);
((BaseActivity) context).finish();
dialog.dismiss();
});
intentToot.putExtras(bundle);
context.startActivity(intentToot);
((BaseActivity) context).finish();
dialog.dismiss();
});
builderSingle.show();
}
@ -542,7 +508,7 @@ public class CrossActionHelper {
}
}
}
} catch (Exception e) {
} catch (IOException e) {
e.printStackTrace();
}
}

View file

@ -30,8 +30,8 @@ import app.fedilab.android.mastodon.client.entities.api.Status;
public class CustomEmoji extends ReplacementSpan {
private final WeakReference<View> viewWeakReference;
private float scale;
private final WeakReference<View> viewWeakReference;
private Drawable imageDrawable;
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) {
if (emojiList != null && !emojiList.isEmpty()) {
if (emojiList != null && emojiList.size() > 0) {
int count = 1;
for (Emoji emoji : emojiList) {
Matcher matcher = Pattern.compile(":" + emoji.shortcode + ":", Pattern.LITERAL)
@ -68,6 +68,13 @@ public class CustomEmoji extends ReplacementSpan {
@Override
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);
}
@ -78,7 +85,7 @@ public class CustomEmoji extends ReplacementSpan {
int emojiSize = (int) (paint.getTextSize() * scale);
imageDrawable.setBounds(0, 0, emojiSize, emojiSize);
int transY = bottom - imageDrawable.getBounds().bottom;
transY -= (int) (paint.getFontMetrics().descent / 2);
transY -= paint.getFontMetrics().descent / 2;
canvas.translate(x, (float) transY);
imageDrawable.draw(canvas);
canvas.restore();
@ -86,50 +93,40 @@ public class CustomEmoji extends ReplacementSpan {
}
public Target<Drawable> getTarget(boolean animate, Status.Callback callback) {
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();
}
}
return new CustomTarget<Drawable>() {
@Override
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
View view = viewWeakReference.get();
if (animate && resource instanceof Animatable) {
Drawable.Callback drawableCallBack = resource.getCallback();
resource.setCallback(new Drawable.Callback() {
@Override
public void invalidateDrawable(@NonNull Drawable drawable) {
if(view != null) {
view.invalidate();
if (drawableCallBack != null) {
drawableCallBack.invalidateDrawable(drawable);
}
view.invalidate();
}
@Override
public void scheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable, long l) {
view.postDelayed(runnable, l);
if (drawableCallBack != null) {
drawableCallBack.scheduleDrawable(drawable, runnable, l);
}
}
@Override
public void unscheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable) {
view.removeCallbacks(runnable);
if (drawableCallBack != null) {
drawableCallBack.unscheduleDrawable(drawable, runnable);
}
}
});
((Animatable) resource).start();
}
imageDrawable = resource;
if(view != null) {
if (view != null) {
view.invalidate();
}
if (callback != null && !callbackCalled) {
@ -140,17 +137,6 @@ public class CustomEmoji extends ReplacementSpan {
@Override
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,6 +69,7 @@ public class DividerDecoration extends RecyclerView.ItemDecoration {
StatusAdapter statusAdapter = ((StatusAdapter) parent.getAdapter());
if (statusAdapter != null && statusAdapter.getItemCount() > position && position > 0) {
Status status = statusAdapter.getItem(position);
int start = (int) Helper.convertDpToPixel(
6 * fontScale * CommentDecorationHelper.getIndentation(status.in_reply_to_id, statusList, indentationMax),
_mContext);
@ -94,6 +95,7 @@ public class DividerDecoration extends RecyclerView.ItemDecoration {
int indentation = Math.min(
CommentDecorationHelper.getIndentation(status.in_reply_to_id, statusList, indentationMax),
indentationMax);
if (indentation > 0) {
Paint paint = new Paint();
paint.setDither(false);
@ -107,7 +109,12 @@ public class DividerDecoration extends RecyclerView.ItemDecoration {
startPx = c.getWidth() - startPx;
float bottomPx = view.getBottom();
int color = colorList.get(j%(colorList.size()-1));
int color;
if (j >= colorList.size()) {
color = colorList.get(j - colorList.size());
} else {
color = colorList.get(j);
}
paint.setColor(ResourcesCompat.getColor(_mContext.getResources(), color, _mContext.getTheme()));
if (j == indentationMax - 1) {
paint.setPathEffect(new DashPathEffect(
@ -118,7 +125,12 @@ public class DividerDecoration extends RecyclerView.ItemDecoration {
c.drawLine(startPx, view.getTop() - margin, startPx, bottomPx, paint);
}
int color = colorList.get(indentation%colorList.size()-1);
int color;
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()));
float startDp = 6 * fontScale * (indentation - 1) + 6 * fontScale;

View file

@ -127,7 +127,12 @@ public class DividerDecorationSimple extends RecyclerView.ItemDecoration {
c.drawLine(startPx, view.getTop() - margin, startPx, bottomPx, paint);
}
int color = colorList.get(indentation%colorList.size()-1);
int color;
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()));
float startDp = 6 * fontScale * (indentation - 1) + 6 * fontScale;

View file

@ -0,0 +1,289 @@
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

@ -0,0 +1,237 @@
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>. */
import static android.content.Context.DOWNLOAD_SERVICE;
import static app.fedilab.android.BaseMainActivity.networkAvailable;
import static app.fedilab.android.BaseMainActivity.currentAccount;
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.ThemeHelper.fetchAccentColor;
@ -34,7 +34,6 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
@ -59,7 +58,6 @@ import android.os.Looper;
import android.os.Parcelable;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.text.Html;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.TypedValue;
@ -80,6 +78,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.browser.customtabs.CustomTabColorSchemeParams;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
@ -90,6 +89,7 @@ import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager;
@ -100,7 +100,6 @@ import com.bumptech.glide.load.resource.bitmap.CenterCrop;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.load.resource.gif.GifDrawable;
import com.bumptech.glide.request.RequestOptions;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@ -141,7 +140,6 @@ import java.util.Random;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -158,7 +156,6 @@ 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.app.Account;
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.Timeline;
import app.fedilab.android.mastodon.exception.DBException;
@ -168,12 +165,9 @@ import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.mastodon.viewmodel.mastodon.OauthVM;
import app.fedilab.android.mastodon.watermark.androidwm.WatermarkBuilder;
import app.fedilab.android.mastodon.watermark.androidwm.bean.WatermarkText;
import app.fedilab.android.peertube.client.data.AccountData;
import es.dmoral.toasty.Toasty;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
public class Helper {
@ -200,30 +194,20 @@ public class Helper {
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_REFRESH_NOTIFICATIONS_ACTION = "RECEIVE_REFRESH_NOTIFICATIONS_ACTION";
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_PEERTUBE_ACTIVITY = "RECEIVE_RECREATE_PEERTUBE_ACTIVITY";
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_MASTODON_LIST = "RECEIVE_MASTODON_LIST";
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_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_NOTIFICATION_TYPE = "ARG_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_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_REFRESH_NOTFICATION = "ARG_REFRESH_NOTFICATION";
public static final String ARG_STATUS_DELETED = "ARG_STATUS_DELETED";
@ -247,8 +231,6 @@ public class Helper {
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_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_DELETE = "ARG_ADMIN_DOMAINBLOCK_DELETE";
public static final String FEDILAB_MUTED_HASHTAGS = "Fedilab muted hashtags";
@ -271,8 +253,6 @@ public class Helper {
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_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_DIRECTORY_ORDER = "ARG_DIRECTORY_ORDER";
public static final String ARG_DIRECTORY_LOCAL = "ARG_DIRECTORY_LOCAL";
@ -288,7 +268,6 @@ public class Helper {
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_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_MENTION = "ARG_MENTION";
public static final String ARG_CHECK_REMOTELY = "ARG_CHECK_REMOTELY";
@ -297,7 +276,6 @@ public class Helper {
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_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_HOME = "WORKER_REFRESH_HOME";
@ -322,9 +300,6 @@ public class Helper {
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_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_IS_MODERATOR = "PREF_IS_MODERATOR";
public static final String PREF_IS_ADMINISTRATOR = "PREF_IS_ADMINISTRATOR";
@ -337,7 +312,6 @@ public class Helper {
public static final int OPEN_NOTIFICATION = 2;
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_STATUS = "INTENT_TARGETED_STATUS";
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 TEMP_MEDIA_DIRECTORY = "TEMP_MEDIA_DIRECTORY";
@ -356,14 +330,11 @@ public class Helper {
public static final Pattern groupPattern = Pattern.compile("(![\\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 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 mathsComposePattern = Pattern.compile("\\\\\\(.*\\\\\\)|\\\\\\[.*\\\\\\]");
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 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 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+)");
@ -430,7 +401,6 @@ public class Helper {
private static int notificationId = 1;
//Allow to store in shared preference first visible fragment when the app starts
private static String slugOfFirstFragment;
private static BaseAccount baseAccount;
static {
LinkedHashMap<PatternType, Pattern> aMap = new LinkedHashMap<>();
@ -441,6 +411,7 @@ public class Helper {
patternHashMap = Collections.unmodifiableMap(aMap);
}
/**
* Manage downloads with URLs
*
@ -484,6 +455,8 @@ public class Helper {
}
}
/***
* Check if the user is connected to Internet
* @return boolean
@ -588,20 +561,6 @@ public class Helper {
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
*
@ -649,13 +608,19 @@ public class Helper {
*/
public static Date stringToDateWithFormat(Context context, String stringDate, String format) {
if (stringDate == null)
return new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat(format, Locale.US);
Date date = new Date();
return null;
Locale userLocale;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
userLocale = context.getResources().getConfiguration().getLocales().get(0);
} else {
userLocale = context.getResources().getConfiguration().locale;
}
SimpleDateFormat dateFormat = new SimpleDateFormat(format, userLocale);
Date date = null;
try {
date = dateFormat.parse(stringDate);
} catch (java.text.ParseException ignored) {
ignored.printStackTrace();
}
return date;
}
@ -683,10 +648,6 @@ public class Helper {
if (url == null) {
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);
boolean customTab = sharedpreferences.getBoolean(context.getString(R.string.SET_CUSTOM_TABS), true);
if (customTab) {
@ -697,35 +658,18 @@ public class Helper {
.build();
builder.setDefaultColorSchemeParams(defaultColors);
CustomTabsIntent customTabsIntent = builder.build();
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();
}
}
customTabsIntent.launchUrl(context, Uri.parse(url));
} else {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (!url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://") && !url.toLowerCase().startsWith("gemini://") && !url.toLowerCase().startsWith("xmpp:")) {
if (!url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://") && !url.toLowerCase().startsWith("gemini://")) {
url = "http://" + url;
}
intent.setData(Uri.parse(url).normalizeScheme());
intent.setData(Uri.parse(url));
try {
context.startActivity(intent);
} catch (Exception e) {
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();
}
Toasty.error(context, context.getString(R.string.toast_error), Toast.LENGTH_LONG).show();
}
}
}
@ -862,7 +806,7 @@ public class Helper {
@SuppressLint("DefaultLocale")
public static String withSuffix(long count) {
if (count < 1000) return String.valueOf(count);
if (count < 1000) return "" + count;
int exp = (int) (Math.log(count) / Math.log(1000));
Locale locale = null;
try {
@ -888,17 +832,12 @@ public class Helper {
*/
public static void sendToastMessage(Context context, String type, String content) {
Intent intentBC = new Intent(context, ToastMessage.class);
Bundle args = new Bundle();
args.putString(RECEIVE_TOAST_TYPE, type);
args.putString(RECEIVE_TOAST_CONTENT, content);
Bundle b = new Bundle();
b.putString(RECEIVE_TOAST_TYPE, type);
b.putString(RECEIVE_TOAST_CONTENT, content);
intentBC.setAction(Helper.RECEIVE_TOAST_MESSAGE);
new CachedBundle(context).insertBundle(args, Helper.getCurrentAccount(context), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentBC.putExtras(bundle);
intentBC.setPackage(BuildConfig.APPLICATION_ID);
context.sendBroadcast(intentBC);
});
intentBC.putExtras(b);
context.sendBroadcast(intentBC);
}
/**
@ -919,25 +858,19 @@ public class Helper {
ft.setCustomAnimations(R.anim.enter, R.anim.exit, R.anim.pop_enter, R.anim.pop_exit);
Fragment _fragment = fragmentManager.findFragmentByTag(tag);
if (_fragment != null && _fragment.isAdded()) {
ft.show(_fragment).commitAllowingStateLoss();
ft.show(_fragment).commit();
fragment = _fragment;
} else {
if (args != null) fragment.setArguments(args);
ft = fragmentManager.beginTransaction();
ft.add(containerViewId, fragment, tag);
if (backStackName != null) {
try {
ft.addToBackStack(backStackName);
}catch (Exception ignored){}
}
if (!fragmentManager.isDestroyed()) {
ft.commitAllowingStateLoss();
}
if (backStackName != null) ft.addToBackStack(backStackName);
ft.commit();
}
fragmentManager.executePendingTransactions();
return fragment;
}
/**
* Load a media into a view
*
@ -980,11 +913,11 @@ public class Helper {
OauthVM oauthVM = new ViewModelProvider((ViewModelStoreOwner) activity).get(OauthVM.class);
if (Helper.getCurrentAccount(activity) != null) {
if (currentAccount != null) {
//Revoke the token
oauthVM.revokeToken(Helper.getCurrentAccount(activity).instance, Helper.getCurrentAccount(activity).token, Helper.getCurrentAccount(activity).client_id, Helper.getCurrentAccount(activity).client_secret);
oauthVM.revokeToken(currentAccount.instance, currentAccount.token, currentAccount.client_id, currentAccount.client_secret);
//Log out the current user
accountDB.removeUser(Helper.getCurrentAccount(activity));
accountDB.removeUser(currentAccount);
}
BaseAccount newAccount = accountDB.getLastUsedAccount();
SharedPreferences.Editor editor = sharedpreferences.edit();
@ -998,7 +931,7 @@ public class Helper {
activity.startActivity(loginActivity);
activity.finish();
} else {
Helper.setCurrentAccount(newAccount);
currentAccount = newAccount;
editor.putString(PREF_USER_TOKEN, newAccount.token);
editor.putString(PREF_USER_SOFTWARE, newAccount.software);
editor.putString(PREF_USER_INSTANCE, newAccount.instance);
@ -1024,7 +957,8 @@ public class Helper {
if (context == null) {
return false;
}
if (context instanceof Activity activity) {
if (context instanceof Activity) {
final Activity activity = (Activity) context;
return !activity.isDestroyed() && !activity.isFinishing();
}
return true;
@ -1160,13 +1094,11 @@ public class Helper {
.toSquare()
.setBackgroundColor(fetchAccentColor(activity))
.build();
if (Helper.isValidContextForGlide(activity)) {
Glide.with(activity)
.asDrawable()
.load(avatar)
.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10)))
.into(view);
}
Glide.with(activity)
.asDrawable()
.load(avatar)
.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10)))
.into(view);
return;
}
}
@ -1332,9 +1264,9 @@ public class Helper {
MimeTypeMap mime = MimeTypeMap.getSingleton();
String extension = mime.getExtensionFromMimeType(cR.getType(uri));
if (uri.toString().endsWith("fedilab_recorded_audio.ogg")) {
extension = "ogg";
attachment.mimeType = "audio/ogg";
if (uri.toString().endsWith("fedilab_recorded_audio.m4a")) {
extension = ".m4a";
attachment.mimeType = "audio/mp4";
}
SimpleDateFormat formatter = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_" + counter, Locale.getDefault());
counter++;
@ -1386,6 +1318,7 @@ public class Helper {
}).start();
}
public static void createFileFromUri(Context context, Uri uri, OnFileCopied callBack) {
new Thread(() -> {
InputStream selectedFileInputStream;
@ -1464,24 +1397,6 @@ public class Helper {
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
*
@ -1559,16 +1474,11 @@ public class Helper {
* @param activity - Activity
*/
public static void recreateMainActivity(Activity activity) {
Bundle args = new Bundle();
args.putBoolean(Helper.RECEIVE_RECREATE_ACTIVITY, true);
Bundle b = new Bundle();
b.putBoolean(Helper.RECEIVE_RECREATE_ACTIVITY, true);
Intent intentBD = new Intent(Helper.BROADCAST_DATA);
new CachedBundle(activity).insertBundle(args, Helper.getCurrentAccount(activity), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentBD.putExtras(bundle);
intentBD.setPackage(BuildConfig.APPLICATION_ID);
activity.sendBroadcast(intentBD);
});
intentBD.putExtras(b);
LocalBroadcastManager.getInstance(activity).sendBroadcast(intentBD);
}
public static void showKeyboard(Context context, View view) {
@ -1605,50 +1515,49 @@ public class Helper {
String channelTitle;
switch (notifType) {
case FAV -> {
case FAV:
channelId = "channel_favourite";
channelTitle = context.getString(R.string.channel_notif_fav);
}
case FOLLLOW -> {
break;
case FOLLLOW:
channelId = "channel_follow";
channelTitle = context.getString(R.string.channel_notif_follow);
}
case MENTION -> {
break;
case MENTION:
channelId = "channel_mention";
channelTitle = context.getString(R.string.channel_notif_mention);
}
case POLL -> {
break;
case POLL:
channelId = "channel_poll";
channelTitle = context.getString(R.string.channel_notif_poll);
}
case BACKUP -> {
break;
case BACKUP:
channelId = "channel_backup";
channelTitle = context.getString(R.string.channel_notif_backup);
}
case STORE -> {
break;
case STORE:
channelId = "channel_media";
channelTitle = context.getString(R.string.channel_notif_media);
}
case TOOT -> {
break;
case TOOT:
channelId = "channel_status";
channelTitle = context.getString(R.string.channel_notif_status);
}
case UPDATE -> {
break;
case UPDATE:
channelId = "channel_update";
channelTitle = context.getString(R.string.channel_notif_update);
}
case SIGN_UP -> {
break;
case SIGN_UP:
channelId = "channel_signup";
channelTitle = context.getString(R.string.channel_notif_signup);
}
case REPORT -> {
break;
case REPORT:
channelId = "channel_report";
channelTitle = context.getString(R.string.channel_notif_report);
}
default -> {
break;
default:
channelId = "channel_boost";
channelTitle = context.getString(R.string.channel_notif_boost);
}
}
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, channelId)
.setSmallIcon(getNotificationIcon(context)).setTicker(message);
@ -1657,25 +1566,31 @@ public class Helper {
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)
.setContentText(message);
int ledColour = Color.BLUE;
int prefColor;
prefColor = Integer.parseInt(sharedpreferences.getString(context.getString(R.string.SET_LED_COLOUR_VAL_N), String.valueOf(LED_COLOUR)));
switch (prefColor) {
case 1 -> // CYAN
ledColour = Color.CYAN;
case 2 -> // MAGENTA
ledColour = Color.MAGENTA;
case 3 -> // GREEN
ledColour = Color.GREEN;
case 4 -> // RED
ledColour = Color.RED;
case 5 -> // YELLOW
ledColour = Color.YELLOW;
case 6 -> // WHITE
ledColour = Color.WHITE;
case 1: // CYAN
ledColour = Color.CYAN;
break;
case 2: // MAGENTA
ledColour = Color.MAGENTA;
break;
case 3: // GREEN
ledColour = Color.GREEN;
break;
case 4: // RED
ledColour = Color.RED;
break;
case 5: // YELLOW
ledColour = Color.YELLOW;
break;
case 6: // WHITE
ledColour = Color.WHITE;
break;
}
@ -1719,7 +1634,7 @@ public class Helper {
.setLargeIcon(icon)
.setSmallIcon(getNotificationIcon(context))
.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)
.build();
@ -1729,6 +1644,7 @@ public class Helper {
}
}
public static String dateDiffFull(Date dateToot) {
SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.MEDIUM, Locale.getDefault());
try {
@ -1738,15 +1654,6 @@ 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
* for 5 seconds.
@ -1849,6 +1756,7 @@ public class Helper {
if (responseCode == HttpURLConnection.HTTP_OK) {
String fileName = "";
String disposition = httpURLConnection.getHeaderField("Content-Disposition");
if (disposition != null) {
// extracts file name from header field
int index = disposition.indexOf("filename=");
@ -1858,12 +1766,7 @@ public class Helper {
}
} else {
// extracts file name from URL
try {
URL downLoadUrlTmp = new URL(downloadUrl);
fileName = downLoadUrlTmp.getPath().replace("/","_");
}catch (Exception exception) {
fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/") + 1);
}
fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/") + 1);
}
fileName = FileNameCleaner.cleanFileName(fileName);
// opens input stream from the HTTP connection
@ -1970,14 +1873,12 @@ public class Helper {
binding.accountUn.setText(account.acct);
binding.accountPp.setOnClickListener(v -> {
Intent intent = new Intent(activity, ProfileActivity.class);
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_ACCOUNT, account);
new CachedBundle(activity).insertBundle(args, Helper.getCurrentAccount(activity), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
activity.startActivity(intent);
});
Bundle b = new Bundle();
b.putSerializable(Helper.ARG_ACCOUNT, account);
intent.putExtras(b);
ActivityOptionsCompat options = ActivityOptionsCompat
.makeSceneTransitionAnimation(activity, binding.accountPp, activity.getString(R.string.activity_porfile_pp));
activity.startActivity(intent, options.toBundle());
});
AccountsVM accountsVM = new ViewModelProvider((ViewModelStoreOwner) activity).get(AccountsVM.class);
@ -2060,6 +1961,7 @@ public class Helper {
Runtime.getRuntime().exit(0);
}
public static void forwardToBrowser(Activity activity, Intent i) {
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
@ -2084,6 +1986,7 @@ public class Helper {
}
}
public static int dialogStyle() {
if (R.style.AppThemeBar == currentThemeId || R.style.AppTheme == currentThemeId) {
return R.style.AppThemeAlertDialog;
@ -2112,83 +2015,6 @@ 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
public enum PatternType {
MENTION,

View file

@ -1,209 +0,0 @@
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,16 +60,6 @@ public class LogoHelper {
return R.drawable.fedilab_logo_crash;
case "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:
return R.drawable.fedilab_logo_bubbles;
}
@ -88,16 +78,6 @@ public class LogoHelper {
return R.drawable.fedilab_logo_crash;
case "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:
return R.drawable.fedilab_logo_bubbles;
}
@ -120,21 +100,6 @@ public class LogoHelper {
case "Mastalab":
mLauncher = BaseMainActivity.iconLauncher.MASTALAB;
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:
mLauncher = BaseMainActivity.iconLauncher.BUBBLES;
}

View file

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

View file

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

View file

@ -24,7 +24,6 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.Html;
@ -35,15 +34,17 @@ import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
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.Target;
import com.bumptech.glide.request.transition.Transition;
import java.net.IDN;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
@ -51,10 +52,8 @@ import app.fedilab.android.activities.MainActivity;
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.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.BaseAccount;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.ui.drawer.StatusAdapter;
import okhttp3.OkHttpClient;
@ -66,21 +65,9 @@ import retrofit2.converter.gson.GsonConverterFactory;
public class NotificationsHelper {
private static final HashMap<String, ReentrantLock> lockMap = new HashMap<>();
public static HashMap<String, List<String>> pushed_notifications = new HashMap<>();
public static HashMap<String, String> since_ids = new HashMap<>();
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 {
public static void task(Context context, String slug) throws DBException {
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
@ -89,6 +76,13 @@ public class NotificationsHelper {
if (accountDb == null) {
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
boolean notif_follow = prefs.getBoolean(context.getString(R.string.SET_NOTIF_FOLLOW), true);
@ -102,54 +96,44 @@ public class NotificationsHelper {
boolean notif_report = prefs.getBoolean(context.getString(R.string.SET_NOTIF_ADMIN_REPORT), true);
//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
}
MastodonNotificationsService mastodonNotificationsService = init(context, slugArray[1]);
String finalLast_notifid = last_notifid;
new Thread(() -> {
ReentrantLock lock = getLock(slug);
// fetch if we get the lock, or ignore, another thread is doing the job
if (lock.tryLock()) {
Notifications notifications = new Notifications();
Call<List<Notification>> notificationsCall;
if (finalLast_notifid != null) {
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 {
MastodonNotificationsService mastodonNotificationsService = init(context, slugArray[1]);
Notifications notifications = new Notifications();
Call<List<Notification>> notificationsCall;
String last_notif_id = prefs.getString(context.getString(R.string.LAST_NOTIFICATION_ID) + slug, null);
notificationsCall = mastodonNotificationsService.getNotifications(accountDb.token, null, null, null, last_notif_id, null, 30);
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());
Response<List<Notification>> notificationsResponse = notificationsCall.execute();
if (notificationsResponse.isSuccessful()) {
notifications.notifications = notificationsResponse.body();
if (notifications.notifications != null) {
if (notifications.notifications.size() > 0) {
since_ids.put(slug, notifications.notifications.get(0).id);
}
} catch (Exception e) {
e.printStackTrace();
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> onRetrieveNotifications(context, notifications, accountDb, last_notif_id);
mainHandler.post(myRunnable);
}
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
notifications.pagination = MastodonHelper.getPagination(notificationsResponse.headers());
}
} catch (Exception e) {
e.printStackTrace();
}
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> onRetrieveNotifications(context, notifications, accountDb);
mainHandler.post(myRunnable);
}).start();
}
private static MastodonNotificationsService init(Context context, String instance) {
private static MastodonNotificationsService init(Context context, @NonNull String instance) {
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
@ -157,14 +141,14 @@ public class NotificationsHelper {
.proxy(Helper.getProxy(context))
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + (instance != null ? IDN.toASCII(instance, IDN.ALLOW_UNASSIGNED) : null) + "/api/v1/")
.baseUrl("https://" + instance + "/api/v1/")
.addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder()))
.client(okHttpClient)
.build();
return retrofit.create(MastodonNotificationsService.class);
}
public static void onRetrieveNotifications(Context context, Notifications newNotifications, final BaseAccount account, String max_id) {
public static void onRetrieveNotifications(Context context, Notifications newNotifications, final BaseAccount account) {
if (newNotifications == null || newNotifications.notifications == null || newNotifications.notifications.size() == 0 || account == null) {
return;
}
@ -183,6 +167,8 @@ public class NotificationsHelper {
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);
final String max_id = prefs.getString(context.getString(R.string.LAST_NOTIFICATION_ID) + key, null);
final List<Notification> notifications = new ArrayList<>();
int pos = 0;
for (Notification notif : notificationsReceived) {
@ -198,24 +184,10 @@ public class NotificationsHelper {
//No previous notifications in cache, so no notification will be sent
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 title = null;
String message = null;
app.fedilab.android.mastodon.client.entities.api.Account targeted_account = null;
Status targeted_status = null;
String targeted_account = null;
Helper.NotifType notifType = Helper.NotifType.MENTION;
switch (notification.type) {
case "mention":
@ -236,7 +208,6 @@ public class NotificationsHelper {
else
message = new SpannableString(Html.fromHtml(notification.status.content)).toString();
}
targeted_status = notification.status;
}
}
break;
@ -259,7 +230,6 @@ public class NotificationsHelper {
else
message = new SpannableString(Html.fromHtml(notification.status.content)).toString();
}
targeted_status = notification.status;
}
}
break;
@ -315,7 +285,7 @@ public class NotificationsHelper {
message = String.format("%s %s", notification.account.display_name, context.getString(R.string.notif_follow_request));
else
message = String.format("@%s %s", notification.account.acct, context.getString(R.string.notif_follow_request));
targeted_account = notification.account;
targeted_account = notification.account.id;
}
break;
case "follow":
@ -326,7 +296,7 @@ public class NotificationsHelper {
message = String.format("%s %s", notification.account.display_name, context.getString(R.string.notif_follow));
else
message = String.format("@%s %s", notification.account.acct, context.getString(R.string.notif_follow));
targeted_account = notification.account;
targeted_account = notification.account.id;
}
break;
case "poll":
@ -367,7 +337,6 @@ public class NotificationsHelper {
else
message = new SpannableString(Html.fromHtml(notification.status.content)).toString();
}
targeted_status = notification.status;
}
}
break;
@ -379,7 +348,7 @@ public class NotificationsHelper {
message = String.format("%s %s", notification.account.display_name, context.getString(R.string.notif_signed_up));
else
message = String.format("@%s %s", notification.account.acct, context.getString(R.string.notif_signed_up));
targeted_account = notification.account;
targeted_account = notification.account.id;
}
break;
case "admin.report":
@ -390,7 +359,7 @@ public class NotificationsHelper {
message = String.format("%s %s", notification.account.display_name, context.getString(R.string.notif_reported));
else
message = String.format("@%s %s", notification.account.acct, context.getString(R.string.notif_reported));
targeted_account = notification.account;
targeted_account = notification.account.id;
}
break;
default:
@ -399,48 +368,62 @@ public class NotificationsHelper {
//Some others notification
final Intent intent = new Intent(context, MainActivity.class);
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.PREF_USER_ID, account.user_id);
if (targeted_account != null) {
args.putSerializable(Helper.INTENT_TARGETED_ACCOUNT, targeted_account);
} else if (targeted_status != null) {
args.putSerializable(Helper.INTENT_TARGETED_STATUS, targeted_status);
}
String finalMessage1 = message;
String finalTitle1 = title;
Helper.NotifType finalNotifType1 = notifType;
new CachedBundle(context).insertBundle(args, account, bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
intent.putExtra(Helper.PREF_USER_INSTANCE, account.instance);
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);
}
if (targeted_account != null)
intent.putExtra(Helper.INTENT_TARGETED_ACCOUNT, targeted_account);
intent.putExtra(Helper.PREF_USER_INSTANCE, account.instance);
notificationUrl = notification.account.avatar;
Handler mainHandler = new Handler(Looper.getMainLooper());
final String finalNotificationUrl = notificationUrl;
Helper.NotifType finalNotifType = notifType;
String finalMessage = message;
String finalTitle = title;
StatusAdapter.sendAction(context, Helper.ARG_REFRESH_NOTFICATION, null, null);
Runnable myRunnable = () -> Glide.with(context)
.asBitmap()
.load(finalNotificationUrl != null ? finalNotificationUrl : R.drawable.fedilab_logo_bubbles)
.listener(new RequestListener<Bitmap>() {
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
super.onLoadFailed(errorDrawable);
@Override
public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
return false;
}
@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(),
getMainLogo(context)), finalNotifType1, finalTitle1, finalMessage1);
getMainLogo(context)), finalNotifType, finalTitle, finalMessage);
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
return false;
}
})
.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,14 +15,12 @@ package app.fedilab.android.mastodon.helper;
* 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.currentUserID;
import static app.fedilab.android.BaseMainActivity.show_boosts;
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_self_boosts;
import static app.fedilab.android.BaseMainActivity.show_self_replies;
import android.annotation.SuppressLint;
import android.content.SharedPreferences;
@ -56,7 +54,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
import app.fedilab.android.BaseMainActivity;
@ -184,18 +181,15 @@ public class PinnedTimelineHelper {
}
}
}
//Create other default timelines
try {
Pinned pinnedAll = new Pinned(activity).getAllPinned(Helper.getCurrentAccount(activity));
if (pinnedAll == null) {
pinnedAll = new Pinned();
pinnedAll.user_id = currentUserID;
pinnedAll.instance = currentInstance;
pinnedAll.pinnedTimelines = new ArrayList<>();
}
if (extraFeatures) {
//Bubble timeline
if (extraFeatures) {
try {
Pinned pinnedAll = new Pinned(activity).getAllPinned(currentAccount);
if (pinnedAll == null) {
pinnedAll = new Pinned();
pinnedAll.user_id = currentUserID;
pinnedAll.instance = currentInstance;
pinnedAll.pinnedTimelines = new ArrayList<>();
}
boolean createDefaultBubbleAtTop = true;
for (PinnedTimeline pinnedTimeline : pinnedAll.pinnedTimelines) {
if (pinnedTimeline.type == Timeline.TimeLineEnum.BUBBLE) {
@ -215,34 +209,11 @@ public class PinnedTimelineHelper {
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);
//Check if changes occurred, if mastodonLists is null it does need, because it is the first call to draw pinned
boolean needRedraw = mastodonLists == null;
@ -288,7 +259,7 @@ public class PinnedTimelineHelper {
for (MastodonList mastodonList : mastodonLists) {
boolean present = false;
try {
Pinned pinnedAll = new Pinned(activity).getAllPinned(Helper.getCurrentAccount(activity));
Pinned pinnedAll = new Pinned(activity).getAllPinned(currentAccount);
if (pinnedAll == null) {
pinnedAll = pinned;
}
@ -329,18 +300,12 @@ public class PinnedTimelineHelper {
}
//Pinned tab position will start after BOTTOM_TIMELINE_COUNT (ie 5)
activityMainBinding.tabLayout.removeAllTabs();
if(activityMainBinding.viewPager.getAdapter() != null) {
activityMainBinding.viewPager.getAdapter().notifyDataSetChanged();
}
int toRemove = FedilabPageAdapter.BOTTOM_TIMELINE_COUNT;
if (!singleBar) {
//Small hack to hide first tabs (they represent the item of the bottom menu)
toRemove = itemToRemoveInBottomMenu(activity);
for (int i = 0; i < (FedilabPageAdapter.BOTTOM_TIMELINE_COUNT - toRemove); i++) {
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);
}
}
@ -374,10 +339,10 @@ public class PinnedTimelineHelper {
break;
case REMOTE:
name = pinnedTimeline.remoteInstance.host;
if (pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.NITTER || pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.NITTER_TAG) {
if (pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.NITTER) {
String remoteInstance = sharedpreferences.getString(activity.getString(R.string.SET_NITTER_HOST), activity.getString(R.string.DEFAULT_NITTER_HOST)).toLowerCase();
//Custom name for Nitter instances
if (pinnedTimeline.remoteInstance.displayName != null && !pinnedTimeline.remoteInstance.displayName.trim().isEmpty()) {
if (pinnedTimeline.remoteInstance.displayName != null && pinnedTimeline.remoteInstance.displayName.trim().length() > 0) {
name = pinnedTimeline.remoteInstance.displayName;
}
ident = "@R@" + remoteInstance;
@ -405,13 +370,10 @@ public class PinnedTimelineHelper {
case MASTODON:
tabCustomViewBinding.icon.setImageResource(R.drawable.mastodon_icon_item);
break;
case LEMMY:
tabCustomViewBinding.icon.setImageResource(R.drawable.lemmy);
break;
case MISSKEY:
tabCustomViewBinding.icon.setImageResource(R.drawable.misskey);
break;
case NITTER_TAG:
case NITTER:
tabCustomViewBinding.icon.setImageResource(R.drawable.nitter);
break;
@ -446,9 +408,6 @@ public class PinnedTimelineHelper {
case BUBBLE:
tabCustomDefaultViewBinding.icon.setImageResource(R.drawable.ic_baseline_bubble_chart_24);
break;
case TREND_MESSAGE:
tabCustomDefaultViewBinding.icon.setImageResource(R.drawable.baseline_moving_24);
break;
}
tab.setCustomView(tabCustomDefaultViewBinding.getRoot());
}
@ -456,9 +415,6 @@ public class PinnedTimelineHelper {
String slug = pinnedTimeline.type.getValue() + (ident != null ? "|" + ident : "");
tab.setTag(slug);
activityMainBinding.tabLayout.addTab(tab, false);
if(activityMainBinding.viewPager.getAdapter() != null){
activityMainBinding.viewPager.getAdapter().notifyDataSetChanged();
}
pinnedTimelineVisibleList.add(pinnedTimeline);
}
}
@ -505,16 +461,12 @@ public class PinnedTimelineHelper {
case MISSKEY:
item.setIcon(R.drawable.misskey);
break;
case LEMMY:
item.setIcon(R.drawable.lemmy);
break;
case PIXELFED:
item.setIcon(R.drawable.pixelfed);
break;
case NITTER_TAG:
case NITTER:
item.setIcon(R.drawable.nitter);
if (pinnedTimeline.remoteInstance.displayName != null && !pinnedTimeline.remoteInstance.displayName.trim().isEmpty()) {
if (pinnedTimeline.remoteInstance.displayName != null && pinnedTimeline.remoteInstance.displayName.trim().length() > 0) {
item.setTitle(pinnedTimeline.remoteInstance.displayName);
} else {
item.setTitle(pinnedTimeline.remoteInstance.host);
@ -566,7 +518,7 @@ public class PinnedTimelineHelper {
bubbleClick(activity, finalPinned, v, activityMainBinding, finalI, activityMainBinding.tabLayout.getTabAt(finalI).getTag().toString());
break;
case REMOTE:
if (pinnedTimelineVisibleList.get(position).remoteInstance.type != RemoteInstance.InstanceType.NITTER && pinnedTimelineVisibleList.get(position).remoteInstance.type != RemoteInstance.InstanceType.NITTER_TAG) {
if (pinnedTimelineVisibleList.get(position).remoteInstance.type != RemoteInstance.InstanceType.NITTER) {
instanceClick(activity, finalPinned, v, activityMainBinding, finalI, activityMainBinding.tabLayout.getTabAt(finalI).getTag().toString());
} else {
nitterClick(activity, finalPinned, v, activityMainBinding, finalI, activityMainBinding.tabLayout.getTabAt(finalI).getTag().toString());
@ -574,7 +526,6 @@ public class PinnedTimelineHelper {
break;
case HOME:
case LOCAL:
case TREND_MESSAGE:
case PUBLIC:
defaultClick(activity, pinnedTimelineVisibleList.get(position).type, v, activityMainBinding, finalI);
break;
@ -588,7 +539,6 @@ public class PinnedTimelineHelper {
activityMainBinding.tabLayout.clearOnTabSelectedListeners();
FedilabPageAdapter fedilabPageAdapter = new FedilabPageAdapter(activity, activity.getSupportFragmentManager(), pinned, bottomMenu);
activityMainBinding.viewPager.setAdapter(fedilabPageAdapter);
activityMainBinding.viewPager.setOffscreenPageLimit(tabStrip.getChildCount());
activityMainBinding.viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(activityMainBinding.tabLayout));
if (!singleBar) {
@ -678,25 +628,16 @@ public class PinnedTimelineHelper {
.inflate(R.menu.option_filter_toots, popup.getMenu());
Menu menu = popup.getMenu();
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 itemShowSelfReplies = menu.findItem(R.id.action_show_self_replies);
final MenuItem itemShowDMs = menu.findItem(R.id.action_show_dms);
final MenuItem itemFilter = menu.findItem(R.id.action_filter);
if (!showExtendedFilter) {
itemShowBoosts.setVisible(false);
itemShowReplies.setVisible(false);
itemShowSelfBoosts.setVisible(false);
itemShowSelfReplies.setVisible(false);
itemShowMyMessages.setVisible(false);
itemShowDMs.setVisible(false);
} else {
itemShowBoosts.setVisible(true);
itemShowReplies.setVisible(true);
itemShowSelfBoosts.setVisible(true);
itemShowSelfReplies.setVisible(true);
itemShowMyMessages.setVisible(true);
itemShowDMs.setVisible(true);
}
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(activity);
@ -710,18 +651,16 @@ public class PinnedTimelineHelper {
}
itemShowBoosts.setChecked(show_boosts);
itemShowMyMessages.setChecked(show_my_messages);
itemShowReplies.setChecked(show_replies);
itemShowSelfBoosts.setChecked(show_self_boosts);
itemShowSelfReplies.setChecked(show_self_replies);
itemShowDMs.setChecked(show_dms);
if (show_filtered != null && show_filtered.length() > 0) {
itemFilter.setTitle(show_filtered);
}
popup.setOnDismissListener(menu1 -> {
if (activityMainBinding.viewPager.getAdapter() != null && activityMainBinding.tabLayout.getSelectedTabPosition() != -1) {
if (activityMainBinding.viewPager.getAdapter() != null) {
Fragment fragment = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition());
if (fragment instanceof FragmentMastodonTimeline fragmentMastodonTimeline && fragment.isVisible()) {
if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) {
FragmentMastodonTimeline fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
fragmentMastodonTimeline.refreshAllAdapters();
}
}
@ -748,26 +687,11 @@ public class PinnedTimelineHelper {
editor.putBoolean(activity.getString(R.string.SET_SHOW_BOOSTS) + currentUserID + currentInstance, show_boosts);
itemShowBoosts.setChecked(show_boosts);
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) {
show_replies = !show_replies;
editor.putBoolean(activity.getString(R.string.SET_SHOW_REPLIES) + currentUserID + currentInstance, show_replies);
itemShowReplies.setChecked(show_replies);
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) {
show_dms = !show_dms;
editor.putBoolean(activity.getString(R.string.SET_SHOW_DMS) + currentUserID + currentInstance, show_dms);
@ -835,7 +759,7 @@ public class PinnedTimelineHelper {
int toRemove = 0;
try {
//If some menu items have been hidden we should not create tab for them
bottomMenuDb = new BottomMenu(activity).getAllBottomMenu(Helper.getCurrentAccount(activity));
bottomMenuDb = new BottomMenu(activity).getAllBottomMenu(currentAccount);
if (bottomMenuDb != null) {
List<BottomMenu.MenuItem> menuItemList = bottomMenuDb.bottom_menu;
if (menuItemList != null) {
@ -906,21 +830,19 @@ public class PinnedTimelineHelper {
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(activity.getString(R.string.SET_INNER_MARKER) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance + slug, null);
editor.commit();
if (activityMainBinding.tabLayout.getSelectedTabPosition() != -1) {
Fragment fragmentMastodonTimeline = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition());
if (fragmentMastodonTimeline instanceof FragmentMastodonTimeline && fragmentMastodonTimeline.isVisible()) {
FragmentTransaction fragTransaction = activity.getSupportFragmentManager().beginTransaction();
fragTransaction.detach(fragmentMastodonTimeline).commit();
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.TAG);
bundle.putSerializable(Helper.ARG_TAG_TIMELINE, tagTimeline);
bundle.putSerializable(Helper.ARG_INITIALIZE_VIEW, false);
fragmentMastodonTimeline.setArguments(bundle);
FragmentTransaction fragTransaction2 = activity.getSupportFragmentManager().beginTransaction();
fragTransaction2.attach(fragmentMastodonTimeline);
fragTransaction2.commit();
((FragmentMastodonTimeline) fragmentMastodonTimeline).recreate();
}
Fragment fragmentMastodonTimeline = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition());
if (fragmentMastodonTimeline instanceof FragmentMastodonTimeline && fragmentMastodonTimeline.isVisible()) {
FragmentTransaction fragTransaction = activity.getSupportFragmentManager().beginTransaction();
fragTransaction.detach(fragmentMastodonTimeline).commit();
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.TAG);
bundle.putSerializable(Helper.ARG_TAG_TIMELINE, tagTimeline);
bundle.putSerializable(Helper.ARG_INITIALIZE_VIEW, false);
fragmentMastodonTimeline.setArguments(bundle);
FragmentTransaction fragTransaction2 = activity.getSupportFragmentManager().beginTransaction();
fragTransaction2.attach(fragmentMastodonTimeline);
fragTransaction2.commit();
((FragmentMastodonTimeline) fragmentMastodonTimeline).recreate();
}
}
}
@ -1136,21 +1058,19 @@ public class PinnedTimelineHelper {
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(activity.getString(R.string.SET_INNER_MARKER) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance + slug, null);
editor.commit();
if (activityMainBinding.tabLayout.getSelectedTabPosition() != -1) {
Fragment fragmentMastodonTimeline = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition());
if (fragmentMastodonTimeline instanceof FragmentMastodonTimeline && fragmentMastodonTimeline.isVisible()) {
FragmentTransaction fragTransaction = activity.getSupportFragmentManager().beginTransaction();
fragTransaction.detach(fragmentMastodonTimeline).commit();
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.BUBBLE);
bundle.putSerializable(Helper.ARG_BUBBLE_TIMELINE, bubbleTimeline);
bundle.putSerializable(Helper.ARG_INITIALIZE_VIEW, false);
fragmentMastodonTimeline.setArguments(bundle);
FragmentTransaction fragTransaction2 = activity.getSupportFragmentManager().beginTransaction();
fragTransaction2.attach(fragmentMastodonTimeline);
fragTransaction2.commit();
((FragmentMastodonTimeline) fragmentMastodonTimeline).recreate();
}
Fragment fragmentMastodonTimeline = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition());
if (fragmentMastodonTimeline instanceof FragmentMastodonTimeline && fragmentMastodonTimeline.isVisible()) {
FragmentTransaction fragTransaction = activity.getSupportFragmentManager().beginTransaction();
fragTransaction.detach(fragmentMastodonTimeline).commit();
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.BUBBLE);
bundle.putSerializable(Helper.ARG_BUBBLE_TIMELINE, bubbleTimeline);
bundle.putSerializable(Helper.ARG_INITIALIZE_VIEW, false);
fragmentMastodonTimeline.setArguments(bundle);
FragmentTransaction fragTransaction2 = activity.getSupportFragmentManager().beginTransaction();
fragTransaction2.attach(fragmentMastodonTimeline);
fragTransaction2.commit();
((FragmentMastodonTimeline) fragmentMastodonTimeline).recreate();
}
}
}
@ -1381,7 +1301,7 @@ public class PinnedTimelineHelper {
} catch (DBException e) {
e.printStackTrace();
}
if (activityMainBinding.viewPager.getAdapter() != null && activityMainBinding.tabLayout.getSelectedTabPosition() != -1) {
if (activityMainBinding.viewPager.getAdapter() != null) {
Fragment fragment = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition());
if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) {
fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
@ -1429,7 +1349,7 @@ public class PinnedTimelineHelper {
int finalOffSetPosition1 = offSetPosition;
item.setOnMenuItemClickListener(item1 -> {
FragmentMastodonTimeline fragmentMastodonTimeline = null;
if (activityMainBinding.viewPager.getAdapter() != null && activityMainBinding.tabLayout.getSelectedTabPosition() != -1) {
if (activityMainBinding.viewPager.getAdapter() != null) {
Fragment fragment = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition());
if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) {
fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
@ -1522,7 +1442,7 @@ public class PinnedTimelineHelper {
} catch (DBException e) {
e.printStackTrace();
}
if (activityMainBinding.viewPager.getAdapter() != null && activityMainBinding.tabLayout.getSelectedTabPosition() != -1) {
if (activityMainBinding.viewPager.getAdapter() != null) {
Fragment fragment = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition());
if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) {
fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
@ -1570,12 +1490,6 @@ public class PinnedTimelineHelper {
PopupMenu popup = new PopupMenu(activity, view);
popup.getMenuInflater()
.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;
popup.setOnMenuItemClickListener(item -> {
int itemId = item.getItemId();
@ -1595,7 +1509,7 @@ public class PinnedTimelineHelper {
}
dialogBuilder.setPositiveButton(R.string.validate, (dialog, id) -> {
String values = editTextName.getText().toString();
if (values.trim().isEmpty()) {
if (values.trim().length() == 0) {
values = remoteInstance.displayName;
}
remoteInstance.displayName = values;
@ -1637,7 +1551,7 @@ public class PinnedTimelineHelper {
} catch (DBException e) {
e.printStackTrace();
}
if (activityMainBinding.viewPager.getAdapter() != null && activityMainBinding.tabLayout.getSelectedTabPosition() != -1) {
if (activityMainBinding.viewPager.getAdapter() != null) {
Fragment fragment = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition());
if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) {
fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);

View file

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

View file

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

View file

@ -15,7 +15,7 @@ package app.fedilab.android.mastodon.helper;
* see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentNightMode;
import static app.fedilab.android.BaseMainActivity.currentAccount;
import android.app.Activity;
import android.content.ClipData;
@ -71,7 +71,6 @@ import java.util.regex.Pattern;
import javax.net.ssl.HttpsURLConnection;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.MySuperGrammerLocator;
import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.databinding.PopupLinksBinding;
@ -85,66 +84,30 @@ 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.Mention;
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.viewmodel.mastodon.FiltersVM;
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 static final String CLICKABLE_SPAN = "CLICKABLE_SPAN";
private static int linkColor;
private static boolean underlineLinks;
private static final String patternBottomTags = "\\n{2,}((#[\\w_À-ú-]+)(\\s*| *))+$";
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,
Status status, Account account, Announcement announcement, WeakReference<View> viewWeakReference) {
return convert(context, text, status, account, announcement, viewWeakReference, null);
}
public static Spannable convert(Context context, String text,
Status status, Account account, Announcement announcement,
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) {
WeakReference<View> viewWeakReference, Status.Callback callback) {
if (text == null) {
return null;
}
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 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;
if (currentNightMode == Configuration.UI_MODE_NIGHT_NO && customLight) {
link_color = sharedpreferences.getInt(context.getString(R.string.SET_LIGHT_LINK), -1);
@ -162,97 +125,21 @@ public class SpannableHelper {
if (linkColor == 0) {
linkColor = -1;
}
if (status != null && status.underlined) {
linkColor = -1;
}
List<Mention> mentions = new ArrayList<>();
if (status != null && status.mentions != null) {
mentions.addAll(status.mentions);
}
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.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{2}", "&nbsp;&nbsp;");
SpannableString initialContent;
if (convertHtml) {
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));
} else {
initialContent = new SpannableString(text);
}
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));
//Get all links
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);
}
SpannableStringBuilder content = new SpannableStringBuilder(initialContent);
URLSpan[] urls = content.getSpans(0, (content.length() - 1), URLSpan.class);
//Loop through links
for (URLSpan span : urls) {
@ -262,7 +149,6 @@ public class SpannableHelper {
if (start < 0 || end > content.length()) {
continue;
}
boolean sameContent = (content.subSequence(start,end).toString().trim().equals(url.trim()));
content.removeSpan(span);
//Get the matching word associated to the URL
String word = content.subSequence(start, end).toString();
@ -312,59 +198,40 @@ public class SpannableHelper {
public void onClick(@NonNull View textView) {
textView.setTag(CLICKABLE_SPAN);
Intent intent;
Bundle args;
Bundle b;
if (word.startsWith("#")) {
intent = new Intent(context, HashTagActivity.class);
args = new Bundle();
args.putString(Helper.ARG_SEARCH_KEYWORD, word.trim());
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);
});
b = new Bundle();
b.putString(Helper.ARG_SEARCH_KEYWORD, word.trim());
intent.putExtras(b);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
} else if (word.startsWith("@")) {
intent = new Intent(context, ProfileActivity.class);
args = new Bundle();
b = new Bundle();
Mention targetedMention = null;
String acct = null;
for (Mention mention : mentions) {
if (word.compareToIgnoreCase("@" + mention.username) == 0) {
if (!checkRemotely) {
targetedMention = mention;
} else {
acct = mention.acct;
}
targetedMention = mention;
break;
}
}
if (targetedMention != null) {
args.putString(Helper.ARG_USER_ID, targetedMention.id);
} else if (acct != null) {
args.putString(Helper.ARG_MENTION, acct);
b.putString(Helper.ARG_USER_ID, targetedMention.id);
} else {
args.putString(Helper.ARG_MENTION, word);
b.putString(Helper.ARG_MENTION, word);
}
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);
});
intent.putExtras(b);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds);
if (!underlineLinks) {
ds.setUnderlineText(status != null && status.underlined);
}
ds.setUnderlineText(false);
if (linkColor != -1) {
ds.setColor(linkColor);
}
@ -372,11 +239,11 @@ public class SpannableHelper {
}, start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
} else {
makeLinks(context, status, content, url, start, end, sameContent);
makeLinks(context, content, url, start, end);
}
replaceQuoteSpans(context, content);
emails(context, content);
}
replaceQuoteSpans(context, content);
emails(context, content, status);
Pattern imgPattern = Pattern.compile("<img [^>]*src=\"([^\"]+)\"[^>]*>");
Matcher matcherImg = imgPattern.matcher(text);
@ -402,7 +269,7 @@ public class SpannableHelper {
CustomEmoji customEmoji = new CustomEmoji(new WeakReference<>(view));
content = customEmoji.makeEmoji(content, emojiList, animate, callback);
if (!imagesToReplace.isEmpty()) {
if (imagesToReplace.size() > 0) {
for (Map.Entry<String, String> entry : imagesToReplace.entrySet()) {
String key = entry.getKey();
String url = entry.getValue();
@ -417,48 +284,29 @@ 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));
}
private static void makeLinks(Context context, Status status, SpannableStringBuilder content, String url, int start, int end, boolean sameContent) {
private static void makeLinks(Context context, SpannableStringBuilder content, String url, int start, int end) {
String newUrl = url;
boolean validUrl = URLUtil.isValidUrl(url) && sameContent;
boolean validUrl = URLUtil.isValidUrl(url) && url.length() == (end - start);
if (validUrl) {
newUrl = Helper.transformURL(context, url);
}
//If URL has been transformed
if (validUrl && newUrl.compareTo(url) != 0) {
content.replace(start, end, newUrl);
end = start + newUrl.length();
url = newUrl;
}
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
boolean truncate = sharedpreferences.getBoolean(context.getString(R.string.SET_TRUNCATE_LINKS), true);
if (truncate) {
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);
}
if (url.length() > 30 && (validUrl || url.startsWith("gimini://"))) {
newUrl = url.substring(0, 30);
newUrl += "";
content.replace(start, end, newUrl);
}
int matchEnd = validUrl ? start + newUrl.length() : end;
@ -662,15 +510,117 @@ public class SpannableHelper {
public void onClick(@NonNull View textView) {
textView.setTag(CLICKABLE_SPAN);
linkClickAction(context, 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)).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
public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds);
if (!underlineLinks) {
ds.setUnderlineText(status != null && status.underlined);
}
ds.setUnderlineText(false);
if (linkColor != -1) {
ds.setColor(linkColor);
}
@ -678,145 +628,8 @@ public class SpannableHelper {
}, 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);
});
}
@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) {
private static void emails(Context context, Spannable content) {
// --- For all patterns defined in Helper class ---
Pattern pattern = Helper.emailPattern;
Matcher matcher = pattern.matcher(content);
@ -845,9 +658,7 @@ public class SpannableHelper {
@Override
public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds);
if (!underlineLinks) {
ds.setUnderlineText(status != null && status.underlined);
}
ds.setUnderlineText(false);
if (linkColor != -1) {
ds.setColor(linkColor);
}
@ -985,22 +796,16 @@ public class SpannableHelper {
@Override
public void onClick(@NonNull View textView) {
Intent intent = new Intent(context, ProfileActivity.class);
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_ACCOUNT, account.moved);
new CachedBundle(context).insertBundle(args, Helper.getCurrentAccount(context), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
context.startActivity(intent);
});
Bundle b = new Bundle();
b.putSerializable(Helper.ARG_ACCOUNT, account.moved);
intent.putExtras(b);
context.startActivity(intent);
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds);
if (!underlineLinks) {
ds.setUnderlineText(false);
}
ds.setUnderlineText(false);
if (linkColor != -1) {
ds.setColor(linkColor);
}

View file

@ -15,9 +15,6 @@ package app.fedilab.android.mastodon.helper;
* see <http://www.gnu.org/licenses>. */
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.content.Context;
@ -26,8 +23,6 @@ import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.os.Build;
import android.util.DisplayMetrics;
import android.util.TypedValue;
@ -42,9 +37,6 @@ import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.content.ContextCompat;
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.IOException;
import java.io.InputStream;
@ -176,10 +168,12 @@ public class ThemeHelper {
String[] list;
try {
list = context.getAssets().list("themes/contributors");
for (String file : list) {
InputStream is = context.getAssets().open("themes/contributors/" + file);
LinkedHashMap<String, String> data = readCSVFile(is);
linkedHashMaps.add(data);
if (list.length > 0) {
for (String file : list) {
InputStream is = context.getAssets().open("themes/contributors/" + file);
LinkedHashMap<String, String> data = readCSVFile(is);
linkedHashMaps.add(data);
}
}
} catch (IOException e) {
e.printStackTrace();
@ -273,7 +267,7 @@ public class ThemeHelper {
public static void switchTo(String themePref) {
if (themes.LIGHT.name().equals(themePref) || themes.SOLARIZED_LIGHT.name().equals(themePref)) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
} else if (themes.DARK.name().equals(themePref) || themes.SOLARIZED_DARK.name().equals(themePref) || themes.DRACULA.name().equals(themePref) || themes.BLACK.name().equals(themePref)) {
} else if (themes.DARK.name().equals(themePref) || themes.SOLARIZED_DARK.name().equals(themePref)) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
@ -284,36 +278,9 @@ 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 {
LIGHT,
DARK,
BLACK,
DRACULA,
SYSTEM,
SOLARIZED_LIGHT,
SOLARIZED_DARK

View file

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

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