Moves Firebase notifications to a "google" build flavor and adds an "fdroid" flavor that uses polling for notifications.
Also, adds a few missing license notices.
This commit is contained in:
parent
cc8d8f716a
commit
5f6fab2b70
|
@ -12,6 +12,14 @@ android {
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary true
|
vectorDrawables.useSupportLibrary true
|
||||||
}
|
}
|
||||||
|
productFlavors {
|
||||||
|
google {
|
||||||
|
buildConfigField "boolean", "USES_PUSH_NOTIFICATIONS", "true"
|
||||||
|
}
|
||||||
|
fdroid {
|
||||||
|
buildConfigField "boolean", "USES_PUSH_NOTIFICATIONS", "false"
|
||||||
|
}
|
||||||
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
|
@ -51,8 +59,8 @@ dependencies {
|
||||||
compile 'com.github.arimorty:floatingsearchview:2.0.3'
|
compile 'com.github.arimorty:floatingsearchview:2.0.3'
|
||||||
compile 'com.theartofdev.edmodo:android-image-cropper:2.4.0'
|
compile 'com.theartofdev.edmodo:android-image-cropper:2.4.0'
|
||||||
compile 'com.jakewharton:butterknife:8.4.0'
|
compile 'com.jakewharton:butterknife:8.4.0'
|
||||||
compile 'com.google.firebase:firebase-messaging:10.0.1'
|
googleCompile 'com.google.firebase:firebase-messaging:10.0.1'
|
||||||
compile 'com.google.firebase:firebase-crash:10.0.1'
|
googleCompile 'com.google.firebase:firebase-crash:10.0.1'
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
|
annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.keylesspalace.tusky">
|
||||||
|
|
||||||
|
<application>
|
||||||
|
<service
|
||||||
|
android:name=".MessagingService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true" />
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
|
@ -0,0 +1,114 @@
|
||||||
|
/* Copyright 2017 Andrew Dawson
|
||||||
|
*
|
||||||
|
* This file is a part of Tusky.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Tusky 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 Tusky; if not,
|
||||||
|
* see <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
import android.app.IntentService;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.text.Spanned;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
|
import com.keylesspalace.tusky.entity.Notification;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import okhttp3.Interceptor;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
import retrofit2.Retrofit;
|
||||||
|
import retrofit2.converter.gson.GsonConverterFactory;
|
||||||
|
|
||||||
|
public class MessagingService extends IntentService {
|
||||||
|
public static final int NOTIFY_ID = 6; // This is an arbitrary number.
|
||||||
|
|
||||||
|
private MastodonAPI mastodonAPI;
|
||||||
|
|
||||||
|
public MessagingService() {
|
||||||
|
super("Tusky Pull Notification Service");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onHandleIntent(Intent intent) {
|
||||||
|
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(
|
||||||
|
getApplicationContext());
|
||||||
|
boolean enabled = preferences.getBoolean("notificationsEnabled", true);
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
createMastodonApi();
|
||||||
|
|
||||||
|
mastodonAPI.notifications(null, null, null).enqueue(new Callback<List<Notification>>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<List<Notification>> call,
|
||||||
|
Response<List<Notification>> response) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
List<Notification> notificationList = response.body();
|
||||||
|
for (Notification notification : notificationList) {
|
||||||
|
NotificationMaker.make(MessagingService.this, NOTIFY_ID, notification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<List<Notification>> call, Throwable t) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createMastodonApi() {
|
||||||
|
SharedPreferences preferences = getSharedPreferences(
|
||||||
|
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||||
|
final String domain = preferences.getString("domain", null);
|
||||||
|
final String accessToken = preferences.getString("accessToken", null);
|
||||||
|
|
||||||
|
OkHttpClient okHttpClient = OkHttpUtils.getCompatibleClientBuilder()
|
||||||
|
.addInterceptor(new Interceptor() {
|
||||||
|
@Override
|
||||||
|
public okhttp3.Response intercept(Chain chain) throws IOException {
|
||||||
|
Request originalRequest = chain.request();
|
||||||
|
|
||||||
|
Request.Builder builder = originalRequest.newBuilder()
|
||||||
|
.header("Authorization", String.format("Bearer %s", accessToken));
|
||||||
|
|
||||||
|
Request newRequest = builder.build();
|
||||||
|
|
||||||
|
return chain.proceed(newRequest);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Gson gson = new GsonBuilder()
|
||||||
|
.registerTypeAdapter(Spanned.class, new SpannedTypeAdapter())
|
||||||
|
.registerTypeAdapter(StringWithEmoji.class, new StringWithEmojiTypeAdapter())
|
||||||
|
.create();
|
||||||
|
|
||||||
|
Retrofit retrofit = new Retrofit.Builder()
|
||||||
|
.baseUrl("https://" + domain)
|
||||||
|
.client(okHttpClient)
|
||||||
|
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
mastodonAPI = retrofit.create(MastodonAPI.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.keylesspalace.tusky">
|
||||||
|
<application>
|
||||||
|
<meta-data android:name="firebase_analytics_collection_enabled" android:value="false" />
|
||||||
|
|
||||||
|
<service android:name=".MyFirebaseInstanceIdService" android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service android:name=".MessagingService" android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
|
@ -0,0 +1,121 @@
|
||||||
|
/* Copyright 2017 Andrew Dawson
|
||||||
|
*
|
||||||
|
* This file is a part of Tusky.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Tusky 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 Tusky; if not,
|
||||||
|
* see <http://www.gnu.org/licenses>.
|
||||||
|
*
|
||||||
|
* If you modify this Program, or any covered work, by linking or combining it with Firebase Cloud
|
||||||
|
* Messaging and Firebase Crash Reporting (or a modified version of those libraries), containing
|
||||||
|
* parts covered by the Google APIs Terms of Service, the licensors of this Program grant you
|
||||||
|
* additional permission to convey the resulting work. */
|
||||||
|
|
||||||
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.text.Spanned;
|
||||||
|
|
||||||
|
import com.google.firebase.messaging.FirebaseMessagingService;
|
||||||
|
import com.google.firebase.messaging.RemoteMessage;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
|
import com.keylesspalace.tusky.entity.Notification;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import okhttp3.Interceptor;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
import retrofit2.Retrofit;
|
||||||
|
import retrofit2.converter.gson.GsonConverterFactory;
|
||||||
|
|
||||||
|
public class MessagingService extends FirebaseMessagingService {
|
||||||
|
private MastodonAPI mastodonAPI;
|
||||||
|
private static final String TAG = "MessagingService";
|
||||||
|
public static final int NOTIFY_ID = 666;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessageReceived(RemoteMessage remoteMessage) {
|
||||||
|
Log.d(TAG, remoteMessage.getFrom());
|
||||||
|
Log.d(TAG, remoteMessage.toString());
|
||||||
|
|
||||||
|
String notificationId = remoteMessage.getData().get("notification_id");
|
||||||
|
|
||||||
|
if (notificationId == null) {
|
||||||
|
Log.e(TAG, "No notification ID in payload!!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, notificationId);
|
||||||
|
|
||||||
|
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(
|
||||||
|
getApplicationContext());
|
||||||
|
boolean enabled = preferences.getBoolean("notificationsEnabled", true);
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
createMastodonAPI();
|
||||||
|
|
||||||
|
mastodonAPI.notification(notificationId).enqueue(new Callback<Notification>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<Notification> call, Response<Notification> response) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
NotificationMaker.make(MessagingService.this, NOTIFY_ID, response.body());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<Notification> call, Throwable t) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createMastodonAPI() {
|
||||||
|
SharedPreferences preferences = getSharedPreferences(getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||||
|
final String domain = preferences.getString("domain", null);
|
||||||
|
final String accessToken = preferences.getString("accessToken", null);
|
||||||
|
|
||||||
|
OkHttpClient okHttpClient = OkHttpUtils.getCompatibleClientBuilder()
|
||||||
|
.addInterceptor(new Interceptor() {
|
||||||
|
@Override
|
||||||
|
public okhttp3.Response intercept(Chain chain) throws IOException {
|
||||||
|
Request originalRequest = chain.request();
|
||||||
|
|
||||||
|
Request.Builder builder = originalRequest.newBuilder()
|
||||||
|
.header("Authorization", String.format("Bearer %s", accessToken));
|
||||||
|
|
||||||
|
Request newRequest = builder.build();
|
||||||
|
|
||||||
|
return chain.proceed(newRequest);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Gson gson = new GsonBuilder()
|
||||||
|
.registerTypeAdapter(Spanned.class, new SpannedTypeAdapter())
|
||||||
|
.registerTypeAdapter(StringWithEmoji.class, new StringWithEmojiTypeAdapter())
|
||||||
|
.create();
|
||||||
|
|
||||||
|
Retrofit retrofit = new Retrofit.Builder()
|
||||||
|
.baseUrl("https://" + domain)
|
||||||
|
.client(okHttpClient)
|
||||||
|
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
mastodonAPI = retrofit.create(MastodonAPI.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,7 +33,7 @@ import retrofit2.Response;
|
||||||
import retrofit2.Retrofit;
|
import retrofit2.Retrofit;
|
||||||
|
|
||||||
public class MyFirebaseInstanceIdService extends FirebaseInstanceIdService {
|
public class MyFirebaseInstanceIdService extends FirebaseInstanceIdService {
|
||||||
private static final String TAG = "MyFirebaseInstanceIdService";
|
private static final String TAG = "com.keylesspalace.tusky.MyFirebaseInstanceIdService";
|
||||||
|
|
||||||
private TuskyAPI tuskyAPI;
|
private TuskyAPI tuskyAPI;
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
android:name=".TuskyApplication">
|
android:name=".TuskyApplication">
|
||||||
<meta-data android:name="firebase_analytics_collection_enabled" android:value="false" />
|
|
||||||
<activity android:name=".SplashActivity">
|
<activity android:name=".SplashActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
@ -72,18 +72,6 @@
|
||||||
android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
|
android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
|
||||||
android:theme="@style/Base.Theme.AppCompat" />
|
android:theme="@style/Base.Theme.AppCompat" />
|
||||||
|
|
||||||
<service android:name=".MyFirebaseInstanceIdService" android:exported="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
|
|
||||||
</intent-filter>
|
|
||||||
</service>
|
|
||||||
|
|
||||||
<service android:name=".MyFirebaseMessagingService" android:exported="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
|
||||||
</intent-filter>
|
|
||||||
</service>
|
|
||||||
|
|
||||||
<receiver android:name=".NotificationClearBroadcastReceiver" />
|
<receiver android:name=".NotificationClearBroadcastReceiver" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
|
|
||||||
package com.keylesspalace.tusky;
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
import android.app.AlarmManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
@ -22,6 +24,7 @@ import android.graphics.Color;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.SystemClock;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
@ -32,7 +35,6 @@ import android.view.Menu;
|
||||||
import com.google.firebase.iid.FirebaseInstanceId;
|
import com.google.firebase.iid.FirebaseInstanceId;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.keylesspalace.tusky.entity.Account;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@ -47,16 +49,13 @@ import retrofit2.Callback;
|
||||||
import retrofit2.Retrofit;
|
import retrofit2.Retrofit;
|
||||||
import retrofit2.converter.gson.GsonConverterFactory;
|
import retrofit2.converter.gson.GsonConverterFactory;
|
||||||
|
|
||||||
/* There isn't presently a way to globally change the theme of a whole application at runtime, just
|
|
||||||
* individual activities. So, each activity has to set its theme before any views are created. And
|
|
||||||
* the most expedient way to accomplish this was to put it in a base class and just have every
|
|
||||||
* activity extend from it. */
|
|
||||||
public class BaseActivity extends AppCompatActivity {
|
public class BaseActivity extends AppCompatActivity {
|
||||||
private static final String TAG = "BaseActivity"; // logging tag
|
private static final String TAG = "BaseActivity"; // logging tag
|
||||||
|
|
||||||
protected MastodonAPI mastodonAPI;
|
protected MastodonAPI mastodonAPI;
|
||||||
protected TuskyAPI tuskyAPI;
|
protected TuskyAPI tuskyAPI;
|
||||||
protected Dispatcher mastodonApiDispatcher;
|
protected Dispatcher mastodonApiDispatcher;
|
||||||
|
protected PendingIntent serviceAlarmIntent;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
@ -66,6 +65,9 @@ public class BaseActivity extends AppCompatActivity {
|
||||||
createMastodonAPI();
|
createMastodonAPI();
|
||||||
createTuskyAPI();
|
createTuskyAPI();
|
||||||
|
|
||||||
|
/* There isn't presently a way to globally change the theme of a whole application at
|
||||||
|
* runtime, just individual activities. So, each activity has to set its theme before any
|
||||||
|
* views are created. */
|
||||||
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("lightTheme", false)) {
|
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("lightTheme", false)) {
|
||||||
setTheme(R.style.AppTheme_Light);
|
setTheme(R.style.AppTheme_Light);
|
||||||
}
|
}
|
||||||
|
@ -154,12 +156,14 @@ public class BaseActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createTuskyAPI() {
|
protected void createTuskyAPI() {
|
||||||
Retrofit retrofit = new Retrofit.Builder()
|
if (BuildConfig.USES_PUSH_NOTIFICATIONS) {
|
||||||
.baseUrl(getString(R.string.tusky_api_url))
|
Retrofit retrofit = new Retrofit.Builder()
|
||||||
.client(OkHttpUtils.getCompatibleClient())
|
.baseUrl(getString(R.string.tusky_api_url))
|
||||||
.build();
|
.client(OkHttpUtils.getCompatibleClient())
|
||||||
|
.build();
|
||||||
|
|
||||||
tuskyAPI = retrofit.create(TuskyAPI.class);
|
tuskyAPI = retrofit.create(TuskyAPI.class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void redirectIfNotLoggedIn() {
|
protected void redirectIfNotLoggedIn() {
|
||||||
|
@ -193,30 +197,48 @@ public class BaseActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void enablePushNotifications() {
|
protected void enablePushNotifications() {
|
||||||
tuskyAPI.register(getBaseUrl(), getAccessToken(), FirebaseInstanceId.getInstance().getToken()).enqueue(new Callback<ResponseBody>() {
|
if (BuildConfig.USES_PUSH_NOTIFICATIONS) {
|
||||||
@Override
|
tuskyAPI.register(getBaseUrl(), getAccessToken(), FirebaseInstanceId.getInstance().getToken()).enqueue(new Callback<ResponseBody>() {
|
||||||
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
|
@Override
|
||||||
Log.d(TAG, "Enable push notifications response: " + response.message());
|
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
|
||||||
}
|
Log.d(TAG, "Enable push notifications response: " + response.message());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Call<ResponseBody> call, Throwable t) {
|
public void onFailure(Call<ResponseBody> call, Throwable t) {
|
||||||
Log.d(TAG, "Enable push notifications failed: " + t.getMessage());
|
Log.d(TAG, "Enable push notifications failed: " + t.getMessage());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
// Start up the MessagingService on a repeating interval for "pull" notifications.
|
||||||
|
long checkInterval = 60 * 1000; // * 10;
|
||||||
|
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
||||||
|
Intent intent = new Intent(this, MessagingService.class);
|
||||||
|
final int SERVICE_REQUEST_CODE = 8574603; // This number is arbitrary.
|
||||||
|
serviceAlarmIntent = PendingIntent.getService(this, SERVICE_REQUEST_CODE, intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
||||||
|
SystemClock.elapsedRealtime(), checkInterval, serviceAlarmIntent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void disablePushNotifications() {
|
protected void disablePushNotifications() {
|
||||||
tuskyAPI.unregister(getBaseUrl(), getAccessToken()).enqueue(new Callback<ResponseBody>() {
|
if (BuildConfig.USES_PUSH_NOTIFICATIONS) {
|
||||||
@Override
|
tuskyAPI.unregister(getBaseUrl(), getAccessToken()).enqueue(new Callback<ResponseBody>() {
|
||||||
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
|
@Override
|
||||||
Log.d(TAG, "Disable push notifications response: " + response.message());
|
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
|
||||||
}
|
Log.d(TAG, "Disable push notifications response: " + response.message());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Call<ResponseBody> call, Throwable t) {
|
public void onFailure(Call<ResponseBody> call, Throwable t) {
|
||||||
Log.d(TAG, "Disable push notifications failed: " + t.getMessage());
|
Log.d(TAG, "Disable push notifications failed: " + t.getMessage());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else if (serviceAlarmIntent != null) {
|
||||||
|
// Cancel the repeating call for "pull" notifications.
|
||||||
|
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
||||||
|
alarmManager.cancel(serviceAlarmIntent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
|
|
||||||
package com.keylesspalace.tusky;
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
|
@ -1,3 +1,18 @@
|
||||||
|
/* Copyright 2017 Andrew Dawson
|
||||||
|
*
|
||||||
|
* This file is a part of Tusky.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Tusky 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 Tusky; if not,
|
||||||
|
* see <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
package com.keylesspalace.tusky;
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
|
|
@ -1,3 +1,18 @@
|
||||||
|
/* Copyright 2017 Andrew Dawson
|
||||||
|
*
|
||||||
|
* This file is a part of Tusky.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Tusky 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 Tusky; if not,
|
||||||
|
* see <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
package com.keylesspalace.tusky;
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
|
|
@ -1,3 +1,18 @@
|
||||||
|
/* Copyright 2017 Andrew Dawson
|
||||||
|
*
|
||||||
|
* This file is a part of Tusky.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Tusky 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 Tusky; if not,
|
||||||
|
* see <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
package com.keylesspalace.tusky;
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
interface LinkListener {
|
interface LinkListener {
|
||||||
|
|
|
@ -187,7 +187,11 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup push notifications
|
// Setup push notifications
|
||||||
if (arePushNotificationsEnabled()) enablePushNotifications();
|
if (arePushNotificationsEnabled()) {
|
||||||
|
enablePushNotifications();
|
||||||
|
} else {
|
||||||
|
disablePushNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
composeButton = floatingBtn;
|
composeButton = floatingBtn;
|
||||||
}
|
}
|
||||||
|
@ -203,7 +207,7 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
|
||||||
.apply();
|
.apply();
|
||||||
|
|
||||||
((NotificationManager) (getSystemService(NOTIFICATION_SERVICE)))
|
((NotificationManager) (getSystemService(NOTIFICATION_SERVICE)))
|
||||||
.cancel(MyFirebaseMessagingService.NOTIFY_ID);
|
.cancel(MessagingService.NOTIFY_ID);
|
||||||
|
|
||||||
/* After editing a profile, the profile header in the navigation drawer needs to be
|
/* After editing a profile, the profile header in the navigation drawer needs to be
|
||||||
* refreshed */
|
* refreshed */
|
||||||
|
|
|
@ -1,319 +0,0 @@
|
||||||
/* Copyright 2017 Andrew Dawson
|
|
||||||
*
|
|
||||||
* This file is a part of Tusky.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* Tusky 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 Tusky; if not,
|
|
||||||
* see <http://www.gnu.org/licenses>.
|
|
||||||
*
|
|
||||||
* If you modify this Program, or any covered work, by linking or combining it with Firebase Cloud
|
|
||||||
* Messaging and Firebase Crash Reporting (or a modified version of those libraries), containing
|
|
||||||
* parts covered by the Google APIs Terms of Service, the licensors of this Program grant you
|
|
||||||
* additional permission to convey the resulting work. */
|
|
||||||
|
|
||||||
package com.keylesspalace.tusky;
|
|
||||||
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.provider.Settings;
|
|
||||||
import android.support.v4.app.NotificationCompat;
|
|
||||||
import android.support.v4.app.TaskStackBuilder;
|
|
||||||
import android.text.Spanned;
|
|
||||||
|
|
||||||
import com.google.firebase.messaging.FirebaseMessagingService;
|
|
||||||
import com.google.firebase.messaging.RemoteMessage;
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
import com.keylesspalace.tusky.entity.Notification;
|
|
||||||
import com.squareup.picasso.Picasso;
|
|
||||||
import com.squareup.picasso.Target;
|
|
||||||
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import okhttp3.Interceptor;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import retrofit2.Call;
|
|
||||||
import retrofit2.Callback;
|
|
||||||
import retrofit2.Response;
|
|
||||||
import retrofit2.Retrofit;
|
|
||||||
import retrofit2.converter.gson.GsonConverterFactory;
|
|
||||||
|
|
||||||
public class MyFirebaseMessagingService extends FirebaseMessagingService {
|
|
||||||
private MastodonAPI mastodonAPI;
|
|
||||||
private static final String TAG = "MyFirebaseMessagingService";
|
|
||||||
public static final int NOTIFY_ID = 666;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMessageReceived(RemoteMessage remoteMessage) {
|
|
||||||
Log.d(TAG, remoteMessage.getFrom());
|
|
||||||
Log.d(TAG, remoteMessage.toString());
|
|
||||||
|
|
||||||
String notificationId = remoteMessage.getData().get("notification_id");
|
|
||||||
|
|
||||||
if (notificationId == null) {
|
|
||||||
Log.e(TAG, "No notification ID in payload!!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(TAG, notificationId);
|
|
||||||
|
|
||||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(
|
|
||||||
getApplicationContext());
|
|
||||||
boolean enabled = preferences.getBoolean("notificationsEnabled", true);
|
|
||||||
if (!enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
createMastodonAPI();
|
|
||||||
|
|
||||||
mastodonAPI.notification(notificationId).enqueue(new Callback<Notification>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(Call<Notification> call, Response<Notification> response) {
|
|
||||||
if (response.isSuccessful()) {
|
|
||||||
buildNotification(response.body());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Call<Notification> call, Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createMastodonAPI() {
|
|
||||||
SharedPreferences preferences = getSharedPreferences(getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
|
||||||
final String domain = preferences.getString("domain", null);
|
|
||||||
final String accessToken = preferences.getString("accessToken", null);
|
|
||||||
|
|
||||||
OkHttpClient okHttpClient = OkHttpUtils.getCompatibleClientBuilder()
|
|
||||||
.addInterceptor(new Interceptor() {
|
|
||||||
@Override
|
|
||||||
public okhttp3.Response intercept(Chain chain) throws IOException {
|
|
||||||
Request originalRequest = chain.request();
|
|
||||||
|
|
||||||
Request.Builder builder = originalRequest.newBuilder()
|
|
||||||
.header("Authorization", String.format("Bearer %s", accessToken));
|
|
||||||
|
|
||||||
Request newRequest = builder.build();
|
|
||||||
|
|
||||||
return chain.proceed(newRequest);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.build();
|
|
||||||
|
|
||||||
Gson gson = new GsonBuilder()
|
|
||||||
.registerTypeAdapter(Spanned.class, new SpannedTypeAdapter())
|
|
||||||
.registerTypeAdapter(StringWithEmoji.class, new StringWithEmojiTypeAdapter())
|
|
||||||
.create();
|
|
||||||
|
|
||||||
Retrofit retrofit = new Retrofit.Builder()
|
|
||||||
.baseUrl("https://" + domain)
|
|
||||||
.client(okHttpClient)
|
|
||||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
mastodonAPI = retrofit.create(MastodonAPI.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String truncateWithEllipses(String string, int limit) {
|
|
||||||
if (string.length() < limit) {
|
|
||||||
return string;
|
|
||||||
} else {
|
|
||||||
return string.substring(0, limit - 3) + "...";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean filterNotification(SharedPreferences preferences,
|
|
||||||
Notification notification) {
|
|
||||||
switch (notification.type) {
|
|
||||||
default:
|
|
||||||
case MENTION: {
|
|
||||||
return preferences.getBoolean("notificationFilterMentions", true);
|
|
||||||
}
|
|
||||||
case FOLLOW: {
|
|
||||||
return preferences.getBoolean("notificationFilterFollows", true);
|
|
||||||
}
|
|
||||||
case REBLOG: {
|
|
||||||
return preferences.getBoolean("notificationFilterReblogs", true);
|
|
||||||
}
|
|
||||||
case FAVOURITE: {
|
|
||||||
return preferences.getBoolean("notificationFilterFavourites", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void buildNotification(Notification body) {
|
|
||||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
|
||||||
final SharedPreferences notificationPreferences = getApplicationContext().getSharedPreferences("Notifications", MODE_PRIVATE);
|
|
||||||
|
|
||||||
if (!filterNotification(preferences, body)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String rawCurrentNotifications = notificationPreferences.getString("current", "[]");
|
|
||||||
JSONArray currentNotifications;
|
|
||||||
|
|
||||||
try {
|
|
||||||
currentNotifications = new JSONArray(rawCurrentNotifications);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
currentNotifications = new JSONArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean alreadyContains = false;
|
|
||||||
|
|
||||||
for(int i = 0; i < currentNotifications.length(); i++) {
|
|
||||||
try {
|
|
||||||
if (currentNotifications.getString(i).equals(body.account.displayName)) {
|
|
||||||
alreadyContains = true;
|
|
||||||
}
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!alreadyContains) {
|
|
||||||
currentNotifications.put(body.account.displayName);
|
|
||||||
}
|
|
||||||
|
|
||||||
SharedPreferences.Editor editor = notificationPreferences.edit();
|
|
||||||
editor.putString("current", currentNotifications.toString());
|
|
||||||
editor.commit();
|
|
||||||
|
|
||||||
Intent resultIntent = new Intent(this, MainActivity.class);
|
|
||||||
resultIntent.putExtra("tab_position", 1);
|
|
||||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
|
|
||||||
stackBuilder.addParentStack(MainActivity.class);
|
|
||||||
stackBuilder.addNextIntent(resultIntent);
|
|
||||||
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
|
||||||
|
|
||||||
Intent deleteIntent = new Intent(this, NotificationClearBroadcastReceiver.class);
|
|
||||||
PendingIntent deletePendingIntent = PendingIntent.getBroadcast(this, 0, deleteIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
|
||||||
|
|
||||||
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
|
|
||||||
.setSmallIcon(R.drawable.ic_notify)
|
|
||||||
.setContentIntent(resultPendingIntent)
|
|
||||||
.setDeleteIntent(deletePendingIntent)
|
|
||||||
.setDefaults(0); // So it doesn't ring twice, notify only in Target callback
|
|
||||||
|
|
||||||
if (currentNotifications.length() == 1) {
|
|
||||||
builder.setContentTitle(titleForType(body))
|
|
||||||
.setContentText(truncateWithEllipses(bodyForType(body), 40));
|
|
||||||
|
|
||||||
Target mTarget = new Target() {
|
|
||||||
@Override
|
|
||||||
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
|
|
||||||
builder.setLargeIcon(bitmap);
|
|
||||||
|
|
||||||
setupPreferences(preferences, builder);
|
|
||||||
|
|
||||||
((NotificationManager) (getSystemService(NOTIFICATION_SERVICE))).notify(NOTIFY_ID, builder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBitmapFailed(Drawable errorDrawable) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPrepareLoad(Drawable placeHolderDrawable) {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Picasso.with(this)
|
|
||||||
.load(body.account.avatar)
|
|
||||||
.placeholder(R.drawable.avatar_default)
|
|
||||||
.transform(new RoundedTransformation(7, 0))
|
|
||||||
.into(mTarget);
|
|
||||||
} else {
|
|
||||||
setupPreferences(preferences, builder);
|
|
||||||
|
|
||||||
try {
|
|
||||||
builder.setContentTitle(String.format(getString(R.string.notification_title_summary), currentNotifications.length()))
|
|
||||||
.setContentText(truncateWithEllipses(joinNames(currentNotifications), 40));
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
builder.setVisibility(android.app.Notification.VISIBILITY_PRIVATE);
|
|
||||||
builder.setCategory(android.app.Notification.CATEGORY_SOCIAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
((NotificationManager) (getSystemService(NOTIFICATION_SERVICE))).notify(NOTIFY_ID, builder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupPreferences(SharedPreferences preferences, NotificationCompat.Builder builder) {
|
|
||||||
if (preferences.getBoolean("notificationAlertSound", true)) {
|
|
||||||
builder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preferences.getBoolean("notificationAlertVibrate", false)) {
|
|
||||||
builder.setVibrate(new long[] { 500, 500 });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preferences.getBoolean("notificationAlertLight", false)) {
|
|
||||||
builder.setLights(0xFF00FF8F, 300, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String joinNames(JSONArray array) throws JSONException {
|
|
||||||
if (array.length() > 3) {
|
|
||||||
return String.format(getString(R.string.notification_summary_large), array.get(0), array.get(1), array.get(2), array.length() - 3);
|
|
||||||
} else if (array.length() == 3) {
|
|
||||||
return String.format(getString(R.string.notification_summary_medium), array.get(0), array.get(1), array.get(2));
|
|
||||||
} else if (array.length() == 2) {
|
|
||||||
return String.format(getString(R.string.notification_summary_small), array.get(0), array.get(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String titleForType(Notification notification) {
|
|
||||||
switch (notification.type) {
|
|
||||||
case MENTION:
|
|
||||||
return String.format(getString(R.string.notification_mention_format), notification.account.getDisplayName());
|
|
||||||
case FOLLOW:
|
|
||||||
return String.format(getString(R.string.notification_follow_format), notification.account.getDisplayName());
|
|
||||||
case FAVOURITE:
|
|
||||||
return String.format(getString(R.string.notification_favourite_format), notification.account.getDisplayName());
|
|
||||||
case REBLOG:
|
|
||||||
return String.format(getString(R.string.notification_reblog_format), notification.account.getDisplayName());
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String bodyForType(Notification notification) {
|
|
||||||
switch (notification.type) {
|
|
||||||
case FOLLOW:
|
|
||||||
return notification.account.username;
|
|
||||||
case MENTION:
|
|
||||||
case FAVOURITE:
|
|
||||||
case REBLOG:
|
|
||||||
return notification.status.content.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,224 @@
|
||||||
|
/* Copyright 2017 Andrew Dawson
|
||||||
|
*
|
||||||
|
* This file is a part of Tusky.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Tusky 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 Tusky; if not,
|
||||||
|
* see <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
import android.support.v4.app.TaskStackBuilder;
|
||||||
|
|
||||||
|
import com.keylesspalace.tusky.entity.Notification;
|
||||||
|
import com.squareup.picasso.Picasso;
|
||||||
|
import com.squareup.picasso.Target;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
class NotificationMaker {
|
||||||
|
static void make(final Context context, final int notifyId, Notification body) {
|
||||||
|
final SharedPreferences preferences =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
final SharedPreferences notificationPreferences = context.getSharedPreferences(
|
||||||
|
"Notifications", Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
if (!filterNotification(preferences, body)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String rawCurrentNotifications = notificationPreferences.getString("current", "[]");
|
||||||
|
JSONArray currentNotifications;
|
||||||
|
|
||||||
|
try {
|
||||||
|
currentNotifications = new JSONArray(rawCurrentNotifications);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
currentNotifications = new JSONArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean alreadyContains = false;
|
||||||
|
|
||||||
|
for(int i = 0; i < currentNotifications.length(); i++) {
|
||||||
|
try {
|
||||||
|
if (currentNotifications.getString(i).equals(body.account.getDisplayName())) {
|
||||||
|
alreadyContains = true;
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!alreadyContains) {
|
||||||
|
currentNotifications.put(body.account.getDisplayName());
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationPreferences.edit()
|
||||||
|
.putString("current", currentNotifications.toString())
|
||||||
|
.commit();
|
||||||
|
|
||||||
|
Intent resultIntent = new Intent(context, MainActivity.class);
|
||||||
|
resultIntent.putExtra("tab_position", 1);
|
||||||
|
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
|
||||||
|
stackBuilder.addParentStack(MainActivity.class);
|
||||||
|
stackBuilder.addNextIntent(resultIntent);
|
||||||
|
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
|
||||||
|
Intent deleteIntent = new Intent(context, NotificationClearBroadcastReceiver.class);
|
||||||
|
PendingIntent deletePendingIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
|
|
||||||
|
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
|
||||||
|
.setSmallIcon(R.drawable.ic_notify)
|
||||||
|
.setContentIntent(resultPendingIntent)
|
||||||
|
.setDeleteIntent(deletePendingIntent)
|
||||||
|
.setDefaults(0); // So it doesn't ring twice, notify only in Target callback
|
||||||
|
|
||||||
|
if (currentNotifications.length() == 1) {
|
||||||
|
builder.setContentTitle(titleForType(context, body))
|
||||||
|
.setContentText(truncateWithEllipses(bodyForType(body), 40));
|
||||||
|
|
||||||
|
Target mTarget = new Target() {
|
||||||
|
@Override
|
||||||
|
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
|
||||||
|
builder.setLargeIcon(bitmap);
|
||||||
|
|
||||||
|
setupPreferences(preferences, builder);
|
||||||
|
|
||||||
|
((NotificationManager) (context.getSystemService(Context.NOTIFICATION_SERVICE)))
|
||||||
|
.notify(notifyId, builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBitmapFailed(Drawable errorDrawable) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPrepareLoad(Drawable placeHolderDrawable) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
Picasso.with(context)
|
||||||
|
.load(body.account.avatar)
|
||||||
|
.placeholder(R.drawable.avatar_default)
|
||||||
|
.transform(new RoundedTransformation(7, 0))
|
||||||
|
.into(mTarget);
|
||||||
|
} else {
|
||||||
|
setupPreferences(preferences, builder);
|
||||||
|
try {
|
||||||
|
builder.setContentTitle(String.format(context.getString(R.string.notification_title_summary), currentNotifications.length()))
|
||||||
|
.setContentText(truncateWithEllipses(joinNames(context, currentNotifications), 40));
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
builder.setVisibility(android.app.Notification.VISIBILITY_PRIVATE);
|
||||||
|
builder.setCategory(android.app.Notification.CATEGORY_SOCIAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
((NotificationManager) (context.getSystemService(Context.NOTIFICATION_SERVICE)))
|
||||||
|
.notify(notifyId, builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean filterNotification(SharedPreferences preferences,
|
||||||
|
Notification notification) {
|
||||||
|
switch (notification.type) {
|
||||||
|
default:
|
||||||
|
case MENTION: {
|
||||||
|
return preferences.getBoolean("notificationFilterMentions", true);
|
||||||
|
}
|
||||||
|
case FOLLOW: {
|
||||||
|
return preferences.getBoolean("notificationFilterFollows", true);
|
||||||
|
}
|
||||||
|
case REBLOG: {
|
||||||
|
return preferences.getBoolean("notificationFilterReblogs", true);
|
||||||
|
}
|
||||||
|
case FAVOURITE: {
|
||||||
|
return preferences.getBoolean("notificationFilterFavourites", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String truncateWithEllipses(String string, int limit) {
|
||||||
|
if (string.length() < limit) {
|
||||||
|
return string;
|
||||||
|
} else {
|
||||||
|
return string.substring(0, limit - 3) + "...";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setupPreferences(SharedPreferences preferences,
|
||||||
|
NotificationCompat.Builder builder) {
|
||||||
|
if (preferences.getBoolean("notificationAlertSound", true)) {
|
||||||
|
builder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferences.getBoolean("notificationAlertVibrate", false)) {
|
||||||
|
builder.setVibrate(new long[] { 500, 500 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferences.getBoolean("notificationAlertLight", false)) {
|
||||||
|
builder.setLights(0xFF00FF8F, 300, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static String joinNames(Context context, JSONArray array) throws JSONException {
|
||||||
|
if (array.length() > 3) {
|
||||||
|
return String.format(context.getString(R.string.notification_summary_large), array.get(0), array.get(1), array.get(2), array.length() - 3);
|
||||||
|
} else if (array.length() == 3) {
|
||||||
|
return String.format(context.getString(R.string.notification_summary_medium), array.get(0), array.get(1), array.get(2));
|
||||||
|
} else if (array.length() == 2) {
|
||||||
|
return String.format(context.getString(R.string.notification_summary_small), array.get(0), array.get(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static String titleForType(Context context, Notification notification) {
|
||||||
|
switch (notification.type) {
|
||||||
|
case MENTION:
|
||||||
|
return String.format(context.getString(R.string.notification_mention_format), notification.account.getDisplayName());
|
||||||
|
case FOLLOW:
|
||||||
|
return String.format(context.getString(R.string.notification_follow_format), notification.account.getDisplayName());
|
||||||
|
case FAVOURITE:
|
||||||
|
return String.format(context.getString(R.string.notification_favourite_format), notification.account.getDisplayName());
|
||||||
|
case REBLOG:
|
||||||
|
return String.format(context.getString(R.string.notification_reblog_format), notification.account.getDisplayName());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static String bodyForType(Notification notification) {
|
||||||
|
switch (notification.type) {
|
||||||
|
case FOLLOW:
|
||||||
|
return notification.account.username;
|
||||||
|
case MENTION:
|
||||||
|
case FAVOURITE:
|
||||||
|
case REBLOG:
|
||||||
|
return notification.status.content.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,7 +42,7 @@ import okhttp3.OkHttpClient;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
|
||||||
class OkHttpUtils {
|
public class OkHttpUtils {
|
||||||
static final String TAG = "OkHttpUtils"; // logging tag
|
static final String TAG = "OkHttpUtils"; // logging tag
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,8 +58,7 @@ class OkHttpUtils {
|
||||||
* TLS 1.1 and 1.2 have to be manually enabled on API levels 16-20.
|
* TLS 1.1 and 1.2 have to be manually enabled on API levels 16-20.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
static OkHttpClient.Builder getCompatibleClientBuilder() {
|
public static OkHttpClient.Builder getCompatibleClientBuilder() {
|
||||||
|
|
||||||
ConnectionSpec fallback = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
ConnectionSpec fallback = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
||||||
.allEnabledCipherSuites()
|
.allEnabledCipherSuites()
|
||||||
.supportsTlsExtensions(true)
|
.supportsTlsExtensions(true)
|
||||||
|
@ -79,7 +78,7 @@ class OkHttpUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
static OkHttpClient getCompatibleClient() {
|
public static OkHttpClient getCompatibleClient() {
|
||||||
return getCompatibleClientBuilder().build();
|
return getCompatibleClientBuilder().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ import com.google.gson.JsonParseException;
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
class SpannedTypeAdapter implements JsonDeserializer<Spanned> {
|
public class SpannedTypeAdapter implements JsonDeserializer<Spanned> {
|
||||||
@Override
|
@Override
|
||||||
public Spanned deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
public Spanned deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||||
return HtmlUtils.fromHtml(Emojione.shortnameToUnicode(json.getAsString(), false));
|
return HtmlUtils.fromHtml(Emojione.shortnameToUnicode(json.getAsString(), false));
|
||||||
|
|
|
@ -1,3 +1,18 @@
|
||||||
|
/* Copyright 2017 Andrew Dawson
|
||||||
|
*
|
||||||
|
* This file is a part of Tusky.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Tusky 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 Tusky; if not,
|
||||||
|
* see <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
package com.keylesspalace.tusky;
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
interface StatusRemoveListener {
|
interface StatusRemoveListener {
|
||||||
|
|
|
@ -1,3 +1,18 @@
|
||||||
|
/* Copyright 2017 Andrew Dawson
|
||||||
|
*
|
||||||
|
* This file is a part of Tusky.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Tusky 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 Tusky; if not,
|
||||||
|
* see <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
package com.keylesspalace.tusky;
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,3 +1,18 @@
|
||||||
|
/* Copyright 2017 Andrew Dawson
|
||||||
|
*
|
||||||
|
* This file is a part of Tusky.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Tusky 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 Tusky; if not,
|
||||||
|
* see <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
package com.keylesspalace.tusky;
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
import com.emojione.Emojione;
|
import com.emojione.Emojione;
|
||||||
|
|
Loading…
Reference in New Issue