From 642e2af23e444a802e953f92ffeaa3624c3e4bdc Mon Sep 17 00:00:00 2001
From: "serage.betelmal"
Date: Sat, 15 Apr 2017 19:05:25 +0100
Subject: [PATCH 01/53] Adding an about activity to the App
---
app/build.gradle | 1 +
app/src/main/AndroidManifest.xml | 40 +++++++++-----
.../keylesspalace/tusky/AboutActivity.java | 25 +++++++++
.../com/keylesspalace/tusky/MainActivity.java | 6 ++-
app/src/main/res/layout/activity_about.xml | 54 +++++++++++++++++++
app/src/main/res/values/strings.xml | 3 ++
app/src/main/res/values/styles.xml | 43 ++++++++++-----
7 files changed, 147 insertions(+), 25 deletions(-)
create mode 100644 app/src/main/java/com/keylesspalace/tusky/AboutActivity.java
create mode 100644 app/src/main/res/layout/activity_about.xml
diff --git a/app/build.gradle b/app/build.gradle
index ebacc223..1e3de3e1 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -50,6 +50,7 @@ dependencies {
compile 'com.jakewharton:butterknife:8.4.0'
compile 'com.google.firebase:firebase-messaging:10.0.1'
compile 'com.google.firebase:firebase-crash:10.0.1'
+ compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha7'
testCompile 'junit:junit:4.12'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 62955811..7bbe6485 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -13,7 +13,10 @@
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
-
+
+
@@ -41,55 +44,68 @@
android:windowSoftInputMode="stateVisible|adjustResize">
+
+
+
+
+
+
-
+
+
-
+
-
+
-
-
+
-
+
+ android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
+ tools:targetApi="24">
-
+
-
+
+
\ No newline at end of file
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 00000000..bbbf02cf
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java
@@ -0,0 +1,25 @@
+package com.keylesspalace.tusky;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.widget.TextView;
+
+public class AboutActivity extends AppCompatActivity {
+ private TextView mVersionTextView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_about);
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+
+ mVersionTextView = (TextView) findViewById(R.id.textView);
+ String versionName = BuildConfig.VERSION_NAME;
+
+ mVersionTextView.
+ setText(getString(R.string.about_application_version)+ versionName);
+ }
+
+}
diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
index 43415269..effc48d0 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
@@ -266,7 +266,8 @@ public class MainActivity extends BaseActivity {
new PrimaryDrawerItem().withIdentifier(2).withName(getString(R.string.action_view_blocks)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_block),
new DividerDrawerItem(),
new SecondaryDrawerItem().withIdentifier(3).withName(getString(R.string.action_view_preferences)).withSelectable(false),
- new SecondaryDrawerItem().withIdentifier(4).withName(getString(R.string.action_logout)).withSelectable(false)
+ new SecondaryDrawerItem().withIdentifier(4).withName(getString(R.string.about_title_activity)).withSelectable(false),
+ new SecondaryDrawerItem().withIdentifier(5).withName(getString(R.string.action_logout)).withSelectable(false)
)
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
@Override
@@ -290,6 +291,9 @@ public class MainActivity extends BaseActivity {
Intent intent = new Intent(MainActivity.this, PreferencesActivity.class);
startActivity(intent);
} else if (drawerItemIdentifier == 4) {
+ Intent intent = new Intent(MainActivity.this, AboutActivity.class);
+ startActivity(intent);
+ } else if (drawerItemIdentifier == 5) {
logout();
}
}
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 00000000..e395a86a
--- /dev/null
+++ b/app/src/main/res/layout/activity_about.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 7d053474..0ef915e5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -141,4 +141,7 @@
Locked Account
+ About
+ App version:
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index a6454085..e0a59373 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -30,7 +30,8 @@
@color/text_color_secondary_dark@color/text_color_tertiary_dark@color/text_color_primary_inverse_dark
- @color/text_color_secondary_inverse_dark
+ @color/text_color_secondary_inverse_dark
+ @color/text_color_tertiary_inverse_dark@color/text_color_primary_dark
@@ -53,11 +54,15 @@
@drawable/tab_page_margin_dark@color/account_header_background_dark@color/toolbar_icon_dark
- @color/account_toolbar_icon_collapsed_dark
- @style/AppTheme.Account.ToolbarPopupTheme.Dark
+
+ @color/account_toolbar_icon_collapsed_dark
+
+ @style/AppTheme.Account.ToolbarPopupTheme.Dark
+ @color/toolbar_icon_dark@color/compose_media_button_dark
- @color/compose_media_button_disabled_dark
+ @color/compose_media_button_disabled_dark
+ @color/color_accent_dark@drawable/border_background_dark@color/image_button_dark
@@ -116,8 +121,10 @@
@color/text_color_secondary_light@color/text_color_tertiary_light@color/text_color_primary_inverse_light
- @color/text_color_secondary_inverse_light
- @color/text_color_tertiary_inverse_light
+ @color/text_color_secondary_inverse_light
+
+ @color/text_color_tertiary_inverse_light
+ @color/text_color_primary_light@style/AppTheme.BottomSheetDialog.Light
@@ -132,18 +139,25 @@
@drawable/favourite_active_light@drawable/favourite_inactive_light@drawable/toggle_small_light
- @color/sensitive_media_warning_background_light
+
+ @color/sensitive_media_warning_background_light
+ @drawable/media_preview_unloaded_light@drawable/status_divider_light@color/color_accent_light@drawable/tab_page_margin_light@color/account_header_background_light
- @color/toolbar_icon_dark
- @color/account_toolbar_icon_collapsed_light
- @style/AppTheme.Account.ToolbarPopupTheme.Light
+ @color/toolbar_icon_dark
+
+
+ @color/account_toolbar_icon_collapsed_light
+
+ @style/AppTheme.Account.ToolbarPopupTheme.Light
+ @color/toolbar_icon_light@color/compose_media_button_light
- @color/compose_media_button_disabled_light
+ @color/compose_media_button_disabled_light
+ @color/compose_mention_light@drawable/border_background_light@color/image_button_light
@@ -159,7 +173,8 @@
@color/color_primary_dark_light@color/color_primary_light@color/text_color_primary_light
- @color/text_color_primary_dark
+ @color/text_color_primary_dark
+ @color/toolbar_background_light@color/text_color_secondary_light
@@ -187,4 +202,8 @@
true
+
+
+
+
From 834ad78c36cec96a82507e798eda858fa286af38 Mon Sep 17 00:00:00 2001
From: Raphael Michel
Date: Sat, 15 Apr 2017 20:23:07 +0200
Subject: [PATCH 02/53] Allow to filter boosts and replies from home (closes
#133)
---
.../keylesspalace/tusky/TimelineAdapter.java | 5 +++
.../keylesspalace/tusky/TimelineFragment.java | 35 +++++++++++++++++++
app/src/main/res/layout/activity_account.xml | 1 +
app/src/main/res/values/strings.xml | 4 +++
app/src/main/res/xml/preferences.xml | 20 +++++++++++
5 files changed, 65 insertions(+)
diff --git a/app/src/main/java/com/keylesspalace/tusky/TimelineAdapter.java b/app/src/main/java/com/keylesspalace/tusky/TimelineAdapter.java
index 8f8fc546..9cb2f937 100644
--- a/app/src/main/java/com/keylesspalace/tusky/TimelineAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/TimelineAdapter.java
@@ -111,6 +111,11 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover
notifyItemRemoved(position);
}
+ public void clear() {
+ statuses.clear();
+ notifyDataSetChanged();
+ }
+
@Nullable
Status getItem(int position) {
if (position >= 0 && position < statuses.size()) {
diff --git a/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java
index 4f1b6e20..b24731fd 100644
--- a/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java
@@ -33,6 +33,7 @@ import android.view.ViewGroup;
import com.keylesspalace.tusky.entity.Status;
+import java.util.Iterator;
import java.util.List;
import retrofit2.Call;
@@ -61,6 +62,9 @@ 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;
public static TimelineFragment newInstance(Kind kind) {
TimelineFragment fragment = new TimelineFragment();
@@ -181,6 +185,8 @@ public class TimelineFragment extends SFragment implements
};
}
recyclerView.addOnScrollListener(scrollListener);
+
+ preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
}
@Override
@@ -274,7 +280,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();
+ }
+ }
+
+ @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);
diff --git a/app/src/main/res/layout/activity_account.xml b/app/src/main/res/layout/activity_account.xml
index e3504dd6..3e442d32 100644
--- a/app/src/main/res/layout/activity_account.xml
+++ b/app/src/main/res/layout/activity_account.xml
@@ -20,6 +20,7 @@
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:contentScrim="?attr/toolbar_background_color"
+ app:collapsedTitleTextAppearance="?attr/android:textColorPrimary"
android:fitsSystemWindows="true"
app:titleEnabled="false">
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 7d053474..97217526 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -132,6 +132,10 @@
BrowserUse Chrome Custom TabsHide follow button while scrolling
+ Toot filtering
+ Tabs
+ Show boosts
+ Show replies%s mentioned you%1$s, %2$s, %3$s and %4$d others
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index b65497de..8b6006e7 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" />
+
+
+
+
+
+
+
+
+
+
+
+
From 689c29dbafc3e2e8c1ecfada755032c9cf102fc1 Mon Sep 17 00:00:00 2001
From: Poulpette
Date: Sun, 30 Apr 2017 01:01:27 +0200
Subject: [PATCH 03/53] Improve French translation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Some typos have been fixed and terminology changes have been suggested.
Also the gender-inclusive marks have been removed: independently
of our personal opinions regarding which sociological features
a written language should or shouldn’t take into account,
the fact remains that interpunct, hyphen, slash and apostrophe
are not admitted characters to highlight the feminine gender
due to the reading issues that would come out of those
graphical modifications. Moreover, those marks do not take into account
accessibility problems either (screen readers, etc.).
Therefore French grammatical rules should logically prevail.
---
app/src/main/res/values-fr/strings.xml | 113 ++++++++++++-------------
1 file changed, 56 insertions(+), 57 deletions(-)
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 06ed674d..e43a0c4d 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -1,22 +1,22 @@
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 ê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.
+ Permission requise pour lire ce média.
+ Permission requise pour enregistrer le média.
- Impossible de mettre une vidéo et une image sur le même pouet.
- Ce média ne peut être mis en ligne.
+ 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 +38,14 @@
Voir plusVoir 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 @%sDavantage de commentaires ?
@@ -55,7 +55,7 @@
FavoriPlusRépondre
- Se connecter avec Mastodon
+ Se connecter à MastodonDéconnexionSuivreNe plus suivre
@@ -65,10 +65,10 @@
SupprimerPOUETPOUET !
- 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.AnnulerFermerRetour
@@ -78,7 +78,7 @@
Utilisateurs bloquésFilMédia
- Ouvrir avec votre navigateur
+ Ouvrir dans votre navigateurEnvoyerAjouter un médiaPrendre une photo
@@ -87,7 +87,7 @@
Redonner la paroleMentionNSFW
- Option
+ OptionsOuvrir le menuNettoyerSauvegarder
@@ -98,11 +98,11 @@
Rechercher un compte…
- Pouet !
- Envoyé !
+ Pouet !
+ Envoyé !Quelle instance ?
- Quoi de neuf ?
+ Quoi de neuf ?Contenu sensibleAfficher le nomBio
@@ -110,49 +110,48 @@
AvatarEn-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).
+ 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.
From 646827d5224e2acb1c9e1c9d61fe415265b280dd Mon Sep 17 00:00:00 2001
From: ButterflyOfFire
Date: Sun, 30 Apr 2017 16:44:53 +0200
Subject: [PATCH 04/53] Update strings.xml
---
app/src/main/res/values-ar/strings.xml | 50 +++++++++++++-------------
1 file changed, 25 insertions(+), 25 deletions(-)
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 0e508b5b..17988f5a 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -1,21 +1,21 @@
وقع هناك خطأ.
-
+ لا يجب أن يترك فارغا.اسم النطاق غير صالحاخفقت المصادقة مع مثيل الخادم هذا.لم يتم العثور على متصفح قابل للإستعمال.تم رفض التصريح.
-
+ المنشور طويل جدا !يجب أن يكون حجم الملف أقل من 4 ميغابايت.لا يمكن رفع هذا النوع من الملفات.تعذر فتح ذاك الملف.
-
+ لا يمكنك إرفاق كلا من الصور و الفيديوهات في نفس المنشور.اخفقت عملية الرفع.
@@ -32,7 +32,7 @@
المستخدمون المحظورون\@%s
- %s عزز
+ %s رقّيمحتوى حساساضغط للعرضاعرض أكثر
@@ -41,7 +41,7 @@
نهاية الحالاتنهاية الاشعاراتنهاية الحسابات
-
+ لا توجد تبويقات هنا بعد. اسحب للأسفل للتحديث !%s عزز تبويقك%s أعجب بتبويقك
@@ -51,7 +51,7 @@
تعليقات إضافية ؟أجب
- عزز
+ رقّيتفضيلالمزيدحرر
@@ -64,9 +64,9 @@
أبلغإحذفتبويق
- بَوِّق
+ بوّقإعادة المحاولة
-
+ ضع علامة على الوسيط باعتباره حسّاساخفي النص وراء تحذيرموافقإلغاء
@@ -81,7 +81,7 @@
إفتح في متصفحارسلإضافة وسائط
-
+ أخذ صورةشاركأكتمإلغاء الكتم
@@ -90,11 +90,11 @@
خياراتإفتح الدرجإمسح
-
-
+ إحفظ
+ تعديل الملف الشخصي
- شارك رابط التبويق إلى ...
-
+ شارك رابط التبويق على ...
+ شارك التبويق على …ابحث عن حسابات ...
@@ -104,11 +104,11 @@
أي سيرفر ؟ما الجديد ؟تحذير عن المحتوى
-
-
+ الإسم العلني
+ السيرة
-
-
+ الصورة الرمزية
+ رأس الصفحةماذا نعني بمثيل الخادم ؟
@@ -123,12 +123,12 @@
تتمة رفع الوسائطجاري الرفع ...
-
+ تنزيل
-
-
-
-
+
+
+
+
الاشعاراتتعديل الاشعارات
@@ -146,7 +146,7 @@
إستخدم سمةً فاتحة اللونالمتصفحإخفاء زر المتابعة أثناء تمرير الصفحة
-
+ إخفاء زر المتابعة عند التمرير إلى أسفل%s أشار إليك%1$s, %2$s, %3$s و %4$d أخرى
@@ -155,6 +155,6 @@
%d تفاعلات جديدةحساب مقفل
-
-
+ شارك محتوى التبويق
+ شارك الرابط إلى التبويق
From df7e077029faadf208a587d06cad94604aaed269 Mon Sep 17 00:00:00 2001
From: "serage.betelmal"
Date: Sun, 30 Apr 2017 21:53:32 +0100
Subject: [PATCH 05/53] Adding more content to the About activity
---
app/src/main/AndroidManifest.xml | 36 ++++------------
.../keylesspalace/tusky/AboutActivity.java | 25 +++++++++--
app/src/main/res/layout/activity_about.xml | 42 +++++++++++++++++--
app/src/main/res/values/strings.xml | 5 ++-
4 files changed, 73 insertions(+), 35 deletions(-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8f375863..a40855ca 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -43,64 +43,44 @@
android:windowSoftInputMode="stateVisible|adjustResize">
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
+
+ android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
diff --git a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java
index bbbf02cf..06c53226 100644
--- a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java
@@ -1,12 +1,18 @@
package com.keylesspalace.tusky;
+import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
+import android.view.View;
+import android.widget.Button;
import android.widget.TextView;
public class AboutActivity extends AppCompatActivity {
private TextView mVersionTextView;
+ private TextView mProjectSiteTextView;
+ private TextView mFeatureSiteTextView;
+ private Button mTuskyAccountButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -14,12 +20,25 @@ public class AboutActivity extends AppCompatActivity {
setContentView(R.layout.activity_about);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
+ mVersionTextView = (TextView) findViewById(R.id.versionTV);
+ mProjectSiteTextView = (TextView) findViewById(R.id.projectURL_TV);
+ mFeatureSiteTextView = (TextView) findViewById(R.id.featuresURL_TV);
+ mTuskyAccountButton = (Button) findViewById(R.id.tusky_profile_button);
- mVersionTextView = (TextView) findViewById(R.id.textView);
String versionName = BuildConfig.VERSION_NAME;
- mVersionTextView.
- setText(getString(R.string.about_application_version)+ versionName);
+ mVersionTextView.setText(getString(R.string.about_application_version) + versionName);
+ mTuskyAccountButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ onAccountTVClick();
+ }
+ });
}
+ private void onAccountTVClick() {
+ Intent intent = new Intent(this, AccountActivity.class);
+ intent.putExtra("id", "72306");
+ startActivity(intent);
+ }
}
diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml
index e395a86a..d28963d0 100644
--- a/app/src/main/res/layout/activity_about.xml
+++ b/app/src/main/res/layout/activity_about.xml
@@ -27,7 +27,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
- android:orientation="vertical">
+ android:orientation="vertical"
+ android:id="@+id/linearLayoutCompat">
+
+
+
+
+
+
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4b78313e..1e406818 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -166,7 +166,10 @@
Locked AccountAbout
- App version:
+ App version:
+ Project website: \n https://tusky.keylesspalace.com
+ Bug reports & feature requests: \n https://github.com/Vavassor/Tusky/issues
+ Tusky\'s ProfileShare content of tootShare link to toot
From 5fb5efe6e99f6c8f194f2f9737ad4f6d8559c19d Mon Sep 17 00:00:00 2001
From: nore715
Date: Mon, 1 May 2017 12:10:19 +0200
Subject: [PATCH 06/53] Update strings.xml
Fix a few grammatical issues to comply with existing rules :
- space before "!" in fr str for "error_compose_character_limit" as in fr str for "footer_empty"
- second part of negation added in fr str for "error_media_upload_opening", "error_media_upload_sending", "error_failed_app_registration" (ne ... pas) as in fr str for "error_empty" and others
- added a period in fr str for "dialog_whats_an_instance"
---
app/src/main/res/values-fr/strings.xml | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 06ed674d..a8c8f7a0 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -3,20 +3,20 @@
Une erreur s’est produite.Ce champ ne peut pas être videLe domaine est invalide
- L’application n’a pu s’authentifier auprès de l’instance.
+ L’application n’a pas pu s’authentifier 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.Impossible de récupérer le jeton d’authentification.
- Votre pouet est trop long!
+ Votre pouet est trop long !Le fichier doit faire moins de 4Mo.Ce type de fichier n’est pas accepté.
- Le fichier ne peut être ouvert.
+ Le fichier ne peut pas ê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.
+ Ce média ne peut pas être mis en ligne.Au moins un pouet a été reporté.Accueil
@@ -116,7 +116,7 @@
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
+ 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).
Média mis en ligne avec succès
From b7a98ad228d9b35637f5f9e21ed720757f09da2f Mon Sep 17 00:00:00 2001
From: Conny Duck
Date: Mon, 1 May 2017 16:18:01 +0200
Subject: [PATCH 07/53] updated german translation
---
app/src/main/res/values-de/strings.xml | 32 ++++++++++++++++++++++----
1 file changed, 27 insertions(+), 5 deletions(-)
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 4ab3a6a0..3cd0c742 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -75,7 +75,7 @@
ProfilEinstellungenFavoriten
- Blockierte Nutzer
+ Blockierte AccountsThreadMedienIm Browser öffnen
@@ -93,7 +93,7 @@
- Teile Toot-URL zu…
+ Toot-Link teilenSuche Accounts…
@@ -125,7 +125,6 @@
Öffentlich sichtbarÖffentlich sichtbar, aber nicht in der öffentlichen TimelineNur für Follower und Erwähnte sichtbar
-
BenachrichtigungenBenachrichtigungseinstellungen
@@ -152,6 +151,29 @@
%d neue InteraktionenGesperrter 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
+
From 7993fbc5b5294824a7bc27c69ce72d5500ad238b Mon Sep 17 00:00:00 2001
From: PhotonQyv
Date: Wed, 3 May 2017 17:02:22 +0100
Subject: [PATCH 08/53] Add files via upload
Added fix for issues #190 & #259 both rotation/orientation related
---
app/src/main/AndroidManifest.xml | 198 ++++++++++++++++---------------
1 file changed, 100 insertions(+), 98 deletions(-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 84c1b093..f2250c65 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,99 +1,101 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
From 89c984fe844bb9cfb4cf89d1ec68aae81f6a7402 Mon Sep 17 00:00:00 2001
From: PhotonQyv
Date: Wed, 3 May 2017 17:03:29 +0100
Subject: [PATCH 09/53] Add files via upload
Added fix for issues #190 & #259 both rotation/orientation related
---
.../com/keylesspalace/tusky/MainActivity.java | 1117 +++++++++--------
1 file changed, 562 insertions(+), 555 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
index 243fce57..1f9f1605 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
@@ -1,556 +1,563 @@
-/* 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.NotificationManager;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.PersistableBundle;
-import android.support.annotation.NonNull;
-import android.support.design.widget.FloatingActionButton;
-import android.support.design.widget.TabLayout;
-import android.support.v4.app.Fragment;
-import android.support.v4.content.ContextCompat;
-import android.support.v4.view.ViewPager;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.style.StyleSpan;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-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.mikepenz.google_material_typeface_library.GoogleMaterial;
-import com.mikepenz.materialdrawer.AccountHeader;
-import com.mikepenz.materialdrawer.AccountHeaderBuilder;
-import com.mikepenz.materialdrawer.Drawer;
-import com.mikepenz.materialdrawer.DrawerBuilder;
-import com.mikepenz.materialdrawer.model.DividerDrawerItem;
-import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
-import com.mikepenz.materialdrawer.model.ProfileDrawerItem;
-import com.mikepenz.materialdrawer.model.SecondaryDrawerItem;
-import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
-import com.mikepenz.materialdrawer.model.interfaces.IProfile;
-import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader;
-import com.mikepenz.materialdrawer.util.DrawerImageLoader;
-import com.squareup.picasso.Picasso;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Stack;
-
-import butterknife.BindView;
-import butterknife.ButterKnife;
-import retrofit2.Call;
-import retrofit2.Callback;
-import retrofit2.Response;
-
-public class MainActivity extends BaseActivity implements SFragment.OnUserRemovedListener {
- private static final String TAG = "MainActivity"; // logging tag
- protected static int COMPOSE_RESULT = 1;
-
- private String loggedInAccountId;
- private String loggedInAccountUsername;
- private Stack pageHistory;
- private AccountHeader headerResult;
- private Drawer drawer;
-
- @BindView(R.id.floating_search_view) FloatingSearchView searchView;
- @BindView(R.id.floating_btn) FloatingActionButton floatingBtn;
- @BindView(R.id.tab_layout) TabLayout tabLayout;
- @BindView(R.id.pager) ViewPager viewPager;
-
- FloatingActionButton composeButton;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-
- pageHistory = new Stack<>();
- if (savedInstanceState != null) {
- List restoredHistory = savedInstanceState.getIntegerArrayList("pageHistory");
- if (restoredHistory != null) {
- pageHistory.addAll(restoredHistory);
- }
- }
-
- ButterKnife.bind(this);
-
- floatingBtn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent intent = new Intent(getApplicationContext(), ComposeActivity.class);
- startActivityForResult(intent, COMPOSE_RESULT);
- }
- });
-
- setupDrawer();
- setupSearchView();
-
- /* Fetch user info while we're doing other things. This has to be after setting up the
- * drawer, though, because its callback touches the header in the drawer. */
- fetchUserInfo();
-
- // Setup the tabs and timeline pager.
- TimelinePagerAdapter adapter = new TimelinePagerAdapter(getSupportFragmentManager());
-
- int pageMargin = getResources().getDimensionPixelSize(R.dimen.tab_page_margin);
- viewPager.setPageMargin(pageMargin);
- Drawable pageMarginDrawable = ThemeUtils.getDrawable(this, R.attr.tab_page_margin_drawable,
- R.drawable.tab_page_margin_dark);
- viewPager.setPageMarginDrawable(pageMarginDrawable);
- viewPager.setAdapter(adapter);
-
- tabLayout.setupWithViewPager(viewPager);
-
- int[] tabIcons = {
- R.drawable.ic_home_24dp,
- R.drawable.ic_notifications_24dp,
- R.drawable.ic_local_24dp,
- R.drawable.ic_public_24dp,
- };
- String[] pageTitles = {
- getString(R.string.title_home),
- getString(R.string.title_notifications),
- getString(R.string.title_public_local),
- getString(R.string.title_public_federated),
- };
- for (int i = 0; i < 4; i++) {
- TabLayout.Tab tab = tabLayout.getTabAt(i);
- tab.setIcon(tabIcons[i]);
- tab.setContentDescription(pageTitles[i]);
- }
-
- tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
- @Override
- public void onTabSelected(TabLayout.Tab tab) {
- viewPager.setCurrentItem(tab.getPosition());
-
- if (pageHistory.isEmpty()) {
- pageHistory.push(0);
- }
-
- if (pageHistory.contains(tab.getPosition())) {
- pageHistory.remove(pageHistory.indexOf(tab.getPosition()));
- }
-
- pageHistory.push(tab.getPosition());
- tintTab(tab, true);
- }
-
- @Override
- public void onTabUnselected(TabLayout.Tab tab) {
- tintTab(tab, false);
- }
-
- @Override
- public void onTabReselected(TabLayout.Tab tab) {}
- });
-
- Intent intent = getIntent();
-
- int tabSelected = 0;
- if (intent != null) {
- int tabPosition = intent.getIntExtra("tab_position", 0);
- if (tabPosition != 0) {
- TabLayout.Tab tab = tabLayout.getTabAt(tabPosition);
- if (tab != null) {
- tab.select();
- tabSelected = tabPosition;
- }
- }
- }
- for (int i = 0; i < 4; i++) {
- tintTab(tabLayout.getTabAt(i), i == tabSelected);
- }
-
- // Setup push notifications
- if (arePushNotificationsEnabled()) {
- enablePushNotifications();
- } else {
- disablePushNotifications();
- }
-
- composeButton = floatingBtn;
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- SharedPreferences notificationPreferences = getApplicationContext()
- .getSharedPreferences("Notifications", MODE_PRIVATE);
- notificationPreferences.edit()
- .putString("current", "[]")
- .apply();
-
- ((NotificationManager) (getSystemService(NOTIFICATION_SERVICE)))
- .cancel(MessagingService.NOTIFY_ID);
-
- /* After editing a profile, the profile header in the navigation drawer needs to be
- * refreshed */
- SharedPreferences preferences = getPrivatePreferences();
- if (preferences.getBoolean("refreshProfileHeader", false)) {
- fetchUserInfo();
- preferences.edit()
- .putBoolean("refreshProfileHeader", false)
- .apply();
- }
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
- ArrayList pageHistoryList = new ArrayList<>();
- pageHistoryList.addAll(pageHistory);
- outState.putIntegerArrayList("pageHistory", pageHistoryList);
- super.onSaveInstanceState(outState, outPersistentState);
- }
-
- private void tintTab(TabLayout.Tab tab, boolean tinted) {
- int color = (tinted) ? R.attr.tab_icon_selected_tint : R.attr.toolbar_icon_tint;
- ThemeUtils.setDrawableTint(this, tab.getIcon(), color);
- }
-
- private void setupDrawer() {
- headerResult = new AccountHeaderBuilder()
- .withActivity(this)
- .withSelectionListEnabledForSingleProfile(false)
- .withDividerBelowHeader(false)
- .withOnAccountHeaderProfileImageListener(new AccountHeader.OnAccountHeaderProfileImageListener() {
- @Override
- public boolean onProfileImageClick(View view, IProfile profile, boolean current) {
- if (current && loggedInAccountId != null) {
- Intent intent = new Intent(MainActivity.this, AccountActivity.class);
- intent.putExtra("id", loggedInAccountId);
- startActivity(intent);
- return true;
- }
- return false;
- }
-
- @Override
- public boolean onProfileImageLongClick(View view, IProfile profile, boolean current) {
- return false;
- }
- })
- .withCompactStyle(true)
- .build();
- headerResult.getView()
- .findViewById(R.id.material_drawer_account_header_current)
- .setContentDescription(getString(R.string.action_view_profile));
-
- DrawerImageLoader.init(new AbstractDrawerImageLoader() {
- @Override
- public void set(ImageView imageView, Uri uri, Drawable placeholder) {
- Picasso.with(imageView.getContext()).load(uri).placeholder(placeholder).into(imageView);
- }
-
- @Override
- public void cancel(ImageView imageView) {
- Picasso.with(imageView.getContext()).cancelRequest(imageView);
- }
- });
-
- Drawable muteDrawable = ContextCompat.getDrawable(this, R.drawable.ic_mute_24dp);
- ThemeUtils.setDrawableTint(this, muteDrawable, R.attr.toolbar_icon_tint);
-
- drawer = new DrawerBuilder()
- .withActivity(this)
- //.withToolbar(toolbar)
- .withAccountHeader(headerResult)
- .withHasStableIds(true)
- .withSelectedItem(-1)
- .addDrawerItems(
- new PrimaryDrawerItem().withIdentifier(0).withName(getString(R.string.action_edit_profile)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_person),
- new PrimaryDrawerItem().withIdentifier(1).withName(getString(R.string.action_view_favourites)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_star),
- new PrimaryDrawerItem().withIdentifier(2).withName(getString(R.string.action_view_mutes)).withSelectable(false).withIcon(muteDrawable),
- 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)
- )
- .withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
- @Override
- public boolean onItemClick(View view, int position, IDrawerItem drawerItem) {
- if (drawerItem != null) {
- long drawerItemIdentifier = drawerItem.getIdentifier();
-
- if (drawerItemIdentifier == 0) {
- Intent intent = new Intent(MainActivity.this, EditProfileActivity.class);
- startActivity(intent);
- } else if (drawerItemIdentifier == 1) {
- Intent intent = new Intent(MainActivity.this, FavouritesActivity.class);
- startActivity(intent);
- } else if (drawerItemIdentifier == 2) {
- Intent intent = new Intent(MainActivity.this, AccountListActivity.class);
- intent.putExtra("type", AccountListActivity.Type.MUTES);
- startActivity(intent);
- } else if (drawerItemIdentifier == 3) {
- Intent intent = new Intent(MainActivity.this, AccountListActivity.class);
- intent.putExtra("type", AccountListActivity.Type.BLOCKS);
- startActivity(intent);
- } else if (drawerItemIdentifier == 4) {
- Intent intent = new Intent(MainActivity.this, PreferencesActivity.class);
- startActivity(intent);
- } else if (drawerItemIdentifier == 5) {
- logout();
- } else if (drawerItemIdentifier == 6) {
- Intent intent = new Intent(MainActivity.this, AccountListActivity.class);
- intent.putExtra("type", AccountListActivity.Type.FOLLOW_REQUESTS);
- startActivity(intent);
- }
- }
-
- return false;
- }
- })
- .build();
- }
-
- private void logout() {
- if (arePushNotificationsEnabled()) disablePushNotifications();
-
- getPrivatePreferences().edit()
- .remove("domain")
- .remove("accessToken")
- .apply();
-
- Intent intent = new Intent(MainActivity.this, LoginActivity.class);
- startActivity(intent);
- finish();
- }
-
- private void setupSearchView() {
- searchView.attachNavigationDrawerToMenuButton(drawer.getDrawerLayout());
-
- // Setup content descriptions for the different elements in the search view.
- final View leftAction = searchView.findViewById(R.id.left_action);
- leftAction.setContentDescription(getString(R.string.action_open_drawer));
- searchView.setOnFocusChangeListener(new FloatingSearchView.OnFocusChangeListener() {
- @Override
- public void onFocus() {
- leftAction.setContentDescription(getString(R.string.action_close));
- }
-
- @Override
- public void onFocusCleared() {
- leftAction.setContentDescription(getString(R.string.action_open_drawer));
- }
- });
- View clearButton = searchView.findViewById(R.id.clear_btn);
- clearButton.setContentDescription(getString(R.string.action_clear));
-
- searchView.setOnQueryChangeListener(new FloatingSearchView.OnQueryChangeListener() {
- @Override
- public void onSearchTextChanged(String oldQuery, String newQuery) {
- if (!oldQuery.equals("") && newQuery.equals("")) {
- searchView.clearSuggestions();
- return;
- }
-
- if (newQuery.length() < 3) {
- return;
- }
-
- searchView.showProgress();
-
- mastodonAPI.searchAccounts(newQuery, false, 5).enqueue(new Callback>() {
- @Override
- public void onResponse(Call> call, Response> response) {
- if (response.isSuccessful()) {
- searchView.swapSuggestions(response.body());
- searchView.hideProgress();
- } else {
- searchView.hideProgress();
- }
- }
-
- @Override
- public void onFailure(Call> call, Throwable t) {
- searchView.hideProgress();
- }
- });
- }
- });
-
- searchView.setOnSearchListener(new FloatingSearchView.OnSearchListener() {
- @Override
- public void onSuggestionClicked(SearchSuggestion searchSuggestion) {
- Account accountSuggestion = (Account) searchSuggestion;
- Intent intent = new Intent(MainActivity.this, AccountActivity.class);
- intent.putExtra("id", accountSuggestion.id);
- startActivity(intent);
- }
-
- @Override
- public void onSearchAction(String currentQuery) {}
- });
-
- searchView.setOnBindSuggestionCallback(new SearchSuggestionsAdapter.OnBindSuggestionCallback() {
- @Override
- public void onBindSuggestion(View suggestionView, ImageView leftIcon, TextView textView, SearchSuggestion item, int itemPosition) {
- Account accountSuggestion = ((Account) item);
-
- Picasso.with(MainActivity.this)
- .load(accountSuggestion.avatar)
- .placeholder(R.drawable.avatar_default)
- .into(leftIcon);
-
- String searchStr = accountSuggestion.getDisplayName() + " " + accountSuggestion.username;
- final SpannableStringBuilder str = new SpannableStringBuilder(searchStr);
-
- str.setSpan(new StyleSpan(Typeface.BOLD), 0, accountSuggestion.getDisplayName().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- textView.setText(str);
- textView.setMaxLines(1);
- textView.setEllipsize(TextUtils.TruncateAt.END);
- }
- });
- }
-
- private void fetchUserInfo() {
- SharedPreferences preferences = getPrivatePreferences();
- final String domain = preferences.getString("domain", null);
- String id = preferences.getString("loggedInAccountId", null);
- String username = preferences.getString("loggedInAccountUsername", null);
-
- if (id != null && username != null) {
- loggedInAccountId = id;
- loggedInAccountUsername = username;
- }
-
- mastodonAPI.accountVerifyCredentials().enqueue(new Callback() {
- @Override
- public void onResponse(Call call, Response response) {
- if (!response.isSuccessful()) {
- onFetchUserInfoFailure(new Exception(response.message()));
- return;
- }
- onFetchUserInfoSuccess(response.body(), domain);
- }
-
- @Override
- public void onFailure(Call call, Throwable t) {
- onFetchUserInfoFailure((Exception) t);
- }
- });
- }
-
- private void onFetchUserInfoSuccess(Account me, String domain) {
- // Add the header image and avatar from the account, into the navigation drawer header.
- headerResult.clear();
-
- ImageView background = headerResult.getHeaderBackgroundView();
- int backgroundWidth = background.getWidth();
- int backgroundHeight = background.getHeight();
- if (backgroundWidth == 0 || backgroundHeight == 0) {
- /* The header ImageView may not be layed out when the verify credentials call returns so
- * measure the dimensions and use those. */
- background.measure(View.MeasureSpec.EXACTLY, View.MeasureSpec.EXACTLY);
- backgroundWidth = background.getMeasuredWidth();
- backgroundHeight = background.getMeasuredHeight();
- }
-
- Picasso.with(MainActivity.this)
- .load(me.header)
- .placeholder(R.drawable.account_header_missing)
- .resize(backgroundWidth, backgroundHeight)
- .centerCrop()
- .into(background);
-
- headerResult.addProfiles(
- new ProfileDrawerItem()
- .withName(me.getDisplayName())
- .withEmail(String.format("%s@%s", me.username, domain))
- .withIcon(me.avatar)
- );
-
- // Show follow requests in the menu, if this is a locked account.
- if (me.locked) {
- PrimaryDrawerItem followRequestsItem = new PrimaryDrawerItem()
- .withIdentifier(6)
- .withName(R.string.action_view_follow_requests)
- .withSelectable(false)
- .withIcon(GoogleMaterial.Icon.gmd_person_add);
- drawer.addItemAtPosition(followRequestsItem, 3);
- }
-
- // Update the current login information.
- loggedInAccountId = me.id;
- loggedInAccountUsername = me.username;
- getPrivatePreferences().edit()
- .putString("loggedInAccountId", loggedInAccountId)
- .putString("loggedInAccountUsername", loggedInAccountUsername)
- .putBoolean("loggedInAccountLocked", me.locked)
- .apply();
- }
-
- private void onFetchUserInfoFailure(Exception exception) {
- Log.e(TAG, "Failed to fetch user info. " + exception.getMessage());
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == COMPOSE_RESULT && resultCode == ComposeActivity.RESULT_OK) {
- TimelinePagerAdapter adapter = (TimelinePagerAdapter) viewPager.getAdapter();
- if (adapter.getCurrentFragment() instanceof SFragment) {
- ((SFragment) adapter.getCurrentFragment()).onSuccessfulStatus();
- }
- }
- super.onActivityResult(requestCode, resultCode, data);
- }
-
- @Override
- public void onBackPressed() {
- if(drawer != null && drawer.isDrawerOpen()) {
- drawer.closeDrawer();
- } else if(pageHistory.size() < 2) {
- super.onBackPressed();
- } else {
- pageHistory.pop();
- viewPager.setCurrentItem(pageHistory.peek());
- }
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
- @NonNull int[] grantResults) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- TimelinePagerAdapter adapter = (TimelinePagerAdapter) viewPager.getAdapter();
- for (Fragment fragment : adapter.getRegisteredFragments()) {
- fragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
- }
- }
-
- @Override
- public void onUserRemoved(String accountId) {
- TimelinePagerAdapter adapter = (TimelinePagerAdapter) viewPager.getAdapter();
- for (Fragment fragment : adapter.getRegisteredFragments()) {
- if (fragment instanceof StatusRemoveListener) {
- StatusRemoveListener listener = (StatusRemoveListener) fragment;
- listener.removePostsByUser(accountId);
- }
- }
- }
+/* 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.NotificationManager;
+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;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.support.annotation.NonNull;
+import android.support.design.widget.FloatingActionButton;
+import android.support.design.widget.TabLayout;
+import android.support.v4.app.Fragment;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.view.ViewPager;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.StyleSpan;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+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.mikepenz.google_material_typeface_library.GoogleMaterial;
+import com.mikepenz.materialdrawer.AccountHeader;
+import com.mikepenz.materialdrawer.AccountHeaderBuilder;
+import com.mikepenz.materialdrawer.Drawer;
+import com.mikepenz.materialdrawer.DrawerBuilder;
+import com.mikepenz.materialdrawer.model.DividerDrawerItem;
+import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
+import com.mikepenz.materialdrawer.model.ProfileDrawerItem;
+import com.mikepenz.materialdrawer.model.SecondaryDrawerItem;
+import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
+import com.mikepenz.materialdrawer.model.interfaces.IProfile;
+import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader;
+import com.mikepenz.materialdrawer.util.DrawerImageLoader;
+import com.squareup.picasso.Picasso;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+
+public class MainActivity extends BaseActivity implements SFragment.OnUserRemovedListener {
+ private static final String TAG = "MainActivity"; // logging tag
+ protected static int COMPOSE_RESULT = 1;
+
+ private String loggedInAccountId;
+ private String loggedInAccountUsername;
+ private Stack pageHistory;
+ private AccountHeader headerResult;
+ private Drawer drawer;
+
+ @BindView(R.id.floating_search_view) FloatingSearchView searchView;
+ @BindView(R.id.floating_btn) FloatingActionButton floatingBtn;
+ @BindView(R.id.tab_layout) TabLayout tabLayout;
+ @BindView(R.id.pager) ViewPager viewPager;
+
+ FloatingActionButton composeButton;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ pageHistory = new Stack<>();
+ if (savedInstanceState != null) {
+ List restoredHistory = savedInstanceState.getIntegerArrayList("pageHistory");
+ if (restoredHistory != null) {
+ pageHistory.addAll(restoredHistory);
+ }
+ }
+
+ ButterKnife.bind(this);
+
+ floatingBtn.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(getApplicationContext(), ComposeActivity.class);
+ startActivityForResult(intent, COMPOSE_RESULT);
+ }
+ });
+
+ setupDrawer();
+ setupSearchView();
+
+ /* Fetch user info while we're doing other things. This has to be after setting up the
+ * drawer, though, because its callback touches the header in the drawer. */
+ fetchUserInfo();
+
+ // Setup the tabs and timeline pager.
+ TimelinePagerAdapter adapter = new TimelinePagerAdapter(getSupportFragmentManager());
+
+ int pageMargin = getResources().getDimensionPixelSize(R.dimen.tab_page_margin);
+ viewPager.setPageMargin(pageMargin);
+ Drawable pageMarginDrawable = ThemeUtils.getDrawable(this, R.attr.tab_page_margin_drawable,
+ R.drawable.tab_page_margin_dark);
+ viewPager.setPageMarginDrawable(pageMarginDrawable);
+ viewPager.setAdapter(adapter);
+
+ tabLayout.setupWithViewPager(viewPager);
+
+ int[] tabIcons = {
+ R.drawable.ic_home_24dp,
+ R.drawable.ic_notifications_24dp,
+ R.drawable.ic_local_24dp,
+ R.drawable.ic_public_24dp,
+ };
+ String[] pageTitles = {
+ getString(R.string.title_home),
+ getString(R.string.title_notifications),
+ getString(R.string.title_public_local),
+ getString(R.string.title_public_federated),
+ };
+ for (int i = 0; i < 4; i++) {
+ TabLayout.Tab tab = tabLayout.getTabAt(i);
+ tab.setIcon(tabIcons[i]);
+ tab.setContentDescription(pageTitles[i]);
+ }
+
+ tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
+ @Override
+ public void onTabSelected(TabLayout.Tab tab) {
+ viewPager.setCurrentItem(tab.getPosition());
+
+ if (pageHistory.isEmpty()) {
+ pageHistory.push(0);
+ }
+
+ if (pageHistory.contains(tab.getPosition())) {
+ pageHistory.remove(pageHistory.indexOf(tab.getPosition()));
+ }
+
+ pageHistory.push(tab.getPosition());
+ tintTab(tab, true);
+ }
+
+ @Override
+ public void onTabUnselected(TabLayout.Tab tab) {
+ tintTab(tab, false);
+ }
+
+ @Override
+ public void onTabReselected(TabLayout.Tab tab) {}
+ });
+
+ Intent intent = getIntent();
+
+ int tabSelected = 0;
+ if (intent != null) {
+ int tabPosition = intent.getIntExtra("tab_position", 0);
+ if (tabPosition != 0) {
+ TabLayout.Tab tab = tabLayout.getTabAt(tabPosition);
+ if (tab != null) {
+ tab.select();
+ tabSelected = tabPosition;
+ }
+ }
+ }
+ for (int i = 0; i < 4; i++) {
+ tintTab(tabLayout.getTabAt(i), i == tabSelected);
+ }
+
+ // Setup push notifications
+ if (arePushNotificationsEnabled()) {
+ enablePushNotifications();
+ } else {
+ disablePushNotifications();
+ }
+
+ composeButton = floatingBtn;
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ SharedPreferences notificationPreferences = getApplicationContext()
+ .getSharedPreferences("Notifications", MODE_PRIVATE);
+ notificationPreferences.edit()
+ .putString("current", "[]")
+ .apply();
+
+ ((NotificationManager) (getSystemService(NOTIFICATION_SERVICE)))
+ .cancel(MessagingService.NOTIFY_ID);
+
+ /* After editing a profile, the profile header in the navigation drawer needs to be
+ * refreshed */
+ SharedPreferences preferences = getPrivatePreferences();
+ if (preferences.getBoolean("refreshProfileHeader", false)) {
+ fetchUserInfo();
+ preferences.edit()
+ .putBoolean("refreshProfileHeader", false)
+ .apply();
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
+ ArrayList pageHistoryList = new ArrayList<>();
+ pageHistoryList.addAll(pageHistory);
+ outState.putIntegerArrayList("pageHistory", pageHistoryList);
+ super.onSaveInstanceState(outState, outPersistentState);
+ }
+
+ private void tintTab(TabLayout.Tab tab, boolean tinted) {
+ int color = (tinted) ? R.attr.tab_icon_selected_tint : R.attr.toolbar_icon_tint;
+ ThemeUtils.setDrawableTint(this, tab.getIcon(), color);
+ }
+
+ private void setupDrawer() {
+ headerResult = new AccountHeaderBuilder()
+ .withActivity(this)
+ .withSelectionListEnabledForSingleProfile(false)
+ .withDividerBelowHeader(false)
+ .withOnAccountHeaderProfileImageListener(new AccountHeader.OnAccountHeaderProfileImageListener() {
+ @Override
+ public boolean onProfileImageClick(View view, IProfile profile, boolean current) {
+ if (current && loggedInAccountId != null) {
+ Intent intent = new Intent(MainActivity.this, AccountActivity.class);
+ intent.putExtra("id", loggedInAccountId);
+ startActivity(intent);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onProfileImageLongClick(View view, IProfile profile, boolean current) {
+ return false;
+ }
+ })
+ .withCompactStyle(true)
+ .build();
+ headerResult.getView()
+ .findViewById(R.id.material_drawer_account_header_current)
+ .setContentDescription(getString(R.string.action_view_profile));
+
+ DrawerImageLoader.init(new AbstractDrawerImageLoader() {
+ @Override
+ public void set(ImageView imageView, Uri uri, Drawable placeholder) {
+ Picasso.with(imageView.getContext()).load(uri).placeholder(placeholder).into(imageView);
+ }
+
+ @Override
+ public void cancel(ImageView imageView) {
+ Picasso.with(imageView.getContext()).cancelRequest(imageView);
+ }
+ });
+
+ Drawable muteDrawable = ContextCompat.getDrawable(this, R.drawable.ic_mute_24dp);
+ ThemeUtils.setDrawableTint(this, muteDrawable, R.attr.toolbar_icon_tint);
+
+ drawer = new DrawerBuilder()
+ .withActivity(this)
+ //.withToolbar(toolbar)
+ .withAccountHeader(headerResult)
+ .withHasStableIds(true)
+ .withSelectedItem(-1)
+ .addDrawerItems(
+ new PrimaryDrawerItem().withIdentifier(0).withName(getString(R.string.action_edit_profile)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_person),
+ new PrimaryDrawerItem().withIdentifier(1).withName(getString(R.string.action_view_favourites)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_star),
+ new PrimaryDrawerItem().withIdentifier(2).withName(getString(R.string.action_view_mutes)).withSelectable(false).withIcon(muteDrawable),
+ 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)
+ )
+ .withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
+ @Override
+ public boolean onItemClick(View view, int position, IDrawerItem drawerItem) {
+ if (drawerItem != null) {
+ long drawerItemIdentifier = drawerItem.getIdentifier();
+
+ if (drawerItemIdentifier == 0) {
+ Intent intent = new Intent(MainActivity.this, EditProfileActivity.class);
+ startActivity(intent);
+ } else if (drawerItemIdentifier == 1) {
+ Intent intent = new Intent(MainActivity.this, FavouritesActivity.class);
+ startActivity(intent);
+ } else if (drawerItemIdentifier == 2) {
+ Intent intent = new Intent(MainActivity.this, AccountListActivity.class);
+ intent.putExtra("type", AccountListActivity.Type.MUTES);
+ startActivity(intent);
+ } else if (drawerItemIdentifier == 3) {
+ Intent intent = new Intent(MainActivity.this, AccountListActivity.class);
+ intent.putExtra("type", AccountListActivity.Type.BLOCKS);
+ startActivity(intent);
+ } else if (drawerItemIdentifier == 4) {
+ Intent intent = new Intent(MainActivity.this, PreferencesActivity.class);
+ startActivity(intent);
+ } else if (drawerItemIdentifier == 5) {
+ logout();
+ } else if (drawerItemIdentifier == 6) {
+ Intent intent = new Intent(MainActivity.this, AccountListActivity.class);
+ intent.putExtra("type", AccountListActivity.Type.FOLLOW_REQUESTS);
+ startActivity(intent);
+ }
+ }
+
+ return false;
+ }
+ })
+ .build();
+ }
+
+ private void logout() {
+ if (arePushNotificationsEnabled()) disablePushNotifications();
+
+ getPrivatePreferences().edit()
+ .remove("domain")
+ .remove("accessToken")
+ .apply();
+
+ Intent intent = new Intent(MainActivity.this, LoginActivity.class);
+ startActivity(intent);
+ finish();
+ }
+
+ private void setupSearchView() {
+ searchView.attachNavigationDrawerToMenuButton(drawer.getDrawerLayout());
+
+ // Setup content descriptions for the different elements in the search view.
+ final View leftAction = searchView.findViewById(R.id.left_action);
+ leftAction.setContentDescription(getString(R.string.action_open_drawer));
+ searchView.setOnFocusChangeListener(new FloatingSearchView.OnFocusChangeListener() {
+ @Override
+ public void onFocus() {
+ leftAction.setContentDescription(getString(R.string.action_close));
+ }
+
+ @Override
+ public void onFocusCleared() {
+ leftAction.setContentDescription(getString(R.string.action_open_drawer));
+ }
+ });
+ View clearButton = searchView.findViewById(R.id.clear_btn);
+ clearButton.setContentDescription(getString(R.string.action_clear));
+
+ searchView.setOnQueryChangeListener(new FloatingSearchView.OnQueryChangeListener() {
+ @Override
+ public void onSearchTextChanged(String oldQuery, String newQuery) {
+ if (!oldQuery.equals("") && newQuery.equals("")) {
+ searchView.clearSuggestions();
+ return;
+ }
+
+ if (newQuery.length() < 3) {
+ return;
+ }
+
+ searchView.showProgress();
+
+ mastodonAPI.searchAccounts(newQuery, false, 5).enqueue(new Callback>() {
+ @Override
+ public void onResponse(Call> call, Response> response) {
+ if (response.isSuccessful()) {
+ searchView.swapSuggestions(response.body());
+ searchView.hideProgress();
+ } else {
+ searchView.hideProgress();
+ }
+ }
+
+ @Override
+ public void onFailure(Call> call, Throwable t) {
+ searchView.hideProgress();
+ }
+ });
+ }
+ });
+
+ searchView.setOnSearchListener(new FloatingSearchView.OnSearchListener() {
+ @Override
+ public void onSuggestionClicked(SearchSuggestion searchSuggestion) {
+ Account accountSuggestion = (Account) searchSuggestion;
+ Intent intent = new Intent(MainActivity.this, AccountActivity.class);
+ intent.putExtra("id", accountSuggestion.id);
+ startActivity(intent);
+ }
+
+ @Override
+ public void onSearchAction(String currentQuery) {}
+ });
+
+ searchView.setOnBindSuggestionCallback(new SearchSuggestionsAdapter.OnBindSuggestionCallback() {
+ @Override
+ public void onBindSuggestion(View suggestionView, ImageView leftIcon, TextView textView, SearchSuggestion item, int itemPosition) {
+ Account accountSuggestion = ((Account) item);
+
+ Picasso.with(MainActivity.this)
+ .load(accountSuggestion.avatar)
+ .placeholder(R.drawable.avatar_default)
+ .into(leftIcon);
+
+ String searchStr = accountSuggestion.getDisplayName() + " " + accountSuggestion.username;
+ final SpannableStringBuilder str = new SpannableStringBuilder(searchStr);
+
+ str.setSpan(new StyleSpan(Typeface.BOLD), 0, accountSuggestion.getDisplayName().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ textView.setText(str);
+ textView.setMaxLines(1);
+ textView.setEllipsize(TextUtils.TruncateAt.END);
+ }
+ });
+ }
+
+ private void fetchUserInfo() {
+ SharedPreferences preferences = getPrivatePreferences();
+ final String domain = preferences.getString("domain", null);
+ String id = preferences.getString("loggedInAccountId", null);
+ String username = preferences.getString("loggedInAccountUsername", null);
+
+ if (id != null && username != null) {
+ loggedInAccountId = id;
+ loggedInAccountUsername = username;
+ }
+
+ mastodonAPI.accountVerifyCredentials().enqueue(new Callback() {
+ @Override
+ public void onResponse(Call call, Response response) {
+ if (!response.isSuccessful()) {
+ onFetchUserInfoFailure(new Exception(response.message()));
+ return;
+ }
+ onFetchUserInfoSuccess(response.body(), domain);
+ }
+
+ @Override
+ public void onFailure(Call call, Throwable t) {
+ onFetchUserInfoFailure((Exception) t);
+ }
+ });
+ }
+
+ private void onFetchUserInfoSuccess(Account me, String domain) {
+ // Add the header image and avatar from the account, into the navigation drawer header.
+ headerResult.clear();
+
+ ImageView background = headerResult.getHeaderBackgroundView();
+ int backgroundWidth = background.getWidth();
+ int backgroundHeight = background.getHeight();
+ if (backgroundWidth == 0 || backgroundHeight == 0) {
+ /* The header ImageView may not be layed out when the verify credentials call returns so
+ * measure the dimensions and use those. */
+ background.measure(View.MeasureSpec.EXACTLY, View.MeasureSpec.EXACTLY);
+ backgroundWidth = background.getMeasuredWidth();
+ backgroundHeight = background.getMeasuredHeight();
+ }
+
+ Picasso.with(MainActivity.this)
+ .load(me.header)
+ .placeholder(R.drawable.account_header_missing)
+ .resize(backgroundWidth, backgroundHeight)
+ .centerCrop()
+ .into(background);
+
+ headerResult.addProfiles(
+ new ProfileDrawerItem()
+ .withName(me.getDisplayName())
+ .withEmail(String.format("%s@%s", me.username, domain))
+ .withIcon(me.avatar)
+ );
+
+ // Show follow requests in the menu, if this is a locked account.
+ if (me.locked) {
+ PrimaryDrawerItem followRequestsItem = new PrimaryDrawerItem()
+ .withIdentifier(6)
+ .withName(R.string.action_view_follow_requests)
+ .withSelectable(false)
+ .withIcon(GoogleMaterial.Icon.gmd_person_add);
+ drawer.addItemAtPosition(followRequestsItem, 3);
+ }
+
+ // Update the current login information.
+ loggedInAccountId = me.id;
+ loggedInAccountUsername = me.username;
+ getPrivatePreferences().edit()
+ .putString("loggedInAccountId", loggedInAccountId)
+ .putString("loggedInAccountUsername", loggedInAccountUsername)
+ .putBoolean("loggedInAccountLocked", me.locked)
+ .apply();
+ }
+
+ private void onFetchUserInfoFailure(Exception exception) {
+ Log.e(TAG, "Failed to fetch user info. " + exception.getMessage());
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == COMPOSE_RESULT && resultCode == ComposeActivity.RESULT_OK) {
+ TimelinePagerAdapter adapter = (TimelinePagerAdapter) viewPager.getAdapter();
+ if (adapter.getCurrentFragment() instanceof SFragment) {
+ ((SFragment) adapter.getCurrentFragment()).onSuccessfulStatus();
+ }
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if(drawer != null && drawer.isDrawerOpen()) {
+ drawer.closeDrawer();
+ } else if(pageHistory.size() < 2) {
+ super.onBackPressed();
+ } else {
+ pageHistory.pop();
+ viewPager.setCurrentItem(pageHistory.peek());
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+ @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ TimelinePagerAdapter adapter = (TimelinePagerAdapter) viewPager.getAdapter();
+ for (Fragment fragment : adapter.getRegisteredFragments()) {
+ fragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+ }
+
+ @Override
+ public void onUserRemoved(String accountId) {
+ TimelinePagerAdapter adapter = (TimelinePagerAdapter) viewPager.getAdapter();
+ for (Fragment fragment : adapter.getRegisteredFragments()) {
+ if (fragment instanceof StatusRemoveListener) {
+ StatusRemoveListener listener = (StatusRemoveListener) fragment;
+ listener.removePostsByUser(accountId);
+ }
+ }
+ }
+
+ // 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
From b047204577327748c10e92cd4eb56adc6813d3e5 Mon Sep 17 00:00:00 2001
From: Vavassor
Date: Wed, 3 May 2017 14:29:33 -0400
Subject: [PATCH 10/53] fixes line endings back to LF
---
app/src/main/AndroidManifest.xml | 200 +--
.../com/keylesspalace/tusky/MainActivity.java | 1124 ++++++++---------
2 files changed, 662 insertions(+), 662 deletions(-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f2250c65..830c6695 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,101 +1,101 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
index 1f9f1605..caaed452 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
@@ -1,563 +1,563 @@
-/* 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.NotificationManager;
-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;
-import android.os.Bundle;
-import android.os.PersistableBundle;
-import android.support.annotation.NonNull;
-import android.support.design.widget.FloatingActionButton;
-import android.support.design.widget.TabLayout;
-import android.support.v4.app.Fragment;
-import android.support.v4.content.ContextCompat;
-import android.support.v4.view.ViewPager;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.style.StyleSpan;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-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.mikepenz.google_material_typeface_library.GoogleMaterial;
-import com.mikepenz.materialdrawer.AccountHeader;
-import com.mikepenz.materialdrawer.AccountHeaderBuilder;
-import com.mikepenz.materialdrawer.Drawer;
-import com.mikepenz.materialdrawer.DrawerBuilder;
-import com.mikepenz.materialdrawer.model.DividerDrawerItem;
-import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
-import com.mikepenz.materialdrawer.model.ProfileDrawerItem;
-import com.mikepenz.materialdrawer.model.SecondaryDrawerItem;
-import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
-import com.mikepenz.materialdrawer.model.interfaces.IProfile;
-import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader;
-import com.mikepenz.materialdrawer.util.DrawerImageLoader;
-import com.squareup.picasso.Picasso;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Stack;
-
-import butterknife.BindView;
-import butterknife.ButterKnife;
-import retrofit2.Call;
-import retrofit2.Callback;
-import retrofit2.Response;
-
-public class MainActivity extends BaseActivity implements SFragment.OnUserRemovedListener {
- private static final String TAG = "MainActivity"; // logging tag
- protected static int COMPOSE_RESULT = 1;
-
- private String loggedInAccountId;
- private String loggedInAccountUsername;
- private Stack pageHistory;
- private AccountHeader headerResult;
- private Drawer drawer;
-
- @BindView(R.id.floating_search_view) FloatingSearchView searchView;
- @BindView(R.id.floating_btn) FloatingActionButton floatingBtn;
- @BindView(R.id.tab_layout) TabLayout tabLayout;
- @BindView(R.id.pager) ViewPager viewPager;
-
- FloatingActionButton composeButton;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-
- pageHistory = new Stack<>();
- if (savedInstanceState != null) {
- List restoredHistory = savedInstanceState.getIntegerArrayList("pageHistory");
- if (restoredHistory != null) {
- pageHistory.addAll(restoredHistory);
- }
- }
-
- ButterKnife.bind(this);
-
- floatingBtn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent intent = new Intent(getApplicationContext(), ComposeActivity.class);
- startActivityForResult(intent, COMPOSE_RESULT);
- }
- });
-
- setupDrawer();
- setupSearchView();
-
- /* Fetch user info while we're doing other things. This has to be after setting up the
- * drawer, though, because its callback touches the header in the drawer. */
- fetchUserInfo();
-
- // Setup the tabs and timeline pager.
- TimelinePagerAdapter adapter = new TimelinePagerAdapter(getSupportFragmentManager());
-
- int pageMargin = getResources().getDimensionPixelSize(R.dimen.tab_page_margin);
- viewPager.setPageMargin(pageMargin);
- Drawable pageMarginDrawable = ThemeUtils.getDrawable(this, R.attr.tab_page_margin_drawable,
- R.drawable.tab_page_margin_dark);
- viewPager.setPageMarginDrawable(pageMarginDrawable);
- viewPager.setAdapter(adapter);
-
- tabLayout.setupWithViewPager(viewPager);
-
- int[] tabIcons = {
- R.drawable.ic_home_24dp,
- R.drawable.ic_notifications_24dp,
- R.drawable.ic_local_24dp,
- R.drawable.ic_public_24dp,
- };
- String[] pageTitles = {
- getString(R.string.title_home),
- getString(R.string.title_notifications),
- getString(R.string.title_public_local),
- getString(R.string.title_public_federated),
- };
- for (int i = 0; i < 4; i++) {
- TabLayout.Tab tab = tabLayout.getTabAt(i);
- tab.setIcon(tabIcons[i]);
- tab.setContentDescription(pageTitles[i]);
- }
-
- tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
- @Override
- public void onTabSelected(TabLayout.Tab tab) {
- viewPager.setCurrentItem(tab.getPosition());
-
- if (pageHistory.isEmpty()) {
- pageHistory.push(0);
- }
-
- if (pageHistory.contains(tab.getPosition())) {
- pageHistory.remove(pageHistory.indexOf(tab.getPosition()));
- }
-
- pageHistory.push(tab.getPosition());
- tintTab(tab, true);
- }
-
- @Override
- public void onTabUnselected(TabLayout.Tab tab) {
- tintTab(tab, false);
- }
-
- @Override
- public void onTabReselected(TabLayout.Tab tab) {}
- });
-
- Intent intent = getIntent();
-
- int tabSelected = 0;
- if (intent != null) {
- int tabPosition = intent.getIntExtra("tab_position", 0);
- if (tabPosition != 0) {
- TabLayout.Tab tab = tabLayout.getTabAt(tabPosition);
- if (tab != null) {
- tab.select();
- tabSelected = tabPosition;
- }
- }
- }
- for (int i = 0; i < 4; i++) {
- tintTab(tabLayout.getTabAt(i), i == tabSelected);
- }
-
- // Setup push notifications
- if (arePushNotificationsEnabled()) {
- enablePushNotifications();
- } else {
- disablePushNotifications();
- }
-
- composeButton = floatingBtn;
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- SharedPreferences notificationPreferences = getApplicationContext()
- .getSharedPreferences("Notifications", MODE_PRIVATE);
- notificationPreferences.edit()
- .putString("current", "[]")
- .apply();
-
- ((NotificationManager) (getSystemService(NOTIFICATION_SERVICE)))
- .cancel(MessagingService.NOTIFY_ID);
-
- /* After editing a profile, the profile header in the navigation drawer needs to be
- * refreshed */
- SharedPreferences preferences = getPrivatePreferences();
- if (preferences.getBoolean("refreshProfileHeader", false)) {
- fetchUserInfo();
- preferences.edit()
- .putBoolean("refreshProfileHeader", false)
- .apply();
- }
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
- ArrayList pageHistoryList = new ArrayList<>();
- pageHistoryList.addAll(pageHistory);
- outState.putIntegerArrayList("pageHistory", pageHistoryList);
- super.onSaveInstanceState(outState, outPersistentState);
- }
-
- private void tintTab(TabLayout.Tab tab, boolean tinted) {
- int color = (tinted) ? R.attr.tab_icon_selected_tint : R.attr.toolbar_icon_tint;
- ThemeUtils.setDrawableTint(this, tab.getIcon(), color);
- }
-
- private void setupDrawer() {
- headerResult = new AccountHeaderBuilder()
- .withActivity(this)
- .withSelectionListEnabledForSingleProfile(false)
- .withDividerBelowHeader(false)
- .withOnAccountHeaderProfileImageListener(new AccountHeader.OnAccountHeaderProfileImageListener() {
- @Override
- public boolean onProfileImageClick(View view, IProfile profile, boolean current) {
- if (current && loggedInAccountId != null) {
- Intent intent = new Intent(MainActivity.this, AccountActivity.class);
- intent.putExtra("id", loggedInAccountId);
- startActivity(intent);
- return true;
- }
- return false;
- }
-
- @Override
- public boolean onProfileImageLongClick(View view, IProfile profile, boolean current) {
- return false;
- }
- })
- .withCompactStyle(true)
- .build();
- headerResult.getView()
- .findViewById(R.id.material_drawer_account_header_current)
- .setContentDescription(getString(R.string.action_view_profile));
-
- DrawerImageLoader.init(new AbstractDrawerImageLoader() {
- @Override
- public void set(ImageView imageView, Uri uri, Drawable placeholder) {
- Picasso.with(imageView.getContext()).load(uri).placeholder(placeholder).into(imageView);
- }
-
- @Override
- public void cancel(ImageView imageView) {
- Picasso.with(imageView.getContext()).cancelRequest(imageView);
- }
- });
-
- Drawable muteDrawable = ContextCompat.getDrawable(this, R.drawable.ic_mute_24dp);
- ThemeUtils.setDrawableTint(this, muteDrawable, R.attr.toolbar_icon_tint);
-
- drawer = new DrawerBuilder()
- .withActivity(this)
- //.withToolbar(toolbar)
- .withAccountHeader(headerResult)
- .withHasStableIds(true)
- .withSelectedItem(-1)
- .addDrawerItems(
- new PrimaryDrawerItem().withIdentifier(0).withName(getString(R.string.action_edit_profile)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_person),
- new PrimaryDrawerItem().withIdentifier(1).withName(getString(R.string.action_view_favourites)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_star),
- new PrimaryDrawerItem().withIdentifier(2).withName(getString(R.string.action_view_mutes)).withSelectable(false).withIcon(muteDrawable),
- 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)
- )
- .withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
- @Override
- public boolean onItemClick(View view, int position, IDrawerItem drawerItem) {
- if (drawerItem != null) {
- long drawerItemIdentifier = drawerItem.getIdentifier();
-
- if (drawerItemIdentifier == 0) {
- Intent intent = new Intent(MainActivity.this, EditProfileActivity.class);
- startActivity(intent);
- } else if (drawerItemIdentifier == 1) {
- Intent intent = new Intent(MainActivity.this, FavouritesActivity.class);
- startActivity(intent);
- } else if (drawerItemIdentifier == 2) {
- Intent intent = new Intent(MainActivity.this, AccountListActivity.class);
- intent.putExtra("type", AccountListActivity.Type.MUTES);
- startActivity(intent);
- } else if (drawerItemIdentifier == 3) {
- Intent intent = new Intent(MainActivity.this, AccountListActivity.class);
- intent.putExtra("type", AccountListActivity.Type.BLOCKS);
- startActivity(intent);
- } else if (drawerItemIdentifier == 4) {
- Intent intent = new Intent(MainActivity.this, PreferencesActivity.class);
- startActivity(intent);
- } else if (drawerItemIdentifier == 5) {
- logout();
- } else if (drawerItemIdentifier == 6) {
- Intent intent = new Intent(MainActivity.this, AccountListActivity.class);
- intent.putExtra("type", AccountListActivity.Type.FOLLOW_REQUESTS);
- startActivity(intent);
- }
- }
-
- return false;
- }
- })
- .build();
- }
-
- private void logout() {
- if (arePushNotificationsEnabled()) disablePushNotifications();
-
- getPrivatePreferences().edit()
- .remove("domain")
- .remove("accessToken")
- .apply();
-
- Intent intent = new Intent(MainActivity.this, LoginActivity.class);
- startActivity(intent);
- finish();
- }
-
- private void setupSearchView() {
- searchView.attachNavigationDrawerToMenuButton(drawer.getDrawerLayout());
-
- // Setup content descriptions for the different elements in the search view.
- final View leftAction = searchView.findViewById(R.id.left_action);
- leftAction.setContentDescription(getString(R.string.action_open_drawer));
- searchView.setOnFocusChangeListener(new FloatingSearchView.OnFocusChangeListener() {
- @Override
- public void onFocus() {
- leftAction.setContentDescription(getString(R.string.action_close));
- }
-
- @Override
- public void onFocusCleared() {
- leftAction.setContentDescription(getString(R.string.action_open_drawer));
- }
- });
- View clearButton = searchView.findViewById(R.id.clear_btn);
- clearButton.setContentDescription(getString(R.string.action_clear));
-
- searchView.setOnQueryChangeListener(new FloatingSearchView.OnQueryChangeListener() {
- @Override
- public void onSearchTextChanged(String oldQuery, String newQuery) {
- if (!oldQuery.equals("") && newQuery.equals("")) {
- searchView.clearSuggestions();
- return;
- }
-
- if (newQuery.length() < 3) {
- return;
- }
-
- searchView.showProgress();
-
- mastodonAPI.searchAccounts(newQuery, false, 5).enqueue(new Callback>() {
- @Override
- public void onResponse(Call> call, Response> response) {
- if (response.isSuccessful()) {
- searchView.swapSuggestions(response.body());
- searchView.hideProgress();
- } else {
- searchView.hideProgress();
- }
- }
-
- @Override
- public void onFailure(Call> call, Throwable t) {
- searchView.hideProgress();
- }
- });
- }
- });
-
- searchView.setOnSearchListener(new FloatingSearchView.OnSearchListener() {
- @Override
- public void onSuggestionClicked(SearchSuggestion searchSuggestion) {
- Account accountSuggestion = (Account) searchSuggestion;
- Intent intent = new Intent(MainActivity.this, AccountActivity.class);
- intent.putExtra("id", accountSuggestion.id);
- startActivity(intent);
- }
-
- @Override
- public void onSearchAction(String currentQuery) {}
- });
-
- searchView.setOnBindSuggestionCallback(new SearchSuggestionsAdapter.OnBindSuggestionCallback() {
- @Override
- public void onBindSuggestion(View suggestionView, ImageView leftIcon, TextView textView, SearchSuggestion item, int itemPosition) {
- Account accountSuggestion = ((Account) item);
-
- Picasso.with(MainActivity.this)
- .load(accountSuggestion.avatar)
- .placeholder(R.drawable.avatar_default)
- .into(leftIcon);
-
- String searchStr = accountSuggestion.getDisplayName() + " " + accountSuggestion.username;
- final SpannableStringBuilder str = new SpannableStringBuilder(searchStr);
-
- str.setSpan(new StyleSpan(Typeface.BOLD), 0, accountSuggestion.getDisplayName().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- textView.setText(str);
- textView.setMaxLines(1);
- textView.setEllipsize(TextUtils.TruncateAt.END);
- }
- });
- }
-
- private void fetchUserInfo() {
- SharedPreferences preferences = getPrivatePreferences();
- final String domain = preferences.getString("domain", null);
- String id = preferences.getString("loggedInAccountId", null);
- String username = preferences.getString("loggedInAccountUsername", null);
-
- if (id != null && username != null) {
- loggedInAccountId = id;
- loggedInAccountUsername = username;
- }
-
- mastodonAPI.accountVerifyCredentials().enqueue(new Callback() {
- @Override
- public void onResponse(Call call, Response response) {
- if (!response.isSuccessful()) {
- onFetchUserInfoFailure(new Exception(response.message()));
- return;
- }
- onFetchUserInfoSuccess(response.body(), domain);
- }
-
- @Override
- public void onFailure(Call call, Throwable t) {
- onFetchUserInfoFailure((Exception) t);
- }
- });
- }
-
- private void onFetchUserInfoSuccess(Account me, String domain) {
- // Add the header image and avatar from the account, into the navigation drawer header.
- headerResult.clear();
-
- ImageView background = headerResult.getHeaderBackgroundView();
- int backgroundWidth = background.getWidth();
- int backgroundHeight = background.getHeight();
- if (backgroundWidth == 0 || backgroundHeight == 0) {
- /* The header ImageView may not be layed out when the verify credentials call returns so
- * measure the dimensions and use those. */
- background.measure(View.MeasureSpec.EXACTLY, View.MeasureSpec.EXACTLY);
- backgroundWidth = background.getMeasuredWidth();
- backgroundHeight = background.getMeasuredHeight();
- }
-
- Picasso.with(MainActivity.this)
- .load(me.header)
- .placeholder(R.drawable.account_header_missing)
- .resize(backgroundWidth, backgroundHeight)
- .centerCrop()
- .into(background);
-
- headerResult.addProfiles(
- new ProfileDrawerItem()
- .withName(me.getDisplayName())
- .withEmail(String.format("%s@%s", me.username, domain))
- .withIcon(me.avatar)
- );
-
- // Show follow requests in the menu, if this is a locked account.
- if (me.locked) {
- PrimaryDrawerItem followRequestsItem = new PrimaryDrawerItem()
- .withIdentifier(6)
- .withName(R.string.action_view_follow_requests)
- .withSelectable(false)
- .withIcon(GoogleMaterial.Icon.gmd_person_add);
- drawer.addItemAtPosition(followRequestsItem, 3);
- }
-
- // Update the current login information.
- loggedInAccountId = me.id;
- loggedInAccountUsername = me.username;
- getPrivatePreferences().edit()
- .putString("loggedInAccountId", loggedInAccountId)
- .putString("loggedInAccountUsername", loggedInAccountUsername)
- .putBoolean("loggedInAccountLocked", me.locked)
- .apply();
- }
-
- private void onFetchUserInfoFailure(Exception exception) {
- Log.e(TAG, "Failed to fetch user info. " + exception.getMessage());
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == COMPOSE_RESULT && resultCode == ComposeActivity.RESULT_OK) {
- TimelinePagerAdapter adapter = (TimelinePagerAdapter) viewPager.getAdapter();
- if (adapter.getCurrentFragment() instanceof SFragment) {
- ((SFragment) adapter.getCurrentFragment()).onSuccessfulStatus();
- }
- }
- super.onActivityResult(requestCode, resultCode, data);
- }
-
- @Override
- public void onBackPressed() {
- if(drawer != null && drawer.isDrawerOpen()) {
- drawer.closeDrawer();
- } else if(pageHistory.size() < 2) {
- super.onBackPressed();
- } else {
- pageHistory.pop();
- viewPager.setCurrentItem(pageHistory.peek());
- }
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
- @NonNull int[] grantResults) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- TimelinePagerAdapter adapter = (TimelinePagerAdapter) viewPager.getAdapter();
- for (Fragment fragment : adapter.getRegisteredFragments()) {
- fragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
- }
- }
-
- @Override
- public void onUserRemoved(String accountId) {
- TimelinePagerAdapter adapter = (TimelinePagerAdapter) viewPager.getAdapter();
- for (Fragment fragment : adapter.getRegisteredFragments()) {
- if (fragment instanceof StatusRemoveListener) {
- StatusRemoveListener listener = (StatusRemoveListener) fragment;
- listener.removePostsByUser(accountId);
- }
- }
- }
-
- // Fix for GitHub issues #190, #259 (MainActivity won't restart on screen rotation.)
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- }
+/* 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.NotificationManager;
+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;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.support.annotation.NonNull;
+import android.support.design.widget.FloatingActionButton;
+import android.support.design.widget.TabLayout;
+import android.support.v4.app.Fragment;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.view.ViewPager;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.StyleSpan;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+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.mikepenz.google_material_typeface_library.GoogleMaterial;
+import com.mikepenz.materialdrawer.AccountHeader;
+import com.mikepenz.materialdrawer.AccountHeaderBuilder;
+import com.mikepenz.materialdrawer.Drawer;
+import com.mikepenz.materialdrawer.DrawerBuilder;
+import com.mikepenz.materialdrawer.model.DividerDrawerItem;
+import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
+import com.mikepenz.materialdrawer.model.ProfileDrawerItem;
+import com.mikepenz.materialdrawer.model.SecondaryDrawerItem;
+import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
+import com.mikepenz.materialdrawer.model.interfaces.IProfile;
+import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader;
+import com.mikepenz.materialdrawer.util.DrawerImageLoader;
+import com.squareup.picasso.Picasso;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+
+public class MainActivity extends BaseActivity implements SFragment.OnUserRemovedListener {
+ private static final String TAG = "MainActivity"; // logging tag
+ protected static int COMPOSE_RESULT = 1;
+
+ private String loggedInAccountId;
+ private String loggedInAccountUsername;
+ private Stack pageHistory;
+ private AccountHeader headerResult;
+ private Drawer drawer;
+
+ @BindView(R.id.floating_search_view) FloatingSearchView searchView;
+ @BindView(R.id.floating_btn) FloatingActionButton floatingBtn;
+ @BindView(R.id.tab_layout) TabLayout tabLayout;
+ @BindView(R.id.pager) ViewPager viewPager;
+
+ FloatingActionButton composeButton;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ pageHistory = new Stack<>();
+ if (savedInstanceState != null) {
+ List restoredHistory = savedInstanceState.getIntegerArrayList("pageHistory");
+ if (restoredHistory != null) {
+ pageHistory.addAll(restoredHistory);
+ }
+ }
+
+ ButterKnife.bind(this);
+
+ floatingBtn.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(getApplicationContext(), ComposeActivity.class);
+ startActivityForResult(intent, COMPOSE_RESULT);
+ }
+ });
+
+ setupDrawer();
+ setupSearchView();
+
+ /* Fetch user info while we're doing other things. This has to be after setting up the
+ * drawer, though, because its callback touches the header in the drawer. */
+ fetchUserInfo();
+
+ // Setup the tabs and timeline pager.
+ TimelinePagerAdapter adapter = new TimelinePagerAdapter(getSupportFragmentManager());
+
+ int pageMargin = getResources().getDimensionPixelSize(R.dimen.tab_page_margin);
+ viewPager.setPageMargin(pageMargin);
+ Drawable pageMarginDrawable = ThemeUtils.getDrawable(this, R.attr.tab_page_margin_drawable,
+ R.drawable.tab_page_margin_dark);
+ viewPager.setPageMarginDrawable(pageMarginDrawable);
+ viewPager.setAdapter(adapter);
+
+ tabLayout.setupWithViewPager(viewPager);
+
+ int[] tabIcons = {
+ R.drawable.ic_home_24dp,
+ R.drawable.ic_notifications_24dp,
+ R.drawable.ic_local_24dp,
+ R.drawable.ic_public_24dp,
+ };
+ String[] pageTitles = {
+ getString(R.string.title_home),
+ getString(R.string.title_notifications),
+ getString(R.string.title_public_local),
+ getString(R.string.title_public_federated),
+ };
+ for (int i = 0; i < 4; i++) {
+ TabLayout.Tab tab = tabLayout.getTabAt(i);
+ tab.setIcon(tabIcons[i]);
+ tab.setContentDescription(pageTitles[i]);
+ }
+
+ tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
+ @Override
+ public void onTabSelected(TabLayout.Tab tab) {
+ viewPager.setCurrentItem(tab.getPosition());
+
+ if (pageHistory.isEmpty()) {
+ pageHistory.push(0);
+ }
+
+ if (pageHistory.contains(tab.getPosition())) {
+ pageHistory.remove(pageHistory.indexOf(tab.getPosition()));
+ }
+
+ pageHistory.push(tab.getPosition());
+ tintTab(tab, true);
+ }
+
+ @Override
+ public void onTabUnselected(TabLayout.Tab tab) {
+ tintTab(tab, false);
+ }
+
+ @Override
+ public void onTabReselected(TabLayout.Tab tab) {}
+ });
+
+ Intent intent = getIntent();
+
+ int tabSelected = 0;
+ if (intent != null) {
+ int tabPosition = intent.getIntExtra("tab_position", 0);
+ if (tabPosition != 0) {
+ TabLayout.Tab tab = tabLayout.getTabAt(tabPosition);
+ if (tab != null) {
+ tab.select();
+ tabSelected = tabPosition;
+ }
+ }
+ }
+ for (int i = 0; i < 4; i++) {
+ tintTab(tabLayout.getTabAt(i), i == tabSelected);
+ }
+
+ // Setup push notifications
+ if (arePushNotificationsEnabled()) {
+ enablePushNotifications();
+ } else {
+ disablePushNotifications();
+ }
+
+ composeButton = floatingBtn;
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ SharedPreferences notificationPreferences = getApplicationContext()
+ .getSharedPreferences("Notifications", MODE_PRIVATE);
+ notificationPreferences.edit()
+ .putString("current", "[]")
+ .apply();
+
+ ((NotificationManager) (getSystemService(NOTIFICATION_SERVICE)))
+ .cancel(MessagingService.NOTIFY_ID);
+
+ /* After editing a profile, the profile header in the navigation drawer needs to be
+ * refreshed */
+ SharedPreferences preferences = getPrivatePreferences();
+ if (preferences.getBoolean("refreshProfileHeader", false)) {
+ fetchUserInfo();
+ preferences.edit()
+ .putBoolean("refreshProfileHeader", false)
+ .apply();
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
+ ArrayList pageHistoryList = new ArrayList<>();
+ pageHistoryList.addAll(pageHistory);
+ outState.putIntegerArrayList("pageHistory", pageHistoryList);
+ super.onSaveInstanceState(outState, outPersistentState);
+ }
+
+ private void tintTab(TabLayout.Tab tab, boolean tinted) {
+ int color = (tinted) ? R.attr.tab_icon_selected_tint : R.attr.toolbar_icon_tint;
+ ThemeUtils.setDrawableTint(this, tab.getIcon(), color);
+ }
+
+ private void setupDrawer() {
+ headerResult = new AccountHeaderBuilder()
+ .withActivity(this)
+ .withSelectionListEnabledForSingleProfile(false)
+ .withDividerBelowHeader(false)
+ .withOnAccountHeaderProfileImageListener(new AccountHeader.OnAccountHeaderProfileImageListener() {
+ @Override
+ public boolean onProfileImageClick(View view, IProfile profile, boolean current) {
+ if (current && loggedInAccountId != null) {
+ Intent intent = new Intent(MainActivity.this, AccountActivity.class);
+ intent.putExtra("id", loggedInAccountId);
+ startActivity(intent);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onProfileImageLongClick(View view, IProfile profile, boolean current) {
+ return false;
+ }
+ })
+ .withCompactStyle(true)
+ .build();
+ headerResult.getView()
+ .findViewById(R.id.material_drawer_account_header_current)
+ .setContentDescription(getString(R.string.action_view_profile));
+
+ DrawerImageLoader.init(new AbstractDrawerImageLoader() {
+ @Override
+ public void set(ImageView imageView, Uri uri, Drawable placeholder) {
+ Picasso.with(imageView.getContext()).load(uri).placeholder(placeholder).into(imageView);
+ }
+
+ @Override
+ public void cancel(ImageView imageView) {
+ Picasso.with(imageView.getContext()).cancelRequest(imageView);
+ }
+ });
+
+ Drawable muteDrawable = ContextCompat.getDrawable(this, R.drawable.ic_mute_24dp);
+ ThemeUtils.setDrawableTint(this, muteDrawable, R.attr.toolbar_icon_tint);
+
+ drawer = new DrawerBuilder()
+ .withActivity(this)
+ //.withToolbar(toolbar)
+ .withAccountHeader(headerResult)
+ .withHasStableIds(true)
+ .withSelectedItem(-1)
+ .addDrawerItems(
+ new PrimaryDrawerItem().withIdentifier(0).withName(getString(R.string.action_edit_profile)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_person),
+ new PrimaryDrawerItem().withIdentifier(1).withName(getString(R.string.action_view_favourites)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_star),
+ new PrimaryDrawerItem().withIdentifier(2).withName(getString(R.string.action_view_mutes)).withSelectable(false).withIcon(muteDrawable),
+ 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)
+ )
+ .withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
+ @Override
+ public boolean onItemClick(View view, int position, IDrawerItem drawerItem) {
+ if (drawerItem != null) {
+ long drawerItemIdentifier = drawerItem.getIdentifier();
+
+ if (drawerItemIdentifier == 0) {
+ Intent intent = new Intent(MainActivity.this, EditProfileActivity.class);
+ startActivity(intent);
+ } else if (drawerItemIdentifier == 1) {
+ Intent intent = new Intent(MainActivity.this, FavouritesActivity.class);
+ startActivity(intent);
+ } else if (drawerItemIdentifier == 2) {
+ Intent intent = new Intent(MainActivity.this, AccountListActivity.class);
+ intent.putExtra("type", AccountListActivity.Type.MUTES);
+ startActivity(intent);
+ } else if (drawerItemIdentifier == 3) {
+ Intent intent = new Intent(MainActivity.this, AccountListActivity.class);
+ intent.putExtra("type", AccountListActivity.Type.BLOCKS);
+ startActivity(intent);
+ } else if (drawerItemIdentifier == 4) {
+ Intent intent = new Intent(MainActivity.this, PreferencesActivity.class);
+ startActivity(intent);
+ } else if (drawerItemIdentifier == 5) {
+ logout();
+ } else if (drawerItemIdentifier == 6) {
+ Intent intent = new Intent(MainActivity.this, AccountListActivity.class);
+ intent.putExtra("type", AccountListActivity.Type.FOLLOW_REQUESTS);
+ startActivity(intent);
+ }
+ }
+
+ return false;
+ }
+ })
+ .build();
+ }
+
+ private void logout() {
+ if (arePushNotificationsEnabled()) disablePushNotifications();
+
+ getPrivatePreferences().edit()
+ .remove("domain")
+ .remove("accessToken")
+ .apply();
+
+ Intent intent = new Intent(MainActivity.this, LoginActivity.class);
+ startActivity(intent);
+ finish();
+ }
+
+ private void setupSearchView() {
+ searchView.attachNavigationDrawerToMenuButton(drawer.getDrawerLayout());
+
+ // Setup content descriptions for the different elements in the search view.
+ final View leftAction = searchView.findViewById(R.id.left_action);
+ leftAction.setContentDescription(getString(R.string.action_open_drawer));
+ searchView.setOnFocusChangeListener(new FloatingSearchView.OnFocusChangeListener() {
+ @Override
+ public void onFocus() {
+ leftAction.setContentDescription(getString(R.string.action_close));
+ }
+
+ @Override
+ public void onFocusCleared() {
+ leftAction.setContentDescription(getString(R.string.action_open_drawer));
+ }
+ });
+ View clearButton = searchView.findViewById(R.id.clear_btn);
+ clearButton.setContentDescription(getString(R.string.action_clear));
+
+ searchView.setOnQueryChangeListener(new FloatingSearchView.OnQueryChangeListener() {
+ @Override
+ public void onSearchTextChanged(String oldQuery, String newQuery) {
+ if (!oldQuery.equals("") && newQuery.equals("")) {
+ searchView.clearSuggestions();
+ return;
+ }
+
+ if (newQuery.length() < 3) {
+ return;
+ }
+
+ searchView.showProgress();
+
+ mastodonAPI.searchAccounts(newQuery, false, 5).enqueue(new Callback>() {
+ @Override
+ public void onResponse(Call> call, Response> response) {
+ if (response.isSuccessful()) {
+ searchView.swapSuggestions(response.body());
+ searchView.hideProgress();
+ } else {
+ searchView.hideProgress();
+ }
+ }
+
+ @Override
+ public void onFailure(Call> call, Throwable t) {
+ searchView.hideProgress();
+ }
+ });
+ }
+ });
+
+ searchView.setOnSearchListener(new FloatingSearchView.OnSearchListener() {
+ @Override
+ public void onSuggestionClicked(SearchSuggestion searchSuggestion) {
+ Account accountSuggestion = (Account) searchSuggestion;
+ Intent intent = new Intent(MainActivity.this, AccountActivity.class);
+ intent.putExtra("id", accountSuggestion.id);
+ startActivity(intent);
+ }
+
+ @Override
+ public void onSearchAction(String currentQuery) {}
+ });
+
+ searchView.setOnBindSuggestionCallback(new SearchSuggestionsAdapter.OnBindSuggestionCallback() {
+ @Override
+ public void onBindSuggestion(View suggestionView, ImageView leftIcon, TextView textView, SearchSuggestion item, int itemPosition) {
+ Account accountSuggestion = ((Account) item);
+
+ Picasso.with(MainActivity.this)
+ .load(accountSuggestion.avatar)
+ .placeholder(R.drawable.avatar_default)
+ .into(leftIcon);
+
+ String searchStr = accountSuggestion.getDisplayName() + " " + accountSuggestion.username;
+ final SpannableStringBuilder str = new SpannableStringBuilder(searchStr);
+
+ str.setSpan(new StyleSpan(Typeface.BOLD), 0, accountSuggestion.getDisplayName().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ textView.setText(str);
+ textView.setMaxLines(1);
+ textView.setEllipsize(TextUtils.TruncateAt.END);
+ }
+ });
+ }
+
+ private void fetchUserInfo() {
+ SharedPreferences preferences = getPrivatePreferences();
+ final String domain = preferences.getString("domain", null);
+ String id = preferences.getString("loggedInAccountId", null);
+ String username = preferences.getString("loggedInAccountUsername", null);
+
+ if (id != null && username != null) {
+ loggedInAccountId = id;
+ loggedInAccountUsername = username;
+ }
+
+ mastodonAPI.accountVerifyCredentials().enqueue(new Callback() {
+ @Override
+ public void onResponse(Call call, Response response) {
+ if (!response.isSuccessful()) {
+ onFetchUserInfoFailure(new Exception(response.message()));
+ return;
+ }
+ onFetchUserInfoSuccess(response.body(), domain);
+ }
+
+ @Override
+ public void onFailure(Call call, Throwable t) {
+ onFetchUserInfoFailure((Exception) t);
+ }
+ });
+ }
+
+ private void onFetchUserInfoSuccess(Account me, String domain) {
+ // Add the header image and avatar from the account, into the navigation drawer header.
+ headerResult.clear();
+
+ ImageView background = headerResult.getHeaderBackgroundView();
+ int backgroundWidth = background.getWidth();
+ int backgroundHeight = background.getHeight();
+ if (backgroundWidth == 0 || backgroundHeight == 0) {
+ /* The header ImageView may not be layed out when the verify credentials call returns so
+ * measure the dimensions and use those. */
+ background.measure(View.MeasureSpec.EXACTLY, View.MeasureSpec.EXACTLY);
+ backgroundWidth = background.getMeasuredWidth();
+ backgroundHeight = background.getMeasuredHeight();
+ }
+
+ Picasso.with(MainActivity.this)
+ .load(me.header)
+ .placeholder(R.drawable.account_header_missing)
+ .resize(backgroundWidth, backgroundHeight)
+ .centerCrop()
+ .into(background);
+
+ headerResult.addProfiles(
+ new ProfileDrawerItem()
+ .withName(me.getDisplayName())
+ .withEmail(String.format("%s@%s", me.username, domain))
+ .withIcon(me.avatar)
+ );
+
+ // Show follow requests in the menu, if this is a locked account.
+ if (me.locked) {
+ PrimaryDrawerItem followRequestsItem = new PrimaryDrawerItem()
+ .withIdentifier(6)
+ .withName(R.string.action_view_follow_requests)
+ .withSelectable(false)
+ .withIcon(GoogleMaterial.Icon.gmd_person_add);
+ drawer.addItemAtPosition(followRequestsItem, 3);
+ }
+
+ // Update the current login information.
+ loggedInAccountId = me.id;
+ loggedInAccountUsername = me.username;
+ getPrivatePreferences().edit()
+ .putString("loggedInAccountId", loggedInAccountId)
+ .putString("loggedInAccountUsername", loggedInAccountUsername)
+ .putBoolean("loggedInAccountLocked", me.locked)
+ .apply();
+ }
+
+ private void onFetchUserInfoFailure(Exception exception) {
+ Log.e(TAG, "Failed to fetch user info. " + exception.getMessage());
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == COMPOSE_RESULT && resultCode == ComposeActivity.RESULT_OK) {
+ TimelinePagerAdapter adapter = (TimelinePagerAdapter) viewPager.getAdapter();
+ if (adapter.getCurrentFragment() instanceof SFragment) {
+ ((SFragment) adapter.getCurrentFragment()).onSuccessfulStatus();
+ }
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if(drawer != null && drawer.isDrawerOpen()) {
+ drawer.closeDrawer();
+ } else if(pageHistory.size() < 2) {
+ super.onBackPressed();
+ } else {
+ pageHistory.pop();
+ viewPager.setCurrentItem(pageHistory.peek());
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+ @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ TimelinePagerAdapter adapter = (TimelinePagerAdapter) viewPager.getAdapter();
+ for (Fragment fragment : adapter.getRegisteredFragments()) {
+ fragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+ }
+
+ @Override
+ public void onUserRemoved(String accountId) {
+ TimelinePagerAdapter adapter = (TimelinePagerAdapter) viewPager.getAdapter();
+ for (Fragment fragment : adapter.getRegisteredFragments()) {
+ if (fragment instanceof StatusRemoveListener) {
+ StatusRemoveListener listener = (StatusRemoveListener) fragment;
+ listener.removePostsByUser(accountId);
+ }
+ }
+ }
+
+ // 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
From 24b7e4db4c06fe21cfcf76de5f87d1891850ca16 Mon Sep 17 00:00:00 2001
From: Vavassor
Date: Wed, 3 May 2017 16:28:46 -0400
Subject: [PATCH 11/53] Fixes reorienting creating a visual duplicate of the
thread. Closes #237
---
app/src/main/AndroidManifest.xml | 11 ++++--
.../keylesspalace/tusky/ThreadAdapter.java | 7 ++--
.../tusky/ViewThreadActivity.java | 8 +++++
.../tusky/ViewThreadFragment.java | 35 ++++++++++++-------
4 files changed, 41 insertions(+), 20 deletions(-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 830c6695..a286e60e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -38,7 +38,8 @@
+ android:name=".MainActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden">
-
-
+
+
diff --git a/app/src/main/java/com/keylesspalace/tusky/ThreadAdapter.java b/app/src/main/java/com/keylesspalace/tusky/ThreadAdapter.java
index afa407f8..97dfc941 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ThreadAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/ThreadAdapter.java
@@ -15,7 +15,6 @@
package com.keylesspalace.tusky;
-import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
@@ -94,11 +93,11 @@ class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
// 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/ViewThreadActivity.java b/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java
index 9dcd5efa..160e438f 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;
@@ -74,4 +75,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/ViewThreadFragment.java b/app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java
index 0cf47600..ef1fc0a7 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java
@@ -34,6 +34,7 @@ import com.keylesspalace.tusky.entity.StatusContext;
import retrofit2.Call;
import retrofit2.Callback;
+import retrofit2.Response;
public class ViewThreadFragment extends SFragment implements
SwipeRefreshLayout.OnRefreshListener, StatusActionListener, StatusRemoveListener {
@@ -42,6 +43,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 +74,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 +119,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();
From bd687fb45d4c2fdda0aa94064a7d27ecfce01e84 Mon Sep 17 00:00:00 2001
From: Vavassor
Date: Wed, 3 May 2017 18:33:15 -0400
Subject: [PATCH 12/53] Fixes crash on android version Lollipop and earlier due
to an unsupported way vector drawable icons were assigned to radio buttons.
---
.../tusky/ComposeOptionsFragment.java | 39 ++++++++++++++++++-
.../com/keylesspalace/tusky/MainActivity.java | 5 ++-
.../res/layout/fragment_compose_options.xml | 8 ----
app/src/main/res/values/attrs.xml | 1 +
app/src/main/res/values/styles.xml | 2 +
5 files changed, 44 insertions(+), 11 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/ComposeOptionsFragment.java b/app/src/main/java/com/keylesspalace/tusky/ComposeOptionsFragment.java
index 3f3e1c0b..1b24c27e 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ComposeOptionsFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/ComposeOptionsFragment.java
@@ -16,9 +16,15 @@
package com.keylesspalace.tusky;
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;
@@ -85,8 +91,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 +146,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/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
index caaed452..764d1bdd 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
@@ -27,8 +27,8 @@ 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.text.SpannableStringBuilder;
import android.text.Spanned;
@@ -274,7 +274,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()
diff --git a/app/src/main/res/layout/fragment_compose_options.xml b/app/src/main/res/layout/fragment_compose_options.xml
index 818cf656..a414a6b9 100644
--- a/app/src/main/res/layout/fragment_compose_options.xml
+++ b/app/src/main/res/layout/fragment_compose_options.xml
@@ -15,48 +15,40 @@
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 0b1e3b2d..b151ffc9 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/styles.xml b/app/src/main/res/values/styles.xml
index a6454085..0256ee02 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -62,6 +62,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 +149,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
From f313884f7da1e447aa59e138b29d888bfcd774f3 Mon Sep 17 00:00:00 2001
From: Vavassor
Date: Wed, 3 May 2017 20:27:59 -0400
Subject: [PATCH 13/53] Adds a link to the text of a post when media is
attached.
---
.../keylesspalace/tusky/ComposeActivity.java | 88 +++++++++++++------
1 file changed, 62 insertions(+), 26 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java
index 4efa93a2..15fefa90 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;
@@ -90,6 +93,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
@@ -143,6 +147,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
Uri uri;
String id;
Call uploadRequest;
+ URLSpan uploadUrl;
ReadyStage readyStage;
byte[] content;
long mediaSize;
@@ -224,7 +229,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() {
@@ -584,24 +589,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 +698,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 +725,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 +747,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 +772,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 +963,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 +1073,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 +1088,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
From bdca1d1c94d2b2526b1635a1b27fed88e7ebf8a7 Mon Sep 17 00:00:00 2001
From: Vavassor
Date: Wed, 3 May 2017 21:49:53 -0400
Subject: [PATCH 14/53] Release 1.1.3
---
app/.gitignore | 1 +
app/build.gradle | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/app/.gitignore b/app/.gitignore
index 3f1ce47a..61e52b44 100644
--- a/app/.gitignore
+++ b/app/.gitignore
@@ -1,2 +1,3 @@
/build
app-release.apk
+app-google-release.apk
diff --git a/app/build.gradle b/app/build.gradle
index a7b9f5ae..8d3f1593 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -7,8 +7,8 @@ android {
applicationId "com.keylesspalace.tusky"
minSdkVersion 15
targetSdkVersion 25
- versionCode 15
- versionName "1.1.2"
+ versionCode 16
+ versionName "1.1.3"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary true
}
From ff073a6abfc6a972e686918f47df4ab66fe1d3b3 Mon Sep 17 00:00:00 2001
From: m4sk1n
Date: Thu, 4 May 2017 07:14:44 +0200
Subject: [PATCH 15/53] i18n: fixed some things in Polish translation
---
app/src/main/res/values-pl/strings.xml | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 00cc880a..af74baf1 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łównaPowiadomieniaLokalne
- Globalne
+ FederalneWątek#%sPostyObserwacje
- Obsereujący
+ ObserwującyUlubioneWyciszeni użytkownicyZablokowani użytkownicy
@@ -73,10 +73,10 @@
OKAnulujZamknij
- Wró
+ WróćProfilPreferencje
- Ulunione
+ UlubioneWyciszeni użytkownicyZablokowani użytkownicyProśby o obserwację
@@ -111,7 +111,7 @@
Cofnięto wyciszenie użytkownikaJaka instancja?
- Opowiedz o czymś…
+ Co ci chodzi po głowie?Ostrzeenie o zawartościNazwa wyświetlanaBiografia
From f93638e58daa4f8ed1afab8efbb8ba845bf62dff Mon Sep 17 00:00:00 2001
From: Caleb Maclennan
Date: Thu, 4 May 2017 10:54:38 +0300
Subject: [PATCH 16/53] Fix spelling and translate new strings for Turkish
---
app/src/main/res/values-tr/strings.xml | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index 24055b06..b98add18 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -29,7 +29,10 @@
Takip edilenlerTakipçilerFavoriler
+ Sesize alınmış kullanıcılarEngellenmiş kullanıcılar
+ Takip Etme İstekler
+ Profili düzeltme\@%s%s yükseltti
@@ -67,7 +70,7 @@
İLET!Tekrar deneMedyayı hassas olarak etiketle
- Metini uyarı ile sakla
+ İçerik uyarıyla gizlensinTamamİptalKapat
@@ -75,6 +78,7 @@
ProfilAyarlarFavoriler
+ Sesize alınmış kullanıcılarEngellenmiş kullanıcılarDiziMedya
@@ -92,6 +96,9 @@
TemizleKaydetProfili düzelt
+ Geri al
+ Kabul et
+ Reddetİletinin adresini paylaş…İletiyi paylaş…
@@ -123,9 +130,10 @@
Medya Yükleme BittiriliyorYükleniyor…İndir
+ Takip etme istekleri: yanıt bekleniyorKamu: 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çıkDirekt: Sadece bahsedilen kullanıcılara açık
@@ -156,4 +164,6 @@
Kitli Hesapİletinin içeriğini paylaşİletinin adresini paylaş
+
+ Takip edebilme istendi
From d5b4b0fe5400da99a0e9bbb4dae301884a8d7e5b Mon Sep 17 00:00:00 2001
From: Conny Duck
Date: Thu, 4 May 2017 14:58:45 +0200
Subject: [PATCH 17/53] fixed crash when media type is unknown
---
.../keylesspalace/tusky/entity/Status.java | 26 ++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
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 4e147358..20007190 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;
}
+
+
}
From 2898baf18dacfcf6ebd849918308746b7de44b32 Mon Sep 17 00:00:00 2001
From: Conny Duck
Date: Thu, 4 May 2017 16:16:24 +0200
Subject: [PATCH 18/53] updated dependencies
---
app/build.gradle | 20 +++++------
.../keylesspalace/tusky/StatusViewHolder.java | 28 ++++++++++-----
.../tusky/ViewMediaFragment.java | 34 +++++++------------
.../main/res/layout/fragment_view_media.xml | 2 +-
4 files changed, 44 insertions(+), 40 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 8d3f1593..58d486e8 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 25
- buildToolsVersion "25.0.2"
+ buildToolsVersion "25.0.3"
defaultConfig {
applicationId "com.keylesspalace.tusky"
minSdkVersion 15
@@ -37,7 +37,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 +47,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'
+ googleCompile 'com.google.firebase:firebase-messaging:10.2.4'
+ googleCompile 'com.google.firebase:firebase-crash:10.2.4'
testCompile 'junit:junit:4.12'
- annotationProcessor 'com.jakewharton:butterknife-compiler:8.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/src/main/java/com/keylesspalace/tusky/StatusViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/StatusViewHolder.java
index 6a2e431d..f6d7c0bf 100644
--- a/app/src/main/java/com/keylesspalace/tusky/StatusViewHolder.java
+++ b/app/src/main/java/com/keylesspalace/tusky/StatusViewHolder.java
@@ -290,14 +290,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 +312,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/ViewMediaFragment.java b/app/src/main/java/com/keylesspalace/tusky/ViewMediaFragment.java
index fed8cbeb..855d3818 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ViewMediaFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/ViewMediaFragment.java
@@ -35,7 +35,12 @@ 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.squareup.picasso.Callback;
import com.squareup.picasso.Picasso;
@@ -43,17 +48,16 @@ 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 {
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;
public static ViewMediaFragment newInstance(String url) {
Bundle arguments = new Bundle();
@@ -90,24 +94,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;
@@ -152,12 +151,6 @@ public class ViewMediaFragment extends DialogFragment {
return rootView;
}
- @Override
- public void onDestroyView() {
- attacher.cleanup();
- super.onDestroyView();
- }
-
private void downloadImage(){
//Permission stuff
@@ -170,14 +163,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();
diff --git a/app/src/main/res/layout/fragment_view_media.xml b/app/src/main/res/layout/fragment_view_media.xml
index ea1918fc..ec2d4efd 100644
--- a/app/src/main/res/layout/fragment_view_media.xml
+++ b/app/src/main/res/layout/fragment_view_media.xml
@@ -5,7 +5,7 @@
android:clickable="true"
android:layout_gravity="center"
android:background="@android:color/black">
-
From 4e4c39e58e353a81e6906f549798817d1e0c26ca Mon Sep 17 00:00:00 2001
From: Conny Duck
Date: Thu, 4 May 2017 16:25:04 +0200
Subject: [PATCH 19/53] added missing quotation marks in MediaTypeDeserializer
---
.../main/java/com/keylesspalace/tusky/entity/Status.java | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
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 20007190..c98ebfa5 100644
--- a/app/src/main/java/com/keylesspalace/tusky/entity/Status.java
+++ b/app/src/main/java/com/keylesspalace/tusky/entity/Status.java
@@ -148,11 +148,11 @@ public class Status {
public Type deserialize(JsonElement json, java.lang.reflect.Type classOfT, JsonDeserializationContext context)
throws JsonParseException {
switch(json.toString()) {
- case "image":
+ case "\"image\"":
return Type.IMAGE;
- case "gifv":
+ case "\"gifv\"":
return Type.GIFV;
- case "video":
+ case "\"video\"":
return Type.VIDEO;
default:
return Type.UNKNOWN;
From 310d9a3c9144a8441197dd7644ce4bd2b9f551ad Mon Sep 17 00:00:00 2001
From: Conny Duck
Date: Thu, 4 May 2017 16:32:21 +0200
Subject: [PATCH 20/53] use TextInputEditText instead of EditText inside
TextInputLayout
---
app/src/main/res/layout/activity_edit_profile.xml | 4 ++--
app/src/main/res/layout/activity_login.xml | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/app/src/main/res/layout/activity_edit_profile.xml b/app/src/main/res/layout/activity_edit_profile.xml
index c18856f5..97049018 100644
--- a/app/src/main/res/layout/activity_edit_profile.xml
+++ b/app/src/main/res/layout/activity_edit_profile.xml
@@ -86,7 +86,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="30dp">
-
-
-
Date: Thu, 4 May 2017 18:07:59 +0200
Subject: [PATCH 21/53] remove redundant account_header background, fix weird
flicker in header when opening AccountActivity
---
.../keylesspalace/tusky/AccountActivity.java | 5 +-
.../tusky/EditProfileActivity.java | 3 +-
.../com/keylesspalace/tusky/MainActivity.java | 2 +-
.../res/drawable/account_header_default.png | Bin 1891 -> 0 bytes
...missing.xml => account_header_default.xml} | 0
app/src/main/res/layout/activity_account.xml | 106 +++++++++---------
6 files changed, 57 insertions(+), 59 deletions(-)
delete mode 100644 app/src/main/res/drawable/account_header_default.png
rename app/src/main/res/drawable/{account_header_missing.xml => account_header_default.xml} (100%)
diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java
index ed12bd2c..ae09b150 100644
--- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java
@@ -156,9 +156,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)) {
@@ -266,7 +263,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();
diff --git a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.java b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.java
index ddb53808..f06da406 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;
@@ -178,7 +177,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/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
index 764d1bdd..ffbfe113 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
@@ -476,7 +476,7 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
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);
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 c4e44ad25b2e13190a7570edcfbcf761b80f38c6..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 1891
zcmeAS@N?(olHy`uVBq!ia0y~yVA{jL!06Ax1{7HzBAW^17FM=u`STG-vy9r}lT
zS7*J0086QmOFBEN;F2Sgmt9b@syJNz{qd7|)2B0YRK^;Oi~auj)n51CavJX#Ze}O^XPEJ<
zCgrj8&-WSL@g>^7fx*vN;1OBOz@VoL!i*J5?aP3IVpSm#C5fda8Tmz^_$WT(t?8x9
z&cJ??DJe`$%7cM{wc69gF{I+w+iQ-33KGUbu1p6S
zbeNlgp-r5Dfk&T#K_ZTU!2lTV2^EZR`oIS!hK7f%s0y)ZfGLF0SWQN@0Yf1!r+^fK
zoP?nP=4KcTb33v^47VdY0h
-
+ 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" />
From 189cff6a4722c5240c335368243dba188e3d2740 Mon Sep 17 00:00:00 2001
From: ButterflyOfFire
Date: Thu, 4 May 2017 20:32:35 +0200
Subject: [PATCH 22/53] i18n - Updating Arabic strings translation
i18n - Updating Arabic strings translation
---
app/src/main/res/values-ar/strings.xml | 20 +++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 17988f5a..8e833ade 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -26,10 +26,13 @@
الخيط#%sالمشاركات
- يتبع
- المتابعون
+ المتابَعون
+ المتابِعونالمفضلة
- المستخدمون المحظورون
+ الحسابات المكتومة
+ الحسابات المحظورة
+ طلبات المتابعة
+ عدل ملفك الشخصي\@%s%s رقّي
@@ -59,7 +62,7 @@
خروجإتبعإلغاء التتبع
- حضر
+ قم بحظرهإلغاء الحظرأبلغإحذف
@@ -92,6 +95,9 @@
إمسحإحفظتعديل الملف الشخصي
+ إلغاء
+ موافقة
+ رفضشارك رابط التبويق على ...شارك التبويق على …
@@ -100,6 +106,8 @@
بَوِّقتم الإرسال !
+ تم فك الحجب عن الحساب
+ تم فك الكتم عن الحسابأي سيرفر ؟ما الجديد ؟
@@ -140,7 +148,7 @@
أخطرني عندمايشار إلييتبعني أحد
- تعزز وتدفع منشوراتي
+ تُرقّى منشوراتييعجب أحد ما بمنشوراتيالمظهرإستخدم سمةً فاتحة اللون
@@ -157,4 +165,6 @@
حساب مقفلشارك محتوى التبويقشارك الرابط إلى التبويق
+
+ طلب متابعة
From aa2394748c2d2ff2b0f0b61354a51f15f43c3c8f Mon Sep 17 00:00:00 2001
From: Vavassor
Date: Thu, 4 May 2017 18:55:34 -0400
Subject: [PATCH 23/53] Reorganizes the whole codebase.
---
.../keylesspalace/tusky/MessagingService.java | 7 +-
app/src/main/AndroidManifest.xml | 4 +-
.../keylesspalace/tusky/AccountActivity.java | 13 +-
.../tusky/AccountListActivity.java | 2 +
.../com/keylesspalace/tusky/BaseActivity.java | 9 +-
.../keylesspalace/tusky/ComposeActivity.java | 11 +-
.../tusky/EditProfileActivity.java | 3 +-
.../tusky/FavouritesActivity.java | 4 +
.../keylesspalace/tusky/LoginActivity.java | 4 +
.../com/keylesspalace/tusky/MainActivity.java | 7 +-
.../tusky/PreferencesActivity.java | 2 +
.../keylesspalace/tusky/ReportActivity.java | 4 +
.../keylesspalace/tusky/ViewTagActivity.java | 4 +
.../tusky/ViewThreadActivity.java | 4 +
.../tusky/{ => adapter}/AccountAdapter.java | 13 +-
.../tusky/{ => adapter}/BlocksAdapter.java | 8 +-
.../tusky/{ => adapter}/FollowAdapter.java | 8 +-
.../{ => adapter}/FollowRequestsAdapter.java | 8 +-
.../tusky/{ => adapter}/FooterViewHolder.java | 4 +-
.../tusky/{ => adapter}/MutesAdapter.java | 11 +-
.../{ => adapter}/NotificationsAdapter.java | 19 +-
.../tusky/{ => adapter}/ReportAdapter.java | 18 +-
.../tusky/{ => adapter}/StatusViewHolder.java | 14 +-
.../tusky/{ => adapter}/ThreadAdapter.java | 15 +-
.../tusky/{ => adapter}/TimelineAdapter.java | 21 +-
.../keylesspalace/tusky/entity/Account.java | 4 +-
.../{ => fragment}/AccountListFragment.java | 16 +-
.../tusky/{ => fragment}/BaseFragment.java | 4 +-
.../ComposeOptionsFragment.java | 7 +-
.../{ => fragment}/NotificationsFragment.java | 11 +-
.../{ => fragment}/PreferencesFragment.java | 4 +-
.../tusky/{ => fragment}/SFragment.java | 15 +-
.../{ => fragment}/TimelineFragment.java | 12 +-
.../{ => fragment}/ViewMediaFragment.java | 3 +-
.../{ => fragment}/ViewThreadFragment.java | 12 +-
.../AccountActionListener.java | 4 +-
.../{ => interfaces}/AdapterItemRemover.java | 4 +-
.../tusky/{ => interfaces}/LinkListener.java | 4 +-
.../StatusActionListener.java | 4 +-
.../StatusRemoveListener.java | 4 +-
.../tusky/{ => json}/SpannedTypeAdapter.java | 3 +-
.../tusky/{ => json}/StringWithEmoji.java | 2 +-
.../StringWithEmojiTypeAdapter.java | 4 +-
.../tusky/{ => network}/MastodonAPI.java | 2 +-
.../tusky/{ => network}/TuskyAPI.java | 2 +-
.../{ => pager}/AccountPagerAdapter.java | 16 +-
.../{ => pager}/TimelinePagerAdapter.java | 13 +-
.../tusky/{ => service}/TuskyTileService.java | 4 +-
.../com/keylesspalace/tusky/util/Assert.java | 29 +++
.../ConversationLineItemDecoration.java | 8 +-
.../tusky/util/CountUpDownLatch.java | 40 +++
.../tusky/{ => util}/CustomTabURLSpan.java | 4 +-
.../tusky/{ => util}/CustomTabsHelper.java | 2 +-
.../keylesspalace/tusky/util/DateUtils.java | 50 ++++
.../tusky/{ => util}/DownsizeImageTask.java | 8 +-
.../tusky/{ => util}/EditTextTyped.java | 2 +-
.../{ => util}/EndlessOnScrollListener.java | 8 +-
.../tusky/{ => util}/FlowLayout.java | 4 +-
.../keylesspalace/tusky/util/HtmlUtils.java | 54 ++++
.../com/keylesspalace/tusky/util/IOUtils.java | 44 ++++
.../tusky/{ => util}/LinkHelper.java | 14 +-
.../com/keylesspalace/tusky/util/Log.java | 53 ++++
.../NotificationClearBroadcastReceiver.java | 2 +-
.../tusky/{ => util}/NotificationMaker.java | 8 +-
.../keylesspalace/tusky/util/OkHttpUtils.java | 244 ++++++++++++++++++
.../{ => util}/RoundedTransformation.java | 2 +-
.../keylesspalace/tusky/util/SpanUtils.java | 129 +++++++++
.../keylesspalace/tusky/util/ThemeUtils.java | 68 +++++
app/src/main/res/layout/activity_compose.xml | 2 +-
app/src/main/res/layout/item_status.xml | 4 +-
70 files changed, 1012 insertions(+), 138 deletions(-)
rename app/src/main/java/com/keylesspalace/tusky/{ => adapter}/AccountAdapter.java (87%)
rename app/src/main/java/com/keylesspalace/tusky/{ => adapter}/BlocksAdapter.java (94%)
rename app/src/main/java/com/keylesspalace/tusky/{ => adapter}/FollowAdapter.java (93%)
rename app/src/main/java/com/keylesspalace/tusky/{ => adapter}/FollowRequestsAdapter.java (94%)
rename app/src/main/java/com/keylesspalace/tusky/{ => adapter}/FooterViewHolder.java (93%)
rename app/src/main/java/com/keylesspalace/tusky/{ => adapter}/MutesAdapter.java (93%)
rename app/src/main/java/com/keylesspalace/tusky/{ => adapter}/NotificationsAdapter.java (95%)
rename app/src/main/java/com/keylesspalace/tusky/{ => adapter}/ReportAdapter.java (90%)
rename app/src/main/java/com/keylesspalace/tusky/{ => adapter}/StatusViewHolder.java (96%)
rename app/src/main/java/com/keylesspalace/tusky/{ => adapter}/ThreadAdapter.java (88%)
rename app/src/main/java/com/keylesspalace/tusky/{ => adapter}/TimelineAdapter.java (88%)
rename app/src/main/java/com/keylesspalace/tusky/{ => fragment}/AccountListFragment.java (95%)
rename app/src/main/java/com/keylesspalace/tusky/{ => fragment}/BaseFragment.java (95%)
rename app/src/main/java/com/keylesspalace/tusky/{ => fragment}/ComposeOptionsFragment.java (97%)
rename app/src/main/java/com/keylesspalace/tusky/{ => fragment}/NotificationsFragment.java (96%)
rename app/src/main/java/com/keylesspalace/tusky/{ => fragment}/PreferencesFragment.java (92%)
rename app/src/main/java/com/keylesspalace/tusky/{ => fragment}/SFragment.java (95%)
rename app/src/main/java/com/keylesspalace/tusky/{ => fragment}/TimelineFragment.java (96%)
rename app/src/main/java/com/keylesspalace/tusky/{ => fragment}/ViewMediaFragment.java (99%)
rename app/src/main/java/com/keylesspalace/tusky/{ => fragment}/ViewThreadFragment.java (93%)
rename app/src/main/java/com/keylesspalace/tusky/{ => interfaces}/AccountActionListener.java (92%)
rename app/src/main/java/com/keylesspalace/tusky/{ => interfaces}/AdapterItemRemover.java (89%)
rename app/src/main/java/com/keylesspalace/tusky/{ => interfaces}/LinkListener.java (90%)
rename app/src/main/java/com/keylesspalace/tusky/{ => interfaces}/StatusActionListener.java (91%)
rename app/src/main/java/com/keylesspalace/tusky/{ => interfaces}/StatusRemoveListener.java (89%)
rename app/src/main/java/com/keylesspalace/tusky/{ => json}/SpannedTypeAdapter.java (93%)
rename app/src/main/java/com/keylesspalace/tusky/{ => json}/StringWithEmoji.java (96%)
rename app/src/main/java/com/keylesspalace/tusky/{ => json}/StringWithEmojiTypeAdapter.java (91%)
rename app/src/main/java/com/keylesspalace/tusky/{ => network}/MastodonAPI.java (99%)
rename app/src/main/java/com/keylesspalace/tusky/{ => network}/TuskyAPI.java (96%)
rename app/src/main/java/com/keylesspalace/tusky/{ => pager}/AccountPagerAdapter.java (84%)
rename app/src/main/java/com/keylesspalace/tusky/{ => pager}/TimelinePagerAdapter.java (87%)
rename app/src/main/java/com/keylesspalace/tusky/{ => service}/TuskyTileService.java (94%)
create mode 100644 app/src/main/java/com/keylesspalace/tusky/util/Assert.java
rename app/src/main/java/com/keylesspalace/tusky/{ => util}/ConversationLineItemDecoration.java (89%)
create mode 100644 app/src/main/java/com/keylesspalace/tusky/util/CountUpDownLatch.java
rename app/src/main/java/com/keylesspalace/tusky/{ => util}/CustomTabURLSpan.java (96%)
rename app/src/main/java/com/keylesspalace/tusky/{ => util}/CustomTabsHelper.java (99%)
create mode 100644 app/src/main/java/com/keylesspalace/tusky/util/DateUtils.java
rename app/src/main/java/com/keylesspalace/tusky/{ => util}/DownsizeImageTask.java (97%)
rename app/src/main/java/com/keylesspalace/tusky/{ => util}/EditTextTyped.java (98%)
rename app/src/main/java/com/keylesspalace/tusky/{ => util}/EndlessOnScrollListener.java (90%)
rename app/src/main/java/com/keylesspalace/tusky/{ => util}/FlowLayout.java (98%)
create mode 100644 app/src/main/java/com/keylesspalace/tusky/util/HtmlUtils.java
create mode 100644 app/src/main/java/com/keylesspalace/tusky/util/IOUtils.java
rename app/src/main/java/com/keylesspalace/tusky/{ => util}/LinkHelper.java (87%)
create mode 100644 app/src/main/java/com/keylesspalace/tusky/util/Log.java
rename app/src/main/java/com/keylesspalace/tusky/{ => util}/NotificationClearBroadcastReceiver.java (97%)
rename app/src/main/java/com/keylesspalace/tusky/{ => util}/NotificationMaker.java (97%)
create mode 100644 app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.java
rename app/src/main/java/com/keylesspalace/tusky/{ => util}/RoundedTransformation.java (98%)
create mode 100644 app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.java
create mode 100644 app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java
diff --git a/app/src/fdroid/java/com/keylesspalace/tusky/MessagingService.java b/app/src/fdroid/java/com/keylesspalace/tusky/MessagingService.java
index 01d98122..ca446945 100644
--- a/app/src/fdroid/java/com/keylesspalace/tusky/MessagingService.java
+++ b/app/src/fdroid/java/com/keylesspalace/tusky/MessagingService.java
@@ -21,12 +21,17 @@ 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 com.keylesspalace.tusky.json.SpannedTypeAdapter;
+import com.keylesspalace.tusky.json.StringWithEmoji;
+import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter;
+import com.keylesspalace.tusky.network.MastodonAPI;
+import com.keylesspalace.tusky.util.NotificationMaker;
+import com.keylesspalace.tusky.util.OkHttpUtils;
import java.util.HashSet;
import java.util.List;
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a286e60e..b5724943 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -79,11 +79,11 @@
android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:theme="@style/Base.Theme.AppCompat" />
-
+
diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java
index ed12bd2c..f3a83f4d 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;
@@ -43,6 +44,14 @@ 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.ThemeUtils;
import com.pkmmte.view.CircularImageView;
import com.squareup.picasso.Picasso;
@@ -237,7 +246,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);
diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.java b/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.java
index 1d1276fb..86c5a05c 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 5bbe7ba1..1803cacd 100644
--- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java
@@ -34,6 +34,13 @@ import android.view.Menu;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
+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 java.io.IOException;
@@ -51,7 +58,7 @@ import retrofit2.converter.gson.GsonConverterFactory;
public class BaseActivity extends AppCompatActivity {
private static final String TAG = "BaseActivity"; // logging tag
- protected MastodonAPI mastodonAPI;
+ public MastodonAPI mastodonAPI;
protected TuskyAPI tuskyAPI;
protected Dispatcher mastodonApiDispatcher;
protected PendingIntent serviceAlarmIntent;
diff --git a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java
index 15fefa90..f0089fd2 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java
@@ -71,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;
@@ -119,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;
diff --git a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.java b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.java
index ddb53808..23c633b6 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;
diff --git a/app/src/main/java/com/keylesspalace/tusky/FavouritesActivity.java b/app/src/main/java/com/keylesspalace/tusky/FavouritesActivity.java
index f45ab2c6..7755f217 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 b85f1faa..8ec47748 100644
--- a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java
@@ -36,6 +36,10 @@ 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;
diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
index 764d1bdd..c93bb5ed 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
@@ -42,6 +42,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;
@@ -82,7 +87,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) {
diff --git a/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java b/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java
index 53e4f22a..58c18588 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 0c3d1895..f8e4e447 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/ViewTagActivity.java b/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java
index 9bce54a1..e73d490a 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 160e438f..20b3534a 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java
@@ -25,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;
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 9941d34d..48c6634b 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 d052db34..e4e34fa9 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 3b09de24..7e9b825f 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 0a54b558..392d33ac 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 e39ca44a..5ff1187e 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 2513cac6..06258a4b 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 2376469b..639bfea3 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 ccdf7aa9..50e07369 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 96%
rename from app/src/main/java/com/keylesspalace/tusky/StatusViewHolder.java
rename to app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java
index 6a2e431d..1459dd4e 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) {
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 88%
rename from app/src/main/java/com/keylesspalace/tusky/ThreadAdapter.java
rename to app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.java
index 97dfc941..46523f52 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ThreadAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.java
@@ -13,24 +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.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;
@@ -55,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);
}
@@ -76,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);
@@ -88,7 +91,7 @@ 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,
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 88%
rename from app/src/main/java/com/keylesspalace/tusky/TimelineAdapter.java
rename to app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java
index 759a2357..892197b7 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,7 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover
notifyItemRemoved(position);
}
- void removeAllByAccountId(String accountId) {
+ 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 +158,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 e7be8e81..c802bd4a 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/AccountListFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.java
similarity index 95%
rename from app/src/main/java/com/keylesspalace/tusky/AccountListFragment.java
rename to app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.java
index 1128602f..df2f5703 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;
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 0a9dcbcf..c0305d12 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 97%
rename from app/src/main/java/com/keylesspalace/tusky/ComposeOptionsFragment.java
rename to app/src/main/java/com/keylesspalace/tusky/fragment/ComposeOptionsFragment.java
index 1b24c27e..ab522602 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ComposeOptionsFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ComposeOptionsFragment.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.res.ColorStateList;
@@ -33,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);
}
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 56afc0fd..bef04290 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 8820f5eb..280098a7 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 3af5cce0..71b04426 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 96%
rename from app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java
rename to app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java
index d23bbed3..4353c43f 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;
@@ -31,7 +31,15 @@ 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.ThemeUtils;
import java.util.List;
@@ -47,7 +55,7 @@ public class TimelineFragment extends SFragment implements
private Call> listCall;
- enum Kind {
+ public enum Kind {
HOME,
PUBLIC_LOCAL,
PUBLIC_FEDERATED,
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 99%
rename from app/src/main/java/com/keylesspalace/tusky/ViewMediaFragment.java
rename to app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.java
index fed8cbeb..9a5dbee2 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ViewMediaFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.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.app.AlertDialog;
import android.app.DownloadManager;
@@ -36,6 +36,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
+import com.keylesspalace.tusky.R;
import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso;
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 93%
rename from app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java
rename to app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java
index ef1fc0a7..d02f4359 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,8 +29,18 @@ 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;
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 bca609cd..116bcae8 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 634935ff..5b49cbfa 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 9e188ae4..62360e34 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 1004bb82..b6b80f72 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 23111c6a..c05f7657 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 3b47acc3..305adba4 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 cad8e8c0..d229c5a9 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 ecf18bcd..86bd2c61 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 011fbc40..aad04523 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 96%
rename from app/src/main/java/com/keylesspalace/tusky/TuskyAPI.java
rename to app/src/main/java/com/keylesspalace/tusky/network/TuskyAPI.java
index 4831949b..700de83c 100644
--- a/app/src/main/java/com/keylesspalace/tusky/TuskyAPI.java
+++ b/app/src/main/java/com/keylesspalace/tusky/network/TuskyAPI.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 okhttp3.ResponseBody;
import retrofit2.Call;
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 505d89bd..8f04ae18 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 03dc88dc..82686041 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 720e9966..67789928 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/util/Assert.java b/app/src/main/java/com/keylesspalace/tusky/util/Assert.java
new file mode 100644
index 00000000..c976184e
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/util/Assert.java
@@ -0,0 +1,29 @@
+/* Copyright 2017 Andrew Dawson
+ *
+ * This file is a part of Tusky.
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tusky; if not,
+ * see . */
+
+package com.keylesspalace.tusky.util;
+
+import com.keylesspalace.tusky.BuildConfig;
+
+/** Android Studio complains about built-in assertions so this is an alternative. */
+public class Assert {
+ private static boolean ENABLED = BuildConfig.DEBUG;
+
+ 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 e5fb8562..dab773c3 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/util/CountUpDownLatch.java b/app/src/main/java/com/keylesspalace/tusky/util/CountUpDownLatch.java
new file mode 100644
index 00000000..5791cf60
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/util/CountUpDownLatch.java
@@ -0,0 +1,40 @@
+/* Copyright 2017 Andrew Dawson
+ *
+ * This file is a part of Tusky.
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tusky; if not,
+ * see . */
+
+package com.keylesspalace.tusky.util;
+
+public class CountUpDownLatch {
+ private int count;
+
+ public CountUpDownLatch() {
+ this.count = 0;
+ }
+
+ public synchronized void countDown() {
+ count--;
+ notifyAll();
+ }
+
+ public synchronized void countUp() {
+ count++;
+ notifyAll();
+ }
+
+ 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 7af9a84f..bef07fcf 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 45670863..f5add047 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/util/DateUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/DateUtils.java
new file mode 100644
index 00000000..b2cc49a2
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/util/DateUtils.java
@@ -0,0 +1,50 @@
+/* Copyright 2017 Andrew Dawson
+ *
+ * This file is a part of Tusky.
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tusky; if not,
+ * see . */
+
+package com.keylesspalace.tusky.util;
+
+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. */
+ public static String getRelativeTimeSpanString(long then, long now) {
+ final long MINUTE = 60;
+ final long HOUR = 60 * MINUTE;
+ final long DAY = 24 * HOUR;
+ final long YEAR = 365 * DAY;
+ long span = (now - then) / 1000;
+ String prefix = "";
+ if (span < 0) {
+ prefix = "in ";
+ span = -span;
+ }
+ String unit;
+ if (span < MINUTE) {
+ unit = "s";
+ } else if (span < HOUR) {
+ span /= MINUTE;
+ unit = "m";
+ } else if (span < DAY) {
+ span /= HOUR;
+ unit = "h";
+ } else if (span < YEAR) {
+ span /= DAY;
+ unit = "d";
+ } else {
+ span /= YEAR;
+ unit = "y";
+ }
+ return prefix + span + unit;
+ }
+}
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 56d50ed9..0cbc35a8 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 367180a2..c32f666f 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 9c91a89a..0b149de0 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 44616643..cf1f9e68 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/util/HtmlUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/HtmlUtils.java
new file mode 100644
index 00000000..eee9bb44
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/util/HtmlUtils.java
@@ -0,0 +1,54 @@
+/* Copyright 2017 Andrew Dawson
+ *
+ * This file is a part of Tusky.
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tusky; if not,
+ * see . */
+
+package com.keylesspalace.tusky.util;
+
+import android.os.Build;
+import android.text.Html;
+import android.text.Spanned;
+
+public class HtmlUtils {
+ private static CharSequence trimTrailingWhitespace(CharSequence s) {
+ int i = s.length();
+ do {
+ i--;
+ } while (i >= 0 && Character.isWhitespace(s.charAt(i)));
+ return s.subSequence(0, i + 1);
+ }
+
+ @SuppressWarnings("deprecation")
+ public static Spanned fromHtml(String html) {
+ Spanned result;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ result = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
+ } else {
+ result = Html.fromHtml(html);
+ }
+ /* Html.fromHtml returns trailing whitespace if the html ends in a
tag, which
+ * all status contents do, so it should be trimmed. */
+ return (Spanned) trimTrailingWhitespace(result);
+ }
+
+ @SuppressWarnings("deprecation")
+ public static String toHtml(Spanned text) {
+ String result;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ result = Html.toHtml(text, Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE);
+ } else {
+ result = Html.toHtml(text);
+ }
+ return result;
+ }
+}
diff --git a/app/src/main/java/com/keylesspalace/tusky/util/IOUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/IOUtils.java
new file mode 100644
index 00000000..2b17eeeb
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/util/IOUtils.java
@@ -0,0 +1,44 @@
+/* Copyright 2017 Andrew Dawson
+ *
+ * This file is a part of Tusky.
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tusky; if not,
+ * see . */
+
+package com.keylesspalace.tusky.util;
+
+import android.support.annotation.Nullable;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class IOUtils {
+ public static void closeQuietly(@Nullable InputStream stream) {
+ try {
+ if (stream != null) {
+ stream.close();
+ }
+ } catch (IOException e) {
+ // intentionally unhandled
+ }
+ }
+
+ public static void closeQuietly(@Nullable OutputStream stream) {
+ try {
+ if (stream != null) {
+ stream.close();
+ }
+ } catch (IOException e) {
+ // intentionally unhandled
+ }
+ }
+}
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 ba9a591f..46ddd5f1 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/util/Log.java b/app/src/main/java/com/keylesspalace/tusky/util/Log.java
new file mode 100644
index 00000000..26d3d85f
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/util/Log.java
@@ -0,0 +1,53 @@
+/* Copyright 2017 Andrew Dawson
+ *
+ * This file is a part of Tusky.
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tusky; if not,
+ * see . */
+
+package com.keylesspalace.tusky.util;
+
+import com.keylesspalace.tusky.BuildConfig;
+
+/**A wrapper for android.util.Log that allows for disabling logging, such as for release builds.*/
+public class Log {
+ private static final boolean LOGGING_ENABLED = BuildConfig.DEBUG;
+
+ public static void i(String tag, String string) {
+ if (LOGGING_ENABLED) {
+ android.util.Log.i(tag, string);
+ }
+ }
+
+ public static void e(String tag, String string) {
+ if (LOGGING_ENABLED) {
+ android.util.Log.e(tag, string);
+ }
+ }
+
+ public static void d(String tag, String string) {
+ if (LOGGING_ENABLED) {
+ android.util.Log.d(tag, string);
+ }
+ }
+
+ public static void v(String tag, String string) {
+ if (LOGGING_ENABLED) {
+ android.util.Log.v(tag, string);
+ }
+ }
+
+ public static void w(String tag, String string) {
+ if (LOGGING_ENABLED) {
+ android.util.Log.w(tag, string);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/keylesspalace/tusky/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 9508dac6..d18902e3 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 8c7bfb85..e5bc7003 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/util/OkHttpUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.java
new file mode 100644
index 00000000..c90002d2
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.java
@@ -0,0 +1,244 @@
+/* Copyright 2017 Andrew Dawson
+ *
+ * This file is part of Tusky.
+ *
+ * Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser 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 Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along with Tusky. If
+ * not, see . */
+
+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;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+import okhttp3.ConnectionSpec;
+import okhttp3.Interceptor;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+
+public class OkHttpUtils {
+ static final String TAG = "OkHttpUtils"; // logging tag
+
+ /**
+ * Makes a Builder with the maximum range of TLS versions and cipher suites enabled.
+ *
+ * It first tries the "approved" list of cipher suites given in OkHttp (the default in
+ * ConnectionSpec.MODERN_TLS) and if that doesn't work falls back to the set of ALL enabled,
+ * then falls back to plain http.
+ *
+ * API level 24 has a regression in elliptic curves where it only supports secp256r1, so this
+ * first tries a fallback without elliptic curves at all, and then tries them after.
+ *
+ * TLS 1.1 and 1.2 have to be manually enabled on API levels 16-20.
+ */
+ @NonNull
+ public static OkHttpClient.Builder getCompatibleClientBuilder() {
+ ConnectionSpec fallback = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
+ .allEnabledCipherSuites()
+ .supportsTlsExtensions(true)
+ .build();
+
+ List specList = new ArrayList<>();
+ specList.add(ConnectionSpec.MODERN_TLS);
+ addNougatFixConnectionSpec(specList);
+ specList.add(fallback);
+ specList.add(ConnectionSpec.CLEARTEXT);
+
+ OkHttpClient.Builder builder = new OkHttpClient.Builder()
+ .addInterceptor(getUserAgentInterceptor())
+ .connectionSpecs(specList);
+
+ return enableHigherTlsOnPreLollipop(builder);
+ }
+
+ @NonNull
+ public static OkHttpClient getCompatibleClient() {
+ return getCompatibleClientBuilder().build();
+ }
+
+ /**
+ * Add a custom User-Agent that contains Tusky & Android Version to all requests
+ * Example:
+ * User-Agent: Tusky/1.1.2 Android/5.0.2
+ */
+ @NonNull
+ private static Interceptor getUserAgentInterceptor() {
+ return new Interceptor() {
+ @Override
+ 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)
+ .build();
+ return chain.proceed(requestWithUserAgent);
+ }
+ };
+ }
+
+
+ /**
+ * Android version Nougat has a regression where elliptic curve cipher suites are supported, but
+ * only the curve secp256r1 is allowed. So, first it's best to just disable all elliptic
+ * ciphers, try the connection, and fall back to the all cipher suites enabled list after.
+ */
+ private static void addNougatFixConnectionSpec(List specList) {
+ if (Build.VERSION.SDK_INT != Build.VERSION_CODES.N) {
+ return;
+ }
+ SSLSocketFactory socketFactory;
+ try {
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
+ TrustManagerFactory.getDefaultAlgorithm());
+ trustManagerFactory.init((KeyStore) null);
+ TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
+ if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
+ throw new IllegalStateException("Unexpected default trust managers:"
+ + Arrays.toString(trustManagers));
+ }
+
+ X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
+
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(null, new TrustManager[] { trustManager }, null);
+ socketFactory = sslContext.getSocketFactory();
+ } catch (NoSuchAlgorithmException|KeyStoreException|KeyManagementException e) {
+ Log.e(TAG, "Failed obtaining the SSL socket factory.");
+ return;
+ }
+ String[] cipherSuites = socketFactory.getDefaultCipherSuites();
+ ArrayList allowedList = new ArrayList<>();
+ for (String suite : cipherSuites) {
+ if (!suite.contains("ECDH")) {
+ allowedList.add(suite);
+ }
+ }
+ ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
+ .cipherSuites(allowedList.toArray(new String[0]))
+ .supportsTlsExtensions(true)
+ .build();
+ specList.add(spec);
+ }
+
+ private static OkHttpClient.Builder enableHigherTlsOnPreLollipop(OkHttpClient.Builder builder) {
+ if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 22) {
+ try {
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
+ TrustManagerFactory.getDefaultAlgorithm());
+ trustManagerFactory.init((KeyStore) null);
+ TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
+ if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
+ throw new IllegalStateException("Unexpected default trust managers:"
+ + Arrays.toString(trustManagers));
+ }
+
+ X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
+
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(null, new TrustManager[] { trustManager }, null);
+ SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
+
+ builder.sslSocketFactory(new SSLSocketFactoryCompat(sslSocketFactory),
+ trustManager);
+ } catch (NoSuchAlgorithmException|KeyStoreException|KeyManagementException e) {
+ Log.e(TAG, "Failed enabling TLS 1.1 & 1.2. " + e.getMessage());
+ }
+ }
+
+ return builder;
+ }
+
+ private static class SSLSocketFactoryCompat extends SSLSocketFactory {
+ private static final String[] DESIRED_TLS_VERSIONS = { "TLSv1", "TLSv1.1", "TLSv1.2",
+ "TLSv1.3" };
+
+ final SSLSocketFactory delegate;
+
+ SSLSocketFactoryCompat(SSLSocketFactory base) {
+ this.delegate = base;
+ }
+
+ @Override
+ public String[] getDefaultCipherSuites() {
+ return delegate.getDefaultCipherSuites();
+ }
+
+ @Override
+ public String[] getSupportedCipherSuites() {
+ return delegate.getSupportedCipherSuites();
+ }
+
+ @Override
+ public Socket createSocket(Socket s, String host, int port, boolean autoClose)
+ throws IOException {
+ return patch(delegate.createSocket(s, host, port, autoClose));
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException {
+ return patch(delegate.createSocket(host, port));
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
+ throws IOException {
+ return patch(delegate.createSocket(host, port, localHost, localPort));
+ }
+
+ @Override
+ public Socket createSocket(InetAddress host, int port) throws IOException {
+ return patch(delegate.createSocket(host, port));
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port, InetAddress localAddress,
+ int localPort) throws IOException {
+ return patch(delegate.createSocket(address, port, localAddress, localPort));
+ }
+
+ @NonNull
+ private static String[] getMatches(String[] wanted, String[] have) {
+ List a = new ArrayList<>(Arrays.asList(wanted));
+ List b = Arrays.asList(have);
+ a.retainAll(b);
+ return a.toArray(new String[0]);
+ }
+
+ private Socket patch(Socket socket) {
+ if (socket instanceof SSLSocket) {
+ SSLSocket sslSocket = (SSLSocket) socket;
+ String[] protocols = getMatches(DESIRED_TLS_VERSIONS,
+ sslSocket.getSupportedProtocols());
+ sslSocket.setEnabledProtocols(protocols);
+ }
+ return socket;
+ }
+ }
+}
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 bd4192b5..ff153d68 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/util/SpanUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.java
new file mode 100644
index 00000000..81a82d2b
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.java
@@ -0,0 +1,129 @@
+/* Copyright 2017 Andrew Dawson
+ *
+ * This file is a part of Tusky.
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tusky; if not,
+ * see . */
+
+package com.keylesspalace.tusky.util;
+
+import android.text.Spannable;
+import android.text.Spanned;
+import android.text.style.ForegroundColorSpan;
+
+public class SpanUtils {
+ private static class FindCharsResult {
+ int charIndex;
+ int stringIndex;
+
+ FindCharsResult() {
+ charIndex = -1;
+ stringIndex = -1;
+ }
+ }
+
+ private static FindCharsResult findChars(String string, int fromIndex, char[] chars) {
+ FindCharsResult result = new FindCharsResult();
+ final int length = string.length();
+ for (int i = fromIndex; i < length; i++) {
+ char c = string.charAt(i);
+ for (int j = 0; j < chars.length; j++) {
+ if (chars[j] == c) {
+ result.charIndex = j;
+ result.stringIndex = i;
+ return result;
+ }
+ }
+ }
+ return result;
+ }
+
+ private static FindCharsResult findStart(String string, int fromIndex, char[] chars) {
+ final int length = string.length();
+ while (fromIndex < length) {
+ FindCharsResult found = findChars(string, fromIndex, chars);
+ int i = found.stringIndex;
+ if (i < 0) {
+ break;
+ } else if (i == 0 || i >= 1 && Character.isWhitespace(string.codePointBefore(i))) {
+ return found;
+ } else {
+ fromIndex = i + 1;
+ }
+ }
+ return new FindCharsResult();
+ }
+
+ private static int findEndOfHashtag(String string, int fromIndex) {
+ final int length = string.length();
+ for (int i = fromIndex + 1; i < length;) {
+ int codepoint = string.codePointAt(i);
+ if (Character.isWhitespace(codepoint)) {
+ return i;
+ } else if (codepoint == '#') {
+ return -1;
+ }
+ i += Character.charCount(codepoint);
+ }
+ return length;
+ }
+
+ private static int findEndOfMention(String string, int fromIndex) {
+ int atCount = 0;
+ final int length = string.length();
+ for (int i = fromIndex + 1; i < length;) {
+ int codepoint = string.codePointAt(i);
+ if (Character.isWhitespace(codepoint)) {
+ return i;
+ } else if (codepoint == '@') {
+ atCount += 1;
+ if (atCount >= 2) {
+ return -1;
+ }
+ }
+ i += Character.charCount(codepoint);
+ }
+ return length;
+ }
+
+ 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);
+ for (int i = oldSpans.length - 1; i >= 0; i--) {
+ text.removeSpan(oldSpans[i]);
+ }
+ // Colour the mentions and hashtags.
+ String string = text.toString();
+ int start;
+ int end = 0;
+ while (end < n) {
+ char[] chars = { '#', '@' };
+ FindCharsResult found = findStart(string, end, chars);
+ start = found.stringIndex;
+ if (start < 0) {
+ break;
+ }
+ if (found.charIndex == 0) {
+ end = findEndOfHashtag(string, start);
+ } else if (found.charIndex == 1) {
+ end = findEndOfMention(string, start);
+ } else {
+ break;
+ }
+ if (end < 0) {
+ break;
+ }
+ text.setSpan(new ForegroundColorSpan(colour), start, end,
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ }
+ }
+}
diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java
new file mode 100644
index 00000000..310341fa
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java
@@ -0,0 +1,68 @@
+/* Copyright 2017 Andrew Dawson
+ *
+ * This file is a part of Tusky.
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tusky; if not,
+ * see . */
+
+package com.keylesspalace.tusky.util;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.AttrRes;
+import android.support.annotation.ColorInt;
+import android.support.annotation.DrawableRes;
+import android.support.v4.content.ContextCompat;
+import android.util.TypedValue;
+import android.widget.ImageView;
+
+public class ThemeUtils {
+ public static Drawable getDrawable(Context context, @AttrRes int attribute,
+ @DrawableRes int fallbackDrawable) {
+ TypedValue value = new TypedValue();
+ @DrawableRes int resourceId;
+ if (context.getTheme().resolveAttribute(attribute, value, true)) {
+ resourceId = value.resourceId;
+ } else {
+ resourceId = fallbackDrawable;
+ }
+ return ContextCompat.getDrawable(context, resourceId);
+ }
+
+ public static @DrawableRes int getDrawableId(Context context, @AttrRes int attribute,
+ @DrawableRes int fallbackDrawableId) {
+ TypedValue value = new TypedValue();
+ if (context.getTheme().resolveAttribute(attribute, value, true)) {
+ return value.resourceId;
+ } else {
+ return fallbackDrawableId;
+ }
+ }
+
+ public static @ColorInt int getColor(Context context, @AttrRes int attribute) {
+ TypedValue value = new TypedValue();
+ if (context.getTheme().resolveAttribute(attribute, value, true)) {
+ return value.data;
+ } else {
+ return Color.BLACK;
+ }
+ }
+
+ public static void setImageViewTint(ImageView view, @AttrRes int attribute) {
+ view.setColorFilter(getColor(view.getContext(), attribute), PorterDuff.Mode.SRC_IN);
+ }
+
+ 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/res/layout/activity_compose.xml b/app/src/main/res/layout/activity_compose.xml
index 9807e944..0252cce7 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">
-
-
-
+
Date: Thu, 4 May 2017 19:02:18 -0400
Subject: [PATCH 24/53] Fixes "google" build flavor, which was messed up by the
repacking process.
---
.../keylesspalace/tusky/MessagingService.java | 7 +
.../tusky/MyFirebaseInstanceIdService.java | 3 +
.../java/com/keylesspalace/tusky/Assert.java | 27 --
.../keylesspalace/tusky/CountUpDownLatch.java | 40 ---
.../com/keylesspalace/tusky/DateUtils.java | 50 ----
.../com/keylesspalace/tusky/HtmlUtils.java | 54 ----
.../java/com/keylesspalace/tusky/IOUtils.java | 44 ----
.../java/com/keylesspalace/tusky/Log.java | 51 ----
.../com/keylesspalace/tusky/OkHttpUtils.java | 242 ------------------
.../com/keylesspalace/tusky/SpanUtils.java | 129 ----------
.../com/keylesspalace/tusky/ThemeUtils.java | 68 -----
11 files changed, 10 insertions(+), 705 deletions(-)
delete mode 100644 app/src/main/java/com/keylesspalace/tusky/Assert.java
delete mode 100644 app/src/main/java/com/keylesspalace/tusky/CountUpDownLatch.java
delete mode 100644 app/src/main/java/com/keylesspalace/tusky/DateUtils.java
delete mode 100644 app/src/main/java/com/keylesspalace/tusky/HtmlUtils.java
delete mode 100644 app/src/main/java/com/keylesspalace/tusky/IOUtils.java
delete mode 100644 app/src/main/java/com/keylesspalace/tusky/Log.java
delete mode 100644 app/src/main/java/com/keylesspalace/tusky/OkHttpUtils.java
delete mode 100644 app/src/main/java/com/keylesspalace/tusky/SpanUtils.java
delete mode 100644 app/src/main/java/com/keylesspalace/tusky/ThemeUtils.java
diff --git a/app/src/google/java/com/keylesspalace/tusky/MessagingService.java b/app/src/google/java/com/keylesspalace/tusky/MessagingService.java
index cf4e5273..c32fff09 100644
--- a/app/src/google/java/com/keylesspalace/tusky/MessagingService.java
+++ b/app/src/google/java/com/keylesspalace/tusky/MessagingService.java
@@ -31,6 +31,13 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.keylesspalace.tusky.entity.Notification;
+import com.keylesspalace.tusky.json.SpannedTypeAdapter;
+import com.keylesspalace.tusky.json.StringWithEmoji;
+import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter;
+import com.keylesspalace.tusky.network.MastodonAPI;
+import com.keylesspalace.tusky.util.Log;
+import com.keylesspalace.tusky.util.NotificationMaker;
+import com.keylesspalace.tusky.util.OkHttpUtils;
import java.io.IOException;
diff --git a/app/src/google/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java b/app/src/google/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java
index adb47879..14d640d5 100644
--- a/app/src/google/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java
+++ b/app/src/google/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java
@@ -25,6 +25,9 @@ import android.content.SharedPreferences;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.FirebaseInstanceIdService;
+import com.keylesspalace.tusky.network.TuskyAPI;
+import com.keylesspalace.tusky.util.Log;
+import com.keylesspalace.tusky.util.OkHttpUtils;
import okhttp3.ResponseBody;
import retrofit2.Call;
diff --git a/app/src/main/java/com/keylesspalace/tusky/Assert.java b/app/src/main/java/com/keylesspalace/tusky/Assert.java
deleted file mode 100644
index e185cfb0..00000000
--- a/app/src/main/java/com/keylesspalace/tusky/Assert.java
+++ /dev/null
@@ -1,27 +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;
-
-/** Android Studio complains about built-in assertions so this is an alternative. */
-class Assert {
- private static boolean ENABLED = BuildConfig.DEBUG;
-
- static void expect(boolean expression) {
- if (ENABLED && !expression) {
- throw new AssertionError();
- }
- }
-}
diff --git a/app/src/main/java/com/keylesspalace/tusky/CountUpDownLatch.java b/app/src/main/java/com/keylesspalace/tusky/CountUpDownLatch.java
deleted file mode 100644
index e0d95438..00000000
--- a/app/src/main/java/com/keylesspalace/tusky/CountUpDownLatch.java
+++ /dev/null
@@ -1,40 +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;
-
-class CountUpDownLatch {
- private int count;
-
- CountUpDownLatch() {
- this.count = 0;
- }
-
- synchronized void countDown() {
- count--;
- notifyAll();
- }
-
- synchronized void countUp() {
- count++;
- notifyAll();
- }
-
- synchronized void await() throws InterruptedException {
- while (count != 0) {
- wait();
- }
- }
-}
diff --git a/app/src/main/java/com/keylesspalace/tusky/DateUtils.java b/app/src/main/java/com/keylesspalace/tusky/DateUtils.java
deleted file mode 100644
index 16137f87..00000000
--- a/app/src/main/java/com/keylesspalace/tusky/DateUtils.java
+++ /dev/null
@@ -1,50 +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;
-
-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) {
- final long MINUTE = 60;
- final long HOUR = 60 * MINUTE;
- final long DAY = 24 * HOUR;
- final long YEAR = 365 * DAY;
- long span = (now - then) / 1000;
- String prefix = "";
- if (span < 0) {
- prefix = "in ";
- span = -span;
- }
- String unit;
- if (span < MINUTE) {
- unit = "s";
- } else if (span < HOUR) {
- span /= MINUTE;
- unit = "m";
- } else if (span < DAY) {
- span /= HOUR;
- unit = "h";
- } else if (span < YEAR) {
- span /= DAY;
- unit = "d";
- } else {
- span /= YEAR;
- unit = "y";
- }
- return prefix + span + unit;
- }
-}
diff --git a/app/src/main/java/com/keylesspalace/tusky/HtmlUtils.java b/app/src/main/java/com/keylesspalace/tusky/HtmlUtils.java
deleted file mode 100644
index d37b4cd2..00000000
--- a/app/src/main/java/com/keylesspalace/tusky/HtmlUtils.java
+++ /dev/null
@@ -1,54 +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.os.Build;
-import android.text.Html;
-import android.text.Spanned;
-
-public class HtmlUtils {
- private static CharSequence trimTrailingWhitespace(CharSequence s) {
- int i = s.length();
- do {
- i--;
- } while (i >= 0 && Character.isWhitespace(s.charAt(i)));
- return s.subSequence(0, i + 1);
- }
-
- @SuppressWarnings("deprecation")
- public static Spanned fromHtml(String html) {
- Spanned result;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- result = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
- } else {
- result = Html.fromHtml(html);
- }
- /* Html.fromHtml returns trailing whitespace if the html ends in a tag, which
- * all status contents do, so it should be trimmed. */
- return (Spanned) trimTrailingWhitespace(result);
- }
-
- @SuppressWarnings("deprecation")
- public static String toHtml(Spanned text) {
- String result;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- result = Html.toHtml(text, Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE);
- } else {
- result = Html.toHtml(text);
- }
- return result;
- }
-}
diff --git a/app/src/main/java/com/keylesspalace/tusky/IOUtils.java b/app/src/main/java/com/keylesspalace/tusky/IOUtils.java
deleted file mode 100644
index 76e53b82..00000000
--- a/app/src/main/java/com/keylesspalace/tusky/IOUtils.java
+++ /dev/null
@@ -1,44 +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.support.annotation.Nullable;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-class IOUtils {
- static void closeQuietly(@Nullable InputStream stream) {
- try {
- if (stream != null) {
- stream.close();
- }
- } catch (IOException e) {
- // intentionally unhandled
- }
- }
-
- static void closeQuietly(@Nullable OutputStream stream) {
- try {
- if (stream != null) {
- stream.close();
- }
- } catch (IOException e) {
- // intentionally unhandled
- }
- }
-}
diff --git a/app/src/main/java/com/keylesspalace/tusky/Log.java b/app/src/main/java/com/keylesspalace/tusky/Log.java
deleted file mode 100644
index d36544ee..00000000
--- a/app/src/main/java/com/keylesspalace/tusky/Log.java
+++ /dev/null
@@ -1,51 +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;
-
-/**A wrapper for android.util.Log that allows for disabling logging, such as for release builds.*/
-public class Log {
- private static final boolean LOGGING_ENABLED = BuildConfig.DEBUG;
-
- public static void i(String tag, String string) {
- if (LOGGING_ENABLED) {
- android.util.Log.i(tag, string);
- }
- }
-
- public static void e(String tag, String string) {
- if (LOGGING_ENABLED) {
- android.util.Log.e(tag, string);
- }
- }
-
- public static void d(String tag, String string) {
- if (LOGGING_ENABLED) {
- android.util.Log.d(tag, string);
- }
- }
-
- public static void v(String tag, String string) {
- if (LOGGING_ENABLED) {
- android.util.Log.v(tag, string);
- }
- }
-
- public static void w(String tag, String string) {
- if (LOGGING_ENABLED) {
- android.util.Log.w(tag, string);
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/keylesspalace/tusky/OkHttpUtils.java b/app/src/main/java/com/keylesspalace/tusky/OkHttpUtils.java
deleted file mode 100644
index a821df4d..00000000
--- a/app/src/main/java/com/keylesspalace/tusky/OkHttpUtils.java
+++ /dev/null
@@ -1,242 +0,0 @@
-/* Copyright 2017 Andrew Dawson
- *
- * This file is part of Tusky.
- *
- * Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
- * Lesser 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 Lesser
- * General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License along with Tusky. If
- * not, see . */
-
-package com.keylesspalace.tusky;
-
-import android.os.Build;
-import android.support.annotation.NonNull;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.security.KeyManagementException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
-import javax.net.ssl.X509TrustManager;
-
-import okhttp3.ConnectionSpec;
-import okhttp3.Interceptor;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.Response;
-
-public class OkHttpUtils {
- static final String TAG = "OkHttpUtils"; // logging tag
-
- /**
- * Makes a Builder with the maximum range of TLS versions and cipher suites enabled.
- *
- * It first tries the "approved" list of cipher suites given in OkHttp (the default in
- * ConnectionSpec.MODERN_TLS) and if that doesn't work falls back to the set of ALL enabled,
- * then falls back to plain http.
- *
- * API level 24 has a regression in elliptic curves where it only supports secp256r1, so this
- * first tries a fallback without elliptic curves at all, and then tries them after.
- *
- * TLS 1.1 and 1.2 have to be manually enabled on API levels 16-20.
- */
- @NonNull
- public static OkHttpClient.Builder getCompatibleClientBuilder() {
- ConnectionSpec fallback = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
- .allEnabledCipherSuites()
- .supportsTlsExtensions(true)
- .build();
-
- List specList = new ArrayList<>();
- specList.add(ConnectionSpec.MODERN_TLS);
- addNougatFixConnectionSpec(specList);
- specList.add(fallback);
- specList.add(ConnectionSpec.CLEARTEXT);
-
- OkHttpClient.Builder builder = new OkHttpClient.Builder()
- .addInterceptor(getUserAgentInterceptor())
- .connectionSpecs(specList);
-
- return enableHigherTlsOnPreLollipop(builder);
- }
-
- @NonNull
- public static OkHttpClient getCompatibleClient() {
- return getCompatibleClientBuilder().build();
- }
-
- /**
- * Add a custom User-Agent that contains Tusky & Android Version to all requests
- * Example:
- * User-Agent: Tusky/1.1.2 Android/5.0.2
- */
- @NonNull
- private static Interceptor getUserAgentInterceptor() {
- return new Interceptor() {
- @Override
- 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)
- .build();
- return chain.proceed(requestWithUserAgent);
- }
- };
- }
-
-
- /**
- * Android version Nougat has a regression where elliptic curve cipher suites are supported, but
- * only the curve secp256r1 is allowed. So, first it's best to just disable all elliptic
- * ciphers, try the connection, and fall back to the all cipher suites enabled list after.
- */
- private static void addNougatFixConnectionSpec(List specList) {
- if (Build.VERSION.SDK_INT != Build.VERSION_CODES.N) {
- return;
- }
- SSLSocketFactory socketFactory;
- try {
- TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
- TrustManagerFactory.getDefaultAlgorithm());
- trustManagerFactory.init((KeyStore) null);
- TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
- if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
- throw new IllegalStateException("Unexpected default trust managers:"
- + Arrays.toString(trustManagers));
- }
-
- X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
-
- SSLContext sslContext = SSLContext.getInstance("TLS");
- sslContext.init(null, new TrustManager[] { trustManager }, null);
- socketFactory = sslContext.getSocketFactory();
- } catch (NoSuchAlgorithmException|KeyStoreException|KeyManagementException e) {
- Log.e(TAG, "Failed obtaining the SSL socket factory.");
- return;
- }
- String[] cipherSuites = socketFactory.getDefaultCipherSuites();
- ArrayList allowedList = new ArrayList<>();
- for (String suite : cipherSuites) {
- if (!suite.contains("ECDH")) {
- allowedList.add(suite);
- }
- }
- ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
- .cipherSuites(allowedList.toArray(new String[0]))
- .supportsTlsExtensions(true)
- .build();
- specList.add(spec);
- }
-
- private static OkHttpClient.Builder enableHigherTlsOnPreLollipop(OkHttpClient.Builder builder) {
- if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 22) {
- try {
- TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
- TrustManagerFactory.getDefaultAlgorithm());
- trustManagerFactory.init((KeyStore) null);
- TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
- if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
- throw new IllegalStateException("Unexpected default trust managers:"
- + Arrays.toString(trustManagers));
- }
-
- X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
-
- SSLContext sslContext = SSLContext.getInstance("TLS");
- sslContext.init(null, new TrustManager[] { trustManager }, null);
- SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
-
- builder.sslSocketFactory(new SSLSocketFactoryCompat(sslSocketFactory),
- trustManager);
- } catch (NoSuchAlgorithmException|KeyStoreException|KeyManagementException e) {
- Log.e(TAG, "Failed enabling TLS 1.1 & 1.2. " + e.getMessage());
- }
- }
-
- return builder;
- }
-
- private static class SSLSocketFactoryCompat extends SSLSocketFactory {
- private static final String[] DESIRED_TLS_VERSIONS = { "TLSv1", "TLSv1.1", "TLSv1.2",
- "TLSv1.3" };
-
- final SSLSocketFactory delegate;
-
- SSLSocketFactoryCompat(SSLSocketFactory base) {
- this.delegate = base;
- }
-
- @Override
- public String[] getDefaultCipherSuites() {
- return delegate.getDefaultCipherSuites();
- }
-
- @Override
- public String[] getSupportedCipherSuites() {
- return delegate.getSupportedCipherSuites();
- }
-
- @Override
- public Socket createSocket(Socket s, String host, int port, boolean autoClose)
- throws IOException {
- return patch(delegate.createSocket(s, host, port, autoClose));
- }
-
- @Override
- public Socket createSocket(String host, int port) throws IOException {
- return patch(delegate.createSocket(host, port));
- }
-
- @Override
- public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
- throws IOException {
- return patch(delegate.createSocket(host, port, localHost, localPort));
- }
-
- @Override
- public Socket createSocket(InetAddress host, int port) throws IOException {
- return patch(delegate.createSocket(host, port));
- }
-
- @Override
- public Socket createSocket(InetAddress address, int port, InetAddress localAddress,
- int localPort) throws IOException {
- return patch(delegate.createSocket(address, port, localAddress, localPort));
- }
-
- @NonNull
- private static String[] getMatches(String[] wanted, String[] have) {
- List a = new ArrayList<>(Arrays.asList(wanted));
- List b = Arrays.asList(have);
- a.retainAll(b);
- return a.toArray(new String[0]);
- }
-
- private Socket patch(Socket socket) {
- if (socket instanceof SSLSocket) {
- SSLSocket sslSocket = (SSLSocket) socket;
- String[] protocols = getMatches(DESIRED_TLS_VERSIONS,
- sslSocket.getSupportedProtocols());
- sslSocket.setEnabledProtocols(protocols);
- }
- return socket;
- }
- }
-}
diff --git a/app/src/main/java/com/keylesspalace/tusky/SpanUtils.java b/app/src/main/java/com/keylesspalace/tusky/SpanUtils.java
deleted file mode 100644
index 09936e45..00000000
--- a/app/src/main/java/com/keylesspalace/tusky/SpanUtils.java
+++ /dev/null
@@ -1,129 +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.text.Spannable;
-import android.text.Spanned;
-import android.text.style.ForegroundColorSpan;
-
-class SpanUtils {
- private static class FindCharsResult {
- int charIndex;
- int stringIndex;
-
- FindCharsResult() {
- charIndex = -1;
- stringIndex = -1;
- }
- }
-
- private static FindCharsResult findChars(String string, int fromIndex, char[] chars) {
- FindCharsResult result = new FindCharsResult();
- final int length = string.length();
- for (int i = fromIndex; i < length; i++) {
- char c = string.charAt(i);
- for (int j = 0; j < chars.length; j++) {
- if (chars[j] == c) {
- result.charIndex = j;
- result.stringIndex = i;
- return result;
- }
- }
- }
- return result;
- }
-
- private static FindCharsResult findStart(String string, int fromIndex, char[] chars) {
- final int length = string.length();
- while (fromIndex < length) {
- FindCharsResult found = findChars(string, fromIndex, chars);
- int i = found.stringIndex;
- if (i < 0) {
- break;
- } else if (i == 0 || i >= 1 && Character.isWhitespace(string.codePointBefore(i))) {
- return found;
- } else {
- fromIndex = i + 1;
- }
- }
- return new FindCharsResult();
- }
-
- private static int findEndOfHashtag(String string, int fromIndex) {
- final int length = string.length();
- for (int i = fromIndex + 1; i < length;) {
- int codepoint = string.codePointAt(i);
- if (Character.isWhitespace(codepoint)) {
- return i;
- } else if (codepoint == '#') {
- return -1;
- }
- i += Character.charCount(codepoint);
- }
- return length;
- }
-
- private static int findEndOfMention(String string, int fromIndex) {
- int atCount = 0;
- final int length = string.length();
- for (int i = fromIndex + 1; i < length;) {
- int codepoint = string.codePointAt(i);
- if (Character.isWhitespace(codepoint)) {
- return i;
- } else if (codepoint == '@') {
- atCount += 1;
- if (atCount >= 2) {
- return -1;
- }
- }
- i += Character.charCount(codepoint);
- }
- return length;
- }
-
- static void highlightSpans(Spannable text, int colour) {
- // Strip all existing colour spans.
- int n = text.length();
- ForegroundColorSpan[] oldSpans = text.getSpans(0, n, ForegroundColorSpan.class);
- for (int i = oldSpans.length - 1; i >= 0; i--) {
- text.removeSpan(oldSpans[i]);
- }
- // Colour the mentions and hashtags.
- String string = text.toString();
- int start;
- int end = 0;
- while (end < n) {
- char[] chars = { '#', '@' };
- FindCharsResult found = findStart(string, end, chars);
- start = found.stringIndex;
- if (start < 0) {
- break;
- }
- if (found.charIndex == 0) {
- end = findEndOfHashtag(string, start);
- } else if (found.charIndex == 1) {
- end = findEndOfMention(string, start);
- } else {
- break;
- }
- if (end < 0) {
- break;
- }
- text.setSpan(new ForegroundColorSpan(colour), start, end,
- Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
- }
- }
-}
diff --git a/app/src/main/java/com/keylesspalace/tusky/ThemeUtils.java b/app/src/main/java/com/keylesspalace/tusky/ThemeUtils.java
deleted file mode 100644
index 6e7a908d..00000000
--- a/app/src/main/java/com/keylesspalace/tusky/ThemeUtils.java
+++ /dev/null
@@ -1,68 +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.content.Context;
-import android.graphics.Color;
-import android.graphics.PorterDuff;
-import android.graphics.drawable.Drawable;
-import android.support.annotation.AttrRes;
-import android.support.annotation.ColorInt;
-import android.support.annotation.DrawableRes;
-import android.support.v4.content.ContextCompat;
-import android.util.TypedValue;
-import android.widget.ImageView;
-
-class ThemeUtils {
- static Drawable getDrawable(Context context, @AttrRes int attribute,
- @DrawableRes int fallbackDrawable) {
- TypedValue value = new TypedValue();
- @DrawableRes int resourceId;
- if (context.getTheme().resolveAttribute(attribute, value, true)) {
- resourceId = value.resourceId;
- } else {
- resourceId = fallbackDrawable;
- }
- return ContextCompat.getDrawable(context, resourceId);
- }
-
- static @DrawableRes int getDrawableId(Context context, @AttrRes int attribute,
- @DrawableRes int fallbackDrawableId) {
- TypedValue value = new TypedValue();
- if (context.getTheme().resolveAttribute(attribute, value, true)) {
- return value.resourceId;
- } else {
- return fallbackDrawableId;
- }
- }
-
- static @ColorInt int getColor(Context context, @AttrRes int attribute) {
- TypedValue value = new TypedValue();
- if (context.getTheme().resolveAttribute(attribute, value, true)) {
- return value.data;
- } else {
- return Color.BLACK;
- }
- }
-
- 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) {
- drawable.setColorFilter(getColor(context, attribute), PorterDuff.Mode.SRC_IN);
- }
-}
From d34f4429cd21f9c92f7d14fff8e1a99e4cffc215 Mon Sep 17 00:00:00 2001
From: Conny Duck
Date: Fri, 5 May 2017 17:21:20 +0200
Subject: [PATCH 25/53] fixed light theme header background when no header
image was set
---
app/src/main/java/com/keylesspalace/tusky/MainActivity.java | 3 +++
1 file changed, 3 insertions(+)
diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
index c93bb5ed..f6d2672d 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
@@ -29,6 +29,7 @@ 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.text.SpannableStringBuilder;
import android.text.Spanned;
@@ -479,6 +480,8 @@ 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)
From a5e43446b5d46167247602548052ce90b85a2808 Mon Sep 17 00:00:00 2001
From: Tryton Van Meer
Date: Fri, 5 May 2017 12:19:11 -0400
Subject: [PATCH 26/53] logout() now creates a dialog asking the user if they
wish to logout.
---
.../com/keylesspalace/tusky/MainActivity.java | 28 +++++++++++++------
1 file changed, 20 insertions(+), 8 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
index c93bb5ed..c9ad6b85 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
@@ -16,6 +16,7 @@
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;
@@ -30,6 +31,7 @@ import android.support.design.widget.TabLayout;
import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
+import android.support.v7.app.AlertDialog;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
@@ -337,16 +339,26 @@ 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")
+ .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() {
From 08cd604e660f9d3a73d2549e272bd781572972ef Mon Sep 17 00:00:00 2001
From: Tryton Van Meer
Date: Fri, 5 May 2017 12:19:34 -0400
Subject: [PATCH 27/53] Added string 'action_logout_confirm'
---
app/src/main/res/values/strings.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5a8f3573..53a75910 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -59,6 +59,7 @@
ComposeLogin with MastodonLog Out
+ Do you wish to logoutFollowUnfollowBlock
From fc2d039d634af6f28f03455fdca402220dd21bde Mon Sep 17 00:00:00 2001
From: Paul
Date: Sat, 6 May 2017 11:30:52 +0000
Subject: [PATCH 28/53] Add loader when waiting to display a media
---
.../main/java/com/keylesspalace/tusky/ViewVideoActivity.java | 4 ++++
.../com/keylesspalace/tusky/fragment/ViewMediaFragment.java | 3 ++-
app/src/main/res/layout/activity_view_video.xml | 5 +++++
app/src/main/res/layout/fragment_view_media.xml | 5 +++++
4 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewVideoActivity.java b/app/src/main/java/com/keylesspalace/tusky/ViewVideoActivity.java
index 05ec430e..9f1c4bf0 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/fragment/ViewMediaFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.java
index 9a5dbee2..f251743f 100644
--- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.java
@@ -82,7 +82,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();
@@ -141,6 +141,7 @@ public class ViewMediaFragment extends DialogFragment {
.into(photoView, new Callback() {
@Override
public void onSuccess() {
+ rootView.findViewById(R.id.view_media_progress).setVisibility(View.GONE);
attacher.update();
}
diff --git a/app/src/main/res/layout/activity_view_video.xml b/app/src/main/res/layout/activity_view_video.xml
index 79b1207e..7a15f215 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" />
+
+
Date: Sun, 7 May 2017 16:14:28 -0400
Subject: [PATCH 29/53] Moves out-of-place Firebase InstanceId call to the
google build flavor. Closes #274
---
.../java/com/keylesspalace/tusky/MessagingService.java | 5 +++++
.../java/com/keylesspalace/tusky/MessagingService.java | 5 +++++
app/src/main/java/com/keylesspalace/tusky/BaseActivity.java | 2 +-
3 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/app/src/fdroid/java/com/keylesspalace/tusky/MessagingService.java b/app/src/fdroid/java/com/keylesspalace/tusky/MessagingService.java
index ca446945..eaa1b1b5 100644
--- a/app/src/fdroid/java/com/keylesspalace/tusky/MessagingService.java
+++ b/app/src/fdroid/java/com/keylesspalace/tusky/MessagingService.java
@@ -134,4 +134,9 @@ public class MessagingService extends IntentService {
.putStringSet("current_ids", currentIds)
.apply();
}
+
+ public static String getInstanceToken() {
+ // This is only used for the "google" build flavor, so this version is just a stub method.
+ return null;
+ }
}
diff --git a/app/src/google/java/com/keylesspalace/tusky/MessagingService.java b/app/src/google/java/com/keylesspalace/tusky/MessagingService.java
index c32fff09..4a2a55da 100644
--- a/app/src/google/java/com/keylesspalace/tusky/MessagingService.java
+++ b/app/src/google/java/com/keylesspalace/tusky/MessagingService.java
@@ -25,6 +25,7 @@ import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.text.Spanned;
+import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import com.google.gson.Gson;
@@ -125,4 +126,8 @@ public class MessagingService extends FirebaseMessagingService {
mastodonAPI = retrofit.create(MastodonAPI.class);
}
+
+ public static String getInstanceToken() {
+ return FirebaseInstanceId.getInstance().getToken();
+ }
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java
index 1803cacd..859b4649 100644
--- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java
@@ -204,7 +204,7 @@ public class BaseActivity extends AppCompatActivity {
protected void enablePushNotifications() {
if (BuildConfig.USES_PUSH_NOTIFICATIONS) {
- String token = com.google.firebase.iid.FirebaseInstanceId.getInstance().getToken();
+ String token = MessagingService.getInstanceToken();
tuskyAPI.register(getBaseUrl(), getAccessToken(), token).enqueue(new Callback() {
@Override
public void onResponse(Call call, retrofit2.Response response) {
From 551d73baeeee7a16f32d1e21611a017747456060 Mon Sep 17 00:00:00 2001
From: Vavassor
Date: Sun, 7 May 2017 22:00:29 -0400
Subject: [PATCH 30/53] Makes about page work with light theme and landscape
orientation.
---
.../keylesspalace/tusky/AboutActivity.java | 19 +--
app/src/main/res/layout/activity_about.xml | 126 +++++++++---------
app/src/main/res/values/strings.xml | 12 +-
app/src/main/res/values/styles.xml | 43 ++----
4 files changed, 90 insertions(+), 110 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java
index 06c53226..bc5fd14e 100644
--- a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java
@@ -8,26 +8,21 @@ import android.view.View;
import android.widget.Button;
import android.widget.TextView;
-public class AboutActivity extends AppCompatActivity {
- private TextView mVersionTextView;
- private TextView mProjectSiteTextView;
- private TextView mFeatureSiteTextView;
- private Button mTuskyAccountButton;
-
+public class AboutActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
+
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
- mVersionTextView = (TextView) findViewById(R.id.versionTV);
- mProjectSiteTextView = (TextView) findViewById(R.id.projectURL_TV);
- mFeatureSiteTextView = (TextView) findViewById(R.id.featuresURL_TV);
- mTuskyAccountButton = (Button) findViewById(R.id.tusky_profile_button);
+
+ TextView versionTextView = (TextView) findViewById(R.id.versionTV);
+ Button mTuskyAccountButton = (Button) findViewById(R.id.tusky_profile_button);
String versionName = BuildConfig.VERSION_NAME;
-
- mVersionTextView.setText(getString(R.string.about_application_version) + versionName);
+ String versionFormat = getString(R.string.about_application_version);
+ versionTextView.setText(String.format(versionFormat, versionName));
mTuskyAccountButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml
index d28963d0..2c83347e 100644
--- a/app/src/main/res/layout/activity_about.xml
+++ b/app/src/main/res/layout/activity_about.xml
@@ -4,87 +4,85 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:padding="@dimen/material_drawer_item_padding"
tools:context="com.keylesspalace.tusky.AboutActivity">
-
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ android:background="?attr/colorPrimary" />
-
+
+
-
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:orientation="vertical"
- android:id="@+id/linearLayoutCompat">
+
-
+
-
+
-
+
-
-
-
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c852a7b3..7b99be65 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -168,9 +168,15 @@
Locked AccountAbout
- App version:
- Project website: \n https://tusky.keylesspalace.com
- Bug reports & feature requests: \n https://github.com/Vavassor/Tusky/issues
+ App version: %s
+
+ Project website:\n
+ https://tusky.keylesspalace.com
+
+
+ Bug reports & feature requests:\n
+ https://github.com/Vavassor/Tusky/issues
+ Tusky\'s ProfileShare content of toot
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 930bb7d8..0256ee02 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -30,8 +30,7 @@
@color/text_color_secondary_dark@color/text_color_tertiary_dark@color/text_color_primary_inverse_dark
- @color/text_color_secondary_inverse_dark
-
+ @color/text_color_secondary_inverse_dark@color/text_color_tertiary_inverse_dark@color/text_color_primary_dark
@@ -54,15 +53,11 @@
@drawable/tab_page_margin_dark@color/account_header_background_dark@color/toolbar_icon_dark
-
- @color/account_toolbar_icon_collapsed_dark
-
- @style/AppTheme.Account.ToolbarPopupTheme.Dark
-
+ @color/account_toolbar_icon_collapsed_dark
+ @style/AppTheme.Account.ToolbarPopupTheme.Dark@color/toolbar_icon_dark@color/compose_media_button_dark
- @color/compose_media_button_disabled_dark
-
+ @color/compose_media_button_disabled_dark@color/color_accent_dark@drawable/border_background_dark@color/image_button_dark
@@ -122,10 +117,8 @@
@color/text_color_secondary_light@color/text_color_tertiary_light@color/text_color_primary_inverse_light
- @color/text_color_secondary_inverse_light
-
- @color/text_color_tertiary_inverse_light
-
+ @color/text_color_secondary_inverse_light
+ @color/text_color_tertiary_inverse_light@color/text_color_primary_light@style/AppTheme.BottomSheetDialog.Light
@@ -140,25 +133,18 @@
@drawable/favourite_active_light@drawable/favourite_inactive_light@drawable/toggle_small_light
-
- @color/sensitive_media_warning_background_light
-
+ @color/sensitive_media_warning_background_light@drawable/media_preview_unloaded_light@drawable/status_divider_light@color/color_accent_light@drawable/tab_page_margin_light@color/account_header_background_light
- @color/toolbar_icon_dark
-
-
- @color/account_toolbar_icon_collapsed_light
-
- @style/AppTheme.Account.ToolbarPopupTheme.Light
-
+ @color/toolbar_icon_dark
+ @color/account_toolbar_icon_collapsed_light
+ @style/AppTheme.Account.ToolbarPopupTheme.Light@color/toolbar_icon_light@color/compose_media_button_light
- @color/compose_media_button_disabled_light
-
+ @color/compose_media_button_disabled_light@color/compose_mention_light@drawable/border_background_light@color/image_button_light
@@ -175,8 +161,7 @@
@color/color_primary_dark_light@color/color_primary_light@color/text_color_primary_light
- @color/text_color_primary_dark
-
+ @color/text_color_primary_dark@color/toolbar_background_light@color/text_color_secondary_light
@@ -204,8 +189,4 @@
true
-
-
-
-
From d2b5fb24a26878312ef3d48e21e119b082047196 Mon Sep 17 00:00:00 2001
From: Vavassor
Date: Sun, 7 May 2017 22:04:02 -0400
Subject: [PATCH 31/53] Removes an unused library from build.gradle.
---
app/build.gradle | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 62555c02..cf42c979 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -46,7 +46,6 @@ dependencies {
compile 'com.android.support:support-v13:25.3.1'
compile 'com.android.support:design:25.3.1'
compile 'com.android.support:exifinterface:25.3.1'
- compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha7'
compile 'com.squareup.retrofit2:retrofit:2.2.0'
compile 'com.squareup.retrofit2:converter-gson:2.2.0'
compile 'com.squareup.picasso:picasso:2.5.2'
@@ -66,4 +65,4 @@ dependencies {
annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
}
-apply plugin: 'com.google.gms.google-services'
\ No newline at end of file
+apply plugin: 'com.google.gms.google-services'
From 17f347cab50e556a25d7b9c072ee357b65c9f424 Mon Sep 17 00:00:00 2001
From: Paul
Date: Mon, 8 May 2017 10:17:41 +0000
Subject: [PATCH 32/53] Add loading on login activity while fetching tokens
---
.../keylesspalace/tusky/LoginActivity.java | 22 ++++++
app/src/main/res/layout/activity_login.xml | 78 ++++++++++++-------
app/src/main/res/values-fr/strings.xml | 2 +
app/src/main/res/values/strings.xml | 2 +
4 files changed, 77 insertions(+), 27 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java
index 8ec47748..9226dc6c 100644
--- a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java
@@ -32,6 +32,7 @@ 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;
@@ -62,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;
@@ -326,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() {
@@ -334,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),
@@ -343,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),
@@ -355,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/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
index 8df45c40..f65930e6 100644
--- a/app/src/main/res/layout/activity_login.xml
+++ b/app/src/main/res/layout/activity_login.xml
@@ -20,39 +20,63 @@
android:src="@drawable/elephant_friend"
android:contentDescription="@null" />
-
-
+
-
+ 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/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 82250ab7..db0fe884 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -111,6 +111,8 @@
Qu’est-ce qu’une instance ?
+ 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
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 7b99be65..b13921d5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -122,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!
From a2a600a9dec116618ec115282d764ec0124e92f0 Mon Sep 17 00:00:00 2001
From: Vavassor
Date: Mon, 8 May 2017 15:00:33 -0400
Subject: [PATCH 33/53] Adds back button to the about page and for the Tusky's
Profile button it searches for the account first and saves the ID for
subsequent presses. Closes #279
---
.../keylesspalace/tusky/AboutActivity.java | 83 +++++++++++++++++--
.../com/keylesspalace/tusky/MainActivity.java | 1 +
app/src/main/res/layout/activity_about.xml | 6 +-
3 files changed, 82 insertions(+), 8 deletions(-)
diff --git a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java
index bc5fd14e..01e59066 100644
--- a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.java
@@ -2,13 +2,25 @@ package com.keylesspalace.tusky;
import android.content.Intent;
import android.os.Bundle;
-import android.support.v7.app.AppCompatActivity;
+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);
@@ -16,24 +28,81 @@ public class AboutActivity extends BaseActivity {
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);
- Button mTuskyAccountButton = (Button) findViewById(R.id.tusky_profile_button);
-
String versionName = BuildConfig.VERSION_NAME;
String versionFormat = getString(R.string.about_application_version);
versionTextView.setText(String.format(versionFormat, versionName));
- mTuskyAccountButton.setOnClickListener(new View.OnClickListener() {
+
+ appAccountButton = (Button) findViewById(R.id.tusky_profile_button);
+ appAccountButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- onAccountTVClick();
+ onAccountButtonClick();
}
});
}
- private void onAccountTVClick() {
+ 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", "72306");
+ 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/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
index 7f183a94..3d39b81a 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
@@ -355,6 +355,7 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
getPrivatePreferences().edit()
.remove("domain")
.remove("accessToken")
+ .remove("appAccountId")
.apply();
Intent intent = new Intent(MainActivity.this, LoginActivity.class);
diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml
index 2c83347e..c36fd02b 100644
--- a/app/src/main/res/layout/activity_about.xml
+++ b/app/src/main/res/layout/activity_about.xml
@@ -15,7 +15,8 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
- android:background="?attr/colorPrimary" />
+ android:background="?attr/toolbar_background_color"
+ android:elevation="4dp" />
Date: Mon, 8 May 2017 16:30:25 -0400
Subject: [PATCH 34/53] Adds a higher-resolution elephant friend to the login
page. Closes #99
---
app/src/main/res/drawable/elephant_friend.png | Bin 24466 -> 41126 bytes
app/src/main/res/layout/activity_login.xml | 4 ++--
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/src/main/res/drawable/elephant_friend.png b/app/src/main/res/drawable/elephant_friend.png
index 3c5145ba987ff14621d0632cec74e02eb3886d8b..a4e5e3e61956944daa51f15ffef64df4f614dd47 100644
GIT binary patch
literal 41126
zcmXtf1yGx9({*qQ6eqY92=4Cg?(R}FxVvkixLYa39f}qAVx<%)?(Xh?p7)S!L{8rw03d1p?*(HrA2JWUNa!K0=b`Chr?rS>y=GJP;$_OjCb2A%+hk4BEVJt8DE$)~<
z!ef)66gqy+%)B>|km&a}NBL+*fq8X7%>;u~PbvpA8D3s22$U%${ABYrib=e9+r?ns;s--OK}&w@Pk;NZMbR4B!hhA|PTPO>;s!b`Wy!B9
zr^7cAk3Gg8D`HEH0coBBrn#O)PI>=~*iV+*KI3dJsba>d!mw#Q0|x;4&ZK^wo(D&N
z22N+h>MM#hum-m|Sf*$%OJ(;kKkq9pHw*^f?W9=dUJC!?l5KLo=5cDpt#f&?Vb6Hw
zfRUN_Trm$E_BLP>u^&E&L6Ctnyd}Yaxwtzlp09uSC#srBl6vj-tiNc}9GuLvOjPJn
zD6H662vc@Wqb>9FrjEAuwq@<2kp#vfCD7jbfEudoqEe4GHvHvFWxWF6UXs+ae|N
z4^Faqk5fb5Me?m8atU-NxO=E!F4QZvnVByhh|J!(s@3GgiMCx>T<1AfTIOq6^1Au_
z@Tx^=ide>*PHqX>(5{1Q)d~s}sdHBSlo3jqh6MxLz_0%*RsYxw{BM+~*;#c2{`bX*z38)XY^L
z;S>sr22BK|0N{$sMw$)qUAmg!+E#Y}O@6`N2<>@c)2{zp3`DdZVGlEY|
zwf~Jb8FKmbLar9l1OL6|rD%S9)5+y%xZtuyu&aulKV6M1*iF0*cYr_T&ll0fWV9B2
z-T%BgYKxF$T@@5SSkwu1C@tVwKss8TZyB1sv*mB0RC>bT`;DcKWrN5RL-jjrk<_x-
z#BL2d#|r;V5wk-^2EL`_O%xDFSNS#n1jMr$3f+`u2cBl~^_MAM`s^05O!uZ7;X;
zbV=wh^j%^Wc;8sSH=F>^baJk2EuUwBAtvsvM}h&LM?I@UiJkRts^pI=QKrq49kVFZI>_y%gCxizJ$gbjzte)CsBg2Ufyzp%-yp58p!I=gHHBw6
z4(h&hTqf%WEFq8nfai(N>_Eu!q{>QQG%=B~XZDIcB(J#E+(4$hX~61CQcA$^-d*(F
z+HChdZEM-dbS_;HYPC5qH)o-BuKJk#)D|e~!dZ
zS5g+{5wfArP2|39zupgua-`L`=bR=(#XAlY<>YpKxu_KUYcuhmfwT{AAD;;~&;WtP
zkL(g5$M<+18GnhIaPd{CKt6;Rw$ff`A^kC*u5Rwx1MkYwr*CiEi%Ud)*VR^7I<@9O
zSESj0aPVCq+gcQ5MifSkeces4#b@KEGw?&s-(9~n4U(Wu_k)8Dl&K^KvxYm#lqR|I
zftUz7cK=IVxj|yaTqRQ_GaHtcE<#1StoJq+XneQ+@A%+71->oNxIe%m*Tm@mw$odA
z?YLdghXp9$8RhuGX4(|!-SQXkf$?2}%JWzJcjusH%CN||;BAf?=$y|go21RvzL0PQ
z;|z(q7`yzdCUDxmIY!?mBzi`Voaq7dW|HqPldN6sIiIXj4^cb4evq6{Ph6>5DVd=$tvz;g85c7V|!!ockyt!eOYsQdspm~7c>bX
zBjee{y?rl&CznoNUtcaCfxEwRw*PWwAXjFU$M4Ng5(?AWoD;VoxJO0eTL{~gDn@9$
zW?DcfhxUGAlr4!9W|g$lE%Gjo`84zsq%|ckayxDU|AMyAFH;)
z(Ohb3>TJ!KVU5`5>b+rr7B4cMWIw^6BAXu(Y7w-Db^!
z?0d|*s`U#D@>zL^UwW`OUP!L^*yR5HAX)+Cd!b4p{}VGnp)^XDT6fC$2Lce|Y>}0-
zGF%%&9Uu$7Dl91AYd_7bMnW1v#El9Fh4s!Lr-uLGT!((&-GH{&m_@_SDw$yocIzt~
zXk^k$M+J6HO?WzN2#WrQ87VZ1CHbJNOwwYB)xCLY<)ISQAli516V=!#${J|yr)KgZoAlBW
zemgrO_Q(^t6s=$iPQZmf<%tuHlPI`&dV$7#qW9xpzxuScX?2mDwzZHH4oE%)ROO;v
zM4J@8--CzlujsdBZEY~3h+6-d36XuqKROvG^09to=az-+SFQL=GUlsQuzU`4%O>fM
zryOm6;^8TrpoTuN6hqj6Kw9A&8)`i-_x%0r>aeBun_W`W$9Ws01x~JxT*}MfZ@p7C
z%XTeHD=O$72KZ^PDS8+wau{Q2D?iwK0T1xy@jb(wjnB2FnO1vu&9xZ$`OharmR>NC
zxux{rm|vK?jQc9&&*0WkUC41wTP2GT_Io6(3xg;RPa|%VR>s%dNh_=z0`bG64khH!
zHLH+Wd)THm^&D_$QIw^Rl0Z9NZmm;vR=>~~LJeoc1L((#o
zb%Oq0?h4qx(ZixB5~7-2s&$r^W1k;q4*%*n;h@Q4eda31m|@D3L9fb>j$7uN?t6bY
z*32@VDyQ~wJi3`(a31tD+TKPq&I!r{;K|R3QL>z55R>{0I0%|?RR`KD(nAg)pbxJQ
z#0I@YdLD7?v=w%4SF2lo(dE3V-+YOQiR~JVM~Dpj-t@o*O#LyOeih9qCEEF$T__5G
zO`LYio|+2uYN7Nmz&wb*ceh6u@b|~c%9UkMf8P97geoS&DeQXlS?4i5r(}A+Cl!Va
zvHmIoLK6Nt*atKGH7e&6pJhrsWg!?*0R$UAiJGsz{xHCd9lv`y_8x>g@p}vY=JT{N
z$@7h$X?c@-p^4M4H$fQZYOk(!%U;3rb8+2~r;*4Z3*VP*$$i%otI6-_iu$y?BK}8d
zddG0O9J=YQNB4=r5gIT2G=KhG#AAJdE5im)VaG|P(R^M`SW*2zDxl>lwA$vrKt@|9
z^tl9y9B?B;E%VZenJ5D}?Dzd>=_x}Ik+*60)EXNbyEj;GSmf{VXLvUY2cY1QDe|E5
zp?1|UCA(3ACqI&pyxKQFcqSKEzgT_j*VcXE1~Ps9do#|XJ&Hb;DqEo6!wBeotzPgx
zdIp@ai1xGfc6)nnLXolKfjvzbF4P9!_>4+U;2zfgWS8E%=pnTxpBCoMd<-A3Jl6_A
z5-)PP(lLhtiOryg9Rs>(($n7~2v&m4n)t~#5EexG{>n=fKQ;l<=V{aYPbkfNh*9s&Ih-MAGaC|-iSjbZUuRw88J_2gjeJr
zziKS|krv@5iyg?T?PUVhSJJ-Rrw7|v1rPvfIRRqoxbQ5K7H3-sV1?U5?6QZco9xmL
z)v`#>fp5B+8|=8R1>LIxFS8@EMB}+ql2{*0a#N?uwzTTo;p;0~o{Qg3IO$wJ1r2@w
z$;Rz^^N+i*8F_l<;WlE1P95LblB@Ay5eaGIutr#U4NUepBoy?J+HM~`wfkkbp^6#N
z{yE~Q7E2;+t|h5~0q>6ivldBP4lgMsKo#7c67r_bJpd2j+%MOCn`Q&9tO_r-`?Dt4
zDAuI@Ts^)f@5`~AJhP?0u*M~%l|Wu|HFeSKP^|eAZpip(L>BKZzC%2y&P|o_%hI{n
zr&(6Dy2iE)h=E~`T)Zx|GR&L16t(c7)vB|(%DYZZEtwbi3{Te%uEwn1h+1|
zF4@%*ZCi1E5|u!%6bRS}Ad9H47g&MZ`D`29{$vm_S
zJkhgAN%RAPW}bw(W1g9l0${k1FN1erHq)C=Tc
z3Uq87-1V-_qF1dVl+GjO_ra^I8rev6_ew^0!co5o&)8Mg9{7~+52os!BF7bIztB(U_>e
zbd3e*;LZ3kh`PxTn-FaJFmgN5rV2zZSa=ig$?WsVm_~~E`jgD{J#KF1PjjctdPfUZ
z>lYcKcO&|@Zy!zdCcR~{aRS=bQM+p&Juz%jHB~ayuQ>YAinWy)aVj{rrDB8gJ0hi$
zx{-=#ME4*)L<r_hdU(Lo)X&Y?Wu6*p;$Pa^n5_GM
z4PE9{rsGWM;9aEgsH-)q%-ua}gBWD#t)yYV2vr)PY(Sqi)b5{e9&s8QFFT)NLhU)R
z_82vBq8(%?bO(;F_tl$^p7s{Jw~}x3!xYq75xZ
z9lsQGgm+*s>BMp#>cc@Nyq?l+;tzxJ(2d50*90a_scl~fgUw`LP8w|CV?X?qvaVcN
z;cxfx+QhiDb){N^?dF8({o`@<$$K?xnrS9JsJm2g8*KiK1vlIx%Zv+gD
zqowzCZS@as(&cs+EbM0js(n_Ug%P{Xv|03+NE-Fy^r)hxBxLl)sVHnPk!>)Ieo>_u
zO#vE(bs-ek1ys{+kk2&b=~>B7~)t1%1W-z*P&?e)oy}-wZzs(
zwLET*wP67E2eC$g{n4-4_m;|3XX+sIgt?z7LX$z*z%>bag)@@yA=J_`uaGVKSv&4}
zG+4Rt-NuSJ9Asvev=izG6F%=Jy;pt$oN2%<5Y6d7ksyo3MHs;X
zd9=LaFL@yPmBM_DZKoPx=kugZQu~D16Nhd0k$vXJlMQ+`#sVF3{Z`M>@lJiPw?hT5
zf{BdI-bGC`%LdX1Q-*U(1Pi`+TT^7qg)9Ua$zum9I1{*nARQ?lU5U$K(;6;fz`*b!94YBwQ$<1cAyeDpKoVn_gv2!?qB
zBJ)3-7dH^l(Y#raDthV9KYE{T4KAkkUQ}60(=YDVvl!O%t?@eCTd1STq+j4J=yD$>#0-jHc-i9KZJe!qlDr+gmb)h8zWY!Jz^JXA?fm9U4>!barnqF6uJQdkh
z`nBwBS*-N0TgFRJvXU7u_S(nG5QQ!n7rt)9E&W3n2#gOU1
zO{G}d*dU8mx)KB*s7P!eWtjlpuF`7_Fkx@*D;t6{$-)CO!!3O?0X4OkH?`j+ebC!&
zpzszuBx(25yK6R%Qq_U_IQa1s{0JQ*P%{~DF3juNY^AkNc-51M89=Hyy?Mrt7IgfI
zcdJA!L4UEtnqR>a*fq#dL8@D_CJc!rF8_Jy%9>gtirA;uJVTN+9y?AqWX2GAs0N&xQz6rqJ87)7R+xYxR{uUaZ6b%t}^#(U}l8xrr%A8=~gzuP%I
z)_Y1on^aKscUVy9t$hpY&wuDgxavEO&nJ<@_uD1SCrp
zqJRy@>MZmAH#O!5h5S~(xjgVi4jZnpNkE`y419x(91DvH$eLJ|+D0`INr}q0HQ(0{
z!NtcTGUstcR^&IzqL-(qPWz%R4-Du|5Ayb2FS2YuDXh(lz+4GRZJ_D<5zbo9##zX?
zG=jNXdl+jeYv01&aXQ_A`pC>gICRZPztUw2FcdB=qNOcGT_PFU(Hc59B2uxHn2%zRH$nWsOr{#Y#GJ
z96kNVAXg-0teumRIefC;ce7RuBFV>B-lfIIiPc857*DIHYTvc@W&Q^f%7ER8up^nD=}|AjM@o=ocfO_b
z*VtgxwbzVqWPefpfPukt+9v36poY;KCv7x|-1$!_t=LL+AZlsR7NEr9BJxxS!vs$R
zuGSH+v4N7-9aW?qe~$%kbD0Yv@$FOzsSBOu(}Fuaa^mYcr$2ODLS|;sONVrv;2WLf
z%I8L{D%U;mIH)3$d;_=7$v}ji4NdOrPm1!+aNkr({f#6Wc>z8_zNP|_m)oXNDG4;W
zQ{A1fJakNZaj>wjsHM-WHMcRfd(KXYlp)7@}@$e9m3_PoNv`B4(V{=5Ht5#C>3
z(W=JB?#`S4TT6?n)eItmS`vx6d-~N{Em6HD4O)ICIS0y~S&c
zgHQJoKc=c=CLmwAk!hBzQl?iwQ`n1-I=}>^Oq%^YCVsYA-*{L{JUg~=eY;7a2L!8i
zoi~(kWfeLEA-qI6MA{3iAx2U%3nlOTx_SqfAxj%-5+6Xmo9#22eo3PY4x`l>w$^!r
z4;j6>yAKjtb~Nt9uGYcuURSRukPu(nBPHtW-u-WSUKoEwPu}TJuNtmifdl=H-u-5+
zj-SewT6$gWzt59cV4>J1kxNqj)c~O)B^yU<40LS>Q1BVi)efd-xqW262vdd_Bbgxs
ztO)gh`wcFcpJ+(Gl0^V8I*F#q7!1eh7RRoq!ZpA%Y;36ndB)(%`mIBqflm%WQYy$r
z^VoSphU}i_o2OIpUqC(;2A$Sa@$s%EkfDs{RY!m37c+*(1b2dgy}1+jRop}uD!hEkikGp=9~3AN_gpVmmU3u%a=#X8
z1cVUeu~b!Df0EBx5AYrK9Hj6NnhN_#9)b^$l8+ja+BmI>5&0fBP}}#IjSJhyu`M=w
z>$UbWuLjog23fd!$Q60)M%vA=UHBSyF64!w?%TR2TkK|1@$yF&)WvTZtx`6AT)U!T
zWm-ciLIbe;`o<<;foNI^wq-_!@$0zpIcisXgb6k0R?q_qt^G!63vsf|>ymsM5ob`e
zb3jMbJE?VQLtS9V*20c=Z;Fv+&^-f1MRs-)tQG_<8awMb@z~w?u|EBGsbPW)|2m_=>z1Mzgfcl(O{P?ret;Y2Hgqj3K8cYeSM`(QD4
z>J~&B3HN-;96flUgE4D|v2V+8vKf?Hy4|@s6zOEFVQ(mW2+gB~Vi&_#uQF&KaC$D&
zS$MGd3uU!{R9GouRk*@zS-(Jcp9dI_)g1n)IdIJ!-LUC1VdkkUt~!6$BH=jHs?lbm
z7CUP|sa?e=-X}<0n_0$_H{$z+cIE(h08nI>BgYXMHI=t}0>VuqJ#>bbDjJ*d!-yU6
zN7@E9&A$3gz;jL#?@IGr&~_7?`aaD46%^R*a-YR
zpH4#nL>M4t$q({idJ?W7&;}jwZf#ssaV&lDS3ywlR-kDq@p$i19k=L?c@cGo(YXw{l)?c)Nw6
zGXb2uKD3-C`&F&TetghtWXk(34|`V_00gj+j}{G-NtbuF4}BK@j0NpQP^Yu7Z;_bC
z@G>F3nRd7G^kK2EAf=C~B|I8r`kbGsZQp3&Us{d1NCEtc2n#=(%)K(E8w4S#$^8`J
zOz-vdcVe!{gGE3bk7krPdezB2?!rawGOAhiGBX5~zZDyw!xaZ>{#
z-oICQQJ11-Co+aMg%DExKssHZxG{X}q~@Mj?#-lFv`_(g&>N!oG)ViS5Eze!=Y}{D
zF|BLj&@I*TX;TQw~fKAa~4f@MQHiHo-}EJRdI
zZ59Z{2Th%~a$hhqF>JqWVM>-=F-L45LJx1>=~2~++cpD7E`Qk2Uu4SqyGATaHYo=B}jHUm?u{46cWG2qRNOfo9h2ha1?*_FQlPrF@Y)VL)_d
z+Xqr*s&xI;jqAHyr5T3_GEP)s^)yzlITMy20yX=8KCsjfU8UPZt;%^YuzMJop63N)Q
zkZTz^O!Oy$m2Q8r89K!%U|Y5K=l8+wkm|^e#Q)yv$D>{)(l<)D_S&ZjS!wEWxiW&~
zJtrvA;0~%YU?v%a;1k$Rl+RoB4ABR0HvZ6a;F3q^+y*1~k^{bEhDs^)$ps&m8YNNB
zd?L&%sPxf)WD2{MlLBfnTem7n0V8~Ki6 >ohL(Qxc6hMewO{KNXr(U5NaY`Jac0O3Ka!#dRxA;8n
z?)n6G!du0yGg?aie&2+&r^uHjt7tR*{e)cRvhOrikBchQR!pid`#-;GzYD{SfY^OPq)1NZ=DM-s69!!>M#hKfrT`mmXr@wOkd
zzYclGU3%zY6BK+pj3EA%K}ZKB9G6jw&%tZ9&Hsv)_71^~d9qYQ;#|050x92}(zHkf
z{QPFmi9^R4`73jKzl634iiu&y>+nSoV!F|}cvpO7q(F8dj8}Z1DgN~gH~?()D`Ec>
z*VL4T;UYq0!0U^h!$U&~%AL84um1rlHV_0rP=!_LM|;RA`~ZC3l<}8X%zum=dT1yS
z#JeR&G
znFEz{JuVJya!lM2*3c%eI_#P;!iexxqv0~#r39FaA
zmY=V%(HrEiS>hNd?APU?CBbCEu}TA2rHgF~X!*M?hP(fTr&AMtbQO?1sXmB}>cvwx
z*cc{0O_5vrgHIPr#f_PjFNi6&uY*eEw}n{_fsqNI6NUnJN}~SL%-sCbL_n`YZ|byG
z60Ls8jl6}~vR#r(wreB5QE+@o9M~Wn%OLPr+=e)^WOVeSW+bRAImUwngWr^}yg5Op
zR9x1DTu6lcmrl?`f(E-lI1FtQLQ1k8Gp&80vfhoM(i6Ecim#42JEYmiFH>&32-Ru)
zzMl=Ar4xTzOT~7js~(g**n2(ykJVLT-n3ME-evdSRT!-}yLnqI~);HLoeWv2MG?w&3=!9Mo~%Yt(OEU;^tDx4lYl|Ue6EZj2!n=-Bx
z=zw!0Havb;8o(97;RUUx9csLaXC#sdR`3D5u#wI9QGhjuC*{O?sH+bOg{AY4A}QU~
zx=0T_c8HME1N=g?*VTUVHym}*+p&LXaO?0W30XK#zF~nLih1u~%NNGtx0|84eeRXxq!P`7x0>s0ks4f_jRJa=oZgi_T
zU@76d%w%cPD`}l=5g@G4<(CYP4GJ+MfJm&z_CT{5+~n3)#{vT6`(v}(+m~Zhuq6Y+&?&Z0wF6v+!vR;
zxFIzh#Ev}iIvAu6k;y8VT}6Zv_1+a>lNroej+^%R#~1)c1kkP#hC#Qv_J^NL`zb1e
zn3)nW@|0FzP`Ql9KuAfa@xVcq;JV%Ha97cCe@bxgge;)a2
zR|f`WEQZX|<3i1}x7DOA*bcW47EW|B=$1LQt@cQ%BuAIy?{}LB?a;q{>qo(r78k&h
zWn!Wt!=N-YRQO6;t$Ycx
z0o?1K)>?i6@fp{gdKFEa@?~j{3d2KS#q`GetadgIok$LjyzJSb2dLL#h^RMFrFh@r
zcE`qXYHB~OT#I1eZIQDQuH0Vuu415~UlCg0h>#Nx*zeZ=xbW$|n>|okJ38P~1_#ms
zMp5fA<+h(39GP$70MO=!QI@<#vC-ELKjiB>-q}e^7O_otG6>7%z%Rrm`GV~WCr&0hncO%K?3_s_2;){1=ScoM?QW67!!
z^6(qbc1ox;Z`A9p;EqwiQ#@gSU9oKvv>ibIYTXw1U-t7|aFw_qV>)y%%vUjcZ{e|H
z9C~FuIQ+dmX3LP*CxFmsf(<8HHP6z;N4|y7hNn1n>6k>t3MUy@^GvQBC1G)4DGq?(orEYM0pR%#iQB!_O&4OA_L%un4o1SM9?tP=kkuIk_Pb@Ea@u`6k54LSqNqJ
zELe293rD3S2e(SOi9{Rmpw~>*bA)MF5ytAlrwmbO#Oi4qf3-lSoFPuwAjSC6=|4Ls
z3eeRhs175QX(BYJIg6|K+c*nKkU6+Pl#(dE>eWD`aB2Q${0TZli}q_(G%jjh+rK;(
ziP09ZVuSG)38IB_2O&Q;LTxqNxY(w2JU8FsZ7g}CteU?VfSa5%y7-n17UmuHnX0Iv
zKTq2`hkK9*E21T%vGoE=WG_XFjoie4hO0>_iL2L`^VmUJI%-I*c>R8teYG9L<&)2(iKkI6Fhtc=%6!NlZLy
z@YnLj*^$h7FNKluYW;;!v%qR=yzoC~yIVgHvyB`jR`boIJo1IT2_(C1;I|w13C}f7
z8sxnHH-mv0J6xu5eC^b5${ZXS90qAdvRoSfi1@vn#fHQ8O<>Z{o*Pf@H5DpN4FHe{
z>w7}%(B07-N5e*#WiO2-#FOhlMS@CFvtR;E4LY5I(jkHXid6OFX+{v~C*%2nW!vvW
z@~WZ4IkRS_F0BDe^4J7H<}LgqD3pS6_nHQBgkzZ_ySRO9KqelT{J_8iQj)OM!>~0w
zc>l=SZ%#BsiTE}JvQj{Cb%wJ(;_9%8g#UNM
zWR85o!(HItU~%_nY&k#m_GWRQxL4i;vI;CB2Vl!1$OwnC=pNU?lo$0sD)P%&mwiX>
zg9^{yE5UJ|a8%yU-Xw8@8ZNLKu&6oPov-*=$~Cu<=Ey55%mu#17CEeH-%B;@HV>hx
zNTzj2lPMB*y=5ssPzB`7_LTFm_Emd@V#e;$k0-2$fX;!8(+@
zOH9I|sL}ha+Fb1)5tEkT7gzv-A%7zdjWC$;VGkKnws*v+)8z!u1LCk7w@5^T>7$LQ
z1?>LR2#)v*@{ysQr~LMxs;Vj@Pv{V1Try${pEpS2>yPfAkpiHh1hI}2SiTfu*H
z&?RQTL&}|t_2Dw0%#sDLf*wWsjvn;+NQ{j6|DlJq@nM8flJ{$zteij*s-(^0!*J4(
zb^2)Is4JX#pZu1D@=%Bx7_$|Xg&GC}5NY6lxpRaa?rDIyWJJmREg=y9=k}(YU9q#6
zl^)P&3qhARBoKfZyJyx^IlH{tp`;pIh@~pbNTr(t`lXKf8CrU1l`eHOIVIo#qy~El
z`_@vv71il4$*avsUZIB*oaicd&3Z+dgD%40V)cMaN<+PUGK5z)g6o%h;AYH~xHoNT
z5)2uiTuuy$Gov+LUzViYdo=WPG^kqGfH~Zfj!}!JZXaRf^ASQBktdvLlhNI&9s?a7
z=JkO`C52YS%W}+56%0CBPPuQ21hmM-yeD_(0re@MynNpGFW6_o03`sMD^C(}j!`$0
zOsTmU*LTQ{kA4Y5jxlXQc2f-#;GHsC9{^O&|hX}htq?1
zwuhdBo_t7_0**uHnG_-W%-Y)c!{xRFqQAet?(A5lsa-u!EFU%}oQoQ{&o;2|8nc@D
z6e*a3Uju^QfBsd(tP6wkcH>J^C@s2z6DU*MLGaXYFHj=*eZ%Iy3Inxx{2W&Al1cQb
zFx0*;0}oT;BBQgj3LcOOw`Tqxc>7O9namf|OFglyp)@?lT3hS5Ay!8uSgC}DLK(bw
zo77I!BI><2Dp}+c;QI^}AJ6yG5J@0I)>n=9X{mv()ARgFi%nk4S)VjQK-zd$&->PADN$X49=ib)cI`fD!@*|Qssej&FAIcc3*6Hh3aMCL{?IvL
z%$!34aNa<;kICm1?po^e+a0uU=a1jWq3Dg1d0ndb7+~xxHU$?EqNy9)2bH;3>(m-V
zu5^d3LQ7~+m)>!QF=>*m8>%lFKoB5hfeIBmoN0eV^zP8&^QY8R#|_1H>sIHzn78xz
z=4=7UTy|xH@&TF8Zf6153nyFVT+Jr9;`N^@X%xKw!Mjp2vHa1(GG@%R$m<$-dv@oz
zW($F$@-QiW;6%S2kE9Dn7{N*XB77F}-!PlsU&K~8#-vC#honJD-?RzJ95qSVF?%1!
z=F+3awj-;mhy}XIXrXEP<{X54uMl5xjbvyn%|nGq8z1ksXC0@NV^N`Trq_ly{*X2=Cf*G2q
zZOaKQTC}_G{`=;?Ra=w`l_Iuo^tkMA8vf%Iso=oy{PQOnFjtQNk|7+=pZ{7cbuFl8
z=YWSmrd>+B(pme>lM|`WBk?BZ0QgK|o=!O0cswXrk!eEE>BEqfrGqcWsg9H6(N-f*
zzV?m44`(xcU{ttq62vh8T0H%d8XBx_D*td+kZynrWv%N`VM~hDxM^a@N=iN~`rhA5
z6i4ncm^owOmtXJ||3Yd=^X4J{Roa#c=VD$AM%9S@2-QIm)wB*4$|JKK)EH1MkS3OC
z+$zLIONOGXo!!7$(W%J+r>wvMYky5kY^cMvqAa|AQr#WW#!o)J%7N?k>bgI&C|&U^
zU?LBP_8*lk7fb8P9!p~dz{v=?)>Gjtm9r^ienbqKxkAY`lT4VaXN~Erm?eIlqtD|H
zs_uVWU7CwdT3d^ouHwVsDX+4xrv;$V_<&=-JC}f%1uXoXE
zDvZ9_=W+DoAq65va3S}7Jfrlx0*Qj
zUvD2*^$isH7!*4+Yu9pck6t>l9qE;Rdx$*H+kZVnJee#JpKMLo5Ch-0YhPYp9V!O~
z$}U2Jr)!^2qN`&tF_T3r)pd5~ZCZ>eKO%I+lJqX<0
z`jOS0pPjD&?0OEt1!YKtx-4|qKr>>?KVwuFlkP4TFLTe>J|;yo9u7Ws{Y&06hqQMj
zz!zLOJ%mieUMGMwk0PaX<09vppE|rtA&hL2ELNg+!j7|}3`L#x-+8`U=xs=-2f?r&
z-eWKl8%IYn?-xm)Ren|ThefTbg9oMUk_^UL`os6BQ004n|#EJ?7&BZ}*R2OBUu^tuLo+5(Q5EMUFxJ9c*
zIcSr}WxJI2GIxL24^bh*m^k4Wb<~3%hMw-b*uVT>n?Pfs3ps}BF9GaKmH}V2(h{LW
ztN>j1VPi(tTybC7BiA1OILTZD3FZ+fN)7zcFrO4>LLnUhF=vTh6>OgQZb-iA-8s+a
z(Ivq>Cxv3mL@;vgBV@3)_L_;iBMy+9pW+qFl+YV*Hque3uV?^`TkL66zRV;}BJZ^H
zcYx^5>)(SU#y$Yjz|{_1L=P`P2Z=Q!x?J(Ijxs{n(mbg$BAJnsn=ADW#-~toK_d*2
zlM~I5^*~xoIuUK6N;K)4F}@
ztNHW(Ee=*%y=dO_jTnfor5wQxXD>ox@E~8~pa3o8oWk7UDJ;b(+4bV+LB#gP6&gLb
zzxahU_1PEuJKuz{QGWqKynP(=IF4l1zB}nIpqk_+}{F$
z&`B`JtFnIIFFT&aLhLs66iurU9XM1>)*p~i`ZCq_9(b}6prCP`i@JD=4;+`^(+s)R
zbF3M0YyR}BowFPhJrj|xlH1@x=2eZLJA%uAlu3sy1@b*^Sn$xA{+YJ#$j7S5TKB8k
z1~Tgplh0+*yt$>xcxLr+Z{9rl%-L(Vp2X0`7yPVVyMop0VtJTGS-g&WQk#Ko5+!_C
zS6>TtQ$Y(&An`8v^?jqFTKvW|y1Rb2AFgdYLoBz`A{}5vsxwz1Gmvw5!
z<7hJ=@-ikb0IPUZ0>j^kk^8*W??z?_6D|@Ti~m_AI_LL=^sbO-jD0jB%{Rmst_6>4
zXTxP>Uh|2vt2e6F%=Yu{)d>jf{jpypQ5h_m9cLH_5hJ`}sTz2!ZU$wo)vM@AK*1R%
zO+r-Eq8?j3yzm;Pek?+?_{_we-^lZtZ~CdB77H=3ZTYp;*!J8ZCP?su6YIPw
zY2xAy0}EZvtdx<5X6btM-{7xu%(aI<`S>h;WjhH5fc43?`M3itgCHw=NYaf=@s70N
zQcQ^XCLi+q>MIte_uYO#?wD+*RSELmb+bdMA}DZywNgjWZvVsZk?OL3M&zWqtX8E4
zBIyj4!rJ)qYB{eWIWho>J^4)_cL1yNtDlc%Wd5=(cs=?osgY9<-`gpRJ