commit
23e3663b40
|
@ -4,8 +4,6 @@
|
|||
|
||||
Tusky is a beautiful Android client for [Mastodon](https://github.com/tootsuite/mastodon). Mastodon is a GNU social-compatible federated social network. That means not one entity controls the whole network, rather, like e-mail, volunteers and organisations operate their own independent servers, users from which can all interact with each other seamlessly.
|
||||
|
||||
It is currently available on [Google Play](https://play.google.com/store/apps/details?id=com.keylesspalace.tusky).
|
||||
|
||||
## Features
|
||||
|
||||
- Material Design
|
||||
|
@ -16,6 +14,10 @@ It is currently available on [Google Play](https://play.google.com/store/apps/de
|
|||
|
||||
My Mastodon account is [Vavassor@mastodon.social](https://mastodon.social/users/Vavassor).
|
||||
|
||||
[<img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="80" />](https://f-droid.org/repository/browse/?fdid=com.keylesspalace.tusky)
|
||||
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" alt="Get it on Google Play" height="80" />](https://play.google.com/store/apps/details?id=com.keylesspalace.tusky&utm_source=github&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1)
|
||||
[![Available at Amazon](/assets/amazon_badge.png)](https://www.amazon.com/gp/product/B06ZYXT88G/ref=mas_pm_tusky)
|
||||
|
||||
## Building
|
||||
The most basic things needed are the Java Development Kit 7 or higher and the Android SDK.
|
||||
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
/build
|
||||
app-release.apk
|
||||
app-google-release.apk
|
||||
src/main/res/raw/keystore_tusky_api.bks
|
||||
|
|
|
@ -2,24 +2,16 @@ apply plugin: 'com.android.application'
|
|||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion "25.0.2"
|
||||
buildToolsVersion "25.0.3"
|
||||
defaultConfig {
|
||||
applicationId "com.keylesspalace.tusky"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 25
|
||||
versionCode 15
|
||||
versionName "1.1.2"
|
||||
versionCode 17
|
||||
versionName "1.1.4-beta.1"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary true
|
||||
}
|
||||
productFlavors {
|
||||
google {
|
||||
buildConfigField "boolean", "USES_PUSH_NOTIFICATIONS", "true"
|
||||
}
|
||||
fdroid {
|
||||
buildConfigField "boolean", "USES_PUSH_NOTIFICATIONS", "false"
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
|
@ -37,7 +29,7 @@ dependencies {
|
|||
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
|
||||
exclude group: 'com.android.support', module: 'support-annotations'
|
||||
})
|
||||
compile('com.mikepenz:materialdrawer:5.8.2@aar') {
|
||||
compile('com.mikepenz:materialdrawer:5.9.1@aar') {
|
||||
transitive = true
|
||||
}
|
||||
compile 'com.android.support:appcompat-v7:25.3.1'
|
||||
|
@ -47,22 +39,22 @@ dependencies {
|
|||
compile 'com.android.support:design:25.3.1'
|
||||
compile 'com.android.support:exifinterface:25.3.1'
|
||||
compile 'com.squareup.retrofit2:retrofit:2.2.0'
|
||||
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
|
||||
compile 'com.squareup.retrofit2:converter-gson:2.2.0'
|
||||
compile 'com.squareup.picasso:picasso:2.5.2'
|
||||
compile 'com.squareup.okhttp3:okhttp:3.7.0'
|
||||
compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'
|
||||
compile 'com.pkmmte.view:circularimageview:1.1'
|
||||
compile 'com.github.peter9870:sparkbutton:master'
|
||||
compile 'com.github.varunest:sparkbutton:1.0.5'
|
||||
compile 'com.mikhaellopez:circularfillableloaders:1.2.0'
|
||||
compile 'com.github.chrisbanes:PhotoView:1.3.1'
|
||||
compile 'com.github.chrisbanes:PhotoView:2.0.0'
|
||||
compile 'com.mikepenz:google-material-typeface:3.0.1.0.original@aar'
|
||||
compile 'com.github.arimorty:floatingsearchview:2.0.4'
|
||||
compile 'com.theartofdev.edmodo:android-image-cropper:2.4.0'
|
||||
compile 'com.jakewharton:butterknife:8.4.0'
|
||||
googleCompile 'com.google.firebase:firebase-messaging:10.0.1'
|
||||
googleCompile 'com.google.firebase:firebase-crash:10.0.1'
|
||||
compile 'com.theartofdev.edmodo:android-image-cropper:2.4.3'
|
||||
compile 'com.jakewharton:butterknife:8.5.1'
|
||||
compile 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0'
|
||||
compile('org.eclipse.paho:org.eclipse.paho.android.service:1.1.1') {
|
||||
exclude module: 'support-v4'
|
||||
}
|
||||
testCompile 'junit:junit:4.12'
|
||||
annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
|
||||
annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
|
||||
}
|
||||
|
||||
apply plugin: 'com.google.gms.google-services'
|
|
@ -1,55 +0,0 @@
|
|||
{
|
||||
"project_info": {
|
||||
"project_number": "268851337880",
|
||||
"firebase_url": "https://tusky-62772.firebaseio.com",
|
||||
"project_id": "tusky-62772",
|
||||
"storage_bucket": "tusky-62772.appspot.com"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:268851337880:android:fc4111b1d145a00e",
|
||||
"android_client_info": {
|
||||
"package_name": "com.keylesspalace.tusky"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "268851337880-eie2ssto2d21bfihn9d1qupcrke8oebf.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.keylesspalace.tusky",
|
||||
"certificate_hash": "18d196307d6e928e99c2e0bb9818c01c38aff2f9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "268851337880-n19d05m282nirs1fc9kdd5n4of6je4fk.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyCbJtSjuk4I3Jy8PdUaO3TaQOXubcOUElo"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"analytics_service": {
|
||||
"status": 1
|
||||
},
|
||||
"appinvite_service": {
|
||||
"status": 2,
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "268851337880-n19d05m282nirs1fc9kdd5n4of6je4fk.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
"ads_service": {
|
||||
"status": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.keylesspalace.tusky">
|
||||
|
||||
<application>
|
||||
<service
|
||||
android:name=".MessagingService"
|
||||
android:enabled="true"
|
||||
android:exported="true" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -1,132 +0,0 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.Spanned;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import com.keylesspalace.tusky.entity.Notification;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.converter.gson.GsonConverterFactory;
|
||||
|
||||
public class MessagingService extends IntentService {
|
||||
public static final int NOTIFY_ID = 6; // This is an arbitrary number.
|
||||
|
||||
private MastodonAPI mastodonAPI;
|
||||
|
||||
public MessagingService() {
|
||||
super("Tusky Pull Notification Service");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(
|
||||
getApplicationContext());
|
||||
boolean enabled = preferences.getBoolean("notificationsEnabled", true);
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
createMastodonApi();
|
||||
|
||||
mastodonAPI.notifications(null, null, null).enqueue(new Callback<List<Notification>>() {
|
||||
@Override
|
||||
public void onResponse(Call<List<Notification>> call,
|
||||
Response<List<Notification>> response) {
|
||||
if (response.isSuccessful()) {
|
||||
onNotificationsReceived(response.body());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<List<Notification>> call, Throwable t) {}
|
||||
});
|
||||
}
|
||||
|
||||
private void createMastodonApi() {
|
||||
SharedPreferences preferences = getSharedPreferences(
|
||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||
final String domain = preferences.getString("domain", null);
|
||||
final String accessToken = preferences.getString("accessToken", null);
|
||||
|
||||
OkHttpClient okHttpClient = OkHttpUtils.getCompatibleClientBuilder()
|
||||
.addInterceptor(new Interceptor() {
|
||||
@Override
|
||||
public okhttp3.Response intercept(Chain chain) throws IOException {
|
||||
Request originalRequest = chain.request();
|
||||
|
||||
Request.Builder builder = originalRequest.newBuilder()
|
||||
.header("Authorization", String.format("Bearer %s", accessToken));
|
||||
|
||||
Request newRequest = builder.build();
|
||||
|
||||
return chain.proceed(newRequest);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
|
||||
Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapter(Spanned.class, new SpannedTypeAdapter())
|
||||
.registerTypeAdapter(StringWithEmoji.class, new StringWithEmojiTypeAdapter())
|
||||
.create();
|
||||
|
||||
Retrofit retrofit = new Retrofit.Builder()
|
||||
.baseUrl("https://" + domain)
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.build();
|
||||
|
||||
mastodonAPI = retrofit.create(MastodonAPI.class);
|
||||
}
|
||||
|
||||
private void onNotificationsReceived(List<Notification> notificationList) {
|
||||
SharedPreferences notificationsPreferences = getSharedPreferences(
|
||||
"Notifications", Context.MODE_PRIVATE);
|
||||
Set<String> currentIds = notificationsPreferences.getStringSet(
|
||||
"current_ids", new HashSet<String>());
|
||||
for (Notification notification : notificationList) {
|
||||
String id = notification.id;
|
||||
if (!currentIds.contains(id)) {
|
||||
currentIds.add(id);
|
||||
NotificationMaker.make(this, NOTIFY_ID, notification);
|
||||
}
|
||||
}
|
||||
notificationsPreferences.edit()
|
||||
.putStringSet("current_ids", currentIds)
|
||||
.apply();
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.keylesspalace.tusky">
|
||||
<application>
|
||||
<meta-data android:name="firebase_analytics_collection_enabled" android:value="false" />
|
||||
|
||||
<service android:name=".MyFirebaseInstanceIdService" android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service android:name=".MessagingService" android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
|
@ -1,121 +0,0 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*
|
||||
* If you modify this Program, or any covered work, by linking or combining it with Firebase Cloud
|
||||
* Messaging and Firebase Crash Reporting (or a modified version of those libraries), containing
|
||||
* parts covered by the Google APIs Terms of Service, the licensors of this Program grant you
|
||||
* additional permission to convey the resulting work. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.Spanned;
|
||||
|
||||
import com.google.firebase.messaging.FirebaseMessagingService;
|
||||
import com.google.firebase.messaging.RemoteMessage;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import com.keylesspalace.tusky.entity.Notification;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.converter.gson.GsonConverterFactory;
|
||||
|
||||
public class MessagingService extends FirebaseMessagingService {
|
||||
private MastodonAPI mastodonAPI;
|
||||
private static final String TAG = "MessagingService";
|
||||
public static final int NOTIFY_ID = 666;
|
||||
|
||||
@Override
|
||||
public void onMessageReceived(RemoteMessage remoteMessage) {
|
||||
Log.d(TAG, remoteMessage.getFrom());
|
||||
Log.d(TAG, remoteMessage.toString());
|
||||
|
||||
String notificationId = remoteMessage.getData().get("notification_id");
|
||||
|
||||
if (notificationId == null) {
|
||||
Log.e(TAG, "No notification ID in payload!!");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, notificationId);
|
||||
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(
|
||||
getApplicationContext());
|
||||
boolean enabled = preferences.getBoolean("notificationsEnabled", true);
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
createMastodonAPI();
|
||||
|
||||
mastodonAPI.notification(notificationId).enqueue(new Callback<Notification>() {
|
||||
@Override
|
||||
public void onResponse(Call<Notification> call, Response<Notification> response) {
|
||||
if (response.isSuccessful()) {
|
||||
NotificationMaker.make(MessagingService.this, NOTIFY_ID, response.body());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<Notification> call, Throwable t) {}
|
||||
});
|
||||
}
|
||||
|
||||
private void createMastodonAPI() {
|
||||
SharedPreferences preferences = getSharedPreferences(getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||
final String domain = preferences.getString("domain", null);
|
||||
final String accessToken = preferences.getString("accessToken", null);
|
||||
|
||||
OkHttpClient okHttpClient = OkHttpUtils.getCompatibleClientBuilder()
|
||||
.addInterceptor(new Interceptor() {
|
||||
@Override
|
||||
public okhttp3.Response intercept(Chain chain) throws IOException {
|
||||
Request originalRequest = chain.request();
|
||||
|
||||
Request.Builder builder = originalRequest.newBuilder()
|
||||
.header("Authorization", String.format("Bearer %s", accessToken));
|
||||
|
||||
Request newRequest = builder.build();
|
||||
|
||||
return chain.proceed(newRequest);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
|
||||
Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapter(Spanned.class, new SpannedTypeAdapter())
|
||||
.registerTypeAdapter(StringWithEmoji.class, new StringWithEmojiTypeAdapter())
|
||||
.create();
|
||||
|
||||
Retrofit retrofit = new Retrofit.Builder()
|
||||
.baseUrl("https://" + domain)
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.build();
|
||||
|
||||
mastodonAPI = retrofit.create(MastodonAPI.class);
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*
|
||||
* If you modify this Program, or any covered work, by linking or combining it with Firebase Cloud
|
||||
* Messaging and Firebase Crash Reporting (or a modified version of those libraries), containing
|
||||
* parts covered by the Google APIs Terms of Service, the licensors of this Program grant you
|
||||
* additional permission to convey the resulting work. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import com.google.firebase.iid.FirebaseInstanceId;
|
||||
import com.google.firebase.iid.FirebaseInstanceIdService;
|
||||
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
import retrofit2.Retrofit;
|
||||
|
||||
public class MyFirebaseInstanceIdService extends FirebaseInstanceIdService {
|
||||
private static final String TAG = "com.keylesspalace.tusky.MyFirebaseInstanceIdService";
|
||||
|
||||
private TuskyAPI tuskyAPI;
|
||||
|
||||
protected void createTuskyAPI() {
|
||||
Retrofit retrofit = new Retrofit.Builder()
|
||||
.baseUrl(getString(R.string.tusky_api_url))
|
||||
.client(OkHttpUtils.getCompatibleClient())
|
||||
.build();
|
||||
|
||||
tuskyAPI = retrofit.create(TuskyAPI.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTokenRefresh() {
|
||||
createTuskyAPI();
|
||||
|
||||
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
|
||||
SharedPreferences preferences = getSharedPreferences(getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||
String accessToken = preferences.getString("accessToken", null);
|
||||
String domain = preferences.getString("domain", null);
|
||||
|
||||
if (accessToken != null && domain != null) {
|
||||
tuskyAPI.unregister("https://" + domain, accessToken).enqueue(new Callback<ResponseBody>() {
|
||||
@Override
|
||||
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
|
||||
Log.d(TAG, response.message());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ResponseBody> call, Throwable t) {
|
||||
Log.d(TAG, t.getMessage());
|
||||
}
|
||||
});
|
||||
tuskyAPI.register("https://" + domain, accessToken, refreshedToken).enqueue(new Callback<ResponseBody>() {
|
||||
@Override
|
||||
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
|
||||
Log.d(TAG, response.message());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ResponseBody> call, Throwable t) {
|
||||
Log.d(TAG, t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,20 +6,23 @@
|
|||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" /> <!--For notifications-->
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" /> <!--Required by Eclipse Paho-->
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!--Required by Eclipse Paho-->
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:name=".TuskyApplication"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:name=".TuskyApplication">
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<activity android:name=".SplashActivity">
|
||||
<activity
|
||||
android:name=".SplashActivity"
|
||||
android:theme="@style/SplashTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
@ -37,7 +40,10 @@
|
|||
android:scheme="@string/oauth_scheme" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".MainActivity" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden">
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ComposeActivity"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize">
|
||||
|
@ -57,14 +63,19 @@
|
|||
<data android:mimeType="image/*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".ViewVideoActivity" android:configChanges="orientation|keyboardHidden|screenSize" />
|
||||
<activity android:name=".ViewThreadActivity" />
|
||||
<activity
|
||||
android:name=".ViewVideoActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize" />
|
||||
<activity
|
||||
android:name=".ViewThreadActivity"
|
||||
android:configChanges="orientation|screenSize" />
|
||||
<activity android:name=".ViewTagActivity" />
|
||||
<activity android:name=".AccountActivity" />
|
||||
<activity android:name=".EditProfileActivity" />
|
||||
<activity android:name=".PreferencesActivity" />
|
||||
<activity android:name=".FavouritesActivity" />
|
||||
<activity android:name=".AccountListActivity" />
|
||||
<activity android:name=".AboutActivity" />
|
||||
<activity
|
||||
android:name=".ReportActivity"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||
|
@ -72,11 +83,12 @@
|
|||
android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
|
||||
android:theme="@style/Base.Theme.AppCompat" />
|
||||
|
||||
<receiver android:name=".NotificationClearBroadcastReceiver" />
|
||||
<receiver android:name=".util.NotificationClearBroadcastReceiver" />
|
||||
|
||||
<service android:name="org.eclipse.paho.android.service.MqttService" />
|
||||
<service
|
||||
tools:targetApi="24"
|
||||
android:name="com.keylesspalace.tusky.TuskyTileService"
|
||||
android:name="com.keylesspalace.tusky.service.TuskyTileService"
|
||||
android:icon="@drawable/ic_send_24dp"
|
||||
android:label="Compose Toot"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.keylesspalace.tusky.entity.Account;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class AboutActivity extends BaseActivity {
|
||||
private Button appAccountButton;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_about);
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar bar = getSupportActionBar();
|
||||
if (bar != null) {
|
||||
bar.setDisplayHomeAsUpEnabled(true);
|
||||
bar.setDisplayShowHomeEnabled(true);
|
||||
}
|
||||
|
||||
TextView versionTextView = (TextView) findViewById(R.id.versionTV);
|
||||
String versionName = BuildConfig.VERSION_NAME;
|
||||
String versionFormat = getString(R.string.about_application_version);
|
||||
versionTextView.setText(String.format(versionFormat, versionName));
|
||||
|
||||
appAccountButton = (Button) findViewById(R.id.tusky_profile_button);
|
||||
appAccountButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
onAccountButtonClick();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onAccountButtonClick() {
|
||||
String appAccountId = getPrivatePreferences().getString("appAccountId", null);
|
||||
if (appAccountId != null) {
|
||||
viewAccount(appAccountId);
|
||||
} else {
|
||||
searchForAccountThenViewIt();
|
||||
}
|
||||
}
|
||||
|
||||
private void viewAccount(String id) {
|
||||
Intent intent = new Intent(this, AccountActivity.class);
|
||||
intent.putExtra("id", id);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void searchForAccountThenViewIt() {
|
||||
Callback<List<Account>> callback = new Callback<List<Account>>() {
|
||||
@Override
|
||||
public void onResponse(Call<List<Account>> call, Response<List<Account>> response) {
|
||||
if (response.isSuccessful()) {
|
||||
List<Account> 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<List<Account>> 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);
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ import android.content.SharedPreferences;
|
|||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.AttrRes;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.AppBarLayout;
|
||||
|
@ -31,6 +32,7 @@ import android.support.design.widget.FloatingActionButton;
|
|||
import android.support.design.widget.Snackbar;
|
||||
import android.support.design.widget.TabLayout;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v7.app.ActionBar;
|
||||
|
@ -43,6 +45,15 @@ import android.widget.TextView;
|
|||
|
||||
import com.keylesspalace.tusky.entity.Account;
|
||||
import com.keylesspalace.tusky.entity.Relationship;
|
||||
import com.keylesspalace.tusky.fragment.SFragment;
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener;
|
||||
import com.keylesspalace.tusky.interfaces.StatusRemoveListener;
|
||||
import com.keylesspalace.tusky.pager.AccountPagerAdapter;
|
||||
import com.keylesspalace.tusky.util.LinkHelper;
|
||||
import com.keylesspalace.tusky.util.Assert;
|
||||
import com.keylesspalace.tusky.util.Log;
|
||||
import com.keylesspalace.tusky.util.TimelineReceiver;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
import com.pkmmte.view.CircularImageView;
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
|
@ -156,9 +167,6 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem
|
|||
// Initialise the default UI states.
|
||||
floatingBtn.hide();
|
||||
|
||||
avatar.setImageResource(R.drawable.avatar_default);
|
||||
header.setImageResource(R.drawable.account_header_default);
|
||||
|
||||
// Obtain information to fill out the profile.
|
||||
obtainAccount();
|
||||
if (!accountId.equals(loggedInAccountId)) {
|
||||
|
@ -237,7 +245,9 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem
|
|||
|
||||
displayName.setText(account.getDisplayName());
|
||||
|
||||
LinkHelper.setClickableText(note, account.note, null, new LinkListener() {
|
||||
boolean useCustomTabs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.getBoolean("customTabs", true);
|
||||
LinkHelper.setClickableText(note, account.note, null, useCustomTabs, new LinkListener() {
|
||||
@Override
|
||||
public void onViewTag(String tag) {
|
||||
Intent intent = new Intent(AccountActivity.this, ViewTagActivity.class);
|
||||
|
@ -266,7 +276,7 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem
|
|||
.into(avatar);
|
||||
Picasso.with(this)
|
||||
.load(account.header)
|
||||
.placeholder(R.drawable.account_header_missing)
|
||||
.placeholder(R.drawable.account_header_default)
|
||||
.into(header);
|
||||
|
||||
NumberFormat nf = NumberFormat.getInstance();
|
||||
|
@ -459,6 +469,7 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem
|
|||
Snackbar.LENGTH_LONG).show();
|
||||
} else {
|
||||
followState = FollowState.NOT_FOLLOWING;
|
||||
broadcast(TimelineReceiver.Types.UNFOLLOW_ACCOUNT, id);
|
||||
}
|
||||
updateButtons();
|
||||
} else {
|
||||
|
@ -509,6 +520,7 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem
|
|||
@Override
|
||||
public void onResponse(Call<Relationship> call, Response<Relationship> response) {
|
||||
if (response.isSuccessful()) {
|
||||
broadcast(TimelineReceiver.Types.BLOCK_ACCOUNT, id);
|
||||
blocking = response.body().blocking;
|
||||
updateButtons();
|
||||
} else {
|
||||
|
@ -546,6 +558,7 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem
|
|||
@Override
|
||||
public void onResponse(Call<Relationship> call, Response<Relationship> response) {
|
||||
if (response.isSuccessful()) {
|
||||
broadcast(TimelineReceiver.Types.MUTE_ACCOUNT, id);
|
||||
muting = response.body().muting;
|
||||
updateButtons();
|
||||
} else {
|
||||
|
@ -578,6 +591,11 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem
|
|||
.show();
|
||||
}
|
||||
|
||||
private void broadcast(String action, String id) {
|
||||
Intent intent = new Intent(action);
|
||||
intent.putExtra("id", id);
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
|
||||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
|
@ -24,7 +22,6 @@ import android.graphics.Color;
|
|||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
@ -34,6 +31,15 @@ import android.view.Menu;
|
|||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.keylesspalace.tusky.entity.Session;
|
||||
import com.keylesspalace.tusky.json.SpannedTypeAdapter;
|
||||
import com.keylesspalace.tusky.json.StringWithEmoji;
|
||||
import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter;
|
||||
import com.keylesspalace.tusky.network.MastodonAPI;
|
||||
import com.keylesspalace.tusky.network.TuskyApi;
|
||||
import com.keylesspalace.tusky.util.Log;
|
||||
import com.keylesspalace.tusky.util.OkHttpUtils;
|
||||
import com.keylesspalace.tusky.util.PushNotificationClient;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -51,10 +57,10 @@ import retrofit2.converter.gson.GsonConverterFactory;
|
|||
public class BaseActivity extends AppCompatActivity {
|
||||
private static final String TAG = "BaseActivity"; // logging tag
|
||||
|
||||
protected MastodonAPI mastodonAPI;
|
||||
protected TuskyAPI tuskyAPI;
|
||||
public MastodonAPI mastodonAPI;
|
||||
public TuskyApi tuskyApi;
|
||||
protected PushNotificationClient pushNotificationClient;
|
||||
protected Dispatcher mastodonApiDispatcher;
|
||||
protected PendingIntent serviceAlarmIntent;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
|
@ -62,7 +68,8 @@ public class BaseActivity extends AppCompatActivity {
|
|||
|
||||
redirectIfNotLoggedIn();
|
||||
createMastodonAPI();
|
||||
createTuskyAPI();
|
||||
createTuskyApi();
|
||||
createPushNotificationClient();
|
||||
|
||||
/* There isn't presently a way to globally change the theme of a whole application at
|
||||
* runtime, just individual activities. So, each activity has to set its theme before any
|
||||
|
@ -154,15 +161,19 @@ public class BaseActivity extends AppCompatActivity {
|
|||
mastodonAPI = retrofit.create(MastodonAPI.class);
|
||||
}
|
||||
|
||||
protected void createTuskyAPI() {
|
||||
if (BuildConfig.USES_PUSH_NOTIFICATIONS) {
|
||||
Retrofit retrofit = new Retrofit.Builder()
|
||||
.baseUrl(getString(R.string.tusky_api_url))
|
||||
.client(OkHttpUtils.getCompatibleClient())
|
||||
.build();
|
||||
protected void createTuskyApi() {
|
||||
Retrofit retrofit = new Retrofit.Builder()
|
||||
.baseUrl("https://" + getString(R.string.tusky_api_url))
|
||||
.client(OkHttpUtils.getCompatibleClient())
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build();
|
||||
|
||||
tuskyAPI = retrofit.create(TuskyAPI.class);
|
||||
}
|
||||
tuskyApi = retrofit.create(TuskyApi.class);
|
||||
}
|
||||
|
||||
protected void createPushNotificationClient() {
|
||||
pushNotificationClient = new PushNotificationClient(getApplicationContext(),
|
||||
"ssl://" + getString(R.string.tusky_api_url) + ":8883");
|
||||
}
|
||||
|
||||
protected void redirectIfNotLoggedIn() {
|
||||
|
@ -196,49 +207,66 @@ public class BaseActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
protected void enablePushNotifications() {
|
||||
if (BuildConfig.USES_PUSH_NOTIFICATIONS) {
|
||||
String token = com.google.firebase.iid.FirebaseInstanceId.getInstance().getToken();
|
||||
tuskyAPI.register(getBaseUrl(), getAccessToken(), token).enqueue(new Callback<ResponseBody>() {
|
||||
@Override
|
||||
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
|
||||
Log.d(TAG, "Enable push notifications response: " + response.message());
|
||||
Callback<ResponseBody> callback = new Callback<ResponseBody>() {
|
||||
@Override
|
||||
public void onResponse(Call<ResponseBody> call,
|
||||
retrofit2.Response<ResponseBody> response) {
|
||||
if (response.isSuccessful()) {
|
||||
pushNotificationClient.subscribeToTopic(getPushNotificationTopic());
|
||||
pushNotificationClient.connect(BaseActivity.this);
|
||||
} else {
|
||||
onEnablePushNotificationsFailure(response.message());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ResponseBody> call, Throwable t) {
|
||||
Log.d(TAG, "Enable push notifications failed: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Start up the MessagingService on a repeating interval for "pull" notifications.
|
||||
long checkInterval = 60 * 1000 * 5;
|
||||
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
||||
Intent intent = new Intent(this, MessagingService.class);
|
||||
final int SERVICE_REQUEST_CODE = 8574603; // This number is arbitrary.
|
||||
serviceAlarmIntent = PendingIntent.getService(this, SERVICE_REQUEST_CODE, intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
||||
SystemClock.elapsedRealtime(), checkInterval, serviceAlarmIntent);
|
||||
}
|
||||
@Override
|
||||
public void onFailure(Call<ResponseBody> call, Throwable t) {
|
||||
onEnablePushNotificationsFailure(t.getMessage());
|
||||
}
|
||||
};
|
||||
String deviceToken = pushNotificationClient.getDeviceToken();
|
||||
Session session = new Session(getDomain(), getAccessToken(), deviceToken);
|
||||
tuskyApi.register(session)
|
||||
.enqueue(callback);
|
||||
}
|
||||
|
||||
private void onEnablePushNotificationsFailure(String message) {
|
||||
Log.e(TAG, "Enabling push notifications failed. " + message);
|
||||
}
|
||||
|
||||
protected void disablePushNotifications() {
|
||||
if (BuildConfig.USES_PUSH_NOTIFICATIONS) {
|
||||
tuskyAPI.unregister(getBaseUrl(), getAccessToken()).enqueue(new Callback<ResponseBody>() {
|
||||
@Override
|
||||
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
|
||||
Log.d(TAG, "Disable push notifications response: " + response.message());
|
||||
Callback<ResponseBody> callback = new Callback<ResponseBody>() {
|
||||
@Override
|
||||
public void onResponse(Call<ResponseBody> call,
|
||||
retrofit2.Response<ResponseBody> response) {
|
||||
if (response.isSuccessful()) {
|
||||
pushNotificationClient.unsubscribeToTopic(getPushNotificationTopic());
|
||||
} else {
|
||||
onDisablePushNotificationsFailure();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ResponseBody> call, Throwable t) {
|
||||
Log.d(TAG, "Disable push notifications failed: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
} else if (serviceAlarmIntent != null) {
|
||||
// Cancel the repeating call for "pull" notifications.
|
||||
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
||||
alarmManager.cancel(serviceAlarmIntent);
|
||||
}
|
||||
@Override
|
||||
public void onFailure(Call<ResponseBody> call, Throwable t) {
|
||||
onDisablePushNotificationsFailure();
|
||||
}
|
||||
};
|
||||
String deviceToken = pushNotificationClient.getDeviceToken();
|
||||
Session session = new Session(getDomain(), getAccessToken(), deviceToken);
|
||||
tuskyApi.unregister(session)
|
||||
.enqueue(callback);
|
||||
}
|
||||
|
||||
private void onDisablePushNotificationsFailure() {
|
||||
Log.e(TAG, "Disabling push notifications failed.");
|
||||
}
|
||||
|
||||
private String getPushNotificationTopic() {
|
||||
return String.format("%s/%s/#", getDomain(), getAccessToken());
|
||||
}
|
||||
|
||||
private String getDomain() {
|
||||
return getPrivatePreferences()
|
||||
.getString("domain", null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,10 @@ import android.support.v7.app.ActionBar;
|
|||
import android.support.v7.content.res.AppCompatResources;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.Editable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.style.URLSpan;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
@ -68,6 +71,14 @@ import android.widget.TextView;
|
|||
|
||||
import com.keylesspalace.tusky.entity.Media;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.fragment.ComposeOptionsFragment;
|
||||
import com.keylesspalace.tusky.util.DownsizeImageTask;
|
||||
import com.keylesspalace.tusky.util.EditTextTyped;
|
||||
import com.keylesspalace.tusky.util.CountUpDownLatch;
|
||||
import com.keylesspalace.tusky.util.IOUtils;
|
||||
import com.keylesspalace.tusky.util.Log;
|
||||
import com.keylesspalace.tusky.util.SpanUtils;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
|
@ -90,6 +101,7 @@ import okhttp3.MultipartBody;
|
|||
import okhttp3.RequestBody;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class ComposeActivity extends BaseActivity implements ComposeOptionsFragment.Listener {
|
||||
private static final String TAG = "ComposeActivity"; // logging tag
|
||||
|
@ -115,7 +127,8 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
private Uri photoUploadUri;
|
||||
// this only exists when a status is trying to be sent, but uploads are still occurring
|
||||
private ProgressDialog finishingUploadDialog;
|
||||
@BindView(R.id.compose_edit_field) EditTextTyped textEditor;
|
||||
@BindView(R.id.compose_edit_field)
|
||||
EditTextTyped textEditor;
|
||||
@BindView(R.id.compose_media_preview_bar) LinearLayout mediaPreviewBar;
|
||||
@BindView(R.id.compose_content_warning_bar) View contentWarningBar;
|
||||
@BindView(R.id.field_content_warning) EditText contentWarningEditor;
|
||||
|
@ -143,6 +156,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
Uri uri;
|
||||
String id;
|
||||
Call<Media> uploadRequest;
|
||||
URLSpan uploadUrl;
|
||||
ReadyStage readyStage;
|
||||
byte[] content;
|
||||
long mediaSize;
|
||||
|
@ -224,7 +238,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
floatingBtn.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
prepareStatus();
|
||||
onSendClicked();
|
||||
}
|
||||
});
|
||||
pickBtn.setOnClickListener(new View.OnClickListener() {
|
||||
|
@ -293,7 +307,9 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
|
||||
if (replyVisibility != null && startingVisibility != null) {
|
||||
// Lowest possible visibility setting in response
|
||||
if (startingVisibility.equals("private") || replyVisibility.equals("private")) {
|
||||
if (startingVisibility.equals("direct") || replyVisibility.equals("direct")) {
|
||||
startingVisibility = "direct";
|
||||
} else if (startingVisibility.equals("private") || replyVisibility.equals("private")) {
|
||||
startingVisibility = "private";
|
||||
} else if (startingVisibility.equals("unlisted") || replyVisibility.equals("unlisted")) {
|
||||
startingVisibility = "unlisted";
|
||||
|
@ -584,24 +600,12 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
enableButtons();
|
||||
}
|
||||
|
||||
private void prepareStatus() {
|
||||
private void onSendClicked() {
|
||||
if (statusAlreadyInFlight) {
|
||||
return;
|
||||
}
|
||||
String contentText = textEditor.getText().toString();
|
||||
String spoilerText = "";
|
||||
if (statusHideText) {
|
||||
spoilerText = contentWarningEditor.getText().toString();
|
||||
}
|
||||
int characterCount = contentText.length() + spoilerText.length();
|
||||
if (characterCount > 0 && characterCount <= STATUS_CHARACTER_LIMIT) {
|
||||
setStateToReadying();
|
||||
readyStatus(contentText, statusVisibility, statusMarkSensitive, spoilerText);
|
||||
} else if (characterCount <= 0) {
|
||||
textEditor.setError(getString(R.string.error_empty));
|
||||
} else {
|
||||
textEditor.setError(getString(R.string.error_compose_character_limit));
|
||||
}
|
||||
setStateToReadying();
|
||||
readyStatus(statusVisibility, statusMarkSensitive);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -705,7 +709,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
|
||||
mastodonAPI.createStatus(content, inReplyToId, spoilerText, visibility, sensitive, mediaIds).enqueue(new Callback<Status>() {
|
||||
@Override
|
||||
public void onResponse(Call<Status> call, retrofit2.Response<Status> response) {
|
||||
public void onResponse(Call<Status> call, Response<Status> response) {
|
||||
if (response.isSuccessful()) {
|
||||
onSendSuccess();
|
||||
} else {
|
||||
|
@ -732,8 +736,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
setStateToNotReadying();
|
||||
}
|
||||
|
||||
private void readyStatus(final String content, final String visibility, final boolean sensitive,
|
||||
final String spoilerText) {
|
||||
private void readyStatus(final String visibility, final boolean sensitive) {
|
||||
finishingUploadDialog = ProgressDialog.show(
|
||||
this, getString(R.string.dialog_title_finishing_media_upload),
|
||||
getString(R.string.dialog_message_uploading_media), true, true);
|
||||
|
@ -755,9 +758,9 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
finishingUploadDialog.dismiss();
|
||||
finishingUploadDialog = null;
|
||||
if (successful) {
|
||||
sendStatus(content, visibility, sensitive, spoilerText);
|
||||
onReadySuccess(visibility, sensitive);
|
||||
} else {
|
||||
onReadyFailure(content, visibility, sensitive, spoilerText);
|
||||
onReadyFailure(visibility, sensitive);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -780,13 +783,33 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
waitForMediaTask.execute();
|
||||
}
|
||||
|
||||
private void onReadyFailure(final String content, final String visibility,
|
||||
final boolean sensitive, final String spoilerText) {
|
||||
private void onReadySuccess(String visibility, boolean sensitive) {
|
||||
/* Validate the status meets the character limit. This has to be delayed until after all
|
||||
* uploads finish because their links are added when the upload succeeds and that affects
|
||||
* whether the limit is met or not. */
|
||||
String contentText = textEditor.getText().toString();
|
||||
String spoilerText = "";
|
||||
if (statusHideText) {
|
||||
spoilerText = contentWarningEditor.getText().toString();
|
||||
}
|
||||
int characterCount = contentText.length() + spoilerText.length();
|
||||
if (characterCount > 0 && characterCount <= STATUS_CHARACTER_LIMIT) {
|
||||
sendStatus(contentText, visibility, sensitive, spoilerText);
|
||||
} else if (characterCount <= 0) {
|
||||
textEditor.setError(getString(R.string.error_empty));
|
||||
setStateToNotReadying();
|
||||
} else {
|
||||
textEditor.setError(getString(R.string.error_compose_character_limit));
|
||||
setStateToNotReadying();
|
||||
}
|
||||
}
|
||||
|
||||
private void onReadyFailure(final String visibility, final boolean sensitive) {
|
||||
doErrorDialog(R.string.error_media_upload_sending, R.string.action_retry,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
readyStatus(content, visibility, sensitive, spoilerText);
|
||||
readyStatus(visibility, sensitive);
|
||||
}
|
||||
});
|
||||
setStateToNotReadying();
|
||||
|
@ -951,6 +974,15 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
textEditor.setPadding(textEditor.getPaddingLeft(), textEditor.getPaddingTop(),
|
||||
textEditor.getPaddingRight(), 0);
|
||||
}
|
||||
// Remove the text URL associated with this media.
|
||||
if (item.uploadUrl != null) {
|
||||
Editable text = textEditor.getText();
|
||||
int start = text.getSpanStart(item.uploadUrl);
|
||||
int end = text.getSpanEnd(item.uploadUrl);
|
||||
if (start != -1 && end != -1) {
|
||||
text.delete(start, end);
|
||||
}
|
||||
}
|
||||
enableMediaButtons();
|
||||
cancelReadyingMedia(item);
|
||||
}
|
||||
|
@ -1052,8 +1084,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
@Override
|
||||
public void onResponse(Call<Media> call, retrofit2.Response<Media> response) {
|
||||
if (response.isSuccessful()) {
|
||||
item.id = response.body().id;
|
||||
waitForMediaLatch.countDown();
|
||||
onUploadSuccess(item, response.body());
|
||||
} else {
|
||||
Log.d(TAG, "Upload request failed. " + response.message());
|
||||
onUploadFailure(item, call.isCanceled());
|
||||
|
@ -1068,6 +1099,22 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
});
|
||||
}
|
||||
|
||||
private void onUploadSuccess(final QueuedMedia item, Media media) {
|
||||
item.id = media.id;
|
||||
|
||||
/* Add the upload URL to the text field. Also, keep a reference to the span so if the user
|
||||
* chooses to remove the media, the URL is also automatically removed. */
|
||||
item.uploadUrl = new URLSpan(media.textUrl);
|
||||
int end = 1 + media.textUrl.length();
|
||||
SpannableStringBuilder builder = new SpannableStringBuilder();
|
||||
builder.append(' ');
|
||||
builder.append(media.textUrl);
|
||||
builder.setSpan(item.uploadUrl, 0, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
textEditor.append(builder);
|
||||
|
||||
waitForMediaLatch.countDown();
|
||||
}
|
||||
|
||||
private void onUploadFailure(QueuedMedia item, boolean isCanceled) {
|
||||
if (!isCanceled) {
|
||||
/* if the upload was voluntarily cancelled, such as if the user clicked on it to remove
|
||||
|
|
|
@ -36,7 +36,6 @@ import android.util.Base64;
|
|||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
|
@ -44,6 +43,8 @@ import android.widget.ProgressBar;
|
|||
|
||||
import com.keylesspalace.tusky.entity.Account;
|
||||
import com.keylesspalace.tusky.entity.Profile;
|
||||
import com.keylesspalace.tusky.util.IOUtils;
|
||||
import com.keylesspalace.tusky.util.Log;
|
||||
import com.pkmmte.view.CircularImageView;
|
||||
import com.squareup.picasso.Picasso;
|
||||
import com.theartofdev.edmodo.cropper.CropImage;
|
||||
|
@ -178,7 +179,7 @@ public class EditProfileActivity extends BaseActivity {
|
|||
.into(avatar);
|
||||
Picasso.with(header.getContext())
|
||||
.load(me.header)
|
||||
.placeholder(R.drawable.account_header_missing)
|
||||
.placeholder(R.drawable.account_header_default)
|
||||
.into(header);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -32,10 +32,15 @@ import android.text.method.LinkMovementMethod;
|
|||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.keylesspalace.tusky.entity.AccessToken;
|
||||
import com.keylesspalace.tusky.entity.AppCredentials;
|
||||
import com.keylesspalace.tusky.network.MastodonAPI;
|
||||
import com.keylesspalace.tusky.util.CustomTabsHelper;
|
||||
import com.keylesspalace.tusky.util.Log;
|
||||
import com.keylesspalace.tusky.util.OkHttpUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
@ -58,6 +63,9 @@ public class LoginActivity extends AppCompatActivity {
|
|||
private String clientId;
|
||||
private String clientSecret;
|
||||
|
||||
@BindView(R.id.login_input) LinearLayout input;
|
||||
@BindView(R.id.login_loading) LinearLayout loading;
|
||||
|
||||
@BindView(R.id.edit_text_domain) EditText editText;
|
||||
@BindView(R.id.button_login) Button button;
|
||||
@BindView(R.id.whats_an_instance) TextView whatsAnInstance;
|
||||
|
@ -322,6 +330,8 @@ public class LoginActivity extends AppCompatActivity {
|
|||
domain = preferences.getString("domain", null);
|
||||
clientId = preferences.getString("clientId", null);
|
||||
clientSecret = preferences.getString("clientSecret", null);
|
||||
|
||||
setLoading(true);
|
||||
/* Since authorization has succeeded, the final step to log in is to exchange
|
||||
* the authorization code for an access token. */
|
||||
Callback<AccessToken> callback = new Callback<AccessToken>() {
|
||||
|
@ -330,6 +340,8 @@ public class LoginActivity extends AppCompatActivity {
|
|||
if (response.isSuccessful()) {
|
||||
onLoginSuccess(response.body().accessToken);
|
||||
} else {
|
||||
setLoading(false);
|
||||
|
||||
editText.setError(getString(R.string.error_retrieving_oauth_token));
|
||||
Log.e(TAG, String.format("%s %s",
|
||||
getString(R.string.error_retrieving_oauth_token),
|
||||
|
@ -339,6 +351,7 @@ public class LoginActivity extends AppCompatActivity {
|
|||
|
||||
@Override
|
||||
public void onFailure(Call<AccessToken> call, Throwable t) {
|
||||
setLoading(false);
|
||||
editText.setError(getString(R.string.error_retrieving_oauth_token));
|
||||
Log.e(TAG, String.format("%s %s",
|
||||
getString(R.string.error_retrieving_oauth_token),
|
||||
|
@ -351,21 +364,34 @@ public class LoginActivity extends AppCompatActivity {
|
|||
} else if (error != null) {
|
||||
/* Authorization failed. Put the error response where the user can read it and they
|
||||
* can try again. */
|
||||
setLoading(false);
|
||||
editText.setError(getString(R.string.error_authorization_denied));
|
||||
Log.e(TAG, getString(R.string.error_authorization_denied) + error);
|
||||
} else {
|
||||
setLoading(false);
|
||||
// This case means a junk response was received somehow.
|
||||
editText.setError(getString(R.string.error_authorization_unknown));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setLoading(boolean loadingState) {
|
||||
if (loadingState) {
|
||||
loading.setVisibility(View.VISIBLE);
|
||||
input.setVisibility(View.GONE);
|
||||
} else {
|
||||
loading.setVisibility(View.GONE);
|
||||
input.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void onLoginSuccess(String accessToken) {
|
||||
boolean committed = preferences.edit()
|
||||
.putString("domain", domain)
|
||||
.putString("accessToken", accessToken)
|
||||
.commit();
|
||||
if (!committed) {
|
||||
setLoading(false);
|
||||
editText.setError(getString(R.string.error_retrieving_oauth_token));
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -15,9 +15,10 @@
|
|||
|
||||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
|
@ -26,9 +27,11 @@ import android.os.PersistableBundle;
|
|||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.design.widget.TabLayout;
|
||||
import android.support.graphics.drawable.VectorDrawableCompat;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
|
@ -41,6 +44,11 @@ import com.arlib.floatingsearchview.FloatingSearchView;
|
|||
import com.arlib.floatingsearchview.suggestions.SearchSuggestionsAdapter;
|
||||
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion;
|
||||
import com.keylesspalace.tusky.entity.Account;
|
||||
import com.keylesspalace.tusky.fragment.SFragment;
|
||||
import com.keylesspalace.tusky.interfaces.StatusRemoveListener;
|
||||
import com.keylesspalace.tusky.pager.TimelinePagerAdapter;
|
||||
import com.keylesspalace.tusky.util.Log;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
|
||||
import com.mikepenz.materialdrawer.AccountHeader;
|
||||
import com.mikepenz.materialdrawer.AccountHeaderBuilder;
|
||||
|
@ -81,7 +89,7 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
|
|||
@BindView(R.id.tab_layout) TabLayout tabLayout;
|
||||
@BindView(R.id.pager) ViewPager viewPager;
|
||||
|
||||
FloatingActionButton composeButton;
|
||||
public FloatingActionButton composeButton;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
@ -206,8 +214,7 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
|
|||
.putString("current", "[]")
|
||||
.apply();
|
||||
|
||||
((NotificationManager) (getSystemService(NOTIFICATION_SERVICE)))
|
||||
.cancel(MessagingService.NOTIFY_ID);
|
||||
pushNotificationClient.clearNotifications(this);
|
||||
|
||||
/* After editing a profile, the profile header in the navigation drawer needs to be
|
||||
* refreshed */
|
||||
|
@ -273,7 +280,8 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
|
|||
}
|
||||
});
|
||||
|
||||
Drawable muteDrawable = ContextCompat.getDrawable(this, R.drawable.ic_mute_24dp);
|
||||
VectorDrawableCompat muteDrawable = VectorDrawableCompat.create(getResources(),
|
||||
R.drawable.ic_mute_24dp, getTheme());
|
||||
ThemeUtils.setDrawableTint(this, muteDrawable, R.attr.toolbar_icon_tint);
|
||||
|
||||
drawer = new DrawerBuilder()
|
||||
|
@ -289,7 +297,8 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
|
|||
new PrimaryDrawerItem().withIdentifier(3).withName(getString(R.string.action_view_blocks)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_block),
|
||||
new DividerDrawerItem(),
|
||||
new SecondaryDrawerItem().withIdentifier(4).withName(getString(R.string.action_view_preferences)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_settings),
|
||||
new SecondaryDrawerItem().withIdentifier(5).withName(getString(R.string.action_logout)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_exit_to_app)
|
||||
new SecondaryDrawerItem().withIdentifier(5).withName(getString(R.string.about_title_activity)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_info),
|
||||
new SecondaryDrawerItem().withIdentifier(6).withName(getString(R.string.action_logout)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_exit_to_app)
|
||||
)
|
||||
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
|
||||
@Override
|
||||
|
@ -315,8 +324,11 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
|
|||
Intent intent = new Intent(MainActivity.this, PreferencesActivity.class);
|
||||
startActivity(intent);
|
||||
} else if (drawerItemIdentifier == 5) {
|
||||
logout();
|
||||
Intent intent = new Intent(MainActivity.this, AboutActivity.class);
|
||||
startActivity(intent);
|
||||
} else if (drawerItemIdentifier == 6) {
|
||||
logout();
|
||||
} else if (drawerItemIdentifier == 7) {
|
||||
Intent intent = new Intent(MainActivity.this, AccountListActivity.class);
|
||||
intent.putExtra("type", AccountListActivity.Type.FOLLOW_REQUESTS);
|
||||
startActivity(intent);
|
||||
|
@ -330,16 +342,27 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
|
|||
}
|
||||
|
||||
private void logout() {
|
||||
if (arePushNotificationsEnabled()) disablePushNotifications();
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.action_logout)
|
||||
.setMessage(R.string.action_logout_confirm)
|
||||
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (arePushNotificationsEnabled()) disablePushNotifications();
|
||||
|
||||
getPrivatePreferences().edit()
|
||||
.remove("domain")
|
||||
.remove("accessToken")
|
||||
.apply();
|
||||
getPrivatePreferences().edit()
|
||||
.remove("domain")
|
||||
.remove("accessToken")
|
||||
.remove("appAccountId")
|
||||
.apply();
|
||||
|
||||
Intent intent = new Intent(MainActivity.this, LoginActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
Intent intent = new Intent(MainActivity.this, LoginActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void setupSearchView() {
|
||||
|
@ -472,9 +495,11 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
|
|||
backgroundHeight = background.getMeasuredHeight();
|
||||
}
|
||||
|
||||
background.setBackgroundColor(ContextCompat.getColor(this, R.color.window_background_dark));
|
||||
|
||||
Picasso.with(MainActivity.this)
|
||||
.load(me.header)
|
||||
.placeholder(R.drawable.account_header_missing)
|
||||
.placeholder(R.drawable.account_header_default)
|
||||
.resize(backgroundWidth, backgroundHeight)
|
||||
.centerCrop()
|
||||
.into(background);
|
||||
|
@ -553,4 +578,10 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fix for GitHub issues #190, #259 (MainActivity won't restart on screen rotation.)
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -19,28 +19,13 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
||||
public class SplashActivity extends AppCompatActivity {
|
||||
private static final int SPLASH_TIME_OUT = 2000;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("lightTheme", false)) {
|
||||
setTheme(R.style.AppTheme_Light);
|
||||
}
|
||||
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
|
||||
setContentView(R.layout.activity_splash);
|
||||
|
||||
/* Determine whether the user is currently logged in, and if so go ahead and load the
|
||||
* timeline. Otherwise, start the activity_login screen. */
|
||||
SharedPreferences preferences = getSharedPreferences(
|
||||
|
@ -48,20 +33,13 @@ public class SplashActivity extends AppCompatActivity {
|
|||
String domain = preferences.getString("domain", null);
|
||||
String accessToken = preferences.getString("accessToken", null);
|
||||
|
||||
final Intent intent;
|
||||
|
||||
Intent intent;
|
||||
if (domain != null && accessToken != null) {
|
||||
intent = new Intent(this, MainActivity.class);
|
||||
} else {
|
||||
intent = new Intent(this, LoginActivity.class);
|
||||
}
|
||||
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}, SPLASH_TIME_OUT);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
|
@ -24,6 +25,10 @@ import android.support.v7.widget.Toolbar;
|
|||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import com.keylesspalace.tusky.fragment.SFragment;
|
||||
import com.keylesspalace.tusky.fragment.ViewThreadFragment;
|
||||
import com.keylesspalace.tusky.interfaces.StatusRemoveListener;
|
||||
|
||||
public class ViewThreadActivity extends BaseActivity implements SFragment.OnUserRemovedListener {
|
||||
Fragment viewThreadFragment;
|
||||
|
||||
|
@ -74,4 +79,11 @@ public class ViewThreadActivity extends BaseActivity implements SFragment.OnUser
|
|||
listener.removePostsByUser(accountId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
/* Provide a stub to ignore configuration changes so the thread isn't reloaded when the
|
||||
* the activity is reoriented or resized. */
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -13,17 +13,18 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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<Account> accountList;
|
||||
AccountActionListener accountActionListener;
|
||||
|
||||
|
@ -38,7 +39,7 @@ abstract class AccountAdapter extends RecyclerView.Adapter {
|
|||
return accountList.size() + 1;
|
||||
}
|
||||
|
||||
void update(List<Account> newAccounts) {
|
||||
public void update(List<Account> newAccounts) {
|
||||
if (newAccounts == null || newAccounts.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
@ -59,14 +60,14 @@ abstract class AccountAdapter extends RecyclerView.Adapter {
|
|||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
void addItems(List<Account> newAccounts) {
|
||||
public void addItems(List<Account> 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;
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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);
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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);
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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);
|
||||
}
|
||||
|
|
@ -13,12 +13,14 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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);
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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<Notification> newNotifications) {
|
||||
public void update(List<Notification> newNotifications) {
|
||||
if (newNotifications == null || newNotifications.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
@ -200,7 +203,7 @@ class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRe
|
|||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
void addItems(List<Notification> new_notifications) {
|
||||
public void addItems(List<Notification> 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);
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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<ReportStatus> 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<ReportStatus> newStatuses) {
|
||||
public void addItems(List<ReportStatus> 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<String> idList = new ArrayList<>();
|
||||
for (ReportStatus status : statusList) {
|
||||
if (status.checked) {
|
|
@ -13,9 +13,10 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.Spanned;
|
||||
|
@ -26,7 +27,13 @@ import android.widget.ImageView;
|
|||
import android.widget.TextView;
|
||||
import android.widget.ToggleButton;
|
||||
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.util.RoundedTransformation;
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.util.DateUtils;
|
||||
import com.keylesspalace.tusky.util.LinkHelper;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
import com.squareup.picasso.Picasso;
|
||||
import com.varunest.sparkbutton.SparkButton;
|
||||
import com.varunest.sparkbutton.SparkEventListener;
|
||||
|
@ -100,7 +107,10 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
|
|||
StatusActionListener listener) {
|
||||
/* Redirect URLSpan's in the status content to the listener for viewing tag pages and
|
||||
* account pages. */
|
||||
LinkHelper.setClickableText(this.content, content, mentions, listener);
|
||||
Context context = this.content.getContext();
|
||||
boolean useCustomTabs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getBoolean("useCustomTabs", true);
|
||||
LinkHelper.setClickableText(this.content, content, mentions, useCustomTabs, listener);
|
||||
}
|
||||
|
||||
private void setAvatar(String url) {
|
||||
|
@ -290,14 +300,20 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
|
|||
}
|
||||
});
|
||||
reblogButton.setEventListener(new SparkEventListener() {
|
||||
@Override
|
||||
public void onEvent(ImageView button, boolean buttonState) {
|
||||
int position = getAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onReblog(!reblogged, position);
|
||||
}
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onEvent(ImageView button, boolean buttonState) {
|
||||
int position = getAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onReblog(!reblogged, position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEventAnimationEnd(ImageView button, boolean buttonState) {}
|
||||
|
||||
@Override
|
||||
public void onEventAnimationStart(ImageView button, boolean buttonState) {}
|
||||
});
|
||||
favouriteButton.setEventListener(new SparkEventListener() {
|
||||
@Override
|
||||
public void onEvent(ImageView button, boolean buttonState) {
|
||||
|
@ -306,6 +322,12 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
|
|||
listener.onFavourite(!favourited, position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEventAnimationEnd(ImageView button, boolean buttonState) {}
|
||||
|
||||
@Override
|
||||
public void onEventAnimationStart(ImageView button, boolean buttonState) {}
|
||||
});
|
||||
moreButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
|
@ -13,25 +13,27 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.adapter;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.interfaces.AdapterItemRemover;
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
|
||||
public class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
|
||||
private List<Status> statuses;
|
||||
private StatusActionListener statusActionListener;
|
||||
private int statusIndex;
|
||||
|
||||
ThreadAdapter(StatusActionListener listener) {
|
||||
public ThreadAdapter(StatusActionListener listener) {
|
||||
this.statusActionListener = listener;
|
||||
this.statuses = new ArrayList<>();
|
||||
this.statusIndex = 0;
|
||||
|
@ -56,7 +58,7 @@ class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
|
|||
return statuses.size();
|
||||
}
|
||||
|
||||
Status getItem(int position) {
|
||||
public Status getItem(int position) {
|
||||
return statuses.get(position);
|
||||
}
|
||||
|
||||
|
@ -77,7 +79,7 @@ class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
|
|||
}
|
||||
}
|
||||
|
||||
int setStatus(Status status) {
|
||||
public int setStatus(Status status) {
|
||||
if (statuses.size() > 0 && statuses.get(statusIndex).equals(status)) {
|
||||
// Do not add this status on refresh, it's already in there.
|
||||
statuses.set(statusIndex, status);
|
||||
|
@ -89,16 +91,16 @@ class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
|
|||
return i;
|
||||
}
|
||||
|
||||
void setContext(List<Status> ancestors, List<Status> descendants) {
|
||||
public void setContext(List<Status> ancestors, List<Status> descendants) {
|
||||
Status mainStatus = null;
|
||||
|
||||
// In case of refresh, remove old ancestors and descendants first. We'll remove all blindly,
|
||||
// as we have no guarantee on their order to be the same as before
|
||||
int old_size = statuses.size();
|
||||
if (old_size > 0) {
|
||||
int oldSize = statuses.size();
|
||||
if (oldSize > 0) {
|
||||
mainStatus = statuses.get(statusIndex);
|
||||
statuses.clear();
|
||||
notifyItemRangeRemoved(0, old_size);
|
||||
notifyItemRangeRemoved(0, oldSize);
|
||||
}
|
||||
|
||||
// Insert newly fetched ancestors
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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<Status> newStatuses) {
|
||||
public void update(List<Status> newStatuses) {
|
||||
if (newStatuses == null || newStatuses.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
@ -131,7 +134,7 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover
|
|||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
void addItems(List<Status> newStatuses) {
|
||||
public void addItems(List<Status> newStatuses) {
|
||||
int end = statuses.size();
|
||||
statuses.addAll(newStatuses);
|
||||
notifyItemRangeInserted(end, newStatuses.size());
|
||||
|
@ -142,7 +145,12 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover
|
|||
notifyItemRemoved(position);
|
||||
}
|
||||
|
||||
void removeAllByAccountId(String accountId) {
|
||||
public void clear() {
|
||||
statuses.clear();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void removeAllByAccountId(String accountId) {
|
||||
for (int i = 0; i < statuses.size();) {
|
||||
Status status = statuses.get(i);
|
||||
if (accountId.equals(status.account.id)) {
|
||||
|
@ -155,7 +163,7 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover
|
|||
}
|
||||
|
||||
@Nullable
|
||||
Status getItem(int position) {
|
||||
public Status getItem(int position) {
|
||||
if (position >= 0 && position < statuses.size()) {
|
||||
return statuses.get(position);
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.entity;
|
||||
|
||||
public class Session {
|
||||
public String instanceUrl;
|
||||
public String accessToken;
|
||||
public String deviceToken;
|
||||
|
||||
public Session(String instanceUrl, String accessToken, String deviceToken) {
|
||||
this.instanceUrl = instanceUrl;
|
||||
this.accessToken = accessToken;
|
||||
this.deviceToken = deviceToken;
|
||||
}
|
||||
}
|
|
@ -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<Type> {
|
||||
@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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.fragment;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
@ -29,8 +29,22 @@ import android.view.LayoutInflater;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
|
||||
import com.keylesspalace.tusky.AccountActivity;
|
||||
import com.keylesspalace.tusky.adapter.AccountAdapter;
|
||||
import com.keylesspalace.tusky.adapter.BlocksAdapter;
|
||||
import com.keylesspalace.tusky.adapter.FollowAdapter;
|
||||
import com.keylesspalace.tusky.adapter.FollowRequestsAdapter;
|
||||
import com.keylesspalace.tusky.adapter.MutesAdapter;
|
||||
import com.keylesspalace.tusky.BaseActivity;
|
||||
import com.keylesspalace.tusky.entity.Account;
|
||||
import com.keylesspalace.tusky.entity.Relationship;
|
||||
import com.keylesspalace.tusky.interfaces.AccountActionListener;
|
||||
import com.keylesspalace.tusky.network.MastodonAPI;
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.util.EndlessOnScrollListener;
|
||||
import com.keylesspalace.tusky.util.Log;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -114,16 +128,22 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi
|
|||
}
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
BaseActivity activity = (BaseActivity) getActivity();
|
||||
|
||||
if (jumpToTopAllowed()) {
|
||||
TabLayout layout = (TabLayout) getActivity().findViewById(R.id.tab_layout);
|
||||
TabLayout layout = (TabLayout) activity.findViewById(R.id.tab_layout);
|
||||
onTabSelectedListener = new TabLayout.OnTabSelectedListener() {
|
||||
@Override
|
||||
public void onTabSelected(TabLayout.Tab tab) {
|
||||
}
|
||||
public void onTabSelected(TabLayout.Tab tab) {}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(TabLayout.Tab tab) {
|
||||
}
|
||||
public void onTabUnselected(TabLayout.Tab tab) {}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(TabLayout.Tab tab) {
|
||||
|
@ -133,16 +153,10 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi
|
|||
layout.addOnTabSelectedListener(onTabSelectedListener);
|
||||
}
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
/* MastodonAPI on the base activity is only guaranteed to be initialised after the parent
|
||||
* activity is created, so everything needing to access the api object has to be delayed
|
||||
* until here. */
|
||||
api = ((BaseActivity) getActivity()).mastodonAPI;
|
||||
api = activity.mastodonAPI;
|
||||
scrollListener = new EndlessOnScrollListener(layoutManager) {
|
||||
@Override
|
||||
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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;
|
||||
|
|
@ -13,12 +13,18 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.fragment;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.BottomSheetDialogFragment;
|
||||
import android.support.graphics.drawable.VectorDrawableCompat;
|
||||
import android.support.v4.graphics.drawable.DrawableCompat;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
@ -27,8 +33,11 @@ import android.widget.CompoundButton;
|
|||
import android.widget.RadioButton;
|
||||
import android.widget.RadioGroup;
|
||||
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
|
||||
public class ComposeOptionsFragment extends BottomSheetDialogFragment {
|
||||
interface Listener {
|
||||
public interface Listener {
|
||||
void onVisibilityChanged(String visibility);
|
||||
void onContentWarningChanged(boolean hideText);
|
||||
}
|
||||
|
@ -85,8 +94,16 @@ public class ComposeOptionsFragment extends BottomSheetDialogFragment {
|
|||
}
|
||||
radio.check(radioCheckedId);
|
||||
|
||||
RadioButton publicButton = (RadioButton) rootView.findViewById(R.id.radio_public);
|
||||
RadioButton unlistedButton = (RadioButton) rootView.findViewById(R.id.radio_unlisted);
|
||||
RadioButton privateButton = (RadioButton) rootView.findViewById(R.id.radio_private);
|
||||
RadioButton directButton = (RadioButton) rootView.findViewById(R.id.radio_direct);
|
||||
setRadioButtonDrawable(getContext(), publicButton, R.drawable.ic_public_24dp);
|
||||
setRadioButtonDrawable(getContext(), unlistedButton, R.drawable.ic_lock_open_24dp);
|
||||
setRadioButtonDrawable(getContext(), privateButton, R.drawable.ic_lock_outline_24dp);
|
||||
setRadioButtonDrawable(getContext(), directButton, R.drawable.ic_email_24dp);
|
||||
|
||||
if (isReply) {
|
||||
RadioButton publicButton = (RadioButton) rootView.findViewById(R.id.radio_public);
|
||||
publicButton.setEnabled(false);
|
||||
}
|
||||
|
||||
|
@ -132,4 +149,27 @@ public class ComposeOptionsFragment extends BottomSheetDialogFragment {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void setRadioButtonDrawable(Context context, RadioButton button,
|
||||
@DrawableRes int id) {
|
||||
ColorStateList list = new ColorStateList(new int[][] {
|
||||
new int[] { -android.R.attr.state_checked },
|
||||
new int[] { android.R.attr.state_checked }
|
||||
}, new int[] {
|
||||
ThemeUtils.getColor(context, R.attr.compose_image_button_tint),
|
||||
ThemeUtils.getColor(context, R.attr.colorAccent)
|
||||
});
|
||||
Drawable drawable = VectorDrawableCompat.create(context.getResources(), id,
|
||||
context.getTheme());
|
||||
if (drawable == null) {
|
||||
return;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
button.setButtonTintList(list);
|
||||
} else {
|
||||
drawable = DrawableCompat.wrap(drawable);
|
||||
DrawableCompat.setTintList(drawable, list);
|
||||
}
|
||||
button.setButtonDrawable(drawable);
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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;
|
||||
|
|
@ -13,11 +13,13 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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) {
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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);
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.fragment;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
@ -23,6 +23,7 @@ import android.preference.PreferenceManager;
|
|||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.design.widget.TabLayout;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.support.v7.widget.DividerItemDecoration;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
|
@ -31,12 +32,23 @@ import android.view.LayoutInflater;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.keylesspalace.tusky.MainActivity;
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.adapter.TimelineAdapter;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||
import com.keylesspalace.tusky.interfaces.StatusRemoveListener;
|
||||
import com.keylesspalace.tusky.util.EndlessOnScrollListener;
|
||||
import com.keylesspalace.tusky.util.Log;
|
||||
import com.keylesspalace.tusky.util.TimelineReceiver;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class TimelineFragment extends SFragment implements
|
||||
SwipeRefreshLayout.OnRefreshListener,
|
||||
|
@ -45,9 +57,7 @@ public class TimelineFragment extends SFragment implements
|
|||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private static final String TAG = "Timeline"; // logging tag
|
||||
|
||||
private Call<List<Status>> listCall;
|
||||
|
||||
enum Kind {
|
||||
public enum Kind {
|
||||
HOME,
|
||||
PUBLIC_LOCAL,
|
||||
PUBLIC_FEDERATED,
|
||||
|
@ -64,7 +74,11 @@ public class TimelineFragment extends SFragment implements
|
|||
private LinearLayoutManager layoutManager;
|
||||
private EndlessOnScrollListener scrollListener;
|
||||
private TabLayout.OnTabSelectedListener onTabSelectedListener;
|
||||
private SharedPreferences preferences;
|
||||
private boolean filterRemoveReplies;
|
||||
private boolean filterRemoveReblogs;
|
||||
private boolean hideFab;
|
||||
private TimelineReceiver timelineReceiver;
|
||||
|
||||
public static TimelineFragment newInstance(Kind kind) {
|
||||
TimelineFragment fragment = new TimelineFragment();
|
||||
|
@ -113,6 +127,25 @@ public class TimelineFragment extends SFragment implements
|
|||
adapter = new TimelineAdapter(this);
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
timelineReceiver = new TimelineReceiver(adapter);
|
||||
LocalBroadcastManager.getInstance(context.getApplicationContext()).registerReceiver(timelineReceiver, TimelineReceiver.getFilter(kind));
|
||||
return rootView;
|
||||
}
|
||||
|
||||
private void onLoadMore(RecyclerView view) {
|
||||
TimelineAdapter adapter = (TimelineAdapter) view.getAdapter();
|
||||
Status status = adapter.getItem(adapter.getItemCount() - 2);
|
||||
if (status != null) {
|
||||
sendFetchTimelineRequest(status.id, null);
|
||||
} else {
|
||||
sendFetchTimelineRequest(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
if (jumpToTopAllowed()) {
|
||||
TabLayout layout = (TabLayout) getActivity().findViewById(R.id.tab_layout);
|
||||
onTabSelectedListener = new TabLayout.OnTabSelectedListener() {
|
||||
|
@ -130,23 +163,6 @@ public class TimelineFragment extends SFragment implements
|
|||
layout.addOnTabSelectedListener(onTabSelectedListener);
|
||||
}
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
private void onLoadMore(RecyclerView view) {
|
||||
TimelineAdapter adapter = (TimelineAdapter) view.getAdapter();
|
||||
Status status = adapter.getItem(adapter.getItemCount() - 2);
|
||||
if (status != null) {
|
||||
sendFetchTimelineRequest(status.id, null);
|
||||
} else {
|
||||
sendFetchTimelineRequest();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
/* This is delayed until onActivityCreated solely because MainActivity.composeButton isn't
|
||||
* guaranteed to be set until then. */
|
||||
if (composeButtonPresent()) {
|
||||
|
@ -189,12 +205,8 @@ public class TimelineFragment extends SFragment implements
|
|||
};
|
||||
}
|
||||
recyclerView.addOnScrollListener(scrollListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (listCall != null) listCall.cancel();
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -203,6 +215,7 @@ public class TimelineFragment extends SFragment implements
|
|||
TabLayout tabLayout = (TabLayout) getActivity().findViewById(R.id.tab_layout);
|
||||
tabLayout.removeOnTabSelectedListener(onTabSelectedListener);
|
||||
}
|
||||
LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(timelineReceiver);
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
|
@ -224,9 +237,9 @@ public class TimelineFragment extends SFragment implements
|
|||
adapter.setFooterState(TimelineAdapter.FooterState.LOADING);
|
||||
}
|
||||
|
||||
Callback<List<Status>> cb = new Callback<List<Status>>() {
|
||||
Callback<List<Status>> callback = new Callback<List<Status>>() {
|
||||
@Override
|
||||
public void onResponse(Call<List<Status>> call, retrofit2.Response<List<Status>> response) {
|
||||
public void onResponse(Call<List<Status>> call, Response<List<Status>> response) {
|
||||
if (response.isSuccessful()) {
|
||||
onFetchTimelineSuccess(response.body(), fromId);
|
||||
} else {
|
||||
|
@ -240,6 +253,7 @@ public class TimelineFragment extends SFragment implements
|
|||
}
|
||||
};
|
||||
|
||||
Call<List<Status>> listCall;
|
||||
switch (kind) {
|
||||
default:
|
||||
case HOME: {
|
||||
|
@ -268,11 +282,7 @@ public class TimelineFragment extends SFragment implements
|
|||
}
|
||||
}
|
||||
callList.add(listCall);
|
||||
listCall.enqueue(cb);
|
||||
}
|
||||
|
||||
private void sendFetchTimelineRequest() {
|
||||
sendFetchTimelineRequest(null, null);
|
||||
listCall.enqueue(callback);
|
||||
}
|
||||
|
||||
public void removePostsByUser(String accountId) {
|
||||
|
@ -288,7 +298,36 @@ public class TimelineFragment extends SFragment implements
|
|||
return false;
|
||||
}
|
||||
|
||||
protected void filterStatuses(List<Status> statuses) {
|
||||
Iterator<Status> it = statuses.iterator();
|
||||
while (it.hasNext()) {
|
||||
Status status = it.next();
|
||||
if ((status.inReplyToId != null && filterRemoveReplies) || (status.reblog != null && filterRemoveReblogs)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void setFiltersFromSettings() {
|
||||
boolean oldRemoveReplies = filterRemoveReplies;
|
||||
boolean oldRemoveReblogs = filterRemoveReblogs;
|
||||
filterRemoveReplies = (kind == Kind.HOME && !preferences.getBoolean("tabFilterHomeReplies", true));
|
||||
filterRemoveReblogs = (kind == Kind.HOME && !preferences.getBoolean("tabFilterHomeBoosts", true));
|
||||
|
||||
if (adapter.getItemCount() > 1 && (oldRemoveReblogs != filterRemoveReblogs || oldRemoveReplies != filterRemoveReplies)) {
|
||||
adapter.clear();
|
||||
sendFetchTimelineRequest(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
setFiltersFromSettings();
|
||||
}
|
||||
|
||||
public void onFetchTimelineSuccess(List<Status> statuses, String fromId) {
|
||||
filterStatuses(statuses);
|
||||
if (fromId != null) {
|
||||
if (statuses.size() > 0 && !findStatus(statuses, fromId)) {
|
||||
adapter.addItems(statuses);
|
||||
|
@ -314,7 +353,7 @@ public class TimelineFragment extends SFragment implements
|
|||
if (status != null) {
|
||||
sendFetchTimelineRequest(null, status.id);
|
||||
} else {
|
||||
sendFetchTimelineRequest();
|
||||
sendFetchTimelineRequest(null, null);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,12 +13,10 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.fragment;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.DownloadManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
@ -30,12 +28,20 @@ import android.support.annotation.StringRes;
|
|||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.github.chrisbanes.photoview.OnOutsidePhotoTapListener;
|
||||
import com.github.chrisbanes.photoview.OnSingleFlingListener;
|
||||
import com.github.chrisbanes.photoview.PhotoView;
|
||||
import com.github.chrisbanes.photoview.PhotoViewAttacher;
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.squareup.picasso.Callback;
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
|
@ -43,17 +49,18 @@ import java.io.File;
|
|||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import uk.co.senab.photoview.PhotoView;
|
||||
import uk.co.senab.photoview.PhotoViewAttacher;
|
||||
|
||||
public class ViewMediaFragment extends DialogFragment {
|
||||
public class ViewMediaFragment extends DialogFragment implements Toolbar.OnMenuItemClickListener {
|
||||
|
||||
private PhotoViewAttacher attacher;
|
||||
private DownloadManager downloadManager;
|
||||
|
||||
private static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1;
|
||||
|
||||
@BindView(R.id.view_media_image) PhotoView photoView;
|
||||
@BindView(R.id.view_media_image)
|
||||
PhotoView photoView;
|
||||
|
||||
@BindView(R.id.toolbar)
|
||||
Toolbar toolbar;
|
||||
|
||||
public static ViewMediaFragment newInstance(String url) {
|
||||
Bundle arguments = new Bundle();
|
||||
|
@ -81,7 +88,7 @@ public class ViewMediaFragment extends DialogFragment {
|
|||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, final ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_view_media, container, false);
|
||||
final View rootView = inflater.inflate(R.layout.fragment_view_media, container, false);
|
||||
ButterKnife.bind(this, rootView);
|
||||
|
||||
Bundle arguments = getArguments();
|
||||
|
@ -90,24 +97,19 @@ public class ViewMediaFragment extends DialogFragment {
|
|||
attacher = new PhotoViewAttacher(photoView);
|
||||
|
||||
// Clicking outside the photo closes the viewer.
|
||||
attacher.setOnPhotoTapListener(new PhotoViewAttacher.OnPhotoTapListener() {
|
||||
attacher.setOnOutsidePhotoTapListener(new OnOutsidePhotoTapListener() {
|
||||
@Override
|
||||
public void onPhotoTap(View view, float x, float y) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOutsidePhotoTap() {
|
||||
public void onOutsidePhotoTap(ImageView imageView) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
/* A vertical swipe motion also closes the viewer. This is especially useful when the photo
|
||||
* mostly fills the screen so clicking outside is difficult. */
|
||||
attacher.setOnSingleFlingListener(new PhotoViewAttacher.OnSingleFlingListener() {
|
||||
attacher.setOnSingleFlingListener(new OnSingleFlingListener() {
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
|
||||
float velocityY) {
|
||||
float velocityY) {
|
||||
if (Math.abs(velocityY) > Math.abs(velocityX)) {
|
||||
dismiss();
|
||||
return true;
|
||||
|
@ -116,22 +118,10 @@ public class ViewMediaFragment extends DialogFragment {
|
|||
}
|
||||
});
|
||||
|
||||
attacher.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
|
||||
AlertDialog downloadDialog = new AlertDialog.Builder(getContext()).create();
|
||||
|
||||
downloadDialog.setButton(AlertDialog.BUTTON_NEUTRAL, getString(R.string.dialog_download_image),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
|
||||
downloadImage();
|
||||
}
|
||||
});
|
||||
downloadDialog.show();
|
||||
return false;
|
||||
public void onClick(View v) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -140,6 +130,10 @@ public class ViewMediaFragment extends DialogFragment {
|
|||
.into(photoView, new Callback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
rootView.findViewById(R.id.view_media_progress).setVisibility(View.GONE);
|
||||
toolbar.setOnMenuItemClickListener(ViewMediaFragment.this);
|
||||
toolbar.inflateMenu(R.menu.view_media_tooblar);
|
||||
|
||||
attacher.update();
|
||||
}
|
||||
|
||||
|
@ -152,12 +146,6 @@ public class ViewMediaFragment extends DialogFragment {
|
|||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
attacher.cleanup();
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
private void downloadImage(){
|
||||
|
||||
//Permission stuff
|
||||
|
@ -170,14 +158,13 @@ public class ViewMediaFragment extends DialogFragment {
|
|||
PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
|
||||
} else {
|
||||
|
||||
|
||||
//download stuff
|
||||
String url = getArguments().getString("url");
|
||||
Uri uri = Uri.parse(url);
|
||||
|
||||
String filename = new File(url).getName();
|
||||
|
||||
downloadManager = (DownloadManager) getContext().getSystemService(Context.DOWNLOAD_SERVICE);
|
||||
DownloadManager downloadManager = (DownloadManager) getContext().getSystemService(Context.DOWNLOAD_SERVICE);
|
||||
|
||||
DownloadManager.Request request = new DownloadManager.Request(uri);
|
||||
request.allowScanningByMediaScanner();
|
||||
|
@ -211,9 +198,24 @@ public class ViewMediaFragment extends DialogFragment {
|
|||
|
||||
private void doErrorDialog(@StringRes int descriptionId, @StringRes int actionId,
|
||||
View.OnClickListener listener) {
|
||||
Snackbar bar = Snackbar.make(getView(), getString(descriptionId),
|
||||
Snackbar.LENGTH_SHORT);
|
||||
bar.setAction(actionId, listener);
|
||||
bar.show();
|
||||
if(getView() != null) {
|
||||
Snackbar bar = Snackbar.make(getView(), getString(descriptionId),
|
||||
Snackbar.LENGTH_SHORT);
|
||||
bar.setAction(actionId, listener);
|
||||
bar.show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
switch (id) {
|
||||
case R.id.action_download:
|
||||
downloadImage();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.fragment;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
@ -29,11 +29,22 @@ import android.view.LayoutInflater;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
|
||||
import com.keylesspalace.tusky.adapter.ThreadAdapter;
|
||||
import com.keylesspalace.tusky.BaseActivity;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.entity.StatusContext;
|
||||
import com.keylesspalace.tusky.network.MastodonAPI;
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||
import com.keylesspalace.tusky.interfaces.StatusRemoveListener;
|
||||
import com.keylesspalace.tusky.util.ConversationLineItemDecoration;
|
||||
import com.keylesspalace.tusky.util.Log;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class ViewThreadFragment extends SFragment implements
|
||||
SwipeRefreshLayout.OnRefreshListener, StatusActionListener, StatusRemoveListener {
|
||||
|
@ -42,6 +53,7 @@ public class ViewThreadFragment extends SFragment implements
|
|||
private SwipeRefreshLayout swipeRefreshLayout;
|
||||
private RecyclerView recyclerView;
|
||||
private ThreadAdapter adapter;
|
||||
private MastodonAPI mastodonApi;
|
||||
private String thisThreadsStatusId;
|
||||
|
||||
public static ViewThreadFragment newInstance(String id) {
|
||||
|
@ -72,25 +84,34 @@ public class ViewThreadFragment extends SFragment implements
|
|||
R.drawable.status_divider_dark);
|
||||
divider.setDrawable(drawable);
|
||||
recyclerView.addItemDecoration(divider);
|
||||
recyclerView.addItemDecoration(new ConversationLineItemDecoration(context, ContextCompat.getDrawable(context, R.drawable.conversation_divider_dark)));
|
||||
recyclerView.addItemDecoration(new ConversationLineItemDecoration(context,
|
||||
ContextCompat.getDrawable(context, R.drawable.conversation_divider_dark)));
|
||||
adapter = new ThreadAdapter(this);
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
String id = getArguments().getString("id");
|
||||
sendStatusRequest(id);
|
||||
sendThreadRequest(id);
|
||||
thisThreadsStatusId = id;
|
||||
mastodonApi = null;
|
||||
thisThreadsStatusId = null;
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
private void sendStatusRequest(final String id) {
|
||||
MastodonAPI api = ((BaseActivity) getActivity()).mastodonAPI;
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
Call<Status> 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<Status> call = mastodonApi.status(id);
|
||||
call.enqueue(new Callback<Status>() {
|
||||
@Override
|
||||
public void onResponse(Call<Status> call, retrofit2.Response<Status> response) {
|
||||
public void onResponse(Call<Status> call, Response<Status> response) {
|
||||
if (response.isSuccessful()) {
|
||||
int position = adapter.setStatus(response.body());
|
||||
recyclerView.scrollToPosition(position);
|
||||
|
@ -108,12 +129,10 @@ public class ViewThreadFragment extends SFragment implements
|
|||
}
|
||||
|
||||
private void sendThreadRequest(final String id) {
|
||||
MastodonAPI api = ((BaseActivity) getActivity()).mastodonAPI;
|
||||
|
||||
Call<StatusContext> call = api.statusContext(id);
|
||||
Call<StatusContext> call = mastodonApi.statusContext(id);
|
||||
call.enqueue(new Callback<StatusContext>() {
|
||||
@Override
|
||||
public void onResponse(Call<StatusContext> call, retrofit2.Response<StatusContext> response) {
|
||||
public void onResponse(Call<StatusContext> call, Response<StatusContext> response) {
|
||||
if (response.isSuccessful()) {
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
StatusContext context = response.body();
|
|
@ -13,9 +13,9 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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);
|
|
@ -13,8 +13,8 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.interfaces;
|
||||
|
||||
interface AdapterItemRemover {
|
||||
public interface AdapterItemRemover {
|
||||
void removeItem(int position);
|
||||
}
|
|
@ -13,9 +13,9 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.interfaces;
|
||||
|
||||
interface LinkListener {
|
||||
public interface LinkListener {
|
||||
void onViewTag(String tag);
|
||||
void onViewAccount(String id);
|
||||
}
|
|
@ -13,13 +13,13 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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);
|
|
@ -13,8 +13,8 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.interfaces;
|
||||
|
||||
interface StatusRemoveListener {
|
||||
public interface StatusRemoveListener {
|
||||
void removePostsByUser(String accountId);
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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;
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.json;
|
||||
|
||||
/**
|
||||
* This is just a wrapper class for a String.
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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<StringWithEmoji> {
|
||||
public class StringWithEmojiTypeAdapter implements JsonDeserializer<StringWithEmoji> {
|
||||
@Override
|
||||
public StringWithEmoji deserialize(JsonElement json, Type typeOfT,
|
||||
JsonDeserializationContext context) throws JsonParseException {
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.network;
|
||||
|
||||
import com.keylesspalace.tusky.entity.AccessToken;
|
||||
import com.keylesspalace.tusky.entity.Account;
|
|
@ -13,19 +13,18 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.network;
|
||||
|
||||
import com.keylesspalace.tusky.entity.Session;
|
||||
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Field;
|
||||
import retrofit2.http.FormUrlEncoded;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.POST;
|
||||
|
||||
public interface TuskyAPI {
|
||||
@FormUrlEncoded
|
||||
public interface TuskyApi {
|
||||
@POST("/register")
|
||||
Call<ResponseBody> register(@Field("instance_url") String instanceUrl, @Field("access_token") String accessToken, @Field("device_token") String deviceToken);
|
||||
@FormUrlEncoded
|
||||
Call<ResponseBody> register(@Body Session session);
|
||||
@POST("/unregister")
|
||||
Call<ResponseBody> unregister(@Field("instance_url") String instanceUrl, @Field("access_token") String accessToken);
|
||||
Call<ResponseBody> unregister(@Body Session session);
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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<Fragment> 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<Fragment> getRegisteredFragments() {
|
||||
public List<Fragment> getRegisteredFragments() {
|
||||
return registeredFragments;
|
||||
}
|
||||
}
|
|
@ -13,31 +13,34 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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<Fragment> 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<Fragment> getRegisteredFragments() {
|
||||
public List<Fragment> getRegisteredFragments() {
|
||||
return registeredFragments;
|
||||
}
|
||||
|
|
@ -13,12 +13,14 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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.
|
|
@ -13,13 +13,15 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import com.keylesspalace.tusky.BuildConfig;
|
||||
|
||||
/** Android Studio complains about built-in assertions so this is an alternative. */
|
||||
class Assert {
|
||||
public class Assert {
|
||||
private static boolean ENABLED = BuildConfig.DEBUG;
|
||||
|
||||
static void expect(boolean expression) {
|
||||
public static void expect(boolean expression) {
|
||||
if (ENABLED && !expression) {
|
||||
throw new AssertionError();
|
||||
}
|
|
@ -13,19 +13,17 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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;
|
||||
|
|
@ -13,26 +13,26 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.util;
|
||||
|
||||
class CountUpDownLatch {
|
||||
public class CountUpDownLatch {
|
||||
private int count;
|
||||
|
||||
CountUpDownLatch() {
|
||||
public CountUpDownLatch() {
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
synchronized void countDown() {
|
||||
public synchronized void countDown() {
|
||||
count--;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
synchronized void countUp() {
|
||||
public synchronized void countUp() {
|
||||
count++;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
synchronized void await() throws InterruptedException {
|
||||
public synchronized void await() throws InterruptedException {
|
||||
while (count != 0) {
|
||||
wait();
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
|
@ -13,12 +13,12 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.util;
|
||||
|
||||
class DateUtils {
|
||||
public class DateUtils {
|
||||
/* This is a rough duplicate of android.text.format.DateUtils.getRelativeTimeSpanString,
|
||||
* but even with the FORMAT_ABBREV_RELATIVE flag it wasn't abbreviating enough. */
|
||||
static String getRelativeTimeSpanString(long then, long now) {
|
||||
public static String getRelativeTimeSpanString(long then, long now) {
|
||||
final long MINUTE = 60;
|
||||
final long HOUR = 60 * MINUTE;
|
||||
final long DAY = 24 * HOUR;
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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<Uri, Void, Boolean> {
|
||||
public class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
|
||||
private int sizeLimit;
|
||||
private ContentResolver contentResolver;
|
||||
private Listener listener;
|
||||
private List<byte[]> 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<Uri, Void, Boolean> {
|
|||
super.onPostExecute(successful);
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
public interface Listener {
|
||||
void onSuccess(List<byte[]> contentList);
|
||||
void onFailure();
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v13.view.inputmethod.EditorInfoCompat;
|
|
@ -13,12 +13,12 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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;
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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; //
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import android.os.Build;
|
||||
import android.text.Html;
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
|
@ -21,8 +21,8 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
class IOUtils {
|
||||
static void closeQuietly(@Nullable InputStream stream) {
|
||||
public class IOUtils {
|
||||
public static void closeQuietly(@Nullable InputStream stream) {
|
||||
try {
|
||||
if (stream != null) {
|
||||
stream.close();
|
||||
|
@ -32,7 +32,7 @@ class IOUtils {
|
|||
}
|
||||
}
|
||||
|
||||
static void closeQuietly(@Nullable OutputStream stream) {
|
||||
public static void closeQuietly(@Nullable OutputStream stream) {
|
||||
try {
|
||||
if (stream != null) {
|
||||
stream.close();
|
|
@ -13,9 +13,8 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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);
|
|
@ -13,7 +13,9 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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 {
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.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(
|
|
@ -13,11 +13,13 @@
|
|||
* You should have received a copy of the GNU Lesser General Public License along with Tusky. If
|
||||
* not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.keylesspalace.tusky.BuildConfig;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
|
@ -94,7 +96,7 @@ public class OkHttpUtils {
|
|||
public Response intercept(Chain chain) throws IOException {
|
||||
Request originalRequest = chain.request();
|
||||
Request requestWithUserAgent = originalRequest.newBuilder()
|
||||
.header("User-Agent", "Tusky/"+BuildConfig.VERSION_NAME+" Android/"+Build.VERSION.RELEASE)
|
||||
.header("User-Agent", "Tusky/"+ BuildConfig.VERSION_NAME+" Android/"+Build.VERSION.RELEASE)
|
||||
.build();
|
||||
return chain.proceed(requestWithUserAgent);
|
||||
}
|
|
@ -0,0 +1,237 @@
|
|||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.Spanned;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.entity.Notification;
|
||||
import com.keylesspalace.tusky.json.SpannedTypeAdapter;
|
||||
import com.keylesspalace.tusky.json.StringWithEmoji;
|
||||
import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter;
|
||||
|
||||
import org.eclipse.paho.android.service.MqttAndroidClient;
|
||||
import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions;
|
||||
import org.eclipse.paho.client.mqttv3.IMqttActionListener;
|
||||
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
|
||||
import org.eclipse.paho.client.mqttv3.IMqttToken;
|
||||
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
|
||||
import org.eclipse.paho.client.mqttv3.MqttClient;
|
||||
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
|
||||
import org.eclipse.paho.client.mqttv3.MqttException;
|
||||
import org.eclipse.paho.client.mqttv3.MqttMessage;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static android.content.Context.NOTIFICATION_SERVICE;
|
||||
|
||||
public class PushNotificationClient {
|
||||
private static final String TAG = "PushNotificationClient";
|
||||
private static final int NOTIFY_ID = 666;
|
||||
|
||||
private static class QueuedAction {
|
||||
enum Type {
|
||||
SUBSCRIBE,
|
||||
UNSUBSCRIBE,
|
||||
DISCONNECT,
|
||||
}
|
||||
|
||||
Type type;
|
||||
String topic;
|
||||
|
||||
QueuedAction(Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
QueuedAction(Type type, String topic) {
|
||||
this.type = type;
|
||||
this.topic = topic;
|
||||
}
|
||||
}
|
||||
|
||||
private MqttAndroidClient mqttAndroidClient;
|
||||
private ArrayDeque<QueuedAction> queuedActions;
|
||||
private ArrayList<String> subscribedTopics;
|
||||
|
||||
public PushNotificationClient(final @NonNull Context applicationContext,
|
||||
@NonNull String serverUri) {
|
||||
queuedActions = new ArrayDeque<>();
|
||||
subscribedTopics = new ArrayList<>();
|
||||
|
||||
// Create the MQTT client.
|
||||
String clientId = MqttClient.generateClientId();
|
||||
mqttAndroidClient = new MqttAndroidClient(applicationContext, serverUri, clientId);
|
||||
mqttAndroidClient.setCallback(new MqttCallbackExtended() {
|
||||
@Override
|
||||
public void connectComplete(boolean reconnect, String serverURI) {
|
||||
if (reconnect) {
|
||||
flushQueuedActions();
|
||||
for (String topic : subscribedTopics) {
|
||||
subscribeToTopic(topic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionLost(Throwable cause) {
|
||||
onConnectionLost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageArrived(String topic, MqttMessage message) throws Exception {
|
||||
onMessageReceived(applicationContext, new String(message.getPayload()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliveryComplete(IMqttDeliveryToken token) {
|
||||
// This client is read-only, so this is unused.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void flushQueuedActions() {
|
||||
while (!queuedActions.isEmpty()) {
|
||||
QueuedAction action = queuedActions.pop();
|
||||
switch (action.type) {
|
||||
case SUBSCRIBE: subscribeToTopic(action.topic); break;
|
||||
case UNSUBSCRIBE: unsubscribeToTopic(action.topic); break;
|
||||
case DISCONNECT: disconnect(); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Connect to the MQTT broker. */
|
||||
public void connect(Context context) {
|
||||
MqttConnectOptions options = new MqttConnectOptions();
|
||||
options.setAutomaticReconnect(true);
|
||||
options.setCleanSession(false);
|
||||
try {
|
||||
String password = context.getString(R.string.tusky_api_keystore_password);
|
||||
InputStream keystore = context.getResources().openRawResource(R.raw.keystore_tusky_api);
|
||||
try {
|
||||
options.setSocketFactory(mqttAndroidClient.getSSLSocketFactory(keystore, password));
|
||||
} finally {
|
||||
IOUtils.closeQuietly(keystore);
|
||||
}
|
||||
mqttAndroidClient.connect(options).setActionCallback(new IMqttActionListener() {
|
||||
@Override
|
||||
public void onSuccess(IMqttToken asyncActionToken) {
|
||||
DisconnectedBufferOptions bufferOptions = new DisconnectedBufferOptions();
|
||||
bufferOptions.setBufferEnabled(true);
|
||||
bufferOptions.setBufferSize(100);
|
||||
bufferOptions.setPersistBuffer(false);
|
||||
bufferOptions.setDeleteOldestMessages(false);
|
||||
mqttAndroidClient.setBufferOpts(bufferOptions);
|
||||
onConnectionSuccess();
|
||||
flushQueuedActions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
|
||||
Log.e(TAG, "An exception occurred while connecting. " + exception.getMessage()
|
||||
+ " " + exception.getCause());
|
||||
onConnectionFailure();
|
||||
}
|
||||
});
|
||||
} catch (MqttException e) {
|
||||
Log.e(TAG, "An exception occurred while connecpting. " + e.getMessage());
|
||||
onConnectionFailure();
|
||||
}
|
||||
}
|
||||
|
||||
private void onConnectionSuccess() {
|
||||
Log.v(TAG, "The connection succeeded.");
|
||||
}
|
||||
|
||||
private void onConnectionFailure() {
|
||||
Log.v(TAG, "The connection failed.");
|
||||
}
|
||||
|
||||
private void onConnectionLost() {
|
||||
Log.v(TAG, "The connection was lost.");
|
||||
}
|
||||
|
||||
/** Disconnect from the MQTT broker. */
|
||||
public void disconnect() {
|
||||
if (!mqttAndroidClient.isConnected()) {
|
||||
queuedActions.add(new QueuedAction(QueuedAction.Type.DISCONNECT));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mqttAndroidClient.disconnect();
|
||||
} catch (MqttException ex) {
|
||||
Log.e(TAG, "An exception occurred while disconnecting.");
|
||||
onDisconnectFailed();
|
||||
}
|
||||
}
|
||||
|
||||
private void onDisconnectFailed() {
|
||||
Log.v(TAG, "Failed while disconnecting from the broker.");
|
||||
}
|
||||
|
||||
/** Subscribe to the push notification topic. */
|
||||
public void subscribeToTopic(final String topic) {
|
||||
if (!mqttAndroidClient.isConnected()) {
|
||||
queuedActions.add(new QueuedAction(QueuedAction.Type.SUBSCRIBE, topic));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mqttAndroidClient.subscribe(topic, 0, null, new IMqttActionListener() {
|
||||
@Override
|
||||
public void onSuccess(IMqttToken asyncActionToken) {
|
||||
subscribedTopics.add(topic);
|
||||
onConnectionSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
|
||||
Log.e(TAG, "An exception occurred while subscribing." + exception.getMessage());
|
||||
onConnectionFailure();
|
||||
}
|
||||
});
|
||||
} catch (MqttException e) {
|
||||
Log.e(TAG, "An exception occurred while subscribing." + e.getMessage());
|
||||
onConnectionFailure();
|
||||
}
|
||||
}
|
||||
|
||||
/** Unsubscribe from the push notification topic. */
|
||||
public void unsubscribeToTopic(String topic) {
|
||||
if (!mqttAndroidClient.isConnected()) {
|
||||
queuedActions.add(new QueuedAction(QueuedAction.Type.UNSUBSCRIBE, topic));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mqttAndroidClient.unsubscribe(topic);
|
||||
subscribedTopics.remove(topic);
|
||||
} catch (MqttException e) {
|
||||
Log.e(TAG, "An exception occurred while unsubscribing." + e.getMessage());
|
||||
onConnectionFailure();
|
||||
}
|
||||
}
|
||||
|
||||
private void onMessageReceived(final Context context, String message) {
|
||||
Log.v(TAG, "Notification received: " + message);
|
||||
|
||||
Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapter(Spanned.class, new SpannedTypeAdapter())
|
||||
.registerTypeAdapter(StringWithEmoji.class, new StringWithEmojiTypeAdapter())
|
||||
.create();
|
||||
Notification notification = gson.fromJson(message, Notification.class);
|
||||
|
||||
NotificationMaker.make(context, NOTIFY_ID, notification);
|
||||
}
|
||||
|
||||
public void clearNotifications(Context context) {
|
||||
((NotificationManager) (context.getSystemService(NOTIFICATION_SERVICE))).cancel(NOTIFY_ID);
|
||||
}
|
||||
|
||||
public String getDeviceToken() {
|
||||
return mqttAndroidClient.getClientId();
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapShader;
|
|
@ -13,13 +13,13 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import android.text.Spannable;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
|
||||
class SpanUtils {
|
||||
public class SpanUtils {
|
||||
private static class FindCharsResult {
|
||||
int charIndex;
|
||||
int stringIndex;
|
||||
|
@ -94,7 +94,7 @@ class SpanUtils {
|
|||
return length;
|
||||
}
|
||||
|
||||
static void highlightSpans(Spannable text, int colour) {
|
||||
public static void highlightSpans(Spannable text, int colour) {
|
||||
// Strip all existing colour spans.
|
||||
int n = text.length();
|
||||
ForegroundColorSpan[] oldSpans = text.getSpans(0, n, ForegroundColorSpan.class);
|
|
@ -13,7 +13,7 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
|
@ -26,8 +26,8 @@ import android.support.v4.content.ContextCompat;
|
|||
import android.util.TypedValue;
|
||||
import android.widget.ImageView;
|
||||
|
||||
class ThemeUtils {
|
||||
static Drawable getDrawable(Context context, @AttrRes int attribute,
|
||||
public class ThemeUtils {
|
||||
public static Drawable getDrawable(Context context, @AttrRes int attribute,
|
||||
@DrawableRes int fallbackDrawable) {
|
||||
TypedValue value = new TypedValue();
|
||||
@DrawableRes int resourceId;
|
||||
|
@ -39,7 +39,7 @@ class ThemeUtils {
|
|||
return ContextCompat.getDrawable(context, resourceId);
|
||||
}
|
||||
|
||||
static @DrawableRes int getDrawableId(Context context, @AttrRes int attribute,
|
||||
public static @DrawableRes int getDrawableId(Context context, @AttrRes int attribute,
|
||||
@DrawableRes int fallbackDrawableId) {
|
||||
TypedValue value = new TypedValue();
|
||||
if (context.getTheme().resolveAttribute(attribute, value, true)) {
|
||||
|
@ -49,7 +49,7 @@ class ThemeUtils {
|
|||
}
|
||||
}
|
||||
|
||||
static @ColorInt int getColor(Context context, @AttrRes int attribute) {
|
||||
public static @ColorInt int getColor(Context context, @AttrRes int attribute) {
|
||||
TypedValue value = new TypedValue();
|
||||
if (context.getTheme().resolveAttribute(attribute, value, true)) {
|
||||
return value.data;
|
||||
|
@ -58,11 +58,11 @@ class ThemeUtils {
|
|||
}
|
||||
}
|
||||
|
||||
static void setImageViewTint(ImageView view, @AttrRes int attribute) {
|
||||
public static void setImageViewTint(ImageView view, @AttrRes int attribute) {
|
||||
view.setColorFilter(getColor(view.getContext(), attribute), PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
|
||||
static void setDrawableTint(Context context, Drawable drawable, @AttrRes int attribute) {
|
||||
public static void setDrawableTint(Context context, Drawable drawable, @AttrRes int attribute) {
|
||||
drawable.setColorFilter(getColor(context, attribute), PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
|
||||
import com.keylesspalace.tusky.adapter.TimelineAdapter;
|
||||
import com.keylesspalace.tusky.fragment.TimelineFragment;
|
||||
|
||||
public class TimelineReceiver extends BroadcastReceiver {
|
||||
public static final class Types {
|
||||
public static final String UNFOLLOW_ACCOUNT = "UNFOLLOW_ACCOUNT";
|
||||
public static final String BLOCK_ACCOUNT = "BLOCK_ACCOUNT";
|
||||
public static final String MUTE_ACCOUNT = "MUTE_ACCOUNT";
|
||||
}
|
||||
|
||||
TimelineAdapter adapter;
|
||||
|
||||
public TimelineReceiver(TimelineAdapter adapter) {
|
||||
super();
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, final Intent intent) {
|
||||
String id = intent.getStringExtra("id");
|
||||
adapter.removeAllByAccountId(id);
|
||||
}
|
||||
|
||||
public static IntentFilter getFilter(TimelineFragment.Kind kind) {
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
if (kind == TimelineFragment.Kind.HOME) {
|
||||
intentFilter.addAction(Types.UNFOLLOW_ACCOUNT);
|
||||
}
|
||||
intentFilter.addAction(Types.BLOCK_ACCOUNT);
|
||||
intentFilter.addAction(Types.MUTE_ACCOUNT);
|
||||
|
||||
return intentFilter;
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<bitmap
|
||||
android:src="@drawable/splash_pattern"
|
||||
android:tileMode="repeat" />
|
||||
</item>
|
||||
<item>
|
||||
<bitmap
|
||||
android:src="@mipmap/ic_logo"
|
||||
android:gravity="center"
|
||||
android:tileMode="disabled" />
|
||||
</item>
|
||||
</layer-list>
|
Binary file not shown.
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 40 KiB |
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/>
|
||||
</vector>
|
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
|
@ -0,0 +1,92 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.keylesspalace.tusky.AboutActivity">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/toolbar_background_color"
|
||||
android:elevation="4dp" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<com.mikhaellopez.circularfillableloaders.CircularFillableLoaders
|
||||
android:id="@+id/circularFillableLoaders"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:src="@mipmap/ic_logo"
|
||||
app:cfl_border="true"
|
||||
app:cfl_border_width="4dp"
|
||||
app:cfl_progress="80"
|
||||
app:cfl_wave_amplitude="0.08"
|
||||
app:cfl_wave_color="?attr/splash_wave_color" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/versionTV"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
android:textIsSelectable="true"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/projectURL_TV"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoLink="web"
|
||||
android:textIsSelectable="true"
|
||||
android:padding="@dimen/text_content_margin"
|
||||
android:text="@string/about_project_site"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/featuresURL_TV"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoLink="web"
|
||||
android:textIsSelectable="true"
|
||||
android:padding="@dimen/text_content_margin"
|
||||
android:text="@string/about_bug_feature_request_site"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/tusky_profile_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:padding="@dimen/text_content_margin"
|
||||
android:text="@string/about_tusky_account"
|
||||
android:textAlignment="center"
|
||||
android:textAllCaps="false"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
android:textColor="@android:color/white" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
|
@ -1,26 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/activity_account"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/activity_account"
|
||||
android:fitsSystemWindows="true">
|
||||
android:fitsSystemWindows="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:id="@+id/account_app_bar_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.Account.AppBarLayout"
|
||||
android:id="@+id/account_app_bar_layout">
|
||||
android:theme="@style/AppTheme.Account.AppBarLayout">
|
||||
|
||||
<android.support.design.widget.CollapsingToolbarLayout
|
||||
android:id="@+id/collapsing_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed"
|
||||
app:contentScrim="?attr/toolbar_background_color"
|
||||
android:fitsSystemWindows="true"
|
||||
app:contentScrim="?attr/toolbar_background_color"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed"
|
||||
app:titleEnabled="false">
|
||||
|
||||
<RelativeLayout
|
||||
|
@ -29,93 +28,96 @@
|
|||
android:background="?attr/account_header_background_color">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/account_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/account_header"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_collapseMode="pin"
|
||||
android:fitsSystemWindows="true"
|
||||
android:layout_alignBottom="@+id/account_header_info"
|
||||
android:layout_alignTop="@+id/account_header_info"
|
||||
android:layout_alignBottom="@id/account_header_info"
|
||||
android:contentDescription="@null" />
|
||||
android:background="@drawable/account_header_default"
|
||||
android:contentDescription="@null"
|
||||
android:fitsSystemWindows="true"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_collapseMode="pin" />
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/account_header_info"
|
||||
android:paddingTop="?attr/actionBarSize"
|
||||
android:background="@drawable/account_header_gradient"
|
||||
android:layout_width="match_parent"
|
||||
app:layout_collapseMode="parallax"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/account_header_gradient"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="?attr/actionBarSize"
|
||||
app:layout_collapseMode="parallax">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:layout_height="wrap_content">
|
||||
android:paddingTop="16dp">
|
||||
|
||||
<com.pkmmte.view.CircularImageView
|
||||
android:id="@+id/account_avatar"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:layout_marginRight="10dp"
|
||||
app:shadow="true"
|
||||
android:id="@+id/account_avatar" />
|
||||
android:src="@drawable/avatar_default"
|
||||
app:shadow="true" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toRightOf="@id/account_avatar"
|
||||
android:layout_alignParentRight="true">
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/account_display_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/account_display_name"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:textStyle="normal|bold"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="18sp" />
|
||||
android:textSize="18sp"
|
||||
android:textStyle="normal|bold" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/account_username"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:id="@+id/account_username" />
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorSecondary" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/account_locked"
|
||||
android:visibility="gone"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_width="16sp"
|
||||
android:layout_height="16sp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginLeft="4dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_toEndOf="@id/account_username"
|
||||
app:srcCompat="@drawable/reblog_disabled_light"
|
||||
android:tint="?android:textColorSecondary"
|
||||
android:layout_toRightOf="@id/account_username"
|
||||
android:contentDescription="@string/description_account_locked" />
|
||||
android:contentDescription="@string/description_account_locked"
|
||||
android:tint="?android:textColorSecondary"
|
||||
android:visibility="gone"
|
||||
app:srcCompat="@drawable/reblog_disabled_light" />
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/account_note"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/account_note"
|
||||
android:textColor="?android:textColorTertiary"
|
||||
android:padding="16dp"
|
||||
android:paddingTop="10dp" />
|
||||
android:paddingTop="10dp"
|
||||
android:textColor="?android:textColorTertiary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -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.support.design.widget.AppBarLayout>
|
||||
|
||||
<android.support.v4.view.ViewPager
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/pager"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<android.support.design.widget.TabLayout
|
||||
android:id="@+id/tab_layout"
|
||||
app:tabBackground="?android:colorBackground"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
app:tabBackground="?android:colorBackground">
|
||||
|
||||
<android.support.design.widget.TabItem
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -172,18 +174,18 @@
|
|||
android:id="@+id/tab_bottom_shadow"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
app:layout_anchor="@id/tab_layout"
|
||||
app:layout_anchorGravity="bottom"
|
||||
android:background="@drawable/material_drawer_shadow_bottom"
|
||||
android:visibility="visible" />
|
||||
android:visibility="visible"
|
||||
app:layout_anchor="@id/tab_layout"
|
||||
app:layout_anchorGravity="bottom" />
|
||||
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
android:id="@+id/floating_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:srcCompat="@drawable/ic_person_add_24dp"
|
||||
android:contentDescription="@string/action_follow" />
|
||||
android:contentDescription="@string/action_follow"
|
||||
app:srcCompat="@drawable/ic_person_add_24dp" />
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<com.keylesspalace.tusky.EditTextTyped
|
||||
<com.keylesspalace.tusky.util.EditTextTyped
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/compose_edit_field"
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp">
|
||||
|
||||
<EditText
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/edit_profile_display_name"
|
||||
|
@ -103,7 +103,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp">
|
||||
|
||||
<EditText
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/edit_profile_note"
|
||||
|
|
|
@ -14,45 +14,69 @@
|
|||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="147dp"
|
||||
android:layout_height="160dp"
|
||||
android:layout_marginBottom="50dp"
|
||||
android:src="@drawable/elephant_friend"
|
||||
android:contentDescription="@null" />
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
<LinearLayout
|
||||
android:id="@+id/login_input"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="250dp">
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical">
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textUri"
|
||||
android:hint="@string/hint_domain"
|
||||
android:ems="10"
|
||||
android:id="@+id/edit_text_domain" />
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
android:layout_width="250dp">
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textUri"
|
||||
android:hint="@string/hint_domain"
|
||||
android:ems="10"
|
||||
android:id="@+id/edit_text_domain" />
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_login"
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:text="@string/action_login" />
|
||||
<Button
|
||||
android:id="@+id/button_login"
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:text="@string/action_login" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="wrap_content"
|
||||
<TextView
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:id="@+id/text_error" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="5dp"
|
||||
android:textAlignment="center"
|
||||
android:id="@+id/whats_an_instance"
|
||||
android:text="@string/link_whats_an_instance" />
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/login_loading"
|
||||
android:visibility="gone"
|
||||
android:id="@+id/text_error" />
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
<ProgressBar
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
<TextView
|
||||
android:paddingTop="10dp"
|
||||
android:textAlignment="center"
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_connection"/>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="5dp"
|
||||
android:textAlignment="center"
|
||||
android:id="@+id/whats_an_instance"
|
||||
android:text="@string/link_whats_an_instance" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
|
@ -1,19 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:background="?attr/splash_background_color"
|
||||
android:layout_height="match_parent">
|
||||
<com.mikhaellopez.circularfillableloaders.CircularFillableLoaders
|
||||
android:id="@+id/circularFillableLoaders"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:src="@mipmap/ic_logo"
|
||||
app:cfl_border="true"
|
||||
app:cfl_border_width="4dp"
|
||||
app:cfl_progress="80"
|
||||
app:cfl_wave_amplitude="0.08"
|
||||
app:cfl_wave_color="?attr/splash_wave_color" />
|
||||
</LinearLayout>
|
|
@ -14,6 +14,11 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_centerInParent="true" />
|
||||
<ProgressBar
|
||||
android:id="@+id/video_progress"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -15,48 +15,40 @@
|
|||
|
||||
<RadioButton
|
||||
android:text="@string/visibility_public"
|
||||
android:button="@drawable/ic_public_24dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/radio_public"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:paddingLeft="10dp"
|
||||
android:buttonTint="@color/drawer_visibility_panel_item"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<RadioButton
|
||||
android:text="@string/visibility_unlisted"
|
||||
android:button="@drawable/ic_lock_open_24dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/radio_unlisted"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:paddingLeft="10dp"
|
||||
android:buttonTint="@color/drawer_visibility_panel_item"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<RadioButton
|
||||
android:text="@string/visibility_private"
|
||||
android:button="@drawable/ic_lock_outline_24dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/radio_private"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:paddingLeft="10dp"
|
||||
android:buttonTint="@color/drawer_visibility_panel_item"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<RadioButton
|
||||
android:text="@string/visibility_direct"
|
||||
android:button="@drawable/ic_email_24dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/radio_direct"
|
||||
android:layout_marginTop="5dp"
|
||||
android:paddingLeft="10dp"
|
||||
android:buttonTint="@color/drawer_visibility_panel_item"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</RadioGroup>
|
||||
|
|
|
@ -1,12 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="true"
|
||||
android:layout_gravity="center"
|
||||
android:background="@android:color/black">
|
||||
<uk.co.senab.photoview.PhotoView
|
||||
android:background="@android:color/black"
|
||||
android:clickable="true">
|
||||
|
||||
<com.github.chrisbanes.photoview.PhotoView
|
||||
android:id="@+id/view_media_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</FrameLayout>
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/toolbar_view_media"
|
||||
app:navigationIcon="?attr/homeAsUpIndicator" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/view_media_progress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
</RelativeLayout>
|
|
@ -95,7 +95,7 @@
|
|||
|
||||
</RelativeLayout>
|
||||
|
||||
<com.keylesspalace.tusky.FlowLayout
|
||||
<com.keylesspalace.tusky.util.FlowLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/status_content_warning_bar"
|
||||
|
@ -127,7 +127,7 @@
|
|||
android:textAllCaps="true"
|
||||
android:background="?attr/content_warning_button" />
|
||||
|
||||
</com.keylesspalace.tusky.FlowLayout>
|
||||
</com.keylesspalace.tusky.util.FlowLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_content"
|
||||
|
|
|
@ -2,7 +2,16 @@
|
|||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/status_share"
|
||||
android:title="@string/action_share"/>
|
||||
android:title="@string/action_share">
|
||||
<menu>
|
||||
<item
|
||||
android:id="@+id/status_share_link"
|
||||
android:title="@string/status_share_link" />
|
||||
<item
|
||||
android:id="@+id/status_share_content"
|
||||
android:title="@string/status_share_content"/>
|
||||
</menu>
|
||||
</item>
|
||||
<item android:title="@string/action_delete"
|
||||
android:id="@+id/status_delete" />
|
||||
</menu>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue