From ac7428ec57e633318c4ee39ee380ad80234f3815 Mon Sep 17 00:00:00 2001 From: Pangoraw Date: Sun, 14 May 2017 11:02:12 +0000 Subject: [PATCH 01/21] Use softer color for light theme. --- app/src/main/res/values/colors.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index d5c8bd02e..87305fbe3 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -44,15 +44,15 @@ #52a5e0 #f4f4f4 #f4f4f4 - #000000 - #000000 + #CC000000 + #CC000000 #3c3c3c #5f636f #ffffff #ffffff #ffffff #f6f7f7 - #000000 + #7C000000 #4f4f4f #4f4f4f #56a7e1 @@ -63,9 +63,9 @@ #cfcfcf #cfcfcf #9F9F9F - #000000 + #DE000000 #EFEFEF - #000000 + #DE000000 #8F8F8F #2F5F6F #EFEFEF From 881a1380b5350d60c5efb11424e080813fe665fd Mon Sep 17 00:00:00 2001 From: Pangoraw Date: Sun, 14 May 2017 11:40:00 +0000 Subject: [PATCH 02/21] Improve french translation. --- app/src/main/res/values-fr/strings.xml | 28 ++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index db0fe884e..3308a4532 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -29,9 +29,12 @@ Abonnés Favoris Utilisateurs bloqués + Utilisateurs muets + Editer votre profil + Demande de follow \@%s - %s boosté + %s a boosté Média sensible Cliquer pour voir Voir plus @@ -91,7 +94,12 @@ Nettoyer Sauvegarder Modifier le profil - + Accepter + Voulez-vous vous déconnectez ? + Rejeter + Annuler + Demandes de follow + Utilisateurs muets Partager l’URL de votre pouet avec… Partager le pouet avec… @@ -122,6 +130,7 @@ Média mis en ligne avec succès. Mise en ligne… Télécharger + Demande de follow en attente d\'une réponse Public : afficher dans les fils publics. Non listé : ne pas afficher dans les fils publics. @@ -145,6 +154,10 @@ Navigateur Utiliser le navigateur intégré. Masquer le bouton de suivi lors du défilement. + Montrer les boosts + Montrer les réponses + Onglets + Filtrage de fil %s a mentionné votre nom. %1$s, %2$s, %3$s et %4$d plus @@ -155,4 +168,15 @@ Compte bloqué. Partager le contenu du pouet. Partager le lien du pouet. + + Compte officiel de Tusky + À propos + Site du projet : https://tusky.keylesspalace.com + Rapport de bugs & demande de fonctionnalités : https://github.com/Vavassor/Tusky/issues + Version : %s + + + Demande effectuée + + Utilisateur débloqué From 6752d45d4bb0d503cd0d62580dfb3ccd12c01848 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Tue, 16 May 2017 22:19:34 -0400 Subject: [PATCH 03/21] Initial client working for MQTT push notifications. --- app/build.gradle | 4 + app/src/main/AndroidManifest.xml | 4 + .../com/keylesspalace/tusky/BaseActivity.java | 43 +-- .../keylesspalace/tusky/ComposeActivity.java | 4 +- .../tusky/fragment/NotificationsFragment.java | 3 + .../service/PushNotificationService.java | 257 ++++++++++++++++++ .../tusky/util/PushNotificationClient.java | 256 +++++++++++++++++ app/src/main/res/values/donottranslate.xml | 1 + 8 files changed, 534 insertions(+), 38 deletions(-) create mode 100644 app/src/main/java/com/keylesspalace/tusky/service/PushNotificationService.java create mode 100644 app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java diff --git a/app/build.gradle b/app/build.gradle index cf42c9791..5ef9205d3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -63,6 +63,10 @@ dependencies { googleCompile 'com.google.firebase:firebase-crash:10.2.4' testCompile 'junit:junit:4.12' annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' + compile 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0' + compile('org.eclipse.paho:org.eclipse.paho.android.service:1.1.1') { + exclude module: 'support-v4' + } } apply plugin: 'com.google.gms.google-services' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 058d165b3..1fd924b55 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + + () { - @Override - public void onResponse(Call call, retrofit2.Response response) { - Log.d(TAG, "Enable push notifications response: " + response.message()); - } - - @Override - public void onFailure(Call call, Throwable t) { - Log.d(TAG, "Enable push notifications failed: " + t.getMessage()); - } - }); + pushNotificationClient.subscribeToTopic(); } else { // Start up the MessagingService on a repeating interval for "pull" notifications. long checkInterval = 60 * 1000 * 5; @@ -231,17 +210,7 @@ public class BaseActivity extends AppCompatActivity { protected void disablePushNotifications() { if (BuildConfig.USES_PUSH_NOTIFICATIONS) { - tuskyAPI.unregister(getBaseUrl(), getAccessToken()).enqueue(new Callback() { - @Override - public void onResponse(Call call, retrofit2.Response response) { - Log.d(TAG, "Disable push notifications response: " + response.message()); - } - - @Override - public void onFailure(Call call, Throwable t) { - Log.d(TAG, "Disable push notifications failed: " + t.getMessage()); - } - }); + pushNotificationClient.unsubscribeToTopic(); } else if (serviceAlarmIntent != null) { // Cancel the repeating call for "pull" notifications. AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); diff --git a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java index f0089fd2f..319257b44 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java @@ -307,7 +307,9 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag if (replyVisibility != null && startingVisibility != null) { // Lowest possible visibility setting in response - if (startingVisibility.equals("private") || replyVisibility.equals("private")) { + if (startingVisibility.equals("direct") || replyVisibility.equals("direct")) { + startingVisibility = "direct"; + } else if (startingVisibility.equals("private") || replyVisibility.equals("private")) { startingVisibility = "private"; } else if (startingVisibility.equals("unlisted") || replyVisibility.equals("unlisted")) { startingVisibility = "unlisted"; diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java index bef04290d..234ece7fc 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java @@ -233,6 +233,9 @@ public class NotificationsFragment extends SFragment implements } else { adapter.update(notifications); } + for (Notification notification : notifications) { + Log.d(TAG, "id: " + notification.id); + } if (notifications.size() == 0 && adapter.getItemCount() == 1) { adapter.setFooterState(NotificationsAdapter.FooterState.EMPTY); } else if (fromId != null) { diff --git a/app/src/main/java/com/keylesspalace/tusky/service/PushNotificationService.java b/app/src/main/java/com/keylesspalace/tusky/service/PushNotificationService.java new file mode 100644 index 000000000..7db3e1241 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/service/PushNotificationService.java @@ -0,0 +1,257 @@ +package com.keylesspalace.tusky.service; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Binder; +import android.os.IBinder; +import android.preference.PreferenceManager; +import android.support.annotation.Nullable; +import android.text.Spanned; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.keylesspalace.tusky.R; +import com.keylesspalace.tusky.entity.Notification; +import com.keylesspalace.tusky.json.SpannedTypeAdapter; +import com.keylesspalace.tusky.json.StringWithEmoji; +import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter; +import com.keylesspalace.tusky.network.MastodonAPI; +import com.keylesspalace.tusky.util.Log; +import com.keylesspalace.tusky.util.NotificationMaker; +import com.keylesspalace.tusky.util.OkHttpUtils; + +import org.eclipse.paho.android.service.MqttAndroidClient; +import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions; +import org.eclipse.paho.client.mqttv3.IMqttActionListener; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.IMqttToken; +import org.eclipse.paho.client.mqttv3.MqttCallbackExtended; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; + +import java.io.IOException; +import java.util.Locale; +import java.util.UUID; + +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 PushNotificationService extends Service { + private class LocalBinder extends Binder { + PushNotificationService getService() { + return PushNotificationService.this; + } + } + + private static final String TAG = "PushNotificationService"; + private static final String CLIENT_NAME = "TuskyMastodonClient"; + private static final String TOPIC = "tusky/notification"; + private static final int NOTIFY_ID = 666; + + private final IBinder binder = new LocalBinder(); + private MqttAndroidClient mqttAndroidClient; + private MastodonAPI mastodonApi; + + @Override + public void onCreate() { + super.onCreate(); + + // Create the MQTT client. + String clientId = String.format(Locale.getDefault(), "%s/%s/%s", CLIENT_NAME, + System.currentTimeMillis(), UUID.randomUUID().toString()); + String serverUri = getString(R.string.tusky_api_url); + mqttAndroidClient = new MqttAndroidClient(this, serverUri, clientId); + mqttAndroidClient.setCallback(new MqttCallbackExtended() { + @Override + public void connectComplete(boolean reconnect, String serverURI) { + if (reconnect) { + subscribeToTopic(); + } + } + + @Override + public void connectionLost(Throwable cause) { + onConnectionLost(); + } + + @Override + public void messageArrived(String topic, MqttMessage message) throws Exception { + onMessageReceived(new String(message.getPayload())); + } + + @Override + public void deliveryComplete(IMqttDeliveryToken token) { + // This client is read-only, so this is unused. + } + }); + + // Open the MQTT connection. + MqttConnectOptions options = new MqttConnectOptions(); + options.setAutomaticReconnect(true); + options.setCleanSession(false); + try { + mqttAndroidClient.connect(options, null, new IMqttActionListener() { + @Override + public void onSuccess(IMqttToken asyncActionToken) { + DisconnectedBufferOptions options = new DisconnectedBufferOptions(); + options.setBufferEnabled(true); + options.setBufferSize(100); + options.setPersistBuffer(false); + options.setDeleteOldestMessages(false); + mqttAndroidClient.setBufferOpts(options); + onConnectionSuccess(); + subscribeToTopic(); + } + + @Override + public void onFailure(IMqttToken asyncActionToken, Throwable exception) { + onConnectionFailure(); + } + }); + } catch (MqttException e) { + Log.e(TAG, "An exception occurred while connecting. " + e.getMessage()); + onConnectionFailure(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + disconnect(); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return binder; + } + + /** Subscribe to the push notification topic. */ + public void subscribeToTopic() { + try { + mqttAndroidClient.subscribe(TOPIC, 0, null, new IMqttActionListener() { + @Override + public void onSuccess(IMqttToken asyncActionToken) { + onConnectionSuccess(); + } + + @Override + public void onFailure(IMqttToken asyncActionToken, Throwable exception) { + onConnectionFailure(); + } + }); + } catch (MqttException e) { + Log.e(TAG, "An exception occurred while subscribing." + e.getMessage()); + onConnectionFailure(); + } + } + + /** Unsubscribe from the push notification topic. */ + public void unsubscribeToTopic() { + try { + mqttAndroidClient.unsubscribe(TOPIC); + } catch (MqttException e) { + Log.e(TAG, "An exception occurred while unsubscribing." + e.getMessage()); + onConnectionFailure(); + } + } + + private void onConnectionSuccess() { + + } + + private void onConnectionFailure() { + + } + + private void onConnectionLost() { + + } + + private void onMessageReceived(String message) { + String notificationId = message; // TODO: finalize the form the messages will be received + + Log.d(TAG, "Notification received: " + notificationId); + + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences( + getApplicationContext()); + boolean enabled = preferences.getBoolean("notificationsEnabled", true); + if (!enabled) { + return; + } + + createMastodonAPI(); + + mastodonApi.notification(notificationId).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + NotificationMaker.make(PushNotificationService.this, NOTIFY_ID, + response.body()); + } + } + + @Override + public void onFailure(Call call, Throwable t) {} + }); + } + + /** Disconnect from the MQTT broker. */ + public void disconnect() { + try { + mqttAndroidClient.disconnect(); + } catch (MqttException ex) { + Log.e(TAG, "An exception occurred while disconnecting."); + onDisconnectFailed(); + } + } + + private void onDisconnectFailed() { + + } + + 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); + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java new file mode 100644 index 000000000..ba8f34500 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java @@ -0,0 +1,256 @@ +package com.keylesspalace.tusky.util; + +import android.content.Context; +import android.content.SharedPreferences; +import android.support.annotation.NonNull; +import android.text.Spanned; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.keylesspalace.tusky.R; +import com.keylesspalace.tusky.entity.Notification; +import com.keylesspalace.tusky.json.SpannedTypeAdapter; +import com.keylesspalace.tusky.json.StringWithEmoji; +import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter; +import com.keylesspalace.tusky.network.MastodonAPI; + +import org.eclipse.paho.android.service.MqttAndroidClient; +import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions; +import org.eclipse.paho.client.mqttv3.IMqttActionListener; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.IMqttToken; +import org.eclipse.paho.client.mqttv3.MqttCallbackExtended; +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; + +import java.io.IOException; +import java.util.ArrayDeque; + +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 PushNotificationClient { + private static final String TAG = "PushNotificationClient"; + private static final String TOPIC = "tusky/notification"; + private static final int NOTIFY_ID = 666; + + private enum QueuedAction { + SUBSCRIBE, + UNSUBSCRIBE, + DISCONNECT, + } + + private MqttAndroidClient mqttAndroidClient; + private MastodonAPI mastodonApi; + private boolean connected; + private ArrayDeque queuedActions; + + public PushNotificationClient(final @NonNull Context context, @NonNull String serverUri) { + queuedActions = new ArrayDeque<>(); + + // Create the MQTT client. + String clientId = MqttClient.generateClientId(); + mqttAndroidClient = new MqttAndroidClient(context, serverUri, clientId); + mqttAndroidClient.setCallback(new MqttCallbackExtended() { + @Override + public void connectComplete(boolean reconnect, String serverURI) { + if (reconnect) { + flushQueuedActions(); + } + } + + @Override + public void connectionLost(Throwable cause) { + onConnectionLost(); + } + + @Override + public void messageArrived(String topic, MqttMessage message) throws Exception { + onMessageReceived(context, new String(message.getPayload())); + } + + @Override + public void deliveryComplete(IMqttDeliveryToken token) { + // This client is read-only, so this is unused. + } + }); + + // Open the MQTT connection. + MqttConnectOptions options = new MqttConnectOptions(); + options.setAutomaticReconnect(true); + options.setCleanSession(false); + try { + /* TLS connection stuffs + InputStream input = context.getResources().openRawResource(R.raw.keystore_tusky_api); + String password = context.getString(R.string.tusky_api_keystore_password); + options.setSocketFactory(mqttAndroidClient.getSSLSocketFactory(input, password)); + */ + mqttAndroidClient.connect(options).setActionCallback(new IMqttActionListener() { + @Override + public void onSuccess(IMqttToken asyncActionToken) { + DisconnectedBufferOptions options = new DisconnectedBufferOptions(); + options.setBufferEnabled(true); + options.setBufferSize(100); + options.setPersistBuffer(false); + options.setDeleteOldestMessages(false); + mqttAndroidClient.setBufferOpts(options); + onConnectionSuccess(); + connected = true; + flushQueuedActions(); + } + + @Override + public void onFailure(IMqttToken asyncActionToken, Throwable exception) { + Log.e(TAG, "An exception occurred while connecting. " + exception.getMessage()); + onConnectionFailure(); + } + }); + } catch (MqttException e) { + Log.e(TAG, "An exception occurred while connecting. " + e.getMessage()); + onConnectionFailure(); + } + } + + private void flushQueuedActions() { + for (QueuedAction action : queuedActions) { + switch (action) { + case SUBSCRIBE: subscribeToTopic(); break; + case UNSUBSCRIBE: unsubscribeToTopic(); break; + case DISCONNECT: disconnect(); break; + } + } + } + + /** Disconnect from the MQTT broker. */ + public void disconnect() { + if (!connected) { + queuedActions.add(QueuedAction.DISCONNECT); + return; + } + try { + mqttAndroidClient.disconnect(); + } catch (MqttException ex) { + Log.e(TAG, "An exception occurred while disconnecting."); + onDisconnectFailed(); + } + } + + private void onDisconnectFailed() { + Log.v(TAG, "Failed while disconnecting from the broker."); + } + + /** Subscribe to the push notification topic. */ + public void subscribeToTopic() { + if (!connected) { + queuedActions.add(QueuedAction.SUBSCRIBE); + return; + } + try { + mqttAndroidClient.subscribe(TOPIC, 0, null, new IMqttActionListener() { + @Override + public void onSuccess(IMqttToken asyncActionToken) { + onConnectionSuccess(); + } + + @Override + public void onFailure(IMqttToken asyncActionToken, Throwable exception) { + Log.e(TAG, "An exception occurred while subscribing." + exception.getMessage()); + onConnectionFailure(); + } + }); + } catch (MqttException e) { + Log.e(TAG, "An exception occurred while subscribing." + e.getMessage()); + onConnectionFailure(); + } + } + + /** Unsubscribe from the push notification topic. */ + public void unsubscribeToTopic() { + if (!connected) { + queuedActions.add(QueuedAction.UNSUBSCRIBE); + return; + } + try { + mqttAndroidClient.unsubscribe(TOPIC); + } catch (MqttException e) { + Log.e(TAG, "An exception occurred while unsubscribing." + e.getMessage()); + onConnectionFailure(); + } + } + + private void onConnectionSuccess() { + Log.v(TAG, "The connection succeeded."); + } + + private void onConnectionFailure() { + Log.v(TAG, "The connection failed."); + } + + private void onConnectionLost() { + Log.v(TAG, "The connection was lost."); + } + + private void onMessageReceived(final Context context, String message) { + String notificationId = message; // TODO: finalize the form the messages will be received + + Log.v(TAG, "Notification received: " + notificationId); + + createMastodonAPI(context); + + mastodonApi.notification(notificationId).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + NotificationMaker.make(context, NOTIFY_ID, response.body()); + } + } + + @Override + public void onFailure(Call call, Throwable t) {} + }); + } + + private void createMastodonAPI(Context context) { + SharedPreferences preferences = context.getSharedPreferences( + context.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); + } +} diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index b2a8e3b6b..73a4b542e 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -3,6 +3,7 @@ Tusky https://tusky.keylesspalace.com https://tuskynotifier.keylesspalace.com + your_password_here oauth2redirect com.keylesspalace.tusky From 388ecfcf2e7b7d19162f057b2e0514bd7b551584 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Thu, 18 May 2017 18:10:46 -0400 Subject: [PATCH 04/21] Removes the product flavor split. --- app/.gitignore | 1 + app/build.gradle | 16 +- app/google-services.json | 55 ------- app/src/fdroid/AndroidManifest.xml | 12 -- .../keylesspalace/tusky/MessagingService.java | 142 ------------------ app/src/google/AndroidManifest.xml | 18 --- .../keylesspalace/tusky/MessagingService.java | 133 ---------------- .../tusky/MyFirebaseInstanceIdService.java | 86 ----------- .../com/keylesspalace/tusky/BaseActivity.java | 32 +--- .../com/keylesspalace/tusky/MainActivity.java | 3 +- .../tusky/util/PushNotificationClient.java | 27 ++-- app/src/main/res/values/donottranslate.xml | 2 +- build.gradle | 1 - 13 files changed, 27 insertions(+), 501 deletions(-) delete mode 100644 app/google-services.json delete mode 100644 app/src/fdroid/AndroidManifest.xml delete mode 100644 app/src/fdroid/java/com/keylesspalace/tusky/MessagingService.java delete mode 100644 app/src/google/AndroidManifest.xml delete mode 100644 app/src/google/java/com/keylesspalace/tusky/MessagingService.java delete mode 100644 app/src/google/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java diff --git a/app/.gitignore b/app/.gitignore index 61e52b441..d4026ab85 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1,3 +1,4 @@ /build app-release.apk app-google-release.apk +src/main/res/raw/keystore_tusky_api.bks diff --git a/app/build.gradle b/app/build.gradle index 5ef9205d3..cb33758a5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,14 +12,6 @@ android { testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary true } - productFlavors { - google { - buildConfigField "boolean", "USES_PUSH_NOTIFICATIONS", "true" - } - fdroid { - buildConfigField "boolean", "USES_PUSH_NOTIFICATIONS", "false" - } - } buildTypes { release { minifyEnabled true @@ -59,14 +51,10 @@ dependencies { compile 'com.github.arimorty:floatingsearchview:2.0.4' compile 'com.theartofdev.edmodo:android-image-cropper:2.4.3' compile 'com.jakewharton:butterknife:8.5.1' - googleCompile 'com.google.firebase:firebase-messaging:10.2.4' - googleCompile 'com.google.firebase:firebase-crash:10.2.4' - testCompile 'junit:junit:4.12' - annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' compile 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0' compile('org.eclipse.paho:org.eclipse.paho.android.service:1.1.1') { exclude module: 'support-v4' } + testCompile 'junit:junit:4.12' + annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' } - -apply plugin: 'com.google.gms.google-services' diff --git a/app/google-services.json b/app/google-services.json deleted file mode 100644 index bfd19f883..000000000 --- a/app/google-services.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "project_info": { - "project_number": "268851337880", - "firebase_url": "https://tusky-62772.firebaseio.com", - "project_id": "tusky-62772", - "storage_bucket": "tusky-62772.appspot.com" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:268851337880:android:fc4111b1d145a00e", - "android_client_info": { - "package_name": "com.keylesspalace.tusky" - } - }, - "oauth_client": [ - { - "client_id": "268851337880-eie2ssto2d21bfihn9d1qupcrke8oebf.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "com.keylesspalace.tusky", - "certificate_hash": "18d196307d6e928e99c2e0bb9818c01c38aff2f9" - } - }, - { - "client_id": "268851337880-n19d05m282nirs1fc9kdd5n4of6je4fk.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyCbJtSjuk4I3Jy8PdUaO3TaQOXubcOUElo" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "appinvite_service": { - "status": 2, - "other_platform_oauth_client": [ - { - "client_id": "268851337880-n19d05m282nirs1fc9kdd5n4of6je4fk.apps.googleusercontent.com", - "client_type": 3 - } - ] - }, - "ads_service": { - "status": 2 - } - } - } - ], - "configuration_version": "1" -} \ No newline at end of file diff --git a/app/src/fdroid/AndroidManifest.xml b/app/src/fdroid/AndroidManifest.xml deleted file mode 100644 index b7060e6e2..000000000 --- a/app/src/fdroid/AndroidManifest.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/fdroid/java/com/keylesspalace/tusky/MessagingService.java b/app/src/fdroid/java/com/keylesspalace/tusky/MessagingService.java deleted file mode 100644 index eaa1b1b53..000000000 --- a/app/src/fdroid/java/com/keylesspalace/tusky/MessagingService.java +++ /dev/null @@ -1,142 +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 . */ - -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 com.keylesspalace.tusky.json.SpannedTypeAdapter; -import com.keylesspalace.tusky.json.StringWithEmoji; -import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter; -import com.keylesspalace.tusky.network.MastodonAPI; -import com.keylesspalace.tusky.util.NotificationMaker; -import com.keylesspalace.tusky.util.OkHttpUtils; - -import java.util.HashSet; -import java.util.List; - -import java.io.IOException; -import java.util.Set; - -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>() { - @Override - public void onResponse(Call> call, - Response> response) { - if (response.isSuccessful()) { - onNotificationsReceived(response.body()); - } - } - - @Override - public void onFailure(Call> 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 void onNotificationsReceived(List notificationList) { - SharedPreferences notificationsPreferences = getSharedPreferences( - "Notifications", Context.MODE_PRIVATE); - Set currentIds = notificationsPreferences.getStringSet( - "current_ids", new HashSet()); - for (Notification notification : notificationList) { - String id = notification.id; - if (!currentIds.contains(id)) { - currentIds.add(id); - NotificationMaker.make(this, NOTIFY_ID, notification); - } - } - notificationsPreferences.edit() - .putStringSet("current_ids", currentIds) - .apply(); - } - - public static String getInstanceToken() { - // This is only used for the "google" build flavor, so this version is just a stub method. - return null; - } -} diff --git a/app/src/google/AndroidManifest.xml b/app/src/google/AndroidManifest.xml deleted file mode 100644 index 20ecbe94c..000000000 --- a/app/src/google/AndroidManifest.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/app/src/google/java/com/keylesspalace/tusky/MessagingService.java b/app/src/google/java/com/keylesspalace/tusky/MessagingService.java deleted file mode 100644 index 4a2a55da5..000000000 --- a/app/src/google/java/com/keylesspalace/tusky/MessagingService.java +++ /dev/null @@ -1,133 +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 . - * - * 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.iid.FirebaseInstanceId; -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.keylesspalace.tusky.json.SpannedTypeAdapter; -import com.keylesspalace.tusky.json.StringWithEmoji; -import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter; -import com.keylesspalace.tusky.network.MastodonAPI; -import com.keylesspalace.tusky.util.Log; -import com.keylesspalace.tusky.util.NotificationMaker; -import com.keylesspalace.tusky.util.OkHttpUtils; - -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() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - NotificationMaker.make(MessagingService.this, NOTIFY_ID, response.body()); - } - } - - @Override - public void onFailure(Call 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); - } - - public static String getInstanceToken() { - return FirebaseInstanceId.getInstance().getToken(); - } -} diff --git a/app/src/google/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java b/app/src/google/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java deleted file mode 100644 index 14d640d54..000000000 --- a/app/src/google/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java +++ /dev/null @@ -1,86 +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 . - * - * 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 com.google.firebase.iid.FirebaseInstanceId; -import com.google.firebase.iid.FirebaseInstanceIdService; -import com.keylesspalace.tusky.network.TuskyAPI; -import com.keylesspalace.tusky.util.Log; -import com.keylesspalace.tusky.util.OkHttpUtils; - -import okhttp3.ResponseBody; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; -import retrofit2.Retrofit; - -public class MyFirebaseInstanceIdService extends FirebaseInstanceIdService { - private static final String TAG = "com.keylesspalace.tusky.MyFirebaseInstanceIdService"; - - private TuskyAPI tuskyAPI; - - protected void createTuskyAPI() { - Retrofit retrofit = new Retrofit.Builder() - .baseUrl(getString(R.string.tusky_api_url)) - .client(OkHttpUtils.getCompatibleClient()) - .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() { - @Override - public void onResponse(Call call, Response response) { - Log.d(TAG, response.message()); - } - - @Override - public void onFailure(Call call, Throwable t) { - Log.d(TAG, t.getMessage()); - } - }); - tuskyAPI.register("https://" + domain, accessToken, refreshedToken).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - Log.d(TAG, response.message()); - } - - @Override - public void onFailure(Call call, Throwable t) { - Log.d(TAG, t.getMessage()); - } - }); - } - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index 4ba4c0b84..e88cd1cb2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -15,8 +15,6 @@ package com.keylesspalace.tusky; -import android.app.AlarmManager; -import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -24,7 +22,6 @@ import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.os.Bundle; -import android.os.SystemClock; import android.preference.PreferenceManager; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; @@ -55,7 +52,6 @@ public class BaseActivity extends AppCompatActivity { public MastodonAPI mastodonAPI; protected PushNotificationClient pushNotificationClient; protected Dispatcher mastodonApiDispatcher; - protected PendingIntent serviceAlarmIntent; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -156,10 +152,8 @@ public class BaseActivity extends AppCompatActivity { } protected void createTuskyAPI() { - if (BuildConfig.USES_PUSH_NOTIFICATIONS) { - // TODO: Remove this test broker address. - pushNotificationClient = new PushNotificationClient(this, "tcp://104.236.116.199:1883"); - } + pushNotificationClient = new PushNotificationClient(this, + getString(R.string.tusky_api_url)); } protected void redirectIfNotLoggedIn() { @@ -193,28 +187,10 @@ public class BaseActivity extends AppCompatActivity { } protected void enablePushNotifications() { - if (BuildConfig.USES_PUSH_NOTIFICATIONS) { - pushNotificationClient.subscribeToTopic(); - } else { - // Start up the MessagingService on a repeating interval for "pull" notifications. - long checkInterval = 60 * 1000 * 5; - 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); - } + pushNotificationClient.subscribeToTopic(); } protected void disablePushNotifications() { - if (BuildConfig.USES_PUSH_NOTIFICATIONS) { - pushNotificationClient.unsubscribeToTopic(); - } else if (serviceAlarmIntent != null) { - // Cancel the repeating call for "pull" notifications. - AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - alarmManager.cancel(serviceAlarmIntent); - } + pushNotificationClient.unsubscribeToTopic(); } } diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java index 3d39b81ae..4c4c2db5e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java @@ -215,8 +215,7 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove .putString("current", "[]") .apply(); - ((NotificationManager) (getSystemService(NOTIFICATION_SERVICE))) - .cancel(MessagingService.NOTIFY_ID); + pushNotificationClient.clearNotifications(); /* After editing a profile, the profile header in the navigation drawer needs to be * refreshed */ diff --git a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java index ba8f34500..6c944f753 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java @@ -26,6 +26,7 @@ import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import java.io.IOException; +import java.io.InputStream; import java.util.ArrayDeque; import okhttp3.Interceptor; @@ -88,20 +89,24 @@ public class PushNotificationClient { options.setAutomaticReconnect(true); options.setCleanSession(false); try { - /* TLS connection stuffs - InputStream input = context.getResources().openRawResource(R.raw.keystore_tusky_api); + /* String password = context.getString(R.string.tusky_api_keystore_password); - options.setSocketFactory(mqttAndroidClient.getSSLSocketFactory(input, password)); + InputStream keystore = context.getResources().openRawResource(R.raw.keystore_tusky_api); + try { + options.setSocketFactory(mqttAndroidClient.getSSLSocketFactory(keystore, password)); + } finally { + IOUtils.closeQuietly(keystore); + } */ mqttAndroidClient.connect(options).setActionCallback(new IMqttActionListener() { @Override public void onSuccess(IMqttToken asyncActionToken) { - DisconnectedBufferOptions options = new DisconnectedBufferOptions(); - options.setBufferEnabled(true); - options.setBufferSize(100); - options.setPersistBuffer(false); - options.setDeleteOldestMessages(false); - mqttAndroidClient.setBufferOpts(options); + DisconnectedBufferOptions bufferOptions = new DisconnectedBufferOptions(); + bufferOptions.setBufferEnabled(true); + bufferOptions.setBufferSize(100); + bufferOptions.setPersistBuffer(false); + bufferOptions.setDeleteOldestMessages(false); + mqttAndroidClient.setBufferOpts(bufferOptions); onConnectionSuccess(); connected = true; flushQueuedActions(); @@ -253,4 +258,8 @@ public class PushNotificationClient { mastodonApi = retrofit.create(MastodonAPI.class); } + + public void clearNotifications() { + // TODO: make it happen + } } diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 73a4b542e..212d54db0 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -2,7 +2,7 @@ Tusky https://tusky.keylesspalace.com - https://tuskynotifier.keylesspalace.com + tcp://tuskyapi.keylesspalace.com your_password_here oauth2redirect diff --git a/build.gradle b/build.gradle index 80e6fd508..bfed59203 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,6 @@ buildscript { // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files - classpath 'com.google.gms:google-services:3.0.0' } } From 73a51447411ddd8bb022aacd23895d1f01362fbb Mon Sep 17 00:00:00 2001 From: Vavassor Date: Fri, 19 May 2017 20:00:57 -0400 Subject: [PATCH 05/21] Adds notification clearing and makes the client a little more stable. --- app/src/main/AndroidManifest.xml | 12 +++---- .../com/keylesspalace/tusky/BaseActivity.java | 2 +- .../com/keylesspalace/tusky/MainActivity.java | 3 +- .../tusky/util/PushNotificationClient.java | 31 ++++++++++++------- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1fd924b55..cbe15faaf 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,18 +6,18 @@ - - - - + + + + + android:theme="@style/AppTheme"> queuedActions; + private boolean subscribed; - public PushNotificationClient(final @NonNull Context context, @NonNull String serverUri) { + public PushNotificationClient(final @NonNull Context applicationContext, + @NonNull String serverUri) { queuedActions = new ArrayDeque<>(); // Create the MQTT client. String clientId = MqttClient.generateClientId(); - mqttAndroidClient = new MqttAndroidClient(context, serverUri, clientId); + mqttAndroidClient = new MqttAndroidClient(applicationContext, serverUri, clientId); mqttAndroidClient.setCallback(new MqttCallbackExtended() { @Override public void connectComplete(boolean reconnect, String serverURI) { if (reconnect) { flushQueuedActions(); + if (subscribed) { + subscribeToTopic(); + } } } @@ -75,7 +82,7 @@ public class PushNotificationClient { @Override public void messageArrived(String topic, MqttMessage message) throws Exception { - onMessageReceived(context, new String(message.getPayload())); + onMessageReceived(applicationContext, new String(message.getPayload())); } @Override @@ -108,7 +115,6 @@ public class PushNotificationClient { bufferOptions.setDeleteOldestMessages(false); mqttAndroidClient.setBufferOpts(bufferOptions); onConnectionSuccess(); - connected = true; flushQueuedActions(); } @@ -125,7 +131,8 @@ public class PushNotificationClient { } private void flushQueuedActions() { - for (QueuedAction action : queuedActions) { + while (!queuedActions.isEmpty()) { + QueuedAction action = queuedActions.pop(); switch (action) { case SUBSCRIBE: subscribeToTopic(); break; case UNSUBSCRIBE: unsubscribeToTopic(); break; @@ -136,7 +143,7 @@ public class PushNotificationClient { /** Disconnect from the MQTT broker. */ public void disconnect() { - if (!connected) { + if (!mqttAndroidClient.isConnected()) { queuedActions.add(QueuedAction.DISCONNECT); return; } @@ -154,7 +161,7 @@ public class PushNotificationClient { /** Subscribe to the push notification topic. */ public void subscribeToTopic() { - if (!connected) { + if (!mqttAndroidClient.isConnected()) { queuedActions.add(QueuedAction.SUBSCRIBE); return; } @@ -162,6 +169,7 @@ public class PushNotificationClient { mqttAndroidClient.subscribe(TOPIC, 0, null, new IMqttActionListener() { @Override public void onSuccess(IMqttToken asyncActionToken) { + subscribed = true; onConnectionSuccess(); } @@ -179,12 +187,13 @@ public class PushNotificationClient { /** Unsubscribe from the push notification topic. */ public void unsubscribeToTopic() { - if (!connected) { + if (!mqttAndroidClient.isConnected()) { queuedActions.add(QueuedAction.UNSUBSCRIBE); return; } try { mqttAndroidClient.unsubscribe(TOPIC); + subscribed = false; } catch (MqttException e) { Log.e(TAG, "An exception occurred while unsubscribing." + e.getMessage()); onConnectionFailure(); @@ -259,7 +268,7 @@ public class PushNotificationClient { mastodonApi = retrofit.create(MastodonAPI.class); } - public void clearNotifications() { - // TODO: make it happen + public void clearNotifications(Context context) { + ((NotificationManager) (context.getSystemService(NOTIFICATION_SERVICE))).cancel(NOTIFY_ID); } } From e282f13fdcb62ed17967ae2ea0cce20d7a04ade8 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Fri, 19 May 2017 21:28:12 -0400 Subject: [PATCH 06/21] Setup client-side for integration with the wryk/tusky-api prototype. --- .../com/keylesspalace/tusky/BaseActivity.java | 76 +++++++++++++++++-- .../network/{TuskyAPI.java => TuskyApi.java} | 7 +- .../tusky/util/PushNotificationClient.java | 61 ++++++++++----- app/src/main/res/values/donottranslate.xml | 2 +- 4 files changed, 115 insertions(+), 31 deletions(-) rename app/src/main/java/com/keylesspalace/tusky/network/{TuskyAPI.java => TuskyApi.java} (73%) diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index 0bb76039f..d01eb28f0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -35,6 +35,8 @@ import com.keylesspalace.tusky.json.SpannedTypeAdapter; import com.keylesspalace.tusky.json.StringWithEmoji; import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter; import com.keylesspalace.tusky.network.MastodonAPI; +import com.keylesspalace.tusky.network.TuskyApi; +import com.keylesspalace.tusky.util.Log; import com.keylesspalace.tusky.util.OkHttpUtils; import com.keylesspalace.tusky.util.PushNotificationClient; @@ -45,11 +47,17 @@ import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.Callback; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class BaseActivity extends AppCompatActivity { + private static final String TAG = "BaseActivity"; // logging tag + public MastodonAPI mastodonAPI; + public TuskyApi tuskyApi; protected PushNotificationClient pushNotificationClient; protected Dispatcher mastodonApiDispatcher; @@ -59,7 +67,8 @@ public class BaseActivity extends AppCompatActivity { redirectIfNotLoggedIn(); createMastodonAPI(); - createTuskyAPI(); + createTuskyApi(); + createPushNotificationClient(); /* 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 @@ -151,9 +160,19 @@ public class BaseActivity extends AppCompatActivity { mastodonAPI = retrofit.create(MastodonAPI.class); } - protected void createTuskyAPI() { + protected void createTuskyApi() { + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("https://" + getString(R.string.tusky_api_domain)) + .client(OkHttpUtils.getCompatibleClient()) + .build(); + + tuskyApi = retrofit.create(TuskyApi.class); + } + + protected void createPushNotificationClient() { + // TODO: Switch to ssl:// when TLS support is added. pushNotificationClient = new PushNotificationClient(getApplicationContext(), - getString(R.string.tusky_api_url)); + "tcp://" + getString(R.string.tusky_api_domain)); } protected void redirectIfNotLoggedIn() { @@ -187,10 +206,57 @@ public class BaseActivity extends AppCompatActivity { } protected void enablePushNotifications() { - pushNotificationClient.subscribeToTopic(); + Callback callback = new Callback() { + @Override + public void onResponse(Call call, + retrofit2.Response response) { + if (response.isSuccessful()) { + pushNotificationClient.subscribeToTopic(getPushNotificationTopic()); + } else { + onEnablePushNotificationsFailure(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + onEnablePushNotificationsFailure(); + } + }; + tuskyApi.register(getBaseUrl(), getAccessToken(), pushNotificationClient.getDeviceToken()) + .enqueue(callback); + } + + private void onEnablePushNotificationsFailure() { + Log.e(TAG, "Enabling push notifications failed."); } protected void disablePushNotifications() { - pushNotificationClient.unsubscribeToTopic(); + Callback callback = new Callback() { + @Override + public void onResponse(Call call, + retrofit2.Response response) { + if (response.isSuccessful()) { + pushNotificationClient.unsubscribeToTopic(getPushNotificationTopic()); + } else { + onDisablePushNotificationsFailure(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + onDisablePushNotificationsFailure(); + } + }; + tuskyApi.unregister(getBaseUrl(), getAccessToken(), pushNotificationClient.getDeviceToken()) + .enqueue(callback); + } + + private void onDisablePushNotificationsFailure() { + Log.e(TAG, "Disabling push notifications failed."); + } + + private String getPushNotificationTopic() { + return String.format("%s/%s/%s", getBaseUrl(), getAccessToken(), + pushNotificationClient.getDeviceToken()); } } diff --git a/app/src/main/java/com/keylesspalace/tusky/network/TuskyAPI.java b/app/src/main/java/com/keylesspalace/tusky/network/TuskyApi.java similarity index 73% rename from app/src/main/java/com/keylesspalace/tusky/network/TuskyAPI.java rename to app/src/main/java/com/keylesspalace/tusky/network/TuskyApi.java index 700de83c8..393decb90 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/TuskyAPI.java +++ b/app/src/main/java/com/keylesspalace/tusky/network/TuskyApi.java @@ -17,15 +17,14 @@ package com.keylesspalace.tusky.network; import okhttp3.ResponseBody; import retrofit2.Call; -import retrofit2.http.Field; import retrofit2.http.FormUrlEncoded; import retrofit2.http.POST; -public interface TuskyAPI { +public interface TuskyApi { @FormUrlEncoded @POST("/register") - Call register(@Field("instance_url") String instanceUrl, @Field("access_token") String accessToken, @Field("device_token") String deviceToken); + Call register(String instanceUrl, String accessToken, String deviceToken); @FormUrlEncoded @POST("/unregister") - Call unregister(@Field("instance_url") String instanceUrl, @Field("access_token") String accessToken); + Call unregister(String instanceUrl, String accessToken, String deviceToken); } diff --git a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java index f75e318e3..40150bb2b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java @@ -29,6 +29,7 @@ import org.eclipse.paho.client.mqttv3.MqttMessage; import java.io.IOException; import java.io.InputStream; import java.util.ArrayDeque; +import java.util.ArrayList; import okhttp3.Interceptor; import okhttp3.OkHttpClient; @@ -43,23 +44,37 @@ import static android.content.Context.NOTIFICATION_SERVICE; public class PushNotificationClient { private static final String TAG = "PushNotificationClient"; - private static final String TOPIC = "tusky/notification"; private static final int NOTIFY_ID = 666; - private enum QueuedAction { - SUBSCRIBE, - UNSUBSCRIBE, - DISCONNECT, + private static class QueuedAction { + enum Type { + SUBSCRIBE, + UNSUBSCRIBE, + DISCONNECT, + } + + Type type; + String topic; + + QueuedAction(Type type) { + this.type = type; + } + + QueuedAction(Type type, String topic) { + this.type = type; + this.topic = topic; + } } private MqttAndroidClient mqttAndroidClient; private MastodonAPI mastodonApi; private ArrayDeque queuedActions; - private boolean subscribed; + private ArrayList subscribedTopics; public PushNotificationClient(final @NonNull Context applicationContext, @NonNull String serverUri) { queuedActions = new ArrayDeque<>(); + subscribedTopics = new ArrayList<>(); // Create the MQTT client. String clientId = MqttClient.generateClientId(); @@ -69,8 +84,8 @@ public class PushNotificationClient { public void connectComplete(boolean reconnect, String serverURI) { if (reconnect) { flushQueuedActions(); - if (subscribed) { - subscribeToTopic(); + for (String topic : subscribedTopics) { + subscribeToTopic(topic); } } } @@ -133,10 +148,10 @@ public class PushNotificationClient { private void flushQueuedActions() { while (!queuedActions.isEmpty()) { QueuedAction action = queuedActions.pop(); - switch (action) { - case SUBSCRIBE: subscribeToTopic(); break; - case UNSUBSCRIBE: unsubscribeToTopic(); break; - case DISCONNECT: disconnect(); break; + switch (action.type) { + case SUBSCRIBE: subscribeToTopic(action.topic); break; + case UNSUBSCRIBE: unsubscribeToTopic(action.topic); break; + case DISCONNECT: disconnect(); break; } } } @@ -144,7 +159,7 @@ public class PushNotificationClient { /** Disconnect from the MQTT broker. */ public void disconnect() { if (!mqttAndroidClient.isConnected()) { - queuedActions.add(QueuedAction.DISCONNECT); + queuedActions.add(new QueuedAction(QueuedAction.Type.DISCONNECT)); return; } try { @@ -160,16 +175,16 @@ public class PushNotificationClient { } /** Subscribe to the push notification topic. */ - public void subscribeToTopic() { + public void subscribeToTopic(final String topic) { if (!mqttAndroidClient.isConnected()) { - queuedActions.add(QueuedAction.SUBSCRIBE); + queuedActions.add(new QueuedAction(QueuedAction.Type.SUBSCRIBE, topic)); return; } try { - mqttAndroidClient.subscribe(TOPIC, 0, null, new IMqttActionListener() { + mqttAndroidClient.subscribe(topic, 0, null, new IMqttActionListener() { @Override public void onSuccess(IMqttToken asyncActionToken) { - subscribed = true; + subscribedTopics.add(topic); onConnectionSuccess(); } @@ -186,14 +201,14 @@ public class PushNotificationClient { } /** Unsubscribe from the push notification topic. */ - public void unsubscribeToTopic() { + public void unsubscribeToTopic(String topic) { if (!mqttAndroidClient.isConnected()) { - queuedActions.add(QueuedAction.UNSUBSCRIBE); + queuedActions.add(new QueuedAction(QueuedAction.Type.UNSUBSCRIBE, topic)); return; } try { - mqttAndroidClient.unsubscribe(TOPIC); - subscribed = false; + mqttAndroidClient.unsubscribe(topic); + subscribedTopics.remove(topic); } catch (MqttException e) { Log.e(TAG, "An exception occurred while unsubscribing." + e.getMessage()); onConnectionFailure(); @@ -271,4 +286,8 @@ public class PushNotificationClient { public void clearNotifications(Context context) { ((NotificationManager) (context.getSystemService(NOTIFICATION_SERVICE))).cancel(NOTIFY_ID); } + + public String getDeviceToken() { + return mqttAndroidClient.getClientId(); + } } diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 212d54db0..e69825343 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -2,7 +2,7 @@ Tusky https://tusky.keylesspalace.com - tcp://tuskyapi.keylesspalace.com + tuskyapi.keylesspalace.com your_password_here oauth2redirect From c90c909ca6b02958b361fcbacf74bb43b71ce4d0 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Sat, 20 May 2017 02:39:29 -0400 Subject: [PATCH 07/21] Integrates with wryk/tusky-api, but only partially working. Registers to the web-service fine but loses connection when subscribing with the broker. --- .../com/keylesspalace/tusky/BaseActivity.java | 23 +- .../keylesspalace/tusky/entity/Session.java | 28 ++ .../keylesspalace/tusky/network/TuskyApi.java | 10 +- .../service/PushNotificationService.java | 257 ------------------ .../tusky/util/PushNotificationClient.java | 48 ++-- 5 files changed, 75 insertions(+), 291 deletions(-) create mode 100644 app/src/main/java/com/keylesspalace/tusky/entity/Session.java delete mode 100644 app/src/main/java/com/keylesspalace/tusky/service/PushNotificationService.java diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index d01eb28f0..da1656fcb 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -31,6 +31,7 @@ import android.view.Menu; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.keylesspalace.tusky.entity.Session; import com.keylesspalace.tusky.json.SpannedTypeAdapter; import com.keylesspalace.tusky.json.StringWithEmoji; import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter; @@ -162,8 +163,9 @@ public class BaseActivity extends AppCompatActivity { protected void createTuskyApi() { Retrofit retrofit = new Retrofit.Builder() - .baseUrl("https://" + getString(R.string.tusky_api_domain)) + .baseUrl("http://" + getString(R.string.tusky_api_domain) + ":8080") .client(OkHttpUtils.getCompatibleClient()) + .addConverterFactory(GsonConverterFactory.create()) .build(); tuskyApi = retrofit.create(TuskyApi.class); @@ -172,7 +174,7 @@ public class BaseActivity extends AppCompatActivity { protected void createPushNotificationClient() { // TODO: Switch to ssl:// when TLS support is added. pushNotificationClient = new PushNotificationClient(getApplicationContext(), - "tcp://" + getString(R.string.tusky_api_domain)); + "tcp://" + getString(R.string.tusky_api_domain) + ":8000"); } protected void redirectIfNotLoggedIn() { @@ -212,6 +214,7 @@ public class BaseActivity extends AppCompatActivity { retrofit2.Response response) { if (response.isSuccessful()) { pushNotificationClient.subscribeToTopic(getPushNotificationTopic()); + pushNotificationClient.connect(); } else { onEnablePushNotificationsFailure(); } @@ -222,7 +225,9 @@ public class BaseActivity extends AppCompatActivity { onEnablePushNotificationsFailure(); } }; - tuskyApi.register(getBaseUrl(), getAccessToken(), pushNotificationClient.getDeviceToken()) + String deviceToken = pushNotificationClient.getDeviceToken(); + Session session = new Session(getDomain(), getAccessToken(), deviceToken); + tuskyApi.register(session) .enqueue(callback); } @@ -247,7 +252,9 @@ public class BaseActivity extends AppCompatActivity { onDisablePushNotificationsFailure(); } }; - tuskyApi.unregister(getBaseUrl(), getAccessToken(), pushNotificationClient.getDeviceToken()) + String deviceToken = pushNotificationClient.getDeviceToken(); + Session session = new Session(getDomain(), getAccessToken(), deviceToken); + tuskyApi.unregister(session) .enqueue(callback); } @@ -256,7 +263,11 @@ public class BaseActivity extends AppCompatActivity { } private String getPushNotificationTopic() { - return String.format("%s/%s/%s", getBaseUrl(), getAccessToken(), - pushNotificationClient.getDeviceToken()); + return String.format("%s/%s/#", getDomain(), getAccessToken()); + } + + private String getDomain() { + return getPrivatePreferences() + .getString("domain", null); } } diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Session.java b/app/src/main/java/com/keylesspalace/tusky/entity/Session.java new file mode 100644 index 000000000..8a3c5d0bb --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Session.java @@ -0,0 +1,28 @@ +/* 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 . */ + +package com.keylesspalace.tusky.entity; + +public class Session { + public String instanceUrl; + public String accessToken; + public String deviceToken; + + public Session(String instanceUrl, String accessToken, String deviceToken) { + this.instanceUrl = instanceUrl; + this.accessToken = accessToken; + this.deviceToken = deviceToken; + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/network/TuskyApi.java b/app/src/main/java/com/keylesspalace/tusky/network/TuskyApi.java index 393decb90..24384dcf8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/TuskyApi.java +++ b/app/src/main/java/com/keylesspalace/tusky/network/TuskyApi.java @@ -15,16 +15,16 @@ package com.keylesspalace.tusky.network; +import com.keylesspalace.tusky.entity.Session; + import okhttp3.ResponseBody; import retrofit2.Call; -import retrofit2.http.FormUrlEncoded; +import retrofit2.http.Body; import retrofit2.http.POST; public interface TuskyApi { - @FormUrlEncoded @POST("/register") - Call register(String instanceUrl, String accessToken, String deviceToken); - @FormUrlEncoded + Call register(@Body Session session); @POST("/unregister") - Call unregister(String instanceUrl, String accessToken, String deviceToken); + Call unregister(@Body Session session); } diff --git a/app/src/main/java/com/keylesspalace/tusky/service/PushNotificationService.java b/app/src/main/java/com/keylesspalace/tusky/service/PushNotificationService.java deleted file mode 100644 index 7db3e1241..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/service/PushNotificationService.java +++ /dev/null @@ -1,257 +0,0 @@ -package com.keylesspalace.tusky.service; - -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Binder; -import android.os.IBinder; -import android.preference.PreferenceManager; -import android.support.annotation.Nullable; -import android.text.Spanned; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.keylesspalace.tusky.R; -import com.keylesspalace.tusky.entity.Notification; -import com.keylesspalace.tusky.json.SpannedTypeAdapter; -import com.keylesspalace.tusky.json.StringWithEmoji; -import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter; -import com.keylesspalace.tusky.network.MastodonAPI; -import com.keylesspalace.tusky.util.Log; -import com.keylesspalace.tusky.util.NotificationMaker; -import com.keylesspalace.tusky.util.OkHttpUtils; - -import org.eclipse.paho.android.service.MqttAndroidClient; -import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions; -import org.eclipse.paho.client.mqttv3.IMqttActionListener; -import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; -import org.eclipse.paho.client.mqttv3.IMqttToken; -import org.eclipse.paho.client.mqttv3.MqttCallbackExtended; -import org.eclipse.paho.client.mqttv3.MqttConnectOptions; -import org.eclipse.paho.client.mqttv3.MqttException; -import org.eclipse.paho.client.mqttv3.MqttMessage; - -import java.io.IOException; -import java.util.Locale; -import java.util.UUID; - -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 PushNotificationService extends Service { - private class LocalBinder extends Binder { - PushNotificationService getService() { - return PushNotificationService.this; - } - } - - private static final String TAG = "PushNotificationService"; - private static final String CLIENT_NAME = "TuskyMastodonClient"; - private static final String TOPIC = "tusky/notification"; - private static final int NOTIFY_ID = 666; - - private final IBinder binder = new LocalBinder(); - private MqttAndroidClient mqttAndroidClient; - private MastodonAPI mastodonApi; - - @Override - public void onCreate() { - super.onCreate(); - - // Create the MQTT client. - String clientId = String.format(Locale.getDefault(), "%s/%s/%s", CLIENT_NAME, - System.currentTimeMillis(), UUID.randomUUID().toString()); - String serverUri = getString(R.string.tusky_api_url); - mqttAndroidClient = new MqttAndroidClient(this, serverUri, clientId); - mqttAndroidClient.setCallback(new MqttCallbackExtended() { - @Override - public void connectComplete(boolean reconnect, String serverURI) { - if (reconnect) { - subscribeToTopic(); - } - } - - @Override - public void connectionLost(Throwable cause) { - onConnectionLost(); - } - - @Override - public void messageArrived(String topic, MqttMessage message) throws Exception { - onMessageReceived(new String(message.getPayload())); - } - - @Override - public void deliveryComplete(IMqttDeliveryToken token) { - // This client is read-only, so this is unused. - } - }); - - // Open the MQTT connection. - MqttConnectOptions options = new MqttConnectOptions(); - options.setAutomaticReconnect(true); - options.setCleanSession(false); - try { - mqttAndroidClient.connect(options, null, new IMqttActionListener() { - @Override - public void onSuccess(IMqttToken asyncActionToken) { - DisconnectedBufferOptions options = new DisconnectedBufferOptions(); - options.setBufferEnabled(true); - options.setBufferSize(100); - options.setPersistBuffer(false); - options.setDeleteOldestMessages(false); - mqttAndroidClient.setBufferOpts(options); - onConnectionSuccess(); - subscribeToTopic(); - } - - @Override - public void onFailure(IMqttToken asyncActionToken, Throwable exception) { - onConnectionFailure(); - } - }); - } catch (MqttException e) { - Log.e(TAG, "An exception occurred while connecting. " + e.getMessage()); - onConnectionFailure(); - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - disconnect(); - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return binder; - } - - /** Subscribe to the push notification topic. */ - public void subscribeToTopic() { - try { - mqttAndroidClient.subscribe(TOPIC, 0, null, new IMqttActionListener() { - @Override - public void onSuccess(IMqttToken asyncActionToken) { - onConnectionSuccess(); - } - - @Override - public void onFailure(IMqttToken asyncActionToken, Throwable exception) { - onConnectionFailure(); - } - }); - } catch (MqttException e) { - Log.e(TAG, "An exception occurred while subscribing." + e.getMessage()); - onConnectionFailure(); - } - } - - /** Unsubscribe from the push notification topic. */ - public void unsubscribeToTopic() { - try { - mqttAndroidClient.unsubscribe(TOPIC); - } catch (MqttException e) { - Log.e(TAG, "An exception occurred while unsubscribing." + e.getMessage()); - onConnectionFailure(); - } - } - - private void onConnectionSuccess() { - - } - - private void onConnectionFailure() { - - } - - private void onConnectionLost() { - - } - - private void onMessageReceived(String message) { - String notificationId = message; // TODO: finalize the form the messages will be received - - Log.d(TAG, "Notification received: " + notificationId); - - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences( - getApplicationContext()); - boolean enabled = preferences.getBoolean("notificationsEnabled", true); - if (!enabled) { - return; - } - - createMastodonAPI(); - - mastodonApi.notification(notificationId).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - NotificationMaker.make(PushNotificationService.this, NOTIFY_ID, - response.body()); - } - } - - @Override - public void onFailure(Call call, Throwable t) {} - }); - } - - /** Disconnect from the MQTT broker. */ - public void disconnect() { - try { - mqttAndroidClient.disconnect(); - } catch (MqttException ex) { - Log.e(TAG, "An exception occurred while disconnecting."); - onDisconnectFailed(); - } - } - - private void onDisconnectFailed() { - - } - - 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); - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java index 40150bb2b..259704f8d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java @@ -105,8 +105,21 @@ public class PushNotificationClient { // This client is read-only, so this is unused. } }); + } - // Open the MQTT connection. + private void flushQueuedActions() { + while (!queuedActions.isEmpty()) { + QueuedAction action = queuedActions.pop(); + switch (action.type) { + case SUBSCRIBE: subscribeToTopic(action.topic); break; + case UNSUBSCRIBE: unsubscribeToTopic(action.topic); break; + case DISCONNECT: disconnect(); break; + } + } + } + + /** Connect to the MQTT broker. */ + public void connect() { MqttConnectOptions options = new MqttConnectOptions(); options.setAutomaticReconnect(true); options.setCleanSession(false); @@ -140,20 +153,21 @@ public class PushNotificationClient { } }); } catch (MqttException e) { - Log.e(TAG, "An exception occurred while connecting. " + e.getMessage()); + Log.e(TAG, "An exception occurred while connecpting. " + e.getMessage()); onConnectionFailure(); } } - private void flushQueuedActions() { - while (!queuedActions.isEmpty()) { - QueuedAction action = queuedActions.pop(); - switch (action.type) { - case SUBSCRIBE: subscribeToTopic(action.topic); break; - case UNSUBSCRIBE: unsubscribeToTopic(action.topic); break; - case DISCONNECT: disconnect(); break; - } - } + private void onConnectionSuccess() { + Log.v(TAG, "The connection succeeded."); + } + + private void onConnectionFailure() { + Log.v(TAG, "The connection failed."); + } + + private void onConnectionLost() { + Log.v(TAG, "The connection was lost."); } /** Disconnect from the MQTT broker. */ @@ -215,18 +229,6 @@ public class PushNotificationClient { } } - private void onConnectionSuccess() { - Log.v(TAG, "The connection succeeded."); - } - - private void onConnectionFailure() { - Log.v(TAG, "The connection failed."); - } - - private void onConnectionLost() { - Log.v(TAG, "The connection was lost."); - } - private void onMessageReceived(final Context context, String message) { String notificationId = message; // TODO: finalize the form the messages will be received From b396f2afc828241aacb9104c9829f88a050511c4 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Sun, 21 May 2017 22:21:23 -0400 Subject: [PATCH 08/21] First notification received successfully from the wryk/tusky-api prototype! --- .../com/keylesspalace/tusky/BaseActivity.java | 2 +- .../tusky/fragment/NotificationsFragment.java | 3 - .../tusky/util/PushNotificationClient.java | 68 ++----------------- 3 files changed, 6 insertions(+), 67 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index da1656fcb..97bbebecc 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -174,7 +174,7 @@ public class BaseActivity extends AppCompatActivity { protected void createPushNotificationClient() { // TODO: Switch to ssl:// when TLS support is added. pushNotificationClient = new PushNotificationClient(getApplicationContext(), - "tcp://" + getString(R.string.tusky_api_domain) + ":8000"); + "tcp://" + getString(R.string.tusky_api_domain) + ":1883"); } protected void redirectIfNotLoggedIn() { diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java index 234ece7fc..bef04290d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java @@ -233,9 +233,6 @@ public class NotificationsFragment extends SFragment implements } else { adapter.update(notifications); } - for (Notification notification : notifications) { - Log.d(TAG, "id: " + notification.id); - } if (notifications.size() == 0 && adapter.getItemCount() == 1) { adapter.setFooterState(NotificationsAdapter.FooterState.EMPTY); } else if (fromId != null) { diff --git a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java index 259704f8d..1d7741a34 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java @@ -2,18 +2,15 @@ package com.keylesspalace.tusky.util; import android.app.NotificationManager; import android.content.Context; -import android.content.SharedPreferences; import android.support.annotation.NonNull; import android.text.Spanned; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.entity.Notification; import com.keylesspalace.tusky.json.SpannedTypeAdapter; import com.keylesspalace.tusky.json.StringWithEmoji; import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter; -import com.keylesspalace.tusky.network.MastodonAPI; import org.eclipse.paho.android.service.MqttAndroidClient; import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions; @@ -26,20 +23,9 @@ import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; -import java.io.IOException; -import java.io.InputStream; import java.util.ArrayDeque; import java.util.ArrayList; -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; - import static android.content.Context.NOTIFICATION_SERVICE; public class PushNotificationClient { @@ -67,7 +53,6 @@ public class PushNotificationClient { } private MqttAndroidClient mqttAndroidClient; - private MastodonAPI mastodonApi; private ArrayDeque queuedActions; private ArrayList subscribedTopics; @@ -148,7 +133,8 @@ public class PushNotificationClient { @Override public void onFailure(IMqttToken asyncActionToken, Throwable exception) { - Log.e(TAG, "An exception occurred while connecting. " + exception.getMessage()); + Log.e(TAG, "An exception occurred while connecting. " + exception.getMessage() + + " " + exception.getCause()); onConnectionFailure(); } }); @@ -230,59 +216,15 @@ public class PushNotificationClient { } private void onMessageReceived(final Context context, String message) { - String notificationId = message; // TODO: finalize the form the messages will be received - - Log.v(TAG, "Notification received: " + notificationId); - - createMastodonAPI(context); - - mastodonApi.notification(notificationId).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - NotificationMaker.make(context, NOTIFY_ID, response.body()); - } - } - - @Override - public void onFailure(Call call, Throwable t) {} - }); - } - - private void createMastodonAPI(Context context) { - SharedPreferences preferences = context.getSharedPreferences( - context.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(); + Log.v(TAG, "Notification received: " + message); Gson gson = new GsonBuilder() .registerTypeAdapter(Spanned.class, new SpannedTypeAdapter()) .registerTypeAdapter(StringWithEmoji.class, new StringWithEmojiTypeAdapter()) .create(); + Notification notification = gson.fromJson(message, Notification.class); - Retrofit retrofit = new Retrofit.Builder() - .baseUrl("https://" + domain) - .client(okHttpClient) - .addConverterFactory(GsonConverterFactory.create(gson)) - .build(); - - mastodonApi = retrofit.create(MastodonAPI.class); + NotificationMaker.make(context, NOTIFY_ID, notification); } public void clearNotifications(Context context) { From 6ee6157b7f35f1ac50c7a2cd2955860bdcb1ba9c Mon Sep 17 00:00:00 2001 From: Vavassor Date: Mon, 22 May 2017 02:05:37 -0400 Subject: [PATCH 09/21] Adds TLS to the push notification client (keystore_tusky_api is omitted). --- .../com/keylesspalace/tusky/BaseActivity.java | 15 +++++++-------- .../tusky/util/PushNotificationClient.java | 6 +++--- app/src/main/res/values/donottranslate.xml | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index 97bbebecc..c6d272441 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -163,7 +163,7 @@ public class BaseActivity extends AppCompatActivity { protected void createTuskyApi() { Retrofit retrofit = new Retrofit.Builder() - .baseUrl("http://" + getString(R.string.tusky_api_domain) + ":8080") + .baseUrl("https://" + getString(R.string.tusky_api_domain) + ":8080") .client(OkHttpUtils.getCompatibleClient()) .addConverterFactory(GsonConverterFactory.create()) .build(); @@ -172,9 +172,8 @@ public class BaseActivity extends AppCompatActivity { } protected void createPushNotificationClient() { - // TODO: Switch to ssl:// when TLS support is added. pushNotificationClient = new PushNotificationClient(getApplicationContext(), - "tcp://" + getString(R.string.tusky_api_domain) + ":1883"); + "ssl://" + getString(R.string.tusky_api_domain) + ":8883"); } protected void redirectIfNotLoggedIn() { @@ -214,15 +213,15 @@ public class BaseActivity extends AppCompatActivity { retrofit2.Response response) { if (response.isSuccessful()) { pushNotificationClient.subscribeToTopic(getPushNotificationTopic()); - pushNotificationClient.connect(); + pushNotificationClient.connect(BaseActivity.this); } else { - onEnablePushNotificationsFailure(); + onEnablePushNotificationsFailure(response.message()); } } @Override public void onFailure(Call call, Throwable t) { - onEnablePushNotificationsFailure(); + onEnablePushNotificationsFailure(t.getMessage()); } }; String deviceToken = pushNotificationClient.getDeviceToken(); @@ -231,8 +230,8 @@ public class BaseActivity extends AppCompatActivity { .enqueue(callback); } - private void onEnablePushNotificationsFailure() { - Log.e(TAG, "Enabling push notifications failed."); + private void onEnablePushNotificationsFailure(String message) { + Log.e(TAG, "Enabling push notifications failed. " + message); } protected void disablePushNotifications() { diff --git a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java index 1d7741a34..f3df738fe 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java @@ -7,6 +7,7 @@ import android.text.Spanned; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.entity.Notification; import com.keylesspalace.tusky.json.SpannedTypeAdapter; import com.keylesspalace.tusky.json.StringWithEmoji; @@ -23,6 +24,7 @@ import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; +import java.io.InputStream; import java.util.ArrayDeque; import java.util.ArrayList; @@ -104,12 +106,11 @@ public class PushNotificationClient { } /** Connect to the MQTT broker. */ - public void connect() { + public void connect(Context context) { MqttConnectOptions options = new MqttConnectOptions(); options.setAutomaticReconnect(true); options.setCleanSession(false); try { - /* String password = context.getString(R.string.tusky_api_keystore_password); InputStream keystore = context.getResources().openRawResource(R.raw.keystore_tusky_api); try { @@ -117,7 +118,6 @@ public class PushNotificationClient { } finally { IOUtils.closeQuietly(keystore); } - */ mqttAndroidClient.connect(options).setActionCallback(new IMqttActionListener() { @Override public void onSuccess(IMqttToken asyncActionToken) { diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index e69825343..6d61f3f36 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -2,7 +2,7 @@ Tusky https://tusky.keylesspalace.com - tuskyapi.keylesspalace.com + apitusky.keylesspalace.com your_password_here oauth2redirect From 1a39e58d3c9b785b5909d06ddfdd5c23fd8d7f12 Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Tue, 23 May 2017 21:34:31 +0200 Subject: [PATCH 10/21] remove unnecessary Log utility class, replace Exception.printStackTrace with logging --- app/proguard-rules.pro | 1 + .../keylesspalace/tusky/AccountActivity.java | 2 +- .../com/keylesspalace/tusky/BaseActivity.java | 2 +- .../keylesspalace/tusky/ComposeActivity.java | 2 +- .../tusky/EditProfileActivity.java | 2 +- .../keylesspalace/tusky/LoginActivity.java | 4 +- .../com/keylesspalace/tusky/MainActivity.java | 2 +- .../keylesspalace/tusky/ReportActivity.java | 2 +- .../tusky/fragment/AccountListFragment.java | 2 +- .../tusky/fragment/NotificationsFragment.java | 2 +- .../tusky/fragment/TimelineFragment.java | 2 +- .../tusky/fragment/ViewThreadFragment.java | 2 +- .../tusky/util/CustomTabURLSpan.java | 1 + .../com/keylesspalace/tusky/util/Log.java | 53 ------------------- .../tusky/util/NotificationMaker.java | 8 ++- .../keylesspalace/tusky/util/OkHttpUtils.java | 1 + 16 files changed, 21 insertions(+), 67 deletions(-) delete mode 100644 app/src/main/java/com/keylesspalace/tusky/util/Log.java diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 31f778d7e..b6c799755 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -46,6 +46,7 @@ # remove all logging from production apk -assumenosideeffects class android.util.Log { + public static *** getStackTraceString(...); public static *** d(...); public static *** w(...); public static *** v(...); diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java index ae6f99aca..624206c1b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java @@ -37,6 +37,7 @@ import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPager; import android.support.v7.app.ActionBar; import android.support.v7.widget.Toolbar; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -51,7 +52,6 @@ import com.keylesspalace.tusky.interfaces.StatusRemoveListener; import com.keylesspalace.tusky.pager.AccountPagerAdapter; import com.keylesspalace.tusky.util.LinkHelper; import com.keylesspalace.tusky.util.Assert; -import com.keylesspalace.tusky.util.Log; import com.keylesspalace.tusky.util.TimelineReceiver; import com.keylesspalace.tusky.util.ThemeUtils; import com.pkmmte.view.CircularImageView; diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index 859b46496..56348aaee 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -29,6 +29,7 @@ import android.preference.PreferenceManager; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.text.Spanned; +import android.util.Log; import android.util.TypedValue; import android.view.Menu; @@ -39,7 +40,6 @@ import com.keylesspalace.tusky.json.StringWithEmoji; import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter; import com.keylesspalace.tusky.network.MastodonAPI; import com.keylesspalace.tusky.network.TuskyAPI; -import com.keylesspalace.tusky.util.Log; import com.keylesspalace.tusky.util.OkHttpUtils; import java.io.IOException; diff --git a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java index f0089fd2f..e8e0cc617 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java @@ -58,6 +58,7 @@ import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextWatcher; import android.text.style.URLSpan; +import android.util.Log; import android.view.MenuItem; import android.view.View; import android.webkit.MimeTypeMap; @@ -76,7 +77,6 @@ import com.keylesspalace.tusky.util.DownsizeImageTask; import com.keylesspalace.tusky.util.EditTextTyped; import com.keylesspalace.tusky.util.CountUpDownLatch; import com.keylesspalace.tusky.util.IOUtils; -import com.keylesspalace.tusky.util.Log; import com.keylesspalace.tusky.util.SpanUtils; import com.keylesspalace.tusky.util.ThemeUtils; diff --git a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.java b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.java index 03d9811dc..e0bc47986 100644 --- a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.java @@ -33,6 +33,7 @@ import android.support.v4.content.ContextCompat; import android.support.v7.app.ActionBar; import android.support.v7.widget.Toolbar; import android.util.Base64; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -44,7 +45,6 @@ import android.widget.ProgressBar; import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Profile; import com.keylesspalace.tusky.util.IOUtils; -import com.keylesspalace.tusky.util.Log; import com.pkmmte.view.CircularImageView; import com.squareup.picasso.Picasso; import com.theartofdev.edmodo.cropper.CropImage; diff --git a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java index 9226dc6cf..5b4473cea 100644 --- a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java @@ -29,6 +29,7 @@ import android.support.customtabs.CustomTabsIntent; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.text.method.LinkMovementMethod; +import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; @@ -39,7 +40,6 @@ import com.keylesspalace.tusky.entity.AccessToken; import com.keylesspalace.tusky.entity.AppCredentials; import com.keylesspalace.tusky.network.MastodonAPI; import com.keylesspalace.tusky.util.CustomTabsHelper; -import com.keylesspalace.tusky.util.Log; import com.keylesspalace.tusky.util.OkHttpUtils; import java.util.HashMap; @@ -201,7 +201,7 @@ public class LoginActivity extends AppCompatActivity { @Override public void onFailure(Call call, Throwable t) { editText.setError(getString(R.string.error_failed_app_registration)); - t.printStackTrace(); + Log.e(TAG, Log.getStackTraceString(t)); } }; diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java index 3d39b81ae..edcf404d3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java @@ -37,6 +37,7 @@ import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; import android.text.style.StyleSpan; +import android.util.Log; import android.view.View; import android.widget.ImageView; import android.widget.TextView; @@ -48,7 +49,6 @@ import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.fragment.SFragment; import com.keylesspalace.tusky.interfaces.StatusRemoveListener; import com.keylesspalace.tusky.pager.TimelinePagerAdapter; -import com.keylesspalace.tusky.util.Log; import com.keylesspalace.tusky.util.ThemeUtils; import com.mikepenz.google_material_typeface_library.GoogleMaterial; import com.mikepenz.materialdrawer.AccountHeader; diff --git a/app/src/main/java/com/keylesspalace/tusky/ReportActivity.java b/app/src/main/java/com/keylesspalace/tusky/ReportActivity.java index f8e4e4472..8b8198fc1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ReportActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ReportActivity.java @@ -25,6 +25,7 @@ import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -33,7 +34,6 @@ import android.widget.EditText; import com.keylesspalace.tusky.adapter.ReportAdapter; import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.util.HtmlUtils; -import com.keylesspalace.tusky.util.Log; import com.keylesspalace.tusky.util.ThemeUtils; import java.util.ArrayList; diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.java index eed0d72a1..b0039671e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.java @@ -25,6 +25,7 @@ import android.support.design.widget.TabLayout; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -43,7 +44,6 @@ import com.keylesspalace.tusky.interfaces.AccountActionListener; import com.keylesspalace.tusky.network.MastodonAPI; import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.util.EndlessOnScrollListener; -import com.keylesspalace.tusky.util.Log; import com.keylesspalace.tusky.util.ThemeUtils; import java.util.List; diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java index bef04290d..06cb1035c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java @@ -27,6 +27,7 @@ import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -40,7 +41,6 @@ import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.interfaces.StatusActionListener; import com.keylesspalace.tusky.interfaces.StatusRemoveListener; import com.keylesspalace.tusky.util.EndlessOnScrollListener; -import com.keylesspalace.tusky.util.Log; import com.keylesspalace.tusky.util.ThemeUtils; import java.util.List; diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java index 17e9b726f..249e238e7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java @@ -28,6 +28,7 @@ import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -39,7 +40,6 @@ import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.interfaces.StatusActionListener; import com.keylesspalace.tusky.interfaces.StatusRemoveListener; import com.keylesspalace.tusky.util.EndlessOnScrollListener; -import com.keylesspalace.tusky.util.Log; import com.keylesspalace.tusky.util.TimelineReceiver; import com.keylesspalace.tusky.util.ThemeUtils; diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java index d02f43596..91fced582 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java @@ -25,6 +25,7 @@ import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -39,7 +40,6 @@ import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.interfaces.StatusActionListener; import com.keylesspalace.tusky.interfaces.StatusRemoveListener; import com.keylesspalace.tusky.util.ConversationLineItemDecoration; -import com.keylesspalace.tusky.util.Log; import com.keylesspalace.tusky.util.ThemeUtils; import retrofit2.Call; diff --git a/app/src/main/java/com/keylesspalace/tusky/util/CustomTabURLSpan.java b/app/src/main/java/com/keylesspalace/tusky/util/CustomTabURLSpan.java index bef07fcf1..718fd8263 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/CustomTabURLSpan.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/CustomTabURLSpan.java @@ -9,6 +9,7 @@ import android.preference.PreferenceManager; import android.support.customtabs.CustomTabsIntent; import android.support.v4.content.ContextCompat; import android.text.style.URLSpan; +import android.util.Log; import android.view.View; import com.keylesspalace.tusky.R; diff --git a/app/src/main/java/com/keylesspalace/tusky/util/Log.java b/app/src/main/java/com/keylesspalace/tusky/util/Log.java deleted file mode 100644 index 26d3d85fe..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/util/Log.java +++ /dev/null @@ -1,53 +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 . */ - -package com.keylesspalace.tusky.util; - -import com.keylesspalace.tusky.BuildConfig; - -/**A wrapper for android.util.Log that allows for disabling logging, such as for release builds.*/ -public class Log { - private static final boolean LOGGING_ENABLED = BuildConfig.DEBUG; - - public static void i(String tag, String string) { - if (LOGGING_ENABLED) { - android.util.Log.i(tag, string); - } - } - - public static void e(String tag, String string) { - if (LOGGING_ENABLED) { - android.util.Log.e(tag, string); - } - } - - public static void d(String tag, String string) { - if (LOGGING_ENABLED) { - android.util.Log.d(tag, string); - } - } - - public static void v(String tag, String string) { - if (LOGGING_ENABLED) { - android.util.Log.v(tag, string); - } - } - - public static void w(String tag, String string) { - if (LOGGING_ENABLED) { - android.util.Log.w(tag, string); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/util/NotificationMaker.java b/app/src/main/java/com/keylesspalace/tusky/util/NotificationMaker.java index e5bc70033..e0c312800 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/NotificationMaker.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/NotificationMaker.java @@ -28,6 +28,7 @@ import android.provider.Settings; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; import android.support.v4.app.TaskStackBuilder; +import android.util.Log; import com.keylesspalace.tusky.MainActivity; import com.keylesspalace.tusky.R; @@ -39,6 +40,9 @@ import org.json.JSONArray; import org.json.JSONException; public class NotificationMaker { + + public static final String TAG = "NotificationMaker"; + public static void make(final Context context, final int notifyId, Notification body) { final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); @@ -66,7 +70,7 @@ public class NotificationMaker { alreadyContains = true; } } catch (JSONException e) { - e.printStackTrace(); + Log.d(TAG, Log.getStackTraceString(e)); } } @@ -127,7 +131,7 @@ public class NotificationMaker { builder.setContentTitle(String.format(context.getString(R.string.notification_title_summary), currentNotifications.length())) .setContentText(truncateWithEllipses(joinNames(context, currentNotifications), 40)); } catch (JSONException e) { - e.printStackTrace(); + Log.d(TAG, Log.getStackTraceString(e)); } } diff --git a/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.java index c90002d20..577af8cf7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.java @@ -17,6 +17,7 @@ package com.keylesspalace.tusky.util; import android.os.Build; import android.support.annotation.NonNull; +import android.util.Log; import com.keylesspalace.tusky.BuildConfig; From 036a3057822f59a8ad710a21d677928790288d23 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Tue, 23 May 2017 19:48:54 -0400 Subject: [PATCH 11/21] pretty much finishes the prototype --- app/src/main/java/com/keylesspalace/tusky/BaseActivity.java | 4 ++-- app/src/main/res/values/donottranslate.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index c6d272441..fa569fa8d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -163,7 +163,7 @@ public class BaseActivity extends AppCompatActivity { protected void createTuskyApi() { Retrofit retrofit = new Retrofit.Builder() - .baseUrl("https://" + getString(R.string.tusky_api_domain) + ":8080") + .baseUrl("https://" + getString(R.string.tusky_api_url)) .client(OkHttpUtils.getCompatibleClient()) .addConverterFactory(GsonConverterFactory.create()) .build(); @@ -173,7 +173,7 @@ public class BaseActivity extends AppCompatActivity { protected void createPushNotificationClient() { pushNotificationClient = new PushNotificationClient(getApplicationContext(), - "ssl://" + getString(R.string.tusky_api_domain) + ":8883"); + "ssl://" + getString(R.string.tusky_api_url) + ":8883"); } protected void redirectIfNotLoggedIn() { diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 6d61f3f36..ad2d3b37b 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -2,7 +2,7 @@ Tusky https://tusky.keylesspalace.com - apitusky.keylesspalace.com + apitusky.keylesspalace.com your_password_here oauth2redirect From 52aa32061ae72bab02e33f8e5a9c779a6dbdaebc Mon Sep 17 00:00:00 2001 From: Vavassor Date: Tue, 23 May 2017 20:13:49 -0400 Subject: [PATCH 12/21] release 1.1.4-beta.1 --- app/build.gradle | 4 ++-- app/src/main/AndroidManifest.xml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index cb33758a5..6453f7c53 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.keylesspalace.tusky" minSdkVersion 15 targetSdkVersion 25 - versionCode 16 - versionName "1.1.3" + versionCode 17 + versionName "1.1.4-beta.1" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary true } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cbe15faaf..c30a26cac 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,7 +9,6 @@ - Date: Thu, 25 May 2017 17:21:11 +0200 Subject: [PATCH 13/21] log some more exceptions --- .../main/java/com/keylesspalace/tusky/ComposeActivity.java | 3 +++ .../java/com/keylesspalace/tusky/EditProfileActivity.java | 2 ++ .../java/com/keylesspalace/tusky/util/DownsizeImageTask.java | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java index e8e0cc617..886d99990 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java @@ -677,6 +677,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag try { descriptor = getContentResolver().openAssetFileDescriptor(uri, "r"); } catch (FileNotFoundException e) { + Log.d(TAG, Log.getStackTraceString(e)); // Eat this exception, having the descriptor be null is sufficient. } if (descriptor != null) { @@ -1037,6 +1038,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag } buffer.flush(); } catch (IOException e) { + Log.d(TAG, Log.getStackTraceString(e)); return null; } return buffer.toByteArray(); @@ -1062,6 +1064,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag try { stream = getContentResolver().openInputStream(item.uri); } catch (FileNotFoundException e) { + Log.d(TAG, Log.getStackTraceString(e)); return; } diff --git a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.java b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.java index e0bc47986..88e590723 100644 --- a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.java @@ -486,12 +486,14 @@ public class EditProfileActivity extends BaseActivity { try { inputStream = contentResolver.openInputStream(uri); } catch (FileNotFoundException e) { + Log.d(TAG, Log.getStackTraceString(e)); return false; } Bitmap sourceBitmap; try { sourceBitmap = BitmapFactory.decodeStream(inputStream, null, null); } catch (OutOfMemoryError error) { + Log.d(TAG, Log.getStackTraceString(error)); return false; } finally { IOUtils.closeQuietly(inputStream); diff --git a/app/src/main/java/com/keylesspalace/tusky/util/DownsizeImageTask.java b/app/src/main/java/com/keylesspalace/tusky/util/DownsizeImageTask.java index 0cbc35a83..fe222a708 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/DownsizeImageTask.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/DownsizeImageTask.java @@ -23,6 +23,7 @@ import android.net.Uri; import android.os.AsyncTask; import android.support.annotation.Nullable; import android.support.media.ExifInterface; +import android.util.Log; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; @@ -32,6 +33,7 @@ import java.util.ArrayList; import java.util.List; public class DownsizeImageTask extends AsyncTask { + private static final String TAG = "DownsizeImageTask"; private int sizeLimit; private ContentResolver contentResolver; private Listener listener; @@ -100,6 +102,7 @@ public class DownsizeImageTask extends AsyncTask { try { inputStream = contentResolver.openInputStream(uri); } catch (FileNotFoundException e) { + Log.d(TAG, Log.getStackTraceString(e)); return ExifInterface.ORIENTATION_UNDEFINED; } if (inputStream == null) { @@ -109,6 +112,7 @@ public class DownsizeImageTask extends AsyncTask { try { exifInterface = new ExifInterface(inputStream); } catch (IOException e) { + Log.d(TAG, Log.getStackTraceString(e)); IOUtils.closeQuietly(inputStream); return ExifInterface.ORIENTATION_UNDEFINED; } From a2c019e8f1f6836462ef6a8429cd7e7effa91c8c Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Thu, 25 May 2017 19:10:49 +0200 Subject: [PATCH 14/21] fix bug in CollapsingToolbarLayout --- .../keylesspalace/tusky/AccountActivity.java | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java index ae6f99aca..361c7769d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.graphics.Color; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; @@ -136,23 +137,17 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem @AttrRes int attribute; if (collapsingToolbar.getHeight() + verticalOffset < 2 * ViewCompat.getMinimumHeight(collapsingToolbar)) { - if (getSupportActionBar() != null && loadedAccount != null) { - getSupportActionBar().setTitle(loadedAccount.getDisplayName()); + toolbar.setTitleTextColor(ThemeUtils.getColor(AccountActivity.this, android.R.attr.textColorPrimary)); - - String subtitle = String.format(getString(R.string.status_username_format), - loadedAccount.username); - getSupportActionBar().setSubtitle(subtitle); toolbar.setSubtitleTextColor(ThemeUtils.getColor(AccountActivity.this, android.R.attr.textColorSecondary)); - } + attribute = R.attr.account_toolbar_icon_tint_collapsed; } else { - if (getSupportActionBar() != null) { - getSupportActionBar().setTitle(""); - getSupportActionBar().setSubtitle(""); - } + toolbar.setTitleTextColor(Color.TRANSPARENT); + toolbar.setSubtitleTextColor(Color.TRANSPARENT); + attribute = R.attr.account_toolbar_icon_tint_uncollapsed; } if (attribute != priorAttribute) { @@ -245,6 +240,15 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem displayName.setText(account.getDisplayName()); + if (getSupportActionBar() != null) { + getSupportActionBar().setTitle(account.getDisplayName()); + + String subtitle = String.format(getString(R.string.status_username_format), + account.username); + getSupportActionBar().setSubtitle(subtitle); + + } + boolean useCustomTabs = PreferenceManager.getDefaultSharedPreferences(this) .getBoolean("customTabs", true); LinkHelper.setClickableText(note, account.note, null, useCustomTabs, new LinkListener() { From 4784fa72f67f900521dda4ddf0725348ab95b569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczak?= Date: Fri, 26 May 2017 15:04:06 +0200 Subject: [PATCH 15/21] updated Polish translation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcin Mikołajczak --- app/src/main/res/values-pl/strings.xml | 32 ++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index af74baf15..c96f719cd 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -59,6 +59,7 @@ Napisz Zaloguj z Mastodon Wyloguj + Czy chcesz wylogować się? Obserwuj Przestań obserwować Zablokuj @@ -121,10 +122,16 @@ Czym jest instancja? - Wprowadź tu domenę lub adres instancji, np. mastodon.social, icosahedron.website, social.tchncs.de, i + Łączenie… + + Domenę lub adres instancji może zostać wprowadzona tutaj, + np. mastodon.social, icosahedron.website, social.tchncs.de, i wiele więcej! - \n\nJeżeli nie posiadasz jeszcze konta, wprowadź tu nazwę instancji, na której chcesz się zarejestrowa.\n\nInstancja jest miejscem, na którym znajduje się twoje konto, lecz komunikując się z innymi serwerami, działa tak, jakby były jednym portalem. - \n\nMożesz znaleźć więcej informacji na mastodon.social. + \n\nJeżeli nie posiadasz jeszcze konta, wprowadź tu nazwę instancji, na której chcesz się zarejestrowa. + Instancja jest miejscem, na którym znajduje się twoje konto, + lecz komunikując się z innymi serwerami, działa tak, + jakby były jednym portalem. + Możesz znaleźć więcej informacji na mastodon.social. Kończenie wysyłania treści Wysyłanie… @@ -151,8 +158,12 @@ Wygląd Użyj jasnego motywu Przeglądarka - Używaj kart Chrome + Używaj niestandardowych kart Chrome Ukryj przycisk obserwacji podczas przewijania + Filtrowanie osi czasu + Karty + Pokazuj podbicia + Pokazuj odpowiedzi %s wspomniał o tobie %1$s, %2$s, %3$s i %4$d innych @@ -161,6 +172,19 @@ %d nowych powiadomień Konto zablokowane + +  O programie + Wersja aplikacji: %s + + Strona projektu:\n + https://tusky.keylesspalace.com + + + Zgłoszenia błędów i propozycje funkcji:\n + https://github.com/Vavassor/Tusky/issues + + Profil Tusky\'ego + Udostępnij zawartość postu Udostępnij link do postu From 7cf72be4b195918fd7f905605021bb548d82e9d3 Mon Sep 17 00:00:00 2001 From: FlameArche Date: Sun, 28 May 2017 02:59:29 +0900 Subject: [PATCH 16/21] Adds Japanese translations. --- app/src/main/res/values-ja/strings.xml | 30 +++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 50a048417..560940d06 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -31,6 +31,8 @@ お気に入り ミュートしたユーザー ブロックしたユーザー + フォローリクエスト + プロフィールを編集 \@%s %sさんがブーストしました @@ -58,6 +60,7 @@ 新規投稿 Mastodonでログイン ログアウト + ログアウトしますか? フォローする フォロー解除 ブロック @@ -78,6 +81,7 @@ お気に入り ミュートしたユーザー ブロックしたユーザー + フォローリクエスト スレッド メディア ブラウザで開く @@ -95,6 +99,8 @@ 保存 プロフィールを編集 取り消す + 許可 + 拒否 トゥートのURLを共有… トゥートを共有… @@ -117,6 +123,8 @@ インスタンスとは? + 接続中… + mastodon.social, mstdn.jp, pawoo.net や その他 のような、あらゆるインスタンスのアドレスやドメインを入力できます。 @@ -128,10 +136,11 @@ メディアをアップロードしています アップロード中… ダウンロード + フォローリクエストの承認待ち:返答を待っています 公開:公開タイムラインに投稿する 未収載:公開タイムラインには表示しない - 非公開:フォロワーだけに公開 + フォロワー限定:フォロワーだけに公開 ダイレクト:返信先のユーザーだけに公開 通知 @@ -151,6 +160,10 @@ ブラウザ Chrome Custom Tabsを使用する スクロール中はフォローボタンを隠す + タイムラインのフィルタリング + タブ + ブーストを表示 + 返信を表示 %sさんが返信しました %1$sさん、%2$sさん、%3$sさんと他%4$d人 @@ -159,6 +172,21 @@ %d件の新しい通知 非公開アカウント + + このアプリについて + + + + + トゥートの内容を共有 トゥートへのリンクを共有 + + フォローリクエスト中 From a8e8732979904a365aeb5bd2dad001de30baad70 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Tue, 30 May 2017 16:57:30 -0400 Subject: [PATCH 17/21] Fixes a validation error on login when instance names are entered with multiple @ symbols like @username@example.com. --- app/src/main/java/com/keylesspalace/tusky/LoginActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java index 9226dc6cf..52e653f84 100644 --- a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java @@ -137,7 +137,7 @@ public class LoginActivity extends AppCompatActivity { s = s.replaceFirst("http://", ""); s = s.replaceFirst("https://", ""); // If a username was included (e.g. username@example.com), just take what's after the '@'. - int at = s.indexOf('@'); + int at = s.lastIndexOf('@'); if (at != -1) { s = s.substring(at + 1); } From 20410024f4618d377f42433e795b3fe0593db657 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Tue, 30 May 2017 19:35:04 -0400 Subject: [PATCH 18/21] Fixes a mistake made while merging #294. --- app/src/main/java/com/keylesspalace/tusky/BaseActivity.java | 2 +- .../com/keylesspalace/tusky/util/PushNotificationClient.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index 7494c1299..45c69b33a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -37,7 +37,7 @@ import com.keylesspalace.tusky.json.SpannedTypeAdapter; import com.keylesspalace.tusky.json.StringWithEmoji; import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter; import com.keylesspalace.tusky.network.MastodonAPI; -import com.keylesspalace.tusky.network.TuskyAPI; +import com.keylesspalace.tusky.network.TuskyApi; import com.keylesspalace.tusky.util.OkHttpUtils; import com.keylesspalace.tusky.util.PushNotificationClient; diff --git a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java index f3df738fe..eb97fb1fa 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java @@ -4,6 +4,7 @@ import android.app.NotificationManager; import android.content.Context; import android.support.annotation.NonNull; import android.text.Spanned; +import android.util.Log; import com.google.gson.Gson; import com.google.gson.GsonBuilder; From 85f7b16736de73ec1abf230ddf285e06400b5246 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Wed, 31 May 2017 20:50:11 -0400 Subject: [PATCH 19/21] Removes "fillable loader" library, since it's no longer used for the splash screen. --- app/build.gradle | 1 - app/src/main/res/layout/activity_about.xml | 10 +++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6453f7c53..83c2245e6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,7 +45,6 @@ dependencies { compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0' compile 'com.pkmmte.view:circularimageview:1.1' compile 'com.github.varunest:sparkbutton:1.0.5' - compile 'com.mikhaellopez:circularfillableloaders:1.2.0' compile 'com.github.chrisbanes:PhotoView:2.0.0' compile 'com.mikepenz:google-material-typeface:3.0.1.0.original@aar' compile 'com.github.arimorty:floatingsearchview:2.0.4' diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml index c36fd02be..3a595f1d5 100644 --- a/app/src/main/res/layout/activity_about.xml +++ b/app/src/main/res/layout/activity_about.xml @@ -30,16 +30,12 @@ android:paddingTop="16dp" android:paddingBottom="16dp"> - + android:contentDescription="@null" /> Date: Fri, 2 Jun 2017 03:12:02 -0400 Subject: [PATCH 20/21] Fixes long content warnings in posts wasting space. Closes #173 --- .../keylesspalace/tusky/util/FlowLayout.java | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/util/FlowLayout.java b/app/src/main/java/com/keylesspalace/tusky/util/FlowLayout.java index cf1f9e686..9436ae187 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/FlowLayout.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/FlowLayout.java @@ -26,7 +26,6 @@ import com.keylesspalace.tusky.R; public class FlowLayout extends ViewGroup { private int paddingHorizontal; // internal padding between child views private int paddingVertical; // - private int totalHeight; public FlowLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); @@ -34,11 +33,10 @@ public class FlowLayout extends ViewGroup { public FlowLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - TypedArray a = context.getTheme().obtainStyledAttributes( - attrs, R.styleable.FlowLayout, 0, 0); + TypedArray a = context.getTheme() + .obtainStyledAttributes(attrs, R.styleable.FlowLayout, 0, 0); try { - paddingHorizontal = a.getDimensionPixelSize( - R.styleable.FlowLayout_paddingHorizontal, 0); + paddingHorizontal = a.getDimensionPixelSize(R.styleable.FlowLayout_paddingHorizontal, 0); paddingVertical = a.getDimensionPixelSize(R.styleable.FlowLayout_paddingVertical, 0); } finally { a.recycle(); @@ -59,26 +57,29 @@ public class FlowLayout extends ViewGroup { } else { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } - totalHeight = 0; + int rowHeight = 0; for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child.getVisibility() != GONE) { - child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), - childHeightMeasureSpec); + int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST); + child.measure(widthSpec, childHeightMeasureSpec); int childwidth = child.getMeasuredWidth(); - totalHeight = Math.max(totalHeight, child.getMeasuredHeight() + paddingVertical); + int childHeight = child.getMeasuredHeight(); if (x + childwidth > width) { x = getPaddingLeft(); - y += totalHeight; + y += rowHeight; + rowHeight = childHeight + paddingVertical; + } else { + rowHeight = Math.max(rowHeight, childHeight + paddingVertical); } x += childwidth + paddingHorizontal; } } if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) { - height = y + totalHeight; + height = y + rowHeight; } else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) { - if (y + totalHeight < height) { - height = y + totalHeight; + if (y + rowHeight < height) { + height = y + rowHeight; } } height += 5; // Fudge to avoid clipping bottom of last row. @@ -90,6 +91,7 @@ public class FlowLayout extends ViewGroup { final int width = r - l; int x = getPaddingLeft(); int y = getPaddingTop(); + int rowHeight = 0; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); if (child.getVisibility() != GONE) { @@ -97,11 +99,14 @@ public class FlowLayout extends ViewGroup { int childHeight = child.getMeasuredHeight(); if (x + childWidth > width) { x = getPaddingLeft(); - y += totalHeight; + y += rowHeight; + rowHeight = childHeight + paddingVertical; + } else { + rowHeight = Math.max(childHeight, rowHeight); } child.layout(x, y, x + childWidth, y + childHeight); x += childWidth + paddingHorizontal; } } } -} +} \ No newline at end of file From 9e7229bfcc2542e73ad9591e66424c3b71fe3ad3 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Fri, 2 Jun 2017 04:47:22 -0400 Subject: [PATCH 21/21] Adds a forgotten keystore. --- app/.gitignore | 2 -- app/src/main/res/raw/keystore_tusky_api.bks | Bin 0 -> 1402 bytes 2 files changed, 2 deletions(-) create mode 100644 app/src/main/res/raw/keystore_tusky_api.bks diff --git a/app/.gitignore b/app/.gitignore index d4026ab85..3f1ce47a1 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1,4 +1,2 @@ /build app-release.apk -app-google-release.apk -src/main/res/raw/keystore_tusky_api.bks diff --git a/app/src/main/res/raw/keystore_tusky_api.bks b/app/src/main/res/raw/keystore_tusky_api.bks new file mode 100644 index 0000000000000000000000000000000000000000..bc47c33175a173a076471054abd5f4c710e1776f GIT binary patch literal 1402 zcmaJ=eM}Q)9KQE@E#Dxt76uef5D=C2rHoA6B7=!KhLb9&LsNGgM|-sPN_)VnGHg-& zh`8aVF^Uyrpw$gsj1~wHhO;BOVPFGiB2%0&14Lp_5OlcfRDvekU+??;-skz{d4C@W zf}jY3i0a2}A5UbDyLCUF^l^>aHE_C+O+*mvM}YVcdYUaTYE05>1EZk~2m&_BnR}-= z6@u{BOXFbzcLqZXKLA=Vq>6_E6!qhwbxW3(dvs4l=4|Xe{MPky8}t&m%`aO~Htq$0 z2Ooi{u^!M(z{|u0LNs$ttQd-%z+LE-MzK*wJVi$uOnNpp9L{mZdBVWg;&@U4t7Z%| zYr@yZ!4QA1*mq#OTrQ7|kApDlU${sPUqTo$BhB=8G?Yn88I5{EOOTY5WHv(!2%G5= z@DZK`_#j-|9kl>}oDYjp4ma-4P*%pzA8WA+y{``pwk_N{c=6s~)KK+@^0OO!t?NEI z_k5(){lt;}K5MX6K^$#=cBWi|+G>V&j5u7|9Y?~xFNF7_i%)h|P3;!-?P%Ae%(}r2 zKB$tcl638xJ4q=;zg@4cKYjk(@wG>)7u_j-Vr*u*zoUy3x1w{2->LgMR#)WIwe|nJ z+i`VQmwW7!pAP%qot(Ne(VM<;G;iqr(4rIN*tY={<^IWzRoQLVgv)~Wv<+{oZMdR; z669$wzoxkSxA*emnjcCM<2TFqSKB8}evzCq?3r-3kMC=_o{Aki9W6COZ7ss+2KVLvBioi+yQO(^*N$78vRzhhqUyyj^rEHIKxK zdz%J26YO;<)X3Mo&n;}4_1teu)|5Fg*wB(D0z+g+@|ICni%E`kq^B6RbLRW!6W%dLpfaU>#2Uisg zSZ;Gg;BXI&eB~Jh;=q@lNxkt+uh4vEnutQP)zf@5Lroj;4>-4UE=>|Fg>NQIIHS{= z@N5bt0}FC)~WDxf}>MLoY3XGpwc>=AW6oML+D5fFQQpB&Z?=` z(xx+7nl$0LOaUh%SlnsS$W`{uY8og#W29NeV3N&TAa2dM1p>%>Y|*uI(L9Cp7kg=G za7TUS1f3*Yy?4W(=f4c=DDOdAByQb+wZPL+!}4I z`HwUw4@gSTw4nSsj=4!2j?H_VJJvdIJX5kVaQU%({7{Ea_!bZQua$wR4`(e{;#>Ol z+6yauypu;qsvM4s>ekuT2P^eYTOOe6PE>YCV*LMVvaN9}F5QOpY4U~@o0fe-+@Gz# zvgy*)yQ>VeCsdv Mhsw4d^t&+e4+K5{Bme*a literal 0 HcmV?d00001