mirror of https://github.com/tuskyapp/Tusky.git
13 changed files with 420 additions and 360 deletions
@ -0,0 +1,61 @@
|
||||
package com.keylesspalace.tusky; |
||||
|
||||
import android.content.Context; |
||||
import android.content.SharedPreferences; |
||||
|
||||
import com.google.firebase.iid.FirebaseInstanceId; |
||||
import com.google.firebase.iid.FirebaseInstanceIdService; |
||||
|
||||
import okhttp3.ResponseBody; |
||||
import retrofit2.Call; |
||||
import retrofit2.Callback; |
||||
import retrofit2.Response; |
||||
import retrofit2.Retrofit; |
||||
|
||||
public class MyFirebaseInstanceIdService extends FirebaseInstanceIdService { |
||||
|
||||
private TuskyAPI tuskyAPI; |
||||
|
||||
protected void createTuskyAPI() { |
||||
Retrofit retrofit = new Retrofit.Builder() |
||||
.baseUrl(getString(R.string.tusky_api_url)) |
||||
.build(); |
||||
|
||||
tuskyAPI = retrofit.create(TuskyAPI.class); |
||||
} |
||||
|
||||
@Override |
||||
public void onTokenRefresh() { |
||||
createTuskyAPI(); |
||||
|
||||
String refreshedToken = FirebaseInstanceId.getInstance().getToken(); |
||||
SharedPreferences preferences = getSharedPreferences(getString(R.string.preferences_file_key), Context.MODE_PRIVATE); |
||||
String accessToken = preferences.getString("accessToken", null); |
||||
String domain = preferences.getString("domain", null); |
||||
|
||||
if (accessToken != null && domain != null) { |
||||
tuskyAPI.unregister("https://" + domain, accessToken).enqueue(new Callback<ResponseBody>() { |
||||
@Override |
||||
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void onFailure(Call<ResponseBody> call, Throwable t) { |
||||
|
||||
} |
||||
}); |
||||
tuskyAPI.register("https://" + domain, accessToken, refreshedToken).enqueue(new Callback<ResponseBody>() { |
||||
@Override |
||||
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void onFailure(Call<ResponseBody> call, Throwable t) { |
||||
|
||||
} |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,189 @@
|
||||
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 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"; |
||||
|
||||
@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); |
||||
|
||||
createMastodonAPI(); |
||||
|
||||
mastodonAPI.notification(notificationId).enqueue(new Callback<Notification>() { |
||||
@Override |
||||
public void onResponse(Call<Notification> call, Response<Notification> response) { |
||||
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 = new OkHttpClient.Builder() |
||||
.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()) |
||||
.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 void buildNotification(Notification body) { |
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); |
||||
|
||||
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); |
||||
|
||||
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this) |
||||
.setSmallIcon(R.drawable.ic_notify) |
||||
.setAutoCancel(true) |
||||
.setContentIntent(resultPendingIntent); |
||||
|
||||
final Integer mId = (int)(System.currentTimeMillis() / 1000); |
||||
|
||||
Target mTarget = new Target() { |
||||
@Override |
||||
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { |
||||
builder.setLargeIcon(bitmap); |
||||
((NotificationManager) (getSystemService(NOTIFICATION_SERVICE))).notify(mId, 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) |
||||
.into(mTarget); |
||||
|
||||
if (preferences.getBoolean("notificationAlertSound", true)) { |
||||
builder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI); |
||||
} |
||||
|
||||
if (preferences.getBoolean("notificationStyleVibrate", false)) { |
||||
builder.setVibrate(new long[] { 500, 500 }); |
||||
} |
||||
|
||||
if (preferences.getBoolean("notificationStyleLight", false)) { |
||||
builder.setLights(0xFF00FF8F, 300, 1000); |
||||
} |
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
||||
builder.setVisibility(android.app.Notification.VISIBILITY_PRIVATE); |
||||
builder.setCategory(android.app.Notification.CATEGORY_SOCIAL); |
||||
} |
||||
|
||||
switch (body.type) { |
||||
case MENTION: |
||||
builder.setContentTitle(String.format(getString(R.string.notification_mention_format), body.account.getDisplayName())) |
||||
.setContentText(truncateWithEllipses(body.status.content.toString(), 40)); |
||||
break; |
||||
case FOLLOW: |
||||
builder.setContentTitle(String.format(getString(R.string.notification_follow_format), body.account.getDisplayName())) |
||||
.setContentText(truncateWithEllipses(body.account.username, 40)); |
||||
break; |
||||
case FAVOURITE: |
||||
builder.setContentTitle(String.format(getString(R.string.notification_favourite_format), body.account.getDisplayName())) |
||||
.setContentText(truncateWithEllipses(body.status.content.toString(), 40)); |
||||
break; |
||||
case REBLOG: |
||||
builder.setContentTitle(String.format(getString(R.string.notification_reblog_format), body.account.getDisplayName())) |
||||
.setContentText(truncateWithEllipses(body.status.content.toString(), 40)); |
||||
break; |
||||
} |
||||
|
||||
((NotificationManager) (getSystemService(NOTIFICATION_SERVICE))).notify(mId, builder.build()); |
||||
} |
||||
} |
||||
@ -1,233 +0,0 @@
|
||||
/* Copyright 2017 Andrew Dawson |
||||
* |
||||
* This file is part of Tusky. |
||||
* |
||||
* Tusky 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.*; |
||||
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 android.text.Spanned; |
||||
|
||||
import com.google.gson.Gson; |
||||
import com.google.gson.GsonBuilder; |
||||
import com.keylesspalace.tusky.entity.*; |
||||
import com.keylesspalace.tusky.entity.Notification; |
||||
import com.squareup.picasso.Picasso; |
||||
import com.squareup.picasso.Target; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import okhttp3.Interceptor; |
||||
import okhttp3.OkHttpClient; |
||||
import okhttp3.Request; |
||||
import retrofit2.Call; |
||||
import retrofit2.Callback; |
||||
import retrofit2.Retrofit; |
||||
import retrofit2.converter.gson.GsonConverterFactory; |
||||
|
||||
public class PullNotificationService extends IntentService { |
||||
static final int NOTIFY_ID = 6; // This is an arbitrary number.
|
||||
private static final String TAG = "PullNotifications"; // logging tag
|
||||
|
||||
public PullNotificationService() { |
||||
super("Tusky Pull Notification Service"); |
||||
} |
||||
|
||||
@Override |
||||
protected void onHandleIntent(Intent intent) { |
||||
SharedPreferences preferences = getSharedPreferences( |
||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE); |
||||
String domain = preferences.getString("domain", null); |
||||
String accessToken = preferences.getString("accessToken", null); |
||||
String lastUpdateId = preferences.getString("lastUpdateId", null); |
||||
checkNotifications(domain, accessToken, lastUpdateId); |
||||
} |
||||
|
||||
private void checkNotifications(final String domain, final String accessToken, |
||||
final String lastUpdateId) { |
||||
OkHttpClient okHttpClient = new OkHttpClient.Builder() |
||||
.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()) |
||||
.create(); |
||||
|
||||
Retrofit retrofit = new Retrofit.Builder() |
||||
.baseUrl("https://" + domain) |
||||
.client(okHttpClient) |
||||
.addConverterFactory(GsonConverterFactory.create(gson)) |
||||
.build(); |
||||
|
||||
MastodonAPI api = retrofit.create(MastodonAPI.class); |
||||
|
||||
api.notifications(null, lastUpdateId, null).enqueue(new Callback<List<Notification>>() { |
||||
@Override |
||||
public void onResponse(Call<List<Notification>> call, retrofit2.Response<List<Notification>> response) { |
||||
onCheckNotificationsSuccess(response.body(), lastUpdateId); |
||||
} |
||||
|
||||
@Override |
||||
public void onFailure(Call<List<Notification>> call, Throwable t) { |
||||
onCheckNotificationsFailure((Exception) t); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
private void onCheckNotificationsSuccess(List<com.keylesspalace.tusky.entity.Notification> notifications, String lastUpdateId) { |
||||
List<MentionResult> mentions = new ArrayList<>(); |
||||
|
||||
for (com.keylesspalace.tusky.entity.Notification notification : notifications) { |
||||
if (notification.type == com.keylesspalace.tusky.entity.Notification.Type.MENTION) { |
||||
Status status = notification.status; |
||||
|
||||
if (status != null) { |
||||
MentionResult mention = new MentionResult(); |
||||
mention.content = status.content.toString(); |
||||
mention.displayName = notification.account.getDisplayName(); |
||||
mention.avatarUrl = status.account.avatar; |
||||
mentions.add(mention); |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (notifications.size() > 0) { |
||||
SharedPreferences preferences = getSharedPreferences( |
||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE); |
||||
SharedPreferences.Editor editor = preferences.edit(); |
||||
editor.putString("lastUpdateId", notifications.get(0).id); |
||||
editor.apply(); |
||||
} |
||||
|
||||
if (mentions.size() > 0) { |
||||
loadAvatar(mentions, mentions.get(0).avatarUrl); |
||||
} |
||||
} |
||||
|
||||
private void onCheckNotificationsFailure(Exception exception) { |
||||
Log.e(TAG, "Failed to check notifications. " + exception.getMessage()); |
||||
} |
||||
|
||||
private static class MentionResult { |
||||
String displayName; |
||||
String content; |
||||
String avatarUrl; |
||||
} |
||||
|
||||
private String truncateWithEllipses(String string, int limit) { |
||||
if (string.length() < limit) { |
||||
return string; |
||||
} else { |
||||
return string.substring(0, limit - 3) + "..."; |
||||
} |
||||
} |
||||
|
||||
private void loadAvatar(final List<MentionResult> mentions, String url) { |
||||
if (url != null) { |
||||
Target target = new Target() { |
||||
@Override |
||||
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { |
||||
updateNotification(mentions, bitmap); |
||||
} |
||||
|
||||
@Override |
||||
public void onBitmapFailed(Drawable errorDrawable) { |
||||
updateNotification(mentions, null); |
||||
} |
||||
|
||||
@Override |
||||
public void onPrepareLoad(Drawable placeHolderDrawable) {} |
||||
}; |
||||
Picasso.with(this) |
||||
.load(url) |
||||
.into(target); |
||||
} else { |
||||
updateNotification(mentions, null); |
||||
} |
||||
} |
||||
|
||||
private void updateNotification(List<MentionResult> mentions, @Nullable Bitmap icon) { |
||||
final int NOTIFICATION_CONTENT_LIMIT = 40; |
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); |
||||
String title; |
||||
if (mentions.size() > 1) { |
||||
title = String.format( |
||||
getString(R.string.notification_service_several_mentions), |
||||
mentions.size()); |
||||
} else { |
||||
title = String.format( |
||||
getString(R.string.notification_service_one_mention), |
||||
mentions.get(0).displayName); |
||||
} |
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this) |
||||
.setSmallIcon(R.drawable.ic_notify) |
||||
.setContentTitle(title); |
||||
if (icon != null) { |
||||
builder.setLargeIcon(icon); |
||||
} |
||||
if (preferences.getBoolean("notificationAlertSound", true)) { |
||||
builder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI); |
||||
} |
||||
if (preferences.getBoolean("notificationStyleVibrate", false)) { |
||||
builder.setVibrate(new long[] { 500, 500 }); |
||||
} |
||||
if (preferences.getBoolean("notificationStyleLight", false)) { |
||||
builder.setLights(0xFF00FF8F, 300, 1000); |
||||
} |
||||
for (int i = 0; i < mentions.size(); i++) { |
||||
MentionResult mention = mentions.get(i); |
||||
String text = truncateWithEllipses(mention.content, NOTIFICATION_CONTENT_LIMIT); |
||||
builder.setContentText(text) |
||||
.setNumber(i); |
||||
} |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
||||
builder.setVisibility(android.app.Notification.VISIBILITY_PRIVATE); |
||||
builder.setCategory(android.app.Notification.CATEGORY_SOCIAL); |
||||
} |
||||
Intent resultIntent = new Intent(this, SplashActivity.class); |
||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); |
||||
stackBuilder.addParentStack(SplashActivity.class); |
||||
stackBuilder.addNextIntent(resultIntent); |
||||
PendingIntent resultPendingIntent = |
||||
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); |
||||
builder.setContentIntent(resultPendingIntent); |
||||
NotificationManager notificationManager = |
||||
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); |
||||
notificationManager.notify(NOTIFY_ID, builder.build()); |
||||
} |
||||
} |
||||
@ -0,0 +1,16 @@
|
||||
package com.keylesspalace.tusky; |
||||
|
||||
import okhttp3.ResponseBody; |
||||
import retrofit2.Call; |
||||
import retrofit2.http.Field; |
||||
import retrofit2.http.FormUrlEncoded; |
||||
import retrofit2.http.POST; |
||||
|
||||
public interface TuskyAPI { |
||||
@FormUrlEncoded |
||||
@POST("/register") |
||||
Call<ResponseBody> register(@Field("instance_url") String instanceUrl, @Field("access_token") String accessToken, @Field("device_token") String deviceToken); |
||||
@FormUrlEncoded |
||||
@POST("/unregister") |
||||
Call<ResponseBody> unregister(@Field("instance_url") String instanceUrl, @Field("access_token") String accessToken); |
||||
} |
||||
Loading…
Reference in new issue