diff --git a/README.md b/README.md
index 2ab51d893..8bc62e885 100644
--- a/README.md
+++ b/README.md
@@ -4,8 +4,6 @@
Tusky is a beautiful Android client for [Mastodon](https://github.com/tootsuite/mastodon). Mastodon is a GNU social-compatible federated social network. That means not one entity controls the whole network, rather, like e-mail, volunteers and organisations operate their own independent servers, users from which can all interact with each other seamlessly.
-It is currently available on [Google Play](https://play.google.com/store/apps/details?id=com.keylesspalace.tusky).
-
## Features
- Material Design
@@ -16,6 +14,10 @@ It is currently available on [Google Play](https://play.google.com/store/apps/de
My Mastodon account is [Vavassor@mastodon.social](https://mastodon.social/users/Vavassor).
+[](https://f-droid.org/repository/browse/?fdid=com.keylesspalace.tusky)
+[](https://play.google.com/store/apps/details?id=com.keylesspalace.tusky&utm_source=github&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1)
+[![Available at Amazon](/assets/amazon_badge.png)](https://www.amazon.com/gp/product/B06ZYXT88G/ref=mas_pm_tusky)
+
## Building
The most basic things needed are the Java Development Kit 7 or higher and the Android SDK.
diff --git a/app/.gitignore b/app/.gitignore
index 3f1ce47a1..d4026ab85 100644
--- a/app/.gitignore
+++ b/app/.gitignore
@@ -1,2 +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 a7b9f5aeb..6453f7c53 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -2,24 +2,16 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 25
- buildToolsVersion "25.0.2"
+ buildToolsVersion "25.0.3"
defaultConfig {
applicationId "com.keylesspalace.tusky"
minSdkVersion 15
targetSdkVersion 25
- versionCode 15
- versionName "1.1.2"
+ versionCode 17
+ versionName "1.1.4-beta.1"
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
@@ -37,7 +29,7 @@ dependencies {
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
- compile('com.mikepenz:materialdrawer:5.8.2@aar') {
+ compile('com.mikepenz:materialdrawer:5.9.1@aar') {
transitive = true
}
compile 'com.android.support:appcompat-v7:25.3.1'
@@ -47,22 +39,22 @@ dependencies {
compile 'com.android.support:design:25.3.1'
compile 'com.android.support:exifinterface:25.3.1'
compile 'com.squareup.retrofit2:retrofit:2.2.0'
- compile 'com.squareup.retrofit2:converter-gson:2.1.0'
+ compile 'com.squareup.retrofit2:converter-gson:2.2.0'
compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.squareup.okhttp3:okhttp:3.7.0'
compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'
compile 'com.pkmmte.view:circularimageview:1.1'
- compile 'com.github.peter9870:sparkbutton:master'
+ compile 'com.github.varunest:sparkbutton:1.0.5'
compile 'com.mikhaellopez:circularfillableloaders:1.2.0'
- compile 'com.github.chrisbanes:PhotoView:1.3.1'
+ 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'
- compile 'com.theartofdev.edmodo:android-image-cropper:2.4.0'
- compile 'com.jakewharton:butterknife:8.4.0'
- googleCompile 'com.google.firebase:firebase-messaging:10.0.1'
- googleCompile 'com.google.firebase:firebase-crash:10.0.1'
+ compile 'com.theartofdev.edmodo:android-image-cropper:2.4.3'
+ compile 'com.jakewharton:butterknife: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.4.0'
+ annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
}
-
-apply plugin: 'com.google.gms.google-services'
\ No newline at end of file
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 01d98122d..000000000
--- a/app/src/fdroid/java/com/keylesspalace/tusky/MessagingService.java
+++ /dev/null
@@ -1,132 +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 android.util.ArraySet;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-
-import com.keylesspalace.tusky.entity.Notification;
-
-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();
- }
-}
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 cf4e52733..000000000
--- a/app/src/google/java/com/keylesspalace/tusky/MessagingService.java
+++ /dev/null
@@ -1,121 +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.messaging.FirebaseMessagingService;
-import com.google.firebase.messaging.RemoteMessage;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-
-import com.keylesspalace.tusky.entity.Notification;
-
-import java.io.IOException;
-
-import okhttp3.Interceptor;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import retrofit2.Call;
-import retrofit2.Callback;
-import retrofit2.Response;
-import retrofit2.Retrofit;
-import retrofit2.converter.gson.GsonConverterFactory;
-
-public class MessagingService extends FirebaseMessagingService {
- private MastodonAPI mastodonAPI;
- private static final String TAG = "MessagingService";
- public static final int NOTIFY_ID = 666;
-
- @Override
- public void onMessageReceived(RemoteMessage remoteMessage) {
- Log.d(TAG, remoteMessage.getFrom());
- Log.d(TAG, remoteMessage.toString());
-
- String notificationId = remoteMessage.getData().get("notification_id");
-
- if (notificationId == null) {
- Log.e(TAG, "No notification ID in payload!!");
- return;
- }
-
- Log.d(TAG, notificationId);
-
- SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(
- getApplicationContext());
- boolean enabled = preferences.getBoolean("notificationsEnabled", true);
- if (!enabled) {
- return;
- }
-
- createMastodonAPI();
-
- mastodonAPI.notification(notificationId).enqueue(new Callback() {
- @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);
- }
-}
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 adb478795..000000000
--- a/app/src/google/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java
+++ /dev/null
@@ -1,83 +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 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/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 84c1b093d..c30a26cac 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,20 +6,23 @@
-
+
+
+
+ android:theme="@style/AppTheme">
-
+
-
@@ -37,7 +40,10 @@
android:scheme="@string/oauth_scheme" />
-
+
+
@@ -57,14 +63,19 @@
-
-
+
+
+
@@ -72,11 +83,12 @@
android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:theme="@style/Base.Theme.AppCompat" />
-
+
+
diff --git a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java
new file mode 100644
index 000000000..01e59066b
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java
@@ -0,0 +1,108 @@
+package com.keylesspalace.tusky;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.design.widget.Snackbar;
+import android.support.v7.app.ActionBar;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.keylesspalace.tusky.entity.Account;
+
+import java.util.List;
+
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+
+public class AboutActivity extends BaseActivity {
+ private Button appAccountButton;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_about);
+
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ ActionBar bar = getSupportActionBar();
+ if (bar != null) {
+ bar.setDisplayHomeAsUpEnabled(true);
+ bar.setDisplayShowHomeEnabled(true);
+ }
+
+ TextView versionTextView = (TextView) findViewById(R.id.versionTV);
+ String versionName = BuildConfig.VERSION_NAME;
+ String versionFormat = getString(R.string.about_application_version);
+ versionTextView.setText(String.format(versionFormat, versionName));
+
+ appAccountButton = (Button) findViewById(R.id.tusky_profile_button);
+ appAccountButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ onAccountButtonClick();
+ }
+ });
+ }
+
+ private void onAccountButtonClick() {
+ String appAccountId = getPrivatePreferences().getString("appAccountId", null);
+ if (appAccountId != null) {
+ viewAccount(appAccountId);
+ } else {
+ searchForAccountThenViewIt();
+ }
+ }
+
+ private void viewAccount(String id) {
+ Intent intent = new Intent(this, AccountActivity.class);
+ intent.putExtra("id", id);
+ startActivity(intent);
+ }
+
+ private void searchForAccountThenViewIt() {
+ Callback> callback = new Callback>() {
+ @Override
+ public void onResponse(Call> call, Response> response) {
+ if (response.isSuccessful()) {
+ List accountList = response.body();
+ if (!accountList.isEmpty()) {
+ String id = accountList.get(0).id;
+ getPrivatePreferences().edit()
+ .putString("appAccountId", id)
+ .apply();
+ viewAccount(id);
+ } else {
+ onSearchFailed();
+ }
+ } else {
+ onSearchFailed();
+ }
+ }
+
+ @Override
+ public void onFailure(Call> call, Throwable t) {
+ onSearchFailed();
+ }
+ };
+ mastodonAPI.searchAccounts("Tusky@mastodon.social", true, null).enqueue(callback);
+ }
+
+ private void onSearchFailed() {
+ Snackbar.make(appAccountButton, R.string.error_generic, Snackbar.LENGTH_LONG).show();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home: {
+ onBackPressed();
+ return true;
+ }
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java
index ed12bd2cb..ae6f99aca 100644
--- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java
@@ -23,6 +23,7 @@ import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
+import android.preference.PreferenceManager;
import android.support.annotation.AttrRes;
import android.support.annotation.Nullable;
import android.support.design.widget.AppBarLayout;
@@ -31,6 +32,7 @@ import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
+import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
@@ -43,6 +45,15 @@ import android.widget.TextView;
import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.entity.Relationship;
+import com.keylesspalace.tusky.fragment.SFragment;
+import com.keylesspalace.tusky.interfaces.LinkListener;
+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;
import com.squareup.picasso.Picasso;
@@ -156,9 +167,6 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem
// Initialise the default UI states.
floatingBtn.hide();
- avatar.setImageResource(R.drawable.avatar_default);
- header.setImageResource(R.drawable.account_header_default);
-
// Obtain information to fill out the profile.
obtainAccount();
if (!accountId.equals(loggedInAccountId)) {
@@ -237,7 +245,9 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem
displayName.setText(account.getDisplayName());
- LinkHelper.setClickableText(note, account.note, null, new LinkListener() {
+ boolean useCustomTabs = PreferenceManager.getDefaultSharedPreferences(this)
+ .getBoolean("customTabs", true);
+ LinkHelper.setClickableText(note, account.note, null, useCustomTabs, new LinkListener() {
@Override
public void onViewTag(String tag) {
Intent intent = new Intent(AccountActivity.this, ViewTagActivity.class);
@@ -266,7 +276,7 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem
.into(avatar);
Picasso.with(this)
.load(account.header)
- .placeholder(R.drawable.account_header_missing)
+ .placeholder(R.drawable.account_header_default)
.into(header);
NumberFormat nf = NumberFormat.getInstance();
@@ -459,6 +469,7 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem
Snackbar.LENGTH_LONG).show();
} else {
followState = FollowState.NOT_FOLLOWING;
+ broadcast(TimelineReceiver.Types.UNFOLLOW_ACCOUNT, id);
}
updateButtons();
} else {
@@ -509,6 +520,7 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem
@Override
public void onResponse(Call call, Response response) {
if (response.isSuccessful()) {
+ broadcast(TimelineReceiver.Types.BLOCK_ACCOUNT, id);
blocking = response.body().blocking;
updateButtons();
} else {
@@ -546,6 +558,7 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem
@Override
public void onResponse(Call call, Response response) {
if (response.isSuccessful()) {
+ broadcast(TimelineReceiver.Types.MUTE_ACCOUNT, id);
muting = response.body().muting;
updateButtons();
} else {
@@ -578,6 +591,11 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem
.show();
}
+ private void broadcast(String action, String id) {
+ Intent intent = new Intent(action);
+ intent.putExtra("id", id);
+ LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
+ }
@Override
public boolean onOptionsItemSelected(MenuItem item) {
diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.java b/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.java
index 1d1276fbc..86c5a05c3 100644
--- a/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.java
@@ -24,6 +24,8 @@ import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
+import com.keylesspalace.tusky.fragment.AccountListFragment;
+
public class AccountListActivity extends BaseActivity {
enum Type {
BLOCKS,
diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java
index 5bbe7ba13..fa569fa8d 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;
@@ -34,6 +31,15 @@ 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;
+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;
import java.io.IOException;
@@ -51,10 +57,10 @@ import retrofit2.converter.gson.GsonConverterFactory;
public class BaseActivity extends AppCompatActivity {
private static final String TAG = "BaseActivity"; // logging tag
- protected MastodonAPI mastodonAPI;
- protected TuskyAPI tuskyAPI;
+ public MastodonAPI mastodonAPI;
+ public TuskyApi tuskyApi;
+ protected PushNotificationClient pushNotificationClient;
protected Dispatcher mastodonApiDispatcher;
- protected PendingIntent serviceAlarmIntent;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -62,7 +68,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
@@ -154,15 +161,19 @@ public class BaseActivity extends AppCompatActivity {
mastodonAPI = retrofit.create(MastodonAPI.class);
}
- protected void createTuskyAPI() {
- if (BuildConfig.USES_PUSH_NOTIFICATIONS) {
- Retrofit retrofit = new Retrofit.Builder()
- .baseUrl(getString(R.string.tusky_api_url))
- .client(OkHttpUtils.getCompatibleClient())
- .build();
+ protected void createTuskyApi() {
+ Retrofit retrofit = new Retrofit.Builder()
+ .baseUrl("https://" + getString(R.string.tusky_api_url))
+ .client(OkHttpUtils.getCompatibleClient())
+ .addConverterFactory(GsonConverterFactory.create())
+ .build();
- tuskyAPI = retrofit.create(TuskyAPI.class);
- }
+ tuskyApi = retrofit.create(TuskyApi.class);
+ }
+
+ protected void createPushNotificationClient() {
+ pushNotificationClient = new PushNotificationClient(getApplicationContext(),
+ "ssl://" + getString(R.string.tusky_api_url) + ":8883");
}
protected void redirectIfNotLoggedIn() {
@@ -196,49 +207,66 @@ public class BaseActivity extends AppCompatActivity {
}
protected void enablePushNotifications() {
- if (BuildConfig.USES_PUSH_NOTIFICATIONS) {
- String token = com.google.firebase.iid.FirebaseInstanceId.getInstance().getToken();
- tuskyAPI.register(getBaseUrl(), getAccessToken(), token).enqueue(new Callback() {
- @Override
- public void onResponse(Call call, retrofit2.Response response) {
- Log.d(TAG, "Enable push notifications response: " + response.message());
+ Callback callback = new Callback() {
+ @Override
+ public void onResponse(Call call,
+ retrofit2.Response response) {
+ if (response.isSuccessful()) {
+ pushNotificationClient.subscribeToTopic(getPushNotificationTopic());
+ pushNotificationClient.connect(BaseActivity.this);
+ } else {
+ onEnablePushNotificationsFailure(response.message());
}
+ }
- @Override
- public void onFailure(Call call, Throwable t) {
- Log.d(TAG, "Enable push notifications failed: " + t.getMessage());
- }
- });
- } else {
- // Start up the MessagingService on a repeating interval for "pull" notifications.
- long checkInterval = 60 * 1000 * 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);
- }
+ @Override
+ public void onFailure(Call call, Throwable t) {
+ onEnablePushNotificationsFailure(t.getMessage());
+ }
+ };
+ String deviceToken = pushNotificationClient.getDeviceToken();
+ Session session = new Session(getDomain(), getAccessToken(), deviceToken);
+ tuskyApi.register(session)
+ .enqueue(callback);
+ }
+
+ private void onEnablePushNotificationsFailure(String message) {
+ Log.e(TAG, "Enabling push notifications failed. " + message);
}
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());
+ 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) {
- Log.d(TAG, "Disable push notifications failed: " + t.getMessage());
- }
- });
- } else if (serviceAlarmIntent != null) {
- // Cancel the repeating call for "pull" notifications.
- AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
- alarmManager.cancel(serviceAlarmIntent);
- }
+ @Override
+ public void onFailure(Call call, Throwable t) {
+ onDisablePushNotificationsFailure();
+ }
+ };
+ String deviceToken = pushNotificationClient.getDeviceToken();
+ Session session = new Session(getDomain(), getAccessToken(), deviceToken);
+ tuskyApi.unregister(session)
+ .enqueue(callback);
+ }
+
+ private void onDisablePushNotificationsFailure() {
+ Log.e(TAG, "Disabling push notifications failed.");
+ }
+
+ private String getPushNotificationTopic() {
+ 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/ComposeActivity.java b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java
index 4efa93a2d..319257b44 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java
@@ -54,7 +54,10 @@ import android.support.v7.app.ActionBar;
import android.support.v7.content.res.AppCompatResources;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
import android.text.TextWatcher;
+import android.text.style.URLSpan;
import android.view.MenuItem;
import android.view.View;
import android.webkit.MimeTypeMap;
@@ -68,6 +71,14 @@ import android.widget.TextView;
import com.keylesspalace.tusky.entity.Media;
import com.keylesspalace.tusky.entity.Status;
+import com.keylesspalace.tusky.fragment.ComposeOptionsFragment;
+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;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -90,6 +101,7 @@ import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.Callback;
+import retrofit2.Response;
public class ComposeActivity extends BaseActivity implements ComposeOptionsFragment.Listener {
private static final String TAG = "ComposeActivity"; // logging tag
@@ -115,7 +127,8 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
private Uri photoUploadUri;
// this only exists when a status is trying to be sent, but uploads are still occurring
private ProgressDialog finishingUploadDialog;
- @BindView(R.id.compose_edit_field) EditTextTyped textEditor;
+ @BindView(R.id.compose_edit_field)
+ EditTextTyped textEditor;
@BindView(R.id.compose_media_preview_bar) LinearLayout mediaPreviewBar;
@BindView(R.id.compose_content_warning_bar) View contentWarningBar;
@BindView(R.id.field_content_warning) EditText contentWarningEditor;
@@ -143,6 +156,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
Uri uri;
String id;
Call uploadRequest;
+ URLSpan uploadUrl;
ReadyStage readyStage;
byte[] content;
long mediaSize;
@@ -224,7 +238,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
floatingBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- prepareStatus();
+ onSendClicked();
}
});
pickBtn.setOnClickListener(new View.OnClickListener() {
@@ -293,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";
@@ -584,24 +600,12 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
enableButtons();
}
- private void prepareStatus() {
+ private void onSendClicked() {
if (statusAlreadyInFlight) {
return;
}
- String contentText = textEditor.getText().toString();
- String spoilerText = "";
- if (statusHideText) {
- spoilerText = contentWarningEditor.getText().toString();
- }
- int characterCount = contentText.length() + spoilerText.length();
- if (characterCount > 0 && characterCount <= STATUS_CHARACTER_LIMIT) {
- setStateToReadying();
- readyStatus(contentText, statusVisibility, statusMarkSensitive, spoilerText);
- } else if (characterCount <= 0) {
- textEditor.setError(getString(R.string.error_empty));
- } else {
- textEditor.setError(getString(R.string.error_compose_character_limit));
- }
+ setStateToReadying();
+ readyStatus(statusVisibility, statusMarkSensitive);
}
@Override
@@ -705,7 +709,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
mastodonAPI.createStatus(content, inReplyToId, spoilerText, visibility, sensitive, mediaIds).enqueue(new Callback() {
@Override
- public void onResponse(Call call, retrofit2.Response response) {
+ public void onResponse(Call call, Response response) {
if (response.isSuccessful()) {
onSendSuccess();
} else {
@@ -732,8 +736,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
setStateToNotReadying();
}
- private void readyStatus(final String content, final String visibility, final boolean sensitive,
- final String spoilerText) {
+ private void readyStatus(final String visibility, final boolean sensitive) {
finishingUploadDialog = ProgressDialog.show(
this, getString(R.string.dialog_title_finishing_media_upload),
getString(R.string.dialog_message_uploading_media), true, true);
@@ -755,9 +758,9 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
finishingUploadDialog.dismiss();
finishingUploadDialog = null;
if (successful) {
- sendStatus(content, visibility, sensitive, spoilerText);
+ onReadySuccess(visibility, sensitive);
} else {
- onReadyFailure(content, visibility, sensitive, spoilerText);
+ onReadyFailure(visibility, sensitive);
}
}
@@ -780,13 +783,33 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
waitForMediaTask.execute();
}
- private void onReadyFailure(final String content, final String visibility,
- final boolean sensitive, final String spoilerText) {
+ private void onReadySuccess(String visibility, boolean sensitive) {
+ /* Validate the status meets the character limit. This has to be delayed until after all
+ * uploads finish because their links are added when the upload succeeds and that affects
+ * whether the limit is met or not. */
+ String contentText = textEditor.getText().toString();
+ String spoilerText = "";
+ if (statusHideText) {
+ spoilerText = contentWarningEditor.getText().toString();
+ }
+ int characterCount = contentText.length() + spoilerText.length();
+ if (characterCount > 0 && characterCount <= STATUS_CHARACTER_LIMIT) {
+ sendStatus(contentText, visibility, sensitive, spoilerText);
+ } else if (characterCount <= 0) {
+ textEditor.setError(getString(R.string.error_empty));
+ setStateToNotReadying();
+ } else {
+ textEditor.setError(getString(R.string.error_compose_character_limit));
+ setStateToNotReadying();
+ }
+ }
+
+ private void onReadyFailure(final String visibility, final boolean sensitive) {
doErrorDialog(R.string.error_media_upload_sending, R.string.action_retry,
new View.OnClickListener() {
@Override
public void onClick(View v) {
- readyStatus(content, visibility, sensitive, spoilerText);
+ readyStatus(visibility, sensitive);
}
});
setStateToNotReadying();
@@ -951,6 +974,15 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
textEditor.setPadding(textEditor.getPaddingLeft(), textEditor.getPaddingTop(),
textEditor.getPaddingRight(), 0);
}
+ // Remove the text URL associated with this media.
+ if (item.uploadUrl != null) {
+ Editable text = textEditor.getText();
+ int start = text.getSpanStart(item.uploadUrl);
+ int end = text.getSpanEnd(item.uploadUrl);
+ if (start != -1 && end != -1) {
+ text.delete(start, end);
+ }
+ }
enableMediaButtons();
cancelReadyingMedia(item);
}
@@ -1052,8 +1084,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
@Override
public void onResponse(Call call, retrofit2.Response response) {
if (response.isSuccessful()) {
- item.id = response.body().id;
- waitForMediaLatch.countDown();
+ onUploadSuccess(item, response.body());
} else {
Log.d(TAG, "Upload request failed. " + response.message());
onUploadFailure(item, call.isCanceled());
@@ -1068,6 +1099,22 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
});
}
+ private void onUploadSuccess(final QueuedMedia item, Media media) {
+ item.id = media.id;
+
+ /* Add the upload URL to the text field. Also, keep a reference to the span so if the user
+ * chooses to remove the media, the URL is also automatically removed. */
+ item.uploadUrl = new URLSpan(media.textUrl);
+ int end = 1 + media.textUrl.length();
+ SpannableStringBuilder builder = new SpannableStringBuilder();
+ builder.append(' ');
+ builder.append(media.textUrl);
+ builder.setSpan(item.uploadUrl, 0, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ textEditor.append(builder);
+
+ waitForMediaLatch.countDown();
+ }
+
private void onUploadFailure(QueuedMedia item, boolean isCanceled) {
if (!isCanceled) {
/* if the upload was voluntarily cancelled, such as if the user clicked on it to remove
diff --git a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.java b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.java
index ddb538083..03d9811dc 100644
--- a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.java
@@ -36,7 +36,6 @@ import android.util.Base64;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
-import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
@@ -44,6 +43,8 @@ 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;
@@ -178,7 +179,7 @@ public class EditProfileActivity extends BaseActivity {
.into(avatar);
Picasso.with(header.getContext())
.load(me.header)
- .placeholder(R.drawable.account_header_missing)
+ .placeholder(R.drawable.account_header_default)
.into(header);
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/FavouritesActivity.java b/app/src/main/java/com/keylesspalace/tusky/FavouritesActivity.java
index f45ab2c6c..7755f2177 100644
--- a/app/src/main/java/com/keylesspalace/tusky/FavouritesActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/FavouritesActivity.java
@@ -23,6 +23,10 @@ import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
+import com.keylesspalace.tusky.fragment.SFragment;
+import com.keylesspalace.tusky.fragment.TimelineFragment;
+import com.keylesspalace.tusky.interfaces.StatusRemoveListener;
+
public class FavouritesActivity extends BaseActivity implements SFragment.OnUserRemovedListener {
private StatusRemoveListener statusRemoveListener;
diff --git a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java
index b85f1faaa..9226dc6cf 100644
--- a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java
@@ -32,10 +32,15 @@ import android.text.method.LinkMovementMethod;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
+import android.widget.LinearLayout;
import android.widget.TextView;
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;
import java.util.Map;
@@ -58,6 +63,9 @@ public class LoginActivity extends AppCompatActivity {
private String clientId;
private String clientSecret;
+ @BindView(R.id.login_input) LinearLayout input;
+ @BindView(R.id.login_loading) LinearLayout loading;
+
@BindView(R.id.edit_text_domain) EditText editText;
@BindView(R.id.button_login) Button button;
@BindView(R.id.whats_an_instance) TextView whatsAnInstance;
@@ -322,6 +330,8 @@ public class LoginActivity extends AppCompatActivity {
domain = preferences.getString("domain", null);
clientId = preferences.getString("clientId", null);
clientSecret = preferences.getString("clientSecret", null);
+
+ setLoading(true);
/* Since authorization has succeeded, the final step to log in is to exchange
* the authorization code for an access token. */
Callback callback = new Callback() {
@@ -330,6 +340,8 @@ public class LoginActivity extends AppCompatActivity {
if (response.isSuccessful()) {
onLoginSuccess(response.body().accessToken);
} else {
+ setLoading(false);
+
editText.setError(getString(R.string.error_retrieving_oauth_token));
Log.e(TAG, String.format("%s %s",
getString(R.string.error_retrieving_oauth_token),
@@ -339,6 +351,7 @@ public class LoginActivity extends AppCompatActivity {
@Override
public void onFailure(Call call, Throwable t) {
+ setLoading(false);
editText.setError(getString(R.string.error_retrieving_oauth_token));
Log.e(TAG, String.format("%s %s",
getString(R.string.error_retrieving_oauth_token),
@@ -351,21 +364,34 @@ public class LoginActivity extends AppCompatActivity {
} else if (error != null) {
/* Authorization failed. Put the error response where the user can read it and they
* can try again. */
+ setLoading(false);
editText.setError(getString(R.string.error_authorization_denied));
Log.e(TAG, getString(R.string.error_authorization_denied) + error);
} else {
+ setLoading(false);
// This case means a junk response was received somehow.
editText.setError(getString(R.string.error_authorization_unknown));
}
}
}
+ private void setLoading(boolean loadingState) {
+ if (loadingState) {
+ loading.setVisibility(View.VISIBLE);
+ input.setVisibility(View.GONE);
+ } else {
+ loading.setVisibility(View.GONE);
+ input.setVisibility(View.VISIBLE);
+ }
+ }
+
private void onLoginSuccess(String accessToken) {
boolean committed = preferences.edit()
.putString("domain", domain)
.putString("accessToken", accessToken)
.commit();
if (!committed) {
+ setLoading(false);
editText.setError(getString(R.string.error_retrieving_oauth_token));
return;
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
index 243fce579..f0a93c234 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
@@ -15,9 +15,10 @@
package com.keylesspalace.tusky;
-import android.app.NotificationManager;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.res.Configuration;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
@@ -26,9 +27,11 @@ import android.os.PersistableBundle;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.TabLayout;
+import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewPager;
+import android.support.v7.app.AlertDialog;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
@@ -41,6 +44,11 @@ import com.arlib.floatingsearchview.FloatingSearchView;
import com.arlib.floatingsearchview.suggestions.SearchSuggestionsAdapter;
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion;
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;
import com.mikepenz.materialdrawer.AccountHeaderBuilder;
@@ -81,7 +89,7 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
@BindView(R.id.tab_layout) TabLayout tabLayout;
@BindView(R.id.pager) ViewPager viewPager;
- FloatingActionButton composeButton;
+ public FloatingActionButton composeButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -206,8 +214,7 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
.putString("current", "[]")
.apply();
- ((NotificationManager) (getSystemService(NOTIFICATION_SERVICE)))
- .cancel(MessagingService.NOTIFY_ID);
+ pushNotificationClient.clearNotifications(this);
/* After editing a profile, the profile header in the navigation drawer needs to be
* refreshed */
@@ -273,7 +280,8 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
}
});
- Drawable muteDrawable = ContextCompat.getDrawable(this, R.drawable.ic_mute_24dp);
+ VectorDrawableCompat muteDrawable = VectorDrawableCompat.create(getResources(),
+ R.drawable.ic_mute_24dp, getTheme());
ThemeUtils.setDrawableTint(this, muteDrawable, R.attr.toolbar_icon_tint);
drawer = new DrawerBuilder()
@@ -289,7 +297,8 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
new PrimaryDrawerItem().withIdentifier(3).withName(getString(R.string.action_view_blocks)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_block),
new DividerDrawerItem(),
new SecondaryDrawerItem().withIdentifier(4).withName(getString(R.string.action_view_preferences)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_settings),
- new SecondaryDrawerItem().withIdentifier(5).withName(getString(R.string.action_logout)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_exit_to_app)
+ new SecondaryDrawerItem().withIdentifier(5).withName(getString(R.string.about_title_activity)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_info),
+ new SecondaryDrawerItem().withIdentifier(6).withName(getString(R.string.action_logout)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_exit_to_app)
)
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
@Override
@@ -315,8 +324,11 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
Intent intent = new Intent(MainActivity.this, PreferencesActivity.class);
startActivity(intent);
} else if (drawerItemIdentifier == 5) {
- logout();
+ Intent intent = new Intent(MainActivity.this, AboutActivity.class);
+ startActivity(intent);
} else if (drawerItemIdentifier == 6) {
+ logout();
+ } else if (drawerItemIdentifier == 7) {
Intent intent = new Intent(MainActivity.this, AccountListActivity.class);
intent.putExtra("type", AccountListActivity.Type.FOLLOW_REQUESTS);
startActivity(intent);
@@ -330,16 +342,27 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
}
private void logout() {
- if (arePushNotificationsEnabled()) disablePushNotifications();
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.action_logout)
+ .setMessage(R.string.action_logout_confirm)
+ .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (arePushNotificationsEnabled()) disablePushNotifications();
- getPrivatePreferences().edit()
- .remove("domain")
- .remove("accessToken")
- .apply();
+ getPrivatePreferences().edit()
+ .remove("domain")
+ .remove("accessToken")
+ .remove("appAccountId")
+ .apply();
- Intent intent = new Intent(MainActivity.this, LoginActivity.class);
- startActivity(intent);
- finish();
+ Intent intent = new Intent(MainActivity.this, LoginActivity.class);
+ startActivity(intent);
+ finish();
+ }
+ })
+ .setNegativeButton(android.R.string.no, null)
+ .show();
}
private void setupSearchView() {
@@ -472,9 +495,11 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
backgroundHeight = background.getMeasuredHeight();
}
+ background.setBackgroundColor(ContextCompat.getColor(this, R.color.window_background_dark));
+
Picasso.with(MainActivity.this)
.load(me.header)
- .placeholder(R.drawable.account_header_missing)
+ .placeholder(R.drawable.account_header_default)
.resize(backgroundWidth, backgroundHeight)
.centerCrop()
.into(background);
@@ -553,4 +578,10 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
}
}
}
+
+ // Fix for GitHub issues #190, #259 (MainActivity won't restart on screen rotation.)
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java b/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java
index 53e4f22a4..58c185881 100644
--- a/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java
@@ -21,6 +21,8 @@ import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
+import com.keylesspalace.tusky.fragment.PreferencesFragment;
+
public class PreferencesActivity extends BaseActivity
implements SharedPreferences.OnSharedPreferenceChangeListener {
private boolean themeSwitched;
diff --git a/app/src/main/java/com/keylesspalace/tusky/ReportActivity.java b/app/src/main/java/com/keylesspalace/tusky/ReportActivity.java
index 0c3d1895f..f8e4e4472 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ReportActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/ReportActivity.java
@@ -30,7 +30,11 @@ import android.view.MenuItem;
import android.view.View;
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;
import java.util.Arrays;
diff --git a/app/src/main/java/com/keylesspalace/tusky/SplashActivity.java b/app/src/main/java/com/keylesspalace/tusky/SplashActivity.java
index 96c766171..9e1b732f8 100644
--- a/app/src/main/java/com/keylesspalace/tusky/SplashActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/SplashActivity.java
@@ -19,28 +19,13 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
-import android.os.Handler;
-import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
-import android.view.Window;
-import android.view.WindowManager;
public class SplashActivity extends AppCompatActivity {
- private static final int SPLASH_TIME_OUT = 2000;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("lightTheme", false)) {
- setTheme(R.style.AppTheme_Light);
- }
-
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
-
- setContentView(R.layout.activity_splash);
-
/* Determine whether the user is currently logged in, and if so go ahead and load the
* timeline. Otherwise, start the activity_login screen. */
SharedPreferences preferences = getSharedPreferences(
@@ -48,20 +33,13 @@ public class SplashActivity extends AppCompatActivity {
String domain = preferences.getString("domain", null);
String accessToken = preferences.getString("accessToken", null);
- final Intent intent;
-
+ Intent intent;
if (domain != null && accessToken != null) {
intent = new Intent(this, MainActivity.class);
} else {
intent = new Intent(this, LoginActivity.class);
}
-
- new Handler().postDelayed(new Runnable() {
- @Override
- public void run() {
- startActivity(intent);
- finish();
- }
- }, SPLASH_TIME_OUT);
+ startActivity(intent);
+ finish();
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java b/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java
index 9bce54a14..e73d490a2 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java
@@ -23,6 +23,10 @@ import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
+import com.keylesspalace.tusky.fragment.SFragment;
+import com.keylesspalace.tusky.fragment.TimelineFragment;
+import com.keylesspalace.tusky.interfaces.StatusRemoveListener;
+
import butterknife.BindView;
import butterknife.ButterKnife;
diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java b/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java
index 9dcd5efac..20b3534ac 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java
@@ -15,6 +15,7 @@
package com.keylesspalace.tusky;
+import android.content.res.Configuration;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
@@ -24,6 +25,10 @@ import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
+import com.keylesspalace.tusky.fragment.SFragment;
+import com.keylesspalace.tusky.fragment.ViewThreadFragment;
+import com.keylesspalace.tusky.interfaces.StatusRemoveListener;
+
public class ViewThreadActivity extends BaseActivity implements SFragment.OnUserRemovedListener {
Fragment viewThreadFragment;
@@ -74,4 +79,11 @@ public class ViewThreadActivity extends BaseActivity implements SFragment.OnUser
listener.removePostsByUser(accountId);
}
}
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ /* Provide a stub to ignore configuration changes so the thread isn't reloaded when the
+ * the activity is reoriented or resized. */
+ }
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewVideoActivity.java b/app/src/main/java/com/keylesspalace/tusky/ViewVideoActivity.java
index 05ec430e2..9f1c4bf03 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ViewVideoActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/ViewVideoActivity.java
@@ -20,13 +20,16 @@ import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
+import android.view.View;
import android.widget.MediaController;
+import android.widget.ProgressBar;
import android.widget.VideoView;
import butterknife.BindView;
import butterknife.ButterKnife;
public class ViewVideoActivity extends BaseActivity {
+ @BindView(R.id.video_progress) ProgressBar progressBar;
@BindView(R.id.video_player) VideoView videoView;
@BindView(R.id.toolbar) Toolbar toolbar;
@@ -56,6 +59,7 @@ public class ViewVideoActivity extends BaseActivity {
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
+ progressBar.setVisibility(View.GONE);
mp.setLooping(true);
}
});
diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java
similarity index 87%
rename from app/src/main/java/com/keylesspalace/tusky/AccountAdapter.java
rename to app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java
index 9941d34d3..48c6634bf 100644
--- a/app/src/main/java/com/keylesspalace/tusky/AccountAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.java
@@ -13,17 +13,18 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.adapter;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import com.keylesspalace.tusky.entity.Account;
+import com.keylesspalace.tusky.interfaces.AccountActionListener;
import java.util.ArrayList;
import java.util.List;
-abstract class AccountAdapter extends RecyclerView.Adapter {
+public abstract class AccountAdapter extends RecyclerView.Adapter {
List accountList;
AccountActionListener accountActionListener;
@@ -38,7 +39,7 @@ abstract class AccountAdapter extends RecyclerView.Adapter {
return accountList.size() + 1;
}
- void update(List newAccounts) {
+ public void update(List newAccounts) {
if (newAccounts == null || newAccounts.isEmpty()) {
return;
}
@@ -59,14 +60,14 @@ abstract class AccountAdapter extends RecyclerView.Adapter {
notifyDataSetChanged();
}
- void addItems(List newAccounts) {
+ public void addItems(List newAccounts) {
int end = accountList.size();
accountList.addAll(newAccounts);
notifyItemRangeInserted(end, newAccounts.size());
}
@Nullable
- Account removeItem(int position) {
+ public Account removeItem(int position) {
if (position < 0 || position >= accountList.size()) {
return null;
}
@@ -75,7 +76,7 @@ abstract class AccountAdapter extends RecyclerView.Adapter {
return account;
}
- void addItem(Account account, int position) {
+ public void addItem(Account account, int position) {
if (position < 0 || position > accountList.size()) {
return;
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/BlocksAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java
similarity index 94%
rename from app/src/main/java/com/keylesspalace/tusky/BlocksAdapter.java
rename to app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java
index d052db34b..e4e34fa9b 100644
--- a/app/src/main/java/com/keylesspalace/tusky/BlocksAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.adapter;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
@@ -22,18 +22,20 @@ import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;
+import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account;
+import com.keylesspalace.tusky.interfaces.AccountActionListener;
import com.pkmmte.view.CircularImageView;
import com.squareup.picasso.Picasso;
import butterknife.BindView;
import butterknife.ButterKnife;
-class BlocksAdapter extends AccountAdapter {
+public class BlocksAdapter extends AccountAdapter {
private static final int VIEW_TYPE_BLOCKED_USER = 0;
private static final int VIEW_TYPE_FOOTER = 1;
- BlocksAdapter(AccountActionListener accountActionListener) {
+ public BlocksAdapter(AccountActionListener accountActionListener) {
super(accountActionListener);
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/FollowAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java
similarity index 93%
rename from app/src/main/java/com/keylesspalace/tusky/FollowAdapter.java
rename to app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java
index 3b09de247..7e9b825fa 100644
--- a/app/src/main/java/com/keylesspalace/tusky/FollowAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.adapter;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
@@ -22,16 +22,18 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
+import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account;
+import com.keylesspalace.tusky.interfaces.AccountActionListener;
import com.pkmmte.view.CircularImageView;
import com.squareup.picasso.Picasso;
/** Both for follows and following lists. */
-class FollowAdapter extends AccountAdapter {
+public class FollowAdapter extends AccountAdapter {
private static final int VIEW_TYPE_ACCOUNT = 0;
private static final int VIEW_TYPE_FOOTER = 1;
- FollowAdapter(AccountActionListener accountActionListener) {
+ public FollowAdapter(AccountActionListener accountActionListener) {
super(accountActionListener);
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/FollowRequestsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java
similarity index 94%
rename from app/src/main/java/com/keylesspalace/tusky/FollowRequestsAdapter.java
rename to app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java
index 0a54b558b..392d33ac7 100644
--- a/app/src/main/java/com/keylesspalace/tusky/FollowRequestsAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.adapter;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
@@ -22,6 +22,8 @@ import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;
+import com.keylesspalace.tusky.interfaces.AccountActionListener;
+import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account;
import com.pkmmte.view.CircularImageView;
import com.squareup.picasso.Picasso;
@@ -29,11 +31,11 @@ import com.squareup.picasso.Picasso;
import butterknife.BindView;
import butterknife.ButterKnife;
-class FollowRequestsAdapter extends AccountAdapter {
+public class FollowRequestsAdapter extends AccountAdapter {
private static final int VIEW_TYPE_FOLLOW_REQUEST = 0;
private static final int VIEW_TYPE_FOOTER = 1;
- FollowRequestsAdapter(AccountActionListener accountActionListener) {
+ public FollowRequestsAdapter(AccountActionListener accountActionListener) {
super(accountActionListener);
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/FooterViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/FooterViewHolder.java
similarity index 93%
rename from app/src/main/java/com/keylesspalace/tusky/FooterViewHolder.java
rename to app/src/main/java/com/keylesspalace/tusky/adapter/FooterViewHolder.java
index e39ca44af..5ff1187e7 100644
--- a/app/src/main/java/com/keylesspalace/tusky/FooterViewHolder.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FooterViewHolder.java
@@ -13,12 +13,14 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.adapter;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.ProgressBar;
+import com.keylesspalace.tusky.R;
+
class FooterViewHolder extends RecyclerView.ViewHolder {
FooterViewHolder(View itemView) {
super(itemView);
diff --git a/app/src/main/java/com/keylesspalace/tusky/MutesAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java
similarity index 93%
rename from app/src/main/java/com/keylesspalace/tusky/MutesAdapter.java
rename to app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java
index 2513cac6b..06258a4bc 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MutesAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.java
@@ -1,4 +1,4 @@
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.adapter;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
@@ -7,21 +7,20 @@ import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;
+import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account;
+import com.keylesspalace.tusky.interfaces.AccountActionListener;
import com.pkmmte.view.CircularImageView;
import com.squareup.picasso.Picasso;
-import java.util.HashSet;
-import java.util.Set;
-
import butterknife.BindView;
import butterknife.ButterKnife;
-class MutesAdapter extends AccountAdapter {
+public class MutesAdapter extends AccountAdapter {
private static final int VIEW_TYPE_MUTED_USER = 0;
private static final int VIEW_TYPE_FOOTER = 1;
- MutesAdapter(AccountActionListener accountActionListener) {
+ public MutesAdapter(AccountActionListener accountActionListener) {
super(accountActionListener);
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/NotificationsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
similarity index 95%
rename from app/src/main/java/com/keylesspalace/tusky/NotificationsAdapter.java
rename to app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
index 2376469b5..639bfea37 100644
--- a/app/src/main/java/com/keylesspalace/tusky/NotificationsAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.adapter;
import android.content.Context;
import android.graphics.Typeface;
@@ -29,21 +29,24 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
+import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Notification;
import com.keylesspalace.tusky.entity.Status;
+import com.keylesspalace.tusky.interfaces.AdapterItemRemover;
+import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.squareup.picasso.Picasso;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
-class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
+public class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
private static final int VIEW_TYPE_MENTION = 0;
private static final int VIEW_TYPE_FOOTER = 1;
private static final int VIEW_TYPE_STATUS_NOTIFICATION = 2;
private static final int VIEW_TYPE_FOLLOW = 3;
- enum FooterState {
+ public enum FooterState {
EMPTY,
END,
LOADING
@@ -54,7 +57,7 @@ class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRe
private NotificationActionListener notificationActionListener;
private FooterState footerState = FooterState.END;
- NotificationsAdapter(StatusActionListener statusListener,
+ public NotificationsAdapter(StatusActionListener statusListener,
NotificationActionListener notificationActionListener) {
super();
notifications = new ArrayList<>();
@@ -63,7 +66,7 @@ class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRe
}
- void setFooterState(FooterState newFooterState) {
+ public void setFooterState(FooterState newFooterState) {
FooterState oldValue = footerState;
footerState = newFooterState;
if (footerState != oldValue) {
@@ -179,7 +182,7 @@ class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRe
return null;
}
- void update(List newNotifications) {
+ public void update(List newNotifications) {
if (newNotifications == null || newNotifications.isEmpty()) {
return;
}
@@ -200,7 +203,7 @@ class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRe
notifyDataSetChanged();
}
- void addItems(List new_notifications) {
+ public void addItems(List new_notifications) {
int end = notifications.size();
notifications.addAll(new_notifications);
notifyItemRangeInserted(end, new_notifications.size());
@@ -223,7 +226,7 @@ class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRe
}
}
- interface NotificationActionListener {
+ public interface NotificationActionListener {
void onViewAccount(String id);
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/ReportAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/ReportAdapter.java
similarity index 90%
rename from app/src/main/java/com/keylesspalace/tusky/ReportAdapter.java
rename to app/src/main/java/com/keylesspalace/tusky/adapter/ReportAdapter.java
index ccdf7aa9b..50e07369d 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ReportAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/ReportAdapter.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.adapter;
import android.support.v7.widget.RecyclerView;
import android.text.Spanned;
@@ -24,16 +24,18 @@ import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
+import com.keylesspalace.tusky.R;
+
import java.util.ArrayList;
import java.util.List;
-class ReportAdapter extends RecyclerView.Adapter {
- static class ReportStatus {
+public class ReportAdapter extends RecyclerView.Adapter {
+ public static class ReportStatus {
String id;
Spanned content;
boolean checked;
- ReportStatus(String id, Spanned content, boolean checked) {
+ public ReportStatus(String id, Spanned content, boolean checked) {
this.id = id;
this.content = content;
this.checked = checked;
@@ -58,7 +60,7 @@ class ReportAdapter extends RecyclerView.Adapter {
private List statusList;
- ReportAdapter() {
+ public ReportAdapter() {
super();
statusList = new ArrayList<>();
}
@@ -82,13 +84,13 @@ class ReportAdapter extends RecyclerView.Adapter {
return statusList.size();
}
- void addItem(ReportStatus status) {
+ public void addItem(ReportStatus status) {
int end = statusList.size();
statusList.add(status);
notifyItemInserted(end);
}
- void addItems(List newStatuses) {
+ public void addItems(List newStatuses) {
int end = statusList.size();
int added = 0;
for (ReportStatus status : newStatuses) {
@@ -102,7 +104,7 @@ class ReportAdapter extends RecyclerView.Adapter {
}
}
- String[] getCheckedStatusIds() {
+ public String[] getCheckedStatusIds() {
List idList = new ArrayList<>();
for (ReportStatus status : statusList) {
if (status.checked) {
diff --git a/app/src/main/java/com/keylesspalace/tusky/StatusViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java
similarity index 89%
rename from app/src/main/java/com/keylesspalace/tusky/StatusViewHolder.java
rename to app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java
index 6a2e431df..1d029e86b 100644
--- a/app/src/main/java/com/keylesspalace/tusky/StatusViewHolder.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java
@@ -13,9 +13,10 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.adapter;
import android.content.Context;
+import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.text.Spanned;
@@ -26,7 +27,13 @@ import android.widget.ImageView;
import android.widget.TextView;
import android.widget.ToggleButton;
+import com.keylesspalace.tusky.R;
+import com.keylesspalace.tusky.util.RoundedTransformation;
+import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.entity.Status;
+import com.keylesspalace.tusky.util.DateUtils;
+import com.keylesspalace.tusky.util.LinkHelper;
+import com.keylesspalace.tusky.util.ThemeUtils;
import com.squareup.picasso.Picasso;
import com.varunest.sparkbutton.SparkButton;
import com.varunest.sparkbutton.SparkEventListener;
@@ -100,7 +107,10 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
StatusActionListener listener) {
/* Redirect URLSpan's in the status content to the listener for viewing tag pages and
* account pages. */
- LinkHelper.setClickableText(this.content, content, mentions, listener);
+ Context context = this.content.getContext();
+ boolean useCustomTabs = PreferenceManager.getDefaultSharedPreferences(context)
+ .getBoolean("useCustomTabs", true);
+ LinkHelper.setClickableText(this.content, content, mentions, useCustomTabs, listener);
}
private void setAvatar(String url) {
@@ -290,14 +300,20 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
}
});
reblogButton.setEventListener(new SparkEventListener() {
- @Override
- public void onEvent(ImageView button, boolean buttonState) {
- int position = getAdapterPosition();
- if (position != RecyclerView.NO_POSITION) {
- listener.onReblog(!reblogged, position);
- }
- }
- });
+ @Override
+ public void onEvent(ImageView button, boolean buttonState) {
+ int position = getAdapterPosition();
+ if (position != RecyclerView.NO_POSITION) {
+ listener.onReblog(!reblogged, position);
+ }
+ }
+
+ @Override
+ public void onEventAnimationEnd(ImageView button, boolean buttonState) {}
+
+ @Override
+ public void onEventAnimationStart(ImageView button, boolean buttonState) {}
+ });
favouriteButton.setEventListener(new SparkEventListener() {
@Override
public void onEvent(ImageView button, boolean buttonState) {
@@ -306,6 +322,12 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
listener.onFavourite(!favourited, position);
}
}
+
+ @Override
+ public void onEventAnimationEnd(ImageView button, boolean buttonState) {}
+
+ @Override
+ public void onEventAnimationStart(ImageView button, boolean buttonState) {}
});
moreButton.setOnClickListener(new View.OnClickListener() {
@Override
diff --git a/app/src/main/java/com/keylesspalace/tusky/ThreadAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.java
similarity index 85%
rename from app/src/main/java/com/keylesspalace/tusky/ThreadAdapter.java
rename to app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.java
index afa407f8e..46523f527 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ThreadAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.java
@@ -13,25 +13,27 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.adapter;
-import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import com.keylesspalace.tusky.R;
+import com.keylesspalace.tusky.interfaces.AdapterItemRemover;
+import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.entity.Status;
import java.util.ArrayList;
import java.util.List;
-class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
+public class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
private List statuses;
private StatusActionListener statusActionListener;
private int statusIndex;
- ThreadAdapter(StatusActionListener listener) {
+ public ThreadAdapter(StatusActionListener listener) {
this.statusActionListener = listener;
this.statuses = new ArrayList<>();
this.statusIndex = 0;
@@ -56,7 +58,7 @@ class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
return statuses.size();
}
- Status getItem(int position) {
+ public Status getItem(int position) {
return statuses.get(position);
}
@@ -77,7 +79,7 @@ class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
}
}
- int setStatus(Status status) {
+ public int setStatus(Status status) {
if (statuses.size() > 0 && statuses.get(statusIndex).equals(status)) {
// Do not add this status on refresh, it's already in there.
statuses.set(statusIndex, status);
@@ -89,16 +91,16 @@ class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
return i;
}
- void setContext(List ancestors, List descendants) {
+ public void setContext(List ancestors, List descendants) {
Status mainStatus = null;
// In case of refresh, remove old ancestors and descendants first. We'll remove all blindly,
// as we have no guarantee on their order to be the same as before
- int old_size = statuses.size();
- if (old_size > 0) {
+ int oldSize = statuses.size();
+ if (oldSize > 0) {
mainStatus = statuses.get(statusIndex);
statuses.clear();
- notifyItemRangeRemoved(0, old_size);
+ notifyItemRangeRemoved(0, oldSize);
}
// Insert newly fetched ancestors
diff --git a/app/src/main/java/com/keylesspalace/tusky/TimelineAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java
similarity index 86%
rename from app/src/main/java/com/keylesspalace/tusky/TimelineAdapter.java
rename to app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java
index 759a23579..4f06ef475 100644
--- a/app/src/main/java/com/keylesspalace/tusky/TimelineAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.adapter;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
@@ -21,16 +21,19 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import com.keylesspalace.tusky.R;
+import com.keylesspalace.tusky.interfaces.AdapterItemRemover;
+import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.entity.Status;
import java.util.ArrayList;
import java.util.List;
-class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
+public class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
private static final int VIEW_TYPE_STATUS = 0;
private static final int VIEW_TYPE_FOOTER = 1;
- enum FooterState {
+ public enum FooterState {
EMPTY,
END,
LOADING
@@ -40,7 +43,7 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover
private StatusActionListener statusListener;
private FooterState footerState = FooterState.END;
- TimelineAdapter(StatusActionListener statusListener) {
+ public TimelineAdapter(StatusActionListener statusListener) {
super();
statuses = new ArrayList<>();
this.statusListener = statusListener;
@@ -79,7 +82,7 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover
}
}
- void setFooterState(FooterState newFooterState) {
+ public void setFooterState(FooterState newFooterState) {
FooterState oldValue = footerState;
footerState = newFooterState;
if (footerState != oldValue) {
@@ -110,7 +113,7 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover
}
}
- void update(List newStatuses) {
+ public void update(List newStatuses) {
if (newStatuses == null || newStatuses.isEmpty()) {
return;
}
@@ -131,7 +134,7 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover
notifyDataSetChanged();
}
- void addItems(List newStatuses) {
+ public void addItems(List newStatuses) {
int end = statuses.size();
statuses.addAll(newStatuses);
notifyItemRangeInserted(end, newStatuses.size());
@@ -142,7 +145,12 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover
notifyItemRemoved(position);
}
- void removeAllByAccountId(String accountId) {
+ public void clear() {
+ statuses.clear();
+ notifyDataSetChanged();
+ }
+
+ public void removeAllByAccountId(String accountId) {
for (int i = 0; i < statuses.size();) {
Status status = statuses.get(i);
if (accountId.equals(status.account.id)) {
@@ -155,7 +163,7 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover
}
@Nullable
- Status getItem(int position) {
+ public Status getItem(int position) {
if (position >= 0 && position < statuses.size()) {
return statuses.get(position);
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Account.java b/app/src/main/java/com/keylesspalace/tusky/entity/Account.java
index e7be8e810..c802bd4aa 100644
--- a/app/src/main/java/com/keylesspalace/tusky/entity/Account.java
+++ b/app/src/main/java/com/keylesspalace/tusky/entity/Account.java
@@ -20,8 +20,8 @@ import android.text.Spanned;
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion;
import com.google.gson.annotations.SerializedName;
-import com.keylesspalace.tusky.HtmlUtils;
-import com.keylesspalace.tusky.StringWithEmoji;
+import com.keylesspalace.tusky.util.HtmlUtils;
+import com.keylesspalace.tusky.json.StringWithEmoji;
public class Account implements SearchSuggestion {
public String id;
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/entity/Status.java b/app/src/main/java/com/keylesspalace/tusky/entity/Status.java
index 4e147358a..c98ebfa5b 100644
--- a/app/src/main/java/com/keylesspalace/tusky/entity/Status.java
+++ b/app/src/main/java/com/keylesspalace/tusky/entity/Status.java
@@ -17,6 +17,10 @@ package com.keylesspalace.tusky.entity;
import android.text.Spanned;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName;
import java.util.Date;
@@ -115,6 +119,7 @@ public class Status {
}
public static class MediaAttachment {
+ @com.google.gson.annotations.JsonAdapter(MediaTypeDeserializer.class)
public enum Type {
@SerializedName("image")
IMAGE,
@@ -122,7 +127,7 @@ public class Status {
GIFV,
@SerializedName("video")
VIDEO,
- UNKNOWN,
+ UNKNOWN
}
public String url;
@@ -137,6 +142,23 @@ public class Status {
public String remoteUrl;
public Type type;
+
+ static class MediaTypeDeserializer implements JsonDeserializer {
+ @Override
+ public Type deserialize(JsonElement json, java.lang.reflect.Type classOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ switch(json.toString()) {
+ case "\"image\"":
+ return Type.IMAGE;
+ case "\"gifv\"":
+ return Type.GIFV;
+ case "\"video\"":
+ return Type.VIDEO;
+ default:
+ return Type.UNKNOWN;
+ }
+ }
+ }
}
public static class Mention {
@@ -150,4 +172,6 @@ public class Status {
@SerializedName("username")
public String localUsername;
}
+
+
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountListFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.java
similarity index 94%
rename from app/src/main/java/com/keylesspalace/tusky/AccountListFragment.java
rename to app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.java
index 1128602f8..eed0d72a1 100644
--- a/app/src/main/java/com/keylesspalace/tusky/AccountListFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.fragment;
import android.content.Context;
import android.content.Intent;
@@ -29,8 +29,22 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+
+import com.keylesspalace.tusky.AccountActivity;
+import com.keylesspalace.tusky.adapter.AccountAdapter;
+import com.keylesspalace.tusky.adapter.BlocksAdapter;
+import com.keylesspalace.tusky.adapter.FollowAdapter;
+import com.keylesspalace.tusky.adapter.FollowRequestsAdapter;
+import com.keylesspalace.tusky.adapter.MutesAdapter;
+import com.keylesspalace.tusky.BaseActivity;
import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.entity.Relationship;
+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;
@@ -114,16 +128,22 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi
}
recyclerView.setAdapter(adapter);
+ return rootView;
+ }
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ BaseActivity activity = (BaseActivity) getActivity();
+
if (jumpToTopAllowed()) {
- TabLayout layout = (TabLayout) getActivity().findViewById(R.id.tab_layout);
+ TabLayout layout = (TabLayout) activity.findViewById(R.id.tab_layout);
onTabSelectedListener = new TabLayout.OnTabSelectedListener() {
@Override
- public void onTabSelected(TabLayout.Tab tab) {
- }
+ public void onTabSelected(TabLayout.Tab tab) {}
@Override
- public void onTabUnselected(TabLayout.Tab tab) {
- }
+ public void onTabUnselected(TabLayout.Tab tab) {}
@Override
public void onTabReselected(TabLayout.Tab tab) {
@@ -133,16 +153,10 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi
layout.addOnTabSelectedListener(onTabSelectedListener);
}
- return rootView;
- }
-
- @Override
- public void onActivityCreated(@Nullable Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
/* MastodonAPI on the base activity is only guaranteed to be initialised after the parent
* activity is created, so everything needing to access the api object has to be delayed
* until here. */
- api = ((BaseActivity) getActivity()).mastodonAPI;
+ api = activity.mastodonAPI;
scrollListener = new EndlessOnScrollListener(layoutManager) {
@Override
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/BaseFragment.java
similarity index 95%
rename from app/src/main/java/com/keylesspalace/tusky/BaseFragment.java
rename to app/src/main/java/com/keylesspalace/tusky/fragment/BaseFragment.java
index 0a9dcbcf3..c0305d125 100644
--- a/app/src/main/java/com/keylesspalace/tusky/BaseFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/BaseFragment.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.fragment;
import android.content.Context;
import android.content.SharedPreferences;
@@ -21,6 +21,8 @@ import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
+import com.keylesspalace.tusky.R;
+
import java.util.ArrayList;
import java.util.List;
diff --git a/app/src/main/java/com/keylesspalace/tusky/ComposeOptionsFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/ComposeOptionsFragment.java
similarity index 69%
rename from app/src/main/java/com/keylesspalace/tusky/ComposeOptionsFragment.java
rename to app/src/main/java/com/keylesspalace/tusky/fragment/ComposeOptionsFragment.java
index 3f3e1c0be..ab5226020 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ComposeOptionsFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ComposeOptionsFragment.java
@@ -13,12 +13,18 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.fragment;
import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.Bundle;
+import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.support.design.widget.BottomSheetDialogFragment;
+import android.support.graphics.drawable.VectorDrawableCompat;
+import android.support.v4.graphics.drawable.DrawableCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -27,8 +33,11 @@ import android.widget.CompoundButton;
import android.widget.RadioButton;
import android.widget.RadioGroup;
+import com.keylesspalace.tusky.R;
+import com.keylesspalace.tusky.util.ThemeUtils;
+
public class ComposeOptionsFragment extends BottomSheetDialogFragment {
- interface Listener {
+ public interface Listener {
void onVisibilityChanged(String visibility);
void onContentWarningChanged(boolean hideText);
}
@@ -85,8 +94,16 @@ public class ComposeOptionsFragment extends BottomSheetDialogFragment {
}
radio.check(radioCheckedId);
+ RadioButton publicButton = (RadioButton) rootView.findViewById(R.id.radio_public);
+ RadioButton unlistedButton = (RadioButton) rootView.findViewById(R.id.radio_unlisted);
+ RadioButton privateButton = (RadioButton) rootView.findViewById(R.id.radio_private);
+ RadioButton directButton = (RadioButton) rootView.findViewById(R.id.radio_direct);
+ setRadioButtonDrawable(getContext(), publicButton, R.drawable.ic_public_24dp);
+ setRadioButtonDrawable(getContext(), unlistedButton, R.drawable.ic_lock_open_24dp);
+ setRadioButtonDrawable(getContext(), privateButton, R.drawable.ic_lock_outline_24dp);
+ setRadioButtonDrawable(getContext(), directButton, R.drawable.ic_email_24dp);
+
if (isReply) {
- RadioButton publicButton = (RadioButton) rootView.findViewById(R.id.radio_public);
publicButton.setEnabled(false);
}
@@ -132,4 +149,27 @@ public class ComposeOptionsFragment extends BottomSheetDialogFragment {
}
});
}
+
+ private static void setRadioButtonDrawable(Context context, RadioButton button,
+ @DrawableRes int id) {
+ ColorStateList list = new ColorStateList(new int[][] {
+ new int[] { -android.R.attr.state_checked },
+ new int[] { android.R.attr.state_checked }
+ }, new int[] {
+ ThemeUtils.getColor(context, R.attr.compose_image_button_tint),
+ ThemeUtils.getColor(context, R.attr.colorAccent)
+ });
+ Drawable drawable = VectorDrawableCompat.create(context.getResources(), id,
+ context.getTheme());
+ if (drawable == null) {
+ return;
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ button.setButtonTintList(list);
+ } else {
+ drawable = DrawableCompat.wrap(drawable);
+ DrawableCompat.setTintList(drawable, list);
+ }
+ button.setButtonDrawable(drawable);
+ }
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/NotificationsFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java
similarity index 96%
rename from app/src/main/java/com/keylesspalace/tusky/NotificationsFragment.java
rename to app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java
index 56afc0fd8..bef04290d 100644
--- a/app/src/main/java/com/keylesspalace/tusky/NotificationsFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.fragment;
import android.content.Context;
import android.content.SharedPreferences;
@@ -31,8 +31,17 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+
+import com.keylesspalace.tusky.MainActivity;
+import com.keylesspalace.tusky.adapter.NotificationsAdapter;
+import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Notification;
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/PreferencesFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/PreferencesFragment.java
similarity index 92%
rename from app/src/main/java/com/keylesspalace/tusky/PreferencesFragment.java
rename to app/src/main/java/com/keylesspalace/tusky/fragment/PreferencesFragment.java
index 8820f5eb0..280098a76 100644
--- a/app/src/main/java/com/keylesspalace/tusky/PreferencesFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/PreferencesFragment.java
@@ -13,11 +13,13 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.fragment;
import android.os.Bundle;
import android.preference.PreferenceFragment;
+import com.keylesspalace.tusky.R;
+
public class PreferencesFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
diff --git a/app/src/main/java/com/keylesspalace/tusky/SFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java
similarity index 95%
rename from app/src/main/java/com/keylesspalace/tusky/SFragment.java
rename to app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java
index 3af5cce0c..71b044261 100644
--- a/app/src/main/java/com/keylesspalace/tusky/SFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.fragment;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -27,8 +27,19 @@ import android.text.Spanned;
import android.view.MenuItem;
import android.view.View;
+import com.keylesspalace.tusky.AccountActivity;
+import com.keylesspalace.tusky.BaseActivity;
+import com.keylesspalace.tusky.ComposeActivity;
+import com.keylesspalace.tusky.R;
+import com.keylesspalace.tusky.ReportActivity;
+import com.keylesspalace.tusky.ViewTagActivity;
+import com.keylesspalace.tusky.ViewThreadActivity;
+import com.keylesspalace.tusky.ViewVideoActivity;
import com.keylesspalace.tusky.entity.Relationship;
import com.keylesspalace.tusky.entity.Status;
+import com.keylesspalace.tusky.interfaces.AdapterItemRemover;
+import com.keylesspalace.tusky.network.MastodonAPI;
+import com.keylesspalace.tusky.util.HtmlUtils;
import java.util.ArrayList;
import java.util.List;
@@ -45,7 +56,7 @@ import retrofit2.Response;
* overlap functionality. So, I'm momentarily leaving it and hopefully working on those will clear
* up what needs to be where. */
public abstract class SFragment extends BaseFragment {
- interface OnUserRemovedListener {
+ public interface OnUserRemovedListener {
void onUserRemoved(String accountId);
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java
similarity index 83%
rename from app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java
rename to app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java
index d23bbed3a..17e9b726f 100644
--- a/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.fragment;
import android.content.Context;
import android.content.SharedPreferences;
@@ -23,6 +23,7 @@ import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.TabLayout;
+import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
@@ -31,12 +32,23 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import com.keylesspalace.tusky.MainActivity;
+import com.keylesspalace.tusky.R;
+import com.keylesspalace.tusky.adapter.TimelineAdapter;
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;
+import java.util.Iterator;
import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
+import retrofit2.Response;
public class TimelineFragment extends SFragment implements
SwipeRefreshLayout.OnRefreshListener,
@@ -45,9 +57,7 @@ public class TimelineFragment extends SFragment implements
SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = "Timeline"; // logging tag
- private Call> listCall;
-
- enum Kind {
+ public enum Kind {
HOME,
PUBLIC_LOCAL,
PUBLIC_FEDERATED,
@@ -64,7 +74,11 @@ public class TimelineFragment extends SFragment implements
private LinearLayoutManager layoutManager;
private EndlessOnScrollListener scrollListener;
private TabLayout.OnTabSelectedListener onTabSelectedListener;
+ private SharedPreferences preferences;
+ private boolean filterRemoveReplies;
+ private boolean filterRemoveReblogs;
private boolean hideFab;
+ private TimelineReceiver timelineReceiver;
public static TimelineFragment newInstance(Kind kind) {
TimelineFragment fragment = new TimelineFragment();
@@ -113,6 +127,25 @@ public class TimelineFragment extends SFragment implements
adapter = new TimelineAdapter(this);
recyclerView.setAdapter(adapter);
+ timelineReceiver = new TimelineReceiver(adapter);
+ LocalBroadcastManager.getInstance(context.getApplicationContext()).registerReceiver(timelineReceiver, TimelineReceiver.getFilter(kind));
+ return rootView;
+ }
+
+ private void onLoadMore(RecyclerView view) {
+ TimelineAdapter adapter = (TimelineAdapter) view.getAdapter();
+ Status status = adapter.getItem(adapter.getItemCount() - 2);
+ if (status != null) {
+ sendFetchTimelineRequest(status.id, null);
+ } else {
+ sendFetchTimelineRequest(null, null);
+ }
+ }
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
if (jumpToTopAllowed()) {
TabLayout layout = (TabLayout) getActivity().findViewById(R.id.tab_layout);
onTabSelectedListener = new TabLayout.OnTabSelectedListener() {
@@ -130,23 +163,6 @@ public class TimelineFragment extends SFragment implements
layout.addOnTabSelectedListener(onTabSelectedListener);
}
- return rootView;
- }
-
- private void onLoadMore(RecyclerView view) {
- TimelineAdapter adapter = (TimelineAdapter) view.getAdapter();
- Status status = adapter.getItem(adapter.getItemCount() - 2);
- if (status != null) {
- sendFetchTimelineRequest(status.id, null);
- } else {
- sendFetchTimelineRequest();
- }
- }
-
- @Override
- public void onActivityCreated(@Nullable Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
/* This is delayed until onActivityCreated solely because MainActivity.composeButton isn't
* guaranteed to be set until then. */
if (composeButtonPresent()) {
@@ -189,12 +205,8 @@ public class TimelineFragment extends SFragment implements
};
}
recyclerView.addOnScrollListener(scrollListener);
- }
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (listCall != null) listCall.cancel();
+ preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
}
@Override
@@ -203,6 +215,7 @@ public class TimelineFragment extends SFragment implements
TabLayout tabLayout = (TabLayout) getActivity().findViewById(R.id.tab_layout);
tabLayout.removeOnTabSelectedListener(onTabSelectedListener);
}
+ LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(timelineReceiver);
super.onDestroyView();
}
@@ -224,9 +237,9 @@ public class TimelineFragment extends SFragment implements
adapter.setFooterState(TimelineAdapter.FooterState.LOADING);
}
- Callback> cb = new Callback>() {
+ Callback> callback = new Callback>() {
@Override
- public void onResponse(Call> call, retrofit2.Response> response) {
+ public void onResponse(Call> call, Response> response) {
if (response.isSuccessful()) {
onFetchTimelineSuccess(response.body(), fromId);
} else {
@@ -240,6 +253,7 @@ public class TimelineFragment extends SFragment implements
}
};
+ Call> listCall;
switch (kind) {
default:
case HOME: {
@@ -268,11 +282,7 @@ public class TimelineFragment extends SFragment implements
}
}
callList.add(listCall);
- listCall.enqueue(cb);
- }
-
- private void sendFetchTimelineRequest() {
- sendFetchTimelineRequest(null, null);
+ listCall.enqueue(callback);
}
public void removePostsByUser(String accountId) {
@@ -288,7 +298,36 @@ public class TimelineFragment extends SFragment implements
return false;
}
+ protected void filterStatuses(List statuses) {
+ Iterator it = statuses.iterator();
+ while (it.hasNext()) {
+ Status status = it.next();
+ if ((status.inReplyToId != null && filterRemoveReplies) || (status.reblog != null && filterRemoveReblogs)) {
+ it.remove();
+ }
+ }
+ }
+
+ protected void setFiltersFromSettings() {
+ boolean oldRemoveReplies = filterRemoveReplies;
+ boolean oldRemoveReblogs = filterRemoveReblogs;
+ filterRemoveReplies = (kind == Kind.HOME && !preferences.getBoolean("tabFilterHomeReplies", true));
+ filterRemoveReblogs = (kind == Kind.HOME && !preferences.getBoolean("tabFilterHomeBoosts", true));
+
+ if (adapter.getItemCount() > 1 && (oldRemoveReblogs != filterRemoveReblogs || oldRemoveReplies != filterRemoveReplies)) {
+ adapter.clear();
+ sendFetchTimelineRequest(null, null);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ setFiltersFromSettings();
+ }
+
public void onFetchTimelineSuccess(List statuses, String fromId) {
+ filterStatuses(statuses);
if (fromId != null) {
if (statuses.size() > 0 && !findStatus(statuses, fromId)) {
adapter.addItems(statuses);
@@ -314,7 +353,7 @@ public class TimelineFragment extends SFragment implements
if (status != null) {
sendFetchTimelineRequest(null, status.id);
} else {
- sendFetchTimelineRequest();
+ sendFetchTimelineRequest(null, null);
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewMediaFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.java
similarity index 75%
rename from app/src/main/java/com/keylesspalace/tusky/ViewMediaFragment.java
rename to app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.java
index fed8cbebd..f53a4ef66 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ViewMediaFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.java
@@ -13,12 +13,10 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.fragment;
-import android.app.AlertDialog;
import android.app.DownloadManager;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
@@ -30,12 +28,20 @@ import android.support.annotation.StringRes;
import android.support.design.widget.Snackbar;
import android.support.v4.app.DialogFragment;
import android.support.v4.content.ContextCompat;
+import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
+import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
+import android.widget.ImageView;
+import com.github.chrisbanes.photoview.OnOutsidePhotoTapListener;
+import com.github.chrisbanes.photoview.OnSingleFlingListener;
+import com.github.chrisbanes.photoview.PhotoView;
+import com.github.chrisbanes.photoview.PhotoViewAttacher;
+import com.keylesspalace.tusky.R;
import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso;
@@ -43,17 +49,18 @@ import java.io.File;
import butterknife.BindView;
import butterknife.ButterKnife;
-import uk.co.senab.photoview.PhotoView;
-import uk.co.senab.photoview.PhotoViewAttacher;
-public class ViewMediaFragment extends DialogFragment {
+public class ViewMediaFragment extends DialogFragment implements Toolbar.OnMenuItemClickListener {
private PhotoViewAttacher attacher;
- private DownloadManager downloadManager;
private static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1;
- @BindView(R.id.view_media_image) PhotoView photoView;
+ @BindView(R.id.view_media_image)
+ PhotoView photoView;
+
+ @BindView(R.id.toolbar)
+ Toolbar toolbar;
public static ViewMediaFragment newInstance(String url) {
Bundle arguments = new Bundle();
@@ -81,7 +88,7 @@ public class ViewMediaFragment extends DialogFragment {
@Override
public View onCreateView(LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) {
- View rootView = inflater.inflate(R.layout.fragment_view_media, container, false);
+ final View rootView = inflater.inflate(R.layout.fragment_view_media, container, false);
ButterKnife.bind(this, rootView);
Bundle arguments = getArguments();
@@ -90,24 +97,19 @@ public class ViewMediaFragment extends DialogFragment {
attacher = new PhotoViewAttacher(photoView);
// Clicking outside the photo closes the viewer.
- attacher.setOnPhotoTapListener(new PhotoViewAttacher.OnPhotoTapListener() {
+ attacher.setOnOutsidePhotoTapListener(new OnOutsidePhotoTapListener() {
@Override
- public void onPhotoTap(View view, float x, float y) {
-
- }
-
- @Override
- public void onOutsidePhotoTap() {
+ public void onOutsidePhotoTap(ImageView imageView) {
dismiss();
}
});
/* A vertical swipe motion also closes the viewer. This is especially useful when the photo
* mostly fills the screen so clicking outside is difficult. */
- attacher.setOnSingleFlingListener(new PhotoViewAttacher.OnSingleFlingListener() {
+ attacher.setOnSingleFlingListener(new OnSingleFlingListener() {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
- float velocityY) {
+ float velocityY) {
if (Math.abs(velocityY) > Math.abs(velocityX)) {
dismiss();
return true;
@@ -116,22 +118,10 @@ public class ViewMediaFragment extends DialogFragment {
}
});
- attacher.setOnLongClickListener(new View.OnLongClickListener() {
+ toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
- public boolean onLongClick(View v) {
-
- AlertDialog downloadDialog = new AlertDialog.Builder(getContext()).create();
-
- downloadDialog.setButton(AlertDialog.BUTTON_NEUTRAL, getString(R.string.dialog_download_image),
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
-
- downloadImage();
- }
- });
- downloadDialog.show();
- return false;
+ public void onClick(View v) {
+ dismiss();
}
});
@@ -140,6 +130,10 @@ public class ViewMediaFragment extends DialogFragment {
.into(photoView, new Callback() {
@Override
public void onSuccess() {
+ rootView.findViewById(R.id.view_media_progress).setVisibility(View.GONE);
+ toolbar.setOnMenuItemClickListener(ViewMediaFragment.this);
+ toolbar.inflateMenu(R.menu.view_media_tooblar);
+
attacher.update();
}
@@ -152,12 +146,6 @@ public class ViewMediaFragment extends DialogFragment {
return rootView;
}
- @Override
- public void onDestroyView() {
- attacher.cleanup();
- super.onDestroyView();
- }
-
private void downloadImage(){
//Permission stuff
@@ -170,14 +158,13 @@ public class ViewMediaFragment extends DialogFragment {
PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
} else {
-
//download stuff
String url = getArguments().getString("url");
Uri uri = Uri.parse(url);
String filename = new File(url).getName();
- downloadManager = (DownloadManager) getContext().getSystemService(Context.DOWNLOAD_SERVICE);
+ DownloadManager downloadManager = (DownloadManager) getContext().getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(uri);
request.allowScanningByMediaScanner();
@@ -211,9 +198,24 @@ public class ViewMediaFragment extends DialogFragment {
private void doErrorDialog(@StringRes int descriptionId, @StringRes int actionId,
View.OnClickListener listener) {
- Snackbar bar = Snackbar.make(getView(), getString(descriptionId),
- Snackbar.LENGTH_SHORT);
- bar.setAction(actionId, listener);
- bar.show();
+ if(getView() != null) {
+ Snackbar bar = Snackbar.make(getView(), getString(descriptionId),
+ Snackbar.LENGTH_SHORT);
+ bar.setAction(actionId, listener);
+ bar.show();
+ }
+ }
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ int id = item.getItemId();
+ switch (id) {
+ case R.id.action_download:
+ downloadImage();
+ break;
+ default:
+ break;
+ }
+ return true;
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java
similarity index 81%
rename from app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java
rename to app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java
index 0cf476005..d02f43596 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.fragment;
import android.content.Context;
import android.graphics.drawable.Drawable;
@@ -29,11 +29,22 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+
+import com.keylesspalace.tusky.adapter.ThreadAdapter;
+import com.keylesspalace.tusky.BaseActivity;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.entity.StatusContext;
+import com.keylesspalace.tusky.network.MastodonAPI;
+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;
import retrofit2.Callback;
+import retrofit2.Response;
public class ViewThreadFragment extends SFragment implements
SwipeRefreshLayout.OnRefreshListener, StatusActionListener, StatusRemoveListener {
@@ -42,6 +53,7 @@ public class ViewThreadFragment extends SFragment implements
private SwipeRefreshLayout swipeRefreshLayout;
private RecyclerView recyclerView;
private ThreadAdapter adapter;
+ private MastodonAPI mastodonApi;
private String thisThreadsStatusId;
public static ViewThreadFragment newInstance(String id) {
@@ -72,25 +84,34 @@ public class ViewThreadFragment extends SFragment implements
R.drawable.status_divider_dark);
divider.setDrawable(drawable);
recyclerView.addItemDecoration(divider);
- recyclerView.addItemDecoration(new ConversationLineItemDecoration(context, ContextCompat.getDrawable(context, R.drawable.conversation_divider_dark)));
+ recyclerView.addItemDecoration(new ConversationLineItemDecoration(context,
+ ContextCompat.getDrawable(context, R.drawable.conversation_divider_dark)));
adapter = new ThreadAdapter(this);
recyclerView.setAdapter(adapter);
- String id = getArguments().getString("id");
- sendStatusRequest(id);
- sendThreadRequest(id);
- thisThreadsStatusId = id;
+ mastodonApi = null;
+ thisThreadsStatusId = null;
return rootView;
}
- private void sendStatusRequest(final String id) {
- MastodonAPI api = ((BaseActivity) getActivity()).mastodonAPI;
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
- Call call = api.status(id);
+ /* BaseActivity's MastodonAPI object isn't guaranteed to be valid until after its onCreate
+ * is run, so all calls that need it can't be done until here. */
+ mastodonApi = ((BaseActivity) getActivity()).mastodonAPI;
+
+ thisThreadsStatusId = getArguments().getString("id");
+ onRefresh();
+ }
+
+ private void sendStatusRequest(final String id) {
+ Call call = mastodonApi.status(id);
call.enqueue(new Callback() {
@Override
- public void onResponse(Call call, retrofit2.Response response) {
+ public void onResponse(Call call, Response response) {
if (response.isSuccessful()) {
int position = adapter.setStatus(response.body());
recyclerView.scrollToPosition(position);
@@ -108,12 +129,10 @@ public class ViewThreadFragment extends SFragment implements
}
private void sendThreadRequest(final String id) {
- MastodonAPI api = ((BaseActivity) getActivity()).mastodonAPI;
-
- Call call = api.statusContext(id);
+ Call call = mastodonApi.statusContext(id);
call.enqueue(new Callback() {
@Override
- public void onResponse(Call call, retrofit2.Response response) {
+ public void onResponse(Call call, Response response) {
if (response.isSuccessful()) {
swipeRefreshLayout.setRefreshing(false);
StatusContext context = response.body();
diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActionListener.java b/app/src/main/java/com/keylesspalace/tusky/interfaces/AccountActionListener.java
similarity index 92%
rename from app/src/main/java/com/keylesspalace/tusky/AccountActionListener.java
rename to app/src/main/java/com/keylesspalace/tusky/interfaces/AccountActionListener.java
index bca609cdf..116bcae8f 100644
--- a/app/src/main/java/com/keylesspalace/tusky/AccountActionListener.java
+++ b/app/src/main/java/com/keylesspalace/tusky/interfaces/AccountActionListener.java
@@ -13,9 +13,9 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.interfaces;
-interface AccountActionListener {
+public interface AccountActionListener {
void onViewAccount(String id);
void onMute(final boolean mute, final String id, final int position);
void onBlock(final boolean block, final String id, final int position);
diff --git a/app/src/main/java/com/keylesspalace/tusky/AdapterItemRemover.java b/app/src/main/java/com/keylesspalace/tusky/interfaces/AdapterItemRemover.java
similarity index 89%
rename from app/src/main/java/com/keylesspalace/tusky/AdapterItemRemover.java
rename to app/src/main/java/com/keylesspalace/tusky/interfaces/AdapterItemRemover.java
index 634935ffa..5b49cbfa7 100644
--- a/app/src/main/java/com/keylesspalace/tusky/AdapterItemRemover.java
+++ b/app/src/main/java/com/keylesspalace/tusky/interfaces/AdapterItemRemover.java
@@ -13,8 +13,8 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.interfaces;
-interface AdapterItemRemover {
+public interface AdapterItemRemover {
void removeItem(int position);
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/LinkListener.java b/app/src/main/java/com/keylesspalace/tusky/interfaces/LinkListener.java
similarity index 90%
rename from app/src/main/java/com/keylesspalace/tusky/LinkListener.java
rename to app/src/main/java/com/keylesspalace/tusky/interfaces/LinkListener.java
index 9e188ae42..62360e34a 100644
--- a/app/src/main/java/com/keylesspalace/tusky/LinkListener.java
+++ b/app/src/main/java/com/keylesspalace/tusky/interfaces/LinkListener.java
@@ -13,9 +13,9 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.interfaces;
-interface LinkListener {
+public interface LinkListener {
void onViewTag(String tag);
void onViewAccount(String id);
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/StatusActionListener.java b/app/src/main/java/com/keylesspalace/tusky/interfaces/StatusActionListener.java
similarity index 91%
rename from app/src/main/java/com/keylesspalace/tusky/StatusActionListener.java
rename to app/src/main/java/com/keylesspalace/tusky/interfaces/StatusActionListener.java
index 1004bb824..b6b80f723 100644
--- a/app/src/main/java/com/keylesspalace/tusky/StatusActionListener.java
+++ b/app/src/main/java/com/keylesspalace/tusky/interfaces/StatusActionListener.java
@@ -13,13 +13,13 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.interfaces;
import android.view.View;
import com.keylesspalace.tusky.entity.Status;
-interface StatusActionListener extends LinkListener {
+public interface StatusActionListener extends LinkListener {
void onReply(int position);
void onReblog(final boolean reblog, final int position);
void onFavourite(final boolean favourite, final int position);
diff --git a/app/src/main/java/com/keylesspalace/tusky/StatusRemoveListener.java b/app/src/main/java/com/keylesspalace/tusky/interfaces/StatusRemoveListener.java
similarity index 89%
rename from app/src/main/java/com/keylesspalace/tusky/StatusRemoveListener.java
rename to app/src/main/java/com/keylesspalace/tusky/interfaces/StatusRemoveListener.java
index 23111c6ab..c05f76573 100644
--- a/app/src/main/java/com/keylesspalace/tusky/StatusRemoveListener.java
+++ b/app/src/main/java/com/keylesspalace/tusky/interfaces/StatusRemoveListener.java
@@ -13,8 +13,8 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.interfaces;
-interface StatusRemoveListener {
+public interface StatusRemoveListener {
void removePostsByUser(String accountId);
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/SpannedTypeAdapter.java b/app/src/main/java/com/keylesspalace/tusky/json/SpannedTypeAdapter.java
similarity index 93%
rename from app/src/main/java/com/keylesspalace/tusky/SpannedTypeAdapter.java
rename to app/src/main/java/com/keylesspalace/tusky/json/SpannedTypeAdapter.java
index 3b47acc3b..305adba41 100644
--- a/app/src/main/java/com/keylesspalace/tusky/SpannedTypeAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/json/SpannedTypeAdapter.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.json;
import android.text.Spanned;
@@ -22,6 +22,7 @@ import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
+import com.keylesspalace.tusky.util.HtmlUtils;
import java.lang.reflect.Type;
diff --git a/app/src/main/java/com/keylesspalace/tusky/StringWithEmoji.java b/app/src/main/java/com/keylesspalace/tusky/json/StringWithEmoji.java
similarity index 96%
rename from app/src/main/java/com/keylesspalace/tusky/StringWithEmoji.java
rename to app/src/main/java/com/keylesspalace/tusky/json/StringWithEmoji.java
index cad8e8c08..d229c5a99 100644
--- a/app/src/main/java/com/keylesspalace/tusky/StringWithEmoji.java
+++ b/app/src/main/java/com/keylesspalace/tusky/json/StringWithEmoji.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.json;
/**
* This is just a wrapper class for a String.
diff --git a/app/src/main/java/com/keylesspalace/tusky/StringWithEmojiTypeAdapter.java b/app/src/main/java/com/keylesspalace/tusky/json/StringWithEmojiTypeAdapter.java
similarity index 91%
rename from app/src/main/java/com/keylesspalace/tusky/StringWithEmojiTypeAdapter.java
rename to app/src/main/java/com/keylesspalace/tusky/json/StringWithEmojiTypeAdapter.java
index ecf18bcd9..86bd2c61b 100644
--- a/app/src/main/java/com/keylesspalace/tusky/StringWithEmojiTypeAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/json/StringWithEmojiTypeAdapter.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.json;
import com.emojione.Emojione;
import com.google.gson.JsonDeserializationContext;
@@ -24,7 +24,7 @@ import com.google.gson.JsonParseException;
import java.lang.reflect.Type;
/** This is a type-based workaround to allow for shortcode conversion when loading display names. */
-class StringWithEmojiTypeAdapter implements JsonDeserializer {
+public class StringWithEmojiTypeAdapter implements JsonDeserializer {
@Override
public StringWithEmoji deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
diff --git a/app/src/main/java/com/keylesspalace/tusky/MastodonAPI.java b/app/src/main/java/com/keylesspalace/tusky/network/MastodonAPI.java
similarity index 99%
rename from app/src/main/java/com/keylesspalace/tusky/MastodonAPI.java
rename to app/src/main/java/com/keylesspalace/tusky/network/MastodonAPI.java
index 011fbc40a..aad04523c 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MastodonAPI.java
+++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonAPI.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.network;
import com.keylesspalace.tusky.entity.AccessToken;
import com.keylesspalace.tusky.entity.Account;
diff --git a/app/src/main/java/com/keylesspalace/tusky/TuskyAPI.java b/app/src/main/java/com/keylesspalace/tusky/network/TuskyApi.java
similarity index 64%
rename from app/src/main/java/com/keylesspalace/tusky/TuskyAPI.java
rename to app/src/main/java/com/keylesspalace/tusky/network/TuskyApi.java
index 4831949bd..24384dcf8 100644
--- a/app/src/main/java/com/keylesspalace/tusky/TuskyAPI.java
+++ b/app/src/main/java/com/keylesspalace/tusky/network/TuskyApi.java
@@ -13,19 +13,18 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.network;
+
+import com.keylesspalace.tusky.entity.Session;
import okhttp3.ResponseBody;
import retrofit2.Call;
-import retrofit2.http.Field;
-import retrofit2.http.FormUrlEncoded;
+import retrofit2.http.Body;
import retrofit2.http.POST;
-public interface TuskyAPI {
- @FormUrlEncoded
+public interface TuskyApi {
@POST("/register")
- Call register(@Field("instance_url") String instanceUrl, @Field("access_token") String accessToken, @Field("device_token") String deviceToken);
- @FormUrlEncoded
+ Call register(@Body Session session);
@POST("/unregister")
- Call unregister(@Field("instance_url") String instanceUrl, @Field("access_token") String accessToken);
+ Call unregister(@Body Session session);
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountPagerAdapter.java b/app/src/main/java/com/keylesspalace/tusky/pager/AccountPagerAdapter.java
similarity index 84%
rename from app/src/main/java/com/keylesspalace/tusky/AccountPagerAdapter.java
rename to app/src/main/java/com/keylesspalace/tusky/pager/AccountPagerAdapter.java
index 505d89bd2..8f04ae186 100644
--- a/app/src/main/java/com/keylesspalace/tusky/AccountPagerAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/pager/AccountPagerAdapter.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.pager;
import android.content.Context;
import android.support.v4.app.Fragment;
@@ -24,23 +24,27 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
+import com.keylesspalace.tusky.R;
+import com.keylesspalace.tusky.fragment.AccountListFragment;
+import com.keylesspalace.tusky.fragment.TimelineFragment;
+
import java.util.ArrayList;
import java.util.List;
-class AccountPagerAdapter extends FragmentPagerAdapter {
+public class AccountPagerAdapter extends FragmentPagerAdapter {
private Context context;
private String accountId;
private String[] pageTitles;
private List registeredFragments;
- AccountPagerAdapter(FragmentManager manager, Context context, String accountId) {
+ public AccountPagerAdapter(FragmentManager manager, Context context, String accountId) {
super(manager);
this.context = context;
this.accountId = accountId;
registeredFragments = new ArrayList<>();
}
- void setPageTitles(String[] titles) {
+ public void setPageTitles(String[] titles) {
pageTitles = titles;
}
@@ -72,7 +76,7 @@ class AccountPagerAdapter extends FragmentPagerAdapter {
return pageTitles[position];
}
- View getTabView(int position, ViewGroup root) {
+ public View getTabView(int position, ViewGroup root) {
View view = LayoutInflater.from(context).inflate(R.layout.tab_account, root, false);
TextView title = (TextView) view.findViewById(R.id.title);
title.setText(pageTitles[position]);
@@ -92,7 +96,7 @@ class AccountPagerAdapter extends FragmentPagerAdapter {
super.destroyItem(container, position, object);
}
- List getRegisteredFragments() {
+ public List getRegisteredFragments() {
return registeredFragments;
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/TimelinePagerAdapter.java b/app/src/main/java/com/keylesspalace/tusky/pager/TimelinePagerAdapter.java
similarity index 87%
rename from app/src/main/java/com/keylesspalace/tusky/TimelinePagerAdapter.java
rename to app/src/main/java/com/keylesspalace/tusky/pager/TimelinePagerAdapter.java
index 03dc88dc9..826860413 100644
--- a/app/src/main/java/com/keylesspalace/tusky/TimelinePagerAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/pager/TimelinePagerAdapter.java
@@ -13,31 +13,34 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.pager;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.view.ViewGroup;
+import com.keylesspalace.tusky.fragment.NotificationsFragment;
+import com.keylesspalace.tusky.fragment.TimelineFragment;
+
import java.util.ArrayList;
import java.util.List;
-class TimelinePagerAdapter extends FragmentPagerAdapter {
+public class TimelinePagerAdapter extends FragmentPagerAdapter {
private int currentFragmentIndex;
private List registeredFragments;
- TimelinePagerAdapter(FragmentManager manager) {
+ public TimelinePagerAdapter(FragmentManager manager) {
super(manager);
currentFragmentIndex = 0;
registeredFragments = new ArrayList<>();
}
- Fragment getCurrentFragment() {
+ public Fragment getCurrentFragment() {
return registeredFragments.get(currentFragmentIndex);
}
- List getRegisteredFragments() {
+ public List getRegisteredFragments() {
return registeredFragments;
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/TuskyTileService.java b/app/src/main/java/com/keylesspalace/tusky/service/TuskyTileService.java
similarity index 94%
rename from app/src/main/java/com/keylesspalace/tusky/TuskyTileService.java
rename to app/src/main/java/com/keylesspalace/tusky/service/TuskyTileService.java
index 720e99664..677899286 100644
--- a/app/src/main/java/com/keylesspalace/tusky/TuskyTileService.java
+++ b/app/src/main/java/com/keylesspalace/tusky/service/TuskyTileService.java
@@ -13,12 +13,14 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.service;
import android.annotation.TargetApi;
import android.content.Intent;
import android.service.quicksettings.TileService;
+import com.keylesspalace.tusky.ComposeActivity;
+
/**
* Small Addition that adds in a QuickSettings tile that opens the Compose activity when clicked
* Created by ztepps on 4/3/17.
diff --git a/app/src/main/java/com/keylesspalace/tusky/Assert.java b/app/src/main/java/com/keylesspalace/tusky/util/Assert.java
similarity index 85%
rename from app/src/main/java/com/keylesspalace/tusky/Assert.java
rename to app/src/main/java/com/keylesspalace/tusky/util/Assert.java
index e185cfb03..c976184e5 100644
--- a/app/src/main/java/com/keylesspalace/tusky/Assert.java
+++ b/app/src/main/java/com/keylesspalace/tusky/util/Assert.java
@@ -13,13 +13,15 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.util;
+
+import com.keylesspalace.tusky.BuildConfig;
/** Android Studio complains about built-in assertions so this is an alternative. */
-class Assert {
+public class Assert {
private static boolean ENABLED = BuildConfig.DEBUG;
- static void expect(boolean expression) {
+ public static void expect(boolean expression) {
if (ENABLED && !expression) {
throw new AssertionError();
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/ConversationLineItemDecoration.java b/app/src/main/java/com/keylesspalace/tusky/util/ConversationLineItemDecoration.java
similarity index 89%
rename from app/src/main/java/com/keylesspalace/tusky/ConversationLineItemDecoration.java
rename to app/src/main/java/com/keylesspalace/tusky/util/ConversationLineItemDecoration.java
index e5fb8562d..dab773c35 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ConversationLineItemDecoration.java
+++ b/app/src/main/java/com/keylesspalace/tusky/util/ConversationLineItemDecoration.java
@@ -13,19 +13,17 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.util;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
-import android.util.TypedValue;
import android.view.View;
-import static android.util.TypedValue.COMPLEX_UNIT_DIP;
+import com.keylesspalace.tusky.R;
-class ConversationLineItemDecoration extends RecyclerView.ItemDecoration {
+public class ConversationLineItemDecoration extends RecyclerView.ItemDecoration {
private final Context mContext;
private final Drawable mDivider;
diff --git a/app/src/main/java/com/keylesspalace/tusky/CountUpDownLatch.java b/app/src/main/java/com/keylesspalace/tusky/util/CountUpDownLatch.java
similarity index 78%
rename from app/src/main/java/com/keylesspalace/tusky/CountUpDownLatch.java
rename to app/src/main/java/com/keylesspalace/tusky/util/CountUpDownLatch.java
index e0d95438b..5791cf606 100644
--- a/app/src/main/java/com/keylesspalace/tusky/CountUpDownLatch.java
+++ b/app/src/main/java/com/keylesspalace/tusky/util/CountUpDownLatch.java
@@ -13,26 +13,26 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.util;
-class CountUpDownLatch {
+public class CountUpDownLatch {
private int count;
- CountUpDownLatch() {
+ public CountUpDownLatch() {
this.count = 0;
}
- synchronized void countDown() {
+ public synchronized void countDown() {
count--;
notifyAll();
}
- synchronized void countUp() {
+ public synchronized void countUp() {
count++;
notifyAll();
}
- synchronized void await() throws InterruptedException {
+ public synchronized void await() throws InterruptedException {
while (count != 0) {
wait();
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/CustomTabURLSpan.java b/app/src/main/java/com/keylesspalace/tusky/util/CustomTabURLSpan.java
similarity index 96%
rename from app/src/main/java/com/keylesspalace/tusky/CustomTabURLSpan.java
rename to app/src/main/java/com/keylesspalace/tusky/util/CustomTabURLSpan.java
index 7af9a84fe..bef07fcf1 100644
--- a/app/src/main/java/com/keylesspalace/tusky/CustomTabURLSpan.java
+++ b/app/src/main/java/com/keylesspalace/tusky/util/CustomTabURLSpan.java
@@ -1,4 +1,4 @@
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.util;
import android.content.ActivityNotFoundException;
import android.content.Context;
@@ -11,6 +11,8 @@ import android.support.v4.content.ContextCompat;
import android.text.style.URLSpan;
import android.view.View;
+import com.keylesspalace.tusky.R;
+
class CustomTabURLSpan extends URLSpan {
CustomTabURLSpan(String url) {
super(url);
diff --git a/app/src/main/java/com/keylesspalace/tusky/CustomTabsHelper.java b/app/src/main/java/com/keylesspalace/tusky/util/CustomTabsHelper.java
similarity index 99%
rename from app/src/main/java/com/keylesspalace/tusky/CustomTabsHelper.java
rename to app/src/main/java/com/keylesspalace/tusky/util/CustomTabsHelper.java
index 456708638..f5add0475 100644
--- a/app/src/main/java/com/keylesspalace/tusky/CustomTabsHelper.java
+++ b/app/src/main/java/com/keylesspalace/tusky/util/CustomTabsHelper.java
@@ -1,4 +1,4 @@
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.util;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
diff --git a/app/src/main/java/com/keylesspalace/tusky/DateUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/DateUtils.java
similarity index 92%
rename from app/src/main/java/com/keylesspalace/tusky/DateUtils.java
rename to app/src/main/java/com/keylesspalace/tusky/util/DateUtils.java
index 16137f87c..b2cc49a2c 100644
--- a/app/src/main/java/com/keylesspalace/tusky/DateUtils.java
+++ b/app/src/main/java/com/keylesspalace/tusky/util/DateUtils.java
@@ -13,12 +13,12 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.util;
-class DateUtils {
+public class DateUtils {
/* This is a rough duplicate of android.text.format.DateUtils.getRelativeTimeSpanString,
* but even with the FORMAT_ABBREV_RELATIVE flag it wasn't abbreviating enough. */
- static String getRelativeTimeSpanString(long then, long now) {
+ public static String getRelativeTimeSpanString(long then, long now) {
final long MINUTE = 60;
final long HOUR = 60 * MINUTE;
final long DAY = 24 * HOUR;
diff --git a/app/src/main/java/com/keylesspalace/tusky/DownsizeImageTask.java b/app/src/main/java/com/keylesspalace/tusky/util/DownsizeImageTask.java
similarity index 97%
rename from app/src/main/java/com/keylesspalace/tusky/DownsizeImageTask.java
rename to app/src/main/java/com/keylesspalace/tusky/util/DownsizeImageTask.java
index 56d50ed99..0cbc35a83 100644
--- a/app/src/main/java/com/keylesspalace/tusky/DownsizeImageTask.java
+++ b/app/src/main/java/com/keylesspalace/tusky/util/DownsizeImageTask.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.util;
import android.content.ContentResolver;
import android.graphics.Bitmap;
@@ -31,13 +31,13 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
-class DownsizeImageTask extends AsyncTask {
+public class DownsizeImageTask extends AsyncTask {
private int sizeLimit;
private ContentResolver contentResolver;
private Listener listener;
private List resultList;
- DownsizeImageTask(int sizeLimit, ContentResolver contentResolver, Listener listener) {
+ public DownsizeImageTask(int sizeLimit, ContentResolver contentResolver, Listener listener) {
this.sizeLimit = sizeLimit;
this.contentResolver = contentResolver;
this.listener = listener;
@@ -219,7 +219,7 @@ class DownsizeImageTask extends AsyncTask {
super.onPostExecute(successful);
}
- interface Listener {
+ public interface Listener {
void onSuccess(List contentList);
void onFailure();
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/EditTextTyped.java b/app/src/main/java/com/keylesspalace/tusky/util/EditTextTyped.java
similarity index 98%
rename from app/src/main/java/com/keylesspalace/tusky/EditTextTyped.java
rename to app/src/main/java/com/keylesspalace/tusky/util/EditTextTyped.java
index 367180a2b..c32f666f4 100644
--- a/app/src/main/java/com/keylesspalace/tusky/EditTextTyped.java
+++ b/app/src/main/java/com/keylesspalace/tusky/util/EditTextTyped.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.util;
import android.content.Context;
import android.support.v13.view.inputmethod.EditorInfoCompat;
diff --git a/app/src/main/java/com/keylesspalace/tusky/EndlessOnScrollListener.java b/app/src/main/java/com/keylesspalace/tusky/util/EndlessOnScrollListener.java
similarity index 90%
rename from app/src/main/java/com/keylesspalace/tusky/EndlessOnScrollListener.java
rename to app/src/main/java/com/keylesspalace/tusky/util/EndlessOnScrollListener.java
index 9c91a89a1..0b149de05 100644
--- a/app/src/main/java/com/keylesspalace/tusky/EndlessOnScrollListener.java
+++ b/app/src/main/java/com/keylesspalace/tusky/util/EndlessOnScrollListener.java
@@ -13,12 +13,12 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.util;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
-abstract class EndlessOnScrollListener extends RecyclerView.OnScrollListener {
+public abstract class EndlessOnScrollListener extends RecyclerView.OnScrollListener {
private static final int VISIBLE_THRESHOLD = 15;
private int currentPage;
private int previousTotalItemCount;
@@ -26,7 +26,7 @@ abstract class EndlessOnScrollListener extends RecyclerView.OnScrollListener {
private int startingPageIndex;
private LinearLayoutManager layoutManager;
- EndlessOnScrollListener(LinearLayoutManager layoutManager) {
+ public EndlessOnScrollListener(LinearLayoutManager layoutManager) {
this.layoutManager = layoutManager;
currentPage = 0;
previousTotalItemCount = 0;
@@ -56,7 +56,7 @@ abstract class EndlessOnScrollListener extends RecyclerView.OnScrollListener {
}
}
- void reset() {
+ public void reset() {
currentPage = startingPageIndex;
previousTotalItemCount = 0;
loading = true;
diff --git a/app/src/main/java/com/keylesspalace/tusky/FlowLayout.java b/app/src/main/java/com/keylesspalace/tusky/util/FlowLayout.java
similarity index 98%
rename from app/src/main/java/com/keylesspalace/tusky/FlowLayout.java
rename to app/src/main/java/com/keylesspalace/tusky/util/FlowLayout.java
index 44616643a..cf1f9e686 100644
--- a/app/src/main/java/com/keylesspalace/tusky/FlowLayout.java
+++ b/app/src/main/java/com/keylesspalace/tusky/util/FlowLayout.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.util;
import android.content.Context;
import android.content.res.TypedArray;
@@ -21,6 +21,8 @@ import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
+import com.keylesspalace.tusky.R;
+
public class FlowLayout extends ViewGroup {
private int paddingHorizontal; // internal padding between child views
private int paddingVertical; //
diff --git a/app/src/main/java/com/keylesspalace/tusky/HtmlUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/HtmlUtils.java
similarity index 98%
rename from app/src/main/java/com/keylesspalace/tusky/HtmlUtils.java
rename to app/src/main/java/com/keylesspalace/tusky/util/HtmlUtils.java
index d37b4cd20..eee9bb44d 100644
--- a/app/src/main/java/com/keylesspalace/tusky/HtmlUtils.java
+++ b/app/src/main/java/com/keylesspalace/tusky/util/HtmlUtils.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.util;
import android.os.Build;
import android.text.Html;
diff --git a/app/src/main/java/com/keylesspalace/tusky/IOUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/IOUtils.java
similarity index 85%
rename from app/src/main/java/com/keylesspalace/tusky/IOUtils.java
rename to app/src/main/java/com/keylesspalace/tusky/util/IOUtils.java
index 76e53b822..2b17eeeb1 100644
--- a/app/src/main/java/com/keylesspalace/tusky/IOUtils.java
+++ b/app/src/main/java/com/keylesspalace/tusky/util/IOUtils.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.util;
import android.support.annotation.Nullable;
@@ -21,8 +21,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-class IOUtils {
- static void closeQuietly(@Nullable InputStream stream) {
+public class IOUtils {
+ public static void closeQuietly(@Nullable InputStream stream) {
try {
if (stream != null) {
stream.close();
@@ -32,7 +32,7 @@ class IOUtils {
}
}
- static void closeQuietly(@Nullable OutputStream stream) {
+ public static void closeQuietly(@Nullable OutputStream stream) {
try {
if (stream != null) {
stream.close();
diff --git a/app/src/main/java/com/keylesspalace/tusky/LinkHelper.java b/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.java
similarity index 87%
rename from app/src/main/java/com/keylesspalace/tusky/LinkHelper.java
rename to app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.java
index ba9a591fe..46ddd5f17 100644
--- a/app/src/main/java/com/keylesspalace/tusky/LinkHelper.java
+++ b/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.java
@@ -13,9 +13,8 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.util;
-import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@@ -26,14 +25,13 @@ import android.view.View;
import android.widget.TextView;
import com.keylesspalace.tusky.entity.Status;
+import com.keylesspalace.tusky.interfaces.LinkListener;
-class LinkHelper {
- static void setClickableText(TextView view, Spanned content,
- @Nullable Status.Mention[] mentions,
- final LinkListener listener) {
+public class LinkHelper {
+ public static void setClickableText(TextView view, Spanned content,
+ @Nullable Status.Mention[] mentions, boolean useCustomTabs,
+ final LinkListener listener) {
SpannableStringBuilder builder = new SpannableStringBuilder(content);
- boolean useCustomTabs = PreferenceManager.getDefaultSharedPreferences(view.getContext())
- .getBoolean("customTabs", true);
URLSpan[] urlSpans = content.getSpans(0, content.length(), URLSpan.class);
for (URLSpan span : urlSpans) {
int start = builder.getSpanStart(span);
diff --git a/app/src/main/java/com/keylesspalace/tusky/Log.java b/app/src/main/java/com/keylesspalace/tusky/util/Log.java
similarity index 95%
rename from app/src/main/java/com/keylesspalace/tusky/Log.java
rename to app/src/main/java/com/keylesspalace/tusky/util/Log.java
index d36544eea..26d3d85fe 100644
--- a/app/src/main/java/com/keylesspalace/tusky/Log.java
+++ b/app/src/main/java/com/keylesspalace/tusky/util/Log.java
@@ -13,7 +13,9 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+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 {
diff --git a/app/src/main/java/com/keylesspalace/tusky/NotificationClearBroadcastReceiver.java b/app/src/main/java/com/keylesspalace/tusky/util/NotificationClearBroadcastReceiver.java
similarity index 97%
rename from app/src/main/java/com/keylesspalace/tusky/NotificationClearBroadcastReceiver.java
rename to app/src/main/java/com/keylesspalace/tusky/util/NotificationClearBroadcastReceiver.java
index 9508dac6a..d18902e36 100644
--- a/app/src/main/java/com/keylesspalace/tusky/NotificationClearBroadcastReceiver.java
+++ b/app/src/main/java/com/keylesspalace/tusky/util/NotificationClearBroadcastReceiver.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.util;
import android.content.BroadcastReceiver;
import android.content.Context;
diff --git a/app/src/main/java/com/keylesspalace/tusky/NotificationMaker.java b/app/src/main/java/com/keylesspalace/tusky/util/NotificationMaker.java
similarity index 97%
rename from app/src/main/java/com/keylesspalace/tusky/NotificationMaker.java
rename to app/src/main/java/com/keylesspalace/tusky/util/NotificationMaker.java
index 8c7bfb85d..e5bc70033 100644
--- a/app/src/main/java/com/keylesspalace/tusky/NotificationMaker.java
+++ b/app/src/main/java/com/keylesspalace/tusky/util/NotificationMaker.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.util;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -29,6 +29,8 @@ import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
+import com.keylesspalace.tusky.MainActivity;
+import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Notification;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Target;
@@ -36,8 +38,8 @@ import com.squareup.picasso.Target;
import org.json.JSONArray;
import org.json.JSONException;
-class NotificationMaker {
- static void make(final Context context, final int notifyId, Notification body) {
+public class NotificationMaker {
+ public static void make(final Context context, final int notifyId, Notification body) {
final SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(context);
final SharedPreferences notificationPreferences = context.getSharedPreferences(
diff --git a/app/src/main/java/com/keylesspalace/tusky/OkHttpUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.java
similarity index 97%
rename from app/src/main/java/com/keylesspalace/tusky/OkHttpUtils.java
rename to app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.java
index a821df4d8..c90002d20 100644
--- a/app/src/main/java/com/keylesspalace/tusky/OkHttpUtils.java
+++ b/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.java
@@ -13,11 +13,13 @@
* You should have received a copy of the GNU Lesser General Public License along with Tusky. If
* not, see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.util;
import android.os.Build;
import android.support.annotation.NonNull;
+import com.keylesspalace.tusky.BuildConfig;
+
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
@@ -94,7 +96,7 @@ public class OkHttpUtils {
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Request requestWithUserAgent = originalRequest.newBuilder()
- .header("User-Agent", "Tusky/"+BuildConfig.VERSION_NAME+" Android/"+Build.VERSION.RELEASE)
+ .header("User-Agent", "Tusky/"+ BuildConfig.VERSION_NAME+" Android/"+Build.VERSION.RELEASE)
.build();
return chain.proceed(requestWithUserAgent);
}
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..f3df738fe
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/util/PushNotificationClient.java
@@ -0,0 +1,237 @@
+package com.keylesspalace.tusky.util;
+
+import android.app.NotificationManager;
+import android.content.Context;
+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 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.InputStream;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+
+import static android.content.Context.NOTIFICATION_SERVICE;
+
+public class PushNotificationClient {
+ private static final String TAG = "PushNotificationClient";
+ private static final int NOTIFY_ID = 666;
+
+ 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 ArrayDeque queuedActions;
+ 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();
+ mqttAndroidClient = new MqttAndroidClient(applicationContext, serverUri, clientId);
+ mqttAndroidClient.setCallback(new MqttCallbackExtended() {
+ @Override
+ public void connectComplete(boolean reconnect, String serverURI) {
+ if (reconnect) {
+ flushQueuedActions();
+ for (String topic : subscribedTopics) {
+ subscribeToTopic(topic);
+ }
+ }
+ }
+
+ @Override
+ public void connectionLost(Throwable cause) {
+ onConnectionLost();
+ }
+
+ @Override
+ public void messageArrived(String topic, MqttMessage message) throws Exception {
+ onMessageReceived(applicationContext, new String(message.getPayload()));
+ }
+
+ @Override
+ public void deliveryComplete(IMqttDeliveryToken token) {
+ // This client is read-only, so this is unused.
+ }
+ });
+ }
+
+ 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(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 {
+ options.setSocketFactory(mqttAndroidClient.getSSLSocketFactory(keystore, password));
+ } finally {
+ IOUtils.closeQuietly(keystore);
+ }
+ mqttAndroidClient.connect(options).setActionCallback(new IMqttActionListener() {
+ @Override
+ public void onSuccess(IMqttToken asyncActionToken) {
+ DisconnectedBufferOptions bufferOptions = new DisconnectedBufferOptions();
+ bufferOptions.setBufferEnabled(true);
+ bufferOptions.setBufferSize(100);
+ bufferOptions.setPersistBuffer(false);
+ bufferOptions.setDeleteOldestMessages(false);
+ mqttAndroidClient.setBufferOpts(bufferOptions);
+ onConnectionSuccess();
+ flushQueuedActions();
+ }
+
+ @Override
+ public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
+ Log.e(TAG, "An exception occurred while connecting. " + exception.getMessage()
+ + " " + exception.getCause());
+ onConnectionFailure();
+ }
+ });
+ } catch (MqttException e) {
+ Log.e(TAG, "An exception occurred while connecpting. " + 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.");
+ }
+
+ /** Disconnect from the MQTT broker. */
+ public void disconnect() {
+ if (!mqttAndroidClient.isConnected()) {
+ queuedActions.add(new QueuedAction(QueuedAction.Type.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(final String topic) {
+ if (!mqttAndroidClient.isConnected()) {
+ queuedActions.add(new QueuedAction(QueuedAction.Type.SUBSCRIBE, topic));
+ return;
+ }
+ try {
+ mqttAndroidClient.subscribe(topic, 0, null, new IMqttActionListener() {
+ @Override
+ public void onSuccess(IMqttToken asyncActionToken) {
+ subscribedTopics.add(topic);
+ 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(String topic) {
+ if (!mqttAndroidClient.isConnected()) {
+ queuedActions.add(new QueuedAction(QueuedAction.Type.UNSUBSCRIBE, topic));
+ return;
+ }
+ try {
+ mqttAndroidClient.unsubscribe(topic);
+ subscribedTopics.remove(topic);
+ } catch (MqttException e) {
+ Log.e(TAG, "An exception occurred while unsubscribing." + e.getMessage());
+ onConnectionFailure();
+ }
+ }
+
+ private void onMessageReceived(final Context context, String message) {
+ 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);
+
+ NotificationMaker.make(context, NOTIFY_ID, notification);
+ }
+
+ 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/java/com/keylesspalace/tusky/RoundedTransformation.java b/app/src/main/java/com/keylesspalace/tusky/util/RoundedTransformation.java
similarity index 98%
rename from app/src/main/java/com/keylesspalace/tusky/RoundedTransformation.java
rename to app/src/main/java/com/keylesspalace/tusky/util/RoundedTransformation.java
index bd4192b51..ff153d687 100644
--- a/app/src/main/java/com/keylesspalace/tusky/RoundedTransformation.java
+++ b/app/src/main/java/com/keylesspalace/tusky/util/RoundedTransformation.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.util;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
diff --git a/app/src/main/java/com/keylesspalace/tusky/SpanUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.java
similarity index 97%
rename from app/src/main/java/com/keylesspalace/tusky/SpanUtils.java
rename to app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.java
index 09936e457..81a82d2b8 100644
--- a/app/src/main/java/com/keylesspalace/tusky/SpanUtils.java
+++ b/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.java
@@ -13,13 +13,13 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.util;
import android.text.Spannable;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
-class SpanUtils {
+public class SpanUtils {
private static class FindCharsResult {
int charIndex;
int stringIndex;
@@ -94,7 +94,7 @@ class SpanUtils {
return length;
}
- static void highlightSpans(Spannable text, int colour) {
+ public static void highlightSpans(Spannable text, int colour) {
// Strip all existing colour spans.
int n = text.length();
ForegroundColorSpan[] oldSpans = text.getSpans(0, n, ForegroundColorSpan.class);
diff --git a/app/src/main/java/com/keylesspalace/tusky/ThemeUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java
similarity index 81%
rename from app/src/main/java/com/keylesspalace/tusky/ThemeUtils.java
rename to app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java
index 6e7a908dd..310341faf 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ThemeUtils.java
+++ b/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java
@@ -13,7 +13,7 @@
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see . */
-package com.keylesspalace.tusky;
+package com.keylesspalace.tusky.util;
import android.content.Context;
import android.graphics.Color;
@@ -26,8 +26,8 @@ import android.support.v4.content.ContextCompat;
import android.util.TypedValue;
import android.widget.ImageView;
-class ThemeUtils {
- static Drawable getDrawable(Context context, @AttrRes int attribute,
+public class ThemeUtils {
+ public static Drawable getDrawable(Context context, @AttrRes int attribute,
@DrawableRes int fallbackDrawable) {
TypedValue value = new TypedValue();
@DrawableRes int resourceId;
@@ -39,7 +39,7 @@ class ThemeUtils {
return ContextCompat.getDrawable(context, resourceId);
}
- static @DrawableRes int getDrawableId(Context context, @AttrRes int attribute,
+ public static @DrawableRes int getDrawableId(Context context, @AttrRes int attribute,
@DrawableRes int fallbackDrawableId) {
TypedValue value = new TypedValue();
if (context.getTheme().resolveAttribute(attribute, value, true)) {
@@ -49,7 +49,7 @@ class ThemeUtils {
}
}
- static @ColorInt int getColor(Context context, @AttrRes int attribute) {
+ public static @ColorInt int getColor(Context context, @AttrRes int attribute) {
TypedValue value = new TypedValue();
if (context.getTheme().resolveAttribute(attribute, value, true)) {
return value.data;
@@ -58,11 +58,11 @@ class ThemeUtils {
}
}
- static void setImageViewTint(ImageView view, @AttrRes int attribute) {
+ public static void setImageViewTint(ImageView view, @AttrRes int attribute) {
view.setColorFilter(getColor(view.getContext(), attribute), PorterDuff.Mode.SRC_IN);
}
- static void setDrawableTint(Context context, Drawable drawable, @AttrRes int attribute) {
+ public static void setDrawableTint(Context context, Drawable drawable, @AttrRes int attribute) {
drawable.setColorFilter(getColor(context, attribute), PorterDuff.Mode.SRC_IN);
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/util/TimelineReceiver.java b/app/src/main/java/com/keylesspalace/tusky/util/TimelineReceiver.java
new file mode 100644
index 000000000..f6acc0d9c
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/util/TimelineReceiver.java
@@ -0,0 +1,41 @@
+package com.keylesspalace.tusky.util;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import com.keylesspalace.tusky.adapter.TimelineAdapter;
+import com.keylesspalace.tusky.fragment.TimelineFragment;
+
+public class TimelineReceiver extends BroadcastReceiver {
+ public static final class Types {
+ public static final String UNFOLLOW_ACCOUNT = "UNFOLLOW_ACCOUNT";
+ public static final String BLOCK_ACCOUNT = "BLOCK_ACCOUNT";
+ public static final String MUTE_ACCOUNT = "MUTE_ACCOUNT";
+ }
+
+ TimelineAdapter adapter;
+
+ public TimelineReceiver(TimelineAdapter adapter) {
+ super();
+ this.adapter = adapter;
+ }
+
+ @Override
+ public void onReceive(Context context, final Intent intent) {
+ String id = intent.getStringExtra("id");
+ adapter.removeAllByAccountId(id);
+ }
+
+ public static IntentFilter getFilter(TimelineFragment.Kind kind) {
+ IntentFilter intentFilter = new IntentFilter();
+ if (kind == TimelineFragment.Kind.HOME) {
+ intentFilter.addAction(Types.UNFOLLOW_ACCOUNT);
+ }
+ intentFilter.addAction(Types.BLOCK_ACCOUNT);
+ intentFilter.addAction(Types.MUTE_ACCOUNT);
+
+ return intentFilter;
+ }
+}
diff --git a/app/src/main/res/drawable/account_header_default.png b/app/src/main/res/drawable/account_header_default.png
deleted file mode 100644
index c4e44ad25..000000000
Binary files a/app/src/main/res/drawable/account_header_default.png and /dev/null differ
diff --git a/app/src/main/res/drawable/account_header_missing.xml b/app/src/main/res/drawable/account_header_default.xml
similarity index 100%
rename from app/src/main/res/drawable/account_header_missing.xml
rename to app/src/main/res/drawable/account_header_default.xml
diff --git a/app/src/main/res/drawable/background_splash.xml b/app/src/main/res/drawable/background_splash.xml
new file mode 100644
index 000000000..548f87bad
--- /dev/null
+++ b/app/src/main/res/drawable/background_splash.xml
@@ -0,0 +1,14 @@
+
+
+ -
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/elephant_friend.png b/app/src/main/res/drawable/elephant_friend.png
index 3c5145ba9..a4e5e3e61 100644
Binary files a/app/src/main/res/drawable/elephant_friend.png and b/app/src/main/res/drawable/elephant_friend.png differ
diff --git a/app/src/main/res/drawable/ic_file_download_black_24dp.xml b/app/src/main/res/drawable/ic_file_download_black_24dp.xml
new file mode 100644
index 000000000..f5f7221a9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_file_download_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/splash_pattern.png b/app/src/main/res/drawable/splash_pattern.png
new file mode 100644
index 000000000..dcc6f6476
Binary files /dev/null and b/app/src/main/res/drawable/splash_pattern.png differ
diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml
new file mode 100644
index 000000000..c36fd02be
--- /dev/null
+++ b/app/src/main/res/layout/activity_about.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_account.xml b/app/src/main/res/layout/activity_account.xml
index aa2c996bc..ba7e8c41d 100644
--- a/app/src/main/res/layout/activity_account.xml
+++ b/app/src/main/res/layout/activity_account.xml
@@ -1,26 +1,25 @@
-
+ android:fitsSystemWindows="true"
+ android:orientation="vertical">
+ android:theme="@style/AppTheme.Account.AppBarLayout">
+ android:background="@drawable/account_header_default"
+ android:contentDescription="@null"
+ android:fitsSystemWindows="true"
+ android:scaleType="centerCrop"
+ app:layout_collapseMode="pin" />
+ android:layout_height="wrap_content"
+ android:background="@drawable/account_header_gradient"
+ android:orientation="vertical"
+ android:paddingTop="?attr/actionBarSize"
+ app:layout_collapseMode="parallax">
+ android:paddingTop="16dp">
+ android:src="@drawable/avatar_default"
+ app:shadow="true" />
+ android:orientation="vertical">
+ android:textSize="18sp"
+ android:textStyle="normal|bold" />
+
+ android:maxLines="1"
+ android:textColor="?android:textColorSecondary" />
+ android:contentDescription="@string/description_account_locked"
+ android:tint="?android:textColorSecondary"
+ android:visibility="gone"
+ app:srcCompat="@drawable/reblog_disabled_light" />
+ android:paddingTop="10dp"
+ android:textColor="?android:textColorTertiary" />
@@ -125,9 +127,9 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
- android:background="@android:color/transparent"
- android:layout_gravity="top"
android:layout_alignParentTop="true"
+ android:layout_gravity="top"
+ android:background="@android:color/transparent"
app:layout_collapseMode="pin"
app:popupTheme="?attr/account_toolbar_popup_theme" />
@@ -136,16 +138,16 @@
+ android:layout_height="wrap_content"
+ app:tabBackground="?android:colorBackground">
+ android:visibility="visible"
+ app:layout_anchor="@id/tab_layout"
+ app:layout_anchorGravity="bottom" />
+ android:contentDescription="@string/action_follow"
+ app:srcCompat="@drawable/ic_person_add_24dp" />
diff --git a/app/src/main/res/layout/activity_compose.xml b/app/src/main/res/layout/activity_compose.xml
index 9807e944b..0252cce7b 100644
--- a/app/src/main/res/layout/activity_compose.xml
+++ b/app/src/main/res/layout/activity_compose.xml
@@ -54,7 +54,7 @@
android:paddingLeft="16dp"
android:paddingRight="16dp">
-
-
-
-
-
+
-
+ android:layout_width="250dp">
+
+
-
+
-
+
+
+
+
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+
+
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml
deleted file mode 100644
index 210118930..000000000
--- a/app/src/main/res/layout/activity_splash.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_view_video.xml b/app/src/main/res/layout/activity_view_video.xml
index 79b1207e7..7a15f215d 100644
--- a/app/src/main/res/layout/activity_view_video.xml
+++ b/app/src/main/res/layout/activity_view_video.xml
@@ -14,6 +14,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true" />
+
diff --git a/app/src/main/res/layout/fragment_view_media.xml b/app/src/main/res/layout/fragment_view_media.xml
index ea1918fca..0faf6fe6d 100644
--- a/app/src/main/res/layout/fragment_view_media.xml
+++ b/app/src/main/res/layout/fragment_view_media.xml
@@ -1,12 +1,30 @@
-
-
+
+
-
\ No newline at end of file
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_status.xml b/app/src/main/res/layout/item_status.xml
index 54a67ea7a..077a1552c 100644
--- a/app/src/main/res/layout/item_status.xml
+++ b/app/src/main/res/layout/item_status.xml
@@ -95,7 +95,7 @@
-
-
+
+ android:title="@string/action_share">
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/view_media_tooblar.xml b/app/src/main/res/menu/view_media_tooblar.xml
new file mode 100644
index 000000000..1de914f5f
--- /dev/null
+++ b/app/src/main/res/menu/view_media_tooblar.xml
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_logo.png b/app/src/main/res/mipmap-hdpi/ic_logo.png
index 249bd6e17..d6eda0b2a 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_logo.png and b/app/src/main/res/mipmap-hdpi/ic_logo.png differ
diff --git a/app/src/main/res/mipmap-ldpi/ic_logo.png b/app/src/main/res/mipmap-ldpi/ic_logo.png
index 4f6885797..200de0489 100644
Binary files a/app/src/main/res/mipmap-ldpi/ic_logo.png and b/app/src/main/res/mipmap-ldpi/ic_logo.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_logo.png b/app/src/main/res/mipmap-mdpi/ic_logo.png
index 579902fbe..683db8b41 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_logo.png and b/app/src/main/res/mipmap-mdpi/ic_logo.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_logo.png b/app/src/main/res/mipmap-xhdpi/ic_logo.png
index 0fb7aa16b..ce42064e3 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_logo.png and b/app/src/main/res/mipmap-xhdpi/ic_logo.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_logo.png b/app/src/main/res/mipmap-xxhdpi/ic_logo.png
index ff9e734e6..a514b484c 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_logo.png and b/app/src/main/res/mipmap-xxhdpi/ic_logo.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_logo.png b/app/src/main/res/mipmap-xxxhdpi/ic_logo.png
index e9a98a481..b1dcf4b64 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_logo.png and b/app/src/main/res/mipmap-xxxhdpi/ic_logo.png differ
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 0e508b5b7..8e833ade5 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -1,21 +1,21 @@
وقع هناك خطأ.
-
+ لا يجب أن يترك فارغا.
اسم النطاق غير صالح
اخفقت المصادقة مع مثيل الخادم هذا.
لم يتم العثور على متصفح قابل للإستعمال.
تم رفض التصريح.
-
+ المنشور طويل جدا !
يجب أن يكون حجم الملف أقل من 4 ميغابايت.
لا يمكن رفع هذا النوع من الملفات.
تعذر فتح ذاك الملف.
-
+ لا يمكنك إرفاق كلا من الصور و الفيديوهات في نفس المنشور.
اخفقت عملية الرفع.
@@ -26,13 +26,16 @@
الخيط
#%s
المشاركات
- يتبع
- المتابعون
+ المتابَعون
+ المتابِعون
المفضلة
- المستخدمون المحظورون
+ الحسابات المكتومة
+ الحسابات المحظورة
+ طلبات المتابعة
+ عدل ملفك الشخصي
\@%s
- %s عزز
+ %s رقّي
محتوى حساس
اضغط للعرض
اعرض أكثر
@@ -41,7 +44,7 @@
نهاية الحالات
نهاية الاشعارات
نهاية الحسابات
-
+ لا توجد تبويقات هنا بعد. اسحب للأسفل للتحديث !
%s عزز تبويقك
%s أعجب بتبويقك
@@ -51,7 +54,7 @@
تعليقات إضافية ؟
أجب
- عزز
+ رقّي
تفضيل
المزيد
حرر
@@ -59,14 +62,14 @@
خروج
إتبع
إلغاء التتبع
- حضر
+ قم بحظره
إلغاء الحظر
أبلغ
إحذف
تبويق
- بَوِّق
+ بوّق
إعادة المحاولة
-
+ ضع علامة على الوسيط باعتباره حسّاس
اخفي النص وراء تحذير
موافق
إلغاء
@@ -81,7 +84,7 @@
إفتح في متصفح
ارسل
إضافة وسائط
-
+ أخذ صورة
شارك
أكتم
إلغاء الكتم
@@ -90,25 +93,30 @@
خيارات
إفتح الدرج
إمسح
-
-
+ إحفظ
+ تعديل الملف الشخصي
+ إلغاء
+ موافقة
+ رفض
- شارك رابط التبويق إلى ...
-
+ شارك رابط التبويق على ...
+ شارك التبويق على …
ابحث عن حسابات ...
بَوِّق
تم الإرسال !
+ تم فك الحجب عن الحساب
+ تم فك الكتم عن الحساب
أي سيرفر ؟
ما الجديد ؟
تحذير عن المحتوى
-
-
+ الإسم العلني
+ السيرة
-
-
+ الصورة الرمزية
+ رأس الصفحة
ماذا نعني بمثيل الخادم ؟
@@ -123,12 +131,12 @@
تتمة رفع الوسائط
جاري الرفع ...
-
+ تنزيل
-
-
-
-
+
+
+
+
الاشعارات
تعديل الاشعارات
@@ -140,13 +148,13 @@
أخطرني عندما
يشار إلي
يتبعني أحد
- تعزز وتدفع منشوراتي
+ تُرقّى منشوراتي
يعجب أحد ما بمنشوراتي
المظهر
إستخدم سمةً فاتحة اللون
المتصفح
إخفاء زر المتابعة أثناء تمرير الصفحة
-
+ إخفاء زر المتابعة عند التمرير إلى أسفل
%s أشار إليك
%1$s, %2$s, %3$s و %4$d أخرى
@@ -155,6 +163,8 @@
%d تفاعلات جديدة
حساب مقفل
-
-
+ شارك محتوى التبويق
+ شارك الرابط إلى التبويق
+
+ طلب متابعة
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 4ab3a6a0b..3cd0c7422 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -75,7 +75,7 @@
Profil
Einstellungen
Favoriten
- Blockierte Nutzer
+ Blockierte Accounts
Thread
Medien
Im Browser öffnen
@@ -93,7 +93,7 @@
- Teile Toot-URL zu…
+ Toot-Link teilen
Suche Accounts…
@@ -125,7 +125,6 @@
Öffentlich sichtbar
Öffentlich sichtbar, aber nicht in der öffentlichen Timeline
Nur für Follower und Erwähnte sichtbar
-
Benachrichtigungen
Benachrichtigungseinstellungen
@@ -152,6 +151,29 @@
%d neue Interaktionen
Gesperrter Account
-
-
+ Profil bearbeiten
+ Foto machen
+ Speichern
+ rückgängig machen
+ Stummgeschaltene Accounts
+ entblockt
+ Avatar
+ Titelbild
+ Bio
+ Nur für Erwähnte sichtbar
+ Stummgeschaltene Nutzer
+ Followanfragen
+ Link teilen
+ Inhalt teilen
+ Akzeptieren
+ Ablehnen
+ Followanfragen
+ Followanfrage gesendet
+ Toot-Inhalt teilen
+ Stummschaltung aufgehoben
+ Followanfrage gesendet: Warten auf Antwort
+ Noch keine Toots hier! Ziehe nach unten um zu aktualisieren!
+ Das darf nicht leer sein.
+ Name
+
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 06ed674d8..db0fe884e 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -1,22 +1,21 @@
Une erreur s’est produite.
- Ce champ ne peut pas être vide
- Le domaine est invalide
- L’application n’a pu s’authentifier auprès de l’instance.
+ Ce champ ne peut pas être vide.
+ Le domaine est invalide.
+ Échec d’authentification auprès de l’instance.
Impossible de trouver un navigateur web.
Une erreur d’autorisation inconnue s’est produite.
- Vous ne pouvez pas vous authentifier.
+ Authentification refusée.
Impossible de récupérer le jeton d’authentification.
- Votre pouet est trop long!
- Le fichier doit faire moins de 4Mo.
+ Votre pouet est trop long !
+ Le fichier doit peser moins de 4 Mo.
Ce type de fichier n’est pas accepté.
- Le fichier ne peut être ouvert.
- Une permission pour lire ce média est requise pour le mettre en ligne.
- Permission d’enregistrer le fichier requise.
-
- Impossible de mettre une vidéo et une image sur le même pouet.
- Ce média ne peut être mis en ligne.
+ Le fichier ne peut pas être ouvert.
+ Permission requise pour lire ce média.
+ Permission requise pour enregistrer le média.
+ Un même pouet ne peut contenir à la fois une vidéo et une image.
+ Échec d’envoi du média.
Au moins un pouet a été reporté.
Accueil
@@ -38,14 +37,14 @@
Voir plus
Voir moins
- fin du pouet
- fin des notifications
- fin des comptes
- Il n’y a pas encore de pouets ici. Glissez vers le bas pour actualiser !
+ Fin du pouet
+ Fin des notifications
+ Fin des comptes
+ Il n’y a aucun pouet pour le moment.\nGlissez vers le bas pour actualiser !
- %s a boosté votre pouet
- %s a ajouté votre pouet dans ses favoris
- %s vous suit
+ %s a boosté votre pouet.
+ %s a ajouté votre pouet à ses favoris.
+ %s vous suit.
Signaler @%s
Davantage de commentaires ?
@@ -55,7 +54,7 @@
Favori
Plus
Répondre
- Se connecter avec Mastodon
+ Se connecter à Mastodon
Déconnexion
Suivre
Ne plus suivre
@@ -65,10 +64,10 @@
Supprimer
POUET
POUET !
- Essayer encore
- Définir le média comme sensible
- Masquer le texte par une mise en garde
- Ok
+ Réessayer
+ Définir le média comme sensible.
+ Masquer le texte par une mise en garde.
+ O.K.
Annuler
Fermer
Retour
@@ -78,7 +77,7 @@
Utilisateurs bloqués
Fil
Média
- Ouvrir avec votre navigateur
+ Ouvrir dans votre navigateur
Envoyer
Ajouter un média
Prendre une photo
@@ -87,7 +86,7 @@
Redonner la parole
Mention
NSFW
- Option
+ Options
Ouvrir le menu
Nettoyer
Sauvegarder
@@ -98,11 +97,11 @@
Rechercher un compte…
- Pouet !
- Envoyé !
+ Pouet !
+ Envoyé !
Quelle instance ?
- Quoi de neuf ?
+ Quoi de neuf ?
Contenu sensible
Afficher le nom
Bio
@@ -110,49 +109,50 @@
Avatar
En-tête
- Qu’est ce qu’une instance ?
+ Qu’est-ce qu’une instance ?
- L’adresse ou le domaine d’une instance peut être entré
- ici, comme mastodon.social, icosahedron.website, social.tchncs.de,
- ou autre (en anglais) !
- \n\nSi vous n’avez pas de compte, vous pouvez entrer le nom de l’instance que vous voulez rejoindre et créer un compte ici.\n\nUne instance est l’endroit où votre compte est
- stocké, mais vous pouvez facilement communiquer et suivre d’autres personnes sur d’autres instances bien que vous soyez sur le même site
- \n\nPlus d’info mastodon.social (anglais).
+ Connexion en cours…
+
+ Indiquer ici l’adresse ou le domaine d’une instance, comme mastodon.social, icosahedron.website, social.tchncs.de,
+ et bien d’autres encore (en anglais) !
+ \n\nSi vous ne disposez d’aucun compte, vous pouvez renseigner le nom de l’instance que vous souhaitez rejoindre et y créer un compte.\n\nUne instance est l’endroit où votre compte est
+ stocké, mais vous pouvez facilement suivre des personnes d’autres instances et communiquer avec elles comme si vous étiez sur le même site.
+ \n\nPour plus d’informations, consultez mastodon.social (anglais).
- Média mis en ligne avec succès
+ Média mis en ligne avec succès.
Mise en ligne…
Télécharger
- Public: Afficher dans les fils publics
- Non-listé: Ne pas afficher dans les fils publics
- Privé: N’afficher que pour vos abonné⋅e⋅s
- Direct: N’afficher que pour les personnes mentionnées
+ Public : afficher dans les fils publics.
+ Non listé : ne pas afficher dans les fils publics.
+ Privé : n’afficher que pour les personnes qui vous suivent.
+ Direct : n’afficher que pour les personnes mentionnées.
Notifications
- Modifier la notification
- Notifications push
+ Modifier la notification.
+ Notifications push.
Alertes
- Émettre un son pour notifier
- Vibrer pour notifier
- Notifier avec une LED
- Me notifier quand
- mentionné
- suivi
- mes pouets sont boostés
- mes pouets sont mis en favoris
+ Émettre un son pour notifier.
+ Vibrer pour notifier.
+ Notifier avec une LED.
+ Me notifier quand…
+ on me mentionne.
+ on me suit.
+ mes pouets sont boostés.
+ mes pouets sont mis en favoris.
Apparence
- Utiliser le thème clair
+ Utiliser le thème clair.
Navigateur
- Utiliser le navigateur intégré
- Cacher le bouton de suivi lors du défilement
+ Utiliser le navigateur intégré.
+ Masquer le bouton de suivi lors du défilement.
- %s vous ont mentionné
+ %s a mentionné votre nom.
%1$s, %2$s, %3$s et %4$d plus
%1$s, %2$s, et %3$s
%1$s et %2$s
- %d nouvelles interactions
+ %d nouvelles interactions.
- Compte bloqué
- Partager le contenu du pouet
- Partager le lien du pouet
+ Compte bloqué.
+ Partager le contenu du pouet.
+ Partager le lien du pouet.
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 00cc880a0..af74baf15 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -21,12 +21,12 @@
Strona główna
Powiadomienia
Lokalne
- Globalne
+ Federalne
Wątek
#%s
Posty
Obserwacje
- Obsereujący
+ Obserwujący
Ulubione
Wyciszeni użytkownicy
Zablokowani użytkownicy
@@ -73,10 +73,10 @@
OK
Anuluj
Zamknij
- Wró
+ Wróć
Profil
Preferencje
- Ulunione
+ Ulubione
Wyciszeni użytkownicy
Zablokowani użytkownicy
Prośby o obserwację
@@ -111,7 +111,7 @@
Cofnięto wyciszenie użytkownika
Jaka instancja?
- Opowiedz o czymś…
+ Co ci chodzi po głowie?
Ostrzeenie o zawartości
Nazwa wyświetlana
Biografia
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index 24055b069..b98add18c 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -29,7 +29,10 @@
Takip edilenler
Takipçiler
Favoriler
+ Sesize alınmış kullanıcılar
Engellenmiş kullanıcılar
+ Takip Etme İstekler
+ Profili düzeltme
\@%s
%s yükseltti
@@ -67,7 +70,7 @@
İLET!
Tekrar dene
Medyayı hassas olarak etiketle
- Metini uyarı ile sakla
+ İçerik uyarıyla gizlensin
Tamam
İptal
Kapat
@@ -75,6 +78,7 @@
Profil
Ayarlar
Favoriler
+ Sesize alınmış kullanıcılar
Engellenmiş kullanıcılar
Dizi
Medya
@@ -92,6 +96,9 @@
Temizle
Kaydet
Profili düzelt
+ Geri al
+ Kabul et
+ Reddet
İletinin adresini paylaş…
İletiyi paylaş…
@@ -123,9 +130,10 @@
Medya Yükleme Bittiriliyor
Yükleniyor…
İndir
+ Takip etme istekleri: yanıt bekleniyor
Kamu: Herkese açık ve sosyal çizelgelerinde çıkar
- Saklı: Herkese açık ancık sosyal çizelgesinde çıkmaz
+ Saklı: Herkese açık ancak sosyal çizelgesinde çıkmaz
Özel: Sadece takipçiler ve bahsedilenlere açık
Direkt: Sadece bahsedilen kullanıcılara açık
@@ -156,4 +164,6 @@
Kitli Hesap
İletinin içeriğini paylaş
İletinin adresini paylaş
+
+ Takip edebilme istendi
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 0b1e3b2de..b151ffc92 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -37,6 +37,7 @@
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index d1006dab4..d5c8bd02e 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -2,6 +2,7 @@
#000000
+ #8f000000
#4c5368
#363c4b
diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml
index b2a8e3b6b..ad2d3b37b 100644
--- a/app/src/main/res/values/donottranslate.xml
+++ b/app/src/main/res/values/donottranslate.xml
@@ -2,7 +2,8 @@
Tusky
https://tusky.keylesspalace.com
- https://tuskynotifier.keylesspalace.com
+ apitusky.keylesspalace.com
+ your_password_here
oauth2redirect
com.keylesspalace.tusky
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5a8f3573d..68dc27683 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -59,6 +59,7 @@
Compose
Login with Mastodon
Log Out
+ Do you wish to logout
Follow
Unfollow
Block
@@ -121,6 +122,8 @@
What\'s an instance?
+ Connecting…
+
The address or domain of any instance can be entered
here, such as mastodon.social, icosahedron.website, social.tchncs.de, and
more!
@@ -157,6 +160,10 @@
Browser
Use Chrome Custom Tabs
Hide follow button while scrolling
+ Timeline filtering
+ Tabs
+ Show boosts
+ Show replies
%s mentioned you
%1$s, %2$s, %3$s and %4$d others
@@ -165,6 +172,19 @@
%d new interactions
Locked Account
+
+ About
+ App version: %s
+
+ Project website:\n
+ https://tusky.keylesspalace.com
+
+
+ Bug reports & feature requests:\n
+ https://github.com/Vavassor/Tusky/issues
+
+ Tusky\'s Profile
+
Share content of toot
Share link to toot
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index a6454085f..91a842e9c 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -14,6 +14,11 @@
- 0dp
- @android:color/black
+
+
@@ -62,6 +67,7 @@
- @drawable/border_background_dark
- @color/image_button_dark
- @color/color_accent_dark
+ - @color/image_button_dark
- @color/color_background_dark
- @drawable/status_divider_dark
@@ -148,6 +154,7 @@
- @drawable/border_background_light
- @color/image_button_light
- @color/color_accent_light
+ - @color/image_button_light
- @color/report_status_background_light
- @drawable/report_status_divider_light
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index b65497de0..8b6006e73 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -21,6 +21,26 @@
android:title="@string/pref_title_custom_tabs"
android:defaultValue="true" />
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/amazon_badge.png b/assets/amazon_badge.png
new file mode 100644
index 000000000..e976e1398
Binary files /dev/null and b/assets/amazon_badge.png differ
diff --git a/build.gradle b/build.gradle
index f8f4d274a..bfed59203 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,11 +5,10 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.3.1'
+ classpath 'com.android.tools.build:gradle:2.3.2'
// 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'
}
}