Compare commits

..

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

1948 changed files with 31561 additions and 100954 deletions

View file

@ -1,41 +0,0 @@
---
name: "Bug"
about: "Something isn't working"
labels:
- Bug
---
<!-- Please, describe the issue here -->
## # Steps for reproducing the issue
<!-- Step, to reproduce it -->
---
<!-- The instance you are using -->
Instance:
<!-- Your social network -->
<!-- Put a x between brackets like: - [x] Mastodon -->
- [ ] Mastodon
- [ ] Pleroma
- [ ] Friendica
- [ ] Pixelfed
<!-- If you know the version of Fedilab that you are using (can be found in about page) -->
Version of Fedilab:
<!-- Your Android version -->
Android version:
<!-- If you read our contributing advice -->
[ ] - I read
the [contributing page](https://codeberg.org/tom79/Fedilab/src/branch/main/CONTRIBUTING.md)

View file

@ -1,26 +0,0 @@
---
name: "Feature"
about: "A new feature or an enhancement to an existing feature"
labels:
- Feature
---
## # Describe the improvement
<!-- Your social network -->
<!-- Put a x between brackets like: - [x] Mastodon -->
- [ ] Mastodon
- [ ] Pleroma
- [ ] Friendica
- [ ] Pixelfed
<!-- Describe the improvement here -->
<!-- If you read our contributing advice -->
[ ] - I read
the [contributing page](https://codeberg.org/tom79/Fedilab/src/branch/main/CONTRIBUTING.md)

2
.gitignore vendored
View file

@ -10,5 +10,3 @@
local.properties
/cropper/build/
/build/
/app/fdroid/release/
/app/playstore/release/

View file

@ -8,14 +8,4 @@ tools for helping in translations. New translations will be automatically merged
If you're submiting a merge request and your work adds new strings to the app, make sure they only
exist in the default strings.xml file (res/values/strings.xml). If you add or modify strings of
other languages, it will interfere with weblate's translations.
### Issues and Reports:
Before creating an issue please take a moment and search the repository issues to avoid duplicates.
For bug reports, please provide as much details as possible to better debug the problem. The
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.
other languages, it will interfere with weblate's translations.

View file

@ -1,24 +1,23 @@
[![Translation status](https://hosted.weblate.org/widgets/fedilab/-/strings/svg-badge.svg)](https://hosted.weblate.org/engage/fedilab/)
&nbsp;&nbsp;&nbsp;[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
# Fedilab is a multi-accounts client for Mastodon, Pleroma, Friendica and Pixelfed
# Fedilab is a multi-accounts client for Mastodon, Pleroma, Peertube, GNU Social, Friendica and Pixelfed
## Donate
[<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://fedilab.app/wiki/home/)
[Release notes](https://codeberg.org/tom79/Fedilab/tags)
[Release notes](https://framagit.org/tom79/fedilab/tags)
Lead developer: [toot.fedilab.app/@apps](https://toot.fedilab.app/@apps)

View file

@ -1,20 +1,16 @@
import java.util.regex.Matcher
import java.util.regex.Pattern
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'androidx.navigation.safeargs.kotlin'
}
def flavor
android {
compileSdk 33
compileSdk 31
defaultConfig {
minSdk 21
targetSdk 33
versionCode 477
versionName "3.17.0"
targetSdk 31
versionCode 393
versionName "3.0.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
flavorDimensions "default"
@ -23,12 +19,8 @@ android {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
applicationIdSuffix '.debug'
}
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
@ -63,21 +55,6 @@ android {
java.srcDirs = ['src/main/java', 'src/fdroid/java']
res.srcDirs = ['src/main/res', 'src/fdroid/res']
}
main {
res.srcDirs = [
'src/main/res/layouts/mastodon',
'src/main/res/layouts/peertube',
'src/main/res/layouts',
'src/main/layout',
'src/main/res/drawables/mastodon',
'src/main/res/drawables/peertube',
'src/main/res/drawables',
'src/main/res/menus/mastodon',
'src/main/res/menus/peertube',
'src/main/res/menus',
'src/main/res'
]
}
}
configurations {
all {
@ -93,140 +70,62 @@ allprojects {
}
dependencies {
implementation project(':autoimageslider')
implementation 'androidx.appcompat:appcompat:1.6.0'
implementation 'com.google.android.material:material:1.7.0'
implementation 'com.jaredrummler:colorpicker:1.1.0'
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation "com.google.code.gson:gson:2.9.1"
implementation "com.google.code.gson:gson:2.8.6"
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.0'
implementation "org.conscrypt:conscrypt-android:2.5.2"
implementation 'com.github.evozi:Cyanea:1.0.7'
implementation 'com.vanniktech:emoji-one:0.6.0'
implementation 'com.github.GrenderG:Toasty:1.5.2'
implementation "com.github.bumptech.glide:glide:4.14.2"
implementation "com.github.bumptech.glide:okhttp3-integration:4.14.2"
implementation("com.github.bumptech.glide:recyclerview-integration:4.14.2") {
implementation 'org.framagit.tom79:SparkButton:1.0.13'
implementation "com.github.bumptech.glide:glide:4.12.0"
implementation 'com.github.mergehez:ArgPlayer:v3.1'
implementation ("com.github.bumptech.glide:recyclerview-integration:4.12.0") {
// Excludes the support library because it's already included by Glide.
transitive = false
}
implementation "org.jsoup:jsoup:1.15.1"
implementation 'com.github.mergehez:ArgPlayer:v3.1'
implementation project(path: ':mytransl')
implementation project(path: ':ratethisapp')
implementation project(path: ':sparkbutton')
implementation 'com.burhanrashid52:photoeditor:1.5.1'
implementation("com.vanniktech:android-image-cropper:4.3.3")
implementation project(path: ':mathjaxandroid')
implementation project(path: ':cropper')
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:2.23.0'
implementation 'com.google.android.exoplayer:exoplayer:2.18.1'
implementation 'com.github.penfeizhou.android.animation:apng:2.22.0'
implementation 'com.github.penfeizhou.android.animation:gif:2.22.0'
implementation 'com.google.android.exoplayer:exoplayer:2.16.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.7.1"
implementation 'app.futured.hauler:hauler:5.0.0'
implementation "com.github.chrisbanes:PhotoView:2.3.0"
implementation "ch.acra:acra-mail:5.9.6"
implementation "ch.acra:acra-mail:5.9.3"
implementation "ch.acra:acra-limiter:5.9.3"
implementation "ch.acra:acra-dialog:5.9.6"
implementation "ch.acra:acra-dialog:5.9.3"
implementation "com.madgag.spongycastle:bctls-jdk15on:1.58.0.0"
implementation 'com.github.UnifiedPush:android-connector:2.0.1'
implementation 'com.github.UnifiedPush:android-connector:2.0.0'
// 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'
}
playstoreImplementation 'com.github.UnifiedPush:android-embedded_fcm_distributor:1.1.0'
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.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'
implementation 'androidx.lifecycle:lifecycle-livedata:2.4.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.4.1'
implementation 'androidx.navigation:navigation-fragment:2.4.2'
implementation 'androidx.navigation:navigation-ui:2.4.2'
testImplementation 'junit:junit:'
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:1.1.5'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1'
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.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.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.vkay94:DoubleTapPlayerView:1.0.0'
//************ CAST **************///
//---> Google libs (google_full)
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() {
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
println("tskReqStr:" +tskReqStr)
Pattern pattern
if( tskReqStr.contains( "assemble" ) ) // to run ./gradlew assembleRelease to build APK
pattern = Pattern.compile("assemble(\\w+)(Release|Debug)")
else if( tskReqStr.contains( "bundle" ) ) // to run ./gradlew bundleRelease to build .aab
pattern = Pattern.compile("assemble(\\w+)(Release|Debug)")
else
pattern = Pattern.compile("generate(\\w+)(Release|Debug)")
Matcher matcher = pattern.matcher( tskReqStr )
println(tskReqStr)
if( matcher.find() )
return matcher.group(1).toLowerCase()
else
{
println "NO MATCH FOUND"
return ""
}
}
println("Flavor: ${getCurrentFlavor()}")
if ( getCurrentFlavor() == "playstore" ){
apply plugin: 'com.google.gms.google-services'
}

View file

@ -1,15 +1,15 @@
package app.fedilab.android;
import static org.junit.Assert.assertEquals;
import android.content.Context;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*

View file

@ -1,68 +0,0 @@
{
"project_info": {
"project_number": "479837431022",
"project_id": "pc-api-4835782490875392372-140",
"storage_bucket": "pc-api-4835782490875392372-140.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:479837431022:android:1102a97a55202beb547fff",
"android_client_info": {
"package_name": "app.fedilab.android"
}
},
"oauth_client": [
{
"client_id": "479837431022-mettpakdcso72c35djvikfc57l4i7n53.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyCklTEEgLUxy__0Vzcr5_H179kYPXGjmGo"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "479837431022-mettpakdcso72c35djvikfc57l4i7n53.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:479837431022:android:529536f519b8f4ce547fff",
"android_client_info": {
"package_name": "app.fedilab.android.debug"
}
},
"oauth_client": [
{
"client_id": "479837431022-mettpakdcso72c35djvikfc57l4i7n53.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyCklTEEgLUxy__0Vzcr5_H179kYPXGjmGo"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "479837431022-mettpakdcso72c35djvikfc57l4i7n53.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"
}

View file

@ -1,3 +0,0 @@
<resources>
<string name="app_name" translatable="false">Fedilab dbg</string>
</resources>

View file

@ -1,19 +0,0 @@
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<shortcut
android:shortcutId="compose"
android:enabled="true"
android:icon="@drawable/ic_baseline_add_comment_24"
android:shortcutShortLabel="@string/compose_shortcut_short_label1"
tools:targetApi="n_mr1">
<intent
android:action="app.fedilab.android.shorcut.compose"
android:targetClass="app.fedilab.android.activities.MainActivity"
android:targetPackage="fr.gouv.etalab.mastodon.debug" />
<categories android:name="android.shortcut.conversation" />
<capability-binding android:key="actions.intent.CREATE_MESSAGE" />
</shortcut>
</shortcuts>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="my_images"
path="Android/data/fr.gouv.etalab.mastodon.debug/files/Pictures" />
<cache-path
name="*"
path="." />
</paths>

View file

@ -1,211 +0,0 @@
package app.fedilab.android.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.peertube.helper.Helper.CAST_ID;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.webkit.MimeTypeMap;
import androidx.appcompat.app.AlertDialog;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.security.GeneralSecurityException;
import app.fedilab.android.R;
import app.fedilab.android.databinding.ActivityPeertubeBinding;
import app.fedilab.android.mastodon.activities.BaseBarActivity;
import app.fedilab.android.peertube.client.data.VideoData;
import app.fedilab.android.peertube.helper.Helper;
import su.litvak.chromecast.api.v2.ChromeCast;
import su.litvak.chromecast.api.v2.MediaStatus;
import su.litvak.chromecast.api.v2.Status;
public class BasePeertubeActivity extends BaseBarActivity {
protected ActivityPeertubeBinding binding;
protected VideoData.Video peertube;
protected ExoPlayer player;
protected String videoURL;
protected String subtitlesStr;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityPeertubeBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
binding.minController.castPlay.setOnClickListener(v -> {
binding.minController.castLoader.setVisibility(View.VISIBLE);
if (PeertubeBaseMainActivity.chromeCast != null) {
new Thread(() -> {
try {
int icon = -1;
if (PeertubeBaseMainActivity.chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PLAYING) {
PeertubeBaseMainActivity.chromeCast.pause();
icon = R.drawable.ic_baseline_play_arrow_32;
} else if (PeertubeBaseMainActivity.chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PAUSED) {
PeertubeBaseMainActivity.chromeCast.play();
icon = R.drawable.ic_baseline_pause_32;
}
if (icon != -1) {
Handler mainHandler = new Handler(Looper.getMainLooper());
int finalIcon = icon;
Runnable myRunnable = () -> binding.minController.castPlay.setImageResource(finalIcon);
mainHandler.post(myRunnable);
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> binding.minController.castLoader.setVisibility(View.GONE);
mainHandler.post(myRunnable);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_cast) {
if (PeertubeBaseMainActivity.chromeCasts != null && PeertubeBaseMainActivity.chromeCasts.size() > 0) {
String[] chromecast_choice = new String[PeertubeBaseMainActivity.chromeCasts.size()];
AlertDialog.Builder alt_bld = new MaterialAlertDialogBuilder(this);
alt_bld.setTitle(R.string.chromecast_choice);
int i = 0;
for (ChromeCast cc : PeertubeBaseMainActivity.chromeCasts) {
chromecast_choice[i] = cc.getTitle();
i++;
}
i = 0;
for (ChromeCast cc : PeertubeBaseMainActivity.chromeCasts) {
if (PeertubeBaseMainActivity.chromecastActivated && cc.isConnected()) {
break;
}
i++;
}
alt_bld.setSingleChoiceItems(chromecast_choice, i, (dialog, position) -> {
PeertubeBaseMainActivity.chromeCast = PeertubeBaseMainActivity.chromeCasts.get(position);
new Thread(() -> {
if (PeertubeBaseMainActivity.chromeCast != null) {
Intent intentBC = new Intent(Helper.RECEIVE_CAST_SETTINGS);
Bundle b = new Bundle();
if (PeertubeBaseMainActivity.chromecastActivated) {
b.putInt("displayed", 0);
intentBC.putExtras(b);
LocalBroadcastManager.getInstance(BasePeertubeActivity.this).sendBroadcast(intentBC);
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
binding.doubleTapPlayerView.setVisibility(View.VISIBLE);
binding.minController.castMiniController.setVisibility(View.GONE);
};
mainHandler.post(myRunnable);
} else {
b.putInt("displayed", 1);
b.putSerializable("castedTube", peertube);
intentBC.putExtras(b);
LocalBroadcastManager.getInstance(BasePeertubeActivity.this).sendBroadcast(intentBC);
try {
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
invalidateOptionsMenu();
binding.minController.castLoader.setVisibility(View.VISIBLE);
player.setPlayWhenReady(false);
binding.doubleTapPlayerView.setVisibility(View.GONE);
binding.minController.castMiniController.setVisibility(View.VISIBLE);
dialog.dismiss();
if (videoURL != null) {
if (player != null && player.getCurrentPosition() > 0) {
videoURL += "?start=" + (player.getCurrentPosition() / 1000);
}
}
};
mainHandler.post(myRunnable);
if (!PeertubeBaseMainActivity.chromeCast.isConnected()) {
PeertubeBaseMainActivity.chromeCast.connect();
}
myRunnable = this::invalidateOptionsMenu;
mainHandler.post(myRunnable);
Status status = PeertubeBaseMainActivity.chromeCast.getStatus();
if (PeertubeBaseMainActivity.chromeCast.isAppAvailable(CAST_ID) && !status.isAppRunning(CAST_ID)) {
PeertubeBaseMainActivity.chromeCast.launchApp(CAST_ID);
}
if (videoURL != null) {
String mime = MimeTypeMap.getFileExtensionFromUrl(videoURL);
PeertubeBaseMainActivity.chromeCast.setRequestTimeout(60000);
PeertubeBaseMainActivity.chromeCast.load(peertube.getTitle(), null, videoURL, mime);
PeertubeBaseMainActivity.chromeCast.play();
binding.minController.castPlay.setImageResource(R.drawable.ic_baseline_pause_32);
}
myRunnable = () -> binding.minController.castLoader.setVisibility(View.GONE);
mainHandler.post(myRunnable);
} catch (IOException | GeneralSecurityException e) {
e.printStackTrace();
}
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
invalidateOptionsMenu();
dialog.dismiss();
};
mainHandler.post(myRunnable);
}
}).start();
});
alt_bld.setPositiveButton(R.string.close, (dialog, id) -> dialog.dismiss());
AlertDialog alert = alt_bld.create();
alert.show();
}
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onCreateOptionsMenu(@NotNull Menu menu) {
getMenuInflater().inflate(R.menu.video_menu, menu);
MenuItem castItem = menu.findItem(R.id.action_cast);
if (PeertubeBaseMainActivity.chromeCasts != null && PeertubeBaseMainActivity.chromeCasts.size() > 0) {
castItem.setVisible(true);
if (PeertubeBaseMainActivity.chromeCast != null && PeertubeBaseMainActivity.chromeCast.isConnected()) {
castItem.setIcon(R.drawable.ic_baseline_cast_connected_24);
} else {
castItem.setIcon(R.drawable.ic_baseline_cast_24);
}
}
return true;
}
}

View file

@ -1,263 +0,0 @@
package app.fedilab.android.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.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import app.fedilab.android.R;
import app.fedilab.android.databinding.ActivityMainPeertubeBinding;
import app.fedilab.android.mastodon.activities.BaseActivity;
import app.fedilab.android.peertube.client.data.VideoData;
import app.fedilab.android.peertube.helper.Helper;
import su.litvak.chromecast.api.v2.ChromeCast;
import su.litvak.chromecast.api.v2.ChromeCasts;
import su.litvak.chromecast.api.v2.ChromeCastsListener;
import su.litvak.chromecast.api.v2.MediaStatus;
public abstract class PeertubeBaseMainActivity extends BaseActivity implements ChromeCastsListener {
public static List<ChromeCast> chromeCasts;
public static ChromeCast chromeCast;
public static boolean chromecastActivated = false;
protected ActivityMainPeertubeBinding binding;
private BroadcastReceiver manage_chromecast;
private VideoData.Video castedTube;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainPeertubeBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
ChromeCastsListener chromeCastsListener = this;
ChromeCasts.registerListener(chromeCastsListener);
binding.castClose.setOnClickListener(v -> {
Intent intentBC = new Intent(Helper.RECEIVE_CAST_SETTINGS);
Bundle b = new Bundle();
b.putInt("displayed", 0);
intentBC.putExtras(b);
LocalBroadcastManager.getInstance(PeertubeBaseMainActivity.this).sendBroadcast(intentBC);
});
binding.castTogglePlay.setOnClickListener(v -> {
if (chromeCast != null) {
new Thread(() -> {
try {
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> binding.castTogglePlay.setVisibility(View.GONE);
mainHandler.post(myRunnable);
int icon = -1;
if (chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PLAYING) {
chromeCast.pause();
icon = R.drawable.ic_baseline_play_arrow_32;
} else if (chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PAUSED) {
chromeCast.play();
icon = R.drawable.ic_baseline_pause_32;
}
if (icon != -1) {
int finalIcon = icon;
myRunnable = () -> binding.castTogglePlay.setImageResource(finalIcon);
mainHandler.post(myRunnable);
}
myRunnable = () -> binding.castTogglePlay.setVisibility(View.VISIBLE);
mainHandler.post(myRunnable);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
});
manage_chromecast = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Bundle b = intent.getExtras();
assert b != null;
int state = b.getInt("state_asked", -1);
int displayed = b.getInt("displayed", -1);
castedTube = (VideoData.Video) b.getSerializable("castedTube");
if (state == 1) {
discoverCast();
} else if (state == 0) {
new Thread(() -> {
try {
if (chromeCast != null) {
chromeCast.stopApp();
chromeCast.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
if (displayed == 1) {
chromecastActivated = true;
if (castedTube != null) {
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;
binding.castInfo.setVisibility(View.GONE);
new Thread(() -> {
try {
if (chromeCast != null) {
chromeCast.stopApp();
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
};
LocalBroadcastManager.getInstance(PeertubeBaseMainActivity.this).registerReceiver(manage_chromecast, new IntentFilter(Helper.RECEIVE_CAST_SETTINGS));
}
@Override
public void newChromeCastDiscovered(ChromeCast chromeCast) {
if (chromeCasts == null) {
chromeCasts = new ArrayList<>();
chromeCasts.add(chromeCast);
} else {
boolean canBeAdded = true;
for (ChromeCast cast : chromeCasts) {
if (cast.getName().compareTo(chromeCast.getName()) == 0) {
canBeAdded = false;
break;
}
}
if (canBeAdded) {
chromeCasts.add(chromeCast);
}
}
try {
if (chromeCast.isAppRunning(Helper.CAST_ID) && chromeCast.getMediaStatus() != null && chromeCast.getMediaStatus().playerState != null) {
if (binding.castInfo.getVisibility() == View.GONE) {
binding.castInfo.setVisibility(View.VISIBLE);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void chromeCastRemoved(ChromeCast chromeCast) {
}
@Override
public void onDestroy() {
super.onDestroy();
ChromeCasts.unregisterListener(this);
if (manage_chromecast != null) {
LocalBroadcastManager.getInstance(PeertubeBaseMainActivity.this).unregisterReceiver(manage_chromecast);
new Thread(() -> {
if (chromeCasts != null && chromeCasts.size() > 0) {
for (ChromeCast cast : chromeCasts) {
try {
cast.stopApp();
cast.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
if (chromeCasts != null) {
chromeCasts = null;
}
if (chromeCast != null) {
chromeCast = null;
}
}
//Method for discovering cast devices
public void discoverCast() {
new Thread(() -> {
if (chromeCasts != null) {
for (ChromeCast cast : chromeCasts) {
try {
cast.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
chromeCasts = null;
}
chromeCasts = new ArrayList<>();
try {
List<NetworkInterface> interfaces;
interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface ni : interfaces) {
if ((!ni.isLoopback()) && ni.isUp() && (ni.getName().equals("wlan0"))) {
Enumeration<InetAddress> inetAddressEnumeration = ni.getInetAddresses();
while (inetAddressEnumeration.hasMoreElements()) {
InetAddress inetAddress = inetAddressEnumeration.nextElement();
ChromeCasts.restartDiscovery(inetAddress);
int tryFind = 0;
while (ChromeCasts.get().isEmpty() && tryFind < 5) {
try {
//noinspection BusyWait
Thread.sleep(1000);
tryFind++;
} catch (InterruptedException ignored) {
}
}
}
}
}
ChromeCasts.stopDiscovery();
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = this::invalidateOptionsMenu;
mainHandler.post(myRunnable);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}

View file

@ -1,52 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/castMiniController"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
android:visibility="gone">
<ImageView
android:id="@+id/cast_play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/play"
android:src="@drawable/ic_baseline_play_arrow_32"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cast_loader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:id="@+id/cast_loader_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/please_wait"
android:textColor="?colorAccent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/cast_loader_small"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ProgressBar
android:id="@+id/cast_loader_small"
android:layout_width="wrap_content"
android:layout_height="18dp"
android:layout_gravity="center"
android:layout_marginStart="5dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/cast_loader_text"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_cast"
android:icon="@drawable/ic_baseline_cast_24"
android:title="@string/cast"
android:visible="false"
app:showAsAction="always" />
</menu>

View file

@ -1,19 +0,0 @@
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<shortcut
android:shortcutId="compose"
android:enabled="true"
android:icon="@drawable/ic_baseline_add_comment_24"
android:shortcutShortLabel="@string/compose_shortcut_short_label1"
tools:targetApi="n_mr1">
<intent
android:action="app.fedilab.android.shorcut.compose"
android:targetClass="app.fedilab.android.activities.MainActivity"
android:targetPackage="fr.gouv.etalab.mastodon" />
<categories android:name="android.shortcut.conversation" />
<capability-binding android:key="actions.intent.CREATE_MESSAGE" />
</shortcut>
</shortcuts>

View file

@ -2,7 +2,7 @@
<paths>
<external-path
name="my_images"
path="Android/data/fr.gouv.etalab.mastodon/files/Pictures" />
path="Android/data/fr.gouv.etalab.mastodon.test/files/Pictures" />
<cache-path
name="*"

View file

@ -3,9 +3,7 @@
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.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
@ -13,171 +11,37 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<queries>
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
</queries>
<application
android:name="app.fedilab.android.MainApplication"
android:name=".MainApplication"
tools:replace="android:allowBackup"
android:allowBackup="false"
android:configChanges="orientation|screenSize"
android:icon="@mipmap/ic_launcher_bubbles"
android:label="@string/app_name"
android:largeHeap="true"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_bubbles_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true"
tools:replace="android:allowBackup">
android:label="@string/app_name"
android:configChanges="orientation|screenSize"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppThemeDark"
>
<activity
android:name=".activities.MainActivity"
android:configChanges="orientation|screenSize|keyboardHidden|screenLayout|smallestScreenSize"
android:exported="true">
android:configChanges="orientation|screenSize"
android:exported="true"
>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.MAIN" />
<data android:mimeType="image/*" />
<data android:mimeType="video/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
<data android:mimeType="video/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- The app is a good candidate for URL in https://domain.name/@xxxxxx-->
<!-- It should cover every URLs for statuses but some others not related to mastodon matching this scheme -->
<data
android:host="*"
android:pathPrefix="/@"
android:scheme="https" />
<category android:name="android.intent.category.LAUNCHER" />
</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:exported="true"
android:name=".activities.LoginActivity"
android:configChanges="orientation|screenSize"
android:exported="true"
android:windowSoftInputMode="stateAlwaysHidden">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
@ -186,71 +50,58 @@
android:scheme="fedilab" />
</intent-filter>
</activity>
<activity
android:name=".mastodon.activities.StatusHistoryActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/status_history"
android:theme="@style/AppThemeBar" />
<activity
android:name=".mastodon.activities.ContextActivity"
android:name=".activities.WebviewConnectActivity"
android:configChanges="keyboardHidden|orientation|screenSize" />
<activity
android:name=".mastodon.activities.DraftActivity"
android:name=".activities.ContextActivity"
android:configChanges="keyboardHidden|orientation|screenSize" />
<activity
android:name=".mastodon.imageeditor.EditImageActivity"
android:name=".activities.DraftActivity"
android:configChanges="keyboardHidden|orientation|screenSize" />
<activity
android:name=".mastodon.activities.ComposeActivity"
android:name=".imageeditor.EditImageActivity"
android:configChanges="keyboardHidden|orientation|screenSize" />
<activity
android:name=".activities.ComposeActivity"
android:configChanges="orientation|screenSize"
android:label="@string/compose" />
<activity
android:name=".mastodon.activities.StatusInfoActivity"
android:name=".activities.StatusInfoActivity"
android:configChanges="keyboardHidden|orientation|screenSize" />
<activity
android:name=".mastodon.activities.FollowRequestActivity"
android:name=".activities.FollowRequestActivity"
android:configChanges="keyboardHidden|orientation|screenSize" />
<activity
android:name=".mastodon.activities.ProfileActivity"
android:name=".activities.WebviewActivity"
android:configChanges="keyboardHidden|orientation|screenSize" />
<activity
android:name=".activities.ProfileActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/account" />
<activity
android:name=".mastodon.activities.admin.AdminAccountActivity"
android:name=".activities.AdminAccountActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/account" />
<activity
android:name=".mastodon.activities.AccountReportActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/account"
android:theme="@style/AppThemeBar" />
<activity
android:name=".mastodon.activities.admin.AdminReportActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/report"
android:theme="@style/AppThemeBar" />
<activity
android:name=".mastodon.activities.ScheduledActivity"
android:name=".activities.ScheduledActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/scheduled" />
<activity
android:name="com.canhub.cropper.CropImageActivity"
android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:theme="@style/Base.Theme.AppCompat" />
<service
android:name=".services.PostMessageService"
android:label="@string/post_message" />
<activity
android:name=".mastodon.activities.SearchResultTabActivity"
android:name=".activities.SearchResultTabActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/search"
android:theme="@style/AppThemeBar" />
android:theme="@style/AppThemeBar"
android:label="@string/search" />
<activity
android:name=".mastodon.activities.TrendsActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/trending"
android:theme="@style/AppThemeBar" />
<activity
android:name=".mastodon.activities.ReorderTimelinesActivity"
android:name=".activities.ReorderTimelinesActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/reorder_timelines"
android:theme="@style/AppThemeBar" />
@ -261,86 +112,84 @@
android:theme="@style/AppThemeBar" />
<activity
android:name=".mastodon.activities.admin.AdminDomainBlockActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/blocked_domains"
android:theme="@style/AppThemeBar" />
<activity
android:name=".mastodon.activities.SuggestionActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/Suggestions"
android:theme="@style/AppThemeBar" />
<activity
android:name=".mastodon.activities.DirectoryActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/Directory"
android:theme="@style/AppThemeBar" />
<activity
android:name=".mastodon.activities.PartnerShipActivity"
android:name=".activities.PartnerShipActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/action_about"
android:theme="@style/AppThemeBar" />
<activity
android:name=".mastodon.activities.ActionActivity"
android:name=".activities.ActionActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/interactions"
android:theme="@style/AppThemeBar" />
<activity
android:name=".mastodon.activities.admin.AdminActionActivity"
android:name=".activities.AdminActionActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/administration"
android:theme="@style/AppThemeBar" />
<activity
android:name=".mastodon.activities.MastodonListActivity"
android:name=".activities.MastodonListActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/action_lists"
android:theme="@style/AppThemeBar" />
<activity
android:name=".mastodon.activities.FollowedTagActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/followed_tags"
android:theme="@style/AppThemeBar" />
<activity
android:name=".mastodon.activities.SettingsActivity"
android:name=".activities.SettingsActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/settings"
android:theme="@style/AppThemeBar" />
<activity
android:name=".mastodon.activities.HashTagActivity"
android:configChanges="keyboardHidden|orientation|screenSize" />
<activity
android:name=".mastodon.activities.AnnouncementActivity"
android:configChanges="keyboardHidden|orientation|screenSize" />
<activity
android:name=".mastodon.activities.MediaActivity"
android:name=".activities.InstanceActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@style/Transparent" />
android:label="@string/action_about_instance"
android:theme="@style/DialogDark" />
<activity
android:name=".activities.InstanceProfileActivity"
android:excludeFromRecents="true"
android:theme="@style/DialogDark" />
<activity
android:name=".activities.ProxyActivity"
android:excludeFromRecents="true"
android:theme="@style/DialogDark" />
<activity
android:name=".activities.HashTagActivity"
android:configChanges="keyboardHidden|orientation|screenSize" />
<activity
android:name=".activities.AnnouncementActivity"
android:configChanges="keyboardHidden|orientation|screenSize" />
<activity
android:name=".activities.MediaActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@style/TransparentDark" />
<activity
android:name=".mastodon.activities.ReportActivity"
android:theme="@style/AppThemeBar"
android:name=".activities.InstanceHealthActivity"
android:excludeFromRecents="true"
android:theme="@style/DialogDark" />
<activity
android:name=".activities.ReportActivity"
android:theme="@style/AppThemeBarDark"
android:windowSoftInputMode="stateVisible" />
<activity
android:name=".mastodon.activities.CustomSharingActivity"
android:name=".activities.CustomSharingActivity"
android:label="@string/settings_title_custom_sharing"
android:theme="@style/AppThemeBar"
android:windowSoftInputMode="stateVisible" />
android:windowSoftInputMode="stateVisible"
android:theme="@style/AppThemeBarDark" />
<activity
android:name=".mastodon.activities.FilterActivity"
android:name=".activities.FilterActivity"
android:label="@string/filters"
android:theme="@style/AppThemeBar"
android:theme="@style/AppThemeBarDark"
android:windowSoftInputMode="stateVisible" />
<activity
android:name=".mastodon.activities.EditProfileActivity"
android:name=".activities.EditProfileActivity"
android:label="@string/edit_profile"
android:theme="@style/AppThemeBar"
android:theme="@style/AppThemeBarDark"
android:windowSoftInputMode="stateVisible" />
<activity
android:name=".mastodon.activities.CacheActivity"
android:name=".activities.CacheActivity"
android:label="@string/action_cache"
android:theme="@style/AppThemeBar" />
android:theme="@style/AppThemeBarDark" />
<provider
android:name="androidx.core.content.FileProvider"
@ -353,7 +202,7 @@
</provider>
<receiver
android:name=".mastodon.broadcastreceiver.ToastMessage"
android:name=".broadcastreceiver.ToastMessage"
android:exported="false">
<intent-filter>
<action android:name="RECEIVE_TOAST_MESSAGE" />
@ -361,7 +210,7 @@
</receiver>
<receiver
android:name=".mastodon.services.CustomReceiver"
android:name=".services.CustomReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
@ -372,105 +221,5 @@
<action android:name="org.unifiedpush.android.connector.REGISTRATION_REFUSED" />
</intent-filter>
</receiver>
<activity
android:name=".peertube.activities.PeertubeMainActivity"
android:configChanges="keyboardHidden|orientation|screenSize" />
<activity
android:name=".peertube.activities.PeertubeActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:launchMode="singleTask"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
tools:targetApi="n" />
<activity
android:name=".peertube.activities.PeertubeEditUploadActivity"
android:configChanges="orientation|screenSize"
android:exported="false"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name=".peertube.activities.ShowChannelActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name=".peertube.activities.ShowAccountActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name=".peertube.activities.AccountActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name=".peertube.activities.MyAccountActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name=".peertube.activities.SearchActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name=".peertube.activities.AllPlaylistsActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name=".peertube.activities.AllLocalPlaylistsActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name=".peertube.activities.PlaylistsActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name=".peertube.activities.VideosTimelineActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name=".peertube.activities.SepiaSearchActivity"
android:configChanges="orientation|screenSize"
android:label="@string/sepia_search"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name=".peertube.activities.ManageInstancesActivity"
android:configChanges="orientation|screenSize"
android:label="@string/instances_picker"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name=".peertube.activities.WebviewActivity"
android:configChanges="keyboardHidden|orientation|screenSize" />
<activity
android:name=".peertube.activities.LoginActivity"
android:configChanges="orientation|screenSize"
android:exported="true"
android:windowSoftInputMode="stateAlwaysHidden">
<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:host="backtotubelab"
android:scheme="tubelab" />
</intent-filter>
</activity>
<activity
android:name=".peertube.activities.SettingsActivity"
android:configChanges="orientation|screenSize"
android:label="@string/settings"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name=".peertube.activities.PeertubeUploadActivity"
android:configChanges="orientation|screenSize"
android:label="@string/upload_video"
android:windowSoftInputMode="stateAlwaysHidden" />
<service
android:name=".peertube.services.RetrieveInfoService"
android:exported="false" />
</application>
</manifest>

View file

@ -1,20 +0,0 @@
{
"1": "Music",
"2": "Films",
"3": "Vehicles",
"4": "Art",
"5": "Sports",
"6": "Travels",
"7": "Gaming",
"8": "People",
"9": "Comedy",
"10": "Entertainment",
"11": "News & Politics",
"12": "How To",
"13": "Education",
"14": "Activism",
"15": "Science & Technology",
"16": "Animals",
"17": "Kids",
"18": "Food"
}

View file

@ -1,199 +0,0 @@
{
"aa": "Afar",
"ab": "Abkhazian",
"af": "Afrikaans",
"ak": "Akan",
"am": "Amharic",
"ar": "Arabic",
"an": "Aragonese",
"ase": "American Sign Language",
"as": "Assamese",
"av": "Avaric",
"avk": "Kotava",
"ay": "Aymara",
"az": "Azerbaijani",
"ba": "Bashkir",
"bm": "Bambara",
"be": "Belarusian",
"bn": "Bengali",
"bfi": "British Sign Language",
"bi": "Bislama",
"bo": "Tibetan",
"bs": "Bosnian",
"br": "Breton",
"bg": "Bulgarian",
"bzs": "Brazilian Sign Language",
"ca": "Catalan",
"cs": "Czech",
"ch": "Chamorro",
"ce": "Chechen",
"cv": "Chuvash",
"kw": "Cornish",
"co": "Corsican",
"cr": "Cree",
"cse": "Czech Sign Language",
"csl": "Chinese Sign Language",
"cy": "Welsh",
"da": "Danish",
"de": "German",
"dv": "Dhivehi",
"dsl": "Danish Sign Language",
"dz": "Dzongkha",
"el": "Greek",
"en": "English",
"eo": "Esperanto",
"et": "Estonian",
"eu": "Basque",
"ee": "Ewe",
"fo": "Faroese",
"fa": "Persian",
"fj": "Fijian",
"fi": "Finnish",
"fr": "French",
"fy": "Western Frisian",
"fsl": "French Sign Language",
"ff": "Fulah",
"gd": "Scottish Gaelic",
"ga": "Irish",
"gl": "Galician",
"gv": "Manx",
"gn": "Guarani",
"gsg": "German Sign Language",
"gu": "Gujarati",
"ht": "Haitian",
"ha": "Hausa",
"sh": "Serbo-Croatian",
"he": "Hebrew",
"hz": "Herero",
"hi": "Hindi",
"ho": "Hiri Motu",
"hr": "Croatian",
"hu": "Hungarian",
"hy": "Armenian",
"ig": "Igbo",
"ii": "Sichuan Yi",
"iu": "Inuktitut",
"id": "Indonesian",
"ik": "Inupiaq",
"is": "Icelandic",
"it": "Italian",
"jv": "Javanese",
"jbo": "Lojban",
"ja": "Japanese",
"jsl": "Japanese Sign Language",
"kab": "Kabyle",
"kl": "Kalaallisut",
"kn": "Kannada",
"ks": "Kashmiri",
"ka": "Georgian",
"kr": "Kanuri",
"kk": "Kazakh",
"km": "Khmer",
"ki": "Kikuyu",
"rw": "Kinyarwanda",
"ky": "Kirghiz",
"kv": "Komi",
"kg": "Kongo",
"ko": "Korean",
"kj": "Kuanyama",
"ku": "Kurdish",
"lo": "Lao",
"la": "Latin",
"lv": "Latvian",
"li": "Limburgan",
"ln": "Lingala",
"lt": "Lithuanian",
"lb": "Luxembourgish",
"lu": "Luba-Katanga",
"lg": "Ganda",
"mh": "Marshallese",
"ml": "Malayalam",
"mr": "Marathi",
"mk": "Macedonian",
"mg": "Malagasy",
"mt": "Maltese",
"mn": "Mongolian",
"mi": "Maori",
"ms": "Malay (macrolanguage)",
"my": "Burmese",
"na": "Nauru",
"nv": "Navajo",
"nr": "South Ndebele",
"nd": "North Ndebele",
"ng": "Ndonga",
"ne": "Nepali (macrolanguage)",
"nl": "Dutch",
"nn": "Norwegian Nynorsk",
"nb": "Norwegian Bokmål",
"no": "Norwegian",
"ny": "Nyanja",
"oc": "Occitan",
"oj": "Ojibwa",
"or": "Oriya (macrolanguage)",
"om": "Oromo",
"os": "Ossetian",
"pa": "Panjabi",
"pks": "Pakistan Sign Language",
"pl": "Polish",
"pt": "Portuguese",
"ps": "Pushto",
"qu": "Quechua",
"rm": "Romansh",
"ro": "Romanian",
"rsl": "Russian Sign Language",
"rn": "Rundi",
"ru": "Russian",
"sg": "Sango",
"sdl": "Saudi Arabian Sign Language",
"sfs": "South African Sign Language",
"si": "Sinhala",
"sk": "Slovak",
"sl": "Slovenian",
"se": "Northern Sami",
"sm": "Samoan",
"sn": "Shona",
"sd": "Sindhi",
"so": "Somali",
"st": "Southern Sotho",
"es": "Spanish",
"sq": "Albanian",
"sc": "Sardinian",
"sr": "Serbian",
"ss": "Swati",
"su": "Sundanese",
"sw": "Swahili (macrolanguage)",
"sv": "Swedish",
"swl": "Swedish Sign Language",
"ty": "Tahitian",
"ta": "Tamil",
"tt": "Tatar",
"te": "Telugu",
"tg": "Tajik",
"tl": "Tagalog",
"th": "Thai",
"ti": "Tigrinya",
"tlh": "Klingon",
"to": "Tonga (Tonga Islands)",
"tn": "Tswana",
"ts": "Tsonga",
"tk": "Turkmen",
"tr": "Turkish",
"tw": "Twi",
"ug": "Uighur",
"uk": "Ukrainian",
"ur": "Urdu",
"uz": "Uzbek",
"ve": "Venda",
"vi": "Vietnamese",
"wa": "Walloon",
"wo": "Wolof",
"xh": "Xhosa",
"yi": "Yiddish",
"yo": "Yoruba",
"za": "Zhuang",
"zh": "Chinese",
"zu": "Zulu",
"zxx": "No linguistic content",
"zh-Hans": "Simplified Chinese",
"zh-Hant": "Traditional Chinese"
}

View file

@ -1,730 +0,0 @@
[
{
"code": "aa",
"language": "Afaraf"
},
{
"code": "ab",
"language": "аҧсуа бызшәа"
},
{
"code": "ae",
"language": "avesta"
},
{
"code": "af",
"language": "Afrikaans"
},
{
"code": "ak",
"language": "Akan"
},
{
"code": "am",
"language": "አማርኛ"
},
{
"code": "an",
"language": "aragonés"
},
{
"code": "ar",
"language": "اللغة العربية"
},
{
"code": "as",
"language": "অসমীয়া"
},
{
"code": "av",
"language": "авар мацӀ"
},
{
"code": "ay",
"language": "aymar aru"
},
{
"code": "az",
"language": "azərbaycan dili"
},
{
"code": "ba",
"language": "башҡорт теле"
},
{
"code": "be",
"language": "беларуская мова"
},
{
"code": "bg",
"language": "български език"
},
{
"code": "bh",
"language": "भोजपुरी"
},
{
"code": "bi",
"language": "Bislama"
},
{
"code": "bm",
"language": "bamanankan"
},
{
"code": "bn",
"language": "বাংলা"
},
{
"code": "bo",
"language": "བོད་ཡིག"
},
{
"code": "br",
"language": "brezhoneg"
},
{
"code": "bs",
"language": "bosanski jezik"
},
{
"code": "ca",
"language": "Català"
},
{
"code": "ce",
"language": "нохчийн мотт"
},
{
"code": "ch",
"language": "Chamoru"
},
{
"code": "co",
"language": "corsu"
},
{
"code": "cr",
"language": "ᓀᐦᐃᔭᐍᐏᐣ"
},
{
"code": "cs",
"language": "čeština"
},
{
"code": "cu",
"language": "ѩзыкъ словѣньскъ"
},
{
"code": "cv",
"language": "чӑваш чӗлхи"
},
{
"code": "cy",
"language": "Cymraeg"
},
{
"code": "da",
"language": "dansk"
},
{
"code": "de",
"language": "Deutsch"
},
{
"code": "dv",
"language": "Dhivehi"
},
{
"code": "dz",
"language": "རྫོང་ཁ"
},
{
"code": "ee",
"language": "Eʋegbe"
},
{
"code": "el",
"language": "Ελληνικά"
},
{
"code": "en",
"language": "English"
},
{
"code": "eo",
"language": "Esperanto"
},
{
"code": "es",
"language": "Español"
},
{
"code": "et",
"language": "eesti"
},
{
"code": "eu",
"language": "euskara"
},
{
"code": "fa",
"language": "فارسی"
},
{
"code": "ff",
"language": "Fulfulde"
},
{
"code": "fi",
"language": "suomi"
},
{
"code": "fj",
"language": "Vakaviti"
},
{
"code": "fo",
"language": "føroyskt"
},
{
"code": "fr",
"language": "Français"
},
{
"code": "fy",
"language": "Frysk"
},
{
"code": "ga",
"language": "Gaeilge"
},
{
"code": "gd",
"language": "Gàidhlig"
},
{
"code": "gl",
"language": "galego"
},
{
"code": "gu",
"language": "ગુજરાતી"
},
{
"code": "gv",
"language": "Gaelg"
},
{
"code": "ha",
"language": "هَوُسَ"
},
{
"code": "he",
"language": "עברית"
},
{
"code": "hi",
"language": "हिन्दी"
},
{
"code": "ho",
"language": "Hiri Motu"
},
{
"code": "hr",
"language": "Hrvatski"
},
{
"code": "ht",
"language": "Kreyòl ayisyen"
},
{
"code": "hu",
"language": "magyar"
},
{
"code": "hy",
"language": "Հայերեն"
},
{
"code": "hz",
"language": "Otjiherero"
},
{
"code": "ia",
"language": "Interlingua"
},
{
"code": "id",
"language": "Bahasa Indonesia"
},
{
"code": "ie",
"language": "Interlingue"
},
{
"code": "ig",
"language": "Asụsụ Igbo"
},
{
"code": "ii",
"language": "ꆈꌠ꒿ Nuosuhxop"
},
{
"code": "ik",
"language": "Iñupiaq"
},
{
"code": "io",
"language": "Ido"
},
{
"code": "is",
"language": "Íslenska"
},
{
"code": "it",
"language": "Italiano"
},
{
"code": "iu",
"language": "ᐃᓄᒃᑎᑐᑦ"
},
{
"code": "ja",
"language": "日本語"
},
{
"code": "jv",
"language": "basa Jawa"
},
{
"code": "ka",
"language": "ქართული"
},
{
"code": "kg",
"language": "Kikongo"
},
{
"code": "ki",
"language": "Gĩkũyũ"
},
{
"code": "kj",
"language": "Kuanyama"
},
{
"code": "kk",
"language": "қазақ тілі"
},
{
"code": "kl",
"language": "kalaallisut"
},
{
"code": "km",
"language": "ខេមរភាសា"
},
{
"code": "kn",
"language": "ಕನ್ನಡ"
},
{
"code": "ko",
"language": "한국어"
},
{
"code": "kr",
"language": "Kanuri"
},
{
"code": "ks",
"language": "कश्मीरी"
},
{
"code": "ku",
"language": "Kurmancî"
},
{
"code": "kv",
"language": "коми кыв"
},
{
"code": "kw",
"language": "Kernewek"
},
{
"code": "ky",
"language": "Кыргызча"
},
{
"code": "la",
"language": "latine"
},
{
"code": "lb",
"language": "Lëtzebuergesch"
},
{
"code": "lg",
"language": "Luganda"
},
{
"code": "li",
"language": "Limburgs"
},
{
"code": "ln",
"language": "Lingála"
},
{
"code": "lo",
"language": "ພາສາ"
},
{
"code": "lt",
"language": "lietuvių kalba"
},
{
"code": "lu",
"language": "Tshiluba"
},
{
"code": "lv",
"language": "latviešu valoda"
},
{
"code": "mg",
"language": "fiteny malagasy"
},
{
"code": "mh",
"language": "Kajin M̧ajeļ"
},
{
"code": "mi",
"language": "te reo Māori"
},
{
"code": "mk",
"language": "македонски јазик"
},
{
"code": "ml",
"language": "മലയാളം"
},
{
"code": "mn",
"language": "Монгол хэл"
},
{
"code": "mr",
"language": "मराठी"
},
{
"code": "ms",
"language": "Bahasa Melayu"
},
{
"code": "mt",
"language": "Malti"
},
{
"code": "my",
"language": "ဗမာစာ"
},
{
"code": "na",
"language": "Ekakairũ Naoero"
},
{
"code": "nb",
"language": "Norsk bokmål"
},
{
"code": "nd",
"language": "isiNdebele"
},
{
"code": "ne",
"language": "नेपाली"
},
{
"code": "ng",
"language": "Owambo"
},
{
"code": "nl",
"language": "Nederlands"
},
{
"code": "nn",
"language": "Norsk Nynorsk"
},
{
"code": "no",
"language": "Norsk"
},
{
"code": "nr",
"language": "isiNdebele"
},
{
"code": "nv",
"language": "Diné bizaad"
},
{
"code": "ny",
"language": "chiCheŵa"
},
{
"code": "oc",
"language": "occitan"
},
{
"code": "oj",
"language": "ᐊᓂᔑᓈᐯᒧᐎᓐ"
},
{
"code": "om",
"language": "Afaan Oromoo"
},
{
"code": "or",
"language": "ଓଡ଼ିଆ"
},
{
"code": "os",
"language": "ирон æвзаг"
},
{
"code": "pa",
"language": "ਪੰਜਾਬੀ"
},
{
"code": "pi",
"language": "पाऴि"
},
{
"code": "pl",
"language": "Polski"
},
{
"code": "ps",
"language": "پښتو"
},
{
"code": "pt",
"language": "Português"
},
{
"code": "qu",
"language": "Runa Simi"
},
{
"code": "rm",
"language": "rumantsch grischun"
},
{
"code": "rn",
"language": "Ikirundi"
},
{
"code": "ro",
"language": "Română"
},
{
"code": "ru",
"language": "Русский"
},
{
"code": "rw",
"language": "Ikinyarwanda"
},
{
"code": "sa",
"language": "संस्कृतम्"
},
{
"code": "sc",
"language": "sardu"
},
{
"code": "sd",
"language": "सिन्धी"
},
{
"code": "se",
"language": "Davvisámegiella"
},
{
"code": "sg",
"language": "yângâ tî sängö"
},
{
"code": "si",
"language": "සිංහල"
},
{
"code": "sk",
"language": "slovenčina"
},
{
"code": "sl",
"language": "slovenščina"
},
{
"code": "sn",
"language": "chiShona"
},
{
"code": "so",
"language": "Soomaaliga"
},
{
"code": "sq",
"language": "Shqip"
},
{
"code": "sr",
"language": "српски језик"
},
{
"code": "ss",
"language": "SiSwati"
},
{
"code": "st",
"language": "Sesotho"
},
{
"code": "su",
"language": "Basa Sunda"
},
{
"code": "sv",
"language": "Svenska"
},
{
"code": "sw",
"language": "Kiswahili"
},
{
"code": "ta",
"language": "தமிழ்"
},
{
"code": "te",
"language": "తెలుగు"
},
{
"code": "tg",
"language": "тоҷикӣ"
},
{
"code": "th",
"language": "ไทย"
},
{
"code": "ti",
"language": "ትግርኛ"
},
{
"code": "tk",
"language": "Türkmen"
},
{
"code": "tl",
"language": "Wikang Tagalog"
},
{
"code": "tn",
"language": "Setswana"
},
{
"code": "to",
"language": "faka Tonga"
},
{
"code": "tr",
"language": "Türkçe"
},
{
"code": "ts",
"language": "Xitsonga"
},
{
"code": "tt",
"language": "татар теле"
},
{
"code": "tw",
"language": "Twi"
},
{
"code": "ty",
"language": "Reo Tahiti"
},
{
"code": "ug",
"language": "ئۇيغۇرچە‎"
},
{
"code": "uk",
"language": "Українська"
},
{
"code": "ur",
"language": "اردو"
},
{
"code": "uz",
"language": "Ўзбек"
},
{
"code": "ve",
"language": "Tshivenḓa"
},
{
"code": "vi",
"language": "Tiếng Việt"
},
{
"code": "vo",
"language": "Volapük"
},
{
"code": "wa",
"language": "walon"
},
{
"code": "wo",
"language": "Wollof"
},
{
"code": "xh",
"language": "isiXhosa"
},
{
"code": "yi",
"language": "ייִדיש"
},
{
"code": "yo",
"language": "Yorùbá"
},
{
"code": "za",
"language": "Saɯ cueŋƅ"
},
{
"code": "zh",
"language": "中文"
},
{
"code": "zu",
"language": "isiZulu"
}
]

File diff suppressed because it is too large Load diff

View file

@ -1,392 +0,0 @@
[
{
"version": "3.17.0",
"code": "477",
"note": "Added:\n- Peertube 2FA support\n- Cache home in background (default disabled -> New settings category and per account) / change frequency\n- Auto-fetch missing messages for the Home (default disabled -> in Settings - Timelines)\n- Automatically switch between tabs when searching\n- More deep links detection\n- Allow to group mentions at the top (default: disabled)\n\n\nFixed:\n- Dynamic color for Android 12+\n- Missing media description for previews\n- Fix a crash when replying\n- Fix button size not changed\n- Forward tags in replies\n- Media cannot be downloaded or shared with Android 10\n- Some crashes"
},
{
"version": "3.16.4",
"code": "476",
"note": "Added:\n- Cache home in background (default disabled -> New settings category and per account) / change frequency\n- Auto-fetch missing messages for the Home (default disabled -> in Settings - Timelines)\n- Automatically switch between tabs when searching\n\nFixed:\n- Some crashes"
},
{
"version": "3.16.3",
"code": "475",
"note": "Added:\n- Peertube 2FA support\n\nFixed:\n- Dynamic color for Android 12+"
},
{
"version": "3.16.2",
"code": "474",
"note": "Added:\n- Peertube support\n- Compose shortcut (long press launcher)\n- Long press compose button to write with another account\n- Edit description and focus for media (for the next Mastodon release)\n\nChanged:\n- Cross actions with two accounts display a dialog\n- Order & compact og values when sharing > title - url - content\n- Tap on top message (user info) open threads\n\nFixed:\n- Text cleared when adding a media\n- Fix Maths not working with quotes\n- Fix crashes"
},
{
"version": "3.16.1",
"code": "473",
"note": "Changed:\n- Edit description and focus for media (for the next Mastodon release)\n\nChanged:\n- Peertube: remove role support to avoid crashes with older instances\n\nFixed:\n- Fix some crashes"
},
{
"version": "3.16.0",
"code": "472",
"note": "Changed:\n- Peertube support\n- Compose shortcut\n- Long press compose button to write with another account\n\nChanged:\n- Cross actions with two accounts display a dialog\n- Order & compact og values when sharing > title - url - content\n\nFixed:\n- Text cleared when adding a media\n- Fix crashes"
},
{
"version": "3.15.2",
"code": "471",
"note": "Changed:\n- Add instance name when sharing\n\nFixed:\n- Fix a crash when removing media\n- Other minor fixes"
},
{
"version": "3.15.1",
"code": "470",
"note": "Changed:\n- Material dialogs\n\nFixed:\n- Light theme issues"
},
{
"version": "3.15.0",
"code": "469",
"note": "Added:\n- Maths support (view and compose)\n- Filter DMs in HOME (long press on the tab)\n- Filter languages for users in home timeline (from their profile)\n- Add several targeted languages for translator\n\nChanged:\n- Hide single media with preview is now a setting (default: disabled)\n- Group items in menu of messages\n\nFixed:\n- Cross-actions didn't display account instances"
},
{
"version": "3.14.6",
"code": "468",
"note": "Added:\n- Maths support (view and compose)\n\nChanged:\n- Hide single media with preview is now a setting (default: disabled)"
},
{
"version": "3.14.5",
"code": "467",
"note": "Changed:\n- Allow to swipe media for profiles\n\nFixed:\n- Fix crashes with pinch zoom\n- Copy/Paste in threads\n- Fix crash when checking redirection on http links\n- Display menu in media viewer resets pinch-zoom"
},
{
"version": "3.14.4",
"code": "466",
"note": "Changed:\n- Media viewer (pinch zoom)\n\nFixed:\n- Cross account actions (long press)\n- Boost color\n- Buttons not visible with custom themes.\n- Fix some bad behaviors with media viewer"
},
{
"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)"
},
{
"version": "3.13.5",
"code": "459",
"note": "Added:\n- Glitch: Allow to post messages locally (Can be turned off in Settings)\n\nFixed:\n- Crashes"
},
{
"version": "3.13.4",
"code": "458",
"note": "Added:\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\nChanged:\n- Full rework on links in messages (also mentions and tags)\n\nFixed:\n- Spoiler text when editing\n- Fix watermarks"
},
{
"version": "3.13.3",
"code": "457",
"note": "Added:\n- Allow to enable extra features in Settings\n- Customizable settings for extra features\n- Support quotes, reactions with messages\n- Support text format (html, markdown, etc.) when composing\n\nFixed:\n- CW not working with media\n- Media not displayed for older instances\n- Some crashes\n"
},
{
"version": "3.13.2",
"code": "456",
"note": "Changed:\n- Hidden media smaller with preview images\n\nFixed:\n- Issue with Media for Android 11+\n- Crash when not setting a translation key\n- Fix DeepL for API pro\n- Crash when visiting a profile with a lot of media\n- Home muted accounts not working without filters\n- Animated custom emoji not displayed"
},
{
"version": "3.13.1",
"code": "455",
"note": "Added:\n- DeepL translation support free/pro keys\n\nChanged:\n- Hide buttons for media when editing\n\nFixed:\n- GIF loaded as static images\n- Suggested accounts cannot be followed"
},
{
"version": "3.13.0",
"code": "454",
"note": "Added:\n- Post random quotes\n- Group reblogs in home timeline\n- Rename Nitter timelines\n- Android 13 support\n- Pagination with search / trending\n- Allow to remove left margin in messages (default: disabled)\n\nChanged:\n- Display translate button only when language is different\n- Respect blank spaces between words in messages\n- Focus button more accessible when editing media\n- Visual feedback for block on account list\n- Visual changes with compose / top bar\n- Use custom Nitter timeline name in manage timelines\n\nFixed:\n- Behavior with cw toggle\n- Truncated gimini links\n- Nav buttons not visible with media (Light theme)\n- Status bar with Android 5\n- Fix links not clickable\n- Fix deep links\n- Fix remote threads not fetched for some instances\n- Adding description to shared media\n- Open with another accounts\n- Chars size not respected for Android 5-6\n- Wrong instance fetched for instances.social\n- Bouncing Timeline on refresh\n- Links to mentions, tags, urls, not visible.\n- Custom channel sounds not applied\n- users with short username are not linked\n- Fix crashes"
},
{
"version": "3.12.3",
"code": "453",
"note": "Added:\n- Pagination with search / trending\n\nFixed:\n- Long press on Nitter tabs\n- Open with another accounts\n- Chars size not respected for Android 5-6\n- Wrong instance fetched for instances.social"
},
{
"version": "3.12.2",
"code": "452",
"note": "Added:\n- Rename Nitter timelines\n- Android 13 support\n\nChanged:\n- Visual feedback for block on account list\n- Visual changes with compose / top bar\n\nFixed:\n- Nav buttons not visible with media (Light theme)\n- Status bar with Android 5\n- Fix links not clickable\n- Fix deep links\n- Fix remote threads not fetched for some instances\n- Adding description to shared media\n- Fix crashes"
},
{
"version": "3.12.1",
"code": "451",
"note": "Added:\n- Post random quotes\n- Group reblogs in home timeline\n\nChanged:\n- Display translate button only when language is different\n- Respect blank spaces between words in messages\n- Focus button more accessible when editing media\n\nFixed:\n- Behavior with cw toggle\n- Truncated gimini links"
},
{
"version": "3.12.0",
"code": "450",
"note": "Added:\n- Full data import/export feature\n- Android 13 themed icon support\n\nFixed:\n- Fix a regression with filters\n- Fix dark solarized theme\n- Fix hide link previews for CW\n- Fix status bar color for all themes\n- Fix language in compose \"...\"\n- Fix add all home muted accounts from lists\n- Fix top notification badges"
},
{
"version": "3.11.3",
"code": "449",
"note": "Added:\n- Add more targeted languages in picker for translations\n- Add account name in push notifications\n\nFixed:\n- Fix a crash when changing language\n- Fix counter colors\n- Fix default link color\n- Fix a crash when clicking on mentions"
},
{
"version": "3.11.2",
"code": "448",
"note": "Added:\n- Mute/Unmute accounts in the Home timeline from their messages or their profiles\n- Add all users from a list to \"Muted home\" in one click\n- Display/Manage users that are muted for home\n\nFixed:\n- Timeline crashes"
},
{
"version": "3.11.0",
"code": "446",
"note": "Added:\n- Display all messages in threads from remote instances (when possible)\n- Allow to unmute/unfollow/unpin a tag from tag timelines\n- Display most used accounts in header menu for an easy switch\n- Automatically add the tag when composing from a tag timeline\n- Add a translate button at the bottom of messages (default: disabled)\n- Add account role in profiles\n- Translate morse\n\nChanged:\n- Disable animations after a refresh\n\nFixed:\n- Contact not working when composing\n- Status bar for black theme\n- Message duplicated in conversations when edited\n- Color issue on Android 5\n- Several crashes"
},
{
"version": "3.10.2",
"code": "445",
"note": "Added:\n- Allow to unmute/unfollow/unpin a tag from tag timelines\n- Automatically add the tag when composing from a tag timeline\n- Add a translate button at the bottom of messages (default: disabled)\n- Add account role in profiles\n\nFixed:\n- Contact not working when composing\n- Status bar for black theme\n- Message duplicated in conversations when edited\n- Color issue on Android 5"
},
{
"version": "3.10.1",
"code": "444",
"note": "Added:\n- Display all messages in threads from remote instances (when possible)\n* Only public messages for instances using the Mastodon API\n* A dedicated button is displayed at the top right when conditions are filled."
},
{
"version": "3.10.0",
"code": "443",
"note": "Added:\n- Dracula theme\n- Customize message colors\n- Enable/Disable Card presentation\n\nChanged:\n- Colors for some themes\n- Space between buttons\n\nFixed:\n- Animated profile pictures not displayed\n- Mentions broken in profile bio and fields\n- Jumps with fit preview images when scrolling up\n- Fetch more button broken with cache\n- Tag patterns in URL break the link\n- Typo in followed tags"
},
{
"version": "3.9.7",
"code": "442",
"note": "Added:\n- Dracula theme\n\nChanged:\n- Colors for Light/Dark/Black themes\n\nFixed:\n- Animated profile pictures not displayed\n- Mentions broken in profile bio and fields\n- Tag patterns in URL break the link\n- Typo in followed tags"
},
{
"version": "3.9.6",
"code": "441",
"note": "Fixed:\n- Jumps with fit preview images when scrolling up\n- Fetch more button broken with cache"
},
{
"version": "3.9.5",
"code": "440",
"note": "Fixed:\n- Custom emoji are not always displayed\n- Jumps in timeline when using \"fit preview images\"\n- Dark theme: timeline buttons without toggle"
},
{
"version": "3.9.4",
"code": "439",
"note": "Changed:\n- Remove card presentation\n- Link color for black theme\n\nFixed:\n- Crash when changing the theme"
},
{
"version": "3.9.3",
"code": "438",
"note": "Added:\n- New design with 5 themes\n\nChanged:\n- Remove built-in browser support\n- Fit preview image displays images vertically\n- Add counters next to images\n\nFixed:\n- Jumps in timelines\n- Replies to wrong messages with followed instances\n- Bug with delete&redraft with a media\n- List cannot be hidden\n- Some crashes"
},
{
"version": "3.9.1",
"code": "436",
"note": "Changed:\n- Remove built-in browser support\n- More spaces between action buttons in messages\n\nFixed:\n- Text size issue\n- Text overlap\n- Wrong background for solarized black\n- Mix between light and dark theme\n- Save button hidden"
},
{
"version": "3.9.0",
"code": "435",
"note": "Added:\n- Migrate to Material Design 3\n- 5 Themes (Light, Dark, Solarized Light/Dark, Black)\n- Automatically switch between Light/Dark\n- Light and Dark theme can be defined for time-based switch\n- Android 12+: Dynamic color\n\nFixed:\n- Jumps in timelines\n"
},
{
"version": "3.8.1",
"code": "434",
"note": "Added:\n- Mute tags with long press in timelines\n\nChanged:\n- Muted account messages are now removed from cache\n\nFixed:\n- Open with another account\n- Fix jumps in profiles\n- Media not displayed in album -> force indexation\n- Built-in browser does not give admin scope\n- Some crashes"
},
{
"version": "3.8.0",
"code": "433",
"note": "Added:\n- List of blocked domains (allow to unblock)\n- Support gemini links\n- Suggested followers\n- Mod/Adm: Manage instance blocked domains\n- Open messages with another account\n- Allow to disable notifications for admins\n- Sort lists\n\nChanged:\n- Allow search term to be edited\n\nFixed:\n- Drafts deleted with no warning\n- Remove lists from \"Manage timelines\"\n- App crashes when proxy is set\n- Filter not synced after being edited\n- Some crashes / improvements"
},
{
"version": "3.7.5",
"code": "432",
"note": "Added:\n- List of blocked domains (allow to unblock)\n- Support gemini links\n- Suggested followers\n\nChanged:\n- Allow search term to be edited\n\nFixed:\n- Drafts deleted with no warning\n- App crashes when proxy is set\n- Filter not synced after being edited\n- Some crashes"
},
{
"version": "3.7.4",
"code": "431",
"note": "Added:\n- Full support to new filters for Mastodon 4\n- Visit profiles without being authenticated / Allow to display all their messages\n\nChanged:\n- Compose view takes the whole width even in threads\n- Accounts can be timed-mute from their profile\n\nFixed:\n- Draft stored when replying \"no\" or dialog prompted without changes\n- Empty pages when starting the app\n- Saving and sharing media fails on some devices\n- Add support for admin notifications\n- Copying content of a message"
},
{
"version": "3.7.3",
"code": "430",
"note": "Added:\n- Visit profiles without being authenticated / Allow to display all their messages\n\nFixed:\n- Saving media fails on some devices"
},
{
"version": "3.7.2",
"code": "429",
"note": "Added:\n- Full support to new filters for Mastodon 4"
},
{
"version": "3.7.1",
"code": "428",
"note": "Added:\n- Support to open links containing /@display_name/ in their path (works on older devices)\n- Display reply count when counters are enabled\n- Add support for filtering profile messages\n\nChanged:\n- Compose view takes the whole width even in threads\n- Reset push notification marker when clearing cache\n\nFixed:\n- Draft stored when replying \"no\" or dialog prompted without changes\n- Filters not working with tags\n- Add a specific error message for followed tags\n- Empty pages when starting the app"
},
{
"version": "3.7.0",
"code": "427",
"note": "Added:\n- Follow tags (dedicated entry in menu)\n- Reduce the list of languages when composing (Settings > Compose)\n- Language indicator when composing\n- Replies are automatically set to first message language\n- Two new Light themes\n- More moderation features\n- List name can be edited\n\nFixed:\n- Filter not working\n- Crash with trends\n- Issue with themes\n- Some content lost when sending messages (mentions)\n- Fix freezes in timelines\n- Some other fixes"
},
{
"version": "3.6.5",
"code": "426",
"note": "- Two new Light themes\n- More moderation features\n\nFixed:\n- Filter not working\n- Crash with trends\n- Some content lost when sending messages (mentions)\n- Some other fixes"
},
{
"version": "3.6.4",
"code": "425",
"note": "Changed:\n- Tag search ordered by popularity\n\nFixed:\n- Unable to get client ID on some devices\n- Issue with messages/notifications not correctly displayed\n- Notifications not received\n- Friendica: issues with mentions and tags (open browser)\n- Improve sharing behaviour"
},
{
"version": "3.6.3",
"code": "424",
"note": "Fixed:\n- Issue with messages/notifications not correctly displayed\n- Friendica: issues with mentions and tags (open browser)\n- Improve sharing behaviour\n"
},
{
"version": "3.6.2",
"code": "423",
"note": "Fixed:\n- Order of notifications\n- URL when sharing boosted message\n- Blank pages when restarting\n- Fix some crashes"
},
{
"version": "3.6.1",
"code": "422",
"note": "Added:\n- Display client in detailed messages\n- Visual support for quotes starting with \">\"\n- Increase indentations for threads (zero to 20, default 5)\n- Visibility for public replies set to unlisted (can be disabled)\n\nChanged:\n- Reduce title size when text size is increased\n\nFixed:\n- Filters are not applied\n- Blocking an account doesn't remove messages in cache\n- Fix some crashes"
},
{
"version": "3.6.0",
"code": "421",
"note": "Added:\n- Edit messages (if your instance supports that feature)\n- Pin/Unpin messages\n- Set the default language for translations\n- Change app icon (Settings > Interface)\n- Allow to disable \"remember position\" in timelines\n- Allow to disable notification aggregation in settings\n- Icon on media previews if a description is available\n\nChanged:\n- Allow to disable/enable media for notifications\n\nFixed:\n- Post loses \"spoiler message\" when adding a media\n- Camera not working on Android 11\n- Notification aggregation\n- Vibrations when fetching new notifications\n- Fix an issue with media timelines\n- Fix some theme issues\n- Fix an issue with built-in browser & openId\n- Bad behaviours with Art Timelines\n- Some crashes"
},
{
"version": "3.5.3",
"code": "420",
"note": "- Edit messages (if your instance support that feature)\n- Some fixes"
},
{
"version": "3.5.2",
"code": "419",
"note": "Added:\n- Pin/Unpin messages\n- Set the default language for translations\n\nChanged:\n- Allow to disable/enable media for notifications\n\nFixed:\n- Wrong images in notification timeline\n- Double icon bug\n- Fix some crashes"
},
{
"version": "3.5.1",
"code": "418",
"note": "Added:\n- Change app icon (Settings > Interface)\n- Allow to disable \"remember position\" in timelines\n- Allow to disable notification aggregation in settings\n\nChanged:\n- Allow to disable/enable media for notifications\n\nFixed:\n- Post loses \"spoiler message\" when adding a media\n- Camera not working on Android 11\n- Notification aggregation\n- Vibrations when fetching new notifications\n- Fix an issue with media timelines\n- Some crashes"
},
{
"version": "3.5.0",
"code": "417",
"note": "Changed:\n- Swipe between timelines\n- Improve cache\n- Button sizes can be changed in settings\n- French translation\n\nFixed:\n- Pleroma: Emoji reactions\n- Sharing (several fixes)\n- Theme issues\n- Rendering issue for links\n- Notifications not removed from cache\n- Issue with watermarks\n- Pagination with bookmarks/favourites\n- Some crashes"
},
{
"version": "3.4.2",
"code": "415",
"note": "Fixed:\n- Attach media to a reply"
},
{
"version": "3.4.1",
"code": "414",
"note": "Added:\n- Disable counters in settings\nFixed:\n- Duplicated messages from cache\n- Notifications in double\n- Drafts not automatically removed\n- Messages not removed from cache after deletion"
},
{
"version": "3.4.0",
"code": "413",
"note": "Added:\n- New cache mechanism (can be disabled in settings)\n- Set thumbnails load behavior Always/Wifi only /ask\n- Add counters for new messages in timelines\nFixed:\n- Contextual menu not working in threads\n- Tag search issue with Friendica\n- Notifications click open the wrong tab\n- Encoding issue with media descriptions\n- Some other fixes."
},
{
"version": "3.3.2",
"code": "409",
"note": "- Fix an issue with cache and home timeline\n- Nitter timelines use the custom instance from settings\n- Fix Nitter issues (only RT)\n- No longer accepts invalid certificate for onion URLs(Google)\n- Fix some crashes"
},
{
"version": "3.3.1",
"code": "408",
"note": "- Improve speed for Nitter instances\n- Allow to edit Nitter accounts with a long press on tabs\n- Fix pagination issue with pinned timelines\n- Fix some crashes\n- Fix visibility when displaying counters"
},
{
"version": "3.3.0",
"code": "407",
"note": "Added:\n- Settings to set all timelines at the top (default disabled)\n- Settings to display timelines in a list (default disabled)\n- Display counters for fav/reblog in timelines (default disabled)\n\nFixed:\n- Visibility issue when replying\n- Some theme issues when composing\n- Some crashes"
},
{
"version": "3.2.0",
"code": "405",
"note": "Added:\n- Export Settings\n- Propagate manual reordering of lists in timeline to \"Lists\" submenu\n- Allow to change the push distributor in settings\n\nChanged:\n- Improve fit preview images\n- Improve notifications\n- Profile media displayed in a grid\n\nFixed:\n- Some Peertube videos not working\n- Respect the default visibility account when replying\n- Discriminate gif from images\n- App crashes when opening external instance timeline\n- Remove button in thread composer crashes the app\n- Back button opens a lot of old activities before closing the app\n- Problems with sharing\n- Reorder Lists with UI issue on change the visibility\n- Link is not shown correctly in posts from Friendica"
},
{
"version": "3.1.0",
"code": "402",
"note": "Added:\n- New theme: Dark Elephant from S1m\n- Error messages from server side when posting fails\n\nChanged:\n- Fetch more buttons more visible\n\nFixed:\n- Issue when fetching missing messages\n- Some issues with themes\n- Too much lost space with reaction (Pleroma)\n- Delete and redraft crashes\n- Crash when playing a video\n- Other crash fixes"
},
{
"version": "3.0.11",
"code": "401",
"note": "Added:\n- New theme: Dark Elephant from S1m\n- Error messages from server side when posting fails\n- Allow to set the fetch time for delayed notifications\n\nChanged:\n- Fetch more buttons more visible\n\nFixed:\n- Issue when fetching missing messages\n- Some issues with themes\n- Too much lost space with reaction (Pleroma)\n- Delete and redraft crashes\n- Crash when playing a video\n- Other crash fixes"
},
{
"version": "3.0.10",
"code": "400",
"note": "Added:\n- Allow to define the max chars count when not detected (In about the instance)\n- Add emoji one picker when composing (must be enabled in settings)\n- Add release notes with the ability to translate them\n\nFixed:\n- Friendica custom emojis not displayed\n- Long press to store media\n- Some bug fixes"
},
{
"version": "3.0.9",
"code": "399",
"note": "Added:\n- Set compose language (from compose menu -> three vertical dots)\n- Add reactions support for Pleroma\n- Add privacy indicator at the top right\n\nChanged\n- Improve the scrolling behaviour\n- Scroll to top (tab reselection) will fetch new messages and then scroll to top\n\nFixed:\n- Empty tag timelines\n- Remove focus point for fit media preview\n- Fix cannot share with one account\n- Fix black theme\n- Theme cannot be selected\n- Fix some button colors"
},
{
"version": "3.0.8",
"code": "398",
"note": "- Keep improving the scroll behaviour\n- Scroll to top (tab reselection) will fetch new messages and then scroll to top\n- Remove focus point for fit media preview\n- Fix cannot share with one account\n- Fix black theme\n- Fix some button colors"
},
{
"version": "3.0.7",
"code": "397",
"note": "- Fix some bugs reported."
},
{
"version": "3.0.6",
"code": "396",
"note": "Added:\n- Allow to set a focus point on previews (media editor)\n- Respect the focus point with previews\n- Pagination with the fetch more button support reading up or down\n- Add trends\n\nFixed:\n- Only last push notification is displayed (not grouped)\n- Bad behavior with the right/left scroll\n- Fix long profiles not fully displayed\n- Issues with some polls\n- Some crashes\n- Some bad behaviors"
},
{
"version": "3.0.5",
"code": "395",
"note": "- Fix some bugs\n- Allow to share with the app"
},
{
"version": "3.0.4",
"code": "394",
"note": "- Fix crashes for some Pleroma instances"
},
{
"version": "3.0.2",
"code": "393",
"note": "- Some bug fixes\n- Improve pinned timelines"
},
{
"version": "3.0.1",
"code": "391",
"note": "Some quick fixes"
},
{
"version": "3.0.0",
"code": "390",
"note": "New version of Fedilab with new feature.\n- You can now compose threads\n- See the whole thread when replying\n- Cache support\n- New design"
}
]

View file

@ -0,0 +1,13 @@
base_theme,2
author,Fedilab
name,Breeze Dark - Yellow
theme_boost_header_color,-14012878
theme_statuses_color,-14473687
theme_link_color,-12734743
theme_icons_color,-4340793
pref_color_background,-15658735
pref_color_navigation_bar,true
pref_color_status_bar,true
theme_accent,-148405
theme_text_color,-1052431
theme_primary,-13552069
1 base_theme 2
2 author Fedilab
3 name Breeze Dark - Yellow
4 theme_boost_header_color -14012878
5 theme_statuses_color -14473687
6 theme_link_color -12734743
7 theme_icons_color -4340793
8 pref_color_background -15658735
9 pref_color_navigation_bar true
10 pref_color_status_bar true
11 theme_accent -148405
12 theme_text_color -1052431
13 theme_primary -13552069

View file

@ -0,0 +1,15 @@
base_theme,2
author,Roboron
name,Cyberpunk Neon
theme_boost_header_color,-16776697,
theme_text_header_1_line,-1441575,
theme_text_header_2_line,-5242717,
theme_statuses_color,-16181197,
theme_link_color,-1441575,
theme_icons_color,-16138810,
pref_color_background,-16774370,
pref_color_navigation_bar,true,
pref_color_status_bar,true,
theme_accent,-1441575,
theme_text_color,-16138810,
theme_primary,-16774370,
1 base_theme,2
2 author,Roboron
3 name,Cyberpunk Neon
4 theme_boost_header_color,-16776697,
5 theme_text_header_1_line,-1441575,
6 theme_text_header_2_line,-5242717,
7 theme_statuses_color,-16181197,
8 theme_link_color,-1441575,
9 theme_icons_color,-16138810,
10 pref_color_background,-16774370,
11 pref_color_navigation_bar,true,
12 pref_color_status_bar,true,
13 theme_accent,-1441575,
14 theme_text_color,-16138810,
15 theme_primary,-16774370,

View file

@ -0,0 +1,15 @@
base_theme,2
author,Jøta Seth
name,Grey Orange
theme_boost_header_color,-14869219
theme_text_header_1_line,-1
theme_text_header_2_line,-1
theme_statuses_color,-14145496
theme_link_color,-26624
theme_icons_color,-26624
pref_color_background,-13092808
pref_color_navigation_bar,true
pref_color_status_bar,true
theme_accent,-26624
theme_text_color,-1
theme_primary,-14408668
1 base_theme 2
2 author Jøta Seth
3 name Grey Orange
4 theme_boost_header_color -14869219
5 theme_text_header_1_line -1
6 theme_text_header_2_line -1
7 theme_statuses_color -14145496
8 theme_link_color -26624
9 theme_icons_color -26624
10 pref_color_background -13092808
11 pref_color_navigation_bar true
12 pref_color_status_bar true
13 theme_accent -26624
14 theme_text_color -1
15 theme_primary -14408668

View file

@ -0,0 +1,15 @@
base_theme,2
author,@AntoineD@h.kher.nl
name,Gruvbox OLED
theme_boost_header_color,-16777216
theme_text_header_1_line,-265785
theme_text_header_2_line,-6777062
theme_statuses_color,-16777216
theme_link_color,-2647775
theme_icons_color,-7175308
pref_color_background,-16777216
pref_color_navigation_bar,true
pref_color_status_bar,true
theme_accent,-9921174
theme_text_color,-265785
theme_primary,-16777216
1 base_theme 2
2 author @AntoineD@h.kher.nl
3 name Gruvbox OLED
4 theme_boost_header_color -16777216
5 theme_text_header_1_line -265785
6 theme_text_header_2_line -6777062
7 theme_statuses_color -16777216
8 theme_link_color -2647775
9 theme_icons_color -7175308
10 pref_color_background -16777216
11 pref_color_navigation_bar true
12 pref_color_status_bar true
13 theme_accent -9921174
14 theme_text_color -265785
15 theme_primary -16777216

View file

@ -0,0 +1,15 @@
base_theme,2
author,AngryTux
name,Less Angry Orange
theme_boost_header_color,-15855063
theme_text_header_1_line,-2128640
theme_text_header_2_line,-5329234
theme_statuses_color,-1
theme_link_color,-12146699
theme_icons_color,-2128640
pref_color_background,-15987700
pref_color_navigation_bar,true
pref_color_status_bar,true
theme_accent,-3968000
theme_text_color,-197380
theme_primary,-14408668
1 base_theme 2
2 author AngryTux
3 name Less Angry Orange
4 theme_boost_header_color -15855063
5 theme_text_header_1_line -2128640
6 theme_text_header_2_line -5329234
7 theme_statuses_color -1
8 theme_link_color -12146699
9 theme_icons_color -2128640
10 pref_color_background -15987700
11 pref_color_navigation_bar true
12 pref_color_status_bar true
13 theme_accent -3968000
14 theme_text_color -197380
15 theme_primary -14408668

View file

@ -0,0 +1,15 @@
base_theme,2
author,Mondstern
name,Mondstern Fedilab
theme_boost_header_color,-1,
theme_text_header_1_line,-13855804,
theme_text_header_2_line,-16227945,
theme_statuses_color,-14935012,
theme_link_color,-15542685,
theme_icons_color,-10723999,
pref_color_background,-15921907,
pref_color_navigation_bar,false,
pref_color_status_bar,false,
theme_accent,-15542685,
theme_text_color,-1,
theme_primary,-14474461,
1 base_theme,2
2 author,Mondstern
3 name,Mondstern Fedilab
4 theme_boost_header_color,-1,
5 theme_text_header_1_line,-13855804,
6 theme_text_header_2_line,-16227945,
7 theme_statuses_color,-14935012,
8 theme_link_color,-15542685,
9 theme_icons_color,-10723999,
10 pref_color_background,-15921907,
11 pref_color_navigation_bar,false,
12 pref_color_status_bar,false,
13 theme_accent,-15542685,
14 theme_text_color,-1,
15 theme_primary,-14474461,

View file

@ -0,0 +1,13 @@
base_theme,2
author,Fedilab
name,Nocturnal
theme_boost_header_color,-12895429
theme_statuses_color,-13553359
theme_link_color,-16747570
theme_icons_color,-10158118
pref_color_background,-14606047
pref_color_navigation_bar,true
pref_color_status_bar,true
theme_accent,-13136013
theme_text_color,-2236963
theme_primary,-14013910
1 base_theme 2
2 author Fedilab
3 name Nocturnal
4 theme_boost_header_color -12895429
5 theme_statuses_color -13553359
6 theme_link_color -16747570
7 theme_icons_color -10158118
8 pref_color_background -14606047
9 pref_color_navigation_bar true
10 pref_color_status_bar true
11 theme_accent -13136013
12 theme_text_color -2236963
13 theme_primary -14013910

View file

@ -0,0 +1,15 @@
base_theme,2
author,Jøta Seth
name,Photon Dark
theme_boost_header_color,-14145496
theme_text_header_1_line,-1
theme_text_header_2_line,-1
theme_statuses_color,-14935012
theme_link_color,-14059009
theme_icons_color,-9474193
pref_color_background,-15921907
pref_color_navigation_bar,true
pref_color_status_bar,true
theme_accent,-14059009
theme_text_color,-1
theme_primary,-14474461
1 base_theme 2
2 author Jøta Seth
3 name Photon Dark
4 theme_boost_header_color -14145496
5 theme_text_header_1_line -1
6 theme_text_header_2_line -1
7 theme_statuses_color -14935012
8 theme_link_color -14059009
9 theme_icons_color -9474193
10 pref_color_background -15921907
11 pref_color_navigation_bar true
12 pref_color_status_bar true
13 theme_accent -14059009
14 theme_text_color -1
15 theme_primary -14474461

View file

@ -0,0 +1,15 @@
base_theme,2
author,Fedilab
name,Solarized Dark - Purple
theme_boost_header_color,-16506327
theme_text_header_1_line,-1120043
theme_text_header_2_line,-1120043
theme_statuses_color,-16304574
theme_link_color,-14251054
theme_icons_color,-7102047
pref_color_background,-16766154
pref_color_navigation_bar,true
pref_color_status_bar,true
theme_accent,-9670204
theme_text_color,-133405
theme_primary,-16304574
1 base_theme 2
2 author Fedilab
3 name Solarized Dark - Purple
4 theme_boost_header_color -16506327
5 theme_text_header_1_line -1120043
6 theme_text_header_2_line -1120043
7 theme_statuses_color -16304574
8 theme_link_color -14251054
9 theme_icons_color -7102047
10 pref_color_background -16766154
11 pref_color_navigation_bar true
12 pref_color_status_bar true
13 theme_accent -9670204
14 theme_text_color -133405
15 theme_primary -16304574

View file

@ -0,0 +1,47 @@
[
{
"theme_name": "Dark",
"base_theme": "DARK",
"primary": "#FF272727",
"primary_dark": "#FF272727",
"primary_light": "#FFd9e1e8",
"accent": "#FF2b90d9",
"accent_dark": "#FF1b80c9",
"accent_light": "#FF772b90d9",
"background": "#FF121212",
"background_dark": "#FF282c37",
"background_light": "#FF282c37",
"should_tint_statusbar": true,
"should_tint_navbar": true
},
{
"theme_name": "Light",
"base_theme": "LIGHT",
"primary": "#FFFFFF",
"primary_dark": "#FFFFFFFF",
"primary_light": "#FFd9e1e8",
"accent": "#FF2b90d9",
"accent_dark": "#FF1b80c9",
"accent_light": "#FF772b90d9",
"background": "#FFFFFFFF",
"background_dark": "#FFFFFFFF",
"background_light": "#FFFFFFFF",
"should_tint_statusbar": true,
"should_tint_navbar": true
},
{
"theme_name": "Black",
"base_theme": "DARK",
"primary": "#FF000000",
"primary_dark": "#FF000000",
"primary_light": "#FF000000",
"accent": "#FF606984",
"accent_dark": "#FF606984",
"accent_light": "#FF606984",
"background": "#FF000000",
"background_dark": "#FF000000",
"background_light": "#FF000000",
"should_tint_statusbar": true,
"should_tint_navbar": true
}
]

File diff suppressed because it is too large Load diff

View file

@ -15,20 +15,16 @@ package app.fedilab.android;
* see <http://www.gnu.org/licenses>. */
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.StrictMode;
import android.webkit.WebView;
import androidx.multidex.MultiDex;
import androidx.multidex.MultiDexApplication;
import androidx.preference.PreferenceManager;
import net.gotev.uploadservice.UploadServiceConfig;
import net.gotev.uploadservice.observer.request.GlobalRequestObserver;
import com.jaredrummler.cyanea.Cyanea;
import com.jaredrummler.cyanea.prefs.CyaneaTheme;
import org.acra.ACRA;
import org.acra.ReportField;
@ -37,50 +33,49 @@ import org.acra.config.DialogConfigurationBuilder;
import org.acra.config.MailSenderConfigurationBuilder;
import org.acra.data.StringFormat;
import java.util.Objects;
import java.util.List;
import app.fedilab.android.mastodon.helper.ThemeHelper;
import app.fedilab.android.peertube.services.GlobalUploadObserver;
import es.dmoral.toasty.Toasty;
public class MainApplication extends MultiDexApplication {
public static String UPLOAD_CHANNEL_ID = "upload_info_peertube";
private WebView webView;
private static MainApplication app;
public static MainApplication getApp() {
return app;
}
@Override
public void onCreate() {
super.onCreate();
try {
webView = new WebView(this);
} catch (Exception ignored) {
app = this;
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(MainApplication.this);
Cyanea.init(this, super.getResources());
List<CyaneaTheme> list = CyaneaTheme.Companion.from(getAssets(), "themes/cyanea_themes.json");
boolean custom_theme = sharedpreferences.getBoolean("use_custom_theme", false);
boolean no_theme_set = sharedpreferences.getBoolean("no_theme_set", true);
if (no_theme_set && !custom_theme) {
list.get(0).apply(Cyanea.getInstance());
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putBoolean("no_theme_set", false);
editor.apply();
}
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
Toasty.Config.getInstance().apply();
if (webView != null) {
try {
webView.destroy();
} catch (Exception ignored) {
}
}
createNotificationChannel();
UploadServiceConfig.initialize(MainApplication.this, UPLOAD_CHANNEL_ID, true);
new GlobalRequestObserver(this, new GlobalUploadObserver());
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(MainApplication.this);
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(MainApplication.this);
boolean send_crash_reports = sharedpreferences.getBoolean(getString(R.string.SET_SEND_CRASH_REPORTS), false);
String currentTheme = sharedpreferences.getString(getString(R.string.SET_THEME_BASE), getString(R.string.SET_DEFAULT_THEME));
ThemeHelper.switchTo(currentTheme);
boolean send_crash_reports = sharedpreferences.getBoolean(getString(R.string.SET_SEND_CRASH_REPORTS), true);
if (send_crash_reports) {
ACRA.init(this, new CoreConfigurationBuilder()
//core configuration:
@ -97,6 +92,7 @@ public class MainApplication extends MultiDexApplication {
.withResIcon(R.mipmap.ic_launcher)
.withText(getString(R.string.crash_title))
.withCommentPrompt(getString(R.string.crash_message))
.withResTheme(R.style.DialogDark)
.withPositiveButtonText(getString(R.string.send_email))
.withNegativeButtonText(getString(R.string.cancel))
.build()
@ -112,15 +108,4 @@ public class MainApplication extends MultiDexApplication {
);
}
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(UPLOAD_CHANNEL_ID,
getString(R.string.notification_channel_name),
NotificationManager.IMPORTANCE_LOW);
channel.setSound(null, null);
((NotificationManager) Objects.requireNonNull(getSystemService(Context.NOTIFICATION_SERVICE))).createNotificationChannel(channel);
}
}
}

View file

@ -18,11 +18,13 @@ package app.fedilab.android.activities;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider;
import java.util.ArrayList;
@ -31,18 +33,17 @@ import java.util.List;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.BuildConfig;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.api.Account;
import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.databinding.ActivityAboutBinding;
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.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 app.fedilab.android.helper.CrossActionHelper;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.MastodonHelper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.viewmodel.mastodon.AccountsVM;
public class AboutActivity extends BaseBarActivity {
public class AboutActivity extends BaseActivity {
private ActivityAboutBinding binding;
@ -51,12 +52,13 @@ public class AboutActivity extends BaseBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyThemeBar(this);
binding = ActivityAboutBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
try {
@ -77,12 +79,13 @@ public class AboutActivity extends BaseBarActivity {
}
binding.aboutSupportPaypal.setOnClickListener(v -> Helper.openBrowser(AboutActivity.this, "https://www.paypal.me/Mastalab"));
binding.accountFollow.setBackgroundTintList(ThemeHelper.getButtonActionColorStateList(AboutActivity.this));
if (BuildConfig.DONATIONS) {
binding.aboutSupportPaypal.setVisibility(View.VISIBLE);
} else {
binding.aboutSupportPaypal.setVisibility(View.GONE);
}
binding.accountFollow.setIconResource(R.drawable.ic_baseline_person_add_24);
binding.accountFollow.setImageResource(R.drawable.ic_baseline_person_add_24);
binding.aboutWebsite.setOnClickListener(v -> Helper.openBrowser(AboutActivity.this, "https://fedilab.app"));
CrossActionHelper.fetchRemoteAccount(AboutActivity.this, "@apps@toot.fedilab.app", new CrossActionHelper.Callback() {
@Override
@ -115,7 +118,7 @@ public class AboutActivity extends BaseBarActivity {
if (relationShips != null && relationShips.size() > 0) {
if (!relationShips.get(0).following) {
binding.accountFollow.setVisibility(View.VISIBLE);
binding.accountFollow.setOnClickListener(v -> accountsVM.follow(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, true, false, null)
binding.accountFollow.setOnClickListener(v -> accountsVM.follow(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, true, false)
.observe(AboutActivity.this, relationShip -> binding.accountFollow.setVisibility(View.GONE)));
}
}

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.activities;
package app.fedilab.android.activities;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
@ -14,52 +14,51 @@ 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.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.MenuItem;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.app.Timeline;
import app.fedilab.android.databinding.ActivityActionsBinding;
import app.fedilab.android.mastodon.client.entities.app.Timeline;
import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.ThemeHelper;
import app.fedilab.android.mastodon.ui.fragment.timeline.FragmentMastodonAccount;
import app.fedilab.android.mastodon.ui.fragment.timeline.FragmentMastodonDomainBlock;
import app.fedilab.android.mastodon.ui.fragment.timeline.FragmentMastodonTimeline;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.ui.fragment.timeline.FragmentMastodonAccount;
import app.fedilab.android.ui.fragment.timeline.FragmentMastodonTimeline;
public class ActionActivity extends BaseBarActivity {
public class ActionActivity extends BaseActivity {
private ActivityActionsBinding binding;
private boolean canGoBack;
private FragmentMastodonTimeline fragmentMastodonTimeline;
private FragmentMastodonAccount fragmentMastodonAccount;
private FragmentMastodonDomainBlock fragmentMastodonDomainBlock;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyThemeBar(this);
binding = ActivityActionsBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
canGoBack = false;
binding.favourites.setOnClickListener(v -> displayTimeline(Timeline.TimeLineEnum.FAVOURITE_TIMELINE));
binding.bookmarks.setOnClickListener(v -> displayTimeline(Timeline.TimeLineEnum.BOOKMARK_TIMELINE));
binding.muted.setOnClickListener(v -> displayTimeline(Timeline.TimeLineEnum.MUTED_TIMELINE));
binding.blocked.setOnClickListener(v -> displayTimeline(Timeline.TimeLineEnum.BLOCKED_TIMELINE));
binding.domainBlock.setOnClickListener(v -> displayTimeline(Timeline.TimeLineEnum.BLOCKED_DOMAIN_TIMELINE));
binding.mutedHome.setOnClickListener(v -> displayTimeline(Timeline.TimeLineEnum.MUTED_TIMELINE_HOME));
}
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) {
if (type == Timeline.TimeLineEnum.MUTED_TIMELINE || type == Timeline.TimeLineEnum.BLOCKED_TIMELINE) {
ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> {
fragmentMastodonAccount = new FragmentMastodonAccount();
@ -74,15 +73,6 @@ public class ActionActivity extends BaseBarActivity {
fragmentTransaction.commit();
});
} else if (type == Timeline.TimeLineEnum.BLOCKED_DOMAIN_TIMELINE) {
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 {
ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> {
@ -112,12 +102,6 @@ public class ActionActivity extends BaseBarActivity {
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;
}
}
@ -132,9 +116,6 @@ public class ActionActivity extends BaseBarActivity {
if (fragmentMastodonAccount != null) {
fragmentMastodonAccount.onDestroyView();
}
if (fragmentMastodonDomainBlock != null) {
fragmentMastodonDomainBlock.onDestroyView();
}
});
setTitle(R.string.interactions);
} else {

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.activities.admin;
package app.fedilab.android.activities;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
@ -19,14 +19,16 @@ import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
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.MenuItem;
import android.view.View;
import android.widget.TextView;
@ -44,8 +46,8 @@ import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
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;
@ -53,140 +55,78 @@ import java.util.concurrent.TimeUnit;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.client.entities.api.Account;
import app.fedilab.android.client.entities.api.AdminAccount;
import app.fedilab.android.client.entities.api.Attachment;
import app.fedilab.android.databinding.ActivityAdminAccountBinding;
import app.fedilab.android.mastodon.activities.BaseActivity;
import app.fedilab.android.mastodon.activities.InstanceProfileActivity;
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.helper.Helper;
import app.fedilab.android.mastodon.helper.MastodonHelper;
import app.fedilab.android.mastodon.helper.SpannableHelper;
import app.fedilab.android.mastodon.helper.ThemeHelper;
import app.fedilab.android.mastodon.viewmodel.mastodon.AdminVM;
import app.fedilab.android.mastodon.viewmodel.mastodon.NodeInfoVM;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.MastodonHelper;
import app.fedilab.android.helper.SpannableHelper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.viewmodel.mastodon.AdminVM;
import app.fedilab.android.viewmodel.mastodon.NodeInfoVM;
import es.dmoral.toasty.Toasty;
public class AdminAccountActivity extends BaseActivity {
private AdminAccount adminAccount;
private Account account;
private ScheduledExecutorService scheduledExecutorService;
private ActivityAdminAccountBinding binding;
private String account_id;
private AdminVM adminVM;
private AdminAccount adminAccount;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyTheme(this);
binding = ActivityAdminAccountBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
ActionBar actionBar = getSupportActionBar();
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);
if (adminAccount != null) {
account = adminAccount.account;
}
}
postponeEnterTransition();
//Remove title
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
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);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
adminVM = new ViewModelProvider(AdminAccountActivity.this).get(AdminVM.class);
if (account_id != null) {
adminVM.getAccount(MainActivity.currentInstance, MainActivity.currentToken, account_id).observe(this, this::initializeView);
return;
}
if (adminAccount != null && adminAccount.account != null) {
initializeView(adminAccount);
binding.toolbar.setPopupTheme(Helper.popupStyle());
if (account != null) {
new Thread(() -> {
account = SpannableHelper.convertAccount(AdminAccountActivity.this, account);
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> initializeView(account);
mainHandler.post(myRunnable);
}).start();
} else {
Toasty.error(AdminAccountActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show();
Toasty.error(AdminAccountActivity.this, getString(R.string.toast_error_loading_account), Toast.LENGTH_LONG).show();
finish();
}
binding.disableAction.setOnClickListener(v -> {
if (adminAccount.disabled) {
adminVM.enable(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, adminAccount.id)
.observe(AdminAccountActivity.this, adminAccountResult -> {
adminAccount.disabled = false;
binding.disableAction.setText(R.string.disable);
binding.disabled.setText(R.string.no);
});
} else {
adminVM.performAction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, adminAccount.id, "disable ", null, null, null, null);
adminAccount.disabled = true;
binding.disableAction.setText(R.string.undisable);
binding.disabled.setText(R.string.yes);
}
});
binding.approveAction.setOnClickListener(v -> {
if (adminAccount.approved) {
adminVM.reject(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, adminAccount.id)
.observe(AdminAccountActivity.this, adminAccountResult -> {
adminAccount = adminAccountResult;
initializeView(adminAccount);
});
} else {
adminVM.approve(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, adminAccount.id);
adminAccount.approved = true;
initializeView(adminAccount);
}
});
binding.silenceAction.setOnClickListener(v -> {
if (adminAccount.silenced) {
adminVM.unsilence(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, adminAccount.id)
.observe(AdminAccountActivity.this, adminAccountResult -> {
adminAccount = adminAccountResult;
initializeView(adminAccount);
});
} else {
adminVM.performAction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, adminAccount.id, "silence", null, null, null, null);
adminAccount.silenced = true;
initializeView(adminAccount);
}
});
binding.suspendAction.setOnClickListener(v -> {
if (adminAccount.suspended) {
adminVM.unsuspend(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, adminAccount.id)
.observe(AdminAccountActivity.this, adminAccountResult -> {
adminAccount = adminAccountResult;
initializeView(adminAccount);
});
} else {
adminVM.performAction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, adminAccount.id, "suspend", null, null, null, null);
adminAccount.suspended = true;
initializeView(adminAccount);
}
});
}
private void initializeView(AdminAccount adminAccount) {
private void initializeView(Account account) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(AdminAccountActivity.this);
if (adminAccount == null) {
Toasty.error(AdminAccountActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show();
if (account == null) {
Toasty.error(AdminAccountActivity.this, getString(R.string.toast_error_loading_account), Toast.LENGTH_LONG).show();
finish();
return;
}
binding.title.setText(String.format(Locale.getDefault(), "@%s", adminAccount.account.acct));
binding.title.setText(String.format(Locale.getDefault(), "@%s", account.acct));
// MastodonHelper.loadPPMastodon(binding.profilePicture, account);
binding.appBar.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
@ -200,18 +140,14 @@ public class AdminAccountActivity extends BaseActivity {
}
});
binding.username.setText(String.format(Locale.getDefault(), "@%s", adminAccount.username));
binding.domain.setText(adminAccount.domain);
binding.email.setText(adminAccount.email);
StringBuilder lastActive = new StringBuilder();
if (adminAccount.ips != null) {
int count = 0;
for (AdminIp ip : adminAccount.ips) {
for (AdminAccount.IP ip : adminAccount.ips) {
lastActive.append(Helper.shortDateToString(ip.used_at)).append(" - ").append(ip.ip).append("\r\n");
count++;
if (count > 4) {
break;
}
}
}
if (lastActive.toString().trim().length() == 0) {
@ -231,9 +167,78 @@ public class AdminAccountActivity extends BaseActivity {
binding.silenceAction.setText(adminAccount.silenced ? R.string.unsilence : R.string.silence);
binding.suspendAction.setText(adminAccount.suspended ? R.string.unsuspend : R.string.suspend);
AdminVM adminVM = new ViewModelProvider(AdminAccountActivity.this).get(AdminVM.class);
binding.disableAction.setOnClickListener(v -> {
if (adminAccount.disabled) {
adminVM.enable(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id)
.observe(AdminAccountActivity.this, adminAccountResult -> {
adminAccount.disabled = false;
binding.disableAction.setText(R.string.disable);
binding.disabled.setText(R.string.no);
});
} else {
adminVM.performAction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, "disable ", null, null, null, null);
adminAccount.disabled = true;
binding.disableAction.setText(R.string.undisable);
binding.disabled.setText(R.string.yes);
}
});
binding.approveAction.setOnClickListener(v -> {
if (adminAccount.approved) {
adminVM.reject(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id)
.observe(AdminAccountActivity.this, adminAccountResult -> {
adminAccount.approved = false;
binding.approveAction.setText(R.string.approve);
binding.approved.setText(R.string.no);
});
} else {
adminVM.approve(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id);
adminAccount.approved = true;
binding.approveAction.setText(R.string.reject);
binding.approved.setText(R.string.yes);
}
});
binding.silenceAction.setOnClickListener(v -> {
if (adminAccount.disabled) {
adminVM.unsilence(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id)
.observe(AdminAccountActivity.this, adminAccountResult -> {
adminAccount.silenced = false;
binding.silenceAction.setText(R.string.silence);
binding.disabled.setText(R.string.no);
});
} else {
adminVM.performAction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, "silence", null, null, null, null);
adminAccount.silenced = true;
binding.disableAction.setText(R.string.unsilence);
binding.disabled.setText(R.string.yes);
}
});
binding.suspendAction.setOnClickListener(v -> {
if (adminAccount.disabled) {
adminVM.unsuspend(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id)
.observe(AdminAccountActivity.this, adminAccountResult -> {
adminAccount.suspended = false;
binding.suspendAction.setText(R.string.suspend);
binding.suspended.setText(R.string.no);
});
} else {
adminVM.performAction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, "suspend", null, null, null, null);
adminAccount.suspended = true;
binding.disableAction.setText(R.string.unsuspend);
binding.suspended.setText(R.string.yes);
}
});
//Retrieve relationship with the connected account
List<String> accountListToCheck = new ArrayList<>();
accountListToCheck.add(account.id);
//Animate emojis
if (adminAccount.account.emojis != null && adminAccount.account.emojis.size() > 0) {
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();
@ -244,35 +249,35 @@ public class AdminAccountActivity extends BaseActivity {
//Tablayout for timelines/following/followers
boolean disableGif = sharedpreferences.getBoolean(getString(R.string.SET_DISABLE_GIF), false);
String targetedUrl = disableGif ? adminAccount.account.avatar_static : adminAccount.account.avatar;
String targetedUrl = disableGif ? account.avatar_static : account.avatar;
Glide.with(AdminAccountActivity.this)
.asDrawable()
.dontTransform()
.load(targetedUrl).into(
new CustomTarget<Drawable>() {
@Override
public void onResourceReady(@NonNull final Drawable resource, Transition<? super Drawable> transition) {
binding.profilePicture.setImageDrawable(resource);
startPostponedEnterTransition();
}
new CustomTarget<Drawable>() {
@Override
public void onResourceReady(@NonNull final Drawable resource, Transition<? super Drawable> transition) {
binding.profilePicture.setImageDrawable(resource);
startPostponedEnterTransition();
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
binding.profilePicture.setImageResource(R.drawable.ic_person);
startPostponedEnterTransition();
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
binding.profilePicture.setImageResource(R.drawable.ic_person);
startPostponedEnterTransition();
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
}
);
}
}
);
//Load header
MastodonHelper.loadProfileMediaMastodon(AdminAccountActivity.this, binding.bannerPp, adminAccount.account, MastodonHelper.MediaAccountType.HEADER);
MastodonHelper.loadProfileMediaMastodon(binding.bannerPp, account, MastodonHelper.MediaAccountType.HEADER);
//Redraws icon for locked accounts
final float scale = getResources().getDisplayMetrics().density;
if (adminAccount.account.locked) {
if (account.locked) {
Drawable img = ContextCompat.getDrawable(AdminAccountActivity.this, R.drawable.ic_baseline_lock_24);
assert img != null;
img.setBounds(0, 0, (int) (16 * scale + 0.5f), (int) (16 * scale + 0.5f));
@ -282,41 +287,38 @@ public class AdminAccountActivity extends BaseActivity {
}
//Peertube account watched by a Mastodon account
//Bot account
if (adminAccount.account.bot) {
if (account.bot) {
binding.accountBot.setVisibility(View.VISIBLE);
}
if (adminAccount.account.acct != null) {
setTitle(adminAccount.account.acct);
if (account.acct != null) {
setTitle(account.acct);
}
final SpannableString content = new SpannableString(getString(R.string.disclaimer_full));
content.setSpan(new UnderlineSpan(), 0, content.length(), 0);
content.setSpan(new ForegroundColorSpan(ThemeHelper.getAttColor(this, R.attr.colorPrimary)), 0, content.length(),
content.setSpan(new ForegroundColorSpan(ContextCompat.getColor(AdminAccountActivity.this, R.color.cyanea_accent_reference)), 0, content.length(),
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
//This account was moved to another one
if (adminAccount.account.moved != null) {
if (account.moved != null) {
binding.accountMoved.setVisibility(View.VISIBLE);
Drawable imgTravel = ContextCompat.getDrawable(AdminAccountActivity.this, R.drawable.ic_baseline_card_travel_24);
assert imgTravel != null;
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(AdminAccountActivity.this, adminAccount.account);
SpannableString spannableString = SpannableHelper.moveToText(AdminAccountActivity.this, account);
binding.accountMoved.setText(spannableString, TextView.BufferType.SPANNABLE);
binding.accountMoved.setMovementMethod(LinkMovementMethod.getInstance());
}
binding.accountDn.setText(
adminAccount.account.getSpanDisplayName(AdminAccountActivity.this,
new WeakReference<>(binding.accountDn)),
TextView.BufferType.SPANNABLE);
binding.accountUn.setText(String.format("@%s", adminAccount.account.acct));
binding.accountDn.setText(account.span_display_name != null ? account.span_display_name : account.display_name, TextView.BufferType.SPANNABLE);
binding.accountUn.setText(String.format("@%s", account.acct));
binding.accountUn.setOnLongClickListener(v -> {
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
String account_id = adminAccount.account.acct;
String account_id = account.acct;
if (account_id.split("@").length == 1)
account_id += "@" + BaseMainActivity.currentInstance;
ClipData clip = ClipData.newPlainText("mastodon_account_id", "@" + account_id);
@ -326,15 +328,15 @@ public class AdminAccountActivity extends BaseActivity {
return false;
});
MastodonHelper.loadPPMastodon(binding.accountPp, adminAccount.account);
MastodonHelper.loadPPMastodon(binding.accountPp, account);
binding.accountPp.setOnClickListener(v -> {
Intent intent = new Intent(AdminAccountActivity.this, MediaActivity.class);
Bundle b = new Bundle();
Attachment attachment = new Attachment();
attachment.description = adminAccount.account.acct;
attachment.preview_url = adminAccount.account.avatar;
attachment.url = adminAccount.account.avatar;
attachment.remote_url = adminAccount.account.avatar;
attachment.description = account.acct;
attachment.preview_url = account.avatar;
attachment.url = account.avatar;
attachment.remote_url = account.avatar;
attachment.type = "image";
ArrayList<Attachment> attachments = new ArrayList<>();
attachments.add(attachment);
@ -348,10 +350,10 @@ public class AdminAccountActivity extends BaseActivity {
});
binding.accountDate.setText(Helper.shortDateToString(adminAccount.created_at));
binding.accountDate.setText(Helper.shortDateToString(account.created_at));
binding.accountDate.setVisibility(View.VISIBLE);
String[] accountInstanceArray = adminAccount.account.acct.split("@");
String[] accountInstanceArray = account.acct.split("@");
String accountInstance = BaseMainActivity.currentInstance;
if (accountInstanceArray.length > 1) {
accountInstance = accountInstanceArray[1];
@ -365,11 +367,12 @@ public class AdminAccountActivity extends BaseActivity {
binding.instanceInfo.setVisibility(View.VISIBLE);
binding.instanceInfo.setOnClickListener(v -> {
InstanceProfileActivity instanceProfileActivity = new InstanceProfileActivity();
Intent intent = new Intent(AdminAccountActivity.this, InstanceProfileActivity.class);
Bundle b = new Bundle();
b.putString(Helper.ARG_INSTANCE, finalAccountInstance);
instanceProfileActivity.setArguments(b);
instanceProfileActivity.show(getSupportFragmentManager(), null);
intent.putExtras(b);
startActivity(intent);
});
}
});

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.activities.admin;
package app.fedilab.android.activities;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
@ -14,14 +14,9 @@ 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;
import static app.fedilab.android.activities.AdminActionActivity.AdminEnum.REPORT;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
@ -29,74 +24,50 @@ import android.view.MenuItem;
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;
import app.fedilab.android.R;
import app.fedilab.android.databinding.ActivityAdminActionsBinding;
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.helper.Helper;
import app.fedilab.android.mastodon.helper.ThemeHelper;
import app.fedilab.android.mastodon.ui.fragment.admin.FragmentAdminAccount;
import app.fedilab.android.mastodon.ui.fragment.admin.FragmentAdminDomain;
import app.fedilab.android.mastodon.ui.fragment.admin.FragmentAdminReport;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.ui.fragment.admin.FragmentAdminAccount;
import app.fedilab.android.ui.fragment.admin.FragmentAdminReport;
public class AdminActionActivity extends BaseBarActivity {
public class AdminActionActivity extends BaseActivity {
public static Boolean local = true, remote = true, active = true, pending = true, disabled = true, silenced = true, suspended = true, staff = null, orderByMostRecent = true;
public static Boolean resolved = null, reportLocal = true, reportRemote = true;
public static Boolean resolved = false, reportLocal = true, reportRemote = true;
private ActivityAdminActionsBinding binding;
private boolean canGoBack;
private FragmentAdminReport fragmentAdminReport;
private FragmentAdminAccount fragmentAdminAccount;
private FragmentAdminDomain fragmentAdminDomain;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
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);
}
}
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyThemeBar(this);
binding = ActivityAdminActionsBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, new IntentFilter(Helper.BROADCAST_DATA));
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
canGoBack = false;
binding.reports.setOnClickListener(v -> displayTimeline(REPORT));
binding.accounts.setOnClickListener(v -> displayTimeline(ACCOUNT));
binding.domains.setOnClickListener(v -> displayTimeline(DOMAIN));
binding.accounts.setOnClickListener(v -> displayTimeline(AdminEnum.ACCOUNT));
}
private void displayTimeline(AdminEnum type) {
canGoBack = true;
if (type == REPORT) {
ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> {
fragmentAdminReport = new FragmentAdminReport();
Bundle bundle = new Bundle();
@ -109,7 +80,9 @@ public class AdminActionActivity extends BaseBarActivity {
fragmentTransaction.replace(R.id.fragment_container, fragmentAdminReport);
fragmentTransaction.commit();
});
} else if (type == ACCOUNT) {
} else {
ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> {
fragmentAdminAccount = new FragmentAdminAccount();
Bundle bundle = new Bundle();
@ -122,19 +95,7 @@ public class AdminActionActivity extends BaseBarActivity {
fragmentTransaction.replace(R.id.fragment_container, fragmentAdminAccount);
fragmentTransaction.commit();
});
} else if (type == DOMAIN) {
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:
@ -143,16 +104,13 @@ public class AdminActionActivity extends BaseBarActivity {
case ACCOUNT:
setTitle(R.string.accounts);
break;
case DOMAIN:
setTitle(R.string.domains);
break;
}
invalidateOptionsMenu();
}
@Override
public boolean onCreateOptionsMenu(@NonNull Menu menu) {
if (canGoBack && fragmentAdminAccount != null) {
if (canGoBack) {
getMenuInflater().inflate(R.menu.menu_admin_account, menu);
}
return super.onCreateOptionsMenu(menu);
@ -165,7 +123,7 @@ public class AdminActionActivity extends BaseBarActivity {
return true;
} else if (item.getItemId() == R.id.action_filter) {
if (getTitle().toString().equalsIgnoreCase(getString(R.string.accounts))) {
AlertDialog.Builder alertDialogBuilder = new MaterialAlertDialogBuilder(AdminActionActivity.this);
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(AdminActionActivity.this, Helper.dialogStyle());
PopupAdminFilterAccountsBinding binding = PopupAdminFilterAccountsBinding.inflate(getLayoutInflater());
alertDialogBuilder.setView(binding.getRoot());
if (local != null && remote == null) {
@ -197,8 +155,6 @@ public class AdminActionActivity extends BaseBarActivity {
binding.moderationAll.setChecked(true);
}
binding.moderation.setOnCheckedChangeListener((group, checkedId) -> {
disabled = null;
silenced = null;
if (checkedId == R.id.moderation_all) {
active = true;
suspended = true;
@ -259,7 +215,7 @@ public class AdminActionActivity extends BaseBarActivity {
AlertDialog alert = alertDialogBuilder.create();
alert.show();
} else {
AlertDialog.Builder alertDialogBuilder = new MaterialAlertDialogBuilder(AdminActionActivity.this);
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(AdminActionActivity.this, Helper.dialogStyle());
PopupAdminFilterReportsBinding binding = PopupAdminFilterReportsBinding.inflate(getLayoutInflater());
alertDialogBuilder.setView(binding.getRoot());
if (resolved == null) {
@ -271,7 +227,7 @@ public class AdminActionActivity extends BaseBarActivity {
if (checkedId == R.id.status_resolved) {
resolved = true;
} else if (checkedId == R.id.status_unresolved) {
resolved = null;
resolved = false;
}
});
if (reportLocal != null && reportRemote == null) {
@ -314,14 +270,6 @@ public class AdminActionActivity extends BaseBarActivity {
return super.onOptionsItemSelected(item);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mReceiver != null) {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
}
}
@Override
public void onBackPressed() {
if (canGoBack) {
@ -329,15 +277,9 @@ public class AdminActionActivity extends BaseBarActivity {
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();
@ -352,9 +294,8 @@ public class AdminActionActivity extends BaseBarActivity {
@SerializedName("REPORT")
REPORT("REPORT"),
@SerializedName("ACCOUNT")
ACCOUNT("ACCOUNT"),
@SerializedName("DOMAIN")
DOMAIN("DOMAIN");
ACCOUNT("ACCOUNT");
private final String value;
AdminEnum(String value) {

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.activities.admin;
package app.fedilab.android.activities;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
@ -19,14 +19,16 @@ import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
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.MenuItem;
import android.view.View;
import android.widget.TextView;
@ -44,7 +46,6 @@ import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@ -54,24 +55,20 @@ import java.util.concurrent.TimeUnit;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.api.Account;
import app.fedilab.android.client.entities.api.AdminAccount;
import app.fedilab.android.client.entities.api.Attachment;
import app.fedilab.android.databinding.ActivityAdminAccountBinding;
import app.fedilab.android.mastodon.activities.BaseBarActivity;
import app.fedilab.android.mastodon.activities.InstanceProfileActivity;
import app.fedilab.android.mastodon.activities.MediaActivity;
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.helper.Helper;
import app.fedilab.android.mastodon.helper.MastodonHelper;
import app.fedilab.android.mastodon.helper.SpannableHelper;
import app.fedilab.android.mastodon.helper.ThemeHelper;
import app.fedilab.android.mastodon.viewmodel.mastodon.AdminVM;
import app.fedilab.android.mastodon.viewmodel.mastodon.NodeInfoVM;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.MastodonHelper;
import app.fedilab.android.helper.SpannableHelper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.viewmodel.mastodon.AdminVM;
import app.fedilab.android.viewmodel.mastodon.NodeInfoVM;
import es.dmoral.toasty.Toasty;
public class AdminReportActivity extends BaseBarActivity {
public class AdminReportActivity extends BaseActivity {
private AdminAccount adminAccount;
private Account account;
@ -82,7 +79,7 @@ public class AdminReportActivity extends BaseBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyTheme(this);
binding = ActivityAdminAccountBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
@ -99,18 +96,25 @@ public class AdminReportActivity extends BaseBarActivity {
//Remove title
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
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);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
binding.toolbar.setPopupTheme(Helper.popupStyle());
if (account != null) {
initializeView(account);
new Thread(() -> {
account = SpannableHelper.convertAccount(AdminReportActivity.this, account);
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> initializeView(account);
mainHandler.post(myRunnable);
}).start();
} else {
Toasty.error(AdminReportActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show();
Toasty.error(AdminReportActivity.this, getString(R.string.toast_error_loading_account), Toast.LENGTH_LONG).show();
finish();
}
}
@ -118,7 +122,7 @@ public class AdminReportActivity extends BaseBarActivity {
private void initializeView(Account account) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(AdminReportActivity.this);
if (account == null) {
Toasty.error(AdminReportActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show();
Toasty.error(AdminReportActivity.this, getString(R.string.toast_error_loading_account), Toast.LENGTH_LONG).show();
finish();
return;
}
@ -142,7 +146,7 @@ public class AdminReportActivity extends BaseBarActivity {
binding.email.setText(adminAccount.email);
StringBuilder lastActive = new StringBuilder();
if (adminAccount.ips != null) {
for (AdminIp ip : adminAccount.ips) {
for (AdminAccount.IP ip : adminAccount.ips) {
lastActive.append(Helper.shortDateToString(ip.used_at)).append(" - ").append(ip.ip).append("\r\n");
}
}
@ -268,27 +272,27 @@ public class AdminReportActivity extends BaseBarActivity {
.asDrawable()
.dontTransform()
.load(targetedUrl).into(
new CustomTarget<Drawable>() {
@Override
public void onResourceReady(@NonNull final Drawable resource, Transition<? super Drawable> transition) {
binding.profilePicture.setImageDrawable(resource);
startPostponedEnterTransition();
}
new CustomTarget<Drawable>() {
@Override
public void onResourceReady(@NonNull final Drawable resource, Transition<? super Drawable> transition) {
binding.profilePicture.setImageDrawable(resource);
startPostponedEnterTransition();
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
binding.profilePicture.setImageResource(R.drawable.ic_person);
startPostponedEnterTransition();
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
binding.profilePicture.setImageResource(R.drawable.ic_person);
startPostponedEnterTransition();
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
}
);
}
}
);
//Load header
MastodonHelper.loadProfileMediaMastodon(AdminReportActivity.this, binding.bannerPp, account, MastodonHelper.MediaAccountType.HEADER);
MastodonHelper.loadProfileMediaMastodon(binding.bannerPp, account, MastodonHelper.MediaAccountType.HEADER);
//Redraws icon for locked accounts
final float scale = getResources().getDisplayMetrics().density;
if (account.locked) {
@ -311,7 +315,7 @@ public class AdminReportActivity extends BaseBarActivity {
final SpannableString content = new SpannableString(getString(R.string.disclaimer_full));
content.setSpan(new UnderlineSpan(), 0, content.length(), 0);
content.setSpan(new ForegroundColorSpan(ThemeHelper.getAttColor(this, R.attr.colorPrimary)), 0, content.length(),
content.setSpan(new ForegroundColorSpan(ContextCompat.getColor(AdminReportActivity.this, R.color.cyanea_accent_reference)), 0, content.length(),
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
//This account was moved to another one
@ -327,10 +331,8 @@ public class AdminReportActivity extends BaseBarActivity {
binding.accountMoved.setMovementMethod(LinkMovementMethod.getInstance());
}
binding.accountDn.setText(
account.getSpanDisplayName(AdminReportActivity.this,
new WeakReference<>(binding.accountDn)),
TextView.BufferType.SPANNABLE);
binding.accountDn.setText(account.span_display_name != null ? account.span_display_name : account.display_name, TextView.BufferType.SPANNABLE);
binding.accountUn.setText(String.format("@%s", account.acct));
binding.accountUn.setOnLongClickListener(v -> {
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
@ -383,11 +385,12 @@ public class AdminReportActivity extends BaseBarActivity {
binding.instanceInfo.setVisibility(View.VISIBLE);
binding.instanceInfo.setOnClickListener(v -> {
InstanceProfileActivity instanceProfileActivity = new InstanceProfileActivity();
Intent intent = new Intent(AdminReportActivity.this, InstanceProfileActivity.class);
Bundle b = new Bundle();
b.putString(Helper.ARG_INSTANCE, finalAccountInstance);
instanceProfileActivity.setArguments(b);
instanceProfileActivity.show(getSupportFragmentManager(), null);
intent.putExtras(b);
startActivity(intent);
});
}
});

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.activities;
package app.fedilab.android.activities;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
@ -18,20 +18,20 @@ package app.fedilab.android.mastodon.activities;
import static app.fedilab.android.BaseMainActivity.currentInstance;
import static app.fedilab.android.BaseMainActivity.emojis;
import android.content.SharedPreferences;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.MenuItem;
import androidx.appcompat.app.ActionBar;
import androidx.preference.PreferenceManager;
import androidx.core.content.ContextCompat;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.api.EmojiInstance;
import app.fedilab.android.databinding.ActivityAnnouncementBinding;
import app.fedilab.android.mastodon.client.entities.api.EmojiInstance;
import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.ui.fragment.timeline.FragmentMastodonAnnouncement;
import app.fedilab.android.exception.DBException;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.ui.fragment.timeline.FragmentMastodonAnnouncement;
public class AnnouncementActivity extends BaseActivity {
@ -40,7 +40,7 @@ public class AnnouncementActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyTheme(this);
ActivityAnnouncementBinding binding = ActivityAnnouncementBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
@ -50,15 +50,14 @@ public class AnnouncementActivity extends BaseActivity {
//Remove title
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
binding.title.setText(R.string.action_announcements);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
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);
Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_tags, new FragmentMastodonAnnouncement(), null, null, null);
if (emojis == null || !emojis.containsKey(currentInstance)) {
new Thread(() -> {

View file

@ -1,5 +1,5 @@
package app.fedilab.android.activities;
/* Copyright 2023 Thomas Schneider
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
*
@ -15,28 +15,32 @@ package app.fedilab.android.activities;
* see <http://www.gnu.org/licenses>. */
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.View;
import app.fedilab.android.databinding.ActivityMainPeertubeBinding;
import app.fedilab.android.mastodon.activities.BaseActivity;
import androidx.annotation.Nullable;
import com.jaredrummler.cyanea.app.CyaneaAppCompatActivity;
import com.vanniktech.emoji.EmojiManager;
import com.vanniktech.emoji.one.EmojiOneProvider;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.ThemeHelper;
public class PeertubeBaseMainActivity extends BaseActivity {
@SuppressLint("Registered")
public class BaseActivity extends CyaneaAppCompatActivity {
protected ActivityMainPeertubeBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainPeertubeBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
static {
Helper.installProvider();
EmojiManager.install(new EmojiOneProvider());
}
//Method for discovering cast devices
public void discoverCast() {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.adjustFontScale(this, getResources().getConfiguration());
Helper.setLocale(this);
}
}

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.entities.app;
package app.fedilab.android.activities;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
@ -14,21 +14,23 @@ 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 com.google.gson.annotations.SerializedName;
import android.annotation.SuppressLint;
import java.io.Serializable;
import java.util.List;
import com.jaredrummler.cyanea.app.CyaneaFragmentActivity;
import com.vanniktech.emoji.EmojiManager;
import com.vanniktech.emoji.one.EmojiOneProvider;
import app.fedilab.android.helper.Helper;
public class Quotes implements Serializable {
@SuppressLint("Registered")
public class BaseFragmentActivity extends CyaneaFragmentActivity {
@SerializedName("quotes")
public List<Quote> quotes;
public static class Quote implements Serializable {
@SerializedName("author")
public String author;
@SerializedName("content")
public String content;
static {
Helper.installProvider();
EmojiManager.install(new EmojiOneProvider());
}
}

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.activities;
package app.fedilab.android.activities;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
@ -15,6 +15,7 @@ package app.fedilab.android.mastodon.activities;
* see <http://www.gnu.org/licenses>. */
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@ -24,27 +25,24 @@ import android.view.MenuItem;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.app.Account;
import app.fedilab.android.client.entities.app.BaseAccount;
import app.fedilab.android.client.entities.app.CacheAccount;
import app.fedilab.android.databinding.ActivityCacheBinding;
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.CacheAccount;
import app.fedilab.android.mastodon.client.entities.app.StatusCache;
import app.fedilab.android.mastodon.client.entities.app.StatusDraft;
import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.helper.CacheHelper;
import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.ui.drawer.CacheAdapter;
import app.fedilab.android.helper.CacheHelper;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.ui.drawer.CacheAdapter;
public class CacheActivity extends BaseBarActivity {
public class CacheActivity extends BaseActivity {
private ActivityCacheBinding binding;
private List<CacheAccount> cacheAccounts;
@ -53,11 +51,12 @@ public class CacheActivity extends BaseBarActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyThemeBar(this);
binding = ActivityCacheBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
CacheHelper.getCacheValues(CacheActivity.this, size -> {
if (size > 0) {
@ -68,35 +67,13 @@ public class CacheActivity extends BaseBarActivity {
new Thread(() -> {
List<BaseAccount> accounts;
List<BaseAccount> accounts = new Account(CacheActivity.this).getPushNotificationAccounts();
cacheAccounts = new ArrayList<>();
try {
accounts = new Account(CacheActivity.this).getAll();
for (BaseAccount baseAccount : accounts) {
CacheAccount cacheAccount = new CacheAccount();
cacheAccount.account = baseAccount;
try {
cacheAccount.home_cache_count = new StatusCache(CacheActivity.this).countHome(baseAccount);
} catch (DBException e) {
e.printStackTrace();
}
try {
cacheAccount.other_cache_count = new StatusCache(CacheActivity.this).countOther(baseAccount);
} catch (DBException e) {
e.printStackTrace();
}
try {
cacheAccount.draft_count = new StatusDraft(CacheActivity.this).count(baseAccount);
} catch (DBException e) {
e.printStackTrace();
}
cacheAccounts.add(cacheAccount);
}
} catch (DBException e) {
e.printStackTrace();
for (BaseAccount baseAccount : accounts) {
CacheAccount cacheAccount = new CacheAccount();
cacheAccount.account = baseAccount;
cacheAccounts.add(cacheAccount);
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
cacheAdapter = new CacheAdapter(cacheAccounts);
@ -121,7 +98,7 @@ public class CacheActivity extends BaseBarActivity {
finish();
return true;
} else if (item.getItemId() == R.id.action_clear) {
AlertDialog.Builder deleteConfirm = new MaterialAlertDialogBuilder(CacheActivity.this);
AlertDialog.Builder deleteConfirm = new AlertDialog.Builder(CacheActivity.this, Helper.dialogStyle());
deleteConfirm.setTitle(getString(R.string.delete_cache));
deleteConfirm.setMessage(getString(R.string.delete_cache_message));
deleteConfirm.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
@ -131,21 +108,7 @@ public class CacheActivity extends BaseBarActivity {
size = size / 1000000.0f;
}
binding.fileCacheSize.setText(String.format("%s %s", String.format(Locale.getDefault(), "%.2f", size), getString(R.string.cache_units)));
AlertDialog.Builder restartBuilder = new MaterialAlertDialogBuilder(CacheActivity.this);
restartBuilder.setMessage(getString(R.string.restart_the_app));
restartBuilder.setNegativeButton(R.string.no, (dialogRestart, whichRestart) -> {
recreate();
dialogRestart.dismiss();
});
restartBuilder.setPositiveButton(R.string.restart, (dialogRestart, whichRestart) -> {
dialogRestart.dismiss();
Helper.restart(CacheActivity.this);
});
AlertDialog alertDialog = restartBuilder.create();
if (!isFinishing()) {
alertDialog.show();
}
cacheAdapter.notifyDataSetChanged();
}));
dialog.dismiss();
});

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.activities;
package app.fedilab.android.activities;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -18,6 +18,7 @@ package app.fedilab.android.mastodon.activities;
import static app.fedilab.android.BaseMainActivity.currentAccount;
import static app.fedilab.android.BaseMainActivity.currentInstance;
import static app.fedilab.android.BaseMainActivity.emojis;
import static app.fedilab.android.ui.drawer.ComposeAdapter.prepareDraft;
import android.Manifest;
import android.annotation.SuppressLint;
@ -25,8 +26,8 @@ import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@ -34,7 +35,6 @@ import android.os.Handler;
import android.os.Looper;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
@ -48,15 +48,11 @@ import androidx.appcompat.app.AlertDialog;
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.work.Data;
import androidx.work.OneTimeWorkRequest;
import androidx.work.OutOfQuotaPolicy;
import androidx.work.WorkManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
@ -64,69 +60,59 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.client.entities.api.Attachment;
import app.fedilab.android.client.entities.api.Context;
import app.fedilab.android.client.entities.api.EmojiInstance;
import app.fedilab.android.client.entities.api.Mention;
import app.fedilab.android.client.entities.api.ScheduledStatus;
import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.client.entities.app.Account;
import app.fedilab.android.client.entities.app.BaseAccount;
import app.fedilab.android.client.entities.app.StatusDraft;
import app.fedilab.android.databinding.ActivityPaginationBinding;
import app.fedilab.android.databinding.PopupContactBinding;
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.Context;
import app.fedilab.android.mastodon.client.entities.api.EmojiInstance;
import app.fedilab.android.mastodon.client.entities.api.Instance;
import app.fedilab.android.mastodon.client.entities.api.Mention;
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.app.BaseAccount;
import app.fedilab.android.mastodon.client.entities.app.StatusDraft;
import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.helper.DividerDecorationSimple;
import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.MastodonHelper;
import app.fedilab.android.mastodon.helper.MediaHelper;
import app.fedilab.android.mastodon.interfaces.OnDownloadInterface;
import app.fedilab.android.mastodon.jobs.ComposeWorker;
import app.fedilab.android.mastodon.jobs.ScheduleThreadWorker;
import app.fedilab.android.mastodon.services.ThreadMessageService;
import app.fedilab.android.mastodon.ui.drawer.AccountsReplyAdapter;
import app.fedilab.android.mastodon.ui.drawer.ComposeAdapter;
import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.mastodon.viewmodel.mastodon.StatusesVM;
import app.fedilab.android.exception.DBException;
import app.fedilab.android.helper.DividerDecorationSimple;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.MastodonHelper;
import app.fedilab.android.helper.MediaHelper;
import app.fedilab.android.helper.SpannableHelper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.jobs.ScheduleThreadWorker;
import app.fedilab.android.services.PostMessageService;
import app.fedilab.android.services.ThreadMessageService;
import app.fedilab.android.ui.drawer.AccountsReplyAdapter;
import app.fedilab.android.ui.drawer.ComposeAdapter;
import app.fedilab.android.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.viewmodel.mastodon.StatusesVM;
import es.dmoral.toasty.Toasty;
public class ComposeActivity extends BaseActivity implements ComposeAdapter.ManageDrafts, AccountsReplyAdapter.ActionDone, ComposeAdapter.promptDraftListener {
public class ComposeActivity extends BaseActivity implements ComposeAdapter.ManageDrafts, AccountsReplyAdapter.ActionDone {
public static final int MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 754;
public static final int REQUEST_AUDIO_PERMISSION_RESULT = 1653;
public static final int PICK_MEDIA = 5700;
public static final int TAKE_PHOTO = 5600;
private final Timer timer = new Timer();
private List<Status> statusList;
private Status statusReply, statusMention, statusQuoted;
private Status statusReply, statusMention;
private StatusDraft statusDraft;
private ComposeAdapter composeAdapter;
private final BroadcastReceiver imageReceiver = new BroadcastReceiver() {
@Override
public void onReceive(android.content.Context context, Intent intent) {
String imgpath = intent.getStringExtra("imgpath");
float focusX = intent.getFloatExtra("focusX", -2);
float focusY = intent.getFloatExtra("focusY", -2);
if (imgpath != null) {
int position = 0;
for (Status status : statusList) {
if (status.media_attachments != null && status.media_attachments.size() > 0) {
for (Attachment attachment : status.media_attachments) {
if (attachment.local_path != null && attachment.local_path.equalsIgnoreCase(imgpath)) {
if (focusX != -2) {
attachment.focus = focusX + "," + focusY;
}
if (attachment.local_path.equalsIgnoreCase(imgpath)) {
composeAdapter.notifyItemChanged(position);
break;
}
@ -137,71 +123,224 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
}
}
};
private boolean promptSaveDraft;
private boolean restoredDraft;
private List<Attachment> sharedAttachments;
private ActivityPaginationBinding binding;
private BaseAccount account;
private String instance, token;
private Uri photoFileUri;
private ScheduledStatus scheduledStatus;
private String visibility;
private Account accountMention;
private app.fedilab.android.client.entities.api.Account accountMention;
private String statusReplyId;
private Account mentionBooster;
private String sharedSubject, sharedContent, sharedTitle, sharedDescription, shareURL, sharedUrlMedia;
private String editMessageId;
private app.fedilab.android.client.entities.api.Account mentionBooster;
private static int visibilityToNumber(String visibility) {
switch (visibility) {
case "public":
return 3;
case "unlisted":
return 2;
case "private":
return 1;
case "direct":
return 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyTheme(this);
binding = ActivityPaginationBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
ActionBar actionBar = getSupportActionBar();
//Remove title
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
return 3;
}
private static String visibilityToString(int visibility) {
switch (visibility) {
case 3:
return "public";
case 2:
return "unlisted";
case 1:
return "private";
case 0:
return "direct";
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
return "public";
}
statusList = new ArrayList<>();
Bundle b = getIntent().getExtras();
if (b != null) {
statusReply = (Status) b.getSerializable(Helper.ARG_STATUS_REPLY);
statusDraft = (StatusDraft) b.getSerializable(Helper.ARG_STATUS_DRAFT);
scheduledStatus = (ScheduledStatus) b.getSerializable(Helper.ARG_STATUS_SCHEDULED);
statusReplyId = b.getString(Helper.ARG_STATUS_REPLY_ID);
statusMention = (Status) b.getSerializable(Helper.ARG_STATUS_MENTION);
account = (Account) b.getSerializable(Helper.ARG_ACCOUNT);
instance = b.getString(Helper.ARG_INSTANCE, null);
token = b.getString(Helper.ARG_TOKEN, null);
visibility = b.getString(Helper.ARG_VISIBILITY, null);
mentionBooster = (app.fedilab.android.client.entities.api.Account) b.getSerializable(Helper.ARG_MENTION_BOOSTER);
accountMention = (app.fedilab.android.client.entities.api.Account) b.getSerializable(Helper.ARG_ACCOUNT_MENTION);
}
binding.toolbar.setPopupTheme(Helper.popupStyle());
//Edit a scheduled status from server
if (scheduledStatus != null) {
statusDraft = new StatusDraft();
List<Status> statuses = new ArrayList<>();
Status status = new Status();
status.text = scheduledStatus.params.text;
status.in_reply_to_id = scheduledStatus.params.in_reply_to_id;
status.poll = scheduledStatus.params.poll;
public static String getVisibility(String defaultVisibility) {
int tootVisibility = visibilityToNumber(defaultVisibility);
if (currentAccount != null && currentAccount.mastodon_account != null && currentAccount.mastodon_account.source != null) {
int userVisibility = visibilityToNumber(currentAccount.mastodon_account.source.privacy);
if (tootVisibility > userVisibility) {
return visibilityToString(userVisibility);
} else {
return visibilityToString(tootVisibility);
if (scheduledStatus.params.media_ids != null && scheduledStatus.params.media_ids.size() > 0) {
status.media_attachments = new ArrayList<>();
new Thread(() -> {
StatusesVM statusesVM = new ViewModelProvider(ComposeActivity.this).get(StatusesVM.class);
for (String attachmentId : scheduledStatus.params.media_ids) {
statusesVM.getAttachment(instance, token, attachmentId)
.observe(ComposeActivity.this, attachment -> status.media_attachments.add(attachment));
}
}).start();
}
status.sensitive = scheduledStatus.params.sensitive;
status.spoiler_text = scheduledStatus.params.spoiler_text;
status.visibility = scheduledStatus.params.visibility;
statusDraft.statusDraftList = statuses;
}
return defaultVisibility;
if (account == null) {
account = currentAccount;
}
if (account == null) {
Toasty.error(ComposeActivity.this, getString(R.string.toast_error), Toasty.LENGTH_SHORT).show();
finish();
return;
}
if (instance == null) {
instance = account.instance;
}
if (token == null) {
token = account.token;
}
if (emojis == null || !emojis.containsKey(currentInstance)) {
new Thread(() -> {
try {
emojis.put(currentInstance, new EmojiInstance(ComposeActivity.this).getEmojiList(currentInstance));
} catch (DBException e) {
e.printStackTrace();
}
}).start();
}
StatusesVM statusesVM = new ViewModelProvider(ComposeActivity.this).get(StatusesVM.class);
//Empty compose
List<Status> statusDraftList = new ArrayList<>();
Status status = new Status();
statusDraftList.add(status);
if (statusReplyId != null && statusDraft != null) {//Delete and redraft
statusesVM.getStatus(currentInstance, BaseMainActivity.currentToken, statusReplyId)
.observe(ComposeActivity.this, status1 -> {
if (status1 != null) {
statusesVM.getContext(currentInstance, BaseMainActivity.currentToken, statusReplyId)
.observe(ComposeActivity.this, statusContext -> {
if (statusContext != null) {
initializeContextRedraftView(statusContext, status1);
} else {
Helper.sendToastMessage(getApplication(), Helper.RECEIVE_TOAST_TYPE_ERROR, getString(R.string.toast_error));
}
});
} else {
Helper.sendToastMessage(getApplication(), Helper.RECEIVE_TOAST_TYPE_ERROR, getString(R.string.toast_error));
}
});
} else if (statusDraft != null) {//Restore a draft with all messages
new Thread(() -> {
if (statusDraft.statusReplyList != null) {
statusDraft.statusReplyList = SpannableHelper.convertStatus(getApplication().getApplicationContext(), statusDraft.statusReplyList);
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
if (statusDraft.statusReplyList != null) {
statusList.addAll(statusDraft.statusReplyList);
binding.recyclerView.addItemDecoration(new DividerDecorationSimple(ComposeActivity.this, statusList));
}
int statusCount = statusList.size();
statusList.addAll(statusDraft.statusDraftList);
composeAdapter = new ComposeAdapter(statusList, statusCount, account, accountMention, visibility);
composeAdapter.manageDrafts = this;
LinearLayoutManager mLayoutManager = new LinearLayoutManager(ComposeActivity.this);
binding.recyclerView.setLayoutManager(mLayoutManager);
binding.recyclerView.setAdapter(composeAdapter);
binding.recyclerView.scrollToPosition(composeAdapter.getItemCount() - 1);
};
mainHandler.post(myRunnable);
}).start();
} else if (statusReply != null) {
new Thread(() -> {
statusReply = SpannableHelper.convertStatus(getApplication().getApplicationContext(), statusReply);
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
statusList.add(statusReply);
int statusCount = statusList.size();
statusDraftList.get(0).in_reply_to_id = statusReply.id;
//We change order for mentions
//At first place the account that has been mentioned if it's not our
statusDraftList.get(0).mentions = new ArrayList<>();
if (!statusReply.account.acct.equalsIgnoreCase(currentAccount.mastodon_account.acct)) {
Mention mention = new Mention();
mention.acct = "@" + statusReply.account.acct;
mention.url = statusReply.account.url;
mention.username = statusReply.account.username;
statusDraftList.get(0).mentions.add(mention);
}
//There are other mentions to
if (statusReply.mentions != null && statusReply.mentions.size() > 0) {
for (Mention mentionTmp : statusReply.mentions) {
if (!mentionTmp.acct.equalsIgnoreCase(statusReply.account.acct) && !mentionTmp.acct.equalsIgnoreCase(currentAccount.mastodon_account.acct)) {
statusDraftList.get(0).mentions.add(mentionTmp);
}
}
}
if (mentionBooster != null) {
Mention mention = new Mention();
mention.acct = mentionBooster.acct;
mention.url = mentionBooster.url;
mention.username = mentionBooster.username;
boolean present = false;
for (Mention mentionTmp : statusDraftList.get(0).mentions) {
if (mentionTmp.acct.equalsIgnoreCase(mentionBooster.acct)) {
present = true;
break;
}
}
if (!present) {
statusDraftList.get(0).mentions.add(mention);
}
}
if (statusReply.spoiler_text != null) {
statusDraftList.get(0).spoiler_text = statusReply.spoiler_text;
}
//StatusDraftList at this point should only have one element
statusList.addAll(statusDraftList);
composeAdapter = new ComposeAdapter(statusList, statusCount, account, accountMention, visibility);
composeAdapter.manageDrafts = this;
LinearLayoutManager mLayoutManager = new LinearLayoutManager(ComposeActivity.this);
binding.recyclerView.setLayoutManager(mLayoutManager);
binding.recyclerView.setAdapter(composeAdapter);
statusesVM.getContext(currentInstance, BaseMainActivity.currentToken, statusReply.id)
.observe(ComposeActivity.this, this::initializeContextView);
};
mainHandler.post(myRunnable);
}).start();
} else {
//Compose without replying
statusList.addAll(statusDraftList);
composeAdapter = new ComposeAdapter(statusList, 0, account, accountMention, visibility);
composeAdapter.manageDrafts = this;
LinearLayoutManager mLayoutManager = new LinearLayoutManager(ComposeActivity.this);
binding.recyclerView.setLayoutManager(mLayoutManager);
binding.recyclerView.setAdapter(composeAdapter);
if (statusMention != null) {
composeAdapter.loadMentions(statusMention);
}
}
MastodonHelper.loadPPMastodon(binding.profilePicture, account.mastodon_account);
LocalBroadcastManager.getInstance(this)
.registerReceiver(imageReceiver,
new IntentFilter(Helper.INTENT_SEND_MODIFIED_IMAGE));
}
@Override
protected void onDestroy() {
super.onDestroy();
if (timer != null) {
timer.cancel();
}
LocalBroadcastManager.getInstance(this)
.unregisterReceiver(imageReceiver);
}
@Override
@ -211,39 +350,23 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
private void storeDraftWarning() {
if (statusDraft == null) {
statusDraft = ComposeAdapter.prepareDraft(statusList, composeAdapter, account.instance, account.user_id);
statusDraft = prepareDraft(statusList, composeAdapter, account.instance, account.user_id);
}
if (canBeSent(statusDraft)) {
if (promptSaveDraft) {
AlertDialog.Builder alt_bld = new MaterialAlertDialogBuilder(ComposeActivity.this);
alt_bld.setMessage(R.string.save_draft);
alt_bld.setPositiveButton(R.string.save, (dialog, id) -> {
dialog.dismiss();
storeDraft(false);
finish();
});
alt_bld.setNegativeButton(R.string.no, (dialog, id) -> {
try {
new StatusDraft(ComposeActivity.this).removeDraft(statusDraft);
} catch (DBException e) {
e.printStackTrace();
}
dialog.dismiss();
finish();
});
AlertDialog alert = alt_bld.create();
alert.show();
} else {
if (!restoredDraft) {
try {
new StatusDraft(ComposeActivity.this).removeDraft(statusDraft);
} catch (DBException e) {
e.printStackTrace();
}
}
AlertDialog.Builder alt_bld = new AlertDialog.Builder(ComposeActivity.this, Helper.dialogStyle());
alt_bld.setMessage(R.string.save_draft);
alt_bld.setPositiveButton(R.string.save, (dialog, id) -> {
dialog.dismiss();
storeDraft(false);
finish();
}
});
alt_bld.setNegativeButton(R.string.no, (dialog, id) -> {
dialog.dismiss();
finish();
});
AlertDialog alert = alt_bld.create();
alert.show();
} else {
finish();
}
@ -263,8 +386,7 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
statusList.addAll(0, context.ancestors);
composeAdapter.setStatusCount(context.ancestors.size() + 1);
composeAdapter.notifyItemRangeInserted(0, context.ancestors.size());
composeAdapter.notifyItemRangeChanged(0, statusList.size());
composeAdapter.notifyItemChanged(context.ancestors.size() + 1);
if (binding.recyclerView.getItemDecorationCount() > 0) {
for (int i = 0; i < binding.recyclerView.getItemDecorationCount(); i++) {
binding.recyclerView.removeItemDecorationAt(i);
@ -274,6 +396,7 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
binding.recyclerView.scrollToPosition(composeAdapter.getItemCount() - 1);
}
/**
* Intialize the common view for the context
*
@ -289,8 +412,7 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
statusList.addAll(0, context.ancestors);
statusList.add(initialStatus);
statusList.add(statusDraft.statusDraftList.get(0));
composeAdapter = new ComposeAdapter(statusList, context.ancestors.size(), account, accountMention, visibility, editMessageId);
composeAdapter.promptDraftListener = this;
composeAdapter = new ComposeAdapter(statusList, context.ancestors.size(), account, accountMention, visibility);
composeAdapter.manageDrafts = this;
LinearLayoutManager mLayoutManager = new LinearLayoutManager(ComposeActivity.this);
binding.recyclerView.setLayoutManager(mLayoutManager);
@ -300,6 +422,7 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
binding.recyclerView.scrollToPosition(composeAdapter.getItemCount() - 1);
}
@Override
public boolean onCreateOptionsMenu(@NonNull Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
@ -307,6 +430,7 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
return true;
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onOptionsItemSelected(MenuItem item) {
@ -316,7 +440,7 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
} else if (item.getItemId() == R.id.action_photo_camera) {
photoFileUri = MediaHelper.dispatchTakePictureIntent(ComposeActivity.this);
} else if (item.getItemId() == R.id.action_contacts) {
AlertDialog.Builder builderSingle = new MaterialAlertDialogBuilder(ComposeActivity.this);
AlertDialog.Builder builderSingle = new AlertDialog.Builder(ComposeActivity.this, Helper.dialogStyle());
builderSingle.setTitle(getString(R.string.select_accounts));
PopupContactBinding popupContactBinding = PopupContactBinding.inflate(getLayoutInflater(), new LinearLayout(ComposeActivity.this), false);
@ -392,7 +516,7 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
}
} else if (item.getItemId() == R.id.action_schedule) {
if (statusDraft == null) {
statusDraft = ComposeAdapter.prepareDraft(statusList, composeAdapter, account.instance, account.user_id);
statusDraft = prepareDraft(statusList, composeAdapter, account.instance, account.user_id);
}
if (canBeSent(statusDraft)) {
MediaHelper.scheduleMessage(ComposeActivity.this, date -> storeDraft(true, date));
@ -403,22 +527,23 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
return true;
}
private void onRetrieveContact(PopupContactBinding popupContactBinding, List<Account> accounts) {
popupContactBinding.loader.setVisibility(View.GONE);
private void onRetrieveContact(PopupContactBinding binding, List<app.fedilab.android.client.entities.api.Account> accounts) {
binding.loader.setVisibility(View.GONE);
if (accounts == null) {
accounts = new ArrayList<>();
}
List<Boolean> checkedValues = new ArrayList<>();
List<Account> contacts = new ArrayList<>(accounts);
for (Account account : contacts) {
List<app.fedilab.android.client.entities.api.Account> contacts = new ArrayList<>(accounts);
for (app.fedilab.android.client.entities.api.Account account : contacts) {
checkedValues.add(composeAdapter.getLastComposeContent().contains("@" + account.acct));
}
AccountsReplyAdapter contactAdapter = new AccountsReplyAdapter(contacts, checkedValues);
contactAdapter.actionDone = ComposeActivity.this;
popupContactBinding.lvAccountsSearch.setAdapter(contactAdapter);
popupContactBinding.lvAccountsSearch.setLayoutManager(new LinearLayoutManager(ComposeActivity.this));
binding.lvAccountsSearch.setAdapter(contactAdapter);
binding.lvAccountsSearch.setLayoutManager(new LinearLayoutManager(ComposeActivity.this));
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
@ -441,327 +566,16 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityPaginationBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
promptSaveDraft = false;
restoredDraft = false;
ActionBar actionBar = getSupportActionBar();
//Remove title
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(false);
}
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
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);
statusList = new ArrayList<>();
Bundle b = getIntent().getExtras();
if (b != null) {
statusReply = (Status) b.getSerializable(Helper.ARG_STATUS_REPLY);
statusQuoted = (Status) b.getSerializable(Helper.ARG_QUOTED_MESSAGE);
statusDraft = (StatusDraft) b.getSerializable(Helper.ARG_STATUS_DRAFT);
scheduledStatus = (ScheduledStatus) b.getSerializable(Helper.ARG_STATUS_SCHEDULED);
statusReplyId = b.getString(Helper.ARG_STATUS_REPLY_ID);
statusMention = (Status) b.getSerializable(Helper.ARG_STATUS_MENTION);
account = (BaseAccount) b.getSerializable(Helper.ARG_ACCOUNT);
editMessageId = b.getString(Helper.ARG_EDIT_STATUS_ID, null);
instance = b.getString(Helper.ARG_INSTANCE, null);
token = b.getString(Helper.ARG_TOKEN, null);
visibility = b.getString(Helper.ARG_VISIBILITY, null);
if (visibility == null && statusReply != null) {
visibility = getVisibility(statusReply.visibility);
} else if (visibility == null && currentAccount != null && currentAccount.mastodon_account != null && currentAccount.mastodon_account.source != null) {
visibility = currentAccount.mastodon_account.source.privacy;
}
mentionBooster = (Account) b.getSerializable(Helper.ARG_MENTION_BOOSTER);
accountMention = (Account) b.getSerializable(Helper.ARG_ACCOUNT_MENTION);
//Shared elements
sharedAttachments = (ArrayList<Attachment>) b.getSerializable(Helper.ARG_MEDIA_ATTACHMENTS);
sharedUrlMedia = b.getString(Helper.ARG_SHARE_URL_MEDIA);
sharedSubject = b.getString(Helper.ARG_SHARE_SUBJECT, null);
sharedContent = b.getString(Helper.ARG_SHARE_CONTENT, null);
sharedTitle = b.getString(Helper.ARG_SHARE_TITLE, null);
sharedDescription = b.getString(Helper.ARG_SHARE_DESCRIPTION, null);
shareURL = b.getString(Helper.ARG_SHARE_URL, null);
}
if (sharedContent != null && shareURL != null && sharedContent.compareTo(shareURL) == 0) {
sharedContent = "";
}
if (sharedTitle != null && sharedSubject != null && sharedSubject.length() > sharedTitle.length()) {
sharedTitle = sharedSubject;
}
//Edit a scheduled status from server
if (scheduledStatus != null) {
statusDraft = new StatusDraft();
List<Status> statuses = new ArrayList<>();
Status status = new Status();
status.id = Helper.generateIdString();
status.text = scheduledStatus.params.text;
status.in_reply_to_id = scheduledStatus.params.in_reply_to_id;
status.poll = scheduledStatus.params.poll;
if (scheduledStatus.params.media_ids != null && scheduledStatus.params.media_ids.size() > 0) {
status.media_attachments = new ArrayList<>();
new Thread(() -> {
StatusesVM statusesVM = new ViewModelProvider(ComposeActivity.this).get(StatusesVM.class);
for (String attachmentId : scheduledStatus.params.media_ids) {
statusesVM.getAttachment(instance, token, attachmentId)
.observe(ComposeActivity.this, attachment -> status.media_attachments.add(attachment));
}
}).start();
}
status.sensitive = scheduledStatus.params.sensitive;
status.spoiler_text = scheduledStatus.params.spoiler_text;
status.visibility = scheduledStatus.params.visibility;
statusDraft.statusDraftList = statuses;
}
if (account == null) {
account = currentAccount;
}
if (account == null) {
Toasty.error(ComposeActivity.this, getString(R.string.toast_error), Toasty.LENGTH_SHORT).show();
finish();
return;
}
if (instance == null) {
instance = account.instance;
}
if (token == null) {
token = account.token;
}
if (emojis == null || !emojis.containsKey(instance)) {
new Thread(() -> {
try {
emojis.put(instance, new EmojiInstance(ComposeActivity.this).getEmojiList(instance));
} catch (DBException e) {
e.printStackTrace();
}
}).start();
}
if (MainActivity.instanceInfo == null) {
String instanceInfo = sharedpreferences.getString(getString(R.string.INSTANCE_INFO) + instance, null);
if (instanceInfo != null) {
MainActivity.instanceInfo = Instance.restore(instanceInfo);
}
}
StatusesVM statusesVM = new ViewModelProvider(ComposeActivity.this).get(StatusesVM.class);
//Empty compose
List<Status> statusDraftList = new ArrayList<>();
Status status = new Status();
status.id = Helper.generateIdString();
if (statusQuoted != null) {
status.quote_id = statusQuoted.id;
}
statusDraftList.add(status);
if (statusReplyId != null && statusDraft != null) {//Delete and redraft
statusesVM.getStatus(currentInstance, BaseMainActivity.currentToken, statusReplyId)
.observe(ComposeActivity.this, status1 -> {
if (status1 != null) {
statusesVM.getContext(currentInstance, BaseMainActivity.currentToken, statusReplyId)
.observe(ComposeActivity.this, statusContext -> {
if (statusContext != null) {
initializeContextRedraftView(statusContext, status1);
} else {
Helper.sendToastMessage(getApplication(), Helper.RECEIVE_TOAST_TYPE_ERROR, getString(R.string.toast_error));
}
});
} else {
Helper.sendToastMessage(getApplication(), Helper.RECEIVE_TOAST_TYPE_ERROR, getString(R.string.toast_error));
}
});
} else if (statusDraft != null) {//Restore a draft with all messages
restoredDraft = true;
if (statusDraft.statusReplyList != null) {
statusList.addAll(statusDraft.statusReplyList);
binding.recyclerView.addItemDecoration(new DividerDecorationSimple(ComposeActivity.this, statusList));
}
int statusCount = statusList.size();
statusList.addAll(statusDraft.statusDraftList);
composeAdapter = new ComposeAdapter(statusList, statusCount, account, accountMention, visibility, editMessageId);
composeAdapter.manageDrafts = this;
composeAdapter.promptDraftListener = this;
LinearLayoutManager mLayoutManager = new LinearLayoutManager(ComposeActivity.this);
binding.recyclerView.setLayoutManager(mLayoutManager);
binding.recyclerView.setAdapter(composeAdapter);
binding.recyclerView.scrollToPosition(composeAdapter.getItemCount() - 1);
} else if (statusReply != null) {
statusList.add(statusReply);
int statusCount = statusList.size();
statusDraftList.get(0).in_reply_to_id = statusReply.id;
//We change order for mentions
//At first place the account that has been mentioned if it's not our
statusDraftList.get(0).mentions = new ArrayList<>();
if (statusReply.account.acct != null && currentAccount.mastodon_account != null && !statusReply.account.acct.equalsIgnoreCase(currentAccount.mastodon_account.acct)) {
Mention mention = new Mention();
mention.acct = "@" + statusReply.account.acct;
mention.url = statusReply.account.url;
mention.username = statusReply.account.username;
statusDraftList.get(0).mentions.add(mention);
}
//There are other mentions to
if (statusReply.mentions != null && statusReply.mentions.size() > 0) {
for (Mention mentionTmp : statusReply.mentions) {
if (statusReply.account.acct != null && !mentionTmp.acct.equalsIgnoreCase(statusReply.account.acct) && currentAccount.mastodon_account != null && !mentionTmp.acct.equalsIgnoreCase(currentAccount.mastodon_account.acct)) {
statusDraftList.get(0).mentions.add(mentionTmp);
}
}
}
if (mentionBooster != null) {
Mention mention = new Mention();
mention.acct = mentionBooster.acct;
mention.url = mentionBooster.url;
mention.username = mentionBooster.username;
boolean present = false;
for (Mention mentionTmp : statusDraftList.get(0).mentions) {
if (mentionTmp.acct.equalsIgnoreCase(mentionBooster.acct)) {
present = true;
break;
}
}
if (!present) {
statusDraftList.get(0).mentions.add(mention);
}
}
if (statusReply.spoiler_text != null) {
statusDraftList.get(0).spoiler_text = statusReply.spoiler_text;
if (statusReply.spoiler_text.trim().length() > 0) {
statusDraftList.get(0).spoilerChecked = true;
}
}
if (statusReply.language != null && !statusReply.language.isEmpty()) {
Set<String> storedLanguages = sharedpreferences.getStringSet(getString(R.string.SET_SELECTED_LANGUAGE), null);
if (storedLanguages == null || storedLanguages.size() == 0) {
statusDraftList.get(0).language = statusReply.language;
} else {
if (storedLanguages.contains(statusReply.language)) {
statusDraftList.get(0).language = statusReply.language;
} else {
String currentCode = sharedpreferences.getString(getString(R.string.SET_COMPOSE_LANGUAGE) + account.user_id + account.instance, Locale.getDefault().getLanguage());
if (currentCode.isEmpty()) {
currentCode = "EN";
}
statusDraftList.get(0).language = currentCode;
}
}
}
//StatusDraftList at this point should only have one element
statusList.addAll(statusDraftList);
composeAdapter = new ComposeAdapter(statusList, statusCount, account, accountMention, visibility, editMessageId);
composeAdapter.manageDrafts = this;
composeAdapter.promptDraftListener = this;
LinearLayoutManager mLayoutManager = new LinearLayoutManager(ComposeActivity.this);
binding.recyclerView.setLayoutManager(mLayoutManager);
binding.recyclerView.setAdapter(composeAdapter);
statusesVM.getContext(currentInstance, BaseMainActivity.currentToken, statusReply.id)
.observe(ComposeActivity.this, this::initializeContextView);
} else if (statusQuoted != null) {
statusList.add(statusQuoted);
int statusCount = statusList.size();
statusDraftList.get(0).quote_id = statusQuoted.id;
//StatusDraftList at this point should only have one element
statusList.addAll(statusDraftList);
composeAdapter = new ComposeAdapter(statusList, statusCount, account, accountMention, visibility, editMessageId);
composeAdapter.manageDrafts = this;
composeAdapter.promptDraftListener = this;
LinearLayoutManager mLayoutManager = new LinearLayoutManager(ComposeActivity.this);
binding.recyclerView.setLayoutManager(mLayoutManager);
binding.recyclerView.setAdapter(composeAdapter);
} else {
//Compose without replying
statusList.addAll(statusDraftList);
composeAdapter = new ComposeAdapter(statusList, 0, account, accountMention, visibility, editMessageId);
composeAdapter.manageDrafts = this;
composeAdapter.promptDraftListener = this;
LinearLayoutManager mLayoutManager = new LinearLayoutManager(ComposeActivity.this);
binding.recyclerView.setLayoutManager(mLayoutManager);
binding.recyclerView.setAdapter(composeAdapter);
if (statusMention != null) {
composeAdapter.loadMentions(statusMention);
}
}
MastodonHelper.loadPPMastodon(binding.profilePicture, account.mastodon_account);
LocalBroadcastManager.getInstance(this)
.registerReceiver(imageReceiver,
new IntentFilter(Helper.INTENT_SEND_MODIFIED_IMAGE));
if (timer != null) {
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
if (promptSaveDraft) {
storeDraft(false);
}
}
}, 0, 10000);
}
if (sharedAttachments != null && sharedAttachments.size() > 0) {
for (Attachment attachment : sharedAttachments) {
composeAdapter.addAttachment(-1, attachment);
}
} /*else if (sharedUri != null && !sharedUri.toString().startsWith("http")) {
List<Uri> uris = new ArrayList<>();
uris.add(sharedUri);
Helper.createAttachmentFromUri(ComposeActivity.this, uris, attachments -> {
for(Attachment attachment: attachments) {
composeAdapter.addAttachment(-1, attachment);
}
});
} */ else if (shareURL != null) {
Helper.download(ComposeActivity.this, sharedUrlMedia, new OnDownloadInterface() {
@Override
public void onDownloaded(String saveFilePath, String downloadUrl, Error error) {
composeAdapter.addSharing(shareURL, sharedTitle, sharedDescription, sharedSubject, sharedContent, saveFilePath);
}
@Override
public void onUpdateProgress(int progress) {
}
});
} else {
if (composeAdapter != null) {
composeAdapter.addSharing(null, null, sharedDescription, null, sharedContent, null);
}
}
}
@Override
public void onItemDraftAdded(int position) {
Status status = new Status();
status.id = Helper.generateIdString();
status.mentions = statusList.get(position - 1).mentions;
status.visibility = statusList.get(position - 1).visibility;
final SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(ComposeActivity.this);
boolean unlistedReplies = sharedpreferences.getBoolean(getString(R.string.SET_UNLISTED_REPLIES), true);
if (status.visibility.equalsIgnoreCase("public") && unlistedReplies) {
status.visibility = "unlisted";
}
status.spoiler_text = statusList.get(position - 1).spoiler_text;
status.sensitive = statusList.get(position - 1).sensitive;
status.mentions = statusList.get(position).mentions;
status.visibility = statusList.get(position).visibility;
status.spoiler_text = statusList.get(position).spoiler_text;
status.sensitive = statusList.get(position).sensitive;
statusList.add(status);
composeAdapter.notifyItemInserted(position);
composeAdapter.notifyItemRangeChanged(0, statusList.size());
binding.recyclerView.smoothScrollToPosition(statusList.size());
composeAdapter.notifyItemInserted(position + 1);
binding.recyclerView.smoothScrollToPosition(position + 1);
}
@Override
@ -788,17 +602,15 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
private void storeDraft(boolean sendMessage, String scheduledDate) {
new Thread(() -> {
//Collect all statusCompose
List<Status> statusDrafts = new ArrayList<>();
List<Status> statusReplies = new ArrayList<>();
for (Status status : statusList) {
if (status.id == null || status.id.startsWith("@fedilab_compose_")) {
if (status.id == null) {
statusDrafts.add(status);
} else {
statusReplies.add(status);
}
}
if (statusDraft == null) {
statusDraft = new StatusDraft(ComposeActivity.this);
@ -808,20 +620,10 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
WorkManager.getInstance(ComposeActivity.this).cancelWorkById(statusDraft.workerUuid);
}
}
if (statusReplies.size() > 0) {
statusDraft.statusReplyList = new ArrayList<>();
statusDraft.statusReplyList.addAll(statusReplies);
}
if (statusDrafts.size() > 0) {
statusDraft.statusDraftList = new ArrayList<>();
statusDraft.statusDraftList.addAll(statusDrafts);
}
if (statusDraft.instance == null) {
statusDraft.instance = account.instance;
}
if (statusDraft.user_id == null) {
statusDraft.user_id = account.user_id;
}
statusDraft.statusReplyList = statusReplies;
statusDraft.statusDraftList = statusDrafts;
statusDraft.instance = account.instance;
statusDraft.user_id = account.user_id;
if (!canBeSent(statusDraft)) {
return;
@ -862,14 +664,8 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
.addTag(Helper.WORKER_SCHEDULED_STATUSES)
.setInitialDelay(delayToPass, TimeUnit.MILLISECONDS)
.build();
WorkManager.getInstance(ComposeActivity.this).enqueue(oneTimeWorkRequest);
statusDraft.workerUuid = oneTimeWorkRequest.getId();
statusDraft.scheduled_at = date;
try {
new StatusDraft(ComposeActivity.this).updateStatusDraft(statusDraft);
} catch (DBException e) {
e.printStackTrace();
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
Toasty.info(ComposeActivity.this, getString(R.string.toot_scheduled), Toasty.LENGTH_LONG).show();
@ -886,21 +682,18 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
mediaCount += status.media_attachments != null ? status.media_attachments.size() : 0;
}
if (mediaCount > 0) {
Data inputData = new Data.Builder()
.putString(Helper.ARG_STATUS_DRAFT_ID, String.valueOf(statusDraft.id))
.putString(Helper.ARG_INSTANCE, instance)
.putString(Helper.ARG_TOKEN, token)
.putString(Helper.ARG_EDIT_STATUS_ID, editMessageId)
.putString(Helper.ARG_USER_ID, account.user_id)
.putString(Helper.ARG_SCHEDULED_DATE, scheduledDate).build();
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(ComposeWorker.class)
.setInputData(inputData)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build();
WorkManager.getInstance(ComposeActivity.this).enqueue(request);
Intent intent = new Intent(ComposeActivity.this, PostMessageService.class);
intent.putExtra(Helper.ARG_STATUS_DRAFT, statusDraft);
intent.putExtra(Helper.ARG_INSTANCE, instance);
intent.putExtra(Helper.ARG_TOKEN, token);
intent.putExtra(Helper.ARG_SCHEDULED_DATE, scheduledDate);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent);
} else {
startService(intent);
}
} else {
new ThreadMessageService(ComposeActivity.this, instance, account.user_id, token, statusDraft, scheduledDate, editMessageId);
new ThreadMessageService(ComposeActivity.this, instance, token, statusDraft, scheduledDate);
}
finish();
}
@ -910,14 +703,10 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
private boolean canBeSent(StatusDraft statusDraft) {
if (statusDraft == null) {
if (statusDraft == null || statusDraft.statusDraftList == null || statusDraft.statusDraftList.size() == 0) {
return false;
}
List<Status> statuses = statusDraft.statusDraftList;
if (statuses == null || statuses.size() == 0) {
return false;
}
Status statusCheck = statuses.get(0);
Status statusCheck = statusDraft.statusDraftList.get(0);
if (statusCheck == null) {
return false;
}
@ -933,11 +722,6 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
composeAdapter.updateContent(isChecked, acct);
}
@Override
public void promptDraft() {
promptSaveDraft = true;
}
public enum mediaType {
PHOTO,

View file

@ -0,0 +1,177 @@
package app.fedilab.android.activities;
/* Copyright 2021 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.BaseMainActivity.currentAccount;
import static app.fedilab.android.ui.drawer.StatusAdapter.sendAction;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.Menu;
import android.view.MenuItem;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.PreferenceManager;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.client.entities.app.QuickLoad;
import app.fedilab.android.client.entities.app.StatusCache;
import app.fedilab.android.databinding.ActivityConversationBinding;
import app.fedilab.android.exception.DBException;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.MastodonHelper;
import app.fedilab.android.helper.SpannableHelper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.ui.fragment.timeline.FragmentMastodonContext;
import app.fedilab.android.viewmodel.mastodon.StatusesVM;
public class ContextActivity extends BaseActivity {
public static boolean expand;
public static boolean displayCW;
public static Resources.Theme theme;
Fragment currentFragment;
private Status focusedStatus;
private ActivityConversationBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyTheme(this);
binding = ActivityConversationBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
ActionBar actionBar = getSupportActionBar();
//Remove title
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
binding.title.setText(R.string.context_conversation);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
binding.toolbar.setPopupTheme(Helper.popupStyle());
Bundle b = getIntent().getExtras();
final SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(ContextActivity.this);
displayCW = sharedpreferences.getBoolean(getString(R.string.SET_EXPAND_CW), false);
focusedStatus = null; // or other values
if (b != null)
focusedStatus = (Status) b.getSerializable(Helper.ARG_STATUS);
if (focusedStatus == null) {
finish();
return;
}
MastodonHelper.loadPPMastodon(binding.profilePicture, currentAccount.mastodon_account);
Bundle bundle = new Bundle();
new Thread(() -> {
focusedStatus = SpannableHelper.convertStatus(getApplication().getApplicationContext(), focusedStatus);
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
bundle.putSerializable(Helper.ARG_STATUS, focusedStatus);
currentFragment = Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_content_main, new FragmentMastodonContext(), bundle, null, null);
};
mainHandler.post(myRunnable);
}).start();
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);
new QuickLoad(getApplication().getApplicationContext()).updateStatus(currentAccount, status);
Handler mainHandler = new Handler(Looper.getMainLooper());
//Update UI
Runnable myRunnable = () -> 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.
getMenuInflater().inflate(R.menu.menu_context, menu);
MenuItem itemExpand = menu.findItem(R.id.action_expand);
if (expand) {
itemExpand.setIcon(R.drawable.ic_baseline_expand_less_24);
} else {
itemExpand.setIcon(R.drawable.ic_baseline_expand_more_24);
}
MenuItem itemDisplayCW = menu.findItem(R.id.action_show_cw);
if (displayCW) {
itemDisplayCW.setIcon(R.drawable.ic_baseline_remove_red_eye_24);
} else {
itemDisplayCW.setIcon(R.drawable.ic_outline_remove_red_eye_24);
}
return true;
}
public void setCurrentFragment(FragmentMastodonContext fragmentMastodonContext) {
currentFragment = fragmentMastodonContext;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
} else if (item.getItemId() == R.id.action_expand) {
expand = !expand;
if (currentFragment != null && currentFragment instanceof FragmentMastodonContext) {
((FragmentMastodonContext) currentFragment).redraw();
}
invalidateOptionsMenu();
} else if (item.getItemId() == R.id.action_show_cw) {
displayCW = !displayCW;
if (currentFragment != null && currentFragment instanceof FragmentMastodonContext) {
((FragmentMastodonContext) currentFragment).refresh();
}
invalidateOptionsMenu();
}
return true;
}
@Override
protected void onDestroy() {
super.onDestroy();
binding = null;
currentFragment = null;
}
}

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.activities;
package app.fedilab.android.activities;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
@ -17,6 +17,7 @@ package app.fedilab.android.mastodon.activities;
import static app.fedilab.android.BaseMainActivity.currentAccount;
import android.content.SharedPreferences;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@ -25,21 +26,23 @@ import android.text.TextUtils;
import android.view.MenuItem;
import android.widget.Toast;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager;
import java.util.List;
import java.util.Set;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.api.Attachment;
import app.fedilab.android.client.entities.api.Emoji;
import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.client.entities.api.Tag;
import app.fedilab.android.databinding.ActivityCustomSharingBinding;
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.helper.Helper;
import app.fedilab.android.mastodon.helper.customsharing.CustomSharingAsyncTask;
import app.fedilab.android.mastodon.helper.customsharing.CustomSharingResponse;
import app.fedilab.android.mastodon.helper.customsharing.OnCustomSharingInterface;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.helper.customsharing.CustomSharingAsyncTask;
import app.fedilab.android.helper.customsharing.CustomSharingResponse;
import app.fedilab.android.helper.customsharing.OnCustomSharingInterface;
import es.dmoral.toasty.Toasty;
@ -48,7 +51,7 @@ import es.dmoral.toasty.Toasty;
* Share status metadata to remote content aggregators
*/
public class CustomSharingActivity extends BaseBarActivity implements OnCustomSharingInterface {
public class CustomSharingActivity extends BaseActivity implements OnCustomSharingInterface {
private String title, keywords, custom_sharing_url, encodedCustomSharingURL;
private String bundle_url;
@ -63,13 +66,14 @@ public class CustomSharingActivity extends BaseBarActivity implements OnCustomSh
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyThemeBar(this);
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(CustomSharingActivity.this);
binding = ActivityCustomSharingBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
Bundle b = getIntent().getExtras();
status = null;

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.activities;
package app.fedilab.android.activities;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
@ -16,11 +16,10 @@ package app.fedilab.android.mastodon.activities;
import static app.fedilab.android.BaseMainActivity.currentAccount;
import android.content.SharedPreferences;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@ -29,24 +28,24 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.api.Attachment;
import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.client.entities.app.StatusDraft;
import app.fedilab.android.databinding.ActivityDraftsBinding;
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.ui.drawer.StatusDraftAdapter;
import app.fedilab.android.mastodon.viewmodel.mastodon.TimelinesVM;
import app.fedilab.android.exception.DBException;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.ui.drawer.StatusDraftAdapter;
import app.fedilab.android.viewmodel.mastodon.TimelinesVM;
public class DraftActivity extends BaseActivity implements StatusDraftAdapter.DraftActions {
@ -60,20 +59,21 @@ public class DraftActivity extends BaseActivity implements StatusDraftAdapter.Dr
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyTheme(this);
binding = ActivityDraftsBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
//Remove title
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(false);
}
binding.toolbar.setPopupTheme(Helper.popupStyle());
binding.title.setText(R.string.drafts);
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);
binding.loader.setVisibility(View.VISIBLE);
binding.lvStatus.setVisibility(View.GONE);
binding.noAction.setVisibility(View.GONE);
@ -99,7 +99,7 @@ public class DraftActivity extends BaseActivity implements StatusDraftAdapter.Dr
finish();
return true;
} else if (item.getItemId() == R.id.action_delete) {
AlertDialog.Builder unfollowConfirm = new MaterialAlertDialogBuilder(DraftActivity.this);
AlertDialog.Builder unfollowConfirm = new AlertDialog.Builder(DraftActivity.this, Helper.dialogStyle());
unfollowConfirm.setTitle(getString(R.string.delete_all));
unfollowConfirm.setMessage(getString(R.string.remove_draft));
unfollowConfirm.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
@ -205,6 +205,12 @@ public class DraftActivity extends BaseActivity implements StatusDraftAdapter.Dr
}
}
@Override
public void onDestroy() {
super.onDestroy();
binding.lvStatus.setAdapter(null);
binding = null;
}
@Override
public void onAllDeleted() {

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.activities;
package app.fedilab.android.activities;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
@ -18,7 +18,7 @@ import static app.fedilab.android.BaseMainActivity.currentAccount;
import static app.fedilab.android.BaseMainActivity.instanceInfo;
import android.content.Intent;
import android.net.Uri;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
@ -30,11 +30,11 @@ import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
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.LinkedHashMap;
@ -43,17 +43,18 @@ import java.util.Locale;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.api.Account;
import app.fedilab.android.client.entities.api.Field;
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.Field;
import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.MastodonHelper;
import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.exception.DBException;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.MastodonHelper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.viewmodel.mastodon.AccountsVM;
import es.dmoral.toasty.Toasty;
public class EditProfileActivity extends BaseBarActivity {
public class EditProfileActivity extends BaseActivity {
public static final int PICK_MEDIA_AVATAR = 5705;
public static final int PICK_MEDIA_HEADER = 5706;
@ -63,11 +64,12 @@ public class EditProfileActivity extends BaseBarActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyThemeBar(this);
binding = ActivityEditProfileBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
new ViewModelProvider(EditProfileActivity.this).get(AccountsVM.class).getConnectedAccount(BaseMainActivity.currentInstance, BaseMainActivity.currentToken)
@ -85,7 +87,7 @@ public class EditProfileActivity extends BaseBarActivity {
@SuppressWarnings("deprecation")
private void initializeView() {
//Hydrate values
MastodonHelper.loadProfileMediaMastodon(EditProfileActivity.this, binding.bannerPp, currentAccount.mastodon_account, MastodonHelper.MediaAccountType.HEADER);
MastodonHelper.loadProfileMediaMastodon(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));
@ -136,7 +138,7 @@ public class EditProfileActivity extends BaseBarActivity {
value = Html.fromHtml(field.value).toString();
fieldItemBinding.value.setText(value);
fieldItemBinding.remove.setOnClickListener(v -> {
AlertDialog.Builder deleteConfirm = new MaterialAlertDialogBuilder(EditProfileActivity.this);
AlertDialog.Builder deleteConfirm = new AlertDialog.Builder(EditProfileActivity.this, Helper.dialogStyle());
deleteConfirm.setTitle(getString(R.string.delete_field));
deleteConfirm.setMessage(getString(R.string.delete_field_confirm));
deleteConfirm.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
@ -158,7 +160,7 @@ public class EditProfileActivity extends BaseBarActivity {
binding.addField.setOnClickListener(view -> {
AccountFieldItemBinding fieldItemBinding = AccountFieldItemBinding.inflate(getLayoutInflater());
fieldItemBinding.remove.setOnClickListener(v -> {
AlertDialog.Builder deleteConfirm = new MaterialAlertDialogBuilder(EditProfileActivity.this);
AlertDialog.Builder deleteConfirm = new AlertDialog.Builder(EditProfileActivity.this, Helper.dialogStyle());
deleteConfirm.setTitle(getString(R.string.delete_field));
deleteConfirm.setMessage(getString(R.string.delete_field_confirm));
deleteConfirm.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
@ -178,8 +180,6 @@ public class EditProfileActivity extends BaseBarActivity {
binding.addField.setVisibility(View.GONE);
}
});
//Actions with the activity
accountsVM = new ViewModelProvider(EditProfileActivity.this).get(AccountsVM.class);
binding.headerSelect.setOnClickListener(view -> startActivityForResult(prepareIntent(), EditProfileActivity.PICK_MEDIA_HEADER));
@ -191,35 +191,30 @@ public class EditProfileActivity extends BaseBarActivity {
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PICK_MEDIA_AVATAR && resultCode == RESULT_OK) {
Uri uri = data.getData();
if (uri != null) {
binding.avatarProgress.setVisibility(View.VISIBLE);
Glide.with(EditProfileActivity.this)
.asDrawable()
.load(uri)
.thumbnail(0.1f)
.into(binding.accountPp);
accountsVM.updateProfilePicture(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, uri, AccountsVM.UpdateMediaType.AVATAR)
.observe(EditProfileActivity.this, account -> {
if (account != null) {
sendBroadCast(account);
binding.avatarProgress.setVisibility(View.GONE);
currentAccount.mastodon_account = account;
Helper.recreateMainActivity(EditProfileActivity.this);
new Thread(() -> {
try {
new app.fedilab.android.mastodon.client.entities.app.Account(EditProfileActivity.this).insertOrUpdate(currentAccount);
} catch (DBException e) {
e.printStackTrace();
}
}).start();
} else {
Helper.sendToastMessage(getApplication(), Helper.RECEIVE_TOAST_TYPE_ERROR, getString(R.string.toast_error));
}
});
} else {
Toasty.error(EditProfileActivity.this, getString(R.string.toast_error), Toasty.LENGTH_LONG).show();
}
binding.avatarProgress.setVisibility(View.VISIBLE);
Glide.with(EditProfileActivity.this)
.asDrawable()
.load(data.getData())
.thumbnail(0.1f)
.into(binding.accountPp);
accountsVM.updateProfilePicture(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, data.getData(), AccountsVM.UpdateMediaType.AVATAR)
.observe(EditProfileActivity.this, account -> {
if (account != null) {
sendBroadCast(account);
binding.avatarProgress.setVisibility(View.GONE);
currentAccount.mastodon_account = account;
Helper.recreateMainActivity(EditProfileActivity.this);
new Thread(() -> {
try {
new app.fedilab.android.client.entities.app.Account(EditProfileActivity.this).insertOrUpdate(currentAccount);
} catch (DBException e) {
e.printStackTrace();
}
}).start();
} else {
Helper.sendToastMessage(getApplication(), Helper.RECEIVE_TOAST_TYPE_ERROR, getString(R.string.toast_error));
}
});
} else if (requestCode == PICK_MEDIA_HEADER && resultCode == RESULT_OK) {
Glide.with(EditProfileActivity.this)
.asDrawable()
@ -235,7 +230,7 @@ public class EditProfileActivity extends BaseBarActivity {
currentAccount.mastodon_account = account;
new Thread(() -> {
try {
new app.fedilab.android.mastodon.client.entities.app.Account(EditProfileActivity.this).insertOrUpdate(currentAccount);
new app.fedilab.android.client.entities.app.Account(EditProfileActivity.this).insertOrUpdate(currentAccount);
} catch (DBException e) {
e.printStackTrace();
}
@ -314,22 +309,22 @@ public class EditProfileActivity extends BaseBarActivity {
return true;
} else if (item.getItemId() == R.id.action_save) {
accountsVM.updateCredentials(BaseMainActivity.currentInstance, BaseMainActivity.currentToken,
binding.discoverable.isChecked(),
binding.bot.isChecked(),
binding.displayName.getText().toString().trim(),
binding.bio.getText().toString(),
binding.locked.isChecked(),
getPrivacy(),
binding.sensitive.isChecked(),
null,
getFields()
)
binding.discoverable.isChecked(),
binding.bot.isChecked(),
binding.displayName.getText().toString().trim(),
binding.bio.getText().toString(),
binding.locked.isChecked(),
getPrivacy(),
binding.sensitive.isChecked(),
null,
getFields()
)
.observe(EditProfileActivity.this, account -> {
if (account != null) {
currentAccount.mastodon_account = account;
new Thread(() -> {
try {
new app.fedilab.android.mastodon.client.entities.app.Account(EditProfileActivity.this).insertOrUpdate(currentAccount);
new app.fedilab.android.client.entities.app.Account(EditProfileActivity.this).insertOrUpdate(currentAccount);
sendBroadCast(account);
} catch (DBException e) {
e.printStackTrace();

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.activities;
package app.fedilab.android.activities;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
@ -15,6 +15,7 @@ package app.fedilab.android.mastodon.activities;
* see <http://www.gnu.org/licenses>. */
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
@ -27,28 +28,27 @@ import android.widget.Button;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.client.entities.api.Filter;
import app.fedilab.android.databinding.ActivityFiltersBinding;
import app.fedilab.android.databinding.PopupAddFilterBinding;
import app.fedilab.android.mastodon.client.entities.api.Filter;
import app.fedilab.android.mastodon.ui.drawer.FilterAdapter;
import app.fedilab.android.mastodon.ui.drawer.KeywordAdapter;
import app.fedilab.android.mastodon.viewmodel.mastodon.FiltersVM;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.ui.drawer.FilterAdapter;
import app.fedilab.android.viewmodel.mastodon.AccountsVM;
public class FilterActivity extends BaseBarActivity implements FilterAdapter.Delete {
public class FilterActivity extends BaseActivity implements FilterAdapter.Delete {
private ActivityFiltersBinding binding;
private List<Filter> filterList;
@ -62,15 +62,14 @@ public class FilterActivity extends BaseBarActivity implements FilterAdapter.Del
* @param listener - {@link FilterAdapter.FilterAction}
*/
public static void addEditFilter(Context context, Filter filter, FilterAdapter.FilterAction listener) {
AlertDialog.Builder dialogBuilder = new MaterialAlertDialogBuilder(context);
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context, Helper.dialogStyle());
PopupAddFilterBinding popupAddFilterBinding = PopupAddFilterBinding.inflate(LayoutInflater.from(context));
FiltersVM filtersVM = new ViewModelProvider((ViewModelStoreOwner) context).get(FiltersVM.class);
AccountsVM accountsVM = new ViewModelProvider((ViewModelStoreOwner) context).get(AccountsVM.class);
dialogBuilder.setView(popupAddFilterBinding.getRoot());
ArrayAdapter<CharSequence> adapterResize = ArrayAdapter.createFromResource(Objects.requireNonNull(context),
R.array.filter_expire, android.R.layout.simple_spinner_dropdown_item);
popupAddFilterBinding.filterExpire.setAdapter(adapterResize);
final int[] expire = {-1};
Filter.FilterParams filterParams = new Filter.FilterParams();
popupAddFilterBinding.filterExpire.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent1, View view, int position1, long id) {
@ -79,21 +78,18 @@ public class FilterActivity extends BaseBarActivity implements FilterAdapter.Del
expire[0] = -1;
break;
case 1:
expire[0] = 1800;
break;
case 2:
expire[0] = 3600;
break;
case 3:
case 2:
expire[0] = 21600;
break;
case 4:
case 3:
expire[0] = 43200;
break;
case 5:
case 4:
expire[0] = 86400;
break;
case 6:
case 5:
expire[0] = 604800;
break;
}
@ -103,29 +99,9 @@ public class FilterActivity extends BaseBarActivity implements FilterAdapter.Del
public void onNothingSelected(AdapterView<?> parent1) {
}
});
if (filter != null) {
filterParams.filter_action = filter.filter_action;
filterParams.title = filter.title;
// filterParams.expires_in = filter.expires_at;
filterParams.context = filter.context;
filterParams.id = filter.id;
if (filter.keywords != null && filter.keywords.size() > 0) {
filterParams.keywords = new ArrayList<>();
for (Filter.KeywordsAttributes keywordsAttributes : filter.keywords) {
Filter.KeywordsParams keywordsParams = new Filter.KeywordsParams();
keywordsParams._destroy = null;
keywordsParams.id = keywordsAttributes.id;
keywordsParams.keyword = keywordsAttributes.keyword;
keywordsParams.whole_word = keywordsAttributes.whole_word;
filterParams.keywords.add(keywordsParams);
}
}
popupAddFilterBinding.addTitle.setText(filter.title);
if (filter.context != null) {
popupAddFilterBinding.addPhrase.setText(filter.phrase);
if (filter.context != null)
for (String val : filter.context) {
switch (val) {
case "home":
@ -140,91 +116,48 @@ public class FilterActivity extends BaseBarActivity implements FilterAdapter.Del
case "thread":
popupAddFilterBinding.contextConversation.setChecked(true);
break;
case "account":
popupAddFilterBinding.contextProfiles.setChecked(true);
break;
}
}
}
if (filter.filter_action.equalsIgnoreCase("warn")) {
popupAddFilterBinding.actionHide.setChecked(true);
popupAddFilterBinding.actionRemove.setChecked(false);
} else {
popupAddFilterBinding.actionHide.setChecked(false);
popupAddFilterBinding.actionRemove.setChecked(true);
}
}
if (filterParams.keywords == null) {
filterParams.keywords = new ArrayList<>();
popupAddFilterBinding.contextWholeWord.setChecked(filter.whole_word);
popupAddFilterBinding.contextDrop.setChecked(filter.irreversible);
}
KeywordAdapter keywordAdapter = new KeywordAdapter(filterParams.keywords);
popupAddFilterBinding.lvKeywords.setAdapter(keywordAdapter);
popupAddFilterBinding.lvKeywords.setNestedScrollingEnabled(false);
popupAddFilterBinding.lvKeywords.setLayoutManager(new LinearLayoutManager(context));
popupAddFilterBinding.addKeyword.setOnClickListener(v -> {
Filter.KeywordsParams keywordsParams = new Filter.KeywordsParams();
keywordsParams.whole_word = true;
filterParams.keywords.add(keywordsParams);
keywordAdapter.notifyItemInserted(filterParams.keywords.size() - 1);
});
popupAddFilterBinding.actionRemove.setOnClickListener(v -> {
popupAddFilterBinding.actionHide.setChecked(false);
popupAddFilterBinding.actionRemove.setChecked(true);
});
popupAddFilterBinding.actionHide.setOnClickListener(v -> {
popupAddFilterBinding.actionRemove.setChecked(false);
popupAddFilterBinding.actionHide.setChecked(true);
});
AlertDialog alertDialog = dialogBuilder.setPositiveButton(R.string.validate, null)
.setNegativeButton(R.string.cancel, null).create();
alertDialog.setOnShowListener(dialogInterface -> {
Button button = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(view -> {
boolean canBeSent = true;
for (int i = 0; i < filterParams.keywords.size(); i++) {
if (filterParams.keywords.get(i).keyword == null || (filterParams.keywords.get(i).keyword.trim().isEmpty() && filterParams.keywords.get(i)._destroy != null && !filterParams.keywords.get(i)._destroy)) {
canBeSent = false;
}
if (popupAddFilterBinding.addPhrase.getText() == null || popupAddFilterBinding.addPhrase.getText().toString().trim().length() == 0) {
popupAddFilterBinding.addPhrase.setError(context.getString(R.string.cannot_be_empty));
return;
}
if (popupAddFilterBinding.addTitle.getText().toString().trim().isEmpty()) {
popupAddFilterBinding.addTitle.setError(context.getString(R.string.cannot_be_empty));
canBeSent = false;
}
if (!popupAddFilterBinding.contextConversation.isChecked() && !popupAddFilterBinding.contextHome.isChecked() && !popupAddFilterBinding.contextPublic.isChecked() && !popupAddFilterBinding.contextNotification.isChecked() && !popupAddFilterBinding.contextProfiles.isChecked()) {
if (!popupAddFilterBinding.contextConversation.isChecked() && !popupAddFilterBinding.contextHome.isChecked() && !popupAddFilterBinding.contextPublic.isChecked() && !popupAddFilterBinding.contextNotification.isChecked()) {
popupAddFilterBinding.contextDescription.setError(context.getString(R.string.cannot_be_empty));
canBeSent = false;
return;
}
if (canBeSent) {
filterParams.context = new ArrayList<>();
if (popupAddFilterBinding.addPhrase.getText() != null && popupAddFilterBinding.addPhrase.getText().toString().trim().length() > 0) {
Filter filterSent = new Filter();
ArrayList<String> contextFilter = new ArrayList<>();
if (popupAddFilterBinding.contextHome.isChecked())
filterParams.context.add("home");
contextFilter.add("home");
if (popupAddFilterBinding.contextPublic.isChecked())
filterParams.context.add("public");
contextFilter.add("public");
if (popupAddFilterBinding.contextNotification.isChecked())
filterParams.context.add("notifications");
contextFilter.add("notifications");
if (popupAddFilterBinding.contextConversation.isChecked())
filterParams.context.add("thread");
if (popupAddFilterBinding.contextProfiles.isChecked())
filterParams.context.add("account");
if (expire[0] != -1) {
filterParams.expires_in = (long) expire[0];
} else {
filterParams.expires_in = null;
}
filterParams.title = popupAddFilterBinding.addTitle.getText().toString().trim();
filterParams.filter_action = popupAddFilterBinding.actionRemove.isChecked() ? "hide" : "warn";
if (filterParams.id != null) {
filtersVM.editFilter(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, filterParams)
contextFilter.add("thread");
filterSent.context = contextFilter;
filterSent.expires_at_sent = expire[0];
filterSent.phrase = popupAddFilterBinding.addPhrase.getText().toString();
filterSent.whole_word = popupAddFilterBinding.contextWholeWord.isChecked();
filterSent.irreversible = popupAddFilterBinding.contextDrop.isChecked();
if (filter != null) {
accountsVM.editFilter(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, filter.id, filterSent.phrase, filterSent.context, filterSent.irreversible, filterSent.whole_word, filterSent.expires_at_sent)
.observe((LifecycleOwner) context, listener::callback);
} else {
filtersVM.addFilter(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, filterParams)
accountsVM.addFilter(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, filterSent.phrase, filterSent.context, filterSent.irreversible, filterSent.whole_word, filterSent.expires_at_sent)
.observe((LifecycleOwner) context, listener::callback);
}
alertDialog.dismiss();
@ -236,8 +169,8 @@ public class FilterActivity extends BaseBarActivity implements FilterAdapter.Del
alertDialog.setTitle(context.getString(R.string.action_update_filter));
alertDialog.setOnDismissListener(dialogInterface -> {
//Hide keyboard
InputMethodManager imm = (InputMethodManager) context.getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(popupAddFilterBinding.addTitle.getWindowToken(), 0);
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(popupAddFilterBinding.addPhrase.getWindowToken(), 0);
});
if (alertDialog.getWindow() != null) {
alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
@ -249,16 +182,17 @@ public class FilterActivity extends BaseBarActivity implements FilterAdapter.Del
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyThemeBar(this);
binding = ActivityFiltersBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
filterList = new ArrayList<>();
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
FiltersVM filtersVM = new ViewModelProvider(FilterActivity.this).get(FiltersVM.class);
filtersVM.getFilters(BaseMainActivity.currentInstance, BaseMainActivity.currentToken)
AccountsVM accountsVM = new ViewModelProvider(FilterActivity.this).get(AccountsVM.class);
accountsVM.getFilters(BaseMainActivity.currentInstance, BaseMainActivity.currentToken)
.observe(FilterActivity.this, filters -> {
BaseMainActivity.mainFilters = filters;
if (filters != null && filters.size() > 0) {
@ -275,11 +209,7 @@ public class FilterActivity extends BaseBarActivity implements FilterAdapter.Del
binding.addFilter.setOnClickListener(v -> addEditFilter(FilterActivity.this, null, filter -> {
if (filter != null) {
if (MainActivity.mainFilters == null) {
MainActivity.mainFilters = new ArrayList<>();
}
filterList.add(0, filter);
MainActivity.mainFilters.add(filter);
if (filterAdapter != null) {
filterAdapter.notifyItemInserted(0);
} else {

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.activities;
package app.fedilab.android.activities;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
@ -15,11 +15,13 @@ package app.fedilab.android.mastodon.activities;
* see <http://www.gnu.org/licenses>. */
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -29,12 +31,13 @@ import java.util.List;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.api.Account;
import app.fedilab.android.client.entities.api.Accounts;
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.helper.MastodonHelper;
import app.fedilab.android.mastodon.ui.drawer.AccountFollowRequestAdapter;
import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.helper.MastodonHelper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.ui.drawer.AccountFollowRequestAdapter;
import app.fedilab.android.viewmodel.mastodon.AccountsVM;
public class FollowRequestActivity extends BaseActivity {
@ -48,13 +51,14 @@ public class FollowRequestActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyTheme(this);
binding = ActivityStatusInfoBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
accountList = new ArrayList<>();
flagLoading = false;

View file

@ -0,0 +1,170 @@
package app.fedilab.android.activities;
/* 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.BaseMainActivity.currentAccount;
import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.Menu;
import android.view.MenuItem;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import java.util.ArrayList;
import java.util.List;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.client.entities.app.Pinned;
import app.fedilab.android.client.entities.app.PinnedTimeline;
import app.fedilab.android.client.entities.app.StatusDraft;
import app.fedilab.android.client.entities.app.TagTimeline;
import app.fedilab.android.client.entities.app.Timeline;
import app.fedilab.android.databinding.ActivityHashtagBinding;
import app.fedilab.android.exception.DBException;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.ui.fragment.timeline.FragmentMastodonTimeline;
import es.dmoral.toasty.Toasty;
public class HashTagActivity extends BaseActivity {
public static int position;
private String tag;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyTheme(this);
ActivityHashtagBinding binding = ActivityHashtagBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
Bundle b = getIntent().getExtras();
if (b != null) {
tag = b.getString(Helper.ARG_SEARCH_KEYWORD, null);
}
if (tag == null)
finish();
setSupportActionBar(binding.toolbar);
ActionBar actionBar = getSupportActionBar();
//Remove title
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
binding.title.setText(tag);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
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.toolbar.setPopupTheme(Helper.popupStyle());
binding.compose.setOnClickListener(v -> {
Intent intentToot = new Intent(HashTagActivity.this, ComposeActivity.class);
StatusDraft statusDraft = new StatusDraft();
Status status = new Status();
status.text = "#" + tag;
List<Status> statuses = new ArrayList<>();
statuses.add(status);
statusDraft.statusDraftList = statuses;
Bundle _b = new Bundle();
_b.putSerializable(Helper.ARG_TAG_TIMELINE, statusDraft);
intentToot.putExtras(_b);
startActivity(intentToot);
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
} else if (item.getItemId() == R.id.action_add_timeline) {
new Thread(() -> {
try {
Pinned pinned = new Pinned(HashTagActivity.this).getPinned(currentAccount);
boolean canBeAdded = true;
boolean update = true;
if (pinned == null) {
pinned = new Pinned();
pinned.pinnedTimelines = new ArrayList<>();
update = false;
} else {
for (PinnedTimeline pinnedTimeline : pinned.pinnedTimelines) {
if (pinnedTimeline.type == Timeline.TimeLineEnum.TAG) {
if (pinnedTimeline.tagTimeline.name.compareTo(tag.trim()) == 0) {
canBeAdded = false;
}
}
}
}
if (!canBeAdded) {
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> Toasty.warning(HashTagActivity.this, getString(R.string.tags_already_stored), Toasty.LENGTH_SHORT).show();
mainHandler.post(myRunnable);
return;
}
PinnedTimeline pinnedTimeline = new PinnedTimeline();
pinnedTimeline.type = Timeline.TimeLineEnum.TAG;
pinnedTimeline.position = pinned.pinnedTimelines.size();
pinnedTimeline.displayed = true;
TagTimeline tagTimeline = new TagTimeline();
tagTimeline.name = tag.trim();
tagTimeline.isNSFW = false;
tagTimeline.isART = false;
pinnedTimeline.tagTimeline = tagTimeline;
pinned.pinnedTimelines.add(pinnedTimeline);
if (update) {
new Pinned(HashTagActivity.this).updatePinned(pinned);
} else {
new Pinned(HashTagActivity.this).insertPinned(pinned);
}
Bundle b = new Bundle();
b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Intent intentBD = new Intent(Helper.BROADCAST_DATA);
intentBD.putExtras(b);
LocalBroadcastManager.getInstance(HashTagActivity.this).sendBroadcast(intentBD);
} catch (DBException e) {
e.printStackTrace();
}
}).start();
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onCreateOptionsMenu(@NonNull Menu menu) {
getMenuInflater().inflate(R.menu.menu_reorder, menu);
return super.onCreateOptionsMenu(menu);
}
}

View file

@ -0,0 +1,109 @@
package app.fedilab.android.activities;
/* 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.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.Html;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.lifecycle.ViewModelProvider;
import com.bumptech.glide.Glide;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.api.Instance;
import app.fedilab.android.databinding.ActivityInstanceBinding;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.viewmodel.mastodon.InstancesVM;
import es.dmoral.toasty.Toasty;
public class InstanceActivity extends BaseActivity {
ActivityInstanceBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyThemeDialog(this);
binding = ActivityInstanceBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
getWindow().setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
if (getSupportActionBar() != null)
getSupportActionBar().hide();
binding.close.setOnClickListener(view -> finish());
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
InstancesVM instancesVM = new ViewModelProvider(InstanceActivity.this).get(InstancesVM.class);
instancesVM.getInstance(BaseMainActivity.currentInstance).observe(InstanceActivity.this, instanceInfo -> {
binding.instanceContainer.setVisibility(View.VISIBLE);
binding.loader.setVisibility(View.GONE);
if (instanceInfo == null || instanceInfo.info == null) {
Toasty.error(InstanceActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show();
return;
}
Instance instance = instanceInfo.info;
binding.instanceTitle.setText(instance.title);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
binding.instanceDescription.setText(Html.fromHtml(instance.description, Html.FROM_HTML_MODE_LEGACY));
else
binding.instanceDescription.setText(Html.fromHtml(instance.description));
if (instance.description == null || instance.description.trim().length() == 0)
binding.instanceDescription.setText(getString(R.string.instance_no_description));
binding.instanceVersion.setText(instance.version);
binding.instanceUri.setText(instance.uri);
if (instance.email == null) {
binding.instanceContact.hide();
}
Glide.with(InstanceActivity.this)
.asBitmap()
.load(instance.thumbnail)
.into(binding.backGroundImage);
binding.instanceContact.setOnClickListener(v -> {
Intent emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", instance.email, null));
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "[Mastodon] - " + instance.uri);
startActivity(Intent.createChooser(emailIntent, getString(R.string.send_email)));
});
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View file

@ -0,0 +1,114 @@
package app.fedilab.android.activities;
/* 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.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.SpannableString;
import android.text.style.UnderlineSpan;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider;
import com.bumptech.glide.Glide;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.app.InstanceSocial;
import app.fedilab.android.databinding.ActivityInstanceSocialBinding;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.viewmodel.mastodon.InstanceSocialVM;
public class InstanceHealthActivity extends BaseActivity {
private ActivityInstanceSocialBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyThemeDialog(this);
binding = ActivityInstanceSocialBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
getWindow().setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
if (getSupportActionBar() != null)
getSupportActionBar().hide();
binding.close.setOnClickListener(view -> finish());
SpannableString content = new SpannableString(binding.refInstance.getText().toString());
content.setSpan(new UnderlineSpan(), 0, content.length(), 0);
binding.refInstance.setText(content);
binding.refInstance.setOnClickListener(view -> {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://instances.social"));
startActivity(browserIntent);
});
checkInstance();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
private void checkInstance() {
InstanceSocialVM instanceSocialVM = new ViewModelProvider(InstanceHealthActivity.this).get(InstanceSocialVM.class);
instanceSocialVM.getInstances(BaseMainActivity.currentInstance.trim()).observe(InstanceHealthActivity.this, instanceSocialList -> {
if (instanceSocialList != null && instanceSocialList.instances.size() > 0) {
InstanceSocial.Instance instanceSocial = instanceSocialList.instances.get(0);
if (instanceSocial.thumbnail != null && !instanceSocial.thumbnail.equals("null"))
Glide.with(InstanceHealthActivity.this)
.asBitmap()
.load(instanceSocial.thumbnail)
.into(binding.backGroundImage);
binding.name.setText(instanceSocial.name);
if (instanceSocial.up) {
binding.up.setText(R.string.is_up);
binding.up.setTextColor(ContextCompat.getColor(InstanceHealthActivity.this, R.color.green_1));
} else {
binding.up.setText(R.string.is_down);
binding.up.setTextColor(ContextCompat.getColor(InstanceHealthActivity.this, R.color.red_1));
}
binding.uptime.setText(getString(R.string.instance_health_uptime, (instanceSocial.uptime * 100)));
if (instanceSocial.checked_at != null)
binding.checkedAt.setText(getString(R.string.instance_health_checkedat, Helper.dateToString(instanceSocial.checked_at)));
binding.values.setText(getString(R.string.instance_health_indication, instanceSocial.version, Helper.withSuffix(instanceSocial.active_users), Helper.withSuffix(instanceSocial.statuses)));
binding.instanceContainer.setVisibility(View.VISIBLE);
} else {
binding.instanceContainer.setVisibility(View.VISIBLE);
binding.mainContainer.setVisibility(View.GONE);
binding.noInstance.setVisibility(View.VISIBLE);
}
binding.loader.setVisibility(View.GONE);
});
}
}

View file

@ -0,0 +1,127 @@
package app.fedilab.android.activities;
/* 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 androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY;
import android.os.Build;
import android.os.Bundle;
import android.text.Html;
import android.text.SpannableString;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import java.util.ArrayList;
import java.util.List;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.api.Account;
import app.fedilab.android.databinding.ActivityInstanceProfileBinding;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.ui.drawer.AccountAdapter;
import app.fedilab.android.viewmodel.mastodon.NodeInfoVM;
import app.fedilab.android.viewmodel.mastodon.SearchVM;
import es.dmoral.toasty.Toasty;
public class InstanceProfileActivity extends BaseActivity {
private String instance;
private ActivityInstanceProfileBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyThemeDialog(this);
binding = ActivityInstanceProfileBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
getWindow().setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
Bundle b = getIntent().getExtras();
if (getSupportActionBar() != null)
getSupportActionBar().hide();
if (b != null)
instance = b.getString(Helper.ARG_INSTANCE, null);
if (instance == null) {
finish();
}
Button close = findViewById(R.id.close);
close.setOnClickListener(view -> finish());
NodeInfoVM nodeInfoVM = new ViewModelProvider(InstanceProfileActivity.this).get(NodeInfoVM.class);
nodeInfoVM.getNodeInfo(instance).observe(InstanceProfileActivity.this, nodeInfo -> {
if (nodeInfo == null) {
Toasty.error(InstanceProfileActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show();
finish();
return;
}
binding.name.setText(nodeInfo.metadata != null ? nodeInfo.metadata.nodeName : instance);
SpannableString descriptionSpan;
if (nodeInfo.metadata != null && nodeInfo.metadata.nodeDescription != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
descriptionSpan = new SpannableString(Html.fromHtml(nodeInfo.metadata.nodeDescription, FROM_HTML_MODE_LEGACY));
else
descriptionSpan = new SpannableString(Html.fromHtml(nodeInfo.metadata.nodeDescription));
binding.description.setText(descriptionSpan, TextView.BufferType.SPANNABLE);
}
binding.userCount.setText(Helper.withSuffix((nodeInfo.usage.users.total)));
binding.statusCount.setText(Helper.withSuffix(((nodeInfo.usage.localPosts))));
String softwareStr = nodeInfo.software.name + " - ";
binding.software.setText(softwareStr);
binding.version.setText(nodeInfo.software.version);
if (nodeInfo.metadata != null && nodeInfo.metadata.staffAccounts != null && nodeInfo.metadata.staffAccounts.size() > 0) {
SearchVM searchVM = new ViewModelProvider(InstanceProfileActivity.this).get(SearchVM.class);
List<Account> accounts = new ArrayList<>();
for (String accountURL : nodeInfo.metadata.staffAccounts) {
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, accountURL, null, "accounts", false, true, false, 0, null, null, 1)
.observe(InstanceProfileActivity.this, results -> {
if (results.accounts != null && results.accounts.size() > 0) {
accounts.add(results.accounts.get(0));
}
if (accounts.size() == nodeInfo.metadata.staffAccounts.size()) {
AccountAdapter accountsListAdapter = new AccountAdapter(accounts);
binding.lvAccounts.setAdapter(accountsListAdapter);
final LinearLayoutManager mLayoutManager;
mLayoutManager = new LinearLayoutManager(InstanceProfileActivity.this);
binding.lvAccounts.setLayoutManager(mLayoutManager);
}
});
}
}
binding.instanceContainer.setVisibility(View.VISIBLE);
binding.loader.setVisibility(View.GONE);
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View file

@ -15,15 +15,11 @@ package app.fedilab.android.activities;
* see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.mastodon.helper.Helper.PREF_USER_SOFTWARE;
import static app.fedilab.android.helper.MastodonHelper.REDIRECT_CONTENT_WEB;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.FrameLayout;
@ -36,18 +32,14 @@ import org.jetbrains.annotations.NotNull;
import java.util.regex.Matcher;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.mastodon.activities.BaseActivity;
import app.fedilab.android.mastodon.activities.ProxyActivity;
import app.fedilab.android.mastodon.client.entities.app.Account;
import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.MastodonHelper;
import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.mastodon.viewmodel.mastodon.AdminVM;
import app.fedilab.android.mastodon.viewmodel.mastodon.OauthVM;
import app.fedilab.android.ui.fragment.FragmentLoginMain;
import app.fedilab.android.client.entities.app.Account;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.ui.fragment.login.FragmentLoginMain;
import app.fedilab.android.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.viewmodel.mastodon.AdminVM;
import app.fedilab.android.viewmodel.mastodon.OauthVM;
import es.dmoral.toasty.Toasty;
@ -56,40 +48,12 @@ public class LoginActivity extends BaseActivity {
public static Account.API apiLogin;
public static String currentInstanceLogin, client_idLogin, client_secretLogin, softwareLogin;
public static boolean requestedAdmin;
@SuppressLint("ApplySharedPref")
public void proceedLogin(Activity activity, Account account) {
new Thread(() -> {
try {
//update the database
new Account(activity).insertOrUpdate(account);
Handler mainHandler = new Handler(Looper.getMainLooper());
BaseMainActivity.currentToken = account.token;
BaseMainActivity.currentUserID = account.user_id;
BaseMainActivity.api = Account.API.MASTODON;
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(activity);
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.PREF_USER_TOKEN, account.token);
editor.putString(PREF_USER_SOFTWARE, BaseMainActivity.api.name());
editor.commit();
//The user is now authenticated, it will be redirected to MainActivity
Runnable myRunnable = () -> {
Intent mainActivity = new Intent(activity, MainActivity.class);
startActivity(mainActivity);
finish();
};
mainHandler.post(myRunnable);
} catch (DBException e) {
e.printStackTrace();
}
}).start();
}
private final int PICK_IMPORT = 5557;
private boolean requestedAdmin;
private void manageItent(Intent intent) {
if (intent != null && intent.getData() != null && intent.getData().toString().contains(MastodonHelper.REDIRECT_CONTENT_WEB + "?code=")) {
if (intent != null && intent.getData() != null && intent.getData().toString().contains(REDIRECT_CONTENT_WEB + "?code=")) {
String url = intent.getData().toString();
Matcher matcher = Helper.codePattern.matcher(url);
if (!matcher.find()) {
@ -103,43 +67,34 @@ public class LoginActivity extends BaseActivity {
String scope = requestedAdmin ? Helper.OAUTH_SCOPES_ADMIN : Helper.OAUTH_SCOPES;
oauthVM.createToken(currentInstanceLogin, "authorization_code", client_idLogin, client_secretLogin, Helper.REDIRECT_CONTENT_WEB, scope, code)
.observe(LoginActivity.this, tokenObj -> {
if (tokenObj != null) {
Account account = new Account();
account.client_id = client_idLogin;
account.client_secret = client_secretLogin;
account.token = tokenObj.token_type + " " + tokenObj.access_token;
account.api = apiLogin;
account.software = softwareLogin;
account.instance = currentInstanceLogin;
//API call to retrieve account information for the new token
AccountsVM accountsVM = new ViewModelProvider(LoginActivity.this).get(AccountsVM.class);
accountsVM.getConnectedAccount(currentInstanceLogin, account.token).observe(LoginActivity.this, mastodonAccount -> {
if (mastodonAccount != null) {
account.mastodon_account = mastodonAccount;
account.user_id = mastodonAccount.id;
//We check if user have really moderator rights
if (requestedAdmin) {
AdminVM adminVM = new ViewModelProvider(LoginActivity.this).get(AdminVM.class);
adminVM.getAccount(account.instance, account.token, account.user_id).observe(LoginActivity.this, adminAccount -> {
account.admin = adminAccount != null;
proceedLogin(LoginActivity.this, account);
});
} else {
proceedLogin(LoginActivity.this, account);
}
} else {
Toasty.error(LoginActivity.this, getString(R.string.toast_token), Toast.LENGTH_LONG).show();
}
});
} else {
Toasty.error(LoginActivity.this, getString(R.string.toast_token), Toast.LENGTH_LONG).show();
}
Account account = new Account();
account.client_id = client_idLogin;
account.client_secret = client_secretLogin;
account.token = tokenObj.token_type + " " + tokenObj.access_token;
account.api = apiLogin;
account.software = softwareLogin;
account.instance = currentInstanceLogin;
//API call to retrieve account information for the new token
AccountsVM accountsVM = new ViewModelProvider(LoginActivity.this).get(AccountsVM.class);
accountsVM.getConnectedAccount(currentInstanceLogin, account.token).observe(LoginActivity.this, mastodonAccount -> {
account.mastodon_account = mastodonAccount;
account.user_id = mastodonAccount.id;
//We check if user have really moderator rights
if (requestedAdmin) {
AdminVM adminVM = new ViewModelProvider(LoginActivity.this).get(AdminVM.class);
adminVM.getAccount(account.instance, account.token, account.user_id).observe(LoginActivity.this, adminAccount -> {
account.admin = adminAccount != null;
WebviewConnectActivity.proceedLogin(LoginActivity.this, account);
});
} else {
WebviewConnectActivity.proceedLogin(LoginActivity.this, account);
}
});
});
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
@ -149,18 +104,24 @@ public class LoginActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyTheme(this);
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);
requestedAdmin = false;
//The activity handles a redirect URI, it will extract token code and will proceed to authentication
//That happens when the user wants to use an external browser
manageItent(getIntent());
}
public boolean requestedAdmin() {
return requestedAdmin;
}
public boolean setAdmin(boolean askAdmin) {
return requestedAdmin = askAdmin;
}
@Override
protected void onResume() {
@ -172,6 +133,9 @@ public class LoginActivity extends BaseActivity {
public boolean onCreateOptionsMenu(@NotNull Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main_login, menu);
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(LoginActivity.this);
boolean embedded_browser = sharedpreferences.getBoolean(getString(R.string.SET_EMBEDDED_BROWSER), true);
menu.findItem(R.id.action_custom_tabs).setChecked(!embedded_browser);
return true;
}
@ -181,8 +145,17 @@ public class LoginActivity extends BaseActivity {
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_proxy) {
(new ProxyActivity()).show(getSupportFragmentManager(), null);
Intent intent = new Intent(LoginActivity.this, ProxyActivity.class);
startActivity(intent);
} else if (id == R.id.action_custom_tabs) {
item.setChecked(!item.isChecked());
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(LoginActivity.this);
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putBoolean(getString(R.string.SET_EMBEDDED_BROWSER), !item.isChecked());
editor.apply();
return false;
} else if (id == R.id.action_request_admin) {
item.setChecked(!item.isChecked());
requestedAdmin = item.isChecked();
@ -191,5 +164,20 @@ public class LoginActivity extends BaseActivity {
return super.onOptionsItemSelected(item);
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PICK_IMPORT && resultCode == RESULT_OK) {
if (data == null || data.getData() == null) {
Toasty.error(LoginActivity.this, getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show();
return;
}
// String filename = Helper.getFilePathFromURI(LoginActivity.this, data.getData());
// Sqlite.importDB(LoginActivity.this, filename);
} else {
Toasty.error(LoginActivity.this, getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show();
}
}
}

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.activities;
package app.fedilab.android.activities;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
@ -16,6 +16,7 @@ package app.fedilab.android.mastodon.activities;
import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.text.Editable;
import android.text.InputFilter;
@ -27,6 +28,7 @@ import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider;
@ -34,36 +36,28 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.client.entities.api.Account;
import app.fedilab.android.client.entities.api.MastodonList;
import app.fedilab.android.client.entities.app.Timeline;
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.Pinned;
import app.fedilab.android.mastodon.client.entities.app.PinnedTimeline;
import app.fedilab.android.mastodon.client.entities.app.Timeline;
import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.ThemeHelper;
import app.fedilab.android.mastodon.ui.drawer.AccountListAdapter;
import app.fedilab.android.mastodon.ui.drawer.MastodonListAdapter;
import app.fedilab.android.mastodon.ui.fragment.timeline.FragmentMastodonTimeline;
import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.mastodon.viewmodel.mastodon.ReorderVM;
import app.fedilab.android.mastodon.viewmodel.mastodon.TimelinesVM;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.ui.drawer.AccountListAdapter;
import app.fedilab.android.ui.drawer.MastodonListAdapter;
import app.fedilab.android.ui.fragment.timeline.FragmentMastodonTimeline;
import app.fedilab.android.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.viewmodel.mastodon.TimelinesVM;
import es.dmoral.toasty.Toasty;
public class MastodonListActivity extends BaseBarActivity implements MastodonListAdapter.ActionOnList {
public class MastodonListActivity extends BaseActivity implements MastodonListAdapter.ActionOnList {
AccountListAdapter accountsInListAdapter;
@ -78,90 +72,45 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
private boolean flagLoading;
private String max_id;
private FragmentMastodonTimeline fragmentMastodonTimeline;
private boolean orderASC;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyThemeBar(this);
binding = ActivityListBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
canGoBack = false;
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
flagLoading = false;
orderASC = true;
max_id = null;
accountsVM = new ViewModelProvider(MastodonListActivity.this).get(AccountsVM.class);
timelinesVM = new ViewModelProvider(MastodonListActivity.this).get(TimelinesVM.class);
timelinesVM.getLists(BaseMainActivity.currentInstance, BaseMainActivity.currentToken)
.observe(MastodonListActivity.this, mastodonLists -> {
ReorderVM reorderVM = new ViewModelProvider(MastodonListActivity.this).get(ReorderVM.class);
reorderVM.getPinned().observe(MastodonListActivity.this, pinned -> {
if (mastodonLists != null && mastodonLists.size() > 0) {
mastodonListList = new ArrayList<>(mastodonLists);
if (pinned != null) {
if (pinned.pinnedTimelines != null && pinned.pinnedTimelines.size() > 0) {
for (PinnedTimeline pinnedTimeline : pinned.pinnedTimelines) {
if (pinnedTimeline.type == Timeline.TimeLineEnum.LIST) {
for (MastodonList mastodonList : mastodonLists) {
if (mastodonList.id.equalsIgnoreCase(pinnedTimeline.mastodonList.id)) {
mastodonList.position = pinnedTimeline.position;
}
}
}
}
}
}
sortAsc(mastodonListList);
mastodonListAdapter = new MastodonListAdapter(mastodonListList);
mastodonListAdapter.actionOnList = this;
binding.notContent.setVisibility(View.GONE);
binding.recyclerView.setAdapter(mastodonListAdapter);
binding.recyclerView.setLayoutManager(new LinearLayoutManager(MastodonListActivity.this));
} else {
binding.notContent.setVisibility(View.VISIBLE);
}
});
if (mastodonLists != null && mastodonLists.size() > 0) {
mastodonListList = new ArrayList<>(mastodonLists);
mastodonListAdapter = new MastodonListAdapter(mastodonListList);
mastodonListAdapter.actionOnList = this;
binding.notContent.setVisibility(View.GONE);
binding.recyclerView.setAdapter(mastodonListAdapter);
binding.recyclerView.setLayoutManager(new LinearLayoutManager(MastodonListActivity.this));
} else {
binding.notContent.setVisibility(View.VISIBLE);
}
});
}
private void sortAsc(List<MastodonList> mastodonLists) {
Collections.sort(mastodonLists, (obj1, obj2) -> obj1.title.compareToIgnoreCase(obj2.title));
orderASC = true;
invalidateOptionsMenu();
}
private void sortDesc(List<MastodonList> mastodonLists) {
Collections.sort(mastodonLists, (obj1, obj2) -> obj2.title.compareToIgnoreCase(obj1.title));
orderASC = false;
invalidateOptionsMenu();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
} else if (item.getItemId() == R.id.action_user_mute_home) {
AlertDialog.Builder dialogBuilder = new MaterialAlertDialogBuilder(MastodonListActivity.this);
dialogBuilder.setTitle(R.string.put_all_accounts_in_home_muted);
dialogBuilder.setPositiveButton(R.string.mute_them_all, (dialog, id) -> {
timelinesVM.getAccountsInList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, mastodonList.id, null, null, 0)
.observe(MastodonListActivity.this, accounts -> {
if (accounts != null && accounts.size() > 0) {
accountsVM.muteAccountsHome(MainActivity.currentAccount, accounts);
}
});
dialog.dismiss();
});
dialogBuilder.setNegativeButton(R.string.cancel, (dialog, id) -> dialog.dismiss());
dialogBuilder.show();
} else if (item.getItemId() == R.id.action_manage_users) {
AlertDialog.Builder dialogBuilder = new MaterialAlertDialogBuilder(MastodonListActivity.this);
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(MastodonListActivity.this, Helper.dialogStyle());
PopupManageAccountsListBinding popupManageAccountsListBinding = PopupManageAccountsListBinding.inflate(getLayoutInflater());
dialogBuilder.setView(popupManageAccountsListBinding.getRoot());
popupManageAccountsListBinding.loader.setVisibility(View.VISIBLE);
@ -258,7 +207,7 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
dialogBuilder.setPositiveButton(R.string.close, (dialog, id) -> dialog.dismiss());
dialogBuilder.create().show();
} else if (item.getItemId() == R.id.action_delete && mastodonList != null) {
AlertDialog.Builder alt_bld = new MaterialAlertDialogBuilder(MastodonListActivity.this);
AlertDialog.Builder alt_bld = new AlertDialog.Builder(MastodonListActivity.this, Helper.dialogStyle());
alt_bld.setTitle(R.string.action_lists_delete);
alt_bld.setMessage(R.string.action_lists_confirm_delete);
alt_bld.setPositiveButton(R.string.delete, (dialog, id) -> {
@ -296,7 +245,7 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
AlertDialog alert = alt_bld.create();
alert.show();
} else if (item.getItemId() == R.id.action_add_list) {
AlertDialog.Builder dialogBuilder = new MaterialAlertDialogBuilder(MastodonListActivity.this);
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(MastodonListActivity.this, Helper.dialogStyle());
PopupAddListBinding popupAddListBinding = PopupAddListBinding.inflate(getLayoutInflater());
dialogBuilder.setView(popupAddListBinding.getRoot());
popupAddListBinding.addList.setFilters(new InputFilter[]{new InputFilter.LengthFilter(255)});
@ -309,13 +258,6 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
}
if (newMastodonList != null) {
mastodonListList.add(0, newMastodonList);
if (mastodonListAdapter == null) {
mastodonListAdapter = new MastodonListAdapter(mastodonListList);
mastodonListAdapter.actionOnList = MastodonListActivity.this;
binding.notContent.setVisibility(View.GONE);
binding.recyclerView.setAdapter(mastodonListAdapter);
binding.recyclerView.setLayoutManager(new LinearLayoutManager(MastodonListActivity.this));
}
mastodonListAdapter.notifyItemInserted(0);
} else {
Toasty.error(MastodonListActivity.this, getString(R.string.toast_error), Toasty.LENGTH_LONG).show();
@ -335,83 +277,6 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
});
dialogBuilder.setNegativeButton(R.string.cancel, (dialog, id) -> dialog.dismiss());
dialogBuilder.create().show();
} else if (item.getItemId() == R.id.action_edit) {
AlertDialog.Builder dialogBuilder = new MaterialAlertDialogBuilder(MastodonListActivity.this);
PopupAddListBinding popupAddListBinding = PopupAddListBinding.inflate(getLayoutInflater());
dialogBuilder.setView(popupAddListBinding.getRoot());
popupAddListBinding.addList.setFilters(new InputFilter[]{new InputFilter.LengthFilter(255)});
popupAddListBinding.addList.setText(mastodonList.title);
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(
BaseMainActivity.currentInstance, BaseMainActivity.currentToken, mastodonList.id,
popupAddListBinding.addList.getText().toString().trim(), null)
.observe(MastodonListActivity.this, newMastodonList -> {
if (mastodonListList != null && newMastodonList != null) {
int position = 0;
for (MastodonList mastodonList : mastodonListList) {
if (newMastodonList.id.equalsIgnoreCase(mastodonList.id)) {
ReorderVM reorderVM = new ViewModelProvider(MastodonListActivity.this).get(ReorderVM.class);
int finalPosition = position;
reorderVM.getAllPinned().observe(MastodonListActivity.this, pinned -> {
if (pinned != null) {
if (pinned.pinnedTimelines != null) {
for (PinnedTimeline pinnedTimeline : pinned.pinnedTimelines) {
if (pinnedTimeline.mastodonList != null) {
if (pinnedTimeline.mastodonList.id.equalsIgnoreCase(newMastodonList.id)) {
if (!newMastodonList.title.equalsIgnoreCase(pinnedTimeline.mastodonList.title)) {
pinnedTimeline.mastodonList.title = newMastodonList.title;
setTitle(newMastodonList.title);
mastodonListList.get(finalPosition).title = newMastodonList.title;
mastodonListAdapter.notifyItemChanged(finalPosition);
new Thread(() -> {
try {
new Pinned(MastodonListActivity.this).updatePinned(pinned);
Bundle b = new Bundle();
b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true);
Intent intentBD = new Intent(Helper.BROADCAST_DATA);
intentBD.putExtras(b);
LocalBroadcastManager.getInstance(MastodonListActivity.this).sendBroadcast(intentBD);
} catch (DBException e) {
e.printStackTrace();
}
}).start();
}
break;
}
}
}
}
}
});
}
position++;
}
} else {
Toasty.error(MastodonListActivity.this, getString(R.string.toast_error), Toasty.LENGTH_LONG).show();
}
});
dialog.dismiss();
} else {
popupAddListBinding.addList.setError(getString(R.string.not_valid_list_name));
}
});
dialogBuilder.setNegativeButton(R.string.cancel, (dialog, id) -> dialog.dismiss());
dialogBuilder.create().show();
} else if (item.getItemId() == R.id.action_order) {
if (mastodonListList != null && mastodonListList.size() > 0 && mastodonListAdapter != null) {
if (orderASC) {
sortDesc(mastodonListList);
} else {
sortAsc(mastodonListList);
}
invalidateOptionsMenu();
mastodonListAdapter.notifyItemRangeChanged(0, mastodonListList.size());
}
}
return super.onOptionsItemSelected(item);
}
@ -441,14 +306,6 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis
public boolean onCreateOptionsMenu(@NonNull Menu menu) {
if (!canGoBack) {
getMenuInflater().inflate(R.menu.menu_main_list, menu);
MenuItem order = menu.findItem(R.id.action_order);
if (order != null) {
if (orderASC) {
order.setIcon(R.drawable.ic_baseline_filter_asc_24);
} else {
order.setIcon(R.drawable.ic_baseline_filter_desc_24);
}
}
} else {
getMenuInflater().inflate(R.menu.menu_list, menu);
}

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.activities;
package app.fedilab.android.activities;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
@ -22,44 +22,41 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.Point;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.view.Display;
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.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.viewpager.widget.ViewPager;
import org.jetbrains.annotations.NotNull;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
import java.util.ArrayList;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.api.Attachment;
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.helper.Helper;
import app.fedilab.android.mastodon.helper.MediaHelper;
import app.fedilab.android.mastodon.helper.TranslateHelper;
import app.fedilab.android.mastodon.interfaces.OnDownloadInterface;
import app.fedilab.android.mastodon.ui.fragment.media.FragmentMedia;
import app.fedilab.android.mastodon.ui.fragment.media.FragmentMediaProfile;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.MediaHelper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.interfaces.OnDownloadInterface;
import app.fedilab.android.ui.fragment.media.FragmentMedia;
import es.dmoral.toasty.Toasty;
public class MediaActivity extends BaseTransparentActivity implements OnDownloadInterface {
public class MediaActivity extends BaseActivity implements OnDownloadInterface {
int flags;
private ArrayList<Attachment> attachments;
@ -71,7 +68,7 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
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);
DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
assert manager != null;
Uri uri = manager.getUriForDownloadedFile(downloadID);
Intent shareIntent = new Intent(Intent.ACTION_SEND);
@ -93,15 +90,15 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
};
private boolean fullscreen;
private Handler handler;
private int minTouch, maxTouch;
private float startX;
private float startY;
private ActivityMediaPagerBinding binding;
private FragmentMedia mCurrentFragment;
private Status status;
private boolean mediaFromProfile;
@Override
protected void onCreate(Bundle savedInstanceState) {
getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
ThemeHelper.applyThemeBar(this);
super.onCreate(savedInstanceState);
ActivityCompat.postponeEnterTransition(MediaActivity.this);
binding = ActivityMediaPagerBinding.inflate(getLayoutInflater());
@ -114,16 +111,11 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
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);
}
if (mediaFromProfile && FragmentMediaProfile.mediaAttachmentProfile != null) {
attachments = new ArrayList<>();
attachments.addAll(FragmentMediaProfile.mediaAttachmentProfile);
}
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
if (attachments == null || attachments.size() == 0)
@ -131,55 +123,27 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
setTitle("");
ScreenSlidePagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
ScreenSlidePagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(MediaActivity.this);
binding.mediaViewpager.setAdapter(mPagerAdapter);
binding.mediaViewpager.setSaveEnabled(false);
binding.mediaViewpager.setCurrentItem(mediaPosition - 1);
binding.haulerView.setOnDragDismissedListener(dragDirection -> ActivityCompat.finishAfterTransition(MediaActivity.this));
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);
intentContext.putExtra(Helper.ARG_STATUS, attachments.get(mediaPosition - 1).status);
intentContext.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intentContext);
});
}
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, translated -> {
if (translated != null) {
attachments.get(mediaPosition - 1).translation = translated;
binding.mediaDescriptionTranslated.setText(translated);
binding.mediaDescriptionTranslated.setVisibility(View.VISIBLE);
binding.mediaDescription.setVisibility(View.GONE);
} else {
Toasty.error(MediaActivity.this, getString(R.string.toast_error_translate), Toast.LENGTH_LONG).show();
}
});
});
if (attachments.get(mediaPosition - 1).translation != null) {
binding.mediaDescription.setVisibility(View.GONE);
binding.mediaDescriptionTranslated.setText(attachments.get(mediaPosition - 1).translation);
binding.mediaDescriptionTranslated.setVisibility(View.VISIBLE);
} else {
binding.mediaDescription.setVisibility(View.VISIBLE);
binding.mediaDescriptionTranslated.setVisibility(View.GONE);
}
}
binding.mediaViewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
public void onPageScrollStateChanged(int state) {
}
binding.mediaViewpager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
}
@Override
public void onPageSelected(int position) {
mediaPosition = position;
super.onPageSelected(position);
String description = attachments.get(position).description;
if (handler != null) {
handler.removeCallbacksAndMessages(null);
@ -188,51 +152,24 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
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, translated -> {
if (translated != null) {
attachments.get(position).translation = translated;
binding.mediaDescriptionTranslated.setText(translated);
binding.mediaDescriptionTranslated.setVisibility(View.VISIBLE);
binding.mediaDescription.setVisibility(View.GONE);
} else {
Toasty.error(MediaActivity.this, getString(R.string.toast_error_translate), Toast.LENGTH_LONG).show();
}
});
});
if (!fullscreen) {
if (attachments.get(position).translation != null) {
binding.mediaDescription.setVisibility(View.GONE);
binding.mediaDescriptionTranslated.setText(attachments.get(position).translation);
binding.mediaDescriptionTranslated.setVisibility(View.VISIBLE);
} else {
binding.mediaDescription.setVisibility(View.VISIBLE);
binding.mediaDescriptionTranslated.setVisibility(View.GONE);
}
} else {
binding.mediaDescription.setVisibility(View.GONE);
binding.mediaDescriptionTranslated.setVisibility(View.GONE);
}
}
@Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
}
});
setFullscreen(true);
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int screenHeight = size.y;
minTouch = (int) (screenHeight * 0.1);
maxTouch = (int) (screenHeight * 0.9);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
try {
return super.dispatchTouchEvent(event);
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
}
return false;
}
public void toogleFullScreen() {
fullscreen = !fullscreen;
setFullscreen(fullscreen);
}
@Override
public boolean onCreateOptionsMenu(@NonNull Menu menu) {
@ -250,17 +187,8 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
int position = binding.mediaViewpager.getCurrentItem();
Attachment attachment = attachments.get(position);
if (Build.VERSION.SDK_INT >= 23) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
if (ContextCompat.checkSelfPermission(MediaActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(MediaActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MediaActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, Helper.EXTERNAL_STORAGE_REQUEST_CODE_MEDIA_SAVE);
} else {
if (attachment.type.compareTo("image") == 0) {
MediaHelper.manageMove(MediaActivity.this, attachment.url, false);
} else {
MediaHelper.manageDownloadsNoPopup(MediaActivity.this, attachment.url);
downloadID = -1;
}
}
if (ContextCompat.checkSelfPermission(MediaActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(MediaActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MediaActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, Helper.EXTERNAL_STORAGE_REQUEST_CODE_MEDIA_SAVE);
} else {
if (attachment.type.compareTo("image") == 0) {
MediaHelper.manageMove(MediaActivity.this, attachment.url, false);
@ -322,40 +250,52 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
}
}
private void toggleScreenContain(boolean fullscreen) {
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(description);
if (attachments.get(binding.mediaViewpager.getCurrentItem()).translation != null) {
binding.mediaDescription.setVisibility(View.GONE);
binding.mediaDescriptionTranslated.setText(attachments.get(binding.mediaViewpager.getCurrentItem()).translation);
binding.mediaDescriptionTranslated.setVisibility(View.VISIBLE);
} else {
binding.mediaDescription.setVisibility(View.VISIBLE);
binding.mediaDescriptionTranslated.setVisibility(View.GONE);
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = event.getX();
startY = event.getY();
break;
case MotionEvent.ACTION_UP:
float endX = event.getX();
float endY = event.getY();
if (endY > minTouch && endY < maxTouch && isAClick(startX, endX, startY, endY)) {
setFullscreen(!fullscreen);
if (!fullscreen) {
String description = attachments.get(binding.mediaViewpager.getCurrentItem()).description;
if (handler != null) {
handler.removeCallbacksAndMessages(null);
}
handler = new Handler();
if (description != null && description.trim().length() > 0 && description.trim().compareTo("null") != 0) {
binding.mediaDescription.setText(description);
}
}
}
} else {
binding.translate.setVisibility(View.GONE);
if (status != null) {
binding.originalMessage.setVisibility(View.VISIBLE);
} else {
binding.originalMessage.setVisibility(View.INVISIBLE);
}
binding.mediaDescriptionTranslated.setVisibility(View.GONE);
binding.mediaDescription.setVisibility(View.GONE);
}
} else {
binding.originalMessage.setVisibility(View.INVISIBLE);
binding.translate.setVisibility(View.GONE);
binding.mediaDescriptionTranslated.setVisibility(View.GONE);
binding.mediaDescription.setVisibility(View.GONE);
break;
}
try {
return super.dispatchTouchEvent(event);
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
}
return false;
}
private boolean isAClick(float startX, float endX, float startY, float endY) {
float differenceX = Math.abs(startX - endX);
float differenceY = Math.abs(startY - endY);
int CLICK_ACTION_THRESHOLD = 200;
return !(differenceX > CLICK_ACTION_THRESHOLD/* =5 */ || differenceY > CLICK_ACTION_THRESHOLD);
}
@Override
public void onDestroy() {
binding = null;
unregisterReceiver(onDownloadComplete);
super.onDestroy();
}
@ -375,15 +315,6 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
super.onPostResume();
}
// Shows the system bars by removing all the flags
// except for the ones that make the content appear under the system bars.
private void showSystemUI() {
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(flags |
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
public boolean getFullScreen() {
return this.fullscreen;
@ -394,17 +325,10 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
if (!fullscreen) {
showSystemUI();
binding.mediaDescription.setVisibility(View.VISIBLE);
binding.translate.setVisibility(View.VISIBLE);
if (mediaFromProfile) {
binding.originalMessage.setVisibility(View.VISIBLE);
}
} else {
hideSystemUI();
binding.mediaDescription.setVisibility(View.GONE);
binding.translate.setVisibility(View.GONE);
binding.originalMessage.setVisibility(View.INVISIBLE);
hideSystemUI();
}
toggleScreenContain(fullscreen);
}
private void hideSystemUI() {
@ -424,22 +348,28 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
| View.SYSTEM_UI_FLAG_FULLSCREEN);
}
public FragmentMedia getCurrentFragment() {
return mCurrentFragment;
// Shows the system bars by removing all the flags
// except for the ones that make the content appear under the system bars.
private void showSystemUI() {
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
/**
* Media Pager
*/
@SuppressWarnings("deprecation")
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
ScreenSlidePagerAdapter(FragmentManager fm) {
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
private class ScreenSlidePagerAdapter extends FragmentStateAdapter {
ScreenSlidePagerAdapter(FragmentActivity fa) {
super(fa);
}
@NotNull
@NonNull
@Override
public Fragment getItem(int position) {
public Fragment createFragment(int position) {
Bundle bundle = new Bundle();
FragmentMedia mediaSliderFragment = new FragmentMedia();
bundle.putInt(Helper.ARG_MEDIA_POSITION, position);
@ -449,15 +379,7 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
}
@Override
public void setPrimaryItem(@NotNull ViewGroup container, int position, @NotNull Object object) {
if (getCurrentFragment() != object) {
mCurrentFragment = ((FragmentMedia) object);
}
super.setPrimaryItem(container, position, object);
}
@Override
public int getCount() {
public int getItemCount() {
return attachments.size();
}
}

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.activities;
package app.fedilab.android.activities;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
@ -16,6 +16,7 @@ package app.fedilab.android.mastodon.activities;
import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.text.method.LinkMovementMethod;
@ -24,6 +25,7 @@ import android.view.View;
import android.widget.TextView;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider;
import java.util.ArrayList;
@ -31,27 +33,29 @@ import java.util.List;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.api.Account;
import app.fedilab.android.client.entities.api.Status;
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.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 app.fedilab.android.helper.CrossActionHelper;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.MastodonHelper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.viewmodel.mastodon.AccountsVM;
public class PartnerShipActivity extends BaseBarActivity {
public class PartnerShipActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyThemeBar(this);
ActivityPartnershipBinding binding = ActivityPartnershipBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
TextView about_partnership = findViewById(R.id.about_partnership);
@ -61,8 +65,9 @@ public class PartnerShipActivity extends BaseBarActivity {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://masto.host"));
startActivity(browserIntent);
});
binding.accountFollow.setBackgroundTintList(ThemeHelper.getButtonActionColorStateList(PartnerShipActivity.this));
setTitle(R.string.action_partnership);
binding.accountFollow.setIconResource(R.drawable.ic_baseline_person_add_24);
binding.accountFollow.setImageResource(R.drawable.ic_baseline_person_add_24);
CrossActionHelper.fetchRemoteAccount(PartnerShipActivity.this, "@mastohost@mastodon.social", new CrossActionHelper.Callback() {
@Override
public void federatedStatus(Status status) {
@ -93,7 +98,7 @@ public class PartnerShipActivity extends BaseBarActivity {
if (relationShips != null && relationShips.size() > 0) {
if (!relationShips.get(0).following) {
binding.accountFollow.setVisibility(View.VISIBLE);
binding.accountFollow.setOnClickListener(v -> accountsVM.follow(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, true, false, null)
binding.accountFollow.setOnClickListener(v -> accountsVM.follow(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, true, false)
.observe(PartnerShipActivity.this, relationShip -> binding.accountFollow.setVisibility(View.GONE)));
}
}

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.activities;
package app.fedilab.android.activities;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
@ -15,44 +15,39 @@ package app.fedilab.android.mastodon.activities;
* see <http://www.gnu.org/licenses>. */
import android.app.Dialog;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.preference.PreferenceManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import app.fedilab.android.R;
import app.fedilab.android.databinding.ActivityProxyBinding;
public class ProxyActivity extends DialogFragment {
public class ProxyActivity extends BaseActivity {
private ActivityProxyBinding binding;
private int position;
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(ProxyActivity.this);
binding = ActivityProxyBinding.inflate(getLayoutInflater());
MaterialAlertDialogBuilder materialAlertDialogBuilder = new MaterialAlertDialogBuilder(requireContext());
materialAlertDialogBuilder.setView(binding.getRoot());
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity());
setContentView(binding.getRoot());
getWindow().setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
if (getSupportActionBar() != null)
getSupportActionBar().hide();
//Enable proxy
boolean enable_proxy = sharedpreferences.getBoolean(getString(R.string.SET_PROXY_ENABLED), false);
binding.enableProxy.setChecked(enable_proxy);
position = sharedpreferences.getInt(getString(R.string.SET_PROXY_TYPE), 0);
position = 0;
String hostVal = sharedpreferences.getString(getString(R.string.SET_PROXY_HOST), "127.0.0.1");
int portVal = sharedpreferences.getInt(getString(R.string.SET_PROXY_PORT), 8118);
final String login = sharedpreferences.getString(getString(R.string.SET_PROXY_LOGIN), null);
@ -67,17 +62,22 @@ public class ProxyActivity extends DialogFragment {
if (pwd != null && binding.proxyPassword.length() > 0) {
binding.proxyPassword.setText(pwd);
}
if (position == 1) binding.protocol.check(R.id.protocol_socks);
binding.protocol.addOnButtonCheckedListener((group, checkedId, isChecked) -> {
if (isChecked) {
if (checkedId == R.id.protocol_http)
position = 0;
else
position = 1;
ArrayAdapter<CharSequence> adapterTrans = ArrayAdapter.createFromResource(ProxyActivity.this,
R.array.proxy_type_choice, android.R.layout.simple_spinner_dropdown_item);
binding.type.setAdapter(adapterTrans);
binding.type.setSelection(sharedpreferences.getInt(getString(R.string.SET_PROXY_TYPE), 0), false);
binding.type.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int p, long id) {
position = p;
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
materialAlertDialogBuilder.setPositiveButton(R.string.save, (dialog1, which) -> {
binding.setProxySave.setOnClickListener(view -> {
String hostVal1 = binding.host.getText().toString().trim();
String portVal1 = binding.port.getText().toString().trim();
String proxy_loginVal = binding.proxyLogin.getText().toString().trim();
@ -91,24 +91,18 @@ public class ProxyActivity extends DialogFragment {
editor.putString(getString(R.string.SET_PROXY_LOGIN), proxy_loginVal);
editor.putString(getString(R.string.SET_PROXY_PASSWORD), proxy_passwordVal);
editor.apply();
finish();
});
materialAlertDialogBuilder.setNeutralButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
Dialog dialog = materialAlertDialogBuilder.create();
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
return dialog;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.activities;
package app.fedilab.android.activities;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
@ -15,14 +15,19 @@ package app.fedilab.android.mastodon.activities;
* see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.helper.PinnedTimelineHelper.sortMenuItem;
import static app.fedilab.android.helper.PinnedTimelineHelper.sortPositionAsc;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.text.Editable;
import android.text.InputFilter;
import android.text.TextWatcher;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
@ -30,6 +35,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.PreferenceManager;
@ -37,31 +43,31 @@ import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.snackbar.Snackbar;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.client.entities.app.BottomMenu;
import app.fedilab.android.client.entities.app.InstanceSocial;
import app.fedilab.android.client.entities.app.Pinned;
import app.fedilab.android.client.entities.app.PinnedTimeline;
import app.fedilab.android.client.entities.app.RemoteInstance;
import app.fedilab.android.client.entities.app.Timeline;
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.InstanceSocial;
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.RemoteInstance;
import app.fedilab.android.mastodon.client.entities.app.Timeline;
import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.PinnedTimelineHelper;
import app.fedilab.android.mastodon.helper.itemtouchhelper.OnStartDragListener;
import app.fedilab.android.mastodon.helper.itemtouchhelper.SimpleItemTouchHelperCallback;
import app.fedilab.android.mastodon.ui.drawer.ReorderBottomMenuAdapter;
import app.fedilab.android.mastodon.ui.drawer.ReorderTabAdapter;
import app.fedilab.android.mastodon.viewmodel.mastodon.InstanceSocialVM;
import app.fedilab.android.mastodon.viewmodel.mastodon.ReorderVM;
import app.fedilab.android.exception.DBException;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.helper.itemtouchhelper.OnStartDragListener;
import app.fedilab.android.helper.itemtouchhelper.OnUndoListener;
import app.fedilab.android.helper.itemtouchhelper.SimpleItemTouchHelperCallback;
import app.fedilab.android.ui.drawer.ReorderBottomMenuAdapter;
import app.fedilab.android.ui.drawer.ReorderTabAdapter;
import app.fedilab.android.viewmodel.mastodon.InstanceSocialVM;
import app.fedilab.android.viewmodel.mastodon.ReorderVM;
import es.dmoral.toasty.Toasty;
import okhttp3.Call;
import okhttp3.Callback;
@ -72,7 +78,7 @@ import okhttp3.RequestBody;
import okhttp3.Response;
public class ReorderTimelinesActivity extends BaseBarActivity implements OnStartDragListener {
public class ReorderTimelinesActivity extends BaseActivity implements OnStartDragListener, OnUndoListener {
private ItemTouchHelper touchHelper;
@ -86,7 +92,6 @@ public class ReorderTimelinesActivity extends BaseBarActivity implements OnStart
private boolean changes;
private boolean bottomChanges;
private boolean update;
public void setChanges(boolean changes) {
this.changes = changes;
}
@ -98,21 +103,15 @@ public class ReorderTimelinesActivity extends BaseBarActivity implements OnStart
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyThemeBar(this);
binding = ActivityReorderTabsBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
searchInstanceRunning = false;
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(ReorderTimelinesActivity.this);
boolean singleBar = sharedpreferences.getBoolean(getString(R.string.SET_USE_SINGLE_TOPBAR), false);
if (singleBar) {
binding.titleBottom.setVisibility(View.GONE);
binding.lvReorderBottom.setVisibility(View.GONE);
binding.titleTop.setVisibility(View.GONE);
}
changes = false;
bottomChanges = false;
ReorderVM reorderVM = new ViewModelProvider(ReorderTimelinesActivity.this).get(ReorderVM.class);
@ -124,8 +123,8 @@ public class ReorderTimelinesActivity extends BaseBarActivity implements OnStart
this.pinned.pinnedTimelines = new ArrayList<>();
update = false;
}
PinnedTimelineHelper.sortPositionAsc(this.pinned.pinnedTimelines);
reorderTabAdapter = new ReorderTabAdapter(this.pinned, ReorderTimelinesActivity.this);
sortPositionAsc(this.pinned.pinnedTimelines);
reorderTabAdapter = new ReorderTabAdapter(this.pinned, ReorderTimelinesActivity.this, ReorderTimelinesActivity.this);
ItemTouchHelper.Callback callback =
new SimpleItemTouchHelperCallback(reorderTabAdapter);
touchHelper = new ItemTouchHelper(callback);
@ -140,7 +139,7 @@ public class ReorderTimelinesActivity extends BaseBarActivity implements OnStart
this.bottomMenu = new BottomMenu(getApplicationContext()).defaultBottomMenu();
this.bottomMenu.bottom_menu = new ArrayList<>();
}
PinnedTimelineHelper.sortMenuItem(this.bottomMenu.bottom_menu);
sortMenuItem(this.bottomMenu.bottom_menu);
reorderBottomMenuAdapter = new ReorderBottomMenuAdapter(this.bottomMenu, ReorderTimelinesActivity.this);
ItemTouchHelper.Callback callback =
new SimpleItemTouchHelperCallback(reorderBottomMenuAdapter);
@ -171,7 +170,7 @@ public class ReorderTimelinesActivity extends BaseBarActivity implements OnStart
}
private void addInstance() {
AlertDialog.Builder dialogBuilder = new MaterialAlertDialogBuilder(ReorderTimelinesActivity.this);
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(ReorderTimelinesActivity.this, Helper.dialogStyle());
PopupSearchInstanceBinding popupSearchInstanceBinding = PopupSearchInstanceBinding.inflate(getLayoutInflater());
dialogBuilder.setView(popupSearchInstanceBinding.getRoot());
TextWatcher textWatcher = autoComplete(popupSearchInstanceBinding);
@ -187,6 +186,7 @@ public class ReorderTimelinesActivity extends BaseBarActivity implements OnStart
popupSearchInstanceBinding.searchInstance.addTextChangedListener(textWatcher);
}
});
popupSearchInstanceBinding.searchInstance.setFilters(new InputFilter[]{new InputFilter.LengthFilter(60)});
dialogBuilder.setPositiveButton(R.string.validate, (dialog, id) -> {
String instanceName = popupSearchInstanceBinding.searchInstance.getText().toString().trim().replace("@", "");
new Thread(() -> {
@ -208,7 +208,7 @@ public class ReorderTimelinesActivity extends BaseBarActivity implements OnStart
} 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", "") + "/rss";
url = "https://" + nitterHost + "/" + instanceName.replaceAll("\\s", "") + "/rss";
}
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
@ -262,10 +262,7 @@ public class ReorderTimelinesActivity extends BaseBarActivity implements OnStart
pinnedTimeline.type = Timeline.TimeLineEnum.REMOTE;
pinnedTimeline.position = pinned.pinnedTimelines.size();
pinned.pinnedTimelines.add(pinnedTimeline);
if (pinned.user_id == null || pinned.instance == null) {
pinned.user_id = MainActivity.currentUserID;
pinned.instance = MainActivity.currentInstance;
}
if (update) {
try {
new Pinned(ReorderTimelinesActivity.this).updatePinned(pinned);
@ -389,6 +386,51 @@ public class ReorderTimelinesActivity extends BaseBarActivity implements OnStart
}
@Override
public void onUndo(PinnedTimeline pinnedTimeline, int position) {
String text = "";
switch (pinnedTimeline.type) {
case TAG:
text = getString(R.string.reorder_tag_removed);
break;
case REMOTE:
text = getString(R.string.reorder_instance_removed);
break;
case LIST:
text = getString(R.string.reorder_list_deleted);
break;
}
Runnable runnable = () -> {
//change position of pinned that are after the removed item
for (int i = pinnedTimeline.position + 1; i < pinned.pinnedTimelines.size(); i++) {
pinned.pinnedTimelines.get(i).position -= 1;
}
pinned.pinnedTimelines.remove(pinnedTimeline);
reorderTabAdapter.notifyItemRemoved(position);
try {
new Pinned(ReorderTimelinesActivity.this).updatePinned(pinned);
changes = true;
} catch (DBException e) {
e.printStackTrace();
}
};
Handler handler = new Handler();
handler.postDelayed(runnable, 4000);
Snackbar.make(binding.getRoot(), text, 4000)
.setAction(getString(R.string.undo), view -> {
pinned.pinnedTimelines.add(position, pinnedTimeline);
reorderTabAdapter.notifyItemInserted(position);
handler.removeCallbacks(runnable);
})
.setTextColor(ThemeHelper.getAttColor(ReorderTimelinesActivity.this, R.attr.mTextColor))
.setActionTextColor(ContextCompat.getColor(ReorderTimelinesActivity.this, R.color.cyanea_accent_reference))
.setBackgroundTint(ContextCompat.getColor(ReorderTimelinesActivity.this, R.color.cyanea_primary_dark_reference))
.show();
}
@Override
public void onStop() {
super.onStop();

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.activities;
package app.fedilab.android.activities;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
@ -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 android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
@ -21,6 +22,7 @@ import android.widget.RadioButton;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.LinearLayoutCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider;
@ -31,19 +33,20 @@ import java.util.List;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.api.Account;
import app.fedilab.android.client.entities.api.RelationShip;
import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.client.entities.app.Timeline;
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.Timeline;
import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.ui.drawer.RulesAdapter;
import app.fedilab.android.mastodon.ui.fragment.timeline.FragmentMastodonTimeline;
import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.ui.drawer.RulesAdapter;
import app.fedilab.android.ui.fragment.timeline.FragmentMastodonTimeline;
import app.fedilab.android.viewmodel.mastodon.AccountsVM;
import es.dmoral.toasty.Toasty;
public class ReportActivity extends BaseBarActivity {
public class ReportActivity extends BaseActivity {
private ActivityReportBinding binding;
private Status status;
@ -61,13 +64,14 @@ public class ReportActivity extends BaseBarActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyThemeBar(this);
binding = ActivityReportBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
Bundle b = getIntent().getExtras();

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.activities;
package app.fedilab.android.activities;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
@ -16,21 +16,21 @@ package app.fedilab.android.mastodon.activities;
import static app.fedilab.android.BaseMainActivity.currentAccount;
import android.content.SharedPreferences;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.MenuItem;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.preference.PreferenceManager;
import androidx.core.content.ContextCompat;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import app.fedilab.android.R;
import app.fedilab.android.databinding.ActivityScheduledBinding;
import app.fedilab.android.mastodon.helper.MastodonHelper;
import app.fedilab.android.mastodon.ui.pageadapter.FedilabScheduledPageAdapter;
import app.fedilab.android.helper.MastodonHelper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.ui.pageadapter.FedilabScheduledPageAdapter;
public class ScheduledActivity extends BaseActivity {
@ -39,7 +39,7 @@ public class ScheduledActivity extends BaseActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyTheme(this);
binding = ActivityScheduledBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
@ -47,39 +47,40 @@ public class ScheduledActivity extends BaseActivity {
//Remove title
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
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, 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)));
binding.scheduleTablayout.addTab(binding.scheduleTablayout.newTab().setText(getString(R.string.reblog)));
binding.scheduleTablayout.addTab(binding.scheduleTablayout.newTab());
binding.scheduleTablayout.addTab(binding.scheduleTablayout.newTab());
binding.scheduleTablayout.addTab(binding.scheduleTablayout.newTab());
binding.scheduleViewpager.setAdapter(new FedilabScheduledPageAdapter(getSupportFragmentManager()));
binding.scheduleViewpager.setAdapter(new FedilabScheduledPageAdapter(ScheduledActivity.this));
binding.scheduleViewpager.setOffscreenPageLimit(3);
binding.scheduleViewpager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(binding.scheduleTablayout));
binding.scheduleTablayout.setTabTextColors(ThemeHelper.getAttColor(ScheduledActivity.this, R.attr.mTextColor), ContextCompat.getColor(ScheduledActivity.this, R.color.cyanea_accent_dark_reference));
binding.scheduleTablayout.setTabIconTint(ThemeHelper.getColorStateList(ScheduledActivity.this));
binding.scheduleTablayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
binding.scheduleViewpager.setCurrentItem(tab.getPosition());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
new TabLayoutMediator(binding.scheduleTablayout, binding.scheduleViewpager,
(tab, position) -> {
binding.scheduleViewpager.setCurrentItem(tab.getPosition(), true);
switch (position) {
case 0:
tab.setText(getString(R.string.toots_server));
break;
case 1:
tab.setText(getString(R.string.toots_client));
break;
case 2:
tab.setText(getString(R.string.reblog));
break;
}
}
).attach();
}
@Override

View file

@ -0,0 +1,236 @@
package app.fedilab.android.activities;
/* 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.app.SearchManager;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SearchView;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import org.jetbrains.annotations.NotNull;
import app.fedilab.android.R;
import app.fedilab.android.databinding.ActivitySearchResultTabsBinding;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.ui.fragment.timeline.FragmentMastodonAccount;
import app.fedilab.android.ui.fragment.timeline.FragmentMastodonTag;
import app.fedilab.android.ui.fragment.timeline.FragmentMastodonTimeline;
import es.dmoral.toasty.Toasty;
public class SearchResultTabActivity extends BaseActivity {
private String search;
private ActivitySearchResultTabsBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyThemeBar(this);
binding = ActivitySearchResultTabsBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
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();
finish();
return;
}
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
setTitle(search);
binding.searchTabLayout.addTab(binding.searchTabLayout.newTab());
binding.searchTabLayout.addTab(binding.searchTabLayout.newTab());
binding.searchTabLayout.addTab(binding.searchTabLayout.newTab());
binding.searchTabLayout.addTab(binding.searchTabLayout.newTab());
binding.searchTabLayout.setTabTextColors(ThemeHelper.getAttColor(SearchResultTabActivity.this, R.attr.mTextColor), ContextCompat.getColor(SearchResultTabActivity.this, R.color.cyanea_accent_dark_reference));
binding.searchTabLayout.setTabIconTint(ThemeHelper.getColorStateList(SearchResultTabActivity.this));
ScreenSlidePagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(SearchResultTabActivity.this);
binding.searchViewpager.setAdapter(mPagerAdapter);
binding.searchViewpager.setSaveEnabled(false);
binding.searchViewpager.setOffscreenPageLimit(3);
new TabLayoutMediator(binding.searchTabLayout, binding.searchViewpager,
(tab, position) -> {
binding.searchViewpager.setCurrentItem(tab.getPosition(), true);
switch (position) {
case 0:
tab.setText(getString(R.string.tags));
break;
case 1:
tab.setText(getString(R.string.accounts));
break;
case 2:
tab.setText(getString(R.string.toots));
break;
case 3:
tab.setText(getString(R.string.action_cache));
break;
}
}
).attach();
binding.searchTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
binding.searchViewpager.setCurrentItem(tab.getPosition());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
Fragment fragment = getSupportFragmentManager().findFragmentByTag("f" + binding.searchViewpager.getCurrentItem());
if (fragment instanceof FragmentMastodonAccount) {
FragmentMastodonAccount fragmentMastodonAccount = ((FragmentMastodonAccount) fragment);
fragmentMastodonAccount.scrollToTop();
} else if (fragment instanceof FragmentMastodonTimeline) {
FragmentMastodonTimeline fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
fragmentMastodonTimeline.scrollToTop();
} else if (fragment instanceof FragmentMastodonTag) {
FragmentMastodonTag fragmentMastodonTag = ((FragmentMastodonTag) fragment);
fragmentMastodonTag.scrollToTop();
}
}
});
}
@Override
public boolean onCreateOptionsMenu(@NonNull Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_search, menu);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
searchView.setIconifiedByDefault(false);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
assert imm != null;
imm.hideSoftInputFromWindow(binding.searchTabLayout.getWindowToken(), 0);
query = query.replaceAll("^#+", "");
search = query.trim();
ScreenSlidePagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(SearchResultTabActivity.this);
binding.searchViewpager.setAdapter(mPagerAdapter);
searchView.clearFocus();
setTitle(search);
searchView.setIconified(true);
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
return false;
}
});
searchView.setOnCloseListener(() -> {
setTitle(search);
return false;
});
searchView.setOnSearchClickListener(v -> {
searchView.setQuery(search, false);
searchView.setIconified(false);
});
return true;
}
@Override
public boolean onOptionsItemSelected(@NotNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
} else if (item.getItemId() == R.id.action_search) {
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* Pager adapter for the 4 fragments
*/
private class ScreenSlidePagerAdapter extends FragmentStateAdapter {
ScreenSlidePagerAdapter(FragmentActivity fa) {
super(fa);
}
@NonNull
@Override
public Fragment createFragment(int position) {
Bundle bundle = new Bundle();
switch (position) {
case 0:
FragmentMastodonTag fragmentMastodonTag = new FragmentMastodonTag();
bundle.putString(Helper.ARG_SEARCH_KEYWORD, search);
fragmentMastodonTag.setArguments(bundle);
return fragmentMastodonTag;
case 1:
FragmentMastodonAccount fragmentMastodonAccount = new FragmentMastodonAccount();
bundle.putString(Helper.ARG_SEARCH_KEYWORD, search);
fragmentMastodonAccount.setArguments(bundle);
return fragmentMastodonAccount;
case 2:
FragmentMastodonTimeline fragmentMastodonTimeline = new FragmentMastodonTimeline();
bundle.putString(Helper.ARG_SEARCH_KEYWORD, search);
fragmentMastodonTimeline.setArguments(bundle);
return fragmentMastodonTimeline;
default:
fragmentMastodonTimeline = new FragmentMastodonTimeline();
bundle.putString(Helper.ARG_SEARCH_KEYWORD_CACHE, search);
fragmentMastodonTimeline.setArguments(bundle);
return fragmentMastodonTimeline;
}
}
@Override
public int getItemCount() {
return 4;
}
}
}

View file

@ -0,0 +1,208 @@
package app.fedilab.android.activities;
/* 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.BaseMainActivity.currentAccount;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.google.gson.annotations.SerializedName;
import java.util.Locale;
import app.fedilab.android.R;
import app.fedilab.android.databinding.ActivitySettingsBinding;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.ui.fragment.settings.FragmentComposeSettings;
import app.fedilab.android.ui.fragment.settings.FragmentInterfaceSettings;
import app.fedilab.android.ui.fragment.settings.FragmentLanguageSettings;
import app.fedilab.android.ui.fragment.settings.FragmentNotificationsSettings;
import app.fedilab.android.ui.fragment.settings.FragmentPrivacySettings;
import app.fedilab.android.ui.fragment.settings.FragmentThemingSettings;
import app.fedilab.android.ui.fragment.settings.FragmentTimelinesSettings;
public class SettingsActivity extends BaseActivity {
private ActivitySettingsBinding binding;
private boolean canGoBack;
private Fragment currentFragment;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyThemeBar(this);
binding = ActivitySettingsBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
canGoBack = false;
binding.setTimelines.setOnClickListener(v -> displaySettings(SettingsEnum.TIMELINES));
binding.setNotifications.setOnClickListener(v -> displaySettings(SettingsEnum.NOTIFICATIONS));
binding.setInterface.setOnClickListener(v -> displaySettings(SettingsEnum.INTERFACE));
binding.setCompose.setOnClickListener(v -> displaySettings(SettingsEnum.COMPOSE));
binding.setPrivacy.setOnClickListener(v -> displaySettings(SettingsEnum.PRIVACY));
binding.setTheming.setOnClickListener(v -> displaySettings(SettingsEnum.THEMING));
binding.setAdministration.setOnClickListener(v -> displaySettings(SettingsEnum.ADMINISTRATION));
binding.setLanguage.setOnClickListener(v -> displaySettings(SettingsEnum.LANGUAGE));
if (currentAccount.admin) {
binding.setAdministration.setVisibility(View.VISIBLE);
} else {
binding.setAdministration.setVisibility(View.GONE);
}
}
public void displaySettings(SettingsEnum settingsEnum) {
ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
String category = "";
switch (settingsEnum) {
case TIMELINES:
FragmentTimelinesSettings fragmentTimelinesSettings = new FragmentTimelinesSettings();
fragmentTransaction.replace(R.id.fragment_container, fragmentTimelinesSettings);
currentFragment = fragmentTimelinesSettings;
category = getString(R.string.settings_category_label_timelines);
break;
case NOTIFICATIONS:
FragmentNotificationsSettings fragmentNotificationsSettings = new FragmentNotificationsSettings();
fragmentTransaction.replace(R.id.fragment_container, fragmentNotificationsSettings);
currentFragment = fragmentNotificationsSettings;
category = getString(R.string.notifications);
break;
case INTERFACE:
FragmentInterfaceSettings fragmentInterfaceSettings = new FragmentInterfaceSettings();
fragmentTransaction.replace(R.id.fragment_container, fragmentInterfaceSettings);
currentFragment = fragmentInterfaceSettings;
category = getString(R.string.settings_category_label_interface);
break;
case COMPOSE:
FragmentComposeSettings fragmentComposeSettings = new FragmentComposeSettings();
fragmentTransaction.replace(R.id.fragment_container, fragmentComposeSettings);
currentFragment = fragmentComposeSettings;
category = getString(R.string.compose);
break;
case PRIVACY:
FragmentPrivacySettings fragmentPrivacySettings = new FragmentPrivacySettings();
fragmentTransaction.replace(R.id.fragment_container, fragmentPrivacySettings);
currentFragment = fragmentPrivacySettings;
category = getString(R.string.action_privacy);
break;
case THEMING:
FragmentThemingSettings fragmentThemingSettings = new FragmentThemingSettings();
fragmentTransaction.replace(R.id.fragment_container, fragmentThemingSettings);
currentFragment = fragmentThemingSettings;
category = getString(R.string.theming);
break;
case LANGUAGE:
FragmentLanguageSettings fragmentLanguageSettings = new FragmentLanguageSettings();
fragmentTransaction.replace(R.id.fragment_container, fragmentLanguageSettings);
currentFragment = fragmentLanguageSettings;
category = getString(R.string.languages);
break;
}
String title = String.format(Locale.getDefault(), "%s - %s", getString(R.string.settings), category);
setTitle(title);
canGoBack = true;
fragmentTransaction.commit();
});
}
@Override
public void onBackPressed() {
if (canGoBack) {
canGoBack = false;
ThemeHelper.slideViewsToRight(binding.fragmentContainer, binding.buttonContainer, () -> {
if (currentFragment != null) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.remove(currentFragment).commit();
}
});
setTitle(R.string.settings);
} else {
super.onBackPressed();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (currentFragment != null) {
currentFragment.onDestroy();
}
binding = null;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
public enum SettingsEnum {
@SerializedName("TIMELINES")
TIMELINES("TIMELINES"),
@SerializedName("NOTIFICATIONS")
NOTIFICATIONS("NOTIFICATIONS"),
@SerializedName("INTERFACE")
INTERFACE("INTERFACE"),
@SerializedName("COMPOSE")
COMPOSE("COMPOSE"),
@SerializedName("PRIVACY")
PRIVACY("PRIVACY"),
@SerializedName("THEMING")
THEMING("THEMING"),
@SerializedName("ADMINISTRATION")
ADMINISTRATION("ADMINISTRATION"),
@SerializedName("LANGUAGE")
LANGUAGE("LANGUAGE");
private final String value;
SettingsEnum(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
}

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.activities;
package app.fedilab.android.activities;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -15,11 +15,13 @@ package app.fedilab.android.mastodon.activities;
* see <http://www.gnu.org/licenses>. */
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -29,15 +31,14 @@ import java.util.List;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.api.Account;
import app.fedilab.android.client.entities.api.Accounts;
import app.fedilab.android.client.entities.api.Status;
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.helper.Helper;
import app.fedilab.android.mastodon.ui.drawer.AccountAdapter;
import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.mastodon.viewmodel.mastodon.StatusesVM;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.ui.drawer.AccountAdapter;
import app.fedilab.android.viewmodel.mastodon.StatusesVM;
public class StatusInfoActivity extends BaseActivity {
@ -53,13 +54,14 @@ public class StatusInfoActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyTheme(this);
binding = ActivityStatusInfoBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
accountList = new ArrayList<>();
Bundle b = getIntent().getExtras();
@ -113,8 +115,6 @@ public class StatusInfoActivity extends BaseActivity {
private void manageView(Accounts accounts) {
binding.loadingNextAccounts.setVisibility(View.GONE);
if (accountList != null && accounts != null && accounts.accounts != null) {
int position = this.accountList.size();
fetchRelationShip(accounts.accounts, position);
int startId = 0;
//There are some statuses present in the timeline
if (accountList.size() > 0) {
@ -127,27 +127,6 @@ public class StatusInfoActivity extends BaseActivity {
}
}
private void fetchRelationShip(List<Account> accounts, int position) {
List<String> ids = new ArrayList<>();
for (Account account : accounts) {
ids.add(account.id);
}
AccountsVM accountsVM = new ViewModelProvider(StatusInfoActivity.this).get(AccountsVM.class);
accountsVM.getRelationships(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, ids)
.observe(StatusInfoActivity.this, relationShips -> {
if (relationShips != null) {
for (RelationShip relationShip : relationShips) {
for (Account account : accounts) {
if (account.id.compareToIgnoreCase(relationShip.id) == 0) {
account.relationShip = relationShip;
}
}
}
accountAdapter.notifyItemRangeChanged(position, accounts.size());
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {

View file

@ -1,5 +1,5 @@
package app.fedilab.android.peertube.activities;
/* Copyright 2023 Thomas Schneider
package app.fedilab.android.activities;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
*
@ -14,47 +14,74 @@ package app.fedilab.android.peertube.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.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.webkit.WebView;
import android.widget.FrameLayout;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import app.fedilab.android.R;
import app.fedilab.android.mastodon.activities.BaseBarActivity;
import app.fedilab.android.peertube.helper.Helper;
import app.fedilab.android.peertube.webview.MastalabWebChromeClient;
import app.fedilab.android.peertube.webview.MastalabWebViewClient;
import app.fedilab.android.databinding.ActivityWebviewBinding;
import app.fedilab.android.helper.CountDrawable;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.webview.CustomWebview;
import app.fedilab.android.webview.FedilabWebChromeClient;
import app.fedilab.android.webview.FedilabWebViewClient;
import es.dmoral.toasty.Toasty;
public class WebviewActivity extends BaseBarActivity {
public class WebviewActivity extends BaseActivity {
private String url;
private boolean peertubeLink;
private WebView webView;
private CustomWebview webView;
private FedilabWebViewClient FedilabWebViewClient;
private ActivityWebviewBinding binding;
private Menu defaultMenu;
@SuppressLint("SetJavaScriptEnabled")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_webview_peertube);
ThemeHelper.applyTheme(this);
binding = ActivityWebviewBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
setSupportActionBar(binding.toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(false);
}
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
Bundle b = getIntent().getExtras();
if (b != null) {
url = b.getString("url", null);
@ -67,16 +94,15 @@ public class WebviewActivity extends BaseBarActivity {
webView = Helper.initializeWebview(WebviewActivity.this, R.id.webview, null);
setTitle("");
FrameLayout webview_container = findViewById(R.id.webview_container);
final ViewGroup videoLayout = findViewById(R.id.videoLayout); // Your own view, read class comments
webView.getSettings().setJavaScriptEnabled(true);
MastalabWebChromeClient mastalabWebChromeClient = new MastalabWebChromeClient(WebviewActivity.this, webView, webview_container, videoLayout);
mastalabWebChromeClient.setOnToggledFullscreen(fullscreen -> {
FedilabWebChromeClient FedilabWebChromeClient = new FedilabWebChromeClient(WebviewActivity.this, webView, binding.webviewContainer, binding.videoLayout);
FedilabWebChromeClient.setOnToggledFullscreen(fullscreen -> {
if (fullscreen) {
videoLayout.setVisibility(View.VISIBLE);
binding.videoLayout.setVisibility(View.VISIBLE);
WindowManager.LayoutParams attrs = getWindow().getAttributes();
attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
attrs.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
@ -88,12 +114,12 @@ public class WebviewActivity extends BaseBarActivity {
attrs.flags &= ~WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
getWindow().setAttributes(attrs);
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
videoLayout.setVisibility(View.GONE);
binding.videoLayout.setVisibility(View.GONE);
}
});
webView.setWebChromeClient(mastalabWebChromeClient);
MastalabWebViewClient mastalabWebViewClient = new MastalabWebViewClient(WebviewActivity.this);
webView.setWebViewClient(mastalabWebViewClient);
webView.setWebChromeClient(FedilabWebChromeClient);
FedilabWebViewClient = new FedilabWebViewClient(WebviewActivity.this);
webView.setWebViewClient(FedilabWebViewClient);
webView.setDownloadListener((url, userAgent, contentDisposition, mimetype, contentLength) -> {
if (Build.VERSION.SDK_INT >= 23) {
@ -109,19 +135,46 @@ public class WebviewActivity extends BaseBarActivity {
if (!url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://"))
url = "http://" + url;
webView.loadUrl(url);
}
public void setCount(Context context, String count) {
if (defaultMenu != null && !peertubeLink) {
MenuItem menuItem = defaultMenu.findItem(R.id.action_block);
LayerDrawable icon = (LayerDrawable) menuItem.getIcon();
CountDrawable badge;
// Reuse drawable if possible
Drawable reuse = icon.findDrawableByLayerId(R.id.ic_block_count);
if (reuse instanceof CountDrawable) {
badge = (CountDrawable) reuse;
} else {
badge = new CountDrawable(context);
}
badge.setCount(count);
icon.mutate();
icon.setDrawableByLayerId(R.id.ic_block_count, badge);
}
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (!peertubeLink)
setCount(WebviewActivity.this, "0");
defaultMenu = menu;
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onCreateOptionsMenu(@NotNull Menu menu) {
getMenuInflater().inflate(R.menu.main_webview, menu);
defaultMenu = menu;
if (peertubeLink) {
menu.findItem(R.id.action_go).setVisible(false);
menu.findItem(R.id.action_block).setVisible(false);
}
return true;
}
@ -131,6 +184,27 @@ public class WebviewActivity extends BaseBarActivity {
int itemId = item.getItemId();
if (itemId == android.R.id.home) {
finish();
return true;
} else if (itemId == R.id.action_block) {
List<String> domains = FedilabWebViewClient.getDomains();
final ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(WebviewActivity.this, R.layout.domains_blocked);
arrayAdapter.addAll(domains);
AlertDialog.Builder builder = new AlertDialog.Builder(WebviewActivity.this, Helper.dialogStyle());
builder.setTitle(R.string.list_of_blocked_domains);
builder.setNegativeButton(R.string.close, (dialog, which) -> dialog.dismiss());
builder.setAdapter(arrayAdapter, (dialog, which) -> {
String strName = arrayAdapter.getItem(which);
assert strName != null;
Toasty.info(WebviewActivity.this, strName, Toast.LENGTH_LONG).show();
});
builder.show();
return true;
} else if (itemId == R.id.action_go) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));

View file

@ -0,0 +1,266 @@
/* Copyright 2021 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>. */
package app.fedilab.android.activities;
import static app.fedilab.android.activities.LoginActivity.apiLogin;
import static app.fedilab.android.activities.LoginActivity.client_idLogin;
import static app.fedilab.android.activities.LoginActivity.client_secretLogin;
import static app.fedilab.android.activities.LoginActivity.currentInstanceLogin;
import static app.fedilab.android.activities.LoginActivity.softwareLogin;
import static app.fedilab.android.helper.Helper.PREF_USER_TOKEN;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;
import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.PreferenceManager;
import java.util.regex.Matcher;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.app.Account;
import app.fedilab.android.databinding.ActivityWebviewConnectBinding;
import app.fedilab.android.exception.DBException;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.viewmodel.mastodon.AdminVM;
import app.fedilab.android.viewmodel.mastodon.OauthVM;
import es.dmoral.toasty.Toasty;
public class WebviewConnectActivity extends BaseActivity {
private ActivityWebviewConnectBinding binding;
private AlertDialog alert;
private String login_url;
private boolean requestedAdmin;
@SuppressWarnings("deprecation")
public static void clearCookies(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
CookieManager.getInstance().removeAllCookies(null);
CookieManager.getInstance().flush();
} else {
CookieSyncManager cookieSyncMngr = CookieSyncManager.createInstance(context);
cookieSyncMngr.startSync();
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.removeAllCookie();
cookieManager.removeSessionCookie();
cookieSyncMngr.stopSync();
cookieSyncMngr.sync();
}
}
@SuppressLint("ApplySharedPref")
public static void proceedLogin(Activity activity, Account account) {
new Thread(() -> {
try {
//update the database
new Account(activity).insertOrUpdate(account);
Handler mainHandler = new Handler(Looper.getMainLooper());
BaseMainActivity.currentToken = account.token;
BaseMainActivity.currentUserID = account.user_id;
BaseMainActivity.api = Account.API.MASTODON;
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(activity);
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(PREF_USER_TOKEN, account.token);
editor.commit();
//The user is now authenticated, it will be redirected to MainActivity
Runnable myRunnable = () -> {
Intent mainActivity = new Intent(activity, MainActivity.class);
activity.startActivity(mainActivity);
activity.finish();
};
mainHandler.post(myRunnable);
} catch (DBException e) {
e.printStackTrace();
}
}).start();
}
@SuppressLint("SetJavaScriptEnabled")
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.applyTheme(this);
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(WebviewConnectActivity.this);
binding = ActivityWebviewConnectBinding.inflate(getLayoutInflater());
View rootView = binding.getRoot();
setContentView(rootView);
Bundle b = getIntent().getExtras();
if (b != null) {
login_url = b.getString("login_url");
requestedAdmin = b.getBoolean("requestedAdmin", false);
}
if (login_url == null)
finish();
clearCookies(WebviewConnectActivity.this);
binding.webviewConnect.getSettings().setJavaScriptEnabled(true);
String user_agent = sharedpreferences.getString(getString(R.string.SET_CUSTOM_USER_AGENT), Helper.USER_AGENT);
binding.webviewConnect.getSettings().setUserAgentString(user_agent);
CookieManager.getInstance().setAcceptThirdPartyCookies(binding.webviewConnect, true);
final ProgressBar pbar = findViewById(R.id.progress_bar);
binding.webviewConnect.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int progress) {
if (progress < 100 && pbar.getVisibility() == ProgressBar.GONE) {
pbar.setVisibility(ProgressBar.VISIBLE);
}
pbar.setProgress(progress);
if (progress == 100) {
pbar.setVisibility(ProgressBar.GONE);
}
}
});
binding.webviewConnect.setWebViewClient(new WebViewClient() {
/* @Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
String x_xsrf_token = null;
String x_csrf_token = null;
if (request.getUrl().toString().contains("accounts/verify_credentials")) {
String cookies = CookieManager.getInstance().getCookie(request.getUrl().toString());
Map<String, String> requestHeaders = request.getRequestHeaders();
Iterator<Map.Entry<String, String>> it = requestHeaders.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> pair = it.next();
if (pair.getKey().compareTo("X-XSRF-TOKEN") == 0) {
x_xsrf_token = pair.getValue();
}
if (pair.getKey().compareTo("X-CSRF-TOKEN") == 0) {
x_csrf_token = pair.getValue();
}
it.remove();
}
if (x_xsrf_token != null && x_csrf_token != null) {
String finalX_xsrf_token = x_xsrf_token;
String finalX_csrf_token = x_csrf_token;
new Handler(Looper.getMainLooper()).post(() -> {
view.stopLoading();
SharedPreferences sharedpreferences1 = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedpreferences1.edit();
String token = "X-XSRF-TOKEN= " + finalX_xsrf_token + ";X-CSRF-TOKEN= " + finalX_csrf_token + "|" + cookies;
editor.putString(Helper.PREF_KEY_OAUTH_TOKEN, token);
editor.commit();
view.setVisibility(View.GONE);
//Update the account with the token;
new UpdateAccountInfoAsyncTask(WebviewConnectActivity.this, token, clientId, clientSecret, null, instance, social);
});
}
}
return super.shouldInterceptRequest(view, request);
}*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
super.shouldOverrideUrlLoading(view, url);
if (url.contains(Helper.REDIRECT_CONTENT_WEB)) {
Matcher matcher = Helper.codePattern.matcher(url);
if (!matcher.find()) {
return false;
}
String code = matcher.group(1);
OauthVM oauthVM = new ViewModelProvider(WebviewConnectActivity.this).get(OauthVM.class);
//API call to get the user token
String scope = requestedAdmin ? Helper.OAUTH_SCOPES_ADMIN : Helper.OAUTH_SCOPES;
oauthVM.createToken(currentInstanceLogin, "authorization_code", client_idLogin, client_secretLogin, Helper.REDIRECT_CONTENT_WEB, scope, code)
.observe(WebviewConnectActivity.this, tokenObj -> {
Account account = new Account();
account.client_id = client_idLogin;
account.client_secret = client_secretLogin;
account.token = tokenObj.token_type + " " + tokenObj.access_token;
account.api = apiLogin;
account.software = softwareLogin;
account.instance = currentInstanceLogin;
//API call to retrieve account information for the new token
AccountsVM accountsVM = new ViewModelProvider(WebviewConnectActivity.this).get(AccountsVM.class);
accountsVM.getConnectedAccount(currentInstanceLogin, account.token).observe(WebviewConnectActivity.this, mastodonAccount -> {
if (mastodonAccount != null) {
account.mastodon_account = mastodonAccount;
account.user_id = mastodonAccount.id;
//We check if user have really moderator rights
if (requestedAdmin) {
AdminVM adminVM = new ViewModelProvider(WebviewConnectActivity.this).get(AdminVM.class);
adminVM.getAccount(account.instance, account.token, account.user_id).observe(WebviewConnectActivity.this, adminAccount -> {
account.admin = adminAccount != null;
proceedLogin(WebviewConnectActivity.this, account);
});
} else {
proceedLogin(WebviewConnectActivity.this, account);
}
} else {
Toasty.error(WebviewConnectActivity.this, getString(R.string.toast_error), Toasty.LENGTH_SHORT).show();
}
});
});
return true;
} else {
return false;
}
}
});
binding.webviewConnect.loadUrl(login_url);
}
@Override
public void onBackPressed() {
if (binding.webviewConnect.canGoBack()) {
binding.webviewConnect.goBack();
} else {
super.onBackPressed();
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (alert != null) {
alert.dismiss();
alert = null;
}
binding.webviewConnect.destroy();
}
}

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.broadcastreceiver;
package app.fedilab.android.broadcastreceiver;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.broadcastreceiver;
package app.fedilab.android.broadcastreceiver;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -14,12 +14,15 @@ package app.fedilab.android.mastodon.broadcastreceiver;
* 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.helper.Helper.RECEIVE_TOAST_CONTENT;
import static app.fedilab.android.helper.Helper.RECEIVE_TOAST_TYPE;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.helper.Helper;
import es.dmoral.toasty.Toasty;
public class ToastMessage extends BroadcastReceiver {
@ -27,8 +30,8 @@ public class ToastMessage extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
Bundle b = intent.getExtras();
if (b != null) {
String type = b.getString(Helper.RECEIVE_TOAST_TYPE, null);
String content = b.getString(Helper.RECEIVE_TOAST_CONTENT, null);
String type = b.getString(RECEIVE_TOAST_TYPE, null);
String content = b.getString(RECEIVE_TOAST_CONTENT, null);
if (type != null && content != null) {
switch (type) {
case Helper.RECEIVE_TOAST_TYPE_ERROR:

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client;
package app.fedilab.android.client;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -15,7 +15,7 @@ package app.fedilab.android.mastodon.client;
* see <http://www.gnu.org/licenses>. */
import app.fedilab.android.mastodon.client.entities.app.WellKnownNodeinfo;
import app.fedilab.android.client.entities.app.WellKnownNodeinfo;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.endpoints;
package app.fedilab.android.client.endpoints;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -17,7 +17,7 @@ package app.fedilab.android.mastodon.client.endpoints;
import java.util.List;
import app.fedilab.android.mastodon.client.entities.api.JoinMastodonInstance;
import app.fedilab.android.client.entities.api.JoinMastodonInstance;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.endpoints;
package app.fedilab.android.client.endpoints;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -17,18 +17,17 @@ package app.fedilab.android.mastodon.client.endpoints;
import java.util.List;
import app.fedilab.android.mastodon.client.entities.api.Account;
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.IdentityProof;
import app.fedilab.android.mastodon.client.entities.api.MastodonList;
import app.fedilab.android.mastodon.client.entities.api.Preferences;
import app.fedilab.android.mastodon.client.entities.api.RelationShip;
import app.fedilab.android.mastodon.client.entities.api.Report;
import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.api.Suggestion;
import app.fedilab.android.mastodon.client.entities.api.Tag;
import app.fedilab.android.mastodon.client.entities.api.Token;
import app.fedilab.android.client.entities.api.Account;
import app.fedilab.android.client.entities.api.FeaturedTag;
import app.fedilab.android.client.entities.api.Filter;
import app.fedilab.android.client.entities.api.IdentityProof;
import app.fedilab.android.client.entities.api.MastodonList;
import app.fedilab.android.client.entities.api.Preferences;
import app.fedilab.android.client.entities.api.RelationShip;
import app.fedilab.android.client.entities.api.Report;
import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.client.entities.api.Tag;
import app.fedilab.android.client.entities.api.Token;
import okhttp3.MultipartBody;
import retrofit2.Call;
import retrofit2.http.Body;
@ -41,6 +40,7 @@ import retrofit2.http.Headers;
import retrofit2.http.Multipart;
import retrofit2.http.PATCH;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Part;
import retrofit2.http.Path;
import retrofit2.http.Query;
@ -96,7 +96,7 @@ public interface MastodonAccountsService {
@Field("source[privacy]") String privacy,
@Field("source[sensitive]") Boolean sensitive,
@Field("source[language]") String language,
@Field("fields_attributes") List<app.fedilab.android.mastodon.client.entities.api.Field.FieldParams> fields
@Field("fields_attributes") List<app.fedilab.android.client.entities.api.Field.FieldParams> fields
);
//Get Account
@ -106,12 +106,6 @@ public interface MastodonAccountsService {
@Path("id") String id
);
//Get Account
@GET("accounts/lookup")
Call<Account> lookUpAccount(
@Query("acct") String acct
);
//Get Account statuses
@GET("accounts/{id}/statuses")
Call<List<Status>> getAccountStatuses(
@ -159,7 +153,6 @@ public interface MastodonAccountsService {
@Path("id") String id
);
//Get Identity proofs
@GET("accounts/{id}/identity_proofs")
Call<List<IdentityProof>> getIdentityProofs(
@ -174,8 +167,7 @@ public interface MastodonAccountsService {
@Header("Authorization") String app_token,
@Path("id") String id,
@Field("reblogs") boolean reblogs,
@Field("notify") boolean notify,
@Field("languages[]") List<String> languages
@Field("notify") boolean notify
);
//Follow account
@ -255,13 +247,6 @@ public interface MastodonAccountsService {
@Query("id[]") List<String> ids
);
//Get familiar followers
@GET("accounts/familiar_followers ")
Call<List<FamiliarFollowers>> getFamiliarFollowers(
@Header("Authorization") String token,
@Query("id[]") List<String> ids
);
//Get search
@GET("accounts/search")
Call<List<Account>> searchAccounts(
@ -331,9 +316,51 @@ public interface MastodonAccountsService {
@DELETE("domain_blocks")
Call<Void> removeDomainBlocks(
@Header("Authorization") String token,
@Query("domain") String domain
@Field("domain") String domain
);
//Get filters
@GET("filters")
Call<List<Filter>> getFilters(
@Header("Authorization") String token);
//Get a filter with its id
@GET("filters/{id}")
Call<Filter> getFilter(
@Header("Authorization") String token,
@Path("id") String id);
//Add a filter
@FormUrlEncoded
@POST("filters")
Call<Filter> addFilter(
@Header("Authorization") String token,
@Field("phrase") String phrase,
@Field("context[]") List<String> context,
@Field("irreversible") boolean irreversible,
@Field("whole_word") boolean whole_word,
@Field("expires_in") long expires_in
);
//Edit a filter
@FormUrlEncoded
@PUT("filters/{id}")
Call<Filter> editFilter(
@Header("Authorization") String token,
@Path("id") String id,
@Field("phrase") String phrase,
@Field("context[]") List<String> context,
@Field("irreversible") boolean irreversible,
@Field("whole_word") boolean whole_word,
@Field("expires_in") long expires_in
);
//Remove a filter
@DELETE("filters/{id}")
Call<Void> removeFilter(
@Header("Authorization") String token,
@Path("id") String id
);
//Post a report
@Headers({"Accept: application/json"})
@ -407,7 +434,7 @@ public interface MastodonAccountsService {
//Get user suggestions
@GET("suggestions")
Call<List<Suggestion>> getSuggestions(
Call<List<Account>> getSuggestions(
@Header("Authorization") String token,
@Query("limit") String limit
);
@ -418,15 +445,4 @@ public interface MastodonAccountsService {
@Header("Authorization") String token,
@Path("account_id") String account_id
);
//Get user suggestions
@GET("directory")
Call<List<Account>> getDirectory(
@Header("Authorization") String token,
@Query("offset") Integer offset,
@Query("limit") Integer limit,
@Query("order") String order,
@Query("local") Boolean local
);
}

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.endpoints;
package app.fedilab.android.client.endpoints;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -17,17 +17,14 @@ package app.fedilab.android.mastodon.client.endpoints;
import java.util.List;
import app.fedilab.android.mastodon.client.entities.api.admin.AdminAccount;
import app.fedilab.android.mastodon.client.entities.api.admin.AdminDomainBlock;
import app.fedilab.android.mastodon.client.entities.api.admin.AdminReport;
import app.fedilab.android.client.entities.api.AdminAccount;
import app.fedilab.android.client.entities.api.AdminReport;
import retrofit2.Call;
import retrofit2.http.DELETE;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Path;
import retrofit2.http.Query;
@ -60,7 +57,6 @@ public interface MastodonAdminService {
@Path("id") String id
);
@FormUrlEncoded
@POST("admin/accounts/{account_id}/action")
Call<Void> performAction(
@Header("Authorization") String app_token,
@ -72,35 +68,35 @@ public interface MastodonAdminService {
@Field("send_email_notification") Boolean send_email_notification
);
@FormUrlEncoded
@POST("admin/accounts/{account_id}/approve")
Call<AdminAccount> approve(
@Header("Authorization") String app_token,
@Path("account_id") String account_id
);
@FormUrlEncoded
@POST("admin/accounts/{account_id}/reject")
Call<AdminAccount> reject(
@Header("Authorization") String app_token,
@Path("account_id") String account_id
);
@FormUrlEncoded
@POST("admin/accounts/{account_id}/enable")
Call<AdminAccount> enable(
@Header("Authorization") String app_token,
@Path("account_id") String account_id
);
@FormUrlEncoded
@POST("admin/accounts/{account_id}/unsilence")
Call<AdminAccount> unsilence(
@Header("Authorization") String app_token,
@Path("account_id") String account_id
);
@FormUrlEncoded
@POST("admin/accounts/{account_id}/unsuspend")
Call<AdminAccount> unsuspend(
@Header("Authorization") String app_token,
@ -118,115 +114,38 @@ public interface MastodonAdminService {
@Query("limit") int limit
);
//***************** ADMIN REPORTS **************
@FormUrlEncoded
@GET("admin/reports/{id}")
Call<AdminReport> getReport(
@Header("Authorization") String token,
@Path("id") String id
);
@FormUrlEncoded
@POST("admin/reports/{id}/assign_to_self")
Call<AdminReport> assignToSelf(
@Header("Authorization") String app_token,
@Path("id") String id
);
@FormUrlEncoded
@POST("admin/reports/{id}/unassign")
Call<AdminReport> unassign(
@Header("Authorization") String app_token,
@Path("id") String id
);
@FormUrlEncoded
@POST("admin/reports/{id}/resolve")
Call<AdminReport> resolved(
@Header("Authorization") String app_token,
@Path("id") String id
);
@FormUrlEncoded
@POST("admin/reports/{id}/reopen")
Call<AdminReport> reopen(
@Header("Authorization") String app_token,
@Path("id") String id
);
//*************** ADMIN DOMAINS ****************
@GET("admin/domain_blocks")
Call<List<AdminDomainBlock>> getDomainBlocks(
@Header("Authorization") String token,
@Query("max_id") String max_id,
@Query("limit") int limit
);
@GET("admin/domain_allows")
Call<List<AdminDomainBlock>> getDomainAllows(
@Header("Authorization") String token,
@Query("max_id") String max_id,
@Query("limit") int limit
);
@GET("admin/domain_blocks/{id}")
Call<AdminDomainBlock> getDomainBlock(
@Header("Authorization") String token,
@Path("id") String id
);
@GET("admin/domain_allows/{id}")
Call<AdminDomainBlock> getDomainAllow(
@Header("Authorization") String token,
@Path("id") String id
);
@FormUrlEncoded
@POST("admin/domain_blocks")
Call<AdminDomainBlock> blockDomain(
@Header("Authorization") String app_token,
@Field("domain") String domain,
@Field("severity") String severity,
@Field("reject_media") Boolean reject_media,
@Field("reject_reports") Boolean reject_reports,
@Field("private_comment") String private_comment,
@Field("public_comment") String public_comment,
@Field("obfuscate") Boolean obfuscate
);
@FormUrlEncoded
@POST("admin/domain_allows")
Call<AdminDomainBlock> allowDomain(
@Header("Authorization") String app_token,
@Path("domain") String domain
);
@FormUrlEncoded
@PUT("admin/domain_blocks/{id}")
Call<AdminDomainBlock> updateBlockDomain(
@Header("Authorization") String app_token,
@Path("id") String id,
@Field("severity") String severity,
@Field("reject_media") Boolean reject_media,
@Field("reject_reports") Boolean reject_reports,
@Field("private_comment") String private_comment,
@Field("public_comment") String public_comment,
@Field("obfuscate") Boolean obfuscate
);
@DELETE("admin/domain_blocks/{id}")
Call<Void> deleteBlockDomain(
@Header("Authorization") String app_token,
@Path("id") String id
);
@DELETE("admin/domain_allows/{id}")
Call<Void> deleteAllowDomain(
@Header("Authorization") String app_token,
@Path("id") String id
);
}

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.endpoints;
package app.fedilab.android.client.endpoints;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -17,7 +17,7 @@ package app.fedilab.android.mastodon.client.endpoints;
import java.util.List;
import app.fedilab.android.mastodon.client.entities.api.Announcement;
import app.fedilab.android.client.entities.api.Announcement;
import retrofit2.Call;
import retrofit2.http.DELETE;
import retrofit2.http.FormUrlEncoded;

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.endpoints;
package app.fedilab.android.client.endpoints;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -15,8 +15,8 @@ package app.fedilab.android.mastodon.client.endpoints;
* see <http://www.gnu.org/licenses>. */
import app.fedilab.android.mastodon.client.entities.api.App;
import app.fedilab.android.mastodon.client.entities.api.Token;
import app.fedilab.android.client.entities.api.App;
import app.fedilab.android.client.entities.api.Token;
import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.endpoints;
package app.fedilab.android.client.endpoints;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -17,11 +17,11 @@ package app.fedilab.android.mastodon.client.endpoints;
import java.util.List;
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.Tag;
import app.fedilab.android.client.entities.api.Account;
import app.fedilab.android.client.entities.api.Activity;
import app.fedilab.android.client.entities.api.Emoji;
import app.fedilab.android.client.entities.api.Instance;
import app.fedilab.android.client.entities.api.Tag;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.endpoints;
package app.fedilab.android.client.endpoints;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -16,8 +16,8 @@ package app.fedilab.android.mastodon.client.endpoints;
import java.util.List;
import app.fedilab.android.mastodon.client.entities.api.Notification;
import app.fedilab.android.mastodon.client.entities.api.PushSubscription;
import app.fedilab.android.client.entities.api.Notification;
import app.fedilab.android.client.entities.api.PushSubscription;
import retrofit2.Call;
import retrofit2.http.DELETE;
import retrofit2.http.Field;
@ -71,12 +71,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[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[alerts][poll]") boolean poll
);
@GET("push/subscription")

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.endpoints;
package app.fedilab.android.client.endpoints;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -15,7 +15,7 @@ package app.fedilab.android.mastodon.client.endpoints;
* see <http://www.gnu.org/licenses>. */
import app.fedilab.android.mastodon.client.entities.api.Oembed;
import app.fedilab.android.client.entities.api.Oembed;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.endpoints;
package app.fedilab.android.client.endpoints;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -15,7 +15,7 @@ package app.fedilab.android.mastodon.client.endpoints;
* see <http://www.gnu.org/licenses>. */
import app.fedilab.android.mastodon.client.entities.api.Results;
import app.fedilab.android.client.entities.api.Results;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Header;

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.endpoints;
package app.fedilab.android.client.endpoints;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -17,16 +17,14 @@ package app.fedilab.android.mastodon.client.endpoints;
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.Card;
import app.fedilab.android.mastodon.client.entities.api.Context;
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.client.entities.api.Account;
import app.fedilab.android.client.entities.api.Attachment;
import app.fedilab.android.client.entities.api.Card;
import app.fedilab.android.client.entities.api.Context;
import app.fedilab.android.client.entities.api.Poll;
import app.fedilab.android.client.entities.api.ScheduledStatus;
import app.fedilab.android.client.entities.api.Status;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.http.DELETE;
import retrofit2.http.Field;
@ -59,42 +57,7 @@ public interface MastodonStatusesService {
@Field("sensitive") Boolean sensitive,
@Field("spoiler_text") String spoiler_text,
@Field("visibility") String visibility,
@Field("language") String language,
@Field("quote_id") String quote_id,
@Field("content_type") String content_type
);
@GET("statuses/{id}/source")
Call<StatusSource> getStatusSource(
@Header("Authorization") String token,
@Path("id") String id);
@GET("statuses/{id}/history")
Call<List<Status>> getStatusHistory(
@Header("Authorization") String token,
@Path("id") String id);
//Post a status
@FormUrlEncoded
@PUT("statuses/{id}")
Call<Status> updateStatus(
@Header("Idempotency-Key") String idempotency_Key,
@Header("Authorization") String token,
@Path("id") String id,
@Field("status") String status,
@Field("media_ids[]") List<String> media_ids,
@Field("poll[options][]") List<String> poll_options,
@Field("poll[expires_in]") Integer poll_expire_in,
@Field("poll[multiple]") Boolean poll_multiple,
@Field("poll[hide_totals]") Boolean poll_hide_totals,
@Field("in_reply_to_id") String in_reply_to_id,
@Field("sensitive") Boolean sensitive,
@Field("spoiler_text") String spoiler_text,
@Field("visibility") String visibility,
@Field("language") String language,
@Field("media_attributes[id][]") List<String> media_id,
@Field("media_attributes[description][]") List<String> media_description,
@Field("media_attributes[focus][]") List<String> focus
@Field("language") String language
);
//Post a scheduled status
@ -254,8 +217,8 @@ public interface MastodonStatusesService {
@Header("Authorization") String token,
@Part MultipartBody.Part file,
@Part MultipartBody.Part thumbnail,
@Part("description") RequestBody description,
@Part("focus") RequestBody focus
@Part("description") String description,
@Part("focus") String focus
);
//Edit a Media
@ -318,18 +281,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

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.endpoints;
package app.fedilab.android.client.endpoints;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -16,15 +16,14 @@ package app.fedilab.android.mastodon.client.endpoints;
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.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.misskey.MisskeyNote;
import app.fedilab.android.mastodon.client.entities.nitter.Nitter;
import app.fedilab.android.mastodon.client.entities.peertube.PeertubeVideo;
import app.fedilab.android.client.entities.api.Account;
import app.fedilab.android.client.entities.api.Conversation;
import app.fedilab.android.client.entities.api.Marker;
import app.fedilab.android.client.entities.api.MastodonList;
import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.client.entities.misskey.MisskeyNote;
import app.fedilab.android.client.entities.nitter.Nitter;
import app.fedilab.android.client.entities.peertube.PeertubeVideo;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
@ -53,46 +52,20 @@ public interface MastodonTimelinesService {
@Query("limit") Integer limit
);
@GET("timelines/bubble")
Call<List<Status>> getBubble(
@Header("Authorization") String token,
@Query("only_media") Boolean only_media,
@Query("remote") Boolean remote,
@Query("with_muted") Boolean with_muted,
@Query("exclude_visibilities") List<String> exclude_visibilities,
@Query("reply_visibility") String reply_visibility,
@Query("max_id") String max_id,
@Query("since_id") String since_id,
@Query("min_id") String min_id,
@Query("limit") Integer limit
);
@GET("trends/statuses")
Call<List<Status>> getStatusTrends(
@Header("Authorization") String token,
@Query("offset") String offset,
@Query("limit") Integer limit);
@GET("trends/tags")
Call<List<Tag>> getTagTrends(@Header("Authorization") String token,
@Query("offset") Integer offset,
@Query("limit") Integer limit);
//Public Tags timelines
@GET("timelines/tag/{hashtag}")
Call<List<Status>> getHashTag(
@Header("Authorization") String token,
@Path("hashtag") String hashtag,
@Query("local") Boolean local,
@Query("only_media") Boolean only_media,
@Query("local") boolean local,
@Query("only_media") boolean only_media,
@Query("all[]") List<String> all,
@Query("any[]") List<String> any,
@Query("none[]") List<String> none,
@Query("max_id") String max_id,
@Query("since_id") String since_id,
@Query("min_id") String min_id,
@Query("limit") Integer limit
@Query("limit") int limit
);
//Home timeline
@ -102,8 +75,8 @@ public interface MastodonTimelinesService {
@Query("max_id") String max_id,
@Query("since_id") String since_id,
@Query("min_id") String min_id,
@Query("limit") Integer limit,
@Query("local") Boolean local
@Query("limit") int limit,
@Query("local") boolean local
);
//List timeline
@ -114,7 +87,7 @@ public interface MastodonTimelinesService {
@Query("max_id") String max_id,
@Query("since_id") String since_id,
@Query("min_id") String min_id,
@Query("limit") Integer limit
@Query("limit") int limit
);
//get conversations

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.endpoints;
package app.fedilab.android.client.endpoints;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.entities.api;
package app.fedilab.android.client.entities.api;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -14,23 +14,15 @@ 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 android.app.Activity;
import android.content.Context;
import android.text.Spannable;
import android.view.View;
import androidx.annotation.Nullable;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import app.fedilab.android.mastodon.helper.SpannableHelper;
public class Account implements Serializable {
@SerializedName("id")
@ -75,70 +67,18 @@ public class Account implements Serializable {
public List<Field> fields;
@SerializedName("suspended")
public boolean suspended;
@SerializedName("limited")
public boolean limited;
@SerializedName("discoverable")
public boolean discoverable;
@SerializedName("group")
public boolean group;
@SerializedName("mute_expires_at")
public Date mute_expires_at;
@SerializedName("moved")
public Account moved;
@SerializedName("role")
public Role role;
//Some extra spannable element - They will be filled automatically when fetching the account
public transient Spannable span_display_name;
public transient Spannable span_note;
public transient RelationShip relationShip;
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);
}
public synchronized Spannable getSpanDisplayName(Activity activity, WeakReference<View> viewWeakReference) {
if (display_name == null || display_name.isEmpty()) {
display_name = username;
}
return SpannableHelper.convertEmoji(activity, display_name, this, viewWeakReference);
}
public synchronized Spannable getSpanDisplayNameTitle(Context context, WeakReference<View> viewWeakReference, String title) {
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);
}
@Override
public boolean equals(@Nullable Object obj) {
boolean same = false;
if (obj instanceof Account) {
same = this.id.equals(((Account) obj).id);
}
return same;
}
public static class Role implements Serializable {
@SerializedName("id")
public String id;
@SerializedName("name")
public String name;
@SerializedName("color")
public String color;
@SerializedName("position")
public int position;
@SerializedName("permissions")
public int permissions;
@SerializedName("highlighted")
public boolean highlighted;
@SerializedName("created_at")
public Date created_at;
@SerializedName("updated_at")
public Date updated_at;
}
public static class AccountParams implements Serializable {
@SerializedName("discoverable")
public boolean discoverable;

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.entities.api;
package app.fedilab.android.client.entities.api;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.entities.api;
package app.fedilab.android.client.entities.api;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.entities.api.admin;
package app.fedilab.android.client.entities.api;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -18,39 +18,10 @@ import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import app.fedilab.android.mastodon.client.entities.api.Account;
public class AdminAccount implements Serializable {
public static LinkedHashMap<Integer, String> permissions;
static {
permissions = new LinkedHashMap<>();
permissions.put(1, "Administrator");
permissions.put(2, "Devops");
permissions.put(4, "View Audit Log");
permissions.put(8, "View Dashboard");
permissions.put(10, "Manage Reports");
permissions.put(20, "Manage Federation");
permissions.put(40, "Manage Settings");
permissions.put(80, "Manage Blocks");
permissions.put(100, "Manage Taxonomies");
permissions.put(200, "Manage Appeals");
permissions.put(400, "Manage Users");
permissions.put(800, "Manage Invites");
permissions.put(1000, "Manage Rules");
permissions.put(2000, "Manage Announcements");
permissions.put(4000, "Manage Custom Emojis");
permissions.put(8000, "Manage Webhooks");
permissions.put(10000, "Invite Users");
permissions.put(20000, "Manage Roles");
permissions.put(40000, "Manage User Access");
permissions.put(80000, "Delete User Data");
}
@SerializedName("id")
public String id;
@SerializedName("username")
@ -62,21 +33,25 @@ public class AdminAccount implements Serializable {
@SerializedName("email")
public String email;
@SerializedName("ip")
public String ip;
public IP ip;
@SerializedName("ips")
public List<IP> ips;
@SerializedName("locale")
public String locale;
@SerializedName("invite_request")
public String invite_request;
@SerializedName("role")
public Role role;
public String role;
@SerializedName("confirmed")
public boolean confirmed;
@SerializedName("suspended")
public boolean suspended;
@SerializedName("silenced")
public boolean silenced;
@SerializedName("disabled")
public boolean disabled;
@SerializedName("approved")
public boolean approved;
@SerializedName("ips")
public List<AdminIp> ips;
@SerializedName("disabled")
public boolean disabled;
@SerializedName("silenced")
public boolean silenced;
@SerializedName("suspended")
public boolean suspended;
@SerializedName("account")
public Account account;
@SerializedName("created_by_application_id")
@ -85,28 +60,12 @@ public class AdminAccount implements Serializable {
public String invited_by_account_id;
@SerializedName("locale")
public String locale;
@SerializedName("invite_request")
public String invite_request;
public static class Role implements Serializable {
public static class IP implements Serializable {
@SerializedName("ip")
public String ip;
@SerializedName("name")
public String name;
@SerializedName("color")
public String color;
@SerializedName("position")
public long position;
@SerializedName("permissions")
public int permissions;
@SerializedName("highlighted")
public boolean highlighted;
@SerializedName("created_at")
public Date created_at;
@SerializedName("updated_at")
public Date updated_at;
@SerializedName("used_at")
public Date used_at;
@SerializedName("user_id")
public String user_id;
}
}

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.entities.api.admin;
package app.fedilab.android.client.entities.api;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -17,8 +17,6 @@ package app.fedilab.android.mastodon.client.entities.api.admin;
import java.util.List;
import app.fedilab.android.mastodon.client.entities.api.Pagination;
public class AdminAccounts {
public Pagination pagination = new Pagination();

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.entities.api.admin;
package app.fedilab.android.client.entities.api;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -20,37 +20,30 @@ import java.io.Serializable;
import java.util.Date;
import java.util.List;
import app.fedilab.android.mastodon.client.entities.api.Instance;
import app.fedilab.android.mastodon.client.entities.api.Status;
public class AdminReport implements Serializable {
@SerializedName("id")
public String id;
@SerializedName("account")
public Account account;
@SerializedName("action_taken")
public Boolean action_taken;
@SerializedName("action_taken_at")
public Date action_taken_at;
public String action_taken;
@SerializedName("action_taken_by_account")
public String action_taken_by_account;
@SerializedName("assigned_account")
public Account assigned_account;
@SerializedName("category")
public String category;
@SerializedName("comment")
public String comment;
@SerializedName("forwarded")
public boolean forwarded;
@SerializedName("created_at")
public Date created_at;
@SerializedName("updated_at")
public Date updated_at;
@SerializedName("account")
public AdminAccount account;
@SerializedName("target_account")
public AdminAccount target_account;
@SerializedName("assigned_account")
public AdminAccount assigned_account;
@SerializedName("action_taken_by_account")
public AdminAccount action_taken_by_account;
public Account target_account;
@SerializedName("statuses")
public List<Status> statuses;
@SerializedName("rules")
public List<Instance.Rule> rules;
@SerializedName("updated_at")
public Date updated_at;
}

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.entities.api.admin;
package app.fedilab.android.client.entities.api;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -17,8 +17,6 @@ package app.fedilab.android.mastodon.client.entities.api.admin;
import java.util.List;
import app.fedilab.android.mastodon.client.entities.api.Pagination;
public class AdminReports {
public Pagination pagination = new Pagination();

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.entities.api;
package app.fedilab.android.client.entities.api;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -14,18 +14,13 @@ 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 android.content.Context;
import android.text.Spannable;
import android.view.View;
import com.google.gson.annotations.SerializedName;
import java.lang.ref.WeakReference;
import java.util.Date;
import java.util.List;
import app.fedilab.android.mastodon.helper.SpannableHelper;
public class Announcement {
@SerializedName("id")
public String id;
@ -54,9 +49,6 @@ public class Announcement {
@SerializedName("reactions")
public List<Reaction> reactions;
public synchronized Spannable getSpanContent(Context context, WeakReference<View> viewWeakReference) {
return SpannableHelper.convert(context, content, null, null, this, viewWeakReference);
}
//Some extra spannable element - They will be filled automatically when fetching the status
public transient Spannable span_content;
}

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.entities.api;
package app.fedilab.android.client.entities.api;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.entities.api;
package app.fedilab.android.client.entities.api;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -14,8 +14,6 @@ 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 androidx.annotation.Nullable;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
@ -45,52 +43,7 @@ public class Attachment implements Serializable {
public long size;
@SerializedName("local_path")
public String local_path;
@SerializedName("meta")
public Meta meta;
@SerializedName("sensitive")
public boolean sensitive = false;
public String peertubeHost = null;
public String peertubeId = null;
public String focus = null;
public String translation = null;
public transient Status status = null;
@Override
public boolean equals(@Nullable Object obj) {
boolean same = false;
if (obj instanceof Attachment && ((Attachment) obj).id != null && this.id != null) {
same = this.id.equals(((Attachment) obj).id);
return same;
} else return super.equals(obj);
}
public static class Meta implements Serializable {
@SerializedName("focus")
public Focus focus;
@SerializedName("original")
public MediaData original;
@SerializedName("small")
public MediaData small;
}
public static class Focus implements Serializable {
@SerializedName("x")
public float x;
@SerializedName("y")
public float y;
}
public static class MediaData implements Serializable {
@SerializedName("width")
public int width;
@SerializedName("height")
public int height;
@SerializedName("size")
public String size;
@SerializedName("aspect")
public float aspect;
}
}

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.entities.api;
package app.fedilab.android.client.entities.api;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.entities.api;
package app.fedilab.android.client.entities.api;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab

View file

@ -1,5 +1,10 @@
package app.fedilab.android.mastodon.client.entities.api;
/* Copyright 2022 Thomas Schneider
package app.fedilab.android.client.entities.api;
import com.google.gson.annotations.SerializedName;
import java.util.List;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
*
@ -13,16 +18,13 @@ 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 com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.util.List;
public class FamiliarFollowers implements Serializable {
public class Conversation {
@SerializedName("id")
public String id;
@SerializedName("unread")
public boolean unread;
@SerializedName("accounts")
public List<Account> accounts;
@SerializedName("last_status")
public Status last_status;
}

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.entities.api;
package app.fedilab.android.client.entities.api;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.entities.api;
package app.fedilab.android.client.entities.api;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab

View file

@ -1,4 +1,4 @@
package app.fedilab.android.mastodon.client.entities.api;
package app.fedilab.android.client.entities.api;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
@ -27,7 +27,7 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.exception.DBException;
import app.fedilab.android.sqlite.Sqlite;

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