Merge branch 'unifiedpush' into develop

This commit is contained in:
Thomas 2021-03-03 14:31:00 +01:00
commit 6cffe6a5f6
85 changed files with 1733 additions and 1837 deletions

View File

@ -1,5 +1,8 @@
apply plugin: 'com.android.application'
import java.util.regex.Matcher
import java.util.regex.Pattern
apply plugin: 'com.android.application'
def flavor
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
@ -42,6 +45,7 @@ android {
buildConfigField "boolean", "DONATIONS", "true"
buildConfigField "boolean", "lite", "false"
resValue "string", "app_name", "Fedilab"
flavor = "fdroid"
}
lite {
minSdkVersion 21
@ -49,12 +53,14 @@ android {
buildConfigField "boolean", "DONATIONS", "true"
buildConfigField "boolean", "lite", "true"
resValue "string", "app_name", "Fedilab Lite"
flavor = "lite"
}
playstore {
applicationId "app.fedilab.android"
buildConfigField "boolean", "DONATIONS", "false"
buildConfigField "boolean", "lite", "false"
resValue "string", "app_name", "Fedilab"
flavor = "playstore"
}
}
buildFeatures {
@ -62,7 +68,7 @@ android {
}
sourceSets {
playstore {
manifest.srcFile "src/common/AndroidManifest.xml"
manifest.srcFile "src/playstore/AndroidManifest.xml"
assets.srcDirs = ['/src/mains/assets', 'src/common/assets']
java.srcDirs = ['src/main/java', 'src/playstore/java','src/common/java']
res.srcDirs = ['src/main/res', 'src/playstore/res','src/common/res']
@ -71,13 +77,13 @@ android {
fdroid {
manifest.srcFile "src/common/AndroidManifest.xml"
assets.srcDirs = ['/src/mains/assets', 'src/common/assets']
java.srcDirs = ['src/main/java', 'src/fdroid/java','src/common/java']
java.srcDirs = ['src/main/java', 'src/fdroid/java','src/common/java', 'src/fdroidcommon/java']
res.srcDirs = ['src/main/res', 'src/fdroid/res','src/common/res']
}
lite {
manifest.srcFile "src/lite/AndroidManifest.xml"
assets.srcDirs = ['/src/mains/assets']
java.srcDirs = ['src/main/java', 'src/lite/java']
java.srcDirs = ['src/main/java', 'src/lite/java', 'src/fdroidcommon/java']
res.srcDirs = ['src/main/res', 'src/lite/res']
}
}
@ -173,6 +179,12 @@ dependencies {
//debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
implementation 'com.huangyz0918:androidwm-light:0.1.2'
implementation 'com.github.UnifiedPush:android-connector:1.1.0'
implementation "com.madgag.spongycastle:bctls-jdk15on:1.58.0.0"
implementation 'commons-net:commons-net:3.6'
//Flavors
//Playstore
@ -182,7 +194,7 @@ dependencies {
playstoreImplementation 'org.framagit.tom79:country-picker-android:1.2.0'
playstoreImplementation 'com.vanniktech:emoji-one:0.6.0'
playstoreImplementation 'ja.burhanrashid52:photoeditor:0.4.0'
playstoreImplementation 'com.github.UnifiedPush:android-connector_fcm_added:1.0.0'
//Fdroid
fdroidApi 'com.theartofdev.edmodo:android-image-cropper:2.8.+'
@ -191,4 +203,29 @@ dependencies {
fdroidImplementation 'com.vanniktech:emoji-one:0.6.0'
fdroidImplementation 'ja.burhanrashid52:photoeditor:0.4.0'
}
def getCurrentFlavor() {
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
Pattern pattern
if( tskReqStr.contains( "assemble" ) )
pattern = Pattern.compile("assemble(\\w+)(Release|Debug)")
else
pattern = Pattern.compile("generate(\\w+)(Release|Debug)")
Matcher matcher = pattern.matcher( tskReqStr )
if( matcher.find() ) {
return matcher.group(1).toLowerCase()
}else
{
println "NO MATCH FOUND"
return ""
}
}
if( getCurrentFlavor() == "playstore") {
apply plugin: 'com.google.gms.google-services'
}

View File

@ -84,5 +84,20 @@
<activity
android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:theme="@style/Base.Theme.AppCompat" />
<receiver
android:name=".services.UnifiedPushService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="org.unifiedpush.android.connector.MESSAGE" />
<action android:name="org.unifiedpush.android.connector.UNREGISTERED" />
<action android:name="org.unifiedpush.android.connector.NEW_ENDPOINT" />
<action android:name="org.unifiedpush.android.connector.REGISTRATION_FAILED" />
<action android:name="org.unifiedpush.android.connector.REGISTRATION_REFUSED" />
</intent-filter>
</receiver>
</application>
</manifest>

View File

@ -1,23 +0,0 @@
package app.fedilab.android.services;
/* Copyright 2020 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 app.fedilab.android.helper.Helper;
public class LiveNotificationService extends BaseLiveNotificationService {
static {
Helper.installProvider();
}
}

View File

@ -0,0 +1,103 @@
package app.fedilab.android.helper;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
import android.text.util.Linkify;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import org.unifiedpush.android.connector.Registration;
import java.util.List;
import app.fedilab.android.R;
import app.fedilab.android.client.Entities.Account;
import app.fedilab.android.jobs.ApplicationJob;
import app.fedilab.android.jobs.NotificationsSyncJob;
import app.fedilab.android.sqlite.AccountDAO;
import app.fedilab.android.sqlite.Sqlite;
import static android.content.Context.MODE_PRIVATE;
import static app.fedilab.android.helper.BaseHelper.NOTIF_NONE;
import static app.fedilab.android.helper.BaseHelper.NOTIF_PUSH;
import static app.fedilab.android.helper.BaseHelper.liveNotifType;
public class PushHelper {
public static void startStreaming(Context context) {
int liveNotifications = liveNotifType(context);
ApplicationJob.cancelAllJob(NotificationsSyncJob.NOTIFICATION_REFRESH);
NotificationsSyncJob.schedule(false);
switch (liveNotifications) {
case NOTIF_PUSH:
new Thread(() -> {
SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
List<Account> accounts = new AccountDAO(context, db).getPushNotificationAccounts();
((Activity) context).runOnUiThread(() -> {
for (Account account : accounts) {
registerAppWithDialog(context, account.getUsername() + "@" + account.getInstance());
}
});
}).start();
break;
case NOTIF_NONE:
new Registration().unregisterApp(context);
break;
}
}
private static void registerAppWithDialog(Context context, String slug) {
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE);
int theme = sharedpreferences.getInt(Helper.SET_THEME, Helper.THEME_DARK);
int style;
if (theme == Helper.THEME_DARK) {
style = R.style.DialogDark;
} else if (theme == Helper.THEME_BLACK) {
style = R.style.DialogBlack;
} else {
style = R.style.Dialog;
}
Registration registration = new Registration();
List<String> distributors = registration.getDistributors(context);
if (distributors.size() == 1 || !registration.getDistributor(context).isEmpty()) {
if (distributors.size() == 1) {
registration.saveDistributor(context, distributors.get(0));
} else {
registration.saveDistributor(context, registration.getDistributor(context));
}
registration.registerApp(context, slug);
return;
}
AlertDialog.Builder alert = new AlertDialog.Builder(context, style);
if (distributors.size() == 0) {
alert.setTitle(R.string.no_distributors_found);
final TextView message = new TextView(context);
String link = "https://fedilab.app/wiki/features/push-notifications#fdroid";
final SpannableString s =
new SpannableString(context.getString(R.string.no_distributors_explanation, link));
Linkify.addLinks(s, Linkify.WEB_URLS);
message.setText(s);
message.setPadding(30, 20, 30, 10);
message.setMovementMethod(LinkMovementMethod.getInstance());
alert.setView(message);
alert.setPositiveButton(R.string.close, (dialog, whichButton) -> dialog.dismiss());
} else {
alert.setTitle(R.string.select_distributors);
String[] distributorsStr = distributors.toArray(new String[0]);
alert.setSingleChoiceItems(distributorsStr, -1, (dialog, item) -> {
String distributor = distributorsStr[item];
registration.saveDistributor(context, distributor);
registration.registerApp(context, slug);
dialog.dismiss();
});
}
alert.show();
}
}

View File

@ -69,18 +69,7 @@
android:name="app.fedilab.android.services.BackupNotificationInDataBaseService"
android:exported="false" />
<receiver android:name=".services.UpgradeReceiver">
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>
<receiver
android:name="app.fedilab.android.services.RestartLiveNotificationReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver
android:name="app.fedilab.android.services.PeertubeUploadReceiver"
android:exported="false">
@ -88,20 +77,7 @@
<action android:name="app.fedilab.android.uploadservice.broadcast.status" />
</intent-filter>
</receiver>
<receiver
android:name="app.fedilab.android.services.StopLiveNotificationReceiver"
android:exported="false">
<intent-filter>
<action android:name="StopLiveNotificationReceiver" />
</intent-filter>
</receiver>
<receiver
android:name="app.fedilab.android.services.StopDelayedNotificationReceiver"
android:exported="false">
<intent-filter>
<action android:name="StopDelayedNotificationReceiver" />
</intent-filter>
</receiver>
<service
android:name="app.fedilab.android.services.StreamingHomeTimelineService"
android:exported="false" />
@ -461,5 +437,19 @@
android:resource="@xml/file_paths" />
</provider>
<receiver
android:name=".services.UnifiedPushService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="org.unifiedpush.android.connector.MESSAGE" />
<action android:name="org.unifiedpush.android.connector.UNREGISTERED" />
<action android:name="org.unifiedpush.android.connector.NEW_ENDPOINT" />
<action android:name="org.unifiedpush.android.connector.REGISTRATION_FAILED" />
<action android:name="org.unifiedpush.android.connector.REGISTRATION_REFUSED" />
</intent-filter>
</receiver>
</application>
</manifest>

View File

@ -133,9 +133,11 @@ import app.fedilab.android.fragments.TabLayoutNotificationsFragment;
import app.fedilab.android.fragments.TabLayoutScheduleFragment;
import app.fedilab.android.fragments.WhoToFollowFragment;
import app.fedilab.android.helper.CrossActions;
import app.fedilab.android.helper.ECDH;
import app.fedilab.android.helper.ExpandableHeightListView;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.MenuFloating;
import app.fedilab.android.helper.PushHelper;
import app.fedilab.android.interfaces.OnFilterActionInterface;
import app.fedilab.android.interfaces.OnRetrieveEmojiAccountInterface;
import app.fedilab.android.interfaces.OnRetrieveFeedsInterface;
@ -156,7 +158,8 @@ import es.dmoral.toasty.Toasty;
import static app.fedilab.android.activities.WebviewActivity.trackingDomains;
import static app.fedilab.android.asynctasks.ManageFiltersAsyncTask.action.GET_ALL_FILTER;
import static app.fedilab.android.helper.BaseHelper.startStreaming;
import static app.fedilab.android.helper.BaseHelper.NOTIF_NONE;
import static app.fedilab.android.helper.BaseHelper.NOTIF_PUSH;
import static app.fedilab.android.helper.Helper.changeDrawableColor;
@ -215,6 +218,7 @@ public abstract class BaseMainActivity extends BaseActivity
Account account = new AccountDAO(BaseMainActivity.this, db).getUniqAccount(userId, instance);
Intent intent = getIntent();
PackageManager pm = getPackageManager();
try {
if (intent != null && intent.getComponent() != null) {
ActivityInfo ai = pm.getActivityInfo(intent.getComponent(), PackageManager.GET_META_DATA);
@ -249,6 +253,7 @@ public abstract class BaseMainActivity extends BaseActivity
e.printStackTrace();
}
if (account == null) {
Helper.logoutCurrentUser(BaseMainActivity.this);
Intent myIntent = new Intent(BaseMainActivity.this, LoginActivity.class);
@ -306,6 +311,7 @@ public abstract class BaseMainActivity extends BaseActivity
activity = this;
rateThisApp();
//Intialize Peertube information
//This task will allow to instance a static PeertubeInformation class
if (social == UpdateAccountInfoAsyncTask.SOCIAL.PEERTUBE) {
@ -418,6 +424,9 @@ public abstract class BaseMainActivity extends BaseActivity
add_new = findViewById(R.id.add_new);
main_app_container = findViewById(R.id.main_app_container);
if (social == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON || social == UpdateAccountInfoAsyncTask.SOCIAL.PLEROMA || social == UpdateAccountInfoAsyncTask.SOCIAL.GNU || social == UpdateAccountInfoAsyncTask.SOCIAL.FRIENDICA) {
new SyncTimelinesAsyncTask(BaseMainActivity.this, 0, Helper.canFetchList(BaseMainActivity.this, account), BaseMainActivity.this);
@ -662,7 +671,7 @@ public abstract class BaseMainActivity extends BaseActivity
}
boolean popupShown = sharedpreferences.getBoolean(Helper.SET_POPUP_PUSH, false);
if (popupShown) {
Helper.startStreaming(BaseMainActivity.this);
PushHelper.startStreaming(BaseMainActivity.this);
}
if (hidde_menu != null)
@ -1194,55 +1203,39 @@ public abstract class BaseMainActivity extends BaseActivity
//Live notification mode
final Spinner set_live_type = dialogView.findViewById(R.id.set_live_type);
String[] labels = {getString(R.string.live_notif), getString(R.string.live_delayed), getString(R.string.no_live_notif)};
String[] labels = {getString(R.string.push_notif), getString(R.string.no_live_notif)};
ArrayAdapter<String> adapterLive = new ArrayAdapter<>(BaseMainActivity.this,
android.R.layout.simple_spinner_dropdown_item, labels);
set_live_type.setAdapter(adapterLive);
TextView set_live_type_indication = dialogView.findViewById(R.id.set_live_type_indication);
switch (Helper.liveNotifType(BaseMainActivity.this)) {
case Helper.NOTIF_LIVE:
set_live_type_indication.setText(R.string.live_notif_indication);
break;
case Helper.NOTIF_DELAYED:
set_live_type_indication.setText(R.string.set_live_type_indication);
case Helper.NOTIF_PUSH:
set_live_type_indication.setText(R.string.set_push_notifications);
break;
case Helper.NOTIF_NONE:
set_live_type_indication.setText(R.string.no_live_indication);
break;
}
set_live_type.setSelection(Helper.liveNotifType(BaseMainActivity.this), false);
int livenotif = Helper.liveNotifType(BaseMainActivity.this);
int selection = 0;
if (livenotif == NOTIF_NONE)
selection = 1;
set_live_type.setSelection(selection, false);
set_live_type.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
SharedPreferences.Editor editor = sharedpreferences.edit();
switch (position) {
case Helper.NOTIF_LIVE:
editor.putBoolean(Helper.SET_LIVE_NOTIFICATIONS, true);
editor.putBoolean(Helper.SET_DELAYED_NOTIFICATIONS, false);
case 0:
editor.putBoolean(Helper.SET_PUSH_NOTIFICATIONS, true);
editor.apply();
startStreaming(BaseMainActivity.this);
PushHelper.startStreaming(BaseMainActivity.this);
set_live_type_indication.setText(R.string.set_push_notifications);
break;
case Helper.NOTIF_DELAYED:
editor.putBoolean(Helper.SET_LIVE_NOTIFICATIONS, false);
editor.putBoolean(Helper.SET_DELAYED_NOTIFICATIONS, true);
editor.apply();
startStreaming(BaseMainActivity.this);
break;
case Helper.NOTIF_NONE:
editor.putBoolean(Helper.SET_LIVE_NOTIFICATIONS, false);
editor.putBoolean(Helper.SET_DELAYED_NOTIFICATIONS, false);
editor.apply();
break;
}
switch (Helper.liveNotifType(BaseMainActivity.this)) {
case Helper.NOTIF_LIVE:
set_live_type_indication.setText(R.string.live_notif_indication);
break;
case Helper.NOTIF_DELAYED:
set_live_type_indication.setText(R.string.set_live_type_indication);
break;
case Helper.NOTIF_NONE:
case 1:
editor.putBoolean(Helper.SET_PUSH_NOTIFICATIONS, false);
set_live_type_indication.setText(R.string.no_live_indication);
editor.apply();
break;
}
}
@ -1386,6 +1379,7 @@ public abstract class BaseMainActivity extends BaseActivity
protected abstract void rateThisApp();
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
@ -2426,6 +2420,7 @@ public abstract class BaseMainActivity extends BaseActivity
protected abstract void launchOwnerNotificationsActivity();
public enum iconLauncher {
BUBBLES,
FEDIVERSE,

View File

@ -15,6 +15,7 @@
package app.fedilab.android.activities;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@ -121,6 +122,7 @@ public class LoginActivity extends BaseActivity {
return Helper.instanceWithProtocol(context, instance) + Helper.EP_AUTHORIZE + "?" + queryString;
}
@SuppressLint("ApplySharedPref")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -325,18 +327,23 @@ public class LoginActivity extends BaseActivity {
thread.interrupt();
thread = null;
}
if (oldSearch == null || !oldSearch.equals(s.toString().trim())) {
thread = new Thread(() -> {
try {
final String response = new HttpsConnection(LoginActivity.this, instance).get("https://instances.social/api/1.0" + action, 30, parameters, Helper.THEKINRAR_SECRET_TOKEN);
String response = null;
try {
response = new HttpsConnection(LoginActivity.this, instance).get("https://instances.social/api/1.0" + action, 30, parameters, Helper.THEKINRAR_SECRET_TOKEN);
} catch (Exception ignored) {
}
if (response == null) {
return;
}
String finalResponse = response;
runOnUiThread(() -> {
String[] instances;
try {
JSONObject jsonObject = new JSONObject(response);
JSONObject jsonObject = new JSONObject(finalResponse);
JSONArray jsonArray = jsonObject.getJSONArray("instances");
int length = 0;
for (int i = 0; i < jsonArray.length(); i++) {

View File

@ -30,6 +30,7 @@ import app.fedilab.android.client.Entities.Account;
import app.fedilab.android.client.Entities.Error;
import app.fedilab.android.client.GNUAPI;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.PushHelper;
import app.fedilab.android.interfaces.OnPostStatusActionInterface;
import app.fedilab.android.sqlite.Sqlite;
import app.fedilab.android.sqlite.TagsCacheDAO;
@ -125,11 +126,6 @@ public class PostStatusAsyncTask {
}
};
thread.start();
if (account != null) {
String key = account.getUsername() + "@" + account.getInstance();
Helper.sleeps.put(key, 30000);
Helper.startStreaming(contextReference.get());
}
};
mainHandler.post(myRunnable);
}).start();

View File

@ -0,0 +1,47 @@
/* 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.asynctasks;
import android.content.Context;
import java.lang.ref.WeakReference;
import app.fedilab.android.client.API;
import app.fedilab.android.client.APIResponse;
import app.fedilab.android.client.Entities.Account;
public class PostSubscriptionAsyncTask {
private final WeakReference<Context> contextReference;
private APIResponse apiResponse;
private final String endpoint;
private final Account account;
public PostSubscriptionAsyncTask(Context context, Account account, String endpoint) {
this.contextReference = new WeakReference<>(context);
this.endpoint = endpoint;
this.account = account;
doInBackground();
}
protected void doInBackground() {
new Thread(() -> {
apiResponse = new API(contextReference.get(), account.getInstance(), account.getToken()).pushSubscription(endpoint, account);
}).start();
}
}

View File

@ -23,7 +23,10 @@ import android.os.Bundle;
import android.text.Html;
import android.text.SpannableString;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.PreferenceManager;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
@ -58,6 +61,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -88,6 +92,7 @@ import app.fedilab.android.client.Entities.Notification;
import app.fedilab.android.client.Entities.Peertube;
import app.fedilab.android.client.Entities.Poll;
import app.fedilab.android.client.Entities.PollOptions;
import app.fedilab.android.client.Entities.PushSubscription;
import app.fedilab.android.client.Entities.Reaction;
import app.fedilab.android.client.Entities.Relationship;
import app.fedilab.android.client.Entities.Report;
@ -99,11 +104,15 @@ import app.fedilab.android.client.Entities.Tag;
import app.fedilab.android.client.Entities.Trends;
import app.fedilab.android.client.Entities.TrendsHistory;
import app.fedilab.android.fragments.DisplayNotificationsFragment;
import app.fedilab.android.helper.ECDH;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.sqlite.AccountDAO;
import app.fedilab.android.sqlite.Sqlite;
import app.fedilab.android.sqlite.TimelineCacheDAO;
import static app.fedilab.android.helper.ECDH.kp_private;
import static app.fedilab.android.helper.ECDH.kp_public;
/**
* Created by Thomas on 23/04/2017.
@ -555,6 +564,34 @@ public class API {
return reactions;
}
/**
* Parse a push notification
*
* @param resobj JSONObject
* @return PushSubscription
*/
private static PushSubscription parsePushNotifications(JSONObject resobj) {
PushSubscription pushSubscription = new PushSubscription();
try {
pushSubscription.setId(resobj.getString("id"));
pushSubscription.setEndpoint(resobj.getString("endpoint"));
pushSubscription.setServer_key(resobj.getString("server_key"));
JSONObject alertsObject = resobj.getJSONObject("alerts");
Iterator<String> iter = alertsObject.keys();
HashMap<String, Boolean> alertsList = new HashMap<>();
while (iter.hasNext()) {
String key = iter.next();
boolean value = (boolean) alertsObject.get(key);
alertsList.put(key, value);
}
pushSubscription.setAlertsList(alertsList);
} catch (JSONException e) {
e.printStackTrace();
}
return pushSubscription;
}
/**
* Parse a reaction
*
@ -5101,7 +5138,6 @@ public class API {
private APIResponse getNotifications(DisplayNotificationsFragment.Type type, String max_id, String since_id, int limit, boolean display) {
HashMap<String, String> params = new HashMap<>();
if (MainActivity.social == UpdateAccountInfoAsyncTask.SOCIAL.PIXELFED) {
params.put("pg", "true");
params.put("page", max_id);
@ -5767,6 +5803,133 @@ public class API {
return apiResponse;
}
/**
* Get subscribed push notifications
*
* @return APIResponse
*/
public APIResponse getPushSubscription() {
PushSubscription pushSubscription = null;
try {
String response = new HttpsConnection(context, this.instance).get(getAbsoluteUrl("/push/subscription"), 10, null, prefKeyOauthTokenT);
pushSubscription = parsePushNotifications(new JSONObject(response));
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
e.printStackTrace();
} catch (NoSuchAlgorithmException | IOException | KeyManagementException | JSONException e) {
e.printStackTrace();
}
apiResponse.setPushSubscription(pushSubscription);
return apiResponse;
}
/**
* Update subscribed push notifications
*
* @return APIResponse
*/
public APIResponse updatePushSubscription(String endpoint) {
PushSubscription pushSubscription = new PushSubscription();
final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true);
boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true);
boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true);
boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true);
boolean notif_status = sharedpreferences.getBoolean(Helper.SET_NOTIF_STATUS, true);
boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true);
HashMap<String, String> params = new HashMap<>();
try {
endpoint = URLEncoder.encode(endpoint, "UTF-8");
} catch (UnsupportedEncodingException ignored) {
}
params.put("subscription[endpoint]", endpoint);
params.put("data[alerts][follow]", String.valueOf(notif_follow));
params.put("data[alerts][mention]", String.valueOf(notif_mention));
params.put("data[alerts][favourite]", String.valueOf(notif_add));
params.put("data[alerts][reblog]", String.valueOf(notif_share));
params.put("data[alerts][poll]", String.valueOf(notif_poll));
try {
String response = new HttpsConnection(context, this.instance).put(getAbsoluteUrl("/push/subscription"), 10, params, prefKeyOauthTokenT);
pushSubscription = parsePushNotifications(new JSONObject(response));
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
e.printStackTrace();
} catch (NoSuchAlgorithmException | IOException | KeyManagementException | JSONException e) {
e.printStackTrace();
}
apiResponse.setPushSubscription(pushSubscription);
return apiResponse;
}
/**
* Subscribe to push notifications
*
* @return APIResponse
*/
public APIResponse pushSubscription(String endpoint, Account account) {
PushSubscription pushSubscription = new PushSubscription();
final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true);
boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true);
boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true);
boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true);
boolean notif_status = sharedpreferences.getBoolean(Helper.SET_NOTIF_STATUS, true);
boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true);
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
String strPub = prefs.getString(kp_public, "");
String strPriv = prefs.getString(kp_private, "");
ECDH ecdh = ECDH.getInstance();
if (strPub.trim().isEmpty() || strPriv.trim().isEmpty()) {
ecdh.newPair(context);
}
String pubKey = ecdh.getPublicKey(context);
byte[] randBytes = new byte[16];
new Random().nextBytes(randBytes);
String auth = ECDH.base64Encode(randBytes);
JSONObject jsonObject = new JSONObject();
try {
JSONObject jsonObjectSub = new JSONObject();
jsonObjectSub.put("endpoint", endpoint);
JSONObject jsonObjectkey = new JSONObject();
jsonObjectkey.put("p256dh", pubKey);
jsonObjectkey.put("auth", auth);
jsonObjectSub.put("keys", jsonObjectkey);
jsonObject.put("subscription", jsonObjectSub);
JSONObject jsonObjectdata = new JSONObject();
JSONObject jsonObjectarlerts = new JSONObject();
jsonObjectarlerts.put("follow", notif_follow);
jsonObjectarlerts.put("mention", notif_mention);
jsonObjectarlerts.put("favourite", notif_add);
jsonObjectarlerts.put("reblog", notif_share);
jsonObjectarlerts.put("poll", notif_poll);
jsonObjectdata.put("alerts", jsonObjectarlerts);
jsonObject.put("data", jsonObjectdata);
} catch (JSONException e) {
e.printStackTrace();
}
try {
String response = new HttpsConnection(context, this.instance).postJson(getAbsoluteUrl("/push/subscription"), 10, jsonObject, prefKeyOauthTokenT);
pushSubscription = parsePushNotifications(new JSONObject(response));
ecdh.saveServerKey(context, account, pushSubscription.getServer_key());
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
e.printStackTrace();
} catch (NoSuchAlgorithmException | IOException | KeyManagementException | JSONException e) {
e.printStackTrace();
}
apiResponse.setPushSubscription(pushSubscription);
return apiResponse;
}
/**
* Update a list by its id
*

View File

@ -35,6 +35,7 @@ import app.fedilab.android.client.Entities.PeertubeNotification;
import app.fedilab.android.client.Entities.PixelFedStory;
import app.fedilab.android.client.Entities.PixelFedStoryItem;
import app.fedilab.android.client.Entities.Playlist;
import app.fedilab.android.client.Entities.PushSubscription;
import app.fedilab.android.client.Entities.Relationship;
import app.fedilab.android.client.Entities.Report;
import app.fedilab.android.client.Entities.Results;
@ -56,6 +57,7 @@ public class APIResponse {
private List<Notification> notifications = null;
private List<Relationship> relationships = null;
private List<Announcement> announcements = null;
private PushSubscription pushSubscription;
private String targetedId = null;
private Results results = null;
private List<HowToVideo> howToVideos = null;
@ -85,6 +87,14 @@ public class APIResponse {
return accounts;
}
public PushSubscription getPushSubscription() {
return pushSubscription;
}
public void setPushSubscription(PushSubscription pushSubscription) {
this.pushSubscription = pushSubscription;
}
public void setAccounts(List<Account> accounts) {
this.accounts = accounts;
}

View File

@ -828,6 +828,37 @@ public class Account implements Parcelable {
}
}, matchStart, matchEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
matcher = Pattern.compile("gemini://([\\d\\w.-]*)(:\\d+)?(/\\S*)?").matcher(fieldSpan);
while (matcher.find()) {
URLSpan[] urls = fieldSpan.getSpans(0, fieldSpan.length(), URLSpan.class);
for (URLSpan span : urls)
fieldSpan.removeSpan(span);
int matchStart = matcher.start(0);
int matchEnd = matcher.end();
final String url = fieldSpan.toString().substring(matchStart, matchEnd);
if (matchStart >= 0 && matchEnd <= fieldSpan.toString().length() && matchEnd >= matchStart) {
fieldSpan.setSpan(new ClickableSpan() {
@Override
public void onClick(@NonNull View textView) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
context.startActivity(intent);
} catch (Exception e) {
Toasty.error(context, context.getString(R.string.toast_no_apps), Toast.LENGTH_LONG).show();
}
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(false);
ds.setColor(link_color);
}
}, matchStart, matchEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
fieldsSpan.put(keySpan, fieldSpan);
}
}
if (accountsMentionUnknown.size() > 0) {
for (Account accountMention : accountsMentionUnknown) {
String targetedAccount = "@" + accountMention.getAcct();

View File

@ -0,0 +1,56 @@
package app.fedilab.android.client.Entities;
/* 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 java.util.HashMap;
public class PushSubscription {
private String id;
private String endpoint;
private HashMap<String, Boolean> alerts;
private String server_key;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public HashMap<String, Boolean> getAlertsList() {
return alerts;
}
public void setAlertsList(HashMap<String, Boolean> alertsList) {
this.alerts = alertsList;
}
public String getServer_key() {
return server_key;
}
public void setServer_key(String server_key) {
this.server_key = server_key;
}
}

View File

@ -665,9 +665,7 @@ public class Status implements Parcelable {
intent.putExtras(b);
context.startActivity(intent);
} else {
if (!url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://"))
finalUrl = "http://" + url;
Helper.openBrowser(context, finalUrl);
Helper.openBrowser(context, url);
}
}
@ -891,10 +889,7 @@ public class Status implements Parcelable {
contentSpanTranslated.setSpan(new ClickableSpan() {
@Override
public void onClick(@NonNull View textView) {
String finalUrl = url;
if (!url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://"))
finalUrl = "http://" + url;
Helper.openBrowser(context, finalUrl);
Helper.openBrowser(context, url);
}
@Override

View File

@ -474,6 +474,63 @@ public class HttpsConnection {
}
String postJson(String urlConnection, int timeout, JSONObject jsonObject, String token) throws IOException, NoSuchAlgorithmException, KeyManagementException, HttpsConnectionException {
URL url = new URL(urlConnection);
byte[] postDataBytes;
postDataBytes = jsonObject.toString().getBytes(StandardCharsets.UTF_8);
if (proxy != null)
httpURLConnection = (HttpURLConnection) url.openConnection(proxy);
else
httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setRequestProperty("User-Agent", USER_AGENT);
httpURLConnection.setConnectTimeout(timeout * 1000);
httpURLConnection.setDoOutput(true);
if (httpURLConnection instanceof HttpsURLConnection) {
((HttpsURLConnection) httpURLConnection).setSSLSocketFactory(new TLSSocketFactory(this.instance));
}
httpURLConnection.setRequestProperty("Content-Type", "application/json");
httpURLConnection.setRequestProperty("Accept", "application/json");
httpURLConnection.setRequestMethod("POST");
setToken(token);
httpURLConnection.setRequestProperty("Content-Length", String.valueOf(postDataBytes.length));
httpURLConnection.getOutputStream().write(postDataBytes);
String response;
if (httpURLConnection.getResponseCode() >= 200 && httpURLConnection.getResponseCode() < 400) {
getSinceMaxId();
response = converToString(httpURLConnection.getInputStream());
} else {
String error = null;
if (httpURLConnection.getErrorStream() != null) {
InputStream stream = httpURLConnection.getErrorStream();
if (stream == null) {
stream = httpURLConnection.getInputStream();
}
try (Scanner scanner = new Scanner(stream)) {
scanner.useDelimiter("\\Z");
if (scanner.hasNext()) {
error = scanner.next();
}
} catch (Exception e) {
e.printStackTrace();
}
}
int responseCode = httpURLConnection.getResponseCode();
try {
httpURLConnection.getInputStream().close();
} catch (Exception ignored) {
}
throw new HttpsConnectionException(responseCode, error);
}
getSinceMaxId();
httpURLConnection.getInputStream().close();
return response;
}
@SuppressWarnings("SameParameterValue")
String postMisskey(String urlConnection, int timeout, JSONObject paramaters, String token) throws IOException, NoSuchAlgorithmException, KeyManagementException, HttpsConnectionException {
URL url = new URL(urlConnection);

View File

@ -1660,8 +1660,8 @@ public class PeertubeAPI {
String errorM = jsonObject.get("error").toString();
message = "Error " + statusCode + " : " + errorM;
} catch (JSONException e) {
if (error.getMessage().split(".").length > 0) {
String errorM = error.getMessage().split(".")[0];
if (error.getMessage().split("\\.").length > 0) {
String errorM = error.getMessage().split("\\.")[0];
message = "Error " + statusCode + " : " + errorM;
}
}

View File

@ -31,7 +31,6 @@ import java.util.List;
import app.fedilab.android.R;
import app.fedilab.android.client.Entities.Account;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.services.LiveNotificationDelayedService;
import static android.content.Context.MODE_PRIVATE;
@ -72,12 +71,6 @@ public class AccountLiveAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putBoolean(Helper.SET_ALLOW_STREAM + accounts.get(i).getId() + accounts.get(i).getInstance(), holder.account_acct_live_notifications.isChecked());
editor.apply();
if (holder.account_acct_live_notifications.isChecked()) {
LiveNotificationDelayedService.totalAccount++;
} else {
LiveNotificationDelayedService.totalAccount--;
}
Helper.startStreaming(context);
});
}

View File

@ -632,18 +632,36 @@ public abstract class BaseStatusListAdapter extends RecyclerView.Adapter<Recycle
String v = status.getVisibility();
holder.status_privacy.setContentDescription(context.getString(R.string.toot_visibility_tilte) + ": " + v);
});
holder.decoration_container.removeAllViews();
if (holder.decoration_container != null) {
holder.decoration_container.removeAllViews();
}
if (type == RetrieveFeedsAsyncTask.Type.CONTEXT && holder.decoration_container != null && status.getIn_reply_to_id() != null) {
int ident = CommentDecorationHelper.getIndentation(status.getIn_reply_to_id(), statuses);
for (int j = 0; j <= ident; j++) {
View view = new View(context);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(1, LinearLayout.LayoutParams.MATCH_PARENT);
params.setMargins((int) Helper.convertDpToPixel(5, context), 0, 0, 0);
params.setMargins((int) Helper.convertDpToPixel(2, context), 0, 0, 0);
view.setLayoutParams(params);
holder.decoration_container.addView(view, 0);
view.setBackgroundResource(R.color.cyanea_accent_reference);
int colorPosition = (ident - j) % 7 + 1;
int color = R.color.cyanea_accent_reference;
if (colorPosition == 1) {
color = R.color.rainbow_1;
} else if (colorPosition == 2) {
color = R.color.rainbow_2;
} else if (colorPosition == 3) {
color = R.color.rainbow_3;
} else if (colorPosition == 4) {
color = R.color.rainbow_4;
} else if (colorPosition == 5) {
color = R.color.rainbow_5;
} else if (colorPosition == 6) {
color = R.color.rainbow_6;
} else if (colorPosition == 7) {
color = R.color.rainbow_7;
}
view.setBackgroundResource(color);
}
}

View File

@ -16,8 +16,6 @@ package app.fedilab.android.fragments;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.TimePickerDialog;
import android.content.ComponentName;
import android.content.ContentUris;
@ -29,7 +27,6 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
@ -74,7 +71,6 @@ import java.util.Set;
import app.fedilab.android.R;
import app.fedilab.android.activities.BaseMainActivity;
import app.fedilab.android.activities.LiveNotificationSettingsAccountsActivity;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.asynctasks.RetrieveRelationshipAsyncTask;
import app.fedilab.android.asynctasks.RetrieveRemoteDataAsyncTask;
@ -88,14 +84,10 @@ import app.fedilab.android.drawers.AccountSearchDevAdapter;
import app.fedilab.android.filelister.FileListerDialog;
import app.fedilab.android.helper.ExpandableHeightListView;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.PushHelper;
import app.fedilab.android.interfaces.OnRetrieveRelationshipInterface;
import app.fedilab.android.interfaces.OnRetrieveRemoteAccountInterface;
import app.fedilab.android.jobs.ApplicationJob;
import app.fedilab.android.jobs.NotificationsSyncJob;
import app.fedilab.android.services.DownloadTrackingDBScriptsService;
import app.fedilab.android.services.LiveNotificationDelayedService;
import app.fedilab.android.services.StopDelayedNotificationReceiver;
import app.fedilab.android.services.StopLiveNotificationReceiver;
import app.fedilab.android.sqlite.AccountDAO;
import app.fedilab.android.sqlite.MainMenuDAO;
import app.fedilab.android.sqlite.Sqlite;
@ -637,23 +629,14 @@ public class ContentSettingsFragment extends Fragment implements OnRetrieveRemot
final Button sound_media = rootView.findViewById(R.id.sound_media);
final Button sound_status = rootView.findViewById(R.id.sound_status);
final ImageButton set_hide_status_bar = rootView.findViewById(R.id.set_hide_status_bar);
Button set_notif_sound = rootView.findViewById(R.id.set_notif_sound);
settings_time_from.setText(time_from);
settings_time_to.setText(time_to);
final LinearLayout set_hide_status_bar_container = rootView.findViewById(R.id.set_hide_status_bar_container);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
set_notif_sound.setVisibility(View.GONE);
channels_container.setVisibility(View.VISIBLE);
set_hide_status_bar_container.setVisibility(View.VISIBLE);
set_hide_status_bar.setOnClickListener(v -> {
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
intent.putExtra(Settings.EXTRA_CHANNEL_ID, LiveNotificationDelayedService.CHANNEL_ID);
startActivity(intent);
});
sound_boost.setOnClickListener(v -> {
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
@ -923,36 +906,6 @@ public class ContentSettingsFragment extends Fragment implements OnRetrieveRemot
}
}
boolean allow_live_notifications = sharedpreferences.getBoolean(Helper.SET_ALLOW_STREAM + userId + instance, true);
TextView set_allow_live_notifications_title = rootView.findViewById(R.id.set_allow_live_notifications_title);
if (account != null) {
set_allow_live_notifications_title.setText(context.getString(R.string.set_allow_live_notifications, account.getAcct() + "@" + account.getInstance()));
}
final SwitchCompat set_allow_live_notifications = rootView.findViewById(R.id.set_allow_live_notifications);
set_allow_live_notifications.setChecked(allow_live_notifications);
set_allow_live_notifications.setOnClickListener(v -> {
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putBoolean(Helper.SET_ALLOW_STREAM + userId + instance, set_allow_live_notifications.isChecked());
editor.apply();
if (set_allow_live_notifications.isChecked()) {
LiveNotificationDelayedService.totalAccount++;
} else {
LiveNotificationDelayedService.totalAccount--;
}
if (set_allow_live_notifications.isChecked()) {
LiveNotificationDelayedService.totalAccount++;
} else {
LiveNotificationDelayedService.totalAccount--;
}
Helper.startStreaming(context);
});
final ImageButton set_allow_live_notifications_others = rootView.findViewById(R.id.set_allow_live_notifications_others);
set_allow_live_notifications_others.setOnClickListener(view -> {
Intent intent = new Intent(context, LiveNotificationSettingsAccountsActivity.class);
startActivity(intent);
});
boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true);
@ -971,35 +924,22 @@ public class ContentSettingsFragment extends Fragment implements OnRetrieveRemot
editor.apply();
if (isChecked) {
notification_container.setVisibility(View.VISIBLE);
notification_container.setVisibility(View.VISIBLE);
Helper.startStreaming(context);
} else {
context.sendBroadcast(new Intent(context, StopLiveNotificationReceiver.class));
context.sendBroadcast(new Intent(context, StopDelayedNotificationReceiver.class));
ApplicationJob.cancelAllJob(NotificationsSyncJob.NOTIFICATION_REFRESH);
notification_container.setVisibility(View.GONE);
}
PushHelper.startStreaming(context);
});
//Live notification mode
final Spinner set_live_type = rootView.findViewById(R.id.set_live_type);
String[] labels = {context.getString(R.string.live_notif), context.getString(R.string.live_delayed), context.getString(R.string.no_live_notif)};
ArrayAdapter<String> adapterLive = new ArrayAdapter<>(Objects.requireNonNull(getActivity()),
android.R.layout.simple_spinner_dropdown_item, labels);
String[] labels = {context.getString(R.string.push_notif), context.getString(R.string.no_live_notif)};
LinearLayout live_notif_per_account = rootView.findViewById(R.id.live_notif_per_account);
set_live_type.setAdapter(adapterLive);
if (Helper.liveNotifType(context) == Helper.NOTIF_NONE) {
live_notif_per_account.setVisibility(View.GONE);
}
TextView set_live_type_indication = rootView.findViewById(R.id.set_live_type_indication);
switch (Helper.liveNotifType(context)) {
case Helper.NOTIF_LIVE:
set_live_type_indication.setText(R.string.live_notif_indication);
break;
case Helper.NOTIF_DELAYED:
set_live_type_indication.setText(R.string.set_live_type_indication);
case Helper.NOTIF_PUSH:
set_live_type_indication.setText(R.string.set_push_notifications);
break;
case Helper.NOTIF_NONE:
set_live_type_indication.setText(R.string.no_live_indication);
@ -1008,40 +948,25 @@ public class ContentSettingsFragment extends Fragment implements OnRetrieveRemot
set_live_type.setSelection(Helper.liveNotifType(context));
liveNotificationCount = 0;
set_live_type.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@SuppressLint("ApplySharedPref")
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (liveNotificationCount > 0) {
SharedPreferences.Editor editor = sharedpreferences.edit();
context.sendBroadcast(new Intent(context, StopLiveNotificationReceiver.class));
context.sendBroadcast(new Intent(context, StopDelayedNotificationReceiver.class));
ApplicationJob.cancelAllJob(NotificationsSyncJob.NOTIFICATION_REFRESH);
switch (position) {
case Helper.NOTIF_LIVE:
editor.putBoolean(Helper.SET_LIVE_NOTIFICATIONS, true);
editor.putBoolean(Helper.SET_DELAYED_NOTIFICATIONS, false);
live_notif_per_account.setVisibility(View.VISIBLE);
case Helper.NOTIF_PUSH:
editor.putBoolean(Helper.SET_PUSH_NOTIFICATIONS, true);
editor.commit();
set_live_type_indication.setText(R.string.live_notif_indication);
Helper.startStreaming(context);
break;
case Helper.NOTIF_DELAYED:
editor.putBoolean(Helper.SET_LIVE_NOTIFICATIONS, false);
editor.putBoolean(Helper.SET_DELAYED_NOTIFICATIONS, true);
live_notif_per_account.setVisibility(View.VISIBLE);
set_live_type_indication.setText(R.string.set_live_type_indication);
editor.commit();
Helper.startStreaming(context);
set_live_type_indication.setText(R.string.set_push_notifications);
break;
case Helper.NOTIF_NONE:
editor.putBoolean(Helper.SET_LIVE_NOTIFICATIONS, false);
editor.putBoolean(Helper.SET_DELAYED_NOTIFICATIONS, false);
editor.putBoolean(Helper.SET_PUSH_NOTIFICATIONS, false);
editor.commit();
set_live_type_indication.setText(R.string.no_live_indication);
live_notif_per_account.setVisibility(View.GONE);
NotificationsSyncJob.schedule(false);
break;
}
PushHelper.startStreaming(context);
} else {
liveNotificationCount++;
}

View File

@ -20,7 +20,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@ -55,12 +54,10 @@ import app.fedilab.android.drawers.NotificationsListAdapter;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.interfaces.OnRetrieveMissingNotificationsInterface;
import app.fedilab.android.interfaces.OnRetrieveNotificationsInterface;
import app.fedilab.android.services.LiveNotificationDelayedService;
import app.fedilab.android.sqlite.AccountDAO;
import app.fedilab.android.sqlite.Sqlite;
import es.dmoral.toasty.Toasty;
import static android.content.Context.MODE_PRIVATE;
import static app.fedilab.android.activities.BaseMainActivity.countNewNotifications;
@ -314,12 +311,6 @@ public class DisplayNotificationsFragment extends Fragment implements OnRetrieve
if (type == Type.ALL) {
if (lastReadNotifications != null && notifications.get(0).getId().compareTo(lastReadNotifications) > 0) {
countNewNotifications++;
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE);
String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null);
String instance = sharedpreferences.getString(Helper.PREF_INSTANCE, Helper.getLiveInstance(context));
SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
Account accountdb = new AccountDAO(context, db).getUniqAccount(userId, instance);
LiveNotificationDelayedService.since_ids.put(accountdb.getAcct() + "@" + accountdb.getInstance(), notifications.get(0).getId());
}
}
for (Notification tmpNotification : notifications) {
@ -446,14 +437,6 @@ public class DisplayNotificationsFragment extends Fragment implements OnRetrieve
MainActivity.lastNotificationId = notifications.get(0).getId();
updateNotificationLastId(notifications.get(0).getId());
}
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE);
String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null);
String instance = sharedpreferences.getString(Helper.PREF_INSTANCE, Helper.getLiveInstance(context));
SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
Account account = new AccountDAO(context, db).getUniqAccount(userId, instance);
if (MainActivity.lastNotificationId != null && notifications.get(0).getId().compareTo(MainActivity.lastNotificationId) > 0) {
LiveNotificationDelayedService.since_ids.put(account.getAcct() + "@" + account.getInstance(), notifications.get(0).getId());
}
}
if (textviewNoAction.getVisibility() == View.VISIBLE) {
textviewNoAction.setVisibility(View.GONE);

View File

@ -96,6 +96,9 @@ import app.fedilab.android.sqlite.SearchDAO;
import app.fedilab.android.sqlite.Sqlite;
import es.dmoral.toasty.Toasty;
import static app.fedilab.android.helper.BaseHelper.NOTIF_PUSH;
import static app.fedilab.android.helper.BaseHelper.liveNotifType;
/**
* Created by Thomas on 24/04/2017.
@ -756,7 +759,7 @@ public class DisplayStatusFragment extends Fragment implements OnPostActionInter
public void onResume() {
super.onResume();
swipeRefreshLayout.setEnabled(true);
boolean liveNotifications = sharedpreferences.getBoolean(Helper.SET_LIVE_NOTIFICATIONS, true);
boolean liveNotifications = liveNotifType(context) == NOTIF_PUSH;
if (type == RetrieveFeedsAsyncTask.Type.HOME || type == RetrieveFeedsAsyncTask.Type.PF_HOME) {
if (this.isVisible()) {
if (statuses != null && statuses.size() > 0) {
@ -861,7 +864,7 @@ public class DisplayStatusFragment extends Fragment implements OnPostActionInter
super.setMenuVisibility(visible);
if (context == null)
return;
int liveNotifications = Helper.liveNotifType(context);
int liveNotifications = liveNotifType(context);
//Store last toot id for home timeline to avoid to notify for those that have been already seen
if (type == RetrieveFeedsAsyncTask.Type.HOME || type == RetrieveFeedsAsyncTask.Type.PF_HOME) {
if (visible) {

View File

@ -211,8 +211,8 @@ import app.fedilab.android.client.Entities.Status;
import app.fedilab.android.client.Entities.Tag;
import app.fedilab.android.client.Entities.TagTimeline;
import app.fedilab.android.client.Tls12SocketFactory;
import app.fedilab.android.services.LiveNotificationDelayedService;
import app.fedilab.android.services.LiveNotificationService;
import app.fedilab.android.jobs.ApplicationJob;
import app.fedilab.android.jobs.NotificationsSyncJob;
import app.fedilab.android.sqlite.AccountDAO;
import app.fedilab.android.sqlite.MainMenuDAO;
import app.fedilab.android.sqlite.Sqlite;
@ -239,6 +239,7 @@ import static app.fedilab.android.helper.ThemeHelper.getAttColor;
import static app.fedilab.android.sqlite.StatusCacheDAO.ARCHIVE_CACHE;
import static app.fedilab.android.sqlite.StatusCacheDAO.BOOKMARK_CACHE;
import static app.fedilab.android.webview.ProxyHelper.setProxy;
import static cafe.adriel.androidaudiorecorder.Util.formatSeconds;
import static com.koushikdutta.async.util.StreamUtility.copyStream;
@ -254,8 +255,8 @@ public class BaseHelper {
public static final String TAG = "mastodon_etalab";
public static final String CLIENT_NAME_VALUE = "Fedilab";
public static final String OAUTH_SCOPES = "read write follow";
public static final String OAUTH_SCOPES_ADMIN = "read write follow admin:read admin:write admin";
public static final String OAUTH_SCOPES = "read write follow push";
public static final String OAUTH_SCOPES_ADMIN = "read write follow push admin:read admin:write admin";
public static final String OAUTH_SCOPES_PEERTUBE = "user";
public static final String PREF_KEY_OAUTH_TOKEN = "oauth_token";
@ -277,6 +278,8 @@ public class BaseHelper {
//Some definitions
public static final String CLIENT_NAME = "client_name";
public static final String APP_PREFS = "app_prefs";
public static final String SERVER_KEY = "server_key";
public static final String SERVER_ENDPOINT = "server_endpoint";
public static final String ID = "id";
public static final String CLIENT_ID = "client_id";
public static final String CLIENT_SECRET = "client_secret";
@ -334,7 +337,7 @@ public class BaseHelper {
public static final String SET_TIME_FROM = "set_time_from";
public static final String SET_TIME_TO = "set_time_to";
public static final String SET_AUTO_STORE = "set_auto_store";
public static final String SET_POPUP_PUSH = "set_popup_push_new";
public static final String SET_POPUP_PUSH = "set_popup_push_new_push";
public static final String SET_POPUP_RELEASE_NOTES = "set_popup_push_release_notes";
public static final String SET_NSFW_TIMEOUT = "set_nsfw_timeout";
public static final String SET_MED_DESC_TIMEOUT = "set_med_desc_timeout";
@ -346,8 +349,7 @@ public class BaseHelper {
public static final String SET_SHOW_BOOSTS = "set_show_boost";
public static final String SET_SHOW_REPLIES = "set_show_replies";
public static final String SET_VIDEO_NSFW = "set_video_nsfw";
public static final String SET_LIVE_NOTIFICATIONS = "set_live_notifications";
public static final String SET_DELAYED_NOTIFICATIONS = "set_delayed_notifications";
public static final String SET_PUSH_NOTIFICATIONS = "set_push_notifications";
public static final String SET_DISABLE_GIF = "set_disable_gif";
public static final String SET_DISABLE_ANIMATED_EMOJI = "set_disable_animated_emoji";
public static final String SET_CAPITALIZE = "set_capitalize";
@ -404,8 +406,7 @@ public class BaseHelper {
public static final int THEME_DARK = 2;
public static final int THEME_BLACK = 3;
public static final int NOTIF_LIVE = 0;
public static final int NOTIF_DELAYED = 1;
public static final int NOTIF_PUSH = 3;
public static final int NOTIF_NONE = 2;
public static final int LED_COLOUR = 0;
@ -587,12 +588,9 @@ public class BaseHelper {
public static int liveNotifType(Context context) {
SharedPreferences sharedpreferences = context.getSharedPreferences(APP_PREFS, Context.MODE_PRIVATE);
boolean live = sharedpreferences.getBoolean(SET_LIVE_NOTIFICATIONS, false);
boolean delayed = sharedpreferences.getBoolean(SET_DELAYED_NOTIFICATIONS, true);
if (delayed) {
return NOTIF_DELAYED;
} else if (live) {
return NOTIF_LIVE;
boolean push = sharedpreferences.getBoolean(SET_PUSH_NOTIFICATIONS, false);
if (push) {
return NOTIF_PUSH;
} else {
return NOTIF_NONE;
}
@ -3166,7 +3164,7 @@ public class BaseHelper {
public static void openBrowser(Context context, String url) {
SharedPreferences sharedpreferences = context.getSharedPreferences(APP_PREFS, android.content.Context.MODE_PRIVATE);
boolean embedded_browser = sharedpreferences.getBoolean(SET_EMBEDDED_BROWSER, true);
if (embedded_browser) {
if (embedded_browser && !url.toLowerCase().startsWith("gemini://")) {
Intent intent = new Intent(context, WebviewActivity.class);
Bundle b = new Bundle();
String finalUrl = url;
@ -4329,29 +4327,6 @@ public class BaseHelper {
}
public static void startStreaming(Context context) {
int liveNotifications = liveNotifType(context);
Intent streamingIntent = null;
switch (liveNotifications) {
case NOTIF_LIVE:
streamingIntent = new Intent(context, LiveNotificationService.class);
break;
case NOTIF_DELAYED:
streamingIntent = new Intent(context, LiveNotificationDelayedService.class);
break;
}
if (streamingIntent != null) {
try {
if (Build.VERSION.SDK_INT >= 26) {
context.startForegroundService(streamingIntent);
} else {
context.startService(streamingIntent);
}
} catch (Exception ignored) {
context.startService(streamingIntent);
}
}
}
public static boolean canFetchList(Context context, Account account) {
SharedPreferences sharedpreferences = context.getSharedPreferences(APP_PREFS, Context.MODE_PRIVATE);

View File

@ -0,0 +1,298 @@
package app.fedilab.android.helper;
/* 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 android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.util.Base64;
import androidx.preference.PreferenceManager;
import org.spongycastle.asn1.ASN1ObjectIdentifier;
import org.spongycastle.asn1.x9.ECNamedCurveTable;
import org.spongycastle.asn1.x9.X9ECParameters;
import org.spongycastle.crypto.params.ECNamedDomainParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.jce.spec.ECNamedCurveSpec;
import org.spongycastle.jce.spec.ECParameterSpec;
import org.spongycastle.jce.spec.ECPrivateKeySpec;
import org.spongycastle.jce.spec.ECPublicKeySpec;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECPoint;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import app.fedilab.android.client.Entities.Account;
import app.fedilab.android.sqlite.AccountDAO;
import app.fedilab.android.sqlite.Sqlite;
public class ECDH {
public static final String kp_public = "kp_public";
public static final String peer_public = "peer_public";
public static final String PROVIDER = org.spongycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME;
public static final String kp_private = "kp_private";
public static final String KEGEN_ALG = "ECDH";
public static final String name = "prime256v1";
private static final String kp_public_affine_x = "kp_public_affine_x";
private static final String kp_public_affine_y = "kp_public_affine_y";
private static ECDH instance;
static {
Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider());
}
public final KeyFactory kf;
private final KeyPairGenerator kpg;
public ECDH() {
try {
kf = KeyFactory.getInstance(KEGEN_ALG, PROVIDER);
kpg = KeyPairGenerator.getInstance(KEGEN_ALG, PROVIDER);
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static synchronized ECDH getInstance() {
if (instance == null) {
instance = new ECDH();
}
return instance;
}
public static String base64Encode(byte[] b) {
byte[] encoded = Base64.encode(
b, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
return new String(encoded);
}
static byte[] base64Decode(String str) {
return Base64.decode(str, Base64.DEFAULT);
}
synchronized KeyPair generateKeyPair()
throws Exception {
ECGenParameterSpec ecParamSpec = new ECGenParameterSpec(name);
kpg.initialize(ecParamSpec);
return kpg.generateKeyPair();
}
private byte[] generateSecret(PrivateKey myPrivKey, PublicKey otherPubKey) throws Exception {
KeyAgreement keyAgreement = KeyAgreement.getInstance(KEGEN_ALG);
keyAgreement.init(myPrivKey);
keyAgreement.doPhase(otherPubKey, true);
return keyAgreement.generateSecret();
}
synchronized KeyPair readKeyPair(Context context)
throws Exception {
return new KeyPair(readMyPublicKey(context), readMyPrivateKey(context));
}
@SuppressLint("ApplySharedPref")
public KeyPair newPair(Context context) {
SharedPreferences.Editor prefsEditor = PreferenceManager
.getDefaultSharedPreferences(context).edit();
KeyPair kp;
try {
kp = generateKeyPair();
} catch (Exception e) {
e.printStackTrace();
return null;
}
ECPublicKey key = (ECPublicKey) kp.getPublic();
byte[] x = key.getW().getAffineX().toByteArray();
byte[] y = key.getW().getAffineY().toByteArray();
BigInteger xbi = new BigInteger(1, x);
BigInteger ybi = new BigInteger(1, y);
X9ECParameters x9 = ECNamedCurveTable.getByName(name);
ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID(name);
ECCurve curve = x9.getCurve();
ECPoint point = curve.createPoint(xbi, ybi);
ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid,
x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed());
ECPublicKeyParameters pubKey = new ECPublicKeyParameters(point, dParams);
ECPrivateKeyParameters privateKey = new ECPrivateKeyParameters(new BigInteger(kp.getPrivate().getEncoded()), pubKey.getParameters());
byte[] privateKeyBytes = privateKey.getD().toByteArray();
String keyString = base64Encode(pubKey.getQ().getEncoded(false));
String keypString = base64Encode(privateKeyBytes);
prefsEditor.putString(kp_public, keyString);
prefsEditor.putString(kp_public_affine_x, key.getW().getAffineX().toString());
prefsEditor.putString(kp_public_affine_y, key.getW().getAffineY().toString());
prefsEditor.putString(kp_private, keypString);
prefsEditor.commit();
return kp;
}
synchronized PublicKey readMyPublicKey(Context context) throws Exception {
X9ECParameters x9 = ECNamedCurveTable.getByName(name);
ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID(name);
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
BigInteger xbi = new BigInteger(prefs.getString(kp_public_affine_x, "0"));
BigInteger ybi = new BigInteger(prefs.getString(kp_public_affine_y, "0"));
ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid,
x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed());
ECNamedCurveSpec ecNamedCurveSpec = new ECNamedCurveSpec(name, dParams.getCurve(), dParams.getG(), dParams.getN());
java.security.spec.ECPoint w = new java.security.spec.ECPoint(xbi, ybi);
return kf.generatePublic(new java.security.spec.ECPublicKeySpec(w, ecNamedCurveSpec));
}
public String uncryptMessage(Context context, String cyphered, String slug) {
getInstance();
SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
String[] slugArray = slug.split("@");
Account account = new AccountDAO(context, db).getUniqAccountUsernameInstance(slugArray[0], slugArray[1]);
byte[] privateKey = getSharedSecret(context, account);
try {
Cipher outCipher = Cipher.getInstance("ECIES", PROVIDER);
// outCipher.init(Cipher.DECRYPT_MODE, readPrivateKey(privateKey));
outCipher.init(Cipher.DECRYPT_MODE, readPrivateKey(privateKey));
byte[] plaintext = outCipher.doFinal(Base64.decode(cyphered, Base64.DEFAULT));
String finalText = new String(plaintext);
return finalText;
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
public PublicKey readPublicKey(String keyStr) throws Exception {
ECParameterSpec parameterSpec = org.spongycastle.jce.ECNamedCurveTable.getParameterSpec(name);
ECCurve curve = parameterSpec.getCurve();
ECPoint point = curve.decodePoint(base64Decode(keyStr));
ECPublicKeySpec pubSpec = new ECPublicKeySpec(point, parameterSpec);
return kf.generatePublic(pubSpec);
}
public PrivateKey readPrivateKey(byte[] key) throws Exception {
ECParameterSpec parameterSpec = org.spongycastle.jce.ECNamedCurveTable.getParameterSpec(name);
ECPrivateKeySpec pubSpec = new ECPrivateKeySpec(new BigInteger(1, key), parameterSpec);
return kf.generatePrivate(pubSpec);
}
synchronized PrivateKey readMyPrivateKey(Context context) throws Exception {
X9ECParameters x9 = ECNamedCurveTable.getByName(name);
ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID(name);
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
BigInteger ybi = new BigInteger(prefs.getString(kp_public_affine_y, "0"));
ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid,
x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed());
ECNamedCurveSpec ecNamedCurveSpec = new ECNamedCurveSpec(name, dParams.getCurve(), dParams.getG(), dParams.getN());
return kf.generatePrivate(new java.security.spec.ECPrivateKeySpec(ybi, ecNamedCurveSpec));
}
private synchronized KeyPair getPair(Context context) {
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
String strPub = prefs.getString(kp_public, "");
String strPriv = prefs.getString(kp_private, "");
if (strPub.trim().isEmpty() || strPriv.trim().isEmpty()) {
return newPair(context);
}
try {
return readKeyPair(context);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
PublicKey getServerKey(Context context, Account account) throws Exception {
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
String serverKey = prefs.getString(peer_public + account.getId() + account.getInstance(), "");
return readPublicKey(serverKey);
}
@SuppressWarnings({"unused", "RedundantSuppression"})
public byte[] getSharedSecret(Context context, Account account) {
try {
KeyPair keyPair = getPair(context);
if (keyPair != null) {
return generateSecret(keyPair.getPrivate(), getServerKey(context, account));
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return null;
}
public String getPublicKey(Context context) {
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
return prefs.getString(kp_public, "");
}
@SuppressLint("ApplySharedPref")
public void saveServerKey(Context context, Account account, String strPeerPublic) {
SharedPreferences.Editor prefsEditor = PreferenceManager
.getDefaultSharedPreferences(context).edit();
prefsEditor.putString(peer_public + account.getId() + account.getInstance(), strPeerPublic);
prefsEditor.commit();
}
}

View File

@ -0,0 +1,311 @@
package app.fedilab.android.helper;
/* 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 android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.text.Html;
import android.text.SpannableString;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.transition.Transition;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.client.API;
import app.fedilab.android.client.APIResponse;
import app.fedilab.android.client.Entities.Account;
import app.fedilab.android.client.Entities.Notification;
import app.fedilab.android.client.GNUAPI;
import app.fedilab.android.fragments.DisplayNotificationsFragment;
import static android.text.Html.FROM_HTML_MODE_LEGACY;
import static app.fedilab.android.helper.BaseHelper.INTENT_ACTION;
import static app.fedilab.android.helper.BaseHelper.INTENT_TARGETED_ACCOUNT;
import static app.fedilab.android.helper.BaseHelper.NOTIFICATION_INTENT;
import static app.fedilab.android.helper.BaseHelper.PREF_INSTANCE;
import static app.fedilab.android.helper.BaseHelper.PREF_KEY_ID;
import static app.fedilab.android.helper.BaseHelper.getMainLogo;
import static app.fedilab.android.helper.BaseHelper.notify_user;
public class NotificationsHelper {
public static HashMap<String, String> since_ids = new HashMap<>();
public static void task(Context context, Account account) {
APIResponse apiResponse;
String key = account.getUsername() + "@" + account.getInstance();
String last_notifid = null;
if (since_ids.containsKey(key)) {
last_notifid = since_ids.get(key);
}
final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
//Check which notifications the user wants to see
boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true);
boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true);
boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true);
boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true);
boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true);
//User disagree with all notifications
if (!notif_follow && !notif_add && !notif_mention && !notif_share && !notif_poll)
return; //Nothing is done
//No account connected, the service is stopped
if (!Helper.isLoggedIn(context))
return;
//If WIFI only and on WIFI OR user defined any connections to use the service.
if (!sharedpreferences.getBoolean(Helper.SET_WIFI_ONLY, false) || Helper.isOnWIFI(context)) {
if (account.getSocial().compareTo("FRIENDICA") != 0 && account.getSocial().compareTo("GNU") != 0) {
API api = new API(context, account.getInstance(), account.getToken());
apiResponse = api.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, last_notifid, false);
} else {
GNUAPI gnuApi = new GNUAPI(context, account.getInstance(), account.getToken());
apiResponse = gnuApi.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, last_notifid);
}
onRetrieveNotifications(context, apiResponse, account);
}
}
public static void onRetrieveNotifications(Context context, APIResponse apiResponse, final Account account) {
List<Notification> notificationsReceived = apiResponse.getNotifications();
if (apiResponse.getError() != null || notificationsReceived == null || notificationsReceived.size() == 0 || account == null)
return;
String key = account.getUsername() + "@" + account.getInstance();
since_ids.put(key, apiResponse.getNotifications().get(0).getId());
final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true);
boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true);
boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true);
boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true);
boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true);
boolean notif_status = sharedpreferences.getBoolean(Helper.SET_NOTIF_STATUS, true);
final String max_id = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null);
final List<Notification> notifications = new ArrayList<>();
int pos = 0;
for (Notification notif : notificationsReceived) {
if (max_id == null || notif.getId().compareTo(max_id) > 0) {
notifications.add(pos, notif);
pos++;
}
}
if (notifications.size() == 0)
return;
//No previous notifications in cache, so no notification will be sent
int newFollows = 0;
int newAdds = 0;
int newMentions = 0;
int newShare = 0;
int newPolls = 0;
int newStatus = 0;
String notificationUrl = null;
String title = null;
String message = null;
String targeted_account = null;
Helper.NotifType notifType = Helper.NotifType.MENTION;
String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null);
for (Notification notification : notifications) {
switch (notification.getType()) {
case "mention":
notifType = Helper.NotifType.MENTION;
if (notif_mention) {
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
message = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_mention));
else
message = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_mention));
if (notification.getStatus() != null) {
if (notification.getStatus().getSpoiler_text() != null && notification.getStatus().getSpoiler_text().length() > 0) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text(), FROM_HTML_MODE_LEGACY));
else
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text()));
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent(), FROM_HTML_MODE_LEGACY));
else
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent()));
}
}
newFollows++;
}
break;
case "status":
notifType = Helper.NotifType.STATUS;
if (notif_status) {
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
message = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_status));
else
message = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_status));
if (notification.getStatus() != null) {
if (notification.getStatus().getSpoiler_text() != null && notification.getStatus().getSpoiler_text().length() > 0) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text(), FROM_HTML_MODE_LEGACY));
else
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text()));
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent(), FROM_HTML_MODE_LEGACY));
else
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent()));
}
}
newStatus++;
}
break;
case "reblog":
notifType = Helper.NotifType.BOOST;
if (notif_share) {
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
message = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_reblog));
else
message = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_reblog));
newShare++;
}
break;
case "favourite":
notifType = Helper.NotifType.FAV;
if (notif_add) {
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
message = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_favourite));
else
message = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_favourite));
newAdds++;
}
break;
case "follow_request":
notifType = Helper.NotifType.FOLLLOW;
if (notif_follow) {
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
message = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_follow_request));
else
message = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_follow_request));
targeted_account = notification.getAccount().getId();
newFollows++;
}
break;
case "follow":
notifType = Helper.NotifType.FOLLLOW;
if (notif_follow) {
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
message = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_follow));
else
message = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_follow));
targeted_account = notification.getAccount().getId();
newFollows++;
}
break;
case "poll":
notifType = Helper.NotifType.POLL;
if (notif_poll) {
if (notification.getAccount().getId() != null && notification.getAccount().getId().equals(userId))
message = context.getString(R.string.notif_poll_self);
else
message = context.getString(R.string.notif_poll);
newPolls++;
}
break;
default:
}
}
int allNotifCount = newFollows + newAdds + newMentions + newShare + newPolls + newStatus;
if (allNotifCount > 0) {
//Some others notification
final Intent intent = new Intent(context, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(INTENT_ACTION, NOTIFICATION_INTENT);
intent.putExtra(PREF_KEY_ID, account.getId());
if (targeted_account != null && notifType == Helper.NotifType.FOLLLOW)
intent.putExtra(INTENT_TARGETED_ACCOUNT, targeted_account);
intent.putExtra(PREF_INSTANCE, account.getInstance());
notificationUrl = notifications.get(0).getAccount().getAvatar();
if (notificationUrl != null) {
Handler mainHandler = new Handler(Looper.getMainLooper());
final String finalNotificationUrl = notificationUrl;
Helper.NotifType finalNotifType = notifType;
String finalMessage = message;
String finalMessage1 = message;
Runnable myRunnable = () -> Glide.with(context)
.asBitmap()
.load(finalNotificationUrl)
.listener(new RequestListener<Bitmap>() {
@Override
public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
return false;
}
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) {
notify_user(context, account, intent, BitmapFactory.decodeResource(context.getResources(),
getMainLogo(context)), finalNotifType, context.getString(R.string.top_notification), finalMessage1);
String lastNotif = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null);
if (lastNotif == null || notifications.get(0).getId().compareTo(lastNotif) > 0) {
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notifications.get(0).getId());
editor.apply();
}
return false;
}
})
.into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
notify_user(context, account, intent, resource, finalNotifType, context.getString(R.string.top_notification), finalMessage);
String lastNotif = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null);
if (lastNotif == null || notifications.get(0).getId().compareTo(lastNotif) > 0) {
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notifications.get(0).getId());
editor.apply();
}
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
});
mainHandler.post(myRunnable);
}
}
}
}

View File

@ -0,0 +1,37 @@
package app.fedilab.android.helper;
/* 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 android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import app.fedilab.android.asynctasks.PostSubscriptionAsyncTask;
import app.fedilab.android.client.Entities.Account;
import app.fedilab.android.sqlite.AccountDAO;
import app.fedilab.android.sqlite.Sqlite;
public class PushNotifications {
public void registerPushNotifications(Context context, String endpoint, String slug) {
SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
String[] slugArray = slug.split("@");
Account account = new AccountDAO(context, db).getUniqAccountUsernameInstance(slugArray[0], slugArray[1]);
new PostSubscriptionAsyncTask(context, account, endpoint);
}
}

View File

@ -15,54 +15,26 @@ package app.fedilab.android.jobs;
* see <http://www.gnu.org/licenses>. */
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.transition.Transition;
import com.evernote.android.job.Job;
import com.evernote.android.job.JobManager;
import com.evernote.android.job.JobRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.client.API;
import app.fedilab.android.client.APIResponse;
import app.fedilab.android.client.Entities.Account;
import app.fedilab.android.client.Entities.Notification;
import app.fedilab.android.client.GNUAPI;
import app.fedilab.android.fragments.DisplayNotificationsFragment;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.NotificationsHelper;
import app.fedilab.android.sqlite.AccountDAO;
import app.fedilab.android.sqlite.Sqlite;
import static app.fedilab.android.helper.Helper.INTENT_ACTION;
import static app.fedilab.android.helper.Helper.INTENT_TARGETED_ACCOUNT;
import static app.fedilab.android.helper.Helper.NOTIFICATION_INTENT;
import static app.fedilab.android.helper.Helper.PREF_INSTANCE;
import static app.fedilab.android.helper.Helper.PREF_KEY_ID;
import static app.fedilab.android.helper.Helper.canNotify;
import static app.fedilab.android.helper.Helper.getMainLogo;
import static app.fedilab.android.helper.Helper.notify_user;
/**
@ -137,223 +109,8 @@ public class BaseNotificationsSyncJob extends Job {
return;
//Retrieve users in db that owner has.
for (Account account : accounts) {
APIResponse apiResponse;
if (account.getSocial().compareTo("FRIENDICA") != 0 && account.getSocial().compareTo("GNU") != 0) {
API api = new API(getContext(), account.getInstance(), account.getToken());
apiResponse = api.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null, false);
} else {
GNUAPI gnuApi = new GNUAPI(getContext(), account.getInstance(), account.getToken());
apiResponse = gnuApi.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null);
}
onRetrieveNotifications(apiResponse, account);
NotificationsHelper.task(getContext(), account);
}
}
}
private void onRetrieveNotifications(APIResponse apiResponse, final Account account) {
List<Notification> notificationsReceived = apiResponse.getNotifications();
if (apiResponse.getError() != null || notificationsReceived == null || notificationsReceived.size() == 0 || account == null)
return;
final SharedPreferences sharedpreferences = getContext().getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true);
boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true);
boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true);
boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true);
boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true);
boolean notif_status = sharedpreferences.getBoolean(Helper.SET_NOTIF_STATUS, true);
final String max_id = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null);
final List<Notification> notifications = new ArrayList<>();
int pos = 0;
for (Notification notif : notificationsReceived) {
if (max_id == null || notif.getId().compareTo(max_id) > 0) {
notifications.add(pos, notif);
pos++;
}
}
if (notifications.size() == 0)
return;
//No previous notifications in cache, so no notification will be sent
int newFollows = 0;
int newAdds = 0;
int newMentions = 0;
int newShare = 0;
int newPolls = 0;
int newStatus = 0;
String notificationUrl = null;
String title = null;
final String message;
String targeted_account = null;
Helper.NotifType notifType = Helper.NotifType.MENTION;
for (Notification notification : notifications) {
switch (notification.getType()) {
case "mention":
notifType = Helper.NotifType.MENTION;
if (notif_mention) {
newMentions++;
if (notificationUrl == null) {
notificationUrl = notification.getAccount().getAvatar();
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
title = String.format("%s %s", notification.getAccount().getDisplay_name(), getContext().getString(R.string.notif_mention));
else
title = String.format("@%s %s", notification.getAccount().getAcct(), getContext().getString(R.string.notif_mention));
}
}
break;
case "status":
notifType = Helper.NotifType.STATUS;
if (notif_status) {
newStatus++;
if (notificationUrl == null) {
notificationUrl = notification.getAccount().getAvatar();
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
title = String.format("%s %s", notification.getAccount().getDisplay_name(), getContext().getString(R.string.notif_status));
else
title = String.format("@%s %s", notification.getAccount().getAcct(), getContext().getString(R.string.notif_status));
}
}
break;
case "reblog":
notifType = Helper.NotifType.BOOST;
if (notif_share) {
newShare++;
if (notificationUrl == null) {
notificationUrl = notification.getAccount().getAvatar();
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
title = String.format("%s %s", notification.getAccount().getDisplay_name(), getContext().getString(R.string.notif_reblog));
else
title = String.format("@%s %s", notification.getAccount().getAcct(), getContext().getString(R.string.notif_reblog));
}
}
break;
case "favourite":
notifType = Helper.NotifType.FAV;
if (notif_add) {
newAdds++;
if (notificationUrl == null) {
notificationUrl = notification.getAccount().getAvatar();
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
title = String.format("%s %s", notification.getAccount().getDisplay_name(), getContext().getString(R.string.notif_favourite));
else
title = String.format("@%s %s", notification.getAccount().getAcct(), getContext().getString(R.string.notif_favourite));
}
}
break;
case "follow_request":
notifType = Helper.NotifType.FOLLLOW;
if (notif_follow) {
newFollows++;
if (notificationUrl == null) {
notificationUrl = notification.getAccount().getAvatar();
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
title = String.format("%s %s", notification.getAccount().getDisplay_name(), getContext().getString(R.string.notif_follow_request));
else
title = String.format("@%s %s", notification.getAccount().getAcct(), getContext().getString(R.string.notif_follow_request));
targeted_account = notification.getAccount().getId();
}
}
break;
case "follow":
notifType = Helper.NotifType.FOLLLOW;
if (notif_follow) {
newFollows++;
if (notificationUrl == null) {
notificationUrl = notification.getAccount().getAvatar();
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
title = String.format("%s %s", notification.getAccount().getDisplay_name(), getContext().getString(R.string.notif_follow));
else
title = String.format("@%s %s", notification.getAccount().getAcct(), getContext().getString(R.string.notif_follow));
targeted_account = notification.getAccount().getId();
}
}
break;
case "poll":
notifType = Helper.NotifType.POLL;
if (notif_poll) {
newPolls++;
if (notificationUrl == null) {
notificationUrl = notification.getAccount().getAvatar();
String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null);
if (notification.getAccount().getId() != null && notification.getAccount().getId().equals(userId))
title = getContext().getString(R.string.notif_poll_self);
else
title = getContext().getString(R.string.notif_poll);
}
}
break;
default:
}
}
int allNotifCount = newFollows + newAdds + newMentions + newShare + newPolls + newStatus;
if (allNotifCount > 0) {
//Some others notification
int other = allNotifCount - 1;
if (other > 0)
message = getContext().getResources().getQuantityString(R.plurals.other_notifications, other, other);
else
message = "";
final Intent intent = new Intent(getContext(), MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(INTENT_ACTION, NOTIFICATION_INTENT);
intent.putExtra(PREF_KEY_ID, account.getId());
if (targeted_account != null && notifType == Helper.NotifType.FOLLLOW)
intent.putExtra(INTENT_TARGETED_ACCOUNT, targeted_account);
intent.putExtra(PREF_INSTANCE, account.getInstance());
if (notificationUrl != null) {
final String finalTitle = title;
Handler mainHandler = new Handler(Looper.getMainLooper());
final String finalNotificationUrl = notificationUrl;
Helper.NotifType finalNotifType = notifType;
Runnable myRunnable = () -> Glide.with(getContext())
.asBitmap()
.load(finalNotificationUrl)
.listener(new RequestListener<Bitmap>() {
@Override
public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
return false;
}
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) {
notify_user(getContext(), account, intent, BitmapFactory.decodeResource(getContext().getResources(),
getMainLogo(getContext())), finalNotifType, finalTitle, message);
String lastNotif = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null);
if (lastNotif == null || notifications.get(0).getId().compareTo(lastNotif) > 0) {
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notifications.get(0).getId());
editor.apply();
}
return false;
}
})
.into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
notify_user(getContext(), account, intent, resource, finalNotifType, finalTitle, message);
String lastNotif = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null);
if (lastNotif == null || notifications.get(0).getId().compareTo(lastNotif) > 0) {
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notifications.get(0).getId());
editor.apply();
}
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
});
mainHandler.post(myRunnable);
}
}
}
}

View File

@ -1,536 +0,0 @@
package app.fedilab.android.services;
/* Copyright 2017 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.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.preference.PreferenceManager;
import android.text.Html;
import android.text.SpannableString;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.transition.Transition;
import com.koushikdutta.async.http.AsyncHttpClient;
import com.koushikdutta.async.http.AsyncHttpRequest;
import com.koushikdutta.async.http.Headers;
import com.koushikdutta.async.http.WebSocket;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.client.API;
import app.fedilab.android.client.Entities.Account;
import app.fedilab.android.client.Entities.Notification;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.sqlite.AccountDAO;
import app.fedilab.android.sqlite.Sqlite;
import static androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY;
import static app.fedilab.android.helper.Helper.getMainLogo;
import static app.fedilab.android.helper.Helper.getNotificationIcon;
/**
* Created by Thomas on 29/11/2017.
* Manage service for streaming api and new notifications
*/
public abstract class BaseLiveNotificationService extends Service implements NetworkStateReceiver.NetworkStateReceiverListener {
public static String CHANNEL_ID = "live_notifications";
public static int totalAccount = 0;
public static int eventsCount = 0;
private static final HashMap<String, String> lastNotification = new HashMap<>();
private static final HashMap<String, WebSocket> webSocketFutures = new HashMap<>();
protected Account account;
private NetworkStateReceiver networkStateReceiver;
private NotificationChannel channel;
public void onCreate() {
super.onCreate();
final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true);
networkStateReceiver = new NetworkStateReceiver();
networkStateReceiver.addListener(this);
registerReceiver(networkStateReceiver, new IntentFilter(android.net.ConnectivityManager.CONNECTIVITY_ACTION));
if (Build.VERSION.SDK_INT >= 26) {
channel = new NotificationChannel(CHANNEL_ID,
"Live notifications",
NotificationManager.IMPORTANCE_DEFAULT);
((NotificationManager) Objects.requireNonNull(getSystemService(Context.NOTIFICATION_SERVICE))).createNotificationChannel(channel);
}
SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
List<Account> accountStreams = new AccountDAO(BaseLiveNotificationService.this, db).getAllAccountCrossAction();
totalAccount = 0;
if (accountStreams != null) {
for (Account account : accountStreams) {
if (account.getSocial() == null || account.getSocial().equals("MASTODON") || account.getSocial().equals("PLEROMA")) {
boolean allowStream = sharedpreferences.getBoolean(Helper.SET_ALLOW_STREAM + account.getId() + account.getInstance(), true);
if (allowStream) {
totalAccount++;
}
}
}
}
if (Build.VERSION.SDK_INT >= 26) {
Intent myIntent = new Intent(BaseLiveNotificationService.this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(
BaseLiveNotificationService.this,
0,
myIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
android.app.Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(getString(R.string.top_notification))
.setContentIntent(pendingIntent)
.setSmallIcon(getNotificationIcon(BaseLiveNotificationService.this))
.setContentText(getString(R.string.top_notification_message, String.valueOf(totalAccount), String.valueOf(eventsCount))).build();
if (notification != null) {
startForeground(2, notification);
} else {
return;
}
}
if (!notify) {
stopSelf();
return;
}
if (totalAccount == 0) {
stopSelf();
}
}
private void startStream() {
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
boolean liveNotifications = sharedpreferences.getBoolean(Helper.SET_LIVE_NOTIFICATIONS, true);
SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
if (liveNotifications) {
List<Account> accountStreams = new AccountDAO(BaseLiveNotificationService.this, db).getAllAccountCrossAction();
if (accountStreams != null) {
for (final Account accountStream : accountStreams) {
if (accountStream.getSocial() == null || accountStream.getSocial().equals("MASTODON") || accountStream.getSocial().equals("PLEROMA")) {
startWork(accountStream);
}
}
}
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true);
if (!notify || intent == null || intent.getBooleanExtra("stop", false)) {
totalAccount = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
stopForeground(STOP_FOREGROUND_DETACH);
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
assert notificationManager != null;
notificationManager.deleteNotificationChannel(CHANNEL_ID);
}
if (intent != null) {
intent.replaceExtras(new Bundle());
}
stopSelf();
}
if (totalAccount > 0) {
return START_STICKY;
}
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
for (Thread t : Thread.getAllStackTraces().keySet()) {
if (t.getName().startsWith("notif_live_")) {
t.interrupt();
}
}
Thread.currentThread().interrupt();
if (networkStateReceiver != null) {
networkStateReceiver.removeListener(this);
unregisterReceiver(networkStateReceiver);
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void taks(Account account) {
if (account != null) {
Headers headers = new Headers();
headers.add("Authorization", "Bearer " + account.getToken());
headers.add("Connection", "Keep-Alive");
headers.add("method", "GET");
headers.add("scheme", "https");
String notif_url = "user:notification";
if (account.getSocial().toUpperCase().equals("PLEROMA"))
notif_url = "user";
String urlKey = "wss://" + account.getInstance() + "/api/v1/streaming/?stream=" + notif_url + "&access_token=" + account.getToken();
Uri url = Uri.parse(urlKey);
AsyncHttpRequest.setDefaultHeaders(headers, url);
String key = account.getAcct() + "@" + account.getInstance();
if (webSocketFutures.get(key) == null || !Objects.requireNonNull(webSocketFutures.get(key)).isOpen()) {
AsyncHttpClient.getDefaultInstance().websocket("wss://" + account.getInstance() + "/api/v1/streaming/?stream=" + notif_url + "&access_token=" + account.getToken(), "wss", (ex, webSocket) -> {
webSocketFutures.put(account.getAcct() + "@" + account.getInstance(), webSocket);
if (ex != null) {
return;
}
webSocket.setStringCallback(s -> {
try {
JSONObject eventJson = new JSONObject(s);
onRetrieveStreaming(account, eventJson);
} catch (JSONException ignored) {
}
});
webSocket.setClosedCallback(ex1 -> {
if (networkStateReceiver.connected) {
startWork(account);
}
});
webSocket.setDataCallback((emitter, byteBufferList) -> {
// note that this data has been read
byteBufferList.recycle();
});
});
}
}
}
private void startWork(Account accountStream) {
String key = accountStream.getAcct() + "@" + accountStream.getInstance();
Thread thread = Helper.getThreadByName("notif_live_" + key);
if (thread == null) {
thread = new Thread() {
@Override
public void run() {
taks(accountStream);
}
};
thread.setName("notif_live_" + key);
thread.start();
} else if (thread.getState() != Thread.State.RUNNABLE) {
thread.interrupt();
thread = new Thread() {
@Override
public void run() {
taks(accountStream);
}
};
thread.setName("notif_live_" + key);
thread.start();
}
}
private void onRetrieveStreaming(Account account, JSONObject response) {
if (response == null)
return;
final Notification notification;
Bundle b = new Bundle();
boolean canSendBroadCast = true;
Helper.EventStreaming event;
final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null);
try {
if ("notification".equals(response.get("event").toString())) {
eventsCount++;
if (Build.VERSION.SDK_INT >= 26) {
channel = new NotificationChannel(CHANNEL_ID,
"Live notifications",
NotificationManager.IMPORTANCE_DEFAULT);
((NotificationManager) Objects.requireNonNull(getSystemService(Context.NOTIFICATION_SERVICE))).createNotificationChannel(channel);
android.app.Notification notificationChannel = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(getString(R.string.top_notification))
.setSmallIcon(getNotificationIcon(BaseLiveNotificationService.this))
.setContentText(getString(R.string.top_notification_message, String.valueOf(totalAccount), String.valueOf(eventsCount))).build();
if (notificationChannel != null) {
startForeground(2, notificationChannel);
} else {
return;
}
}
event = Helper.EventStreaming.NOTIFICATION;
notification = API.parseNotificationResponse(BaseLiveNotificationService.this, new JSONObject(response.get("payload").toString()));
b.putParcelable("data", notification);
boolean liveNotifications = sharedpreferences.getBoolean(Helper.SET_LIVE_NOTIFICATIONS, true);
boolean canNotify = Helper.canNotify(BaseLiveNotificationService.this);
boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true);
String targeted_account = null;
Helper.NotifType notifType = Helper.NotifType.MENTION;
boolean activityRunning = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("isMainActivityRunning", false);
String key = account.getAcct() + "@" + account.getInstance();
if (lastNotification.containsKey(key) && notification.getId().compareTo(Objects.requireNonNull(lastNotification.get(key))) <= 0) {
canNotify = false;
}
String lastNotif = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null);
if (notification.getId().compareTo(Objects.requireNonNull(lastNotif)) <= 0) {
canNotify = false;
}
boolean allowStream = sharedpreferences.getBoolean(Helper.SET_ALLOW_STREAM + account.getId() + account.getInstance(), true);
if (!allowStream) {
canNotify = false;
}
if ((userId == null || !userId.equals(account.getId()) || !activityRunning) && liveNotifications && canNotify && notify) {
lastNotification.put(key, notification.getId());
boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true);
boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true);
boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true);
boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true);
boolean notif_status = sharedpreferences.getBoolean(Helper.SET_NOTIF_STATUS, true);
boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true);
boolean somethingToPush = (notif_follow || notif_add || notif_mention || notif_share || notif_poll || notif_status);
String message = null;
if (somethingToPush) {
switch (notification.getType()) {
case "mention":
notifType = Helper.NotifType.MENTION;
if (notif_mention) {
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_mention));
else
message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_mention));
if (notification.getStatus() != null) {
if (notification.getStatus().getSpoiler_text() != null && notification.getStatus().getSpoiler_text().length() > 0) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text(), FROM_HTML_MODE_LEGACY));
else
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text()));
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent(), FROM_HTML_MODE_LEGACY));
else
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent()));
}
}
} else {
canSendBroadCast = false;
}
break;
case "status":
notifType = Helper.NotifType.STATUS;
if (notif_status) {
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_status));
else
message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_status));
if (notification.getStatus() != null) {
if (notification.getStatus().getSpoiler_text() != null && notification.getStatus().getSpoiler_text().length() > 0) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text(), FROM_HTML_MODE_LEGACY));
else
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text()));
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent(), FROM_HTML_MODE_LEGACY));
else
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent()));
}
}
} else {
canSendBroadCast = false;
}
break;
case "reblog":
notifType = Helper.NotifType.BOOST;
if (notif_share) {
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_reblog));
else
message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_reblog));
} else {
canSendBroadCast = false;
}
break;
case "favourite":
notifType = Helper.NotifType.FAV;
if (notif_add) {
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_favourite));
else
message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_favourite));
} else {
canSendBroadCast = false;
}
break;
case "follow_request":
notifType = Helper.NotifType.FOLLLOW;
if (notif_follow) {
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_follow_request));
else
message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_follow_request));
targeted_account = notification.getAccount().getId();
} else {
canSendBroadCast = false;
}
break;
case "follow":
notifType = Helper.NotifType.FOLLLOW;
if (notif_follow) {
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_follow));
else
message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_follow));
targeted_account = notification.getAccount().getId();
} else {
canSendBroadCast = false;
}
break;
case "poll":
notifType = Helper.NotifType.POLL;
if (notif_poll) {
if (notification.getAccount().getId() != null && notification.getAccount().getId().equals(userId))
message = getString(R.string.notif_poll_self);
else
message = getString(R.string.notif_poll);
} else {
canSendBroadCast = false;
}
break;
default:
}
//Some others notification
final Intent intent = new Intent(BaseLiveNotificationService.this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(Helper.INTENT_ACTION, Helper.NOTIFICATION_INTENT);
intent.putExtra(Helper.PREF_KEY_ID, account.getId());
intent.putExtra(Helper.PREF_INSTANCE, account.getInstance());
if (targeted_account != null) {
intent.putExtra(Helper.INTENT_TARGETED_ACCOUNT, targeted_account);
}
final String finalMessage = message;
Handler mainHandler = new Handler(Looper.getMainLooper());
Helper.NotifType finalNotifType = notifType;
Runnable myRunnable = () -> {
if (finalMessage != null) {
Glide.with(BaseLiveNotificationService.this)
.asBitmap()
.load(notification.getAccount().getAvatar())
.listener(new RequestListener<Bitmap>() {
@Override
public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
return false;
}
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) {
assert e != null;
Helper.notify_user(BaseLiveNotificationService.this, account, intent, BitmapFactory.decodeResource(getResources(),
getMainLogo(BaseLiveNotificationService.this)), finalNotifType, "@" + notification.getAccount().getAcct(), finalMessage);
return false;
}
})
.into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
Helper.notify_user(BaseLiveNotificationService.this, account, intent, resource, finalNotifType, "@" + notification.getAccount().getAcct(), finalMessage);
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
});
}
};
mainHandler.post(myRunnable);
}
}
if (canSendBroadCast) {
b.putString("userIdService", account.getId());
Intent intentBC = new Intent(Helper.RECEIVE_DATA);
intentBC.putExtra("eventStreaming", event);
intentBC.putExtras(b);
b.putParcelable("data", notification);
LocalBroadcastManager.getInstance(BaseLiveNotificationService.this).sendBroadcast(intentBC);
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notification.getId());
editor.apply();
}
}
} catch (Exception ignored) {
}
}
@Override
public void networkAvailable() {
startStream();
}
@Override
public void networkUnavailable() {
for (Thread t : Thread.getAllStackTraces().keySet()) {
if (t.getName().startsWith("notif_live_")) {
t.interrupt();
}
}
Thread.currentThread().interrupt();
}
}

View File

@ -1,520 +0,0 @@
package app.fedilab.android.services;
/* Copyright 2019 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.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.text.Html;
import android.text.SpannableString;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.transition.Transition;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.client.API;
import app.fedilab.android.client.APIResponse;
import app.fedilab.android.client.Entities.Account;
import app.fedilab.android.client.Entities.Notification;
import app.fedilab.android.client.GNUAPI;
import app.fedilab.android.fragments.DisplayNotificationsFragment;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.sqlite.AccountDAO;
import app.fedilab.android.sqlite.Sqlite;
import static androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY;
import static app.fedilab.android.helper.Helper.getMainLogo;
import static app.fedilab.android.helper.Helper.getNotificationIcon;
import static app.fedilab.android.helper.Helper.sleeps;
/**
* Created by Thomas on 10/09/2019.
* Manage service for live notifications delayed
*/
public class LiveNotificationDelayedService extends Service {
public static String CHANNEL_ID = "live_notifications";
public static int totalAccount = 0;
public static int eventsCount = 0;
public static HashMap<String, String> since_ids = new HashMap<>();
protected Account account;
private NotificationChannel channel;
private boolean fetch;
public void onCreate() {
super.onCreate();
final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true);
if (Build.VERSION.SDK_INT >= 26) {
channel = new NotificationChannel(CHANNEL_ID,
"Live notifications",
NotificationManager.IMPORTANCE_DEFAULT);
((NotificationManager) Objects.requireNonNull(getSystemService(Context.NOTIFICATION_SERVICE))).createNotificationChannel(channel);
}
SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
List<Account> accountStreams = new AccountDAO(LiveNotificationDelayedService.this, db).getAllAccountCrossAction();
totalAccount = 0;
if (accountStreams != null) {
for (Account account : accountStreams) {
boolean allowStream = sharedpreferences.getBoolean(Helper.SET_ALLOW_STREAM + account.getId() + account.getInstance(), true);
if (allowStream) {
totalAccount++;
}
}
}
if (Build.VERSION.SDK_INT >= 26) {
Intent myIntent = new Intent(LiveNotificationDelayedService.this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(
LiveNotificationDelayedService.this,
0,
myIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
android.app.Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setShowWhen(false)
.setContentIntent(pendingIntent)
.setContentTitle(getString(R.string.top_notification))
.setSmallIcon(getNotificationIcon(LiveNotificationDelayedService.this))
.setContentText(getString(R.string.top_notification_message, String.valueOf(totalAccount), String.valueOf(eventsCount))).build();
if (notification != null) {
startForeground(1, notification);
} else {
return;
}
}
if (!notify) {
stopSelf();
return;
}
if (totalAccount > 0) {
startStream();
} else {
stopSelf();
}
}
@Override
public void onDestroy() {
super.onDestroy();
for (Thread t : Thread.getAllStackTraces().keySet()) {
if (t.getName().startsWith("notif_delayed_")) {
t.interrupt();
}
}
Thread.currentThread().interrupt();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true);
if (!notify || intent == null || intent.getBooleanExtra("stop", false)) {
totalAccount = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
stopForeground(STOP_FOREGROUND_DETACH);
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
assert notificationManager != null;
notificationManager.deleteNotificationChannel(CHANNEL_ID);
}
if (intent != null) {
intent.replaceExtras(new Bundle());
}
stopSelf();
}
if (totalAccount > 0) {
return START_STICKY;
}
return START_NOT_STICKY;
}
private void startStream() {
SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
if (Helper.liveNotifType(LiveNotificationDelayedService.this) == Helper.NOTIF_DELAYED) {
List<Account> accountStreams = new AccountDAO(LiveNotificationDelayedService.this, db).getAllAccountCrossAction();
final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
fetch = true;
if (accountStreams != null) {
for (final Account accountStream : accountStreams) {
String key = accountStream.getUsername() + "@" + accountStream.getInstance();
boolean allowStream = sharedpreferences.getBoolean(Helper.SET_ALLOW_STREAM + accountStream.getId() + accountStream.getInstance(), true);
if (!allowStream) {
continue;
}
if (!sleeps.containsKey(key)) {
sleeps.put(key, 30000);
}
Thread thread = Helper.getThreadByName("notif_delayed_" + key);
if (thread == null) {
startThread(accountStream, key);
} else if (thread.getState() != Thread.State.RUNNABLE) {
thread.interrupt();
startThread(accountStream, key);
}
}
}
}
}
private void startThread(Account accountStream, String key) {
Thread thread = new Thread() {
@Override
public void run() {
while (fetch) {
task(accountStream);
fetch = (Helper.liveNotifType(LiveNotificationDelayedService.this) == Helper.NOTIF_DELAYED);
if (sleeps.containsKey(key) && sleeps.get(key) != null) {
try {
Thread.sleep(sleeps.get(key));
} catch (InterruptedException e) {
SystemClock.sleep(sleeps.get(key));
}
}
}
}
};
thread.setName("notif_delayed_" + key);
thread.start();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void task(Account account) {
String key = account.getUsername() + "@" + account.getInstance();
APIResponse apiResponse;
String last_notifid = null;
if (since_ids.containsKey(key)) {
last_notifid = since_ids.get(key);
}
apiResponse = null;
try {
if (account.getSocial().compareTo("FRIENDICA") != 0 && account.getSocial().compareTo("GNU") != 0) {
API api;
api = new API(LiveNotificationDelayedService.this, account.getInstance(), account.getToken());
apiResponse = api.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, last_notifid, false);
} else {
GNUAPI gnuApi;
gnuApi = new GNUAPI(LiveNotificationDelayedService.this, account.getInstance(), account.getToken());
apiResponse = gnuApi.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, last_notifid);
}
} catch (Exception ignored) {
}
if (apiResponse != null && apiResponse.getNotifications() != null && apiResponse.getNotifications().size() > 0) {
since_ids.put(key, apiResponse.getNotifications().get(0).getId());
for (Notification notification : apiResponse.getNotifications()) {
if (last_notifid != null && notification.getId().compareTo(last_notifid) > 0) {
onRetrieveStreaming(account, notification);
sleeps.put(key, 30000);
} else {
if (apiResponse.getNotifications().size() == 1) { //TODO: use min id with Pixelfed when available for removing this fix.
if (sleeps.containsKey(key) && sleeps.get(key) != null) {
int newWaitTime = sleeps.get(key) + 30000;
if (newWaitTime > 900000) {
newWaitTime = 900000;
}
sleeps.put(key, newWaitTime);
} else {
sleeps.put(key, 60000);
}
}
break;
}
}
} else {
if (sleeps.containsKey(key) && sleeps.get(key) != null) {
int newWaitTime = sleeps.get(key) + 30000;
if (newWaitTime > 900000) {
newWaitTime = 900000;
}
sleeps.put(key, newWaitTime);
} else {
sleeps.put(key, 60000);
}
}
}
private void onRetrieveStreaming(Account account, Notification notification) {
Bundle b = new Bundle();
boolean canSendBroadCast = true;
Helper.EventStreaming event;
final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null);
try {
eventsCount++;
if (Build.VERSION.SDK_INT >= 26) {
channel = new NotificationChannel(CHANNEL_ID,
"Live notifications",
NotificationManager.IMPORTANCE_DEFAULT);
((NotificationManager) Objects.requireNonNull(getSystemService(Context.NOTIFICATION_SERVICE))).createNotificationChannel(channel);
android.app.Notification notificationChannel = new NotificationCompat.Builder(this, CHANNEL_ID)
.setShowWhen(false)
.setContentTitle(getString(R.string.top_notification))
.setSmallIcon(getNotificationIcon(LiveNotificationDelayedService.this))
.setContentText(getString(R.string.top_notification_message, String.valueOf(totalAccount), String.valueOf(eventsCount))).build();
if (notificationChannel != null) {
startForeground(1, notificationChannel);
} else {
return;
}
}
event = Helper.EventStreaming.NOTIFICATION;
boolean canNotify = Helper.canNotify(LiveNotificationDelayedService.this);
boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true);
String targeted_account = null;
Helper.NotifType notifType = Helper.NotifType.MENTION;
boolean activityRunning = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("isMainActivityRunning", false);
boolean allowStream = sharedpreferences.getBoolean(Helper.SET_ALLOW_STREAM + account.getId() + account.getInstance(), true);
if (!allowStream) {
canNotify = false;
}
if ((userId == null || !userId.equals(account.getId()) || !activityRunning) && canNotify && notify) {
boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true);
boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true);
boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true);
boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true);
boolean notif_status = sharedpreferences.getBoolean(Helper.SET_NOTIF_STATUS, true);
boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true);
boolean somethingToPush = (notif_follow || notif_add || notif_mention || notif_share || notif_poll || notif_status);
String message = null;
if (somethingToPush) {
switch (notification.getType()) {
case "mention":
notifType = Helper.NotifType.MENTION;
if (notif_mention) {
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_mention));
else
message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_mention));
if (notification.getStatus() != null) {
if (notification.getStatus().getSpoiler_text() != null && notification.getStatus().getSpoiler_text().length() > 0) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text(), FROM_HTML_MODE_LEGACY));
else
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text()));
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent(), FROM_HTML_MODE_LEGACY));
else
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent()));
}
}
} else {
canSendBroadCast = false;
}
break;
case "status":
notifType = Helper.NotifType.STATUS;
if (notif_status) {
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_status));
else
message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_status));
if (notification.getStatus() != null) {
if (notification.getStatus().getSpoiler_text() != null && notification.getStatus().getSpoiler_text().length() > 0) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text(), FROM_HTML_MODE_LEGACY));
else
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text()));
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent(), FROM_HTML_MODE_LEGACY));
else
message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent()));
}
}
} else {
canSendBroadCast = false;
}
break;
case "reblog":
notifType = Helper.NotifType.BOOST;
if (notif_share) {
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_reblog));
else
message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_reblog));
} else {
canSendBroadCast = false;
}
break;
case "favourite":
notifType = Helper.NotifType.FAV;
if (notif_add) {
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_favourite));
else
message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_favourite));
} else {
canSendBroadCast = false;
}
break;
case "follow_request":
notifType = Helper.NotifType.FOLLLOW;
if (notif_follow) {
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_follow_request));
else
message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_follow_request));
targeted_account = notification.getAccount().getId();
} else {
canSendBroadCast = false;
}
break;
case "follow":
notifType = Helper.NotifType.FOLLLOW;
if (notif_follow) {
if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0)
message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_follow));
else
message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_follow));
targeted_account = notification.getAccount().getId();
} else {
canSendBroadCast = false;
}
break;
case "poll":
notifType = Helper.NotifType.POLL;
if (notif_poll) {
if (notification.getAccount().getId() != null && notification.getAccount().getId().equals(userId))
message = getString(R.string.notif_poll_self);
else
message = getString(R.string.notif_poll);
} else {
canSendBroadCast = false;
}
break;
default:
}
//Some others notification
final Intent intent = new Intent(LiveNotificationDelayedService.this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(Helper.INTENT_ACTION, Helper.NOTIFICATION_INTENT);
intent.putExtra(Helper.PREF_KEY_ID, account.getId());
intent.putExtra(Helper.PREF_INSTANCE, account.getInstance());
if (targeted_account != null) {
intent.putExtra(Helper.INTENT_TARGETED_ACCOUNT, targeted_account);
}
final String finalMessage = message;
Handler mainHandler = new Handler(Looper.getMainLooper());
Helper.NotifType finalNotifType = notifType;
Runnable myRunnable = () -> {
if (finalMessage != null) {
Glide.with(LiveNotificationDelayedService.this)
.asBitmap()
.load(notification.getAccount().getAvatar())
.listener(new RequestListener<Bitmap>() {
@Override
public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
return false;
}
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) {
Helper.notify_user(LiveNotificationDelayedService.this, account, intent, BitmapFactory.decodeResource(getResources(),
getMainLogo(LiveNotificationDelayedService.this)), finalNotifType, "@" + notification.getAccount().getAcct(), finalMessage);
return false;
}
})
.into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
Helper.notify_user(LiveNotificationDelayedService.this, account, intent, resource, finalNotifType, "@" + notification.getAccount().getAcct(), finalMessage);
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
});
}
};
mainHandler.post(myRunnable);
}
}
if (canSendBroadCast) {
b.putString("userIdService", account.getId());
Intent intentBC = new Intent(Helper.RECEIVE_DATA);
intentBC.putExtra("eventStreaming", event);
intentBC.putExtras(b);
b.putParcelable("data", notification);
LocalBroadcastManager.getInstance(LiveNotificationDelayedService.this).sendBroadcast(intentBC);
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notification.getId());
editor.apply();
}
} catch (Exception ignored) {
}
}
}

View File

@ -1,45 +0,0 @@
package app.fedilab.android.services;
/* Copyright 2017 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.jobs.NotificationsSyncJob;
import static app.fedilab.android.helper.BaseHelper.startStreaming;
/**
* Created by Thomas on 22/09/2017.
* BroadcastReceiver for restarting the service
*/
public class RestartLiveNotificationReceiver extends BroadcastReceiver {
@SuppressLint("UnsafeProtectedBroadcastReceiver")
@Override
public void onReceive(Context context, Intent intent) {
int type = Helper.liveNotifType(context);
if (type == Helper.NOTIF_DELAYED || type == Helper.NOTIF_LIVE) {
startStreaming(context);
} else {
NotificationsSyncJob.schedule(false);
}
}
}

View File

@ -1,41 +0,0 @@
package app.fedilab.android.services;
/* Copyright 2019 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/**
* Created by Thomas on 18/10/2019.
* BroadcastReceiver for restarting delayed notification service
*/
public class StopDelayedNotificationReceiver extends BroadcastReceiver {
@SuppressLint("UnsafeProtectedBroadcastReceiver")
@Override
public void onReceive(Context context, Intent intent) {
Intent streamingServiceIntent = new Intent(context, LiveNotificationDelayedService.class);
streamingServiceIntent.putExtra("stop", true);
try {
context.startService(streamingServiceIntent);
} catch (Exception ignored) {
}
}
}

View File

@ -1,41 +0,0 @@
package app.fedilab.android.services;
/* Copyright 2017 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/**
* Created by Thomas on 22/09/2017.
* BroadcastReceiver for restarting the service
*/
public class StopLiveNotificationReceiver extends BroadcastReceiver {
@SuppressLint("UnsafeProtectedBroadcastReceiver")
@Override
public void onReceive(Context context, Intent intent) {
Intent streamingServiceIntent = new Intent(context, LiveNotificationService.class);
streamingServiceIntent.putExtra("stop", true);
try {
context.startService(streamingServiceIntent);
} catch (Exception ignored) {
}
}
}

View File

@ -0,0 +1,76 @@
package app.fedilab.android.services;
/* 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 android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.unifiedpush.android.connector.MessagingReceiver;
import org.unifiedpush.android.connector.MessagingReceiverHandler;
import app.fedilab.android.client.Entities.Account;
import app.fedilab.android.helper.ECDH;
import app.fedilab.android.helper.NotificationsHelper;
import app.fedilab.android.helper.PushNotifications;
import app.fedilab.android.sqlite.AccountDAO;
import app.fedilab.android.sqlite.Sqlite;
class handler implements MessagingReceiverHandler {
@Override
public void onMessage(@Nullable Context context, @NotNull String s, @NotNull String slug) {
new Thread(() -> {
if (context != null) {
SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
String[] slugArray = slug.split("@");
ECDH ecdh = ECDH.getInstance();
//ecdh.uncryptMessage(context, s, slug);
Account account = new AccountDAO(context, db).getUniqAccountUsernameInstance(slugArray[0], slugArray[1]);
NotificationsHelper.task(context, account);
}
}).start();
}
@Override
public void onNewEndpoint(@Nullable Context context, @NotNull String endpoint, @NotNull String slug) {
if (context != null) {
new PushNotifications()
.registerPushNotifications(context, endpoint, slug);
}
}
@Override
public void onRegistrationFailed(@Nullable Context context, @NotNull String s) {
}
@Override
public void onRegistrationRefused(@Nullable Context context, @NotNull String s) {
}
@Override
public void onUnregistered(@Nullable Context context, @NotNull String s) {
}
}
public class UnifiedPushService extends MessagingReceiver {
public UnifiedPushService() {
super(new handler());
}
}

View File

@ -1,42 +0,0 @@
package app.fedilab.android.services;
/* Copyright 2020 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;
public class UpgradeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction() != null && intent.getAction().compareTo(Intent.ACTION_MY_PACKAGE_REPLACED) == 0) {
Intent streamingServiceIntent = new Intent(context, LiveNotificationDelayedService.class);
streamingServiceIntent.putExtra("stop", true);
try {
context.startService(streamingServiceIntent);
} catch (Exception ignored) {
}
streamingServiceIntent = new Intent(context, LiveNotificationService.class);
streamingServiceIntent.putExtra("stop", true);
try {
context.startService(streamingServiceIntent);
} catch (Exception ignored) {
}
}
}
}

View File

@ -278,6 +278,22 @@ public class AccountDAO {
}
/**
* Returns all Account in db
*
* @return Account List<Account>
*/
public List<Account> getPushNotificationAccounts() {
try {
Cursor c = db.query(Sqlite.TABLE_USER_ACCOUNT, null, "(" + Sqlite.COL_SOCIAL + " = 'MASTODON' OR " + Sqlite.COL_SOCIAL + " = 'PLEROMA') AND " + Sqlite.COL_OAUTHTOKEN + " != 'null'", null, null, null, Sqlite.COL_INSTANCE + " ASC", null);
return cursorToListUser(c);
} catch (Exception e) {
return null;
}
}
/**
* Returns an Account by token
*
@ -311,6 +327,22 @@ public class AccountDAO {
}
}
/**
* Returns an Account by token
*
* @param userName String
* @param instance String
* @return Account
*/
public Account getUniqAccountUsernameInstance(String userName, String instance) {
try {
Cursor c = db.query(Sqlite.TABLE_USER_ACCOUNT, null, Sqlite.COL_USERNAME + " = \"" + userName + "\" AND " + Sqlite.COL_INSTANCE + " = \"" + instance + "\"", null, null, null, null, "1");
return cursorToUser(c);
} catch (Exception e) {
return null;
}
}
/**
* Test if the current user is already stored in data base

View File

@ -89,102 +89,6 @@
android:layout_marginBottom="10dp"
android:textColor="@color/mastodonC2" />
<LinearLayout
android:id="@+id/live_notif_per_account"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/settings_option_margin"
android:layout_marginBottom="@dimen/settings_option_margin"
android:gravity="center_vertical"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/set_allow_live_notifications_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/set_allow_live_notifications"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/set_allow_live_notifications_indication"
android:textColor="@color/mastodonC2"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/set_allow_live_notifications"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageButton
android:id="@+id/set_allow_live_notifications_others"
style="@style/colored_button"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="end"
android:contentDescription="@string/live_notif"
android:padding="5dp"
android:src="@drawable/ic_account_circle_acct" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/set_hide_status_bar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/settings_option_margin"
android:layout_marginBottom="@dimen/settings_option_margin"
android:gravity="center_vertical"
android:orientation="horizontal"
android:visibility="gone">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/set_hide_status_bar"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/set_hide_status_bar_indication"
android:textColor="@color/mastodonC2"
android:textSize="12sp" />
</LinearLayout>
<ImageButton
android:id="@+id/set_hide_status_bar"
style="@style/colored_button"
android:layout_width="40dp"
android:layout_height="40dp"
android:contentDescription="@string/bookmark_add"
android:padding="5dp"
android:src="@drawable/ic_hide_status_bar" />
</LinearLayout>
<LinearLayout
android:id="@+id/notification_settings"

View File

@ -1038,6 +1038,9 @@
<string name="set_bibliogram">استبدال Instagram بـ Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram هو بديل مفتوح المصدر لـ Instagram يركز على الخصوصية.</string>
<string name="set_bibliogram_host">أدخل مضيفك المخصص أو اتركه فارغًا لاستخدام bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">أخفِ شريط إشعارات Fedilab</string>
<string name="set_hide_status_bar_indication">لإخفاء الإشعارات المتبقية على شريط الحالة ، اضغط على زر أيقونة العين ثم قم بإلغاء تحديد: \"العرض على شريط الحالة\"</string>
<string name="set_live_type_indication">سيتم تأجيل الإشعارات كل 30 ثانية. الشيء الذي من شأنه أن يسمح باستنزاف أقل للبطارية.</string>

View File

@ -1005,6 +1005,9 @@
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -1007,6 +1007,9 @@
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -1031,6 +1031,9 @@
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -997,6 +997,9 @@ Ara ja pots connectar-te al compte escrivint <b>%1$s</b> en el primer camp i fen
<string name="set_bibliogram">Substituir Instagram per Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram és un frontal alternatiu de codi obert per Instagram que protegeix la privacitat.</string>
<string name="set_bibliogram_host">Introdueix aquí el teu servidor personalitzat o deixa-ho en blanc i s\'usarà bibliogram.art</string>
<string name="set_libreddit">Substituïu Reddit per Libreddit</string>
<string name="set_libreddit_indication">Libreddit és una interfície alternativa a Reddit, és de codi obert i prioritza la privadesa.</string>
<string name="set_libreddit_host">Introduïu el vostre servidor personalitzat o deixeu-ho em blanc per utilitzar libredd.it</string>
<string name="set_hide_status_bar">Amaga la barra de notificacions de Fedilab</string>
<string name="set_hide_status_bar_indication">Per amagar la notificació que queda a la barra d\'estat, toca la icona de l\'ull i després desactiva: \"Mostra a la barra d\'estat\"</string>
<string name="set_live_type_indication">S\'ajornaran les notificacions cada 30 segons. Això permet gastar menys la pila.</string>

View File

@ -1021,6 +1021,9 @@ Uživatelské jméno a heslo nejsou nikdy ukládány. Jsou použity pouze během
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -1038,6 +1038,9 @@
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -1007,6 +1007,9 @@
<string name="set_bibliogram">Erstat Instagram med Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram er et open source Instagram front-end alternativt fokuseret på datafortrolighed.</string>
<string name="set_bibliogram_host">Angiv din tilpassede vært eller lad stå tomt for at benytte bibliogram.art</string>
<string name="set_libreddit">Erstat Reddit med Libreddit</string>
<string name="set_libreddit_indication">Libreddit er en fortrolighedorienteret, alternativ open-source Twitter front-end.</string>
<string name="set_libreddit_host">Angiv din tilpassede vært eller lad stå tomt for brug af libredd.it</string>
<string name="set_hide_status_bar">Skjul Fedilab-notifikationsbjælke</string>
<string name="set_hide_status_bar_indication">For at skjule den resterende notifikation på statusbjælken, så klik på øjeikonknappen og fjern derefter markeringen: \"Vises på statusbjælke\"</string>
<string name="set_live_type_indication">Notifikationer udskydes hvert 30. sekund. Dette vil betyde lavere batteridræning.</string>

View File

@ -995,6 +995,9 @@ Sobald du die ersten Buchstaben eintippst, werden Namensvorschläge angezeigt\n\
<string name="set_bibliogram">Instagram durch Bibliogram ersetzen</string>
<string name="set_bibliogram_indication">Bibliogram ist ein alternatives Open-Source-Frontend von Instagram, das sich auf den Datenschutz konzentriert.</string>
<string name="set_bibliogram_host">Geben Sie Ihren benutzerdefinierten Host ein oder lassen Sie das Feld für die Verwendung von bibliogram.art frei</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Fedilab Benachrichtigungsleiste ausblenden</string>
<string name="set_hide_status_bar_indication">Um die verbleibende Benachrichtigung in der Statusleiste zu verstecken, klicken Sie auf die Augensymbol-Schaltfläche und deaktivieren Sie: \"In Statusleiste anzeigen\"</string>
<string name="set_live_type_indication">Benachrichtigungen werden 30 Sekunden verzögert. Dadurch wird weniger Akku verbraucht.</string>

View File

@ -1007,6 +1007,9 @@
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Απόκρυψη της γραμμής ειδοποιήσεων του Φέντιλαμπ</string>
<string name="set_hide_status_bar_indication">Για απόκρυψη της υπόλοιπης ειδοποίησης στη γραμμή κατάστασης, πίεσε στο πλήκτρο με το μάτι, και μετά απο-επέλεξε την...: «Προβολή στη γραμμή κατάστασης»</string>
<string name="set_live_type_indication">Οι ειδοποιήσεις θα καθυστερούν κάθε 30 δευτερόλεπτα. Αυτό θα βοηθήσει στην χαμηλότερη απομύζηση της μπαταρίας.</string>

View File

@ -1007,6 +1007,9 @@
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -1001,6 +1001,9 @@ https://yandex.ru/legal/confidential/?lang=en </string>
<string name="set_bibliogram">Reemplazar Instagram con Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram es una interfaz alternativa de Instagram de código abierto centrada en la privacidad.</string>
<string name="set_bibliogram_host">Introduzca su servidor personalizado o déjelo en blanco para usar bibliogram.art</string>
<string name="set_libreddit">Reemplazar Reddit con Libreddit</string>
<string name="set_libreddit_indication">Libreddit es una interfaz alternativa de código abierto de Reddit enfocada en la privacidad.</string>
<string name="set_libreddit_host">Introduce tu servidor personalizado o déjalo en blanco para usar libredd.it</string>
<string name="set_hide_status_bar">Ocultar la barra de notificaciones de Fedilab</string>
<string name="set_hide_status_bar_indication">Para ocultar el resto de la notificación en la barra de estado, presione en el botón con el ícono de un ojo y luego desactivar \"Mostrar en la barra de estado\"</string>
<string name="set_live_type_indication">Las notificaciones se retrasarán cada 30 segundos. Esto permitirá gastar menos batería.</string>

View File

@ -1006,6 +1006,9 @@
<string name="set_bibliogram">Ordeztu Instagram Bibliogram-ekin</string>
<string name="set_bibliogram_indication">Bibliogram pribatutasuna aintzat duen Instagram interfaze libre bat da.</string>
<string name="set_bibliogram_host">Sartu zure ostalari pertsonalizatua edo laga hutsik bibliogram.art erabiltzeko</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Ezkutatu Fedilab jakinarazpen-barra</string>
<string name="set_hide_status_bar_indication">Egoera barran geratzen den jakinarazpena ezkutatzeko, sakatu begiaren ikonoa eta desmarkatu \"Erakutsi egoera-barran\"</string>
<string name="set_live_type_indication">Jakinarazpenak 30 segundo atzeratuko dira. Honela bateria gutxiago erabiliko da.</string>

View File

@ -1007,6 +1007,9 @@
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -1004,6 +1004,9 @@ Le bouton de connexion sactivera une fois quun domaine valide sera renseig
<string name="set_bibliogram">Remplacer Instagram par Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram est une interface ouverte en alternative à Instagram axée sur la confidentialité.</string>
<string name="set_bibliogram_host">Entrez votre hôte personnalisé ou laissez vide pour utiliser bibliogram.art</string>
<string name="set_libreddit">Remplacer Reddit par Libreddit</string>
<string name="set_libreddit_indication">Libreddit est une alternative ouverte à l\'interface de Reddit axée sur la protection de la vie privée.</string>
<string name="set_libreddit_host">Entrez votre hôte personnalisé ou laissez vide pour utiliser libredd.it</string>
<string name="set_hide_status_bar">Masquer la barre de notification de Fedilab</string>
<string name="set_hide_status_bar_indication">Pour cacher le reste de la notification dans la barre de statut, cliquez sur le bouton en forme d\'œil, puis décochez: \"Afficher dans la barre de statut\"</string>
<string name="set_live_type_indication">Les notifications seront mises à jour toutes les 30 secondes. Cela permet de réduire la consommation de la batterie.</string>

View File

@ -1007,6 +1007,9 @@
<string name="set_bibliogram">Substituír Instagram con Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram é unha alternativa de código aberto centrada na privacidade a interface de Instagram.</string>
<string name="set_bibliogram_host">Escribe o servidor personalizado ou deixa en branco para usar bibliogram.art</string>
<string name="set_libreddit">Substituir Reddit por Libreddit</string>
<string name="set_libreddit_indication">Libreddit é unha alternativa de código aberto á interface de Reddit centrada na privacidade.</string>
<string name="set_libreddit_host">Escribe o teu servidor personalizado ou deixa baleiro para usar libredd.it</string>
<string name="set_hide_status_bar">Ocultar barra de notificacións de Fedilab</string>
<string name="set_hide_status_bar_indication">Para ocultar as notificacións remanentes na barra de estado, pulsa na icona do ollo e desmarca: \"Mostrar en barra de estado\"</string>
<string name="set_live_type_indication">As notificacións retrasaranse cada 30 segundos. Esto permite aforrar batería.</string>

View File

@ -999,6 +999,9 @@
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -1005,6 +1005,9 @@ A Yandexnek megvan a saját adatvédelmi szabályzata, ami itt található: http
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -1006,6 +1006,9 @@
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -1001,6 +1001,9 @@ https://yandex.ru/legal/confidential/?lang=en
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -1007,6 +1007,9 @@
<string name="set_bibliogram">Sostituisci Instagram con Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram è un\'interfaccia alternativa e open source per Instagram, focalizzata sulla privacy.</string>
<string name="set_bibliogram_host">Inserisci il tuo host personalizzato o lascia vuoto per usare bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Nascondi barra di notifica di Fedilab</string>
<string name="set_hide_status_bar_indication">Per nascondere le notifiche rimanenti nella barra di stato, premi il pulsante con l\'icona di un occhio e deseleziona: \"Mostra nella barra di stato\"</string>
<string name="set_live_type_indication">Le notifiche saranno differite ogni 30 secondi. Questo consentirà un minore consumo di batteria.</string>

View File

@ -990,6 +990,9 @@
<string name="set_bibliogram">InstagramをBibliogramに置き換える</string>
<string name="set_bibliogram_indication">Bibliogramはプライバシーを重視したオープンソースのInstagramの代替フロントエンドです。</string>
<string name="set_bibliogram_host">カスタムホストを入力してください。空欄にするとbibliogram.artが使用されます。</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">通知バーからFedilabを非表示にする</string>
<string name="set_hide_status_bar_indication">ステータスバー上の通知を非表示にするには、目のアイコンボタンをタップして「ステータスバーに表示する」のチェックを外します</string>
<string name="set_live_type_indication">通知は30秒ごとに遅延が発生します。バッテリーの持ちが改善します。</string>

View File

@ -1007,6 +1007,9 @@
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -999,6 +999,9 @@
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -1005,6 +1005,9 @@
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -1006,6 +1006,9 @@ Je kunt beginnen met typen en er zullen namen gesuggereerd worden.\n\n
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -997,6 +997,9 @@ Adresser vil bli foreslått når du begynner å skrive.\n\n
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -1007,6 +1007,9 @@
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -1020,6 +1020,9 @@
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -1007,6 +1007,9 @@
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -1013,6 +1013,9 @@ Aceste date sunt strict confidențiale și pot fi folosite doar de aplicație.
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -1023,6 +1023,9 @@
<string name="set_bibliogram">Заменить Instagram на Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram это альтернативный front-end для Instagram с открытым исходным кодом и сфокусированный на приватности.</string>
<string name="set_bibliogram_host">Введите свой собственный хост или оставьте пустым для использования bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Скрыть панель уведомлений Fedilab</string>
<string name="set_hide_status_bar_indication">Чтобы скрыть уведомление в строке состояния, нажмите на кнопку с пиктограммой глаза и снимите флажок: \"Показывать в строке состояния\"</string>
<string name="set_live_type_indication">Уведомления будут появляться на 30 секунд позже. Это позволит уменьшить влияние на батарею.</string>

View File

@ -970,6 +970,9 @@
<string name="set_bibliogram">Remplasa Instagram cun Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram est un\'interfache alternativa pro Instagram, a mitza aberta e progetada pro sa riservadesa.</string>
<string name="set_bibliogram_host">Inserta s\'istrangiadore (host) personalizadu tuo o lassa bòidu pro bibliogram.art</string>
<string name="set_libreddit">Remplasa Reddit cun Libreddit</string>
<string name="set_libreddit_indication">Libreddit est un\'interfache alternativa a mitza aberta pro Reddit progetada pro sa riservadesa.</string>
<string name="set_libreddit_host">Inserta s\'istrangiadore (host) personalizadu tuo o lassa bòidu pro impreare libredd.it</string>
<string name="set_hide_status_bar">Cua sa barra de is notìficas de Fedilab</string>
<string name="set_hide_status_bar_indication">Pro cuare is notìficas chi abarrant in sa barra de istadu toca in su butone cun s\'icona a forma de ogru e boga sa seletzione a: \&quot;Ammustra in sa barra de istadu\&quot;</string>
<string name="set_live_type_indication">Is notìficas ant a èssere perlongadas cada 30 segundos. Custu at a permìtere de consumire de mancu sa bateria.</string>

View File

@ -1007,6 +1007,9 @@
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -1023,6 +1023,9 @@
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -1015,6 +1015,9 @@
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -1007,6 +1007,9 @@
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Dölj Fedilab meddelandefält</string>
<string name="set_hide_status_bar_indication">För att dölja kvarvarande notifieringar i statusbaren, klicka på ögonikons knappen och kryssa ur: \"Visa i statusbar\"</string>
<string name="set_live_type_indication">Notifieringar kommer fördröjas var 30:e sekund. Detta kommer att dra mindre ström.</string>

View File

@ -1021,6 +1021,9 @@
<string name="set_bibliogram">Zamiyń Instagram na Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram to ôtwartozdrzōdłowo, skupiōno na prywatności alternatywa do przodnich funkcyji Instagrama.</string>
<string name="set_bibliogram_host">Wkludź swōj włosny host abo ôstow prōzne, żeby używać bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Skryj posek powiadōmiyń Fedilab</string>
<string name="set_hide_status_bar_indication">Żeby skryć permanyntne powiadōmiynie we posku statusu, tyknij knefel z ôkym i ôdznacz „Pokoż we posku statusu”</string>
<string name="set_live_type_indication">Powiadōmiynia bydōm ôpōźniōne co 30 sekund. To przizwoli na myńsze wyużycie bateryje.</string>

View File

@ -1001,6 +1001,9 @@
<string name="set_bibliogram">Instagram\'ı Bibliogram ile Değiştirin</string>
<string name="set_bibliogram_indication">Bibliogram, mahremiyete odaklanan açık kaynaklı bir alternatif Instagram arayüzdür.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Reddit yerine Libreddit kullan</string>
<string name="set_libreddit_indication">Libreddit, mahremiyete odaklanan açık kaynaklı bir alternatif Reddit arayüzüdür.</string>
<string name="set_libreddit_host">Sunucu adresi girin ya da libredd.it sunucusunu kullanmak için boş bırakın</string>
<string name="set_hide_status_bar">Fedilab bildirim çubuğunu gizle</string>
<string name="set_hide_status_bar_indication">Durum çubuğunda kalan bildirimi gizlemek için, göz simgesine dokunun ve ardından \"Durum çubuğunda göster\" seçeneğinin işaretini kaldırın</string>
<string name="set_live_type_indication">Bildirimler 30 saniye geciktirilecek. Bu sayede pil harcaması azalacak.</string>

View File

@ -1013,6 +1013,9 @@
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -996,6 +996,9 @@ và %d thông báo khác</item>
<string name="set_bibliogram">Replace Instagram with Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram is an open source alternative Instagram front-end focused on privacy.</string>
<string name="set_bibliogram_host">Enter your custom host or leave blank for using bibliogram.art</string>
<string name="set_libreddit">Replace Reddit with Libreddit</string>
<string name="set_libreddit_indication">Libreddit is an open source alternative Reddit front-end focused on privacy.</string>
<string name="set_libreddit_host">Enter your custom host or leave blank for using libredd.it</string>
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>

View File

@ -999,6 +999,9 @@
<string name="set_bibliogram">将 Instagram 替换成 Bibliogram</string>
<string name="set_bibliogram_indication">Bibliogram 是个注重隐私保护的 Instagram 开源替代前端。</string>
<string name="set_bibliogram_host">输入你的自定义主机或者留空以使用 bibliogram.art</string>
<string name="set_libreddit">用 Libreddit 替换 Reddit</string>
<string name="set_libreddit_indication">Libreddit 是一个开源的 Reddit 替代前端,专注于隐私。</string>
<string name="set_libreddit_host">输入您的自定义主机或留空使用 libredd.it</string>
<string name="set_hide_status_bar">隐藏 Fedilab 通知栏</string>
<string name="set_hide_status_bar_indication">要隐藏留在状态栏里的通知,请点击眼睛图标的按钮,然后取消勾选:“在状态栏内显示”</string>
<string name="set_live_type_indication">通知将被延迟为每30秒产生。这将允许耗尽较少电池。</string>

View File

@ -998,6 +998,9 @@ Yandex 有適當的隱私權政策可以在這裡找到https://yandex.ru/l
<string name="set_bibliogram">將 Instagram 以 Bibliogram 取代</string>
<string name="set_bibliogram_indication">Bibliogram 是一個尊重隱私的開放原始碼替代 Instagram 前端。</string>
<string name="set_bibliogram_host">輸入您的自訂主機或留空以使用 bibliogram.art</string>
<string name="set_libreddit">用 Libreddit 取代 Reddit</string>
<string name="set_libreddit_indication">Libreddit 是一個尊重隱私的開放原始碼替代 Reddit 前端。</string>
<string name="set_libreddit_host">輸入您的自訂主機或留空以使用 libredd.it</string>
<string name="set_hide_status_bar">隱藏 Fedilab 通知列</string>
<string name="set_hide_status_bar_indication">要隱藏狀態列中的其餘通知,點擊眼睛圖示的按鈕上然後取消勾選「在狀態列中顯示」</string>
<string name="set_live_type_indication">通知將會每30秒被延遲一次。這將會降低電池的消耗。</string>

View File

@ -130,4 +130,13 @@
<color name="dark_icon_theme">#f3f3f3</color>
<color name="black_icon_theme">#606984</color>
<color name="rainbow_1">#ff0000</color>
<color name="rainbow_2">#ffa500</color>
<color name="rainbow_3">#ffff00</color>
<color name="rainbow_4">#008000</color>
<color name="rainbow_5">#0000ff</color>
<color name="rainbow_6">#4b0082</color>
<color name="rainbow_7">#ee82ee</color>
</resources>

View File

@ -82,7 +82,7 @@
<string name="disclaimer_full">Information below may reflect the user\'s profile incompletely.</string>
<string name="insert_emoji">Insert emoji</string>
<string name="no_emoji">The app did not collect custom emojis for the moment.</string>
<string name="live_notif">Live notifications</string>
<string name="push_notif">Push notifications</string>
<string name="logout_confirmation">Are you sure you want to logout?</string>
<string name="logout_account_confirmation">Are you sure you want to logout @%1$s@%2$s?</string>
<!-- Status -->
@ -984,7 +984,6 @@
<string name="set_blur_sensitive_indication">If disabled, sensitive media will be hidden with a button</string>
<string name="set_long_press_media_indication">Store media in full size with a long press on previews</string>
<string name="set_display_timeline_in_list_indication">Add an ellipse button at the top right for listing all tags/instances/lists</string>
<string name="live_notif_indication">Keep an open connection to the streaming API for live notifications.</string>
<string name="set_enable_time_slot_indication">During the time slot, the app will send notifications. You can reverse (ie: silent) this time slot with the right spinner.</string>
<string name="set_display_fedilab_features_button_indication">Display a Fedilab button below profile picture. It is a shortcut for accessing in-app features.</string>
<string name="set_quick_reply_indication">Allow to reply directly in timelines below statuses</string>
@ -1121,8 +1120,7 @@
<string name="set_hide_status_bar">Hide Fedilab notification bar</string>
<string name="set_hide_status_bar_indication">For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\"</string>
<string name="set_live_type_indication">Notifications will be delayed every 30 seconds. That will allow to drain less battery.</string>
<string name="live_delayed">Live notifications delayed</string>
<string name="set_push_notifications">Use a push notifications system for getting notifications in real time.</string>
<string name="no_live_notif">No live notifications</string>
<string name="no_live_indication">Notifications will be fetched every 15 minutes.</string>
<string name="action_add_notes">Add notes</string>
@ -1243,5 +1241,8 @@
<string name="set_video_cache">Video cache in MB, zero means no cache.</string>
<string name="set_watermark">Watermarks</string>
<string name="set_watermark_indication">Automatically add a watermark at the bottom of pictures. The text can be customized for each account.</string>
<string name="no_distributors_found">No distributors found!</string>
<string name="no_distributors_explanation">You need a distributor for receiving push notifications.\nYou will find more details at %1$s.\n\nYou can also disable push notifications in settings for ignoring that message.</string>
<string name="select_distributors">Select a distributor</string>
</resources>

View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2017 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>
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="app.fedilab.android"
android:installLocation="auto">
<application
android:name="app.fedilab.android.activities.MainApplication"
android:allowBackup="false"
android:hardwareAccelerated="true"
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/AppThemeDark"
android:usesCleartextTraffic="true"
tools:replace="android:allowBackup">
<activity-alias
android:name=".activities.MainActivity.Bubbles"
android:enabled="true"
android:icon="@mipmap/ic_launcher_bubbles"
android:roundIcon="@mipmap/ic_launcher_bubbles_round"
android:targetActivity=".activities.MainActivity">
<meta-data
android:name="icon"
android:value="bubbles" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<activity
android:name="app.fedilab.android.activities.PhotoEditorActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/app_name" />
<activity
android:name="app.fedilab.android.activities.OwnerStatusActivity"
android:configChanges="orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/AppThemeDark_NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name="app.fedilab.android.activities.OwnerNotificationChartsActivity"
android:configChanges="orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTask"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name="app.fedilab.android.activities.OwnerChartsActivity"
android:configChanges="orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTask"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name="app.fedilab.android.activities.OwnerNotificationActivity"
android:configChanges="orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/AppThemeDark_NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:theme="@style/Base.Theme.AppCompat" />
<receiver
android:name=".services.UnifiedPushService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="org.unifiedpush.android.connector.MESSAGE" />
<action android:name="org.unifiedpush.android.connector.UNREGISTERED" />
<action android:name="org.unifiedpush.android.connector.NEW_ENDPOINT" />
<action android:name="org.unifiedpush.android.connector.REGISTRATION_FAILED" />
<action android:name="org.unifiedpush.android.connector.REGISTRATION_REFUSED" />
</intent-filter>
</receiver>
<receiver
android:name=".services.FakeDistributor"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="org.unifiedpush.android.distributor.REGISTER" />
<action android:name="org.unifiedpush.android.distributor.UNREGISTER" />
</intent-filter>
</receiver>
</application>
</manifest>

View File

@ -0,0 +1,50 @@
package app.fedilab.android.helper;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.sqlite.SQLiteDatabase;
import org.unifiedpush.android.connector_fcm_added.RegistrationFCM;
import java.util.List;
import app.fedilab.android.client.Entities.Account;
import app.fedilab.android.jobs.ApplicationJob;
import app.fedilab.android.jobs.NotificationsSyncJob;
import app.fedilab.android.sqlite.AccountDAO;
import app.fedilab.android.sqlite.Sqlite;
import static app.fedilab.android.helper.BaseHelper.NOTIF_NONE;
import static app.fedilab.android.helper.BaseHelper.NOTIF_PUSH;
import static app.fedilab.android.helper.BaseHelper.liveNotifType;
public class PushHelper {
public static void startStreaming(Context context) {
int liveNotifications = liveNotifType(context);
ApplicationJob.cancelAllJob(NotificationsSyncJob.NOTIFICATION_REFRESH);
NotificationsSyncJob.schedule(false);
switch (liveNotifications) {
case NOTIF_PUSH:
new Thread(() -> {
SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
List<Account> accounts = new AccountDAO(context, db).getPushNotificationAccounts();
((Activity) context).runOnUiThread(() -> {
RegistrationFCM registration = new RegistrationFCM();
for (Account account : accounts) {
registration.registerAppWithDialog(context, account.getUsername() + "@" + account.getInstance());
}
});
}).start();
break;
case NOTIF_NONE:
new RegistrationFCM().unregisterApp(context);
break;
}
}
}

View File

@ -0,0 +1,10 @@
package app.fedilab.android.services;
import org.unifiedpush.android.connector_fcm_added.FakeDistributorReceiver;
public class FakeDistributor extends FakeDistributorReceiver {
public FakeDistributor() {
super(new HandlerFCM());
}
}

View File

@ -0,0 +1,18 @@
package app.fedilab.android.services;
import android.content.Context;
import androidx.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
import org.unifiedpush.android.connector_fcm_added.GetEndpointHandler;
public class HandlerFCM implements GetEndpointHandler {
@Override
public @NotNull String getEndpoint(@Nullable Context context, @NotNull String token, @NotNull String instance) {
return "https://gotify.fedilab.app/FCM?token=" + token + "&instance=" + instance;
}
}

View File

@ -6,7 +6,8 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.1'
classpath 'com.android.tools.build:gradle:4.1.2'
classpath 'com.google.gms:google-services:4.3.5'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@ -17,6 +18,7 @@ allprojects {
repositories {
jcenter()
google()
maven { url 'https://jitpack.io' }
}
}