Merge pull request #2 from Vavassor/master
Catching up with Vavassor/Tusky
46
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
1. Fork the repository on the Github page by clicking the Fork button. This makes a fork of the project under your Github account.
|
||||||
|
2. Clone your fork to your machine. ```git clone https://github.com/<Your_Username>/Tusky```
|
||||||
|
3. Create a new branch named after your change. ```git checkout -b your-change-name``` (```checkout``` switches to a branch, ```-b``` specifies that the branch is a new one)
|
||||||
|
|
||||||
|
## Making Changes
|
||||||
|
|
||||||
|
### Text
|
||||||
|
All english text that will be visible to users should be put in ```app/src/main/res/values/strings.xml```. Any text that is missing in a translation will fall back to the version in this file. Be aware that anything added to this file will need to be translated, so be very concise with wording and try to add as few things as possible. Look for existing strings to use first. If there is untranslatable text that you don't want to keep as a string constant in a java class, you can use the string resource file ```app/src/main/res/values/donottranslate.xml```.
|
||||||
|
|
||||||
|
### Translation
|
||||||
|
Each translation has a single file that contains all of the text. A given locale's file can be found at ```app/src/main/res/values-<language code>[_<country code>]/strings.xml```. So, it could be ```values-en_US``` or ```values-es_ES```, for example. Specifically, they're the [two-letter ISO 639-1 language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) and the optional [ISO 3166-1 alpha-2 country code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2), which is used for a dialect of that particular country.
|
||||||
|
|
||||||
|
If you're starting a translation that doesn't already exist, you can just copy the english ```strings.xml``` to a new ```values``` directory and replace the english text inside each of the ```<string>``` ```</string>``` pairs.
|
||||||
|
|
||||||
|
Strings follow XML rules, which means that apostrophes and quotation marks have to be "escaped" with a backslash like: ```shouldn\'t``` and ```\"formidable\"```. Also, formatting is ignored when shown in the application, so things like new lines have to be explicitly expressed with codes like ```\n``` for a new line. See also: [String Resources](https://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling).
|
||||||
|
|
||||||
|
Please keep the organization and ordering of each of the strings the same as in the default ```strings.xml``` file. It just helps to keep so many translation files straight and up-to-date.
|
||||||
|
|
||||||
|
There are no icons or other resources needing localization, so it's just the text.
|
||||||
|
|
||||||
|
### Java
|
||||||
|
For java, I generally follow this [Android Style Guide](https://source.android.com/source/code-style), which is what Android uses for their own source code. I encourage the use of optional annotations like ```@Nullable``` and ```@NotNull```. Also, if you ever make helper functions that take Android resources, annotations like ```@StringRes```, ```@DrawableRes```, and ```@AttrRes``` are helpful. They can prevent small errors, like accidentally passing an attribute id to a function that takes a drawable id, for example (both are ints).
|
||||||
|
|
||||||
|
### Visuals
|
||||||
|
There are two themes in the app, so any visual changes should be checked with both themes to ensure they look appropriate for both. Usually, you can use existing color attributes like ```?attr/colorPrimary``` and ```?attr/textColorSecondary```. For icons and drawables, use a white drawable and tint it at runtime using ```ThemeUtils``` and specify an attribute that references different colours depending on the theme. Do not reference attributes in drawable files, because it is only supported in API levels 21+.
|
||||||
|
|
||||||
|
### Saving
|
||||||
|
Any time you get a good chunk of work done it's good to make a commit. You can either uses Android Studio's built-in UI for doing this or running the commands:
|
||||||
|
```
|
||||||
|
git add .
|
||||||
|
git commit -m "Describe the changes in this commit here."
|
||||||
|
```
|
||||||
|
|
||||||
|
## Submitting Your Changes
|
||||||
|
1. Make sure your branch is up-to-date with the ```master``` branch. Run:
|
||||||
|
```
|
||||||
|
git fetch
|
||||||
|
git rebase origin/master
|
||||||
|
```
|
||||||
|
It may refuse to start the rebase if there's changes that haven't been committed, so make sure you've added and committed everything. If there were changes on master to any of the parts of files you worked on, a conflict will arise when you rebase. [Resolving a merge conflict](https://help.github.com/articles/resolving-a-merge-conflict-using-the-command-line) is a good guide to help with this. After committing the resolution, you can run ```git rebase --continue``` to finish the rebase. If you want to cancel, like if you make some mistake in resolving the conflict, you can always do ```git rebase --abort```.
|
||||||
|
|
||||||
|
2. Push your local branch to your fork on Github by running ```git push origin your-change-name```.
|
||||||
|
3. Then, go to the original project page and make a pull request. Select your fork/branch and use ```master``` as the base branch.
|
|
@ -1,6 +1,6 @@
|
||||||
# Tusky
|
# Tusky
|
||||||
|
|
||||||
![](https://lh3.googleusercontent.com/6Ctl3PXaQi19qMaipWwzHAoKS9M9zy328cuulNZNAmRbjsPkSXs2xJ2OcyQNpOy23hI=w100)
|
![](app/src/main/res/drawable/tusky_logo.png)
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,14 @@ android {
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary true
|
vectorDrawables.useSupportLibrary true
|
||||||
}
|
}
|
||||||
|
productFlavors {
|
||||||
|
google {
|
||||||
|
buildConfigField "boolean", "USES_PUSH_NOTIFICATIONS", "true"
|
||||||
|
}
|
||||||
|
fdroid {
|
||||||
|
buildConfigField "boolean", "USES_PUSH_NOTIFICATIONS", "false"
|
||||||
|
}
|
||||||
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
|
@ -38,22 +46,23 @@ dependencies {
|
||||||
compile 'com.android.support:support-v13:25.3.1'
|
compile 'com.android.support:support-v13:25.3.1'
|
||||||
compile 'com.android.support:design:25.3.1'
|
compile 'com.android.support:design:25.3.1'
|
||||||
compile 'com.android.support:exifinterface: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.picasso:picasso:2.5.2'
|
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.pkmmte.view:circularimageview:1.1'
|
||||||
compile 'com.github.peter9870:sparkbutton:master'
|
compile 'com.github.peter9870:sparkbutton:master'
|
||||||
compile 'com.mikhaellopez:circularfillableloaders:1.2.0'
|
compile 'com.mikhaellopez:circularfillableloaders:1.2.0'
|
||||||
compile 'com.squareup.retrofit2:retrofit:2.2.0'
|
|
||||||
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
|
|
||||||
compile 'com.github.chrisbanes:PhotoView:1.3.1'
|
compile 'com.github.chrisbanes:PhotoView:1.3.1'
|
||||||
compile 'com.mikepenz:google-material-typeface:3.0.1.0.original@aar'
|
compile 'com.mikepenz:google-material-typeface:3.0.1.0.original@aar'
|
||||||
compile 'com.github.arimorty:floatingsearchview:2.0.3'
|
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'
|
compile 'com.jakewharton:butterknife:8.4.0'
|
||||||
compile 'com.google.firebase:firebase-messaging:10.0.1'
|
googleCompile 'com.google.firebase:firebase-messaging:10.0.1'
|
||||||
compile 'com.google.firebase:firebase-crash:10.0.1'
|
googleCompile 'com.google.firebase:firebase-crash:10.0.1'
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
|
annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
apply plugin: 'com.google.gms.google-services'
|
apply plugin: 'com.google.gms.google-services'
|
12
app/src/fdroid/AndroidManifest.xml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?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>
|
|
@ -0,0 +1,132 @@
|
||||||
|
/* 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();
|
||||||
|
}
|
||||||
|
}
|
18
app/src/google/AndroidManifest.xml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,121 @@
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,7 +33,7 @@ import retrofit2.Response;
|
||||||
import retrofit2.Retrofit;
|
import retrofit2.Retrofit;
|
||||||
|
|
||||||
public class MyFirebaseInstanceIdService extends FirebaseInstanceIdService {
|
public class MyFirebaseInstanceIdService extends FirebaseInstanceIdService {
|
||||||
private static final String TAG = "MyFirebaseInstanceIdService";
|
private static final String TAG = "com.keylesspalace.tusky.MyFirebaseInstanceIdService";
|
||||||
|
|
||||||
private TuskyAPI tuskyAPI;
|
private TuskyAPI tuskyAPI;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<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" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
@ -12,8 +13,9 @@
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme"
|
||||||
<meta-data android:name="firebase_analytics_collection_enabled" android:value="false" />
|
android:name=".TuskyApplication">
|
||||||
|
|
||||||
<activity android:name=".SplashActivity">
|
<activity android:name=".SplashActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
@ -59,24 +61,16 @@
|
||||||
<activity android:name=".ViewThreadActivity" />
|
<activity android:name=".ViewThreadActivity" />
|
||||||
<activity android:name=".ViewTagActivity" />
|
<activity android:name=".ViewTagActivity" />
|
||||||
<activity android:name=".AccountActivity" />
|
<activity android:name=".AccountActivity" />
|
||||||
|
<activity android:name=".EditProfileActivity" />
|
||||||
<activity android:name=".PreferencesActivity" />
|
<activity android:name=".PreferencesActivity" />
|
||||||
<activity android:name=".FavouritesActivity" />
|
<activity android:name=".FavouritesActivity" />
|
||||||
<activity android:name=".BlocksActivity" />
|
<activity android:name=".AccountListActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ReportActivity"
|
android:name=".ReportActivity"
|
||||||
android:windowSoftInputMode="stateVisible|adjustResize" />
|
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||||
|
<activity
|
||||||
<service android:name=".MyFirebaseInstanceIdService" android:exported="true">
|
android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
|
||||||
<intent-filter>
|
android:theme="@style/Base.Theme.AppCompat" />
|
||||||
<action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
|
|
||||||
</intent-filter>
|
|
||||||
</service>
|
|
||||||
|
|
||||||
<service android:name=".MyFirebaseMessagingService" android:exported="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
|
|
||||||
</intent-filter>
|
|
||||||
</service>
|
|
||||||
|
|
||||||
<receiver android:name=".NotificationClearBroadcastReceiver" />
|
<receiver android:name=".NotificationClearBroadcastReceiver" />
|
||||||
|
|
||||||
|
@ -87,9 +81,19 @@
|
||||||
android:label="Compose Toot"
|
android:label="Compose Toot"
|
||||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.service.quicksettings.action.QS_TILE"/>
|
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="android.support.v4.content.FileProvider"
|
||||||
|
android:authorities="com.keylesspalace.tusky.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths" />
|
||||||
|
</provider>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 48 KiB |
112
app/src/main/ic_launcher.svg
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
<svg width="512" height="512" version="1.1" viewBox="0 0 135.46666 135.46666" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="c">
|
||||||
|
<stop offset="0"/>
|
||||||
|
<stop stop-opacity="0" offset="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<clipPath id="g">
|
||||||
|
<circle cx="125" cy="125" r="112.52" fill="#2588d0"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="e">
|
||||||
|
<g transform="matrix(.26458 0 0 .26458 7.7244 -29.561)" clip-path="url(#g)" fill="url(#a)" opacity=".05">
|
||||||
|
<g stroke-width=".5">
|
||||||
|
<path d="m98.535 73.473c-1.8371-0.09054-2.2298 0.10241-1.635 0.69722l70.711 70.711c-0.59481-0.59481-0.2021-0.78775 1.635-0.69722z" fill="url(#a)"/>
|
||||||
|
<path d="m96.9 74.17c0.70818 0.70818 2.8162 1.986 5.553 4.0332l70.711 70.711c-2.7368-2.0472-4.8448-3.3251-5.553-4.0332z" fill="url(#a)"/>
|
||||||
|
<path d="m102.45 78.203c2.4807 1.8557 4.9237 4.0232 7.1498 6.2492l70.711 70.711c-2.226-2.226-4.6691-4.3935-7.1498-6.2492z" fill="url(#a)"/>
|
||||||
|
<path d="m109.6 84.452c3.2825 3.2825 6.093 6.6922 7.8561 9.4168l70.711 70.711c-1.7631-2.7246-4.5736-6.1343-7.8561-9.4168z" fill="url(#a)"/>
|
||||||
|
<path d="m117.46 93.869c2.9912 4.6225 5.6692 8.3037 12.262 28.254l70.711 70.711c-6.5925-19.95-9.2705-23.631-12.262-28.254z" fill="url(#a)"/>
|
||||||
|
<path d="m129.72 122.12c2.4794 7.5032 5.7538 14.14 6.6211 16.113l70.711 70.711c-0.86733-1.9736-4.1417-8.6101-6.6211-16.113z" fill="url(#a)"/>
|
||||||
|
<path d="m136.34 138.24 1.3476 3.0059 70.711 70.711-1.3476-3.0059z" fill="url(#a)"/>
|
||||||
|
<path d="m137.69 141.24c-0.0653 0.0814-0.11927 0.14322-0.18554 0.22656l70.711 70.711c0.0663-0.0833 0.12024-0.14516 0.18554-0.22656z" fill="url(#a)"/>
|
||||||
|
<path d="m137.5 141.47-3.4395 3.0098 70.711 70.711 3.4395-3.0098z" fill="url(#a)"/>
|
||||||
|
<path d="m134.06 144.48c-5.5481 5.7637-16.033 11.691-24.285 13.729l70.711 70.711c8.2524-2.0372 18.737-7.9648 24.285-13.729z" fill="url(#a)"/>
|
||||||
|
<path d="m109.78 158.21c-6.3614 1.5706-21.301 1.1651-27.809 0.0137l70.711 70.711c6.5077 1.1514 21.447 1.5569 27.809-0.0137z" fill="url(#a)"/>
|
||||||
|
<path d="m81.971 158.22c-7.8527-1.3894-14.205-5.074-19.134-10.002l70.711 70.711c4.9282 4.9282 11.281 8.6128 19.134 10.002z" fill="url(#a)"/>
|
||||||
|
<path d="m62.837 148.22c-6.1069-6.1069-10.026-14.123-11.902-22.049l70.711 70.711c1.8752 7.9251 5.7946 15.942 11.902 22.049z" fill="url(#a)"/>
|
||||||
|
<path d="m50.936 126.17c-1.5987-9.5426 0.52116-16.959 1.6895-21.707l70.711 70.711c-1.1683 4.7477-3.2882 12.164-1.6894 21.707z" fill="url(#a)"/>
|
||||||
|
<path d="m52.625 104.46c2.6637-10.825 9.7356-20.465 18.479-26.402l70.711 70.711c-8.7429 5.9372-15.815 15.578-18.479 26.402z" fill="url(#a)"/>
|
||||||
|
<path d="m71.104 78.061c3.0009-2.0378 4.6792-2.7066 4.4597-2.9261l70.711 70.711c0.21947 0.21947-1.4588 0.88834-4.4597 2.9261z" fill="url(#a)"/>
|
||||||
|
<path d="m75.563 75.134c-0.11026-0.11026-0.69947-0.10709-1.8406-0.10709l70.711 70.711c1.1411 0 1.7303-3e-3 1.8406 0.10709z" fill="url(#a)"/>
|
||||||
|
<path d="m73.723 75.027c-3.9668 0-10.077 2.6389-14.275 6.0215l70.711 70.711c4.1987-3.3826 10.309-6.0215 14.275-6.0215z" fill="url(#a)"/>
|
||||||
|
<path d="m59.447 81.049c-8.0199 6.461-14.768 15.598-18.961 26.336l70.711 70.711c4.1931-10.738 10.941-19.875 18.961-26.336z" fill="url(#a)"/>
|
||||||
|
<path d="m40.486 107.38c-2.2433 6.6848-2.2747 5.5109-2.418 15.162l70.711 70.711c0.14324-9.6512 0.17463-8.4773 2.418-15.162z" fill="url(#a)"/>
|
||||||
|
<path d="m38.068 122.55c0.37633 6.9829 1.0389 9.3071 3.0723 15.406l70.711 70.711c-2.0333-6.0991-2.6959-8.4234-3.0723-15.406z" fill="url(#a)"/>
|
||||||
|
<path d="m41.141 137.95c2.3472 7.0404 6.2768 13.161 11.343 18.227l70.711 70.711c-5.0663-5.0663-8.9959-11.187-11.343-18.227z" fill="url(#a)"/>
|
||||||
|
<path d="m52.484 156.18c2.9744 2.9744 6.3406 5.5855 10.008 7.806l70.711 70.711c-3.6678-2.2205-7.034-4.8316-10.008-7.806z" fill="url(#a)"/>
|
||||||
|
<path d="m62.492 163.99c4.9985 3.0261 15.324 6.7023 22.098 7.8027l70.711 70.711c-6.7734-1.1005-17.099-4.7766-22.098-7.8027z" fill="url(#a)"/>
|
||||||
|
<path d="m84.59 171.79c1.8904 0.30967 4.992 0.90865 7.8848 1.1875l70.711 70.711c-2.8928-0.27885-5.9944-0.87783-7.8848-1.1875z" fill="url(#a)"/>
|
||||||
|
<path d="m92.475 172.98c12.195 0.94625 15.055-0.32666 24.506-1.8418l70.711 70.711c-9.4508 1.5151-12.311 2.788-24.506 1.8418z" fill="url(#a)"/>
|
||||||
|
<path d="m116.98 171.13c8.5906-2.4983 16.678-7.275 22.934-12.367l70.711 70.711c-6.2561 5.0922-14.343 9.8689-22.934 12.367z" fill="url(#a)"/>
|
||||||
|
<path d="m139.91 158.77 5.7871-4.7168 70.711 70.711-5.7871 4.7168z" fill="url(#a)"/>
|
||||||
|
<path d="m145.7 154.05c0.0891 0.11275 0.1968 0.26762 0.28321 0.375l70.711 70.711c-0.0864-0.10738-0.19411-0.26225-0.28321-0.375z" fill="url(#a)"/>
|
||||||
|
<path d="m145.98 154.43 1.9648 2.0644 70.711 70.711-1.9648-2.0644z" fill="url(#a)"/>
|
||||||
|
<path d="m147.95 156.49c0.29948 0.31455 0.59902 0.62179 0.89884 0.92161l70.711 70.711c-0.29982-0.29982-0.59936-0.60705-0.89884-0.9216z" fill="url(#a)"/>
|
||||||
|
<path d="m148.85 157.41c5.8411 5.8411 11.787 8.8672 19.377 8.303l70.711 70.711c-7.5898 0.56423-13.535-2.4619-19.377-8.303z" fill="url(#a)"/>
|
||||||
|
<path d="m168.22 165.71c7.5551-0.56163 13.041-2.6761 17.49-6.7422l70.711 70.711c-4.4488 4.066-9.9351 6.1806-17.49 6.7422z" fill="url(#a)"/>
|
||||||
|
<path d="m185.71 158.97c4.5132-4.1248 5.5354-6.236 7.6699-12.512l70.711 70.711c-2.1345 6.2758-3.1567 8.3869-7.6699 12.512z" fill="url(#a)"/>
|
||||||
|
<path d="m193.38 146.46c2.9552-8.6889 4.3184-16.193 3.9922-29.721l70.711 70.711c0.32618 13.527-1.0369 21.032-3.9922 29.721z" fill="url(#a)"/>
|
||||||
|
<path d="m197.38 116.74c-0.33294-13.818-1.6567-19.14-5.4434-23.453l70.711 70.711c3.7867 4.3128 5.1104 9.6355 5.4434 23.453z" fill="url(#a)"/>
|
||||||
|
<path d="m191.93 93.287c-0.0959-0.10288-0.19218-0.20269-0.28888-0.2994l70.711 70.711c0.0967 0.0967 0.19301 0.19652 0.28888 0.2994z" fill="url(#a)"/>
|
||||||
|
<path d="m191.64 92.988c-3.0781-3.0781-6.553-3.0044-8.5724 1.8287l70.711 70.711c2.0194-4.8331 5.4943-4.9068 8.5724-1.8287z" fill="url(#a)"/>
|
||||||
|
<path d="m183.07 94.816c-0.79737 1.9089-0.78178 2.91 0.11132 6.918l70.711 70.711c-0.8931-4.008-0.90869-5.0091-0.11132-6.918z" fill="url(#a)"/>
|
||||||
|
<path d="m183.18 101.73c1.6289 7.3102 1.7837 25.828 0.45313 32.072l70.711 70.711c1.3306-6.2445 1.1758-24.762-0.45313-32.072z" fill="url(#a)"/>
|
||||||
|
<path d="m183.64 133.81c-2.7904 13.095-11.463 17.859-22.207 12.49l70.711 70.711c10.744 5.3688 19.417 0.60451 22.207-12.49z" fill="url(#a)"/>
|
||||||
|
<path d="m161.43 146.3c-2.4274-1.213-3.8861-1.9048-5.748-3.5762l70.711 70.711c1.862 1.6714 3.3206 2.3632 5.748 3.5762z" fill="url(#a)"/>
|
||||||
|
<path d="m155.68 142.72c-0.18578-0.19582-0.32768-0.35348-0.50391-0.54101l70.711 70.711c0.17623 0.18753 0.31813 0.34519 0.50391 0.54101z" fill="url(#a)"/>
|
||||||
|
<path d="m155.18 142.18c1.047-1.7899 1.5781-3.0116 2.7754-4.9883l70.711 70.711c-1.1973 1.9767-1.7284 3.1984-2.7754 4.9883z" fill="url(#a)"/>
|
||||||
|
<path d="m157.95 137.19c4.9715-8.2077 9.7196-21.436 11.604-32.795l70.711 70.711c-1.8839 11.359-6.632 24.587-11.604 32.795z" fill="url(#a)"/>
|
||||||
|
<path d="m169.56 104.4c1.1273-6.797 0.81713-9.1729-0.83126-10.821l70.711 70.711c1.6484 1.6484 1.9586 4.0243 0.83126 10.821z" fill="url(#a)"/>
|
||||||
|
<path d="m168.73 93.575c-0.32731-0.32731-0.70738-0.62594-1.1394-0.92479l70.711 70.711c0.43206 0.29886 0.81213 0.59748 1.1394 0.92479z" fill="url(#a)"/>
|
||||||
|
<path d="m167.59 92.65c-2.0094-0.98322-2.8331-1.0491-4.9531-0.39844l70.711 70.711c2.12-0.65064 2.9438-0.58478 4.9531 0.39844z" fill="url(#a)"/>
|
||||||
|
<path d="m162.63 92.252c-1.3988 0.42933-3.0221 1.3082-3.6055 1.9531l70.711 70.711c0.58337-0.64492 2.2067-1.5238 3.6055-1.9531z" fill="url(#a)"/>
|
||||||
|
<path d="m159.03 94.205c-0.58363 0.64492-1.9734 4.6467-3.0879 8.8945l70.711 70.711c1.1145-4.2479 2.5043-8.2496 3.0879-8.8945z" fill="url(#a)"/>
|
||||||
|
<path d="m155.94 103.1c-1.1144 4.2479-2.7555 9.2858-3.6465 11.193l70.711 70.711c0.89102-1.9076 2.532-6.9455 3.6465-11.193z" fill="url(#a)"/>
|
||||||
|
<path d="m152.29 114.29c-0.89099 1.9076-1.8493 4.2376-2.1309 5.1777l70.711 70.711c0.28157-0.94013 1.2399-3.2702 2.1309-5.1777z" fill="url(#a)"/>
|
||||||
|
<path d="m150.16 119.47c-0.28161 0.94013-1.2853 3.2472-2.2305 5.127l70.711 70.711c0.94515-1.8798 1.9489-4.1868 2.2305-5.127z" fill="url(#a)"/>
|
||||||
|
<path d="m147.93 124.6-2 4.0059 70.711 70.711 2-4.0059z" fill="url(#a)"/>
|
||||||
|
<path d="m145.93 128.6-1.1016-2.5469 70.711 70.711 1.1016 2.5469z" fill="url(#a)"/>
|
||||||
|
<path d="m144.83 126.06c-0.50786-1.1277-2.6738-6.8816-5.1504-12.898l70.711 70.711c2.4766 6.0168 4.6425 11.771 5.1504 12.898z" fill="url(#a)"/>
|
||||||
|
<path d="m139.68 113.16c-5.7036-13.857-11.141-23.408-17.401-29.668l70.711 70.711c6.2598 6.2598 11.697 15.811 17.401 29.668z" fill="url(#a)"/>
|
||||||
|
<path d="m122.28 83.49c-6.0835-6.0835-12.944-9.058-21.58-9.8534l70.711 70.711c8.6358 0.79538 15.496 3.7699 21.58 9.8534z" fill="url(#a)"/>
|
||||||
|
<path d="m100.7 73.637c-0.8718-0.08029-1.5892-0.13573-2.1641-0.16406l70.711 70.711c0.57491 0.0283 1.2923 0.0838 2.1641 0.16407z" fill="url(#a)"/>
|
||||||
|
</g>
|
||||||
|
<path d="m98.535 73.473c-4.0243-0.19833-1.1175 0.96369 3.918 4.7305 6.1387 4.592 12.047 11.094 15.006 15.666 2.9912 4.6225 5.6692 8.3037 12.262 28.254 2.4794 7.5032 5.7538 14.14 6.6211 16.113l1.3476 3.0059c-0.0653 0.0814-0.11927 0.14322-0.18554 0.22656l-3.4395 3.0098c-5.5481 5.7637-16.033 11.691-24.285 13.729-6.3614 1.5706-21.301 1.1651-27.809 0.0137-17.584-3.1111-27.647-17.73-31.035-32.051-1.5987-9.5426 0.52116-16.959 1.6895-21.707 2.6637-10.825 9.7356-20.465 18.479-26.402 4.5085-3.0615 6.0316-3.0332 2.6191-3.0332-3.9668 0-10.077 2.6389-14.275 6.0215-8.0199 6.461-14.768 15.598-18.961 26.336-2.2433 6.6848-2.2747 5.5109-2.418 15.162 0.37633 6.9829 1.0389 9.3071 3.0723 15.406 3.7252 11.174 11.436 20.03 21.352 26.033 4.9985 3.0261 15.324 6.7023 22.098 7.8027 1.8904 0.30967 4.992 0.90865 7.8848 1.1875 12.195 0.94625 15.055-0.32666 24.506-1.8418 8.5906-2.4983 16.678-7.275 22.934-12.367l5.7871-4.7168c0.0891 0.11275 0.1968 0.26762 0.28321 0.375l1.9648 2.0644c6.134 6.4426 12.296 9.8178 20.275 9.2246 7.5551-0.56163 13.041-2.6761 17.49-6.7422 4.5132-4.1248 5.5354-6.236 7.6699-12.512 2.9552-8.6889 4.3184-16.193 3.9922-29.721-0.33294-13.818-1.6567-19.14-5.4434-23.453-3.1472-3.3774-6.7785-3.4556-8.8613 1.5293-0.79737 1.9089-0.78178 2.91 0.11132 6.918 1.6289 7.3102 1.7837 25.828 0.45313 32.072-2.7904 13.095-11.463 17.859-22.207 12.49-2.4274-1.213-3.8861-1.9048-5.748-3.5762-0.18578-0.19582-0.32768-0.35348-0.50391-0.54101 1.047-1.7899 1.5781-3.0116 2.7754-4.9883 4.9715-8.2077 9.7196-21.436 11.604-32.795 1.3511-8.1466 0.63728-9.9421-1.9707-11.746-2.0094-0.98322-2.8331-1.0491-4.9531-0.39844-1.3988 0.42933-3.0221 1.3082-3.6055 1.9531-0.58363 0.64492-1.9734 4.6467-3.0879 8.8945-1.1144 4.2479-2.7555 9.2858-3.6465 11.193-0.89099 1.9076-1.8493 4.2376-2.1309 5.1777-0.28161 0.94013-1.2853 3.2472-2.2305 5.127l-2 4.0059-1.1016-2.5469c-0.50786-1.1277-2.6738-6.8816-5.1504-12.898-11.247-27.323-21.459-37.908-38.98-39.522-0.8718-0.080295-1.5892-0.13573-2.1641-0.16406z" fill="url(#a)"/>
|
||||||
|
</g>
|
||||||
|
</clipPath>
|
||||||
|
<linearGradient id="a" x1="146.2" x2="201.21" y1="153.65" y2="210.2" gradientUnits="userSpaceOnUse" xlink:href="#c"/>
|
||||||
|
<clipPath id="f">
|
||||||
|
<circle transform="matrix(1.0037 0 0 1.0037 1.3836 -1.2671)" cx="40.292" cy="2.6396" r="29.772" clip-path="url(#e)" opacity=".1"/>
|
||||||
|
</clipPath>
|
||||||
|
<linearGradient id="b" x1="45.701" x2="61.309" y1="6.5707" y2="22.179" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0"/>
|
||||||
|
<stop stop-opacity="0" offset="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<filter id="d" x="-.024" y="-.024" width="1.048" height="1.048" color-interpolation-filters="sRGB">
|
||||||
|
<feGaussianBlur stdDeviation="13.532652"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<g transform="translate(-8.0628 99.804)">
|
||||||
|
<g transform="matrix(2 0 0 2 -5.3112 -36.986)">
|
||||||
|
<g transform="matrix(.046875 0 0 .046875 6.3858 34.613)">
|
||||||
|
<g>
|
||||||
|
<circle transform="matrix(.98504 0 0 .98504 29.403 -5.6005)" cx="710.14" cy="-672.09" r="676.63" filter="url(#d)" opacity=".36"/>
|
||||||
|
<ellipse cx="728.91" cy="-684.4" rx="663.22" ry="661.64" fill="#393f4f"/>
|
||||||
|
<ellipse cx="725.02" cy="-684.4" rx="597.02" ry="595.6" fill="#2588d0"/>
|
||||||
|
<circle transform="matrix(20.463 0 0 20.414 -95.572 -724.78)" cx="40.292" cy="1.9781" r="29.176" clip-path="url(#f)" fill="url(#b)" opacity=".1"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(21.768 0 0 21.716 -148.18 -741.68)" stroke-width=".052917">
|
||||||
|
<g stroke="#efefef">
|
||||||
|
<g stroke="#efefef" stroke-width=".052917">
|
||||||
|
<g transform="matrix(1.0012 0 0 1.0012 -.2806 -.13189)" stroke-width=".052853">
|
||||||
|
<path d="m33.997 14.956c-6.7505-0.65592-11.276-4.003-12.835-9.4932-0.97232-3.4236-0.40007-6.6554 1.7689-9.9897 1.963-3.0177 4.5842-4.9793 6.6538-4.9793h0.55721l-0.68124 0.45556c-2.3327 1.5599-4.1732 3.9523-5.0456 6.5585-0.51198 1.5295-0.64528 4.5724-0.27628 6.3067 0.63604 2.9894 2.4635 5.3876 5.0854 6.6734 1.7788 0.87238 3.409 1.2191 5.7324 1.2191 3.8422 0 6.9587-1.203 9.6137-3.711l0.90726-0.85704-0.45582-1.0092c-0.40414-0.89475-1.3238-3.3221-3.0074-7.9377-0.66266-1.8167-1.3902-3.0367-2.6217-4.3966-0.83567-0.92274-2.4857-2.3558-3.8598-3.3523-0.41933-0.3041-0.41152-0.30624 0.78481-0.21562 4.547 0.34444 6.8542 2.8969 10.192 11.276 0.58286 1.463 0.84878 2.0983 0.96426 2.3599 0.02953 0.0669 0.07669 0.21387 0.13247 0.16887 0.05579-0.044998 0.2425-0.39246 0.30776-0.50578 0.06527-0.11332 0.13756-0.25964 0.22146-0.43667 0.0839-0.17703 0.1794-0.38476 0.29108-0.62088 0.81832-1.7301 1.8142-4.5739 2.1755-6.2124 0.17685-0.80191 0.31139-1.0422 0.6944-1.2403 1.2546-0.64879 2.3739-0.046158 2.3739 1.2782 0 2.3978-1.562 7.2043-3.3553 10.325-0.11458 0.1994-0.2299 0.32992-0.28958 0.44825-0.05969 0.11833-0.22117 0.32942-0.18689 0.38982 0.0262 0.046164 0.03631 0.058028 0.07857 0.1003 0.04494 0.044952 0.10546 0.1025 0.18606 0.16729 0.59444 0.47791 2.1261 1.0701 2.9132 1.178 1.0012 0.13722 2.1429-0.34975 2.7808-1.186 1.4436-1.8927 1.7772-5.2915 1.0067-10.256-0.28139-1.8131-0.2804-1.8507 0.0612-2.3095 0.8275-1.1115 1.8285-0.9019 2.5288 0.52951 1.4339 2.9307 1.2081 10.094-0.43371 13.76-0.9119 2.0362-2.4949 3.1576-5.135 3.6376-2.1119 0.38401-3.6271-0.10683-5.3738-1.7407l-1.0573-0.98905-0.49724 0.52519c-1.2916 1.3642-4.2775 3.0339-6.4849 3.6264-1.0871 0.29178-3.7581 0.64007-4.619 0.60228-0.28227-0.01239-1.0906-0.07862-1.7963-0.14719z" fill="#fefefe" stroke="#e9e9eb"/>
|
||||||
|
<path d="m49.244 6.7482c-0.30781-0.38303-0.80659-1.1368-1.1084-1.6749l-0.54874-0.97853 0.47196-0.85537c0.25958-0.47046 0.64637-1.3655 0.89107-1.9821l0.39948-0.98883 1.3378 1.3563c0.8003 0.81132 1.266 1.4388 1.2301 1.6104-0.08034 0.3845-1.217 2.7582-1.713 3.561l-0.40066 0.6485z" fill="#e9e9eb" stroke="#efefef"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<path d="m45.251 7.0162s0.6208 1.4785 1.9195 3.2142" fill="none" stroke="#e9e9eb"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<style>.st0{fill:#e0e0e0}.st1{fill:#fff}.st2{clip-path:url(#SVGID_2_);fill:#fbbc05}.st3{clip-path:url(#SVGID_4_);fill:#ea4335}.st4{clip-path:url(#SVGID_6_);fill:#34a853}.st5{clip-path:url(#SVGID_8_);fill:#4285f4}</style>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 14 KiB |
|
@ -17,5 +17,7 @@ package com.keylesspalace.tusky;
|
||||||
|
|
||||||
interface AccountActionListener {
|
interface AccountActionListener {
|
||||||
void onViewAccount(String id);
|
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);
|
void onBlock(final boolean block, final String id, final int position);
|
||||||
|
void onRespondToFollowRequest(final boolean accept, final String id, final int position);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,11 +28,11 @@ import android.support.design.widget.CollapsingToolbarLayout;
|
||||||
import android.support.design.widget.FloatingActionButton;
|
import android.support.design.widget.FloatingActionButton;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
import android.support.design.widget.TabLayout;
|
import android.support.design.widget.TabLayout;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.view.ViewCompat;
|
import android.support.v4.view.ViewCompat;
|
||||||
import android.support.v4.view.ViewPager;
|
import android.support.v4.view.ViewPager;
|
||||||
import android.support.v7.app.ActionBar;
|
import android.support.v7.app.ActionBar;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.text.method.LinkMovementMethod;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
@ -54,7 +54,7 @@ import retrofit2.Call;
|
||||||
import retrofit2.Callback;
|
import retrofit2.Callback;
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
|
|
||||||
public class AccountActivity extends BaseActivity {
|
public class AccountActivity extends BaseActivity implements SFragment.OnUserRemovedListener {
|
||||||
private static final String TAG = "AccountActivity"; // logging tag
|
private static final String TAG = "AccountActivity"; // logging tag
|
||||||
|
|
||||||
private String accountId;
|
private String accountId;
|
||||||
|
@ -63,6 +63,7 @@ public class AccountActivity extends BaseActivity {
|
||||||
private boolean muting = false;
|
private boolean muting = false;
|
||||||
private boolean isSelf;
|
private boolean isSelf;
|
||||||
private TabLayout tabLayout;
|
private TabLayout tabLayout;
|
||||||
|
private AccountPagerAdapter pagerAdapter;
|
||||||
private Account loadedAccount;
|
private Account loadedAccount;
|
||||||
|
|
||||||
@BindView(R.id.account_locked) ImageView accountLockedView;
|
@BindView(R.id.account_locked) ImageView accountLockedView;
|
||||||
|
@ -80,8 +81,7 @@ public class AccountActivity extends BaseActivity {
|
||||||
accountId = intent.getStringExtra("id");
|
accountId = intent.getStringExtra("id");
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedPreferences preferences = getSharedPreferences(
|
SharedPreferences preferences = getPrivatePreferences();
|
||||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
|
||||||
String loggedInAccountId = preferences.getString("loggedInAccountId", null);
|
String loggedInAccountId = preferences.getString("loggedInAccountId", null);
|
||||||
|
|
||||||
final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||||
|
@ -142,6 +142,7 @@ public class AccountActivity extends BaseActivity {
|
||||||
// Setup the tabs and timeline pager.
|
// Setup the tabs and timeline pager.
|
||||||
AccountPagerAdapter adapter = new AccountPagerAdapter(getSupportFragmentManager(), this,
|
AccountPagerAdapter adapter = new AccountPagerAdapter(getSupportFragmentManager(), this,
|
||||||
accountId);
|
accountId);
|
||||||
|
pagerAdapter = adapter;
|
||||||
String[] pageTitles = {
|
String[] pageTitles = {
|
||||||
getString(R.string.title_statuses),
|
getString(R.string.title_statuses),
|
||||||
getString(R.string.title_follows),
|
getString(R.string.title_follows),
|
||||||
|
@ -165,6 +166,12 @@ public class AccountActivity extends BaseActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSaveInstanceState(Bundle outState) {
|
||||||
|
outState.putString("accountId", accountId);
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
}
|
||||||
|
|
||||||
private void obtainAccount() {
|
private void obtainAccount() {
|
||||||
mastodonAPI.account(accountId).enqueue(new Callback<Account>() {
|
mastodonAPI.account(accountId).enqueue(new Callback<Account>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -183,12 +190,6 @@ public class AccountActivity extends BaseActivity {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSaveInstanceState(Bundle outState) {
|
|
||||||
outState.putString("accountId", accountId);
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onObtainAccountSuccess(Account account) {
|
private void onObtainAccountSuccess(Account account) {
|
||||||
loadedAccount = account;
|
loadedAccount = account;
|
||||||
|
|
||||||
|
@ -204,9 +205,21 @@ public class AccountActivity extends BaseActivity {
|
||||||
|
|
||||||
displayName.setText(account.getDisplayName());
|
displayName.setText(account.getDisplayName());
|
||||||
|
|
||||||
note.setText(account.note);
|
LinkHelper.setClickableText(note, account.note, null, new LinkListener() {
|
||||||
note.setLinksClickable(true);
|
@Override
|
||||||
note.setMovementMethod(LinkMovementMethod.getInstance());
|
public void onViewTag(String tag) {
|
||||||
|
Intent intent = new Intent(AccountActivity.this, ViewTagActivity.class);
|
||||||
|
intent.putExtra("hashtag", tag);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewAccount(String id) {
|
||||||
|
Intent intent = new Intent(AccountActivity.this, AccountActivity.class);
|
||||||
|
intent.putExtra("id", id);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (account.locked) {
|
if (account.locked) {
|
||||||
accountLockedView.setVisibility(View.VISIBLE);
|
accountLockedView.setVisibility(View.VISIBLE);
|
||||||
|
@ -289,6 +302,16 @@ public class AccountActivity extends BaseActivity {
|
||||||
updateButtons();
|
updateButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUserRemoved(String accountId) {
|
||||||
|
for (Fragment fragment : pagerAdapter.getRegisteredFragments()) {
|
||||||
|
if (fragment instanceof StatusRemoveListener) {
|
||||||
|
StatusRemoveListener listener = (StatusRemoveListener) fragment;
|
||||||
|
listener.removePostsByUser(accountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void updateFollowButton(FloatingActionButton button) {
|
private void updateFollowButton(FloatingActionButton button) {
|
||||||
if (following) {
|
if (following) {
|
||||||
button.setImageResource(R.drawable.ic_person_minus_24px);
|
button.setImageResource(R.drawable.ic_person_minus_24px);
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
package com.keylesspalace.tusky;
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
|
||||||
import com.keylesspalace.tusky.entity.Account;
|
import com.keylesspalace.tusky.entity.Account;
|
||||||
|
@ -64,6 +65,24 @@ abstract class AccountAdapter extends RecyclerView.Adapter {
|
||||||
notifyItemRangeInserted(end, newAccounts.size());
|
notifyItemRangeInserted(end, newAccounts.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
Account removeItem(int position) {
|
||||||
|
if (position < 0 || position >= accountList.size()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Account account = accountList.remove(position);
|
||||||
|
notifyItemRemoved(position);
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addItem(Account account, int position) {
|
||||||
|
if (position < 0 || position > accountList.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
accountList.add(position, account);
|
||||||
|
notifyItemInserted(position);
|
||||||
|
}
|
||||||
|
|
||||||
public Account getItem(int position) {
|
public Account getItem(int position) {
|
||||||
if (position >= 0 && position < accountList.size()) {
|
if (position >= 0 && position < accountList.size()) {
|
||||||
return accountList.get(position);
|
return accountList.get(position);
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
package com.keylesspalace.tusky;
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
|
@ -23,23 +24,54 @@ import android.support.v7.app.ActionBar;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
public class BlocksActivity extends BaseActivity {
|
public class AccountListActivity extends BaseActivity {
|
||||||
|
enum Type {
|
||||||
|
BLOCKS,
|
||||||
|
MUTES,
|
||||||
|
FOLLOW_REQUESTS,
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_blocks);
|
setContentView(R.layout.activity_account_list);
|
||||||
|
|
||||||
|
Type type;
|
||||||
|
Intent intent = getIntent();
|
||||||
|
if (intent != null) {
|
||||||
|
type = (Type) intent.getSerializableExtra("type");
|
||||||
|
} else {
|
||||||
|
type = Type.BLOCKS;
|
||||||
|
}
|
||||||
|
|
||||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
ActionBar bar = getSupportActionBar();
|
ActionBar bar = getSupportActionBar();
|
||||||
if (bar != null) {
|
if (bar != null) {
|
||||||
bar.setTitle(getString(R.string.title_blocks));
|
switch (type) {
|
||||||
|
case BLOCKS: { bar.setTitle(getString(R.string.title_blocks)); break; }
|
||||||
|
case MUTES: { bar.setTitle(getString(R.string.title_mutes)); break; }
|
||||||
|
case FOLLOW_REQUESTS: {
|
||||||
|
bar.setTitle(getString(R.string.title_follow_requests));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
bar.setDisplayHomeAsUpEnabled(true);
|
bar.setDisplayHomeAsUpEnabled(true);
|
||||||
bar.setDisplayShowHomeEnabled(true);
|
bar.setDisplayShowHomeEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||||
Fragment fragment = AccountFragment.newInstance(AccountFragment.Type.BLOCKS);
|
AccountListFragment.Type fragmentType;
|
||||||
|
switch (type) {
|
||||||
|
default:
|
||||||
|
case BLOCKS: { fragmentType = AccountListFragment.Type.BLOCKS; break; }
|
||||||
|
case MUTES: { fragmentType = AccountListFragment.Type.MUTES; break; }
|
||||||
|
case FOLLOW_REQUESTS: {
|
||||||
|
fragmentType = AccountListFragment.Type.FOLLOW_REQUESTS;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Fragment fragment = AccountListFragment.newInstance(fragmentType);
|
||||||
fragmentTransaction.add(R.id.fragment_container, fragment);
|
fragmentTransaction.add(R.id.fragment_container, fragment);
|
||||||
fragmentTransaction.commit();
|
fragmentTransaction.commit();
|
||||||
}
|
}
|
|
@ -20,6 +20,7 @@ import android.content.Intent;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.design.widget.Snackbar;
|
||||||
import android.support.design.widget.TabLayout;
|
import android.support.design.widget.TabLayout;
|
||||||
import android.support.v7.widget.DividerItemDecoration;
|
import android.support.v7.widget.DividerItemDecoration;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
@ -37,16 +38,15 @@ import retrofit2.Call;
|
||||||
import retrofit2.Callback;
|
import retrofit2.Callback;
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
|
|
||||||
public class AccountFragment extends BaseFragment implements AccountActionListener {
|
public class AccountListFragment extends BaseFragment implements AccountActionListener {
|
||||||
private static final String TAG = "Account"; // logging tag
|
private static final String TAG = "AccountList"; // logging tag
|
||||||
|
|
||||||
private Call<List<Account>> listCall;
|
|
||||||
|
|
||||||
public enum Type {
|
public enum Type {
|
||||||
FOLLOWS,
|
FOLLOWS,
|
||||||
FOLLOWERS,
|
FOLLOWERS,
|
||||||
BLOCKS,
|
BLOCKS,
|
||||||
MUTES,
|
MUTES,
|
||||||
|
FOLLOW_REQUESTS,
|
||||||
}
|
}
|
||||||
|
|
||||||
private Type type;
|
private Type type;
|
||||||
|
@ -58,18 +58,18 @@ public class AccountFragment extends BaseFragment implements AccountActionListen
|
||||||
private TabLayout.OnTabSelectedListener onTabSelectedListener;
|
private TabLayout.OnTabSelectedListener onTabSelectedListener;
|
||||||
private MastodonAPI api;
|
private MastodonAPI api;
|
||||||
|
|
||||||
public static AccountFragment newInstance(Type type) {
|
public static AccountListFragment newInstance(Type type) {
|
||||||
Bundle arguments = new Bundle();
|
Bundle arguments = new Bundle();
|
||||||
AccountFragment fragment = new AccountFragment();
|
AccountListFragment fragment = new AccountListFragment();
|
||||||
arguments.putString("type", type.name());
|
arguments.putSerializable("type", type);
|
||||||
fragment.setArguments(arguments);
|
fragment.setArguments(arguments);
|
||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AccountFragment newInstance(Type type, String accountId) {
|
public static AccountListFragment newInstance(Type type, String accountId) {
|
||||||
Bundle arguments = new Bundle();
|
Bundle arguments = new Bundle();
|
||||||
AccountFragment fragment = new AccountFragment();
|
AccountListFragment fragment = new AccountListFragment();
|
||||||
arguments.putString("type", type.name());
|
arguments.putSerializable("type", type);
|
||||||
arguments.putString("accountId", accountId);
|
arguments.putString("accountId", accountId);
|
||||||
fragment.setArguments(arguments);
|
fragment.setArguments(arguments);
|
||||||
return fragment;
|
return fragment;
|
||||||
|
@ -79,7 +79,7 @@ public class AccountFragment extends BaseFragment implements AccountActionListen
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
Bundle arguments = getArguments();
|
Bundle arguments = getArguments();
|
||||||
type = Type.valueOf(arguments.getString("type"));
|
type = (Type) arguments.getSerializable("type");
|
||||||
accountId = arguments.getString("accountId");
|
accountId = arguments.getString("accountId");
|
||||||
api = null;
|
api = null;
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ public class AccountFragment extends BaseFragment implements AccountActionListen
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
|
||||||
@Nullable Bundle savedInstanceState) {
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
|
||||||
View rootView = inflater.inflate(R.layout.fragment_account, container, false);
|
View rootView = inflater.inflate(R.layout.fragment_account_list, container, false);
|
||||||
|
|
||||||
Context context = getContext();
|
Context context = getContext();
|
||||||
recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
|
recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
|
||||||
|
@ -105,6 +105,10 @@ public class AccountFragment extends BaseFragment implements AccountActionListen
|
||||||
scrollListener = null;
|
scrollListener = null;
|
||||||
if (type == Type.BLOCKS) {
|
if (type == Type.BLOCKS) {
|
||||||
adapter = new BlocksAdapter(this);
|
adapter = new BlocksAdapter(this);
|
||||||
|
} else if (type == Type.MUTES) {
|
||||||
|
adapter = new MutesAdapter(this);
|
||||||
|
} else if (type == Type.FOLLOW_REQUESTS) {
|
||||||
|
adapter = new FollowRequestsAdapter(this);
|
||||||
} else {
|
} else {
|
||||||
adapter = new FollowAdapter(this);
|
adapter = new FollowAdapter(this);
|
||||||
}
|
}
|
||||||
|
@ -154,12 +158,6 @@ public class AccountFragment extends BaseFragment implements AccountActionListen
|
||||||
recyclerView.addOnScrollListener(scrollListener);
|
recyclerView.addOnScrollListener(scrollListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
if (listCall != null) listCall.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
if (jumpToTopAllowed()) {
|
if (jumpToTopAllowed()) {
|
||||||
|
@ -186,6 +184,7 @@ public class AccountFragment extends BaseFragment implements AccountActionListen
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Call<List<Account>> listCall;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
default:
|
default:
|
||||||
case FOLLOWS: {
|
case FOLLOWS: {
|
||||||
|
@ -204,6 +203,10 @@ public class AccountFragment extends BaseFragment implements AccountActionListen
|
||||||
listCall = api.mutes(fromId, uptoId, null);
|
listCall = api.mutes(fromId, uptoId, null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case FOLLOW_REQUESTS: {
|
||||||
|
listCall = api.followRequests(fromId, uptoId, null);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
callList.add(listCall);
|
callList.add(listCall);
|
||||||
listCall.enqueue(cb);
|
listCall.enqueue(cb);
|
||||||
|
@ -236,12 +239,78 @@ public class AccountFragment extends BaseFragment implements AccountActionListen
|
||||||
Log.e(TAG, "Fetch failure: " + exception.getMessage());
|
Log.e(TAG, "Fetch failure: " + exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void onViewAccount(String id) {
|
public void onViewAccount(String id) {
|
||||||
Intent intent = new Intent(getContext(), AccountActivity.class);
|
Intent intent = new Intent(getContext(), AccountActivity.class);
|
||||||
intent.putExtra("id", id);
|
intent.putExtra("id", id);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMute(final boolean mute, final String id, final int position) {
|
||||||
|
if (api == null) {
|
||||||
|
/* If somehow an unmute button is clicked after onCreateView but before
|
||||||
|
* onActivityCreated, then this would get called with a null api object, so this eats
|
||||||
|
* that input. */
|
||||||
|
Log.d(TAG, "MastodonAPI isn't initialised so this mute can't occur.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Callback<Relationship> callback = new Callback<Relationship>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<Relationship> call, Response<Relationship> response) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
onMuteSuccess(mute, id, position);
|
||||||
|
} else {
|
||||||
|
onMuteFailure(mute, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<Relationship> call, Throwable t) {
|
||||||
|
onMuteFailure(mute, id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Call<Relationship> call;
|
||||||
|
if (!mute) {
|
||||||
|
call = api.unmuteAccount(id);
|
||||||
|
} else {
|
||||||
|
call = api.muteAccount(id);
|
||||||
|
}
|
||||||
|
callList.add(call);
|
||||||
|
call.enqueue(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onMuteSuccess(boolean muted, final String id, final int position) {
|
||||||
|
if (muted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final MutesAdapter mutesAdapter = (MutesAdapter) adapter;
|
||||||
|
final Account unmutedUser = mutesAdapter.removeItem(position);
|
||||||
|
View.OnClickListener listener = new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
mutesAdapter.addItem(unmutedUser, position);
|
||||||
|
onMute(true, id, position);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Snackbar.make(recyclerView, R.string.confirmation_unmuted, Snackbar.LENGTH_LONG)
|
||||||
|
.setAction(R.string.action_undo, listener)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onMuteFailure(boolean mute, String id) {
|
||||||
|
String verb;
|
||||||
|
if (mute) {
|
||||||
|
verb = "mute";
|
||||||
|
} else {
|
||||||
|
verb = "unmute";
|
||||||
|
}
|
||||||
|
Log.e(TAG, String.format("Failed to %s account id %s", verb, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void onBlock(final boolean block, final String id, final int position) {
|
public void onBlock(final boolean block, final String id, final int position) {
|
||||||
if (api == null) {
|
if (api == null) {
|
||||||
/* If somehow an unblock button is clicked after onCreateView but before
|
/* If somehow an unblock button is clicked after onCreateView but before
|
||||||
|
@ -255,7 +324,7 @@ public class AccountFragment extends BaseFragment implements AccountActionListen
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call<Relationship> call, Response<Relationship> response) {
|
public void onResponse(Call<Relationship> call, Response<Relationship> response) {
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
onBlockSuccess(block, position);
|
onBlockSuccess(block, id, position);
|
||||||
} else {
|
} else {
|
||||||
onBlockFailure(block, id);
|
onBlockFailure(block, id);
|
||||||
}
|
}
|
||||||
|
@ -277,9 +346,22 @@ public class AccountFragment extends BaseFragment implements AccountActionListen
|
||||||
call.enqueue(cb);
|
call.enqueue(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onBlockSuccess(boolean blocked, int position) {
|
private void onBlockSuccess(boolean blocked, final String id, final int position) {
|
||||||
BlocksAdapter blocksAdapter = (BlocksAdapter) adapter;
|
if (blocked) {
|
||||||
blocksAdapter.setBlocked(blocked, position);
|
return;
|
||||||
|
}
|
||||||
|
final BlocksAdapter blocksAdapter = (BlocksAdapter) adapter;
|
||||||
|
final Account unblockedUser = blocksAdapter.removeItem(position);
|
||||||
|
View.OnClickListener listener = new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
blocksAdapter.addItem(unblockedUser, position);
|
||||||
|
onBlock(true, id, position);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Snackbar.make(recyclerView, R.string.confirmation_unblocked, Snackbar.LENGTH_LONG)
|
||||||
|
.setAction(R.string.action_undo, listener)
|
||||||
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onBlockFailure(boolean block, String id) {
|
private void onBlockFailure(boolean block, String id) {
|
||||||
|
@ -292,8 +374,56 @@ public class AccountFragment extends BaseFragment implements AccountActionListen
|
||||||
Log.e(TAG, String.format("Failed to %s account id %s", verb, id));
|
Log.e(TAG, String.format("Failed to %s account id %s", verb, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRespondToFollowRequest(final boolean accept, final String accountId,
|
||||||
|
final int position) {
|
||||||
|
if (api == null) {
|
||||||
|
/* If somehow an response button is clicked after onCreateView but before
|
||||||
|
* onActivityCreated, then this would get called with a null api object, so this eats
|
||||||
|
* that input. */
|
||||||
|
Log.d(TAG, "MastodonAPI isn't initialised, so follow requests can't be responded to.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Callback<Relationship> callback = new Callback<Relationship>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<Relationship> call, Response<Relationship> response) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
onRespondToFollowRequestSuccess(position);
|
||||||
|
} else {
|
||||||
|
onRespondToFollowRequestFailure(accept, accountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<Relationship> call, Throwable t) {
|
||||||
|
onRespondToFollowRequestFailure(accept, accountId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Call<Relationship> call;
|
||||||
|
if (accept) {
|
||||||
|
call = api.authorizeFollowRequest(accountId);
|
||||||
|
} else {
|
||||||
|
call = api.rejectFollowRequest(accountId);
|
||||||
|
}
|
||||||
|
callList.add(call);
|
||||||
|
call.enqueue(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onRespondToFollowRequestSuccess(int position) {
|
||||||
|
FollowRequestsAdapter followRequestsAdapter = (FollowRequestsAdapter) adapter;
|
||||||
|
followRequestsAdapter.removeItem(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onRespondToFollowRequestFailure(boolean accept, String accountId) {
|
||||||
|
String verb = (accept) ? "accept" : "reject";
|
||||||
|
String message = String.format("Failed to %s account id %s.", verb, accountId);
|
||||||
|
Log.e(TAG, message);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean jumpToTopAllowed() {
|
private boolean jumpToTopAllowed() {
|
||||||
return type != Type.BLOCKS;
|
return type == Type.FOLLOWS || type == Type.FOLLOWERS;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void jumpToTop() {
|
private void jumpToTop() {
|
|
@ -24,15 +24,20 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
class AccountPagerAdapter extends FragmentPagerAdapter {
|
class AccountPagerAdapter extends FragmentPagerAdapter {
|
||||||
private Context context;
|
private Context context;
|
||||||
private String accountId;
|
private String accountId;
|
||||||
private String[] pageTitles;
|
private String[] pageTitles;
|
||||||
|
private List<Fragment> registeredFragments;
|
||||||
|
|
||||||
AccountPagerAdapter(FragmentManager manager, Context context, String accountId) {
|
AccountPagerAdapter(FragmentManager manager, Context context, String accountId) {
|
||||||
super(manager);
|
super(manager);
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.accountId = accountId;
|
this.accountId = accountId;
|
||||||
|
registeredFragments = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setPageTitles(String[] titles) {
|
void setPageTitles(String[] titles) {
|
||||||
|
@ -46,10 +51,10 @@ class AccountPagerAdapter extends FragmentPagerAdapter {
|
||||||
return TimelineFragment.newInstance(TimelineFragment.Kind.USER, accountId);
|
return TimelineFragment.newInstance(TimelineFragment.Kind.USER, accountId);
|
||||||
}
|
}
|
||||||
case 1: {
|
case 1: {
|
||||||
return AccountFragment.newInstance(AccountFragment.Type.FOLLOWS, accountId);
|
return AccountListFragment.newInstance(AccountListFragment.Type.FOLLOWS, accountId);
|
||||||
}
|
}
|
||||||
case 2: {
|
case 2: {
|
||||||
return AccountFragment.newInstance(AccountFragment.Type.FOLLOWERS, accountId);
|
return AccountListFragment.newInstance(AccountListFragment.Type.FOLLOWERS, accountId);
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
return null;
|
return null;
|
||||||
|
@ -73,4 +78,21 @@ class AccountPagerAdapter extends FragmentPagerAdapter {
|
||||||
title.setText(pageTitles[position]);
|
title.setText(pageTitles[position]);
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object instantiateItem(ViewGroup container, int position) {
|
||||||
|
Fragment fragment = (Fragment) super.instantiateItem(container, position);
|
||||||
|
registeredFragments.add(fragment);
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroyItem(ViewGroup container, int position, Object object) {
|
||||||
|
registeredFragments.remove((Fragment) object);
|
||||||
|
super.destroyItem(container, position, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Fragment> getRegisteredFragments() {
|
||||||
|
return registeredFragments;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
|
|
||||||
package com.keylesspalace.tusky;
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
import android.app.AlarmManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
@ -22,6 +24,7 @@ import android.graphics.Color;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.SystemClock;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
@ -29,7 +32,6 @@ import android.text.Spanned;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
|
|
||||||
import com.google.firebase.iid.FirebaseInstanceId;
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
|
@ -46,16 +48,13 @@ import retrofit2.Callback;
|
||||||
import retrofit2.Retrofit;
|
import retrofit2.Retrofit;
|
||||||
import retrofit2.converter.gson.GsonConverterFactory;
|
import retrofit2.converter.gson.GsonConverterFactory;
|
||||||
|
|
||||||
/* 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 views are created. And
|
|
||||||
* the most expedient way to accomplish this was to put it in a base class and just have every
|
|
||||||
* activity extend from it. */
|
|
||||||
public class BaseActivity extends AppCompatActivity {
|
public class BaseActivity extends AppCompatActivity {
|
||||||
private static final String TAG = "BaseActivity"; // logging tag
|
private static final String TAG = "BaseActivity"; // logging tag
|
||||||
|
|
||||||
protected MastodonAPI mastodonAPI;
|
protected MastodonAPI mastodonAPI;
|
||||||
protected TuskyAPI tuskyAPI;
|
protected TuskyAPI tuskyAPI;
|
||||||
protected Dispatcher mastodonApiDispatcher;
|
protected Dispatcher mastodonApiDispatcher;
|
||||||
|
protected PendingIntent serviceAlarmIntent;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
@ -65,6 +64,9 @@ public class BaseActivity extends AppCompatActivity {
|
||||||
createMastodonAPI();
|
createMastodonAPI();
|
||||||
createTuskyAPI();
|
createTuskyAPI();
|
||||||
|
|
||||||
|
/* 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
|
||||||
|
* views are created. */
|
||||||
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("lightTheme", false)) {
|
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("lightTheme", false)) {
|
||||||
setTheme(R.style.AppTheme_Light);
|
setTheme(R.style.AppTheme_Light);
|
||||||
}
|
}
|
||||||
|
@ -96,8 +98,12 @@ public class BaseActivity extends AppCompatActivity {
|
||||||
overridePendingTransition(R.anim.slide_from_left, R.anim.slide_to_right);
|
overridePendingTransition(R.anim.slide_from_left, R.anim.slide_to_right);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected SharedPreferences getPrivatePreferences() {
|
||||||
|
return getSharedPreferences(getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
protected String getAccessToken() {
|
protected String getAccessToken() {
|
||||||
SharedPreferences preferences = getSharedPreferences(getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
SharedPreferences preferences = getPrivatePreferences();
|
||||||
return preferences.getString("accessToken", null);
|
return preferences.getString("accessToken", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +113,7 @@ public class BaseActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getBaseUrl() {
|
protected String getBaseUrl() {
|
||||||
SharedPreferences preferences = getSharedPreferences(getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
SharedPreferences preferences = getPrivatePreferences();
|
||||||
return "https://" + preferences.getString("domain", null);
|
return "https://" + preferences.getString("domain", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,6 +122,7 @@ public class BaseActivity extends AppCompatActivity {
|
||||||
|
|
||||||
Gson gson = new GsonBuilder()
|
Gson gson = new GsonBuilder()
|
||||||
.registerTypeAdapter(Spanned.class, new SpannedTypeAdapter())
|
.registerTypeAdapter(Spanned.class, new SpannedTypeAdapter())
|
||||||
|
.registerTypeAdapter(StringWithEmoji.class, new StringWithEmojiTypeAdapter())
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
OkHttpClient okHttpClient = OkHttpUtils.getCompatibleClientBuilder()
|
OkHttpClient okHttpClient = OkHttpUtils.getCompatibleClientBuilder()
|
||||||
|
@ -148,6 +155,7 @@ public class BaseActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createTuskyAPI() {
|
protected void createTuskyAPI() {
|
||||||
|
if (BuildConfig.USES_PUSH_NOTIFICATIONS) {
|
||||||
Retrofit retrofit = new Retrofit.Builder()
|
Retrofit retrofit = new Retrofit.Builder()
|
||||||
.baseUrl(getString(R.string.tusky_api_url))
|
.baseUrl(getString(R.string.tusky_api_url))
|
||||||
.client(OkHttpUtils.getCompatibleClient())
|
.client(OkHttpUtils.getCompatibleClient())
|
||||||
|
@ -155,10 +163,10 @@ public class BaseActivity extends AppCompatActivity {
|
||||||
|
|
||||||
tuskyAPI = retrofit.create(TuskyAPI.class);
|
tuskyAPI = retrofit.create(TuskyAPI.class);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void redirectIfNotLoggedIn() {
|
protected void redirectIfNotLoggedIn() {
|
||||||
SharedPreferences preferences = getSharedPreferences(
|
SharedPreferences preferences = getPrivatePreferences();
|
||||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
|
||||||
String domain = preferences.getString("domain", null);
|
String domain = preferences.getString("domain", null);
|
||||||
String accessToken = preferences.getString("accessToken", null);
|
String accessToken = preferences.getString("accessToken", null);
|
||||||
if (domain == null || accessToken == null) {
|
if (domain == null || accessToken == null) {
|
||||||
|
@ -188,7 +196,9 @@ public class BaseActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void enablePushNotifications() {
|
protected void enablePushNotifications() {
|
||||||
tuskyAPI.register(getBaseUrl(), getAccessToken(), FirebaseInstanceId.getInstance().getToken()).enqueue(new Callback<ResponseBody>() {
|
if (BuildConfig.USES_PUSH_NOTIFICATIONS) {
|
||||||
|
String token = com.google.firebase.iid.FirebaseInstanceId.getInstance().getToken();
|
||||||
|
tuskyAPI.register(getBaseUrl(), getAccessToken(), token).enqueue(new Callback<ResponseBody>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
|
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
|
||||||
Log.d(TAG, "Enable push notifications response: " + response.message());
|
Log.d(TAG, "Enable push notifications response: " + response.message());
|
||||||
|
@ -199,9 +209,21 @@ public class BaseActivity extends AppCompatActivity {
|
||||||
Log.d(TAG, "Enable push notifications failed: " + t.getMessage());
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void disablePushNotifications() {
|
protected void disablePushNotifications() {
|
||||||
|
if (BuildConfig.USES_PUSH_NOTIFICATIONS) {
|
||||||
tuskyAPI.unregister(getBaseUrl(), getAccessToken()).enqueue(new Callback<ResponseBody>() {
|
tuskyAPI.unregister(getBaseUrl(), getAccessToken()).enqueue(new Callback<ResponseBody>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
|
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
|
||||||
|
@ -213,5 +235,10 @@ public class BaseActivity extends AppCompatActivity {
|
||||||
Log.d(TAG, "Disable push notifications failed: " + t.getMessage());
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
|
|
||||||
package com.keylesspalace.tusky;
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
|
@ -40,4 +42,9 @@ public class BaseFragment extends Fragment {
|
||||||
}
|
}
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected SharedPreferences getPrivatePreferences() {
|
||||||
|
return getContext().getSharedPreferences(
|
||||||
|
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,9 +26,6 @@ import com.keylesspalace.tusky.entity.Account;
|
||||||
import com.pkmmte.view.CircularImageView;
|
import com.pkmmte.view.CircularImageView;
|
||||||
import com.squareup.picasso.Picasso;
|
import com.squareup.picasso.Picasso;
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
|
@ -36,11 +33,8 @@ class BlocksAdapter extends AccountAdapter {
|
||||||
private static final int VIEW_TYPE_BLOCKED_USER = 0;
|
private static final int VIEW_TYPE_BLOCKED_USER = 0;
|
||||||
private static final int VIEW_TYPE_FOOTER = 1;
|
private static final int VIEW_TYPE_FOOTER = 1;
|
||||||
|
|
||||||
private Set<Integer> unblockedAccountPositions;
|
|
||||||
|
|
||||||
BlocksAdapter(AccountActionListener accountActionListener) {
|
BlocksAdapter(AccountActionListener accountActionListener) {
|
||||||
super(accountActionListener);
|
super(accountActionListener);
|
||||||
unblockedAccountPositions = new HashSet<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -65,8 +59,7 @@ class BlocksAdapter extends AccountAdapter {
|
||||||
if (position < accountList.size()) {
|
if (position < accountList.size()) {
|
||||||
BlockedUserViewHolder holder = (BlockedUserViewHolder) viewHolder;
|
BlockedUserViewHolder holder = (BlockedUserViewHolder) viewHolder;
|
||||||
holder.setupWithAccount(accountList.get(position));
|
holder.setupWithAccount(accountList.get(position));
|
||||||
boolean blocked = !unblockedAccountPositions.contains(position);
|
holder.setupActionListener(accountActionListener, true);
|
||||||
holder.setupActionListener(accountActionListener, blocked, position);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,15 +72,6 @@ class BlocksAdapter extends AccountAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setBlocked(boolean blocked, int position) {
|
|
||||||
if (blocked) {
|
|
||||||
unblockedAccountPositions.remove(position);
|
|
||||||
} else {
|
|
||||||
unblockedAccountPositions.add(position);
|
|
||||||
}
|
|
||||||
notifyItemChanged(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
static class BlockedUserViewHolder extends RecyclerView.ViewHolder {
|
static class BlockedUserViewHolder extends RecyclerView.ViewHolder {
|
||||||
@BindView(R.id.blocked_user_avatar) CircularImageView avatar;
|
@BindView(R.id.blocked_user_avatar) CircularImageView avatar;
|
||||||
@BindView(R.id.blocked_user_username) TextView username;
|
@BindView(R.id.blocked_user_username) TextView username;
|
||||||
|
@ -114,13 +98,15 @@ class BlocksAdapter extends AccountAdapter {
|
||||||
.into(avatar);
|
.into(avatar);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setupActionListener(final AccountActionListener listener, final boolean blocked,
|
void setupActionListener(final AccountActionListener listener, final boolean blocked) {
|
||||||
final int position) {
|
|
||||||
unblock.setOnClickListener(new View.OnClickListener() {
|
unblock.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
|
int position = getAdapterPosition();
|
||||||
|
if (position != RecyclerView.NO_POSITION) {
|
||||||
listener.onBlock(!blocked, id, position);
|
listener.onBlock(!blocked, id, position);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
avatar.setOnClickListener(new View.OnClickListener() {
|
avatar.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -18,7 +18,6 @@ package com.keylesspalace.tusky;
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
@ -36,8 +35,10 @@ import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Environment;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
import android.provider.MediaStore;
|
||||||
import android.provider.OpenableColumns;
|
import android.provider.OpenableColumns;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
@ -48,6 +49,7 @@ import android.support.v13.view.inputmethod.InputConnectionCompat;
|
||||||
import android.support.v13.view.inputmethod.InputContentInfoCompat;
|
import android.support.v13.view.inputmethod.InputContentInfoCompat;
|
||||||
import android.support.v4.app.ActivityCompat;
|
import android.support.v4.app.ActivityCompat;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.support.v4.content.FileProvider;
|
||||||
import android.support.v7.app.ActionBar;
|
import android.support.v7.app.ActionBar;
|
||||||
import android.support.v7.content.res.AppCompatResources;
|
import android.support.v7.content.res.AppCompatResources;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
|
@ -77,9 +79,11 @@ import com.keylesspalace.tusky.entity.Media;
|
||||||
import com.keylesspalace.tusky.entity.Status;
|
import com.keylesspalace.tusky.entity.Status;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
@ -99,8 +103,11 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
||||||
private static final int STATUS_CHARACTER_LIMIT = 500;
|
private static final int STATUS_CHARACTER_LIMIT = 500;
|
||||||
private static final int STATUS_MEDIA_SIZE_LIMIT = 4000000; // 4MB
|
private static final int STATUS_MEDIA_SIZE_LIMIT = 4000000; // 4MB
|
||||||
private static final int MEDIA_PICK_RESULT = 1;
|
private static final int MEDIA_PICK_RESULT = 1;
|
||||||
|
private static final int MEDIA_TAKE_PHOTO_RESULT = 2;
|
||||||
private static final int PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1;
|
private static final int PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1;
|
||||||
private static final int MEDIA_SIZE_UNKNOWN = -1;
|
private static final int MEDIA_SIZE_UNKNOWN = -1;
|
||||||
|
private static final int COMPOSE_SUCCESS = -1;
|
||||||
|
private static final int THUMBNAIL_SIZE = 128; // pixels
|
||||||
|
|
||||||
private String inReplyToId;
|
private String inReplyToId;
|
||||||
private EditText textEditor;
|
private EditText textEditor;
|
||||||
|
@ -120,8 +127,11 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
||||||
private TextView charactersLeft;
|
private TextView charactersLeft;
|
||||||
private Button floatingBtn;
|
private Button floatingBtn;
|
||||||
private ImageButton pickBtn;
|
private ImageButton pickBtn;
|
||||||
|
private ImageButton takeBtn;
|
||||||
private Button nsfwBtn;
|
private Button nsfwBtn;
|
||||||
private ProgressBar postProgress;
|
private ProgressBar postProgress;
|
||||||
|
private ImageButton visibilityBtn;
|
||||||
|
private Uri photoUploadUri;
|
||||||
|
|
||||||
private static class QueuedMedia {
|
private static class QueuedMedia {
|
||||||
enum Type {
|
enum Type {
|
||||||
|
@ -335,23 +345,17 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
||||||
actionBar.setHomeAsUpIndicator(closeIcon);
|
actionBar.setHomeAsUpIndicator(closeIcon);
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedPreferences preferences = getSharedPreferences(
|
SharedPreferences preferences = getPrivatePreferences();
|
||||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
|
||||||
|
|
||||||
floatingBtn = (Button) findViewById(R.id.floating_btn);
|
floatingBtn = (Button) findViewById(R.id.floating_btn);
|
||||||
pickBtn = (ImageButton) findViewById(R.id.compose_photo_pick);
|
pickBtn = (ImageButton) findViewById(R.id.compose_photo_pick);
|
||||||
|
takeBtn = (ImageButton) findViewById(R.id.compose_photo_take);
|
||||||
nsfwBtn = (Button) findViewById(R.id.action_toggle_nsfw);
|
nsfwBtn = (Button) findViewById(R.id.action_toggle_nsfw);
|
||||||
final ImageButton visibilityBtn = (ImageButton) findViewById(R.id.action_toggle_visibility);
|
visibilityBtn = (ImageButton) findViewById(R.id.action_toggle_visibility);
|
||||||
|
|
||||||
floatingBtn.setOnClickListener(new View.OnClickListener() {
|
floatingBtn.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
pickBtn.setClickable(false);
|
|
||||||
nsfwBtn.setClickable(false);
|
|
||||||
visibilityBtn.setClickable(false);
|
|
||||||
floatingBtn.setEnabled(false);
|
|
||||||
|
|
||||||
postProgress.setVisibility(View.VISIBLE);
|
|
||||||
sendStatus();
|
sendStatus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -361,6 +365,12 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
||||||
onMediaPick();
|
onMediaPick();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
takeBtn.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
initiateCameraApp();
|
||||||
|
}
|
||||||
|
});
|
||||||
nsfwBtn.setOnClickListener(new View.OnClickListener() {
|
nsfwBtn.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
|
@ -567,26 +577,69 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setStatusVisibility(String visibility) {
|
private void disableButtons() {
|
||||||
statusVisibility = visibility;
|
pickBtn.setClickable(false);
|
||||||
switch (visibility) {
|
takeBtn.setClickable(false);
|
||||||
case "public": {
|
nsfwBtn.setClickable(false);
|
||||||
floatingBtn.setText(R.string.action_send_public);
|
visibilityBtn.setClickable(false);
|
||||||
floatingBtn.setCompoundDrawables(null, null, null, null);
|
floatingBtn.setEnabled(false);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case "private": {
|
|
||||||
|
private void enableButtons() {
|
||||||
|
pickBtn.setClickable(true);
|
||||||
|
takeBtn.setClickable(true);
|
||||||
|
nsfwBtn.setClickable(true);
|
||||||
|
visibilityBtn.setClickable(true);
|
||||||
|
floatingBtn.setEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addLockToSendButton() {
|
||||||
floatingBtn.setText(R.string.action_send);
|
floatingBtn.setText(R.string.action_send);
|
||||||
Drawable lock = AppCompatResources.getDrawable(this, R.drawable.send_private);
|
Drawable lock = AppCompatResources.getDrawable(this, R.drawable.send_private);
|
||||||
if (lock != null) {
|
if (lock != null) {
|
||||||
lock.setBounds(0, 0, lock.getIntrinsicWidth(), lock.getIntrinsicHeight());
|
lock.setBounds(0, 0, lock.getIntrinsicWidth(), lock.getIntrinsicHeight());
|
||||||
floatingBtn.setCompoundDrawables(null, null, lock, null);
|
floatingBtn.setCompoundDrawables(null, null, lock, null);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setStatusVisibility(String visibility) {
|
||||||
|
statusVisibility = visibility;
|
||||||
|
switch (visibility) {
|
||||||
|
case "public": {
|
||||||
|
floatingBtn.setText(R.string.action_send_public);
|
||||||
|
floatingBtn.setCompoundDrawables(null, null, null, null);
|
||||||
|
Drawable globe = AppCompatResources.getDrawable(this, R.drawable.ic_public_24dp);
|
||||||
|
if (globe != null) {
|
||||||
|
visibilityBtn.setImageDrawable(globe);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "private": {
|
||||||
|
addLockToSendButton();
|
||||||
|
Drawable lock = AppCompatResources.getDrawable(this,
|
||||||
|
R.drawable.ic_lock_outline_24dp);
|
||||||
|
if (lock != null) {
|
||||||
|
visibilityBtn.setImageDrawable(lock);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "direct": {
|
||||||
|
addLockToSendButton();
|
||||||
|
Drawable envelope = AppCompatResources.getDrawable(this, R.drawable.ic_email_24dp);
|
||||||
|
if (envelope != null) {
|
||||||
|
visibilityBtn.setImageDrawable(envelope);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "unlisted":
|
||||||
default: {
|
default: {
|
||||||
floatingBtn.setText(R.string.action_send);
|
floatingBtn.setText(R.string.action_send);
|
||||||
floatingBtn.setCompoundDrawables(null, null, null, null);
|
floatingBtn.setCompoundDrawables(null, null, null, null);
|
||||||
|
Drawable openLock = AppCompatResources.getDrawable(this,
|
||||||
|
R.drawable.ic_lock_open_24dp);
|
||||||
|
if (openLock != null) {
|
||||||
|
visibilityBtn.setImageDrawable(openLock);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -615,6 +668,18 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
||||||
updateVisibleCharactersLeft();
|
updateVisibleCharactersLeft();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setStateToReadying() {
|
||||||
|
statusAlreadyInFlight = true;
|
||||||
|
disableButtons();
|
||||||
|
postProgress.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setStateToNotReadying() {
|
||||||
|
postProgress.setVisibility(View.INVISIBLE);
|
||||||
|
statusAlreadyInFlight = false;
|
||||||
|
enableButtons();
|
||||||
|
}
|
||||||
|
|
||||||
private void sendStatus() {
|
private void sendStatus() {
|
||||||
if (statusAlreadyInFlight) {
|
if (statusAlreadyInFlight) {
|
||||||
return;
|
return;
|
||||||
|
@ -624,9 +689,12 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
||||||
if (statusHideText) {
|
if (statusHideText) {
|
||||||
spoilerText = contentWarningEditor.getText().toString();
|
spoilerText = contentWarningEditor.getText().toString();
|
||||||
}
|
}
|
||||||
if (contentText.length() + spoilerText.length() <= STATUS_CHARACTER_LIMIT) {
|
int characterCount = contentText.length() + spoilerText.length();
|
||||||
statusAlreadyInFlight = true;
|
if (characterCount > 0 && characterCount <= STATUS_CHARACTER_LIMIT) {
|
||||||
|
setStateToReadying();
|
||||||
readyStatus(contentText, statusVisibility, statusMarkSensitive, spoilerText);
|
readyStatus(contentText, statusVisibility, statusMarkSensitive, spoilerText);
|
||||||
|
} else if (characterCount <= 0) {
|
||||||
|
textEditor.setError(getString(R.string.error_empty));
|
||||||
} else {
|
} else {
|
||||||
textEditor.setError(getString(R.string.error_compose_character_limit));
|
textEditor.setError(getString(R.string.error_compose_character_limit));
|
||||||
}
|
}
|
||||||
|
@ -685,11 +753,9 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
||||||
* the status they reply to and that behaviour needs to be kept separate. */
|
* the status they reply to and that behaviour needs to be kept separate. */
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SharedPreferences preferences = getSharedPreferences(
|
getPrivatePreferences().edit()
|
||||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
.putString("rememberedVisibility", statusVisibility)
|
||||||
SharedPreferences.Editor editor = preferences.edit();
|
.apply();
|
||||||
editor.putString("rememberedVisibility", statusVisibility);
|
|
||||||
editor.apply();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private EditText createEditText(String[] contentMimeTypes) {
|
private EditText createEditText(String[] contentMimeTypes) {
|
||||||
|
@ -719,7 +785,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
||||||
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
|
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||||
editText.setLayoutParams(layoutParams);
|
editText.setLayoutParams(layoutParams);
|
||||||
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
|
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
|
||||||
editText.setEms(10);
|
editText.setEms(10);
|
||||||
editText.setBackgroundColor(0);
|
editText.setBackgroundColor(0);
|
||||||
editText.setGravity(Gravity.START | Gravity.TOP);
|
editText.setGravity(Gravity.START | Gravity.TOP);
|
||||||
|
@ -816,13 +882,13 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
||||||
private void onSendSuccess() {
|
private void onSendSuccess() {
|
||||||
Snackbar bar = Snackbar.make(findViewById(R.id.activity_compose), getString(R.string.confirmation_send), Snackbar.LENGTH_SHORT);
|
Snackbar bar = Snackbar.make(findViewById(R.id.activity_compose), getString(R.string.confirmation_send), Snackbar.LENGTH_SHORT);
|
||||||
bar.show();
|
bar.show();
|
||||||
|
setResult(COMPOSE_SUCCESS);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSendFailure() {
|
private void onSendFailure() {
|
||||||
postProgress.setVisibility(View.INVISIBLE);
|
|
||||||
textEditor.setError(getString(R.string.error_generic));
|
textEditor.setError(getString(R.string.error_generic));
|
||||||
statusAlreadyInFlight = false;
|
setStateToNotReadying();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readyStatus(final String content, final String visibility, final boolean sensitive,
|
private void readyStatus(final String content, final String visibility, final boolean sensitive,
|
||||||
|
@ -857,7 +923,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
||||||
@Override
|
@Override
|
||||||
protected void onCancelled() {
|
protected void onCancelled() {
|
||||||
removeAllMediaFromQueue();
|
removeAllMediaFromQueue();
|
||||||
statusAlreadyInFlight = false;
|
setStateToNotReadying();
|
||||||
super.onCancelled();
|
super.onCancelled();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -882,7 +948,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
||||||
readyStatus(content, visibility, sensitive, spoilerText);
|
readyStatus(content, visibility, sensitive, spoilerText);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
statusAlreadyInFlight = false;
|
setStateToNotReadying();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onMediaPick() {
|
private void onMediaPick() {
|
||||||
|
@ -919,6 +985,41 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private File createNewImageFile() throws IOException {
|
||||||
|
// Create an image file name
|
||||||
|
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
|
||||||
|
String imageFileName = "Tusky_" + timeStamp + "_";
|
||||||
|
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
|
||||||
|
return File.createTempFile(
|
||||||
|
imageFileName, /* prefix */
|
||||||
|
".jpg", /* suffix */
|
||||||
|
storageDir /* directory */
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initiateCameraApp() {
|
||||||
|
// We don't need to ask for permission in this case, because the used calls require
|
||||||
|
// android.permission.WRITE_EXTERNAL_STORAGE only on SDKs *older* than Kitkat, which was
|
||||||
|
// way before permission dialogues have been introduced.
|
||||||
|
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||||
|
if (intent.resolveActivity(getPackageManager()) != null) {
|
||||||
|
File photoFile = null;
|
||||||
|
try {
|
||||||
|
photoFile = createNewImageFile();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
displayTransientError(R.string.error_media_upload_opening);
|
||||||
|
}
|
||||||
|
// Continue only if the File was successfully created
|
||||||
|
if (photoFile != null) {
|
||||||
|
photoUploadUri = FileProvider.getUriForFile(this,
|
||||||
|
"com.keylesspalace.tusky.fileprovider",
|
||||||
|
photoFile);
|
||||||
|
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUploadUri);
|
||||||
|
startActivityForResult(intent, MEDIA_TAKE_PHOTO_RESULT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void initiateMediaPicking() {
|
private void initiateMediaPicking() {
|
||||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
@ -932,16 +1033,22 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
||||||
startActivityForResult(intent, MEDIA_PICK_RESULT);
|
startActivityForResult(intent, MEDIA_PICK_RESULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enableMediaPicking() {
|
private void enableMediaButtons() {
|
||||||
pickBtn.setEnabled(true);
|
pickBtn.setEnabled(true);
|
||||||
ThemeUtils.setDrawableTint(this, pickBtn.getDrawable(),
|
ThemeUtils.setDrawableTint(this, pickBtn.getDrawable(),
|
||||||
R.attr.compose_media_button_tint);
|
R.attr.compose_media_button_tint);
|
||||||
|
takeBtn.setEnabled(true);
|
||||||
|
ThemeUtils.setDrawableTint(this, takeBtn.getDrawable(),
|
||||||
|
R.attr.compose_media_button_tint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void disableMediaPicking() {
|
private void disableMediaButtons() {
|
||||||
pickBtn.setEnabled(false);
|
pickBtn.setEnabled(false);
|
||||||
ThemeUtils.setDrawableTint(this, pickBtn.getDrawable(),
|
ThemeUtils.setDrawableTint(this, pickBtn.getDrawable(),
|
||||||
R.attr.compose_media_button_disabled_tint);
|
R.attr.compose_media_button_disabled_tint);
|
||||||
|
takeBtn.setEnabled(false);
|
||||||
|
ThemeUtils.setDrawableTint(this, takeBtn.getDrawable(),
|
||||||
|
R.attr.compose_media_button_disabled_tint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMediaToQueue(QueuedMedia.Type type, Bitmap preview, Uri uri, long mediaSize) {
|
private void addMediaToQueue(QueuedMedia.Type type, Bitmap preview, Uri uri, long mediaSize) {
|
||||||
|
@ -976,11 +1083,11 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
||||||
textEditor.getPaddingRight(), totalHeight);
|
textEditor.getPaddingRight(), totalHeight);
|
||||||
// If there's one video in the queue it is full, so disable the button to queue more.
|
// If there's one video in the queue it is full, so disable the button to queue more.
|
||||||
if (item.type == QueuedMedia.Type.VIDEO) {
|
if (item.type == QueuedMedia.Type.VIDEO) {
|
||||||
disableMediaPicking();
|
disableMediaButtons();
|
||||||
}
|
}
|
||||||
} else if (queuedCount >= Status.MAX_MEDIA_ATTACHMENTS) {
|
} else if (queuedCount >= Status.MAX_MEDIA_ATTACHMENTS) {
|
||||||
// Limit the total media attachments, also.
|
// Limit the total media attachments, also.
|
||||||
disableMediaPicking();
|
disableMediaButtons();
|
||||||
}
|
}
|
||||||
if (queuedCount >= 1) {
|
if (queuedCount >= 1) {
|
||||||
showMarkSensitive(true);
|
showMarkSensitive(true);
|
||||||
|
@ -1003,7 +1110,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
||||||
textEditor.setPadding(textEditor.getPaddingLeft(), textEditor.getPaddingTop(),
|
textEditor.setPadding(textEditor.getPaddingLeft(), textEditor.getPaddingTop(),
|
||||||
textEditor.getPaddingRight(), 0);
|
textEditor.getPaddingRight(), 0);
|
||||||
}
|
}
|
||||||
enableMediaPicking();
|
enableMediaButtons();
|
||||||
cancelReadyingMedia(item);
|
cancelReadyingMedia(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1164,6 +1271,9 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
||||||
Uri uri = data.getData();
|
Uri uri = data.getData();
|
||||||
long mediaSize = getMediaSize(getContentResolver(), uri);
|
long mediaSize = getMediaSize(getContentResolver(), uri);
|
||||||
pickMedia(uri, mediaSize);
|
pickMedia(uri, mediaSize);
|
||||||
|
} else if (requestCode == MEDIA_TAKE_PHOTO_RESULT && resultCode == RESULT_OK) {
|
||||||
|
long mediaSize = getMediaSize(getContentResolver(), photoUploadUri);
|
||||||
|
pickMedia(photoUploadUri, mediaSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1190,7 +1300,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
||||||
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
|
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
|
||||||
retriever.setDataSource(this, uri);
|
retriever.setDataSource(this, uri);
|
||||||
Bitmap source = retriever.getFrameAtTime();
|
Bitmap source = retriever.getFrameAtTime();
|
||||||
Bitmap bitmap = ThumbnailUtils.extractThumbnail(source, 128, 128);
|
Bitmap bitmap = ThumbnailUtils.extractThumbnail(source, THUMBNAIL_SIZE, THUMBNAIL_SIZE);
|
||||||
source.recycle();
|
source.recycle();
|
||||||
addMediaToQueue(QueuedMedia.Type.VIDEO, bitmap, uri, mediaSize);
|
addMediaToQueue(QueuedMedia.Type.VIDEO, bitmap, uri, mediaSize);
|
||||||
break;
|
break;
|
||||||
|
@ -1203,8 +1313,9 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
||||||
displayTransientError(R.string.error_media_upload_opening);
|
displayTransientError(R.string.error_media_upload_opening);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bitmap source = BitmapFactory.decodeStream(stream);
|
Bitmap source = BitmapFactory.decodeStream(stream);
|
||||||
Bitmap bitmap = ThumbnailUtils.extractThumbnail(source, 128, 128);
|
Bitmap bitmap = ThumbnailUtils.extractThumbnail(source, THUMBNAIL_SIZE, THUMBNAIL_SIZE);
|
||||||
source.recycle();
|
source.recycle();
|
||||||
try {
|
try {
|
||||||
if (stream != null) {
|
if (stream != null) {
|
||||||
|
|
|
@ -73,10 +73,14 @@ public class ComposeOptionsFragment extends BottomSheetDialogFragment {
|
||||||
radioCheckedId = R.id.radio_unlisted;
|
radioCheckedId = R.id.radio_unlisted;
|
||||||
}
|
}
|
||||||
if (statusVisibility != null) {
|
if (statusVisibility != null) {
|
||||||
if (statusVisibility.equals("unlisted")) {
|
if (statusVisibility.equals("public")) {
|
||||||
radioCheckedId = R.id.radio_unlisted;
|
radioCheckedId = R.id.radio_public;
|
||||||
} else if (statusVisibility.equals("private")) {
|
} else if (statusVisibility.equals("private")) {
|
||||||
radioCheckedId = R.id.radio_private;
|
radioCheckedId = R.id.radio_private;
|
||||||
|
} else if (statusVisibility.equals("unlisted")) {
|
||||||
|
radioCheckedId = R.id.radio_unlisted;
|
||||||
|
} else if (statusVisibility.equals("direct")) {
|
||||||
|
radioCheckedId = R.id.radio_direct;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
radio.check(radioCheckedId);
|
radio.check(radioCheckedId);
|
||||||
|
@ -113,6 +117,10 @@ public class ComposeOptionsFragment extends BottomSheetDialogFragment {
|
||||||
visibility = "private";
|
visibility = "private";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case R.id.radio_direct: {
|
||||||
|
visibility = "direct";
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
listener.onVisibilityChanged(visibility);
|
listener.onVisibilityChanged(visibility);
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ class CustomTabURLSpan extends URLSpan {
|
||||||
customTabsIntent.launchUrl(context, uri);
|
customTabsIntent.launchUrl(context, uri);
|
||||||
}
|
}
|
||||||
} catch (ActivityNotFoundException e) {
|
} catch (ActivityNotFoundException e) {
|
||||||
android.util.Log.w("URLSpan", "Activity was not found for intent, " + customTabsIntent.toString());
|
Log.w("URLSpan", "Activity was not found for intent, " + customTabsIntent.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import android.graphics.BitmapFactory;
|
||||||
import android.graphics.Matrix;
|
import android.graphics.Matrix;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.support.media.ExifInterface;
|
import android.support.media.ExifInterface;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
@ -42,6 +43,7 @@ class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private static Bitmap reorientBitmap(Bitmap bitmap, int orientation) {
|
private static Bitmap reorientBitmap(Bitmap bitmap, int orientation) {
|
||||||
Matrix matrix = new Matrix();
|
Matrix matrix = new Matrix();
|
||||||
switch (orientation) {
|
switch (orientation) {
|
||||||
|
|
|
@ -0,0 +1,515 @@
|
||||||
|
/* 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.Manifest;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.design.widget.Snackbar;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.support.v7.app.ActionBar;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
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.ImageView;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
|
||||||
|
import com.keylesspalace.tusky.entity.Account;
|
||||||
|
import com.keylesspalace.tusky.entity.Profile;
|
||||||
|
import com.theartofdev.edmodo.cropper.CropImage;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
public class EditProfileActivity extends BaseActivity {
|
||||||
|
private static final String TAG = "EditProfileActivity";
|
||||||
|
private static final int AVATAR_PICK_RESULT = 1;
|
||||||
|
private static final int HEADER_PICK_RESULT = 2;
|
||||||
|
private static final int PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1;
|
||||||
|
private static final int AVATAR_WIDTH = 120;
|
||||||
|
private static final int AVATAR_HEIGHT = 120;
|
||||||
|
private static final int HEADER_WIDTH = 700;
|
||||||
|
private static final int HEADER_HEIGHT = 335;
|
||||||
|
|
||||||
|
private enum PickType {
|
||||||
|
NOTHING,
|
||||||
|
AVATAR,
|
||||||
|
HEADER
|
||||||
|
}
|
||||||
|
|
||||||
|
@BindView(R.id.edit_profile_display_name) EditText displayNameEditText;
|
||||||
|
@BindView(R.id.edit_profile_note) EditText noteEditText;
|
||||||
|
@BindView(R.id.edit_profile_avatar) Button avatarButton;
|
||||||
|
@BindView(R.id.edit_profile_avatar_preview) ImageView avatarPreview;
|
||||||
|
@BindView(R.id.edit_profile_avatar_progress) ProgressBar avatarProgress;
|
||||||
|
@BindView(R.id.edit_profile_header) Button headerButton;
|
||||||
|
@BindView(R.id.edit_profile_header_preview) ImageView headerPreview;
|
||||||
|
@BindView(R.id.edit_profile_header_progress) ProgressBar headerProgress;
|
||||||
|
@BindView(R.id.edit_profile_save_progress) ProgressBar saveProgress;
|
||||||
|
|
||||||
|
private String priorDisplayName;
|
||||||
|
private String priorNote;
|
||||||
|
private boolean isAlreadySaving;
|
||||||
|
private PickType currentlyPicking;
|
||||||
|
private String avatarBase64;
|
||||||
|
private String headerBase64;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_edit_profile);
|
||||||
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
|
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
ActionBar actionBar = getSupportActionBar();
|
||||||
|
if (actionBar != null) {
|
||||||
|
actionBar.setTitle(null);
|
||||||
|
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||||
|
actionBar.setDisplayShowHomeEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
priorDisplayName = savedInstanceState.getString("priorDisplayName");
|
||||||
|
priorNote = savedInstanceState.getString("priorNote");
|
||||||
|
isAlreadySaving = savedInstanceState.getBoolean("isAlreadySaving");
|
||||||
|
currentlyPicking = (PickType) savedInstanceState.getSerializable("currentlyPicking");
|
||||||
|
avatarBase64 = savedInstanceState.getString("avatarBase64");
|
||||||
|
headerBase64 = savedInstanceState.getString("headerBase64");
|
||||||
|
} else {
|
||||||
|
priorDisplayName = null;
|
||||||
|
priorNote = null;
|
||||||
|
isAlreadySaving = false;
|
||||||
|
currentlyPicking = PickType.NOTHING;
|
||||||
|
avatarBase64 = null;
|
||||||
|
headerBase64 = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
avatarButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
onMediaPick(PickType.AVATAR);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
headerButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
onMediaPick(PickType.HEADER);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
avatarPreview.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
avatarPreview.setImageBitmap(null);
|
||||||
|
avatarPreview.setVisibility(View.GONE);
|
||||||
|
avatarBase64 = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
headerPreview.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
headerPreview.setImageBitmap(null);
|
||||||
|
headerPreview.setVisibility(View.GONE);
|
||||||
|
headerBase64 = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mastodonAPI.accountVerifyCredentials().enqueue(new Callback<Account>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<Account> call, Response<Account> response) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
onAccountVerifyCredentialsFailed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Account me = response.body();
|
||||||
|
priorDisplayName = me.getDisplayName();
|
||||||
|
priorNote = me.note.toString();
|
||||||
|
displayNameEditText.setText(priorDisplayName);
|
||||||
|
noteEditText.setText(priorNote);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<Account> call, Throwable t) {
|
||||||
|
onAccountVerifyCredentialsFailed();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSaveInstanceState(Bundle outState) {
|
||||||
|
outState.putString("priorDisplayName", priorDisplayName);
|
||||||
|
outState.putString("priorNote", priorNote);
|
||||||
|
outState.putBoolean("isAlreadySaving", isAlreadySaving);
|
||||||
|
outState.putSerializable("currentlyPicking", currentlyPicking);
|
||||||
|
outState.putString("avatarBase64", avatarBase64);
|
||||||
|
outState.putString("headerBase64", headerBase64);
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAccountVerifyCredentialsFailed() {
|
||||||
|
Log.e(TAG, "The account failed to load.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onMediaPick(PickType pickType) {
|
||||||
|
if (currentlyPicking != PickType.NOTHING) {
|
||||||
|
// Ignore inputs if another pick operation is still occurring.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentlyPicking = pickType;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN &&
|
||||||
|
ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
|
ActivityCompat.requestPermissions(this,
|
||||||
|
new String[] { Manifest.permission.READ_EXTERNAL_STORAGE },
|
||||||
|
PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);
|
||||||
|
} else {
|
||||||
|
initiateMediaPicking();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
|
||||||
|
@NonNull int[] grantResults) {
|
||||||
|
switch (requestCode) {
|
||||||
|
case PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE: {
|
||||||
|
if (grantResults.length > 0
|
||||||
|
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
initiateMediaPicking();
|
||||||
|
} else {
|
||||||
|
endMediaPicking();
|
||||||
|
Snackbar.make(avatarButton, R.string.error_media_upload_permission,
|
||||||
|
Snackbar.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initiateMediaPicking() {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
intent.setType("image/*");
|
||||||
|
switch (currentlyPicking) {
|
||||||
|
case AVATAR: { startActivityForResult(intent, AVATAR_PICK_RESULT); break; }
|
||||||
|
case HEADER: { startActivityForResult(intent, HEADER_PICK_RESULT); break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.edit_profile_toolbar, menu);
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.home: {
|
||||||
|
onBackPressed();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case R.id.action_save: {
|
||||||
|
save();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void save() {
|
||||||
|
if (isAlreadySaving || currentlyPicking != PickType.NOTHING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String newDisplayName = displayNameEditText.getText().toString();
|
||||||
|
if (newDisplayName.isEmpty()) {
|
||||||
|
displayNameEditText.setError(getString(R.string.error_empty));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (priorDisplayName != null && priorDisplayName.equals(newDisplayName)) {
|
||||||
|
// If it's not any different, don't patch it.
|
||||||
|
newDisplayName = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String newNote = noteEditText.getText().toString();
|
||||||
|
if (newNote.isEmpty()) {
|
||||||
|
noteEditText.setError(getString(R.string.error_empty));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (priorNote != null && priorNote.equals(newNote)) {
|
||||||
|
// If it's not any different, don't patch it.
|
||||||
|
newNote = null;
|
||||||
|
}
|
||||||
|
if (newDisplayName == null && newNote == null && avatarBase64 == null
|
||||||
|
&& headerBase64 == null) {
|
||||||
|
// If nothing is changed, then there's nothing to save.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveProgress.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
isAlreadySaving = true;
|
||||||
|
|
||||||
|
Profile profile = new Profile();
|
||||||
|
profile.displayName = newDisplayName;
|
||||||
|
profile.note = newNote;
|
||||||
|
profile.avatar = avatarBase64;
|
||||||
|
profile.header = headerBase64;
|
||||||
|
mastodonAPI.accountUpdateCredentials(profile).enqueue(new Callback<Account>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<Account> call, Response<Account> response) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
onSaveFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
getPrivatePreferences().edit()
|
||||||
|
.putBoolean("refreshProfileHeader", true)
|
||||||
|
.apply();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<Account> call, Throwable t) {
|
||||||
|
onSaveFailure();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSaveFailure() {
|
||||||
|
isAlreadySaving = false;
|
||||||
|
Snackbar.make(avatarButton, R.string.error_media_upload_sending, Snackbar.LENGTH_LONG)
|
||||||
|
.show();
|
||||||
|
saveProgress.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void beginMediaPicking() {
|
||||||
|
switch (currentlyPicking) {
|
||||||
|
case AVATAR: {
|
||||||
|
avatarProgress.setVisibility(View.VISIBLE);
|
||||||
|
avatarPreview.setVisibility(View.INVISIBLE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case HEADER: {
|
||||||
|
headerProgress.setVisibility(View.VISIBLE);
|
||||||
|
headerPreview.setVisibility(View.INVISIBLE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void endMediaPicking() {
|
||||||
|
switch (currentlyPicking) {
|
||||||
|
case AVATAR: {
|
||||||
|
avatarProgress.setVisibility(View.GONE);
|
||||||
|
avatarPreview.setVisibility(View.GONE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case HEADER: {
|
||||||
|
headerProgress.setVisibility(View.GONE);
|
||||||
|
headerPreview.setVisibility(View.GONE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentlyPicking = PickType.NOTHING;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
switch (requestCode) {
|
||||||
|
case AVATAR_PICK_RESULT: {
|
||||||
|
if (resultCode == RESULT_OK && data != null) {
|
||||||
|
CropImage.activity(data.getData())
|
||||||
|
.setInitialCropWindowPaddingRatio(0)
|
||||||
|
.setAspectRatio(AVATAR_WIDTH, AVATAR_HEIGHT)
|
||||||
|
.start(this);
|
||||||
|
} else {
|
||||||
|
endMediaPicking();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case HEADER_PICK_RESULT: {
|
||||||
|
if (resultCode == RESULT_OK && data != null) {
|
||||||
|
CropImage.activity(data.getData())
|
||||||
|
.setInitialCropWindowPaddingRatio(0)
|
||||||
|
.setAspectRatio(HEADER_WIDTH, HEADER_HEIGHT)
|
||||||
|
.start(this);
|
||||||
|
} else {
|
||||||
|
endMediaPicking();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE: {
|
||||||
|
CropImage.ActivityResult result = CropImage.getActivityResult(data);
|
||||||
|
if (resultCode == RESULT_OK) {
|
||||||
|
beginResize(result.getUri());
|
||||||
|
} else if (resultCode == CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE) {
|
||||||
|
onResizeFailure();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void beginResize(Uri uri) {
|
||||||
|
beginMediaPicking();
|
||||||
|
int width, height;
|
||||||
|
switch (currentlyPicking) {
|
||||||
|
default: {
|
||||||
|
throw new AssertionError("PickType not set.");
|
||||||
|
}
|
||||||
|
case AVATAR: {
|
||||||
|
width = AVATAR_WIDTH;
|
||||||
|
height = AVATAR_HEIGHT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case HEADER: {
|
||||||
|
width = HEADER_WIDTH;
|
||||||
|
height = HEADER_HEIGHT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new ResizeImageTask(getContentResolver(), width, height, new ResizeImageTask.Listener() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<Bitmap> contentList) {
|
||||||
|
Bitmap bitmap = contentList.get(0);
|
||||||
|
PickType pickType = currentlyPicking;
|
||||||
|
endMediaPicking();
|
||||||
|
switch (pickType) {
|
||||||
|
case AVATAR: {
|
||||||
|
avatarPreview.setImageBitmap(bitmap);
|
||||||
|
avatarPreview.setVisibility(View.VISIBLE);
|
||||||
|
avatarBase64 = bitmapToBase64(bitmap);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case HEADER: {
|
||||||
|
headerPreview.setImageBitmap(bitmap);
|
||||||
|
headerPreview.setVisibility(View.VISIBLE);
|
||||||
|
headerBase64 = bitmapToBase64(bitmap);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure() {
|
||||||
|
onResizeFailure();
|
||||||
|
}
|
||||||
|
}).execute(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onResizeFailure() {
|
||||||
|
Snackbar.make(avatarButton, R.string.error_media_upload_sending, Snackbar.LENGTH_LONG)
|
||||||
|
.show();
|
||||||
|
endMediaPicking();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String bitmapToBase64(Bitmap bitmap) {
|
||||||
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
|
||||||
|
byte[] byteArray = stream.toByteArray();
|
||||||
|
IOUtils.closeQuietly(stream);
|
||||||
|
return "data:image/png;base64," + Base64.encodeToString(byteArray, Base64.DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ResizeImageTask extends AsyncTask<Uri, Void, Boolean> {
|
||||||
|
private ContentResolver contentResolver;
|
||||||
|
private int resizeWidth;
|
||||||
|
private int resizeHeight;
|
||||||
|
private Listener listener;
|
||||||
|
private List<Bitmap> resultList;
|
||||||
|
|
||||||
|
ResizeImageTask(ContentResolver contentResolver, int width, int height, Listener listener) {
|
||||||
|
this.contentResolver = contentResolver;
|
||||||
|
this.resizeWidth = width;
|
||||||
|
this.resizeHeight = height;
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Boolean doInBackground(Uri... uris) {
|
||||||
|
resultList = new ArrayList<>();
|
||||||
|
for (Uri uri : uris) {
|
||||||
|
InputStream inputStream;
|
||||||
|
try {
|
||||||
|
inputStream = contentResolver.openInputStream(uri);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Bitmap sourceBitmap;
|
||||||
|
try {
|
||||||
|
sourceBitmap = BitmapFactory.decodeStream(inputStream, null, null);
|
||||||
|
} catch (OutOfMemoryError error) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(inputStream);
|
||||||
|
}
|
||||||
|
if (sourceBitmap == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Bitmap bitmap = Bitmap.createScaledBitmap(sourceBitmap, resizeWidth, resizeHeight,
|
||||||
|
false);
|
||||||
|
sourceBitmap.recycle();
|
||||||
|
if (bitmap == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
resultList.add(bitmap);
|
||||||
|
if (isCancelled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Boolean successful) {
|
||||||
|
if (successful) {
|
||||||
|
listener.onSuccess(resultList);
|
||||||
|
} else {
|
||||||
|
listener.onFailure();
|
||||||
|
}
|
||||||
|
super.onPostExecute(successful);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
void onSuccess(List<Bitmap> contentList);
|
||||||
|
void onFailure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
/* Copyright 2017 Andrew Dawson
|
||||||
|
*
|
||||||
|
* This file is a part of Tusky.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||||
|
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||||
|
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
* Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||||
|
* see <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.keylesspalace.tusky.entity.Account;
|
||||||
|
import com.pkmmte.view.CircularImageView;
|
||||||
|
import com.squareup.picasso.Picasso;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
|
class FollowRequestsAdapter extends AccountAdapter {
|
||||||
|
private static final int VIEW_TYPE_FOLLOW_REQUEST = 0;
|
||||||
|
private static final int VIEW_TYPE_FOOTER = 1;
|
||||||
|
|
||||||
|
FollowRequestsAdapter(AccountActionListener accountActionListener) {
|
||||||
|
super(accountActionListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
switch (viewType) {
|
||||||
|
default:
|
||||||
|
case VIEW_TYPE_FOLLOW_REQUEST: {
|
||||||
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
|
.inflate(R.layout.item_follow_request, parent, false);
|
||||||
|
return new FollowRequestViewHolder(view);
|
||||||
|
}
|
||||||
|
case VIEW_TYPE_FOOTER: {
|
||||||
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
|
.inflate(R.layout.item_footer, parent, false);
|
||||||
|
return new FooterViewHolder(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
|
||||||
|
if (position < accountList.size()) {
|
||||||
|
FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder;
|
||||||
|
holder.setupWithAccount(accountList.get(position));
|
||||||
|
holder.setupActionListener(accountActionListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
if (position == accountList.size()) {
|
||||||
|
return VIEW_TYPE_FOOTER;
|
||||||
|
} else {
|
||||||
|
return VIEW_TYPE_FOLLOW_REQUEST;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class FollowRequestViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
@BindView(R.id.follow_request_avatar) CircularImageView avatar;
|
||||||
|
@BindView(R.id.follow_request_username) TextView username;
|
||||||
|
@BindView(R.id.follow_request_display_name) TextView displayName;
|
||||||
|
@BindView(R.id.follow_request_accept) ImageButton accept;
|
||||||
|
@BindView(R.id.follow_request_reject) ImageButton reject;
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
FollowRequestViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
ButterKnife.bind(this, itemView);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupWithAccount(Account account) {
|
||||||
|
id = account.id;
|
||||||
|
displayName.setText(account.getDisplayName());
|
||||||
|
String format = username.getContext().getString(R.string.status_username_format);
|
||||||
|
String formattedUsername = String.format(format, account.username);
|
||||||
|
username.setText(formattedUsername);
|
||||||
|
Picasso.with(avatar.getContext())
|
||||||
|
.load(account.avatar)
|
||||||
|
.error(R.drawable.avatar_error)
|
||||||
|
.placeholder(R.drawable.avatar_default)
|
||||||
|
.into(avatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupActionListener(final AccountActionListener listener) {
|
||||||
|
accept.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
int position = getAdapterPosition();
|
||||||
|
if (position != RecyclerView.NO_POSITION) {
|
||||||
|
listener.onRespondToFollowRequest(true, id, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
reject.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
int position = getAdapterPosition();
|
||||||
|
if (position != RecyclerView.NO_POSITION) {
|
||||||
|
listener.onRespondToFollowRequest(false, id, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
avatar.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
listener.onViewAccount(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,8 @@ class FooterViewHolder extends RecyclerView.ViewHolder {
|
||||||
FooterViewHolder(View itemView) {
|
FooterViewHolder(View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
ProgressBar progressBar = (ProgressBar) itemView.findViewById(R.id.footer_progress_bar);
|
ProgressBar progressBar = (ProgressBar) itemView.findViewById(R.id.footer_progress_bar);
|
||||||
|
if (progressBar != null) {
|
||||||
progressBar.setIndeterminate(true);
|
progressBar.setIndeterminate(true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -19,6 +19,7 @@ import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
class IOUtils {
|
class IOUtils {
|
||||||
static void closeQuietly(@Nullable InputStream stream) {
|
static void closeQuietly(@Nullable InputStream stream) {
|
||||||
|
@ -30,4 +31,14 @@ class IOUtils {
|
||||||
// intentionally unhandled
|
// intentionally unhandled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void closeQuietly(@Nullable OutputStream stream) {
|
||||||
|
try {
|
||||||
|
if (stream != null) {
|
||||||
|
stream.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// intentionally unhandled
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
82
app/src/main/java/com/keylesspalace/tusky/LinkHelper.java
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
/* 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.preference.PreferenceManager;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.text.method.LinkMovementMethod;
|
||||||
|
import android.text.style.ClickableSpan;
|
||||||
|
import android.text.style.URLSpan;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.keylesspalace.tusky.entity.Status;
|
||||||
|
|
||||||
|
class LinkHelper {
|
||||||
|
static void setClickableText(TextView view, Spanned content,
|
||||||
|
@Nullable Status.Mention[] mentions,
|
||||||
|
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);
|
||||||
|
int end = builder.getSpanEnd(span);
|
||||||
|
int flags = builder.getSpanFlags(span);
|
||||||
|
CharSequence text = builder.subSequence(start, end);
|
||||||
|
if (text.charAt(0) == '#') {
|
||||||
|
final String tag = text.subSequence(1, text.length()).toString();
|
||||||
|
ClickableSpan newSpan = new ClickableSpan() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View widget) {
|
||||||
|
listener.onViewTag(tag);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
builder.removeSpan(span);
|
||||||
|
builder.setSpan(newSpan, start, end, flags);
|
||||||
|
} else if (text.charAt(0) == '@' && mentions != null) {
|
||||||
|
final String accountUsername = text.subSequence(1, text.length()).toString();
|
||||||
|
String id = null;
|
||||||
|
for (Status.Mention mention : mentions) {
|
||||||
|
if (mention.localUsername.equals(accountUsername)) {
|
||||||
|
id = mention.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (id != null) {
|
||||||
|
final String accountId = id;
|
||||||
|
ClickableSpan newSpan = new ClickableSpan() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View widget) {
|
||||||
|
listener.onViewAccount(accountId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
builder.removeSpan(span);
|
||||||
|
builder.setSpan(newSpan, start, end, flags);
|
||||||
|
}
|
||||||
|
} else if (useCustomTabs) {
|
||||||
|
ClickableSpan newSpan = new CustomTabURLSpan(span.getURL());
|
||||||
|
builder.removeSpan(span);
|
||||||
|
builder.setSpan(newSpan, start, end, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
view.setText(builder);
|
||||||
|
view.setLinksClickable(true);
|
||||||
|
view.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
|
}
|
||||||
|
}
|
21
app/src/main/java/com/keylesspalace/tusky/LinkListener.java
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/* 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;
|
||||||
|
|
||||||
|
interface LinkListener {
|
||||||
|
void onViewTag(String tag);
|
||||||
|
void onViewAccount(String id);
|
||||||
|
}
|
|
@ -16,15 +16,17 @@
|
||||||
package com.keylesspalace.tusky;
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.customtabs.CustomTabsIntent;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.text.method.LinkMovementMethod;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
@ -110,26 +112,6 @@ public class LoginActivity extends AppCompatActivity {
|
||||||
textView.setMovementMethod(LinkMovementMethod.getInstance());
|
textView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Apply any updates needed.
|
|
||||||
int versionCode = 1;
|
|
||||||
try {
|
|
||||||
versionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode;
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
Log.e(TAG, "The app version was not found. " + e.getMessage());
|
|
||||||
}
|
|
||||||
if (preferences.getInt("lastUpdateVersion", 0) != versionCode) {
|
|
||||||
SharedPreferences.Editor editor = preferences.edit();
|
|
||||||
if (versionCode == 14) {
|
|
||||||
/* This version switches the order of scheme and host in the OAuth redirect URI.
|
|
||||||
* But to fix it requires forcing the app to re-authenticate with servers. So, clear
|
|
||||||
* out the stored client id/secret pairs. The only other things that are lost are
|
|
||||||
* "rememberedVisibility", "loggedInUsername", and "loggedInAccountId". */
|
|
||||||
editor.clear();
|
|
||||||
}
|
|
||||||
editor.putInt("lastUpdateVersion", versionCode);
|
|
||||||
editor.apply();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -201,10 +183,10 @@ public class LoginActivity extends AppCompatActivity {
|
||||||
AppCredentials credentials = response.body();
|
AppCredentials credentials = response.body();
|
||||||
clientId = credentials.clientId;
|
clientId = credentials.clientId;
|
||||||
clientSecret = credentials.clientSecret;
|
clientSecret = credentials.clientSecret;
|
||||||
SharedPreferences.Editor editor = preferences.edit();
|
preferences.edit()
|
||||||
editor.putString(domain + "/client_id", clientId);
|
.putString(domain + "/client_id", clientId)
|
||||||
editor.putString(domain + "/client_secret", clientSecret);
|
.putString(domain + "/client_secret", clientSecret)
|
||||||
editor.apply();
|
.apply();
|
||||||
redirectUserToAuthorizeAndLogin(editText);
|
redirectUserToAuthorizeAndLogin(editText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,7 +208,6 @@ public class LoginActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chain together the key-value pairs into a query string, for either appending to a URL or
|
* Chain together the key-value pairs into a query string, for either appending to a URL or
|
||||||
* as the content of an HTTP request.
|
* as the content of an HTTP request.
|
||||||
|
@ -245,6 +226,36 @@ public class LoginActivity extends AppCompatActivity {
|
||||||
return s.toString();
|
return s.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean openInCustomTab(Uri uri, Context context) {
|
||||||
|
boolean lightTheme = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
.getBoolean("lightTheme", false);
|
||||||
|
int toolbarColorRes;
|
||||||
|
if (lightTheme) {
|
||||||
|
toolbarColorRes = R.color.custom_tab_toolbar_light;
|
||||||
|
} else {
|
||||||
|
toolbarColorRes = R.color.custom_tab_toolbar_dark;
|
||||||
|
}
|
||||||
|
int toolbarColor = ContextCompat.getColor(context, toolbarColorRes);
|
||||||
|
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
|
||||||
|
builder.setToolbarColor(toolbarColor);
|
||||||
|
CustomTabsIntent customTabsIntent = builder.build();
|
||||||
|
try {
|
||||||
|
String packageName = CustomTabsHelper.getPackageNameToUse(context);
|
||||||
|
/* If we cant find a package name, it means theres no browser that supports
|
||||||
|
* Chrome Custom Tabs installed. So, we fallback to the webview */
|
||||||
|
if (packageName == null) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
customTabsIntent.intent.setPackage(packageName);
|
||||||
|
customTabsIntent.launchUrl(context, uri);
|
||||||
|
}
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Log.w("URLSpan", "Activity was not found for intent, " + customTabsIntent.toString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void redirectUserToAuthorizeAndLogin(EditText editText) {
|
private void redirectUserToAuthorizeAndLogin(EditText editText) {
|
||||||
/* To authorize this app and log in it's necessary to redirect to the domain given,
|
/* To authorize this app and log in it's necessary to redirect to the domain given,
|
||||||
* activity_login there, and the server will redirect back to the app with its response. */
|
* activity_login there, and the server will redirect back to the app with its response. */
|
||||||
|
@ -256,23 +267,26 @@ public class LoginActivity extends AppCompatActivity {
|
||||||
parameters.put("response_type", "code");
|
parameters.put("response_type", "code");
|
||||||
parameters.put("scope", OAUTH_SCOPES);
|
parameters.put("scope", OAUTH_SCOPES);
|
||||||
String url = "https://" + domain + endpoint + "?" + toQueryString(parameters);
|
String url = "https://" + domain + endpoint + "?" + toQueryString(parameters);
|
||||||
Intent viewIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
Uri uri = Uri.parse(url);
|
||||||
|
if (!openInCustomTab(uri, this)) {
|
||||||
|
Intent viewIntent = new Intent(Intent.ACTION_VIEW, uri);
|
||||||
if (viewIntent.resolveActivity(getPackageManager()) != null) {
|
if (viewIntent.resolveActivity(getPackageManager()) != null) {
|
||||||
startActivity(viewIntent);
|
startActivity(viewIntent);
|
||||||
} else {
|
} else {
|
||||||
editText.setError(getString(R.string.error_no_web_browser_found));
|
editText.setError(getString(R.string.error_no_web_browser_found));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStop() {
|
protected void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
if (domain != null) {
|
if (domain != null) {
|
||||||
SharedPreferences.Editor editor = preferences.edit();
|
preferences.edit()
|
||||||
editor.putString("domain", domain);
|
.putString("domain", domain)
|
||||||
editor.putString("clientId", clientId);
|
.putString("clientId", clientId)
|
||||||
editor.putString("clientSecret", clientSecret);
|
.putString("clientSecret", clientSecret)
|
||||||
editor.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,10 +361,14 @@ public class LoginActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onLoginSuccess(String accessToken) {
|
private void onLoginSuccess(String accessToken) {
|
||||||
SharedPreferences.Editor editor = preferences.edit();
|
boolean committed = preferences.edit()
|
||||||
editor.putString("domain", domain);
|
.putString("domain", domain)
|
||||||
editor.putString("accessToken", accessToken);
|
.putString("accessToken", accessToken)
|
||||||
editor.commit();
|
.commit();
|
||||||
|
if (!committed) {
|
||||||
|
editText.setError(getString(R.string.error_retrieving_oauth_token));
|
||||||
|
return;
|
||||||
|
}
|
||||||
Intent intent = new Intent(this, MainActivity.class);
|
Intent intent = new Intent(this, MainActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
finish();
|
finish();
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
package com.keylesspalace.tusky;
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
@ -24,8 +23,11 @@ import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.PersistableBundle;
|
import android.os.PersistableBundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.support.design.widget.FloatingActionButton;
|
import android.support.design.widget.FloatingActionButton;
|
||||||
import android.support.design.widget.TabLayout;
|
import android.support.design.widget.TabLayout;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v4.view.ViewPager;
|
import android.support.v4.view.ViewPager;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
|
@ -64,8 +66,9 @@ import retrofit2.Call;
|
||||||
import retrofit2.Callback;
|
import retrofit2.Callback;
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
|
|
||||||
public class MainActivity extends BaseActivity {
|
public class MainActivity extends BaseActivity implements SFragment.OnUserRemovedListener {
|
||||||
private static final String TAG = "MainActivity"; // logging tag and Volley request tag
|
private static final String TAG = "MainActivity"; // logging tag
|
||||||
|
protected static int COMPOSE_RESULT = 1;
|
||||||
|
|
||||||
private String loggedInAccountId;
|
private String loggedInAccountId;
|
||||||
private String loggedInAccountUsername;
|
private String loggedInAccountUsername;
|
||||||
|
@ -99,7 +102,7 @@ public class MainActivity extends BaseActivity {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
Intent intent = new Intent(getApplicationContext(), ComposeActivity.class);
|
Intent intent = new Intent(getApplicationContext(), ComposeActivity.class);
|
||||||
startActivity(intent);
|
startActivityForResult(intent, COMPOSE_RESULT);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -184,7 +187,11 @@ public class MainActivity extends BaseActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup push notifications
|
// Setup push notifications
|
||||||
if (arePushNotificationsEnabled()) enablePushNotifications();
|
if (arePushNotificationsEnabled()) {
|
||||||
|
enablePushNotifications();
|
||||||
|
} else {
|
||||||
|
disablePushNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
composeButton = floatingBtn;
|
composeButton = floatingBtn;
|
||||||
}
|
}
|
||||||
|
@ -193,12 +200,24 @@ public class MainActivity extends BaseActivity {
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
SharedPreferences notificationPreferences = getApplicationContext().getSharedPreferences("Notifications", MODE_PRIVATE);
|
SharedPreferences notificationPreferences = getApplicationContext()
|
||||||
SharedPreferences.Editor editor = notificationPreferences.edit();
|
.getSharedPreferences("Notifications", MODE_PRIVATE);
|
||||||
editor.putString("current", "[]");
|
notificationPreferences.edit()
|
||||||
editor.apply();
|
.putString("current", "[]")
|
||||||
|
.apply();
|
||||||
|
|
||||||
((NotificationManager) (getSystemService(NOTIFICATION_SERVICE))).cancel(MyFirebaseMessagingService.NOTIFY_ID);
|
((NotificationManager) (getSystemService(NOTIFICATION_SERVICE)))
|
||||||
|
.cancel(MessagingService.NOTIFY_ID);
|
||||||
|
|
||||||
|
/* After editing a profile, the profile header in the navigation drawer needs to be
|
||||||
|
* refreshed */
|
||||||
|
SharedPreferences preferences = getPrivatePreferences();
|
||||||
|
if (preferences.getBoolean("refreshProfileHeader", false)) {
|
||||||
|
fetchUserInfo();
|
||||||
|
preferences.edit()
|
||||||
|
.putBoolean("refreshProfileHeader", false)
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -254,6 +273,9 @@ public class MainActivity extends BaseActivity {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Drawable muteDrawable = ContextCompat.getDrawable(this, R.drawable.ic_mute_24dp);
|
||||||
|
ThemeUtils.setDrawableTint(this, muteDrawable, R.attr.toolbar_icon_tint);
|
||||||
|
|
||||||
drawer = new DrawerBuilder()
|
drawer = new DrawerBuilder()
|
||||||
.withActivity(this)
|
.withActivity(this)
|
||||||
//.withToolbar(toolbar)
|
//.withToolbar(toolbar)
|
||||||
|
@ -261,12 +283,13 @@ public class MainActivity extends BaseActivity {
|
||||||
.withHasStableIds(true)
|
.withHasStableIds(true)
|
||||||
.withSelectedItem(-1)
|
.withSelectedItem(-1)
|
||||||
.addDrawerItems(
|
.addDrawerItems(
|
||||||
new PrimaryDrawerItem().withIdentifier(0).withName(R.string.action_view_profile).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_person),
|
new PrimaryDrawerItem().withIdentifier(0).withName(getString(R.string.action_edit_profile)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_person),
|
||||||
new PrimaryDrawerItem().withIdentifier(1).withName(getString(R.string.action_view_favourites)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_star),
|
new PrimaryDrawerItem().withIdentifier(1).withName(getString(R.string.action_view_favourites)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_star),
|
||||||
new PrimaryDrawerItem().withIdentifier(2).withName(getString(R.string.action_view_blocks)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_block),
|
new PrimaryDrawerItem().withIdentifier(2).withName(getString(R.string.action_view_mutes)).withSelectable(false).withIcon(muteDrawable),
|
||||||
|
new PrimaryDrawerItem().withIdentifier(3).withName(getString(R.string.action_view_blocks)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_block),
|
||||||
new DividerDrawerItem(),
|
new DividerDrawerItem(),
|
||||||
new SecondaryDrawerItem().withIdentifier(3).withName(getString(R.string.action_view_preferences)).withSelectable(false),
|
new SecondaryDrawerItem().withIdentifier(4).withName(getString(R.string.action_view_preferences)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_settings),
|
||||||
new SecondaryDrawerItem().withIdentifier(4).withName(getString(R.string.action_logout)).withSelectable(false)
|
new SecondaryDrawerItem().withIdentifier(5).withName(getString(R.string.action_logout)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_exit_to_app)
|
||||||
)
|
)
|
||||||
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
|
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -275,22 +298,28 @@ public class MainActivity extends BaseActivity {
|
||||||
long drawerItemIdentifier = drawerItem.getIdentifier();
|
long drawerItemIdentifier = drawerItem.getIdentifier();
|
||||||
|
|
||||||
if (drawerItemIdentifier == 0) {
|
if (drawerItemIdentifier == 0) {
|
||||||
if (loggedInAccountId != null) {
|
Intent intent = new Intent(MainActivity.this, EditProfileActivity.class);
|
||||||
Intent intent = new Intent(MainActivity.this, AccountActivity.class);
|
|
||||||
intent.putExtra("id", loggedInAccountId);
|
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
|
||||||
} else if (drawerItemIdentifier == 1) {
|
} else if (drawerItemIdentifier == 1) {
|
||||||
Intent intent = new Intent(MainActivity.this, FavouritesActivity.class);
|
Intent intent = new Intent(MainActivity.this, FavouritesActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
} else if (drawerItemIdentifier == 2) {
|
} else if (drawerItemIdentifier == 2) {
|
||||||
Intent intent = new Intent(MainActivity.this, BlocksActivity.class);
|
Intent intent = new Intent(MainActivity.this, AccountListActivity.class);
|
||||||
|
intent.putExtra("type", AccountListActivity.Type.MUTES);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
} else if (drawerItemIdentifier == 3) {
|
} else if (drawerItemIdentifier == 3) {
|
||||||
Intent intent = new Intent(MainActivity.this, PreferencesActivity.class);
|
Intent intent = new Intent(MainActivity.this, AccountListActivity.class);
|
||||||
|
intent.putExtra("type", AccountListActivity.Type.BLOCKS);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
} else if (drawerItemIdentifier == 4) {
|
} else if (drawerItemIdentifier == 4) {
|
||||||
|
Intent intent = new Intent(MainActivity.this, PreferencesActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
} else if (drawerItemIdentifier == 5) {
|
||||||
logout();
|
logout();
|
||||||
|
} else if (drawerItemIdentifier == 6) {
|
||||||
|
Intent intent = new Intent(MainActivity.this, AccountListActivity.class);
|
||||||
|
intent.putExtra("type", AccountListActivity.Type.FOLLOW_REQUESTS);
|
||||||
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,11 +332,10 @@ public class MainActivity extends BaseActivity {
|
||||||
private void logout() {
|
private void logout() {
|
||||||
if (arePushNotificationsEnabled()) disablePushNotifications();
|
if (arePushNotificationsEnabled()) disablePushNotifications();
|
||||||
|
|
||||||
SharedPreferences preferences = getSharedPreferences(getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
getPrivatePreferences().edit()
|
||||||
SharedPreferences.Editor editor = preferences.edit();
|
.remove("domain")
|
||||||
editor.remove("domain");
|
.remove("accessToken")
|
||||||
editor.remove("accessToken");
|
.apply();
|
||||||
editor.apply();
|
|
||||||
|
|
||||||
Intent intent = new Intent(MainActivity.this, LoginActivity.class);
|
Intent intent = new Intent(MainActivity.this, LoginActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
|
@ -377,9 +405,7 @@ public class MainActivity extends BaseActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSearchAction(String currentQuery) {
|
public void onSearchAction(String currentQuery) {}
|
||||||
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
searchView.setOnBindSuggestionCallback(new SearchSuggestionsAdapter.OnBindSuggestionCallback() {
|
searchView.setOnBindSuggestionCallback(new SearchSuggestionsAdapter.OnBindSuggestionCallback() {
|
||||||
|
@ -404,8 +430,7 @@ public class MainActivity extends BaseActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fetchUserInfo() {
|
private void fetchUserInfo() {
|
||||||
SharedPreferences preferences = getSharedPreferences(
|
SharedPreferences preferences = getPrivatePreferences();
|
||||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
|
||||||
final String domain = preferences.getString("domain", null);
|
final String domain = preferences.getString("domain", null);
|
||||||
String id = preferences.getString("loggedInAccountId", null);
|
String id = preferences.getString("loggedInAccountId", null);
|
||||||
String username = preferences.getString("loggedInAccountUsername", null);
|
String username = preferences.getString("loggedInAccountUsername", null);
|
||||||
|
@ -422,14 +447,26 @@ public class MainActivity extends BaseActivity {
|
||||||
onFetchUserInfoFailure(new Exception(response.message()));
|
onFetchUserInfoFailure(new Exception(response.message()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
onFetchUserInfoSuccess(response.body(), domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<Account> call, Throwable t) {
|
||||||
|
onFetchUserInfoFailure((Exception) t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onFetchUserInfoSuccess(Account me, String domain) {
|
||||||
|
// Add the header image and avatar from the account, into the navigation drawer header.
|
||||||
|
headerResult.clear();
|
||||||
|
|
||||||
Account me = response.body();
|
|
||||||
ImageView background = headerResult.getHeaderBackgroundView();
|
ImageView background = headerResult.getHeaderBackgroundView();
|
||||||
int backgroundWidth = background.getWidth();
|
int backgroundWidth = background.getWidth();
|
||||||
int backgroundHeight = background.getHeight();
|
int backgroundHeight = background.getHeight();
|
||||||
if (backgroundWidth == 0 || backgroundHeight == 0) {
|
if (backgroundWidth == 0 || backgroundHeight == 0) {
|
||||||
/* The header ImageView may not be layed out when the verify credentials call
|
/* The header ImageView may not be layed out when the verify credentials call returns so
|
||||||
* returns so measure the dimensions and use those. */
|
* measure the dimensions and use those. */
|
||||||
background.measure(View.MeasureSpec.EXACTLY, View.MeasureSpec.EXACTLY);
|
background.measure(View.MeasureSpec.EXACTLY, View.MeasureSpec.EXACTLY);
|
||||||
backgroundWidth = background.getMeasuredWidth();
|
backgroundWidth = background.getMeasuredWidth();
|
||||||
backgroundHeight = background.getMeasuredHeight();
|
backgroundHeight = background.getMeasuredHeight();
|
||||||
|
@ -449,31 +486,40 @@ public class MainActivity extends BaseActivity {
|
||||||
.withIcon(me.avatar)
|
.withIcon(me.avatar)
|
||||||
);
|
);
|
||||||
|
|
||||||
onFetchUserInfoSuccess(me.id, me.username);
|
// Show follow requests in the menu, if this is a locked account.
|
||||||
|
if (me.locked) {
|
||||||
|
PrimaryDrawerItem followRequestsItem = new PrimaryDrawerItem()
|
||||||
|
.withIdentifier(6)
|
||||||
|
.withName(R.string.action_view_follow_requests)
|
||||||
|
.withSelectable(false)
|
||||||
|
.withIcon(GoogleMaterial.Icon.gmd_person_add);
|
||||||
|
drawer.addItemAtPosition(followRequestsItem, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// Update the current login information.
|
||||||
public void onFailure(Call<Account> call, Throwable t) {
|
loggedInAccountId = me.id;
|
||||||
onFetchUserInfoFailure((Exception) t);
|
loggedInAccountUsername = me.username;
|
||||||
}
|
getPrivatePreferences().edit()
|
||||||
});
|
.putString("loggedInAccountId", loggedInAccountId)
|
||||||
}
|
.putString("loggedInAccountUsername", loggedInAccountUsername)
|
||||||
|
.apply();
|
||||||
private void onFetchUserInfoSuccess(String id, String username) {
|
|
||||||
loggedInAccountId = id;
|
|
||||||
loggedInAccountUsername = username;
|
|
||||||
SharedPreferences preferences = getSharedPreferences(
|
|
||||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
|
||||||
SharedPreferences.Editor editor = preferences.edit();
|
|
||||||
editor.putString("loggedInAccountId", loggedInAccountId);
|
|
||||||
editor.putString("loggedInAccountUsername", loggedInAccountUsername);
|
|
||||||
editor.apply();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFetchUserInfoFailure(Exception exception) {
|
private void onFetchUserInfoFailure(Exception exception) {
|
||||||
Log.e(TAG, "Failed to fetch user info. " + exception.getMessage());
|
Log.e(TAG, "Failed to fetch user info. " + exception.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
if (requestCode == COMPOSE_RESULT && resultCode == ComposeActivity.RESULT_OK) {
|
||||||
|
TimelinePagerAdapter adapter = (TimelinePagerAdapter) viewPager.getAdapter();
|
||||||
|
if (adapter.getCurrentFragment() instanceof SFragment) {
|
||||||
|
((SFragment) adapter.getCurrentFragment()).onSuccessfulStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
if(drawer != null && drawer.isDrawerOpen()) {
|
if(drawer != null && drawer.isDrawerOpen()) {
|
||||||
|
@ -485,4 +531,25 @@ public class MainActivity extends BaseActivity {
|
||||||
viewPager.setCurrentItem(pageHistory.peek());
|
viewPager.setCurrentItem(pageHistory.peek());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
|
||||||
|
@NonNull int[] grantResults) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
TimelinePagerAdapter adapter = (TimelinePagerAdapter) viewPager.getAdapter();
|
||||||
|
for (Fragment fragment : adapter.getRegisteredFragments()) {
|
||||||
|
fragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUserRemoved(String accountId) {
|
||||||
|
TimelinePagerAdapter adapter = (TimelinePagerAdapter) viewPager.getAdapter();
|
||||||
|
for (Fragment fragment : adapter.getRegisteredFragments()) {
|
||||||
|
if (fragment instanceof StatusRemoveListener) {
|
||||||
|
StatusRemoveListener listener = (StatusRemoveListener) fragment;
|
||||||
|
listener.removePostsByUser(accountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -20,6 +20,7 @@ import com.keylesspalace.tusky.entity.Account;
|
||||||
import com.keylesspalace.tusky.entity.AppCredentials;
|
import com.keylesspalace.tusky.entity.AppCredentials;
|
||||||
import com.keylesspalace.tusky.entity.Media;
|
import com.keylesspalace.tusky.entity.Media;
|
||||||
import com.keylesspalace.tusky.entity.Notification;
|
import com.keylesspalace.tusky.entity.Notification;
|
||||||
|
import com.keylesspalace.tusky.entity.Profile;
|
||||||
import com.keylesspalace.tusky.entity.Relationship;
|
import com.keylesspalace.tusky.entity.Relationship;
|
||||||
import com.keylesspalace.tusky.entity.Status;
|
import com.keylesspalace.tusky.entity.Status;
|
||||||
import com.keylesspalace.tusky.entity.StatusContext;
|
import com.keylesspalace.tusky.entity.StatusContext;
|
||||||
|
@ -29,6 +30,7 @@ import java.util.List;
|
||||||
import okhttp3.MultipartBody;
|
import okhttp3.MultipartBody;
|
||||||
import okhttp3.ResponseBody;
|
import okhttp3.ResponseBody;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
|
import retrofit2.http.Body;
|
||||||
import retrofit2.http.DELETE;
|
import retrofit2.http.DELETE;
|
||||||
import retrofit2.http.Field;
|
import retrofit2.http.Field;
|
||||||
import retrofit2.http.FormUrlEncoded;
|
import retrofit2.http.FormUrlEncoded;
|
||||||
|
@ -115,11 +117,7 @@ public interface MastodonAPI {
|
||||||
@GET("api/v1/accounts/verify_credentials")
|
@GET("api/v1/accounts/verify_credentials")
|
||||||
Call<Account> accountVerifyCredentials();
|
Call<Account> accountVerifyCredentials();
|
||||||
@PATCH("api/v1/accounts/update_credentials")
|
@PATCH("api/v1/accounts/update_credentials")
|
||||||
Call<Account> accountUpdateCredentials(
|
Call<Account> accountUpdateCredentials(@Body Profile profile);
|
||||||
@Field("display_name") String displayName,
|
|
||||||
@Field("note") String note,
|
|
||||||
@Field("avatar") String avatar,
|
|
||||||
@Field("header") String header);
|
|
||||||
@GET("api/v1/accounts/search")
|
@GET("api/v1/accounts/search")
|
||||||
Call<List<Account>> searchAccounts(
|
Call<List<Account>> searchAccounts(
|
||||||
@Query("q") String q,
|
@Query("q") String q,
|
||||||
|
|
105
app/src/main/java/com/keylesspalace/tusky/MutesAdapter.java
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.keylesspalace.tusky.entity.Account;
|
||||||
|
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 {
|
||||||
|
private static final int VIEW_TYPE_MUTED_USER = 0;
|
||||||
|
private static final int VIEW_TYPE_FOOTER = 1;
|
||||||
|
|
||||||
|
MutesAdapter(AccountActionListener accountActionListener) {
|
||||||
|
super(accountActionListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
switch (viewType) {
|
||||||
|
default:
|
||||||
|
case VIEW_TYPE_MUTED_USER: {
|
||||||
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
|
.inflate(R.layout.item_muted_user, parent, false);
|
||||||
|
return new MutesAdapter.MutedUserViewHolder(view);
|
||||||
|
}
|
||||||
|
case VIEW_TYPE_FOOTER: {
|
||||||
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
|
.inflate(R.layout.item_footer, parent, false);
|
||||||
|
return new FooterViewHolder(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
|
||||||
|
if (position < accountList.size()) {
|
||||||
|
MutedUserViewHolder holder = (MutedUserViewHolder) viewHolder;
|
||||||
|
holder.setupWithAccount(accountList.get(position));
|
||||||
|
holder.setupActionListener(accountActionListener, true, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
if (position == accountList.size()) {
|
||||||
|
return VIEW_TYPE_FOOTER;
|
||||||
|
} else {
|
||||||
|
return VIEW_TYPE_MUTED_USER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MutedUserViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
@BindView(R.id.muted_user_avatar) CircularImageView avatar;
|
||||||
|
@BindView(R.id.muted_user_username) TextView username;
|
||||||
|
@BindView(R.id.muted_user_display_name) TextView displayName;
|
||||||
|
@BindView(R.id.muted_user_unmute) ImageButton unmute;
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
MutedUserViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
ButterKnife.bind(this, itemView);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupWithAccount(Account account) {
|
||||||
|
id = account.id;
|
||||||
|
displayName.setText(account.getDisplayName());
|
||||||
|
String format = username.getContext().getString(R.string.status_username_format);
|
||||||
|
String formattedUsername = String.format(format, account.username);
|
||||||
|
username.setText(formattedUsername);
|
||||||
|
Picasso.with(avatar.getContext())
|
||||||
|
.load(account.avatar)
|
||||||
|
.error(R.drawable.avatar_error)
|
||||||
|
.placeholder(R.drawable.avatar_default)
|
||||||
|
.into(avatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupActionListener(final AccountActionListener listener, final boolean muted,
|
||||||
|
final int position) {
|
||||||
|
unmute.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
listener.onMute(!muted, id, position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
avatar.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
listener.onViewAccount(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,318 +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.app.NotificationManager;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.provider.Settings;
|
|
||||||
import android.support.v4.app.NotificationCompat;
|
|
||||||
import android.support.v4.app.TaskStackBuilder;
|
|
||||||
import android.text.Spanned;
|
|
||||||
|
|
||||||
import com.google.firebase.messaging.FirebaseMessagingService;
|
|
||||||
import com.google.firebase.messaging.RemoteMessage;
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
import com.keylesspalace.tusky.entity.Notification;
|
|
||||||
import com.squareup.picasso.Picasso;
|
|
||||||
import com.squareup.picasso.Target;
|
|
||||||
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import okhttp3.Interceptor;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import retrofit2.Call;
|
|
||||||
import retrofit2.Callback;
|
|
||||||
import retrofit2.Response;
|
|
||||||
import retrofit2.Retrofit;
|
|
||||||
import retrofit2.converter.gson.GsonConverterFactory;
|
|
||||||
|
|
||||||
public class MyFirebaseMessagingService extends FirebaseMessagingService {
|
|
||||||
private MastodonAPI mastodonAPI;
|
|
||||||
private static final String TAG = "MyFirebaseMessagingService";
|
|
||||||
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()) {
|
|
||||||
buildNotification(response.body());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Call<Notification> call, Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createMastodonAPI() {
|
|
||||||
SharedPreferences preferences = getSharedPreferences(getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
|
||||||
final String domain = preferences.getString("domain", null);
|
|
||||||
final String accessToken = preferences.getString("accessToken", null);
|
|
||||||
|
|
||||||
OkHttpClient okHttpClient = 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())
|
|
||||||
.create();
|
|
||||||
|
|
||||||
Retrofit retrofit = new Retrofit.Builder()
|
|
||||||
.baseUrl("https://" + domain)
|
|
||||||
.client(okHttpClient)
|
|
||||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
mastodonAPI = retrofit.create(MastodonAPI.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String truncateWithEllipses(String string, int limit) {
|
|
||||||
if (string.length() < limit) {
|
|
||||||
return string;
|
|
||||||
} else {
|
|
||||||
return string.substring(0, limit - 3) + "...";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean filterNotification(SharedPreferences preferences,
|
|
||||||
Notification notification) {
|
|
||||||
switch (notification.type) {
|
|
||||||
default:
|
|
||||||
case MENTION: {
|
|
||||||
return preferences.getBoolean("notificationFilterMentions", true);
|
|
||||||
}
|
|
||||||
case FOLLOW: {
|
|
||||||
return preferences.getBoolean("notificationFilterFollows", true);
|
|
||||||
}
|
|
||||||
case REBLOG: {
|
|
||||||
return preferences.getBoolean("notificationFilterReblogs", true);
|
|
||||||
}
|
|
||||||
case FAVOURITE: {
|
|
||||||
return preferences.getBoolean("notificationFilterFavourites", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void buildNotification(Notification body) {
|
|
||||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
|
||||||
final SharedPreferences notificationPreferences = getApplicationContext().getSharedPreferences("Notifications", MODE_PRIVATE);
|
|
||||||
|
|
||||||
if (!filterNotification(preferences, body)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String rawCurrentNotifications = notificationPreferences.getString("current", "[]");
|
|
||||||
JSONArray currentNotifications;
|
|
||||||
|
|
||||||
try {
|
|
||||||
currentNotifications = new JSONArray(rawCurrentNotifications);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
currentNotifications = new JSONArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean alreadyContains = false;
|
|
||||||
|
|
||||||
for(int i = 0; i < currentNotifications.length(); i++) {
|
|
||||||
try {
|
|
||||||
if (currentNotifications.getString(i).equals(body.account.displayName)) {
|
|
||||||
alreadyContains = true;
|
|
||||||
}
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!alreadyContains) {
|
|
||||||
currentNotifications.put(body.account.displayName);
|
|
||||||
}
|
|
||||||
|
|
||||||
SharedPreferences.Editor editor = notificationPreferences.edit();
|
|
||||||
editor.putString("current", currentNotifications.toString());
|
|
||||||
editor.commit();
|
|
||||||
|
|
||||||
Intent resultIntent = new Intent(this, MainActivity.class);
|
|
||||||
resultIntent.putExtra("tab_position", 1);
|
|
||||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
|
|
||||||
stackBuilder.addParentStack(MainActivity.class);
|
|
||||||
stackBuilder.addNextIntent(resultIntent);
|
|
||||||
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
|
||||||
|
|
||||||
Intent deleteIntent = new Intent(this, NotificationClearBroadcastReceiver.class);
|
|
||||||
PendingIntent deletePendingIntent = PendingIntent.getBroadcast(this, 0, deleteIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
|
||||||
|
|
||||||
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
|
|
||||||
.setSmallIcon(R.drawable.ic_notify)
|
|
||||||
.setContentIntent(resultPendingIntent)
|
|
||||||
.setDeleteIntent(deletePendingIntent)
|
|
||||||
.setDefaults(0); // So it doesn't ring twice, notify only in Target callback
|
|
||||||
|
|
||||||
if (currentNotifications.length() == 1) {
|
|
||||||
builder.setContentTitle(titleForType(body))
|
|
||||||
.setContentText(truncateWithEllipses(bodyForType(body), 40));
|
|
||||||
|
|
||||||
Target mTarget = new Target() {
|
|
||||||
@Override
|
|
||||||
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
|
|
||||||
builder.setLargeIcon(bitmap);
|
|
||||||
|
|
||||||
setupPreferences(preferences, builder);
|
|
||||||
|
|
||||||
((NotificationManager) (getSystemService(NOTIFICATION_SERVICE))).notify(NOTIFY_ID, builder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBitmapFailed(Drawable errorDrawable) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPrepareLoad(Drawable placeHolderDrawable) {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Picasso.with(this)
|
|
||||||
.load(body.account.avatar)
|
|
||||||
.placeholder(R.drawable.avatar_default)
|
|
||||||
.transform(new RoundedTransformation(7, 0))
|
|
||||||
.into(mTarget);
|
|
||||||
} else {
|
|
||||||
setupPreferences(preferences, builder);
|
|
||||||
|
|
||||||
try {
|
|
||||||
builder.setContentTitle(String.format(getString(R.string.notification_title_summary), currentNotifications.length()))
|
|
||||||
.setContentText(truncateWithEllipses(joinNames(currentNotifications), 40));
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
builder.setVisibility(android.app.Notification.VISIBILITY_PRIVATE);
|
|
||||||
builder.setCategory(android.app.Notification.CATEGORY_SOCIAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
((NotificationManager) (getSystemService(NOTIFICATION_SERVICE))).notify(NOTIFY_ID, builder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupPreferences(SharedPreferences preferences, NotificationCompat.Builder builder) {
|
|
||||||
if (preferences.getBoolean("notificationAlertSound", true)) {
|
|
||||||
builder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preferences.getBoolean("notificationAlertVibrate", false)) {
|
|
||||||
builder.setVibrate(new long[] { 500, 500 });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preferences.getBoolean("notificationAlertLight", false)) {
|
|
||||||
builder.setLights(0xFF00FF8F, 300, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String joinNames(JSONArray array) throws JSONException {
|
|
||||||
if (array.length() > 3) {
|
|
||||||
return String.format(getString(R.string.notification_summary_large), array.get(0), array.get(1), array.get(2), array.length() - 3);
|
|
||||||
} else if (array.length() == 3) {
|
|
||||||
return String.format(getString(R.string.notification_summary_medium), array.get(0), array.get(1), array.get(2));
|
|
||||||
} else if (array.length() == 2) {
|
|
||||||
return String.format(getString(R.string.notification_summary_small), array.get(0), array.get(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String titleForType(Notification notification) {
|
|
||||||
switch (notification.type) {
|
|
||||||
case MENTION:
|
|
||||||
return String.format(getString(R.string.notification_mention_format), notification.account.getDisplayName());
|
|
||||||
case FOLLOW:
|
|
||||||
return String.format(getString(R.string.notification_follow_format), notification.account.getDisplayName());
|
|
||||||
case FAVOURITE:
|
|
||||||
return String.format(getString(R.string.notification_favourite_format), notification.account.getDisplayName());
|
|
||||||
case REBLOG:
|
|
||||||
return String.format(getString(R.string.notification_reblog_format), notification.account.getDisplayName());
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String bodyForType(Notification notification) {
|
|
||||||
switch (notification.type) {
|
|
||||||
case FOLLOW:
|
|
||||||
return notification.account.username;
|
|
||||||
case MENTION:
|
|
||||||
case FAVOURITE:
|
|
||||||
case REBLOG:
|
|
||||||
return notification.status.content.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
224
app/src/main/java/com/keylesspalace/tusky/NotificationMaker.java
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
/* 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.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
import android.support.v4.app.TaskStackBuilder;
|
||||||
|
|
||||||
|
import com.keylesspalace.tusky.entity.Notification;
|
||||||
|
import com.squareup.picasso.Picasso;
|
||||||
|
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) {
|
||||||
|
final SharedPreferences preferences =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
final SharedPreferences notificationPreferences = context.getSharedPreferences(
|
||||||
|
"Notifications", Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
if (!filterNotification(preferences, body)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String rawCurrentNotifications = notificationPreferences.getString("current", "[]");
|
||||||
|
JSONArray currentNotifications;
|
||||||
|
|
||||||
|
try {
|
||||||
|
currentNotifications = new JSONArray(rawCurrentNotifications);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
currentNotifications = new JSONArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean alreadyContains = false;
|
||||||
|
|
||||||
|
for(int i = 0; i < currentNotifications.length(); i++) {
|
||||||
|
try {
|
||||||
|
if (currentNotifications.getString(i).equals(body.account.getDisplayName())) {
|
||||||
|
alreadyContains = true;
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!alreadyContains) {
|
||||||
|
currentNotifications.put(body.account.getDisplayName());
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationPreferences.edit()
|
||||||
|
.putString("current", currentNotifications.toString())
|
||||||
|
.commit();
|
||||||
|
|
||||||
|
Intent resultIntent = new Intent(context, MainActivity.class);
|
||||||
|
resultIntent.putExtra("tab_position", 1);
|
||||||
|
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
|
||||||
|
stackBuilder.addParentStack(MainActivity.class);
|
||||||
|
stackBuilder.addNextIntent(resultIntent);
|
||||||
|
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
|
||||||
|
Intent deleteIntent = new Intent(context, NotificationClearBroadcastReceiver.class);
|
||||||
|
PendingIntent deletePendingIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
|
|
||||||
|
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
|
||||||
|
.setSmallIcon(R.drawable.ic_notify)
|
||||||
|
.setContentIntent(resultPendingIntent)
|
||||||
|
.setDeleteIntent(deletePendingIntent)
|
||||||
|
.setDefaults(0); // So it doesn't ring twice, notify only in Target callback
|
||||||
|
|
||||||
|
if (currentNotifications.length() == 1) {
|
||||||
|
builder.setContentTitle(titleForType(context, body))
|
||||||
|
.setContentText(truncateWithEllipses(bodyForType(body), 40));
|
||||||
|
|
||||||
|
Target mTarget = new Target() {
|
||||||
|
@Override
|
||||||
|
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
|
||||||
|
builder.setLargeIcon(bitmap);
|
||||||
|
|
||||||
|
setupPreferences(preferences, builder);
|
||||||
|
|
||||||
|
((NotificationManager) (context.getSystemService(Context.NOTIFICATION_SERVICE)))
|
||||||
|
.notify(notifyId, builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBitmapFailed(Drawable errorDrawable) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPrepareLoad(Drawable placeHolderDrawable) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
Picasso.with(context)
|
||||||
|
.load(body.account.avatar)
|
||||||
|
.placeholder(R.drawable.avatar_default)
|
||||||
|
.transform(new RoundedTransformation(7, 0))
|
||||||
|
.into(mTarget);
|
||||||
|
} else {
|
||||||
|
setupPreferences(preferences, builder);
|
||||||
|
try {
|
||||||
|
builder.setContentTitle(String.format(context.getString(R.string.notification_title_summary), currentNotifications.length()))
|
||||||
|
.setContentText(truncateWithEllipses(joinNames(context, currentNotifications), 40));
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
builder.setVisibility(android.app.Notification.VISIBILITY_PRIVATE);
|
||||||
|
builder.setCategory(android.app.Notification.CATEGORY_SOCIAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
((NotificationManager) (context.getSystemService(Context.NOTIFICATION_SERVICE)))
|
||||||
|
.notify(notifyId, builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean filterNotification(SharedPreferences preferences,
|
||||||
|
Notification notification) {
|
||||||
|
switch (notification.type) {
|
||||||
|
default:
|
||||||
|
case MENTION: {
|
||||||
|
return preferences.getBoolean("notificationFilterMentions", true);
|
||||||
|
}
|
||||||
|
case FOLLOW: {
|
||||||
|
return preferences.getBoolean("notificationFilterFollows", true);
|
||||||
|
}
|
||||||
|
case REBLOG: {
|
||||||
|
return preferences.getBoolean("notificationFilterReblogs", true);
|
||||||
|
}
|
||||||
|
case FAVOURITE: {
|
||||||
|
return preferences.getBoolean("notificationFilterFavourites", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String truncateWithEllipses(String string, int limit) {
|
||||||
|
if (string.length() < limit) {
|
||||||
|
return string;
|
||||||
|
} else {
|
||||||
|
return string.substring(0, limit - 3) + "...";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setupPreferences(SharedPreferences preferences,
|
||||||
|
NotificationCompat.Builder builder) {
|
||||||
|
if (preferences.getBoolean("notificationAlertSound", true)) {
|
||||||
|
builder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferences.getBoolean("notificationAlertVibrate", false)) {
|
||||||
|
builder.setVibrate(new long[] { 500, 500 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferences.getBoolean("notificationAlertLight", false)) {
|
||||||
|
builder.setLights(0xFF00FF8F, 300, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static String joinNames(Context context, JSONArray array) throws JSONException {
|
||||||
|
if (array.length() > 3) {
|
||||||
|
return String.format(context.getString(R.string.notification_summary_large), array.get(0), array.get(1), array.get(2), array.length() - 3);
|
||||||
|
} else if (array.length() == 3) {
|
||||||
|
return String.format(context.getString(R.string.notification_summary_medium), array.get(0), array.get(1), array.get(2));
|
||||||
|
} else if (array.length() == 2) {
|
||||||
|
return String.format(context.getString(R.string.notification_summary_small), array.get(0), array.get(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static String titleForType(Context context, Notification notification) {
|
||||||
|
switch (notification.type) {
|
||||||
|
case MENTION:
|
||||||
|
return String.format(context.getString(R.string.notification_mention_format), notification.account.getDisplayName());
|
||||||
|
case FOLLOW:
|
||||||
|
return String.format(context.getString(R.string.notification_follow_format), notification.account.getDisplayName());
|
||||||
|
case FAVOURITE:
|
||||||
|
return String.format(context.getString(R.string.notification_favourite_format), notification.account.getDisplayName());
|
||||||
|
case REBLOG:
|
||||||
|
return String.format(context.getString(R.string.notification_reblog_format), notification.account.getDisplayName());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static String bodyForType(Notification notification) {
|
||||||
|
switch (notification.type) {
|
||||||
|
case FOLLOW:
|
||||||
|
return notification.account.username;
|
||||||
|
case MENTION:
|
||||||
|
case FAVOURITE:
|
||||||
|
case REBLOG:
|
||||||
|
return notification.status.content.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,6 +34,7 @@ import com.keylesspalace.tusky.entity.Status;
|
||||||
import com.squareup.picasso.Picasso;
|
import com.squareup.picasso.Picasso;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
|
class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
|
||||||
|
@ -42,9 +43,16 @@ class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRe
|
||||||
private static final int VIEW_TYPE_STATUS_NOTIFICATION = 2;
|
private static final int VIEW_TYPE_STATUS_NOTIFICATION = 2;
|
||||||
private static final int VIEW_TYPE_FOLLOW = 3;
|
private static final int VIEW_TYPE_FOLLOW = 3;
|
||||||
|
|
||||||
|
enum FooterState {
|
||||||
|
EMPTY,
|
||||||
|
END,
|
||||||
|
LOADING
|
||||||
|
}
|
||||||
|
|
||||||
private List<Notification> notifications;
|
private List<Notification> notifications;
|
||||||
private StatusActionListener statusListener;
|
private StatusActionListener statusListener;
|
||||||
private NotificationActionListener notificationActionListener;
|
private NotificationActionListener notificationActionListener;
|
||||||
|
private FooterState footerState = FooterState.END;
|
||||||
|
|
||||||
NotificationsAdapter(StatusActionListener statusListener,
|
NotificationsAdapter(StatusActionListener statusListener,
|
||||||
NotificationActionListener notificationActionListener) {
|
NotificationActionListener notificationActionListener) {
|
||||||
|
@ -54,6 +62,15 @@ class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRe
|
||||||
this.notificationActionListener = notificationActionListener;
|
this.notificationActionListener = notificationActionListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void setFooterState(FooterState newFooterState) {
|
||||||
|
FooterState oldValue = footerState;
|
||||||
|
footerState = newFooterState;
|
||||||
|
if (footerState != oldValue) {
|
||||||
|
notifyItemChanged(notifications.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
switch (viewType) {
|
switch (viewType) {
|
||||||
|
@ -64,8 +81,24 @@ class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRe
|
||||||
return new StatusViewHolder(view);
|
return new StatusViewHolder(view);
|
||||||
}
|
}
|
||||||
case VIEW_TYPE_FOOTER: {
|
case VIEW_TYPE_FOOTER: {
|
||||||
View view = LayoutInflater.from(parent.getContext())
|
View view;
|
||||||
|
switch (footerState) {
|
||||||
|
default:
|
||||||
|
case LOADING:
|
||||||
|
view = LayoutInflater.from(parent.getContext())
|
||||||
.inflate(R.layout.item_footer, parent, false);
|
.inflate(R.layout.item_footer, parent, false);
|
||||||
|
break;
|
||||||
|
case END: {
|
||||||
|
view = LayoutInflater.from(parent.getContext())
|
||||||
|
.inflate(R.layout.item_footer_end, parent, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EMPTY: {
|
||||||
|
view = LayoutInflater.from(parent.getContext())
|
||||||
|
.inflate(R.layout.item_footer_empty, parent, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
return new FooterViewHolder(view);
|
return new FooterViewHolder(view);
|
||||||
}
|
}
|
||||||
case VIEW_TYPE_STATUS_NOTIFICATION: {
|
case VIEW_TYPE_STATUS_NOTIFICATION: {
|
||||||
|
@ -178,6 +211,18 @@ class NotificationsAdapter extends RecyclerView.Adapter implements AdapterItemRe
|
||||||
notifyItemChanged(position);
|
notifyItemChanged(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeAllByAccountId(String id) {
|
||||||
|
for (int i = 0; i < notifications.size();) {
|
||||||
|
Notification notification = notifications.get(i);
|
||||||
|
if (id.equals(notification.account.id)) {
|
||||||
|
notifications.remove(i);
|
||||||
|
notifyItemRemoved(i);
|
||||||
|
} else {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface NotificationActionListener {
|
interface NotificationActionListener {
|
||||||
void onViewAccount(String id);
|
void onViewAccount(String id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,9 @@ import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.design.widget.FloatingActionButton;
|
||||||
import android.support.design.widget.TabLayout;
|
import android.support.design.widget.TabLayout;
|
||||||
import android.support.v4.widget.SwipeRefreshLayout;
|
import android.support.v4.widget.SwipeRefreshLayout;
|
||||||
import android.support.v7.widget.DividerItemDecoration;
|
import android.support.v7.widget.DividerItemDecoration;
|
||||||
|
@ -39,16 +41,18 @@ import retrofit2.Callback;
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
|
|
||||||
public class NotificationsFragment extends SFragment implements
|
public class NotificationsFragment extends SFragment implements
|
||||||
SwipeRefreshLayout.OnRefreshListener, StatusActionListener,
|
SwipeRefreshLayout.OnRefreshListener, StatusActionListener, StatusRemoveListener,
|
||||||
NotificationsAdapter.NotificationActionListener {
|
NotificationsAdapter.NotificationActionListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
private static final String TAG = "Notifications"; // logging tag
|
private static final String TAG = "Notifications"; // logging tag
|
||||||
|
|
||||||
private SwipeRefreshLayout swipeRefreshLayout;
|
private SwipeRefreshLayout swipeRefreshLayout;
|
||||||
private LinearLayoutManager layoutManager;
|
private LinearLayoutManager layoutManager;
|
||||||
|
private RecyclerView recyclerView;
|
||||||
private EndlessOnScrollListener scrollListener;
|
private EndlessOnScrollListener scrollListener;
|
||||||
private NotificationsAdapter adapter;
|
private NotificationsAdapter adapter;
|
||||||
private TabLayout.OnTabSelectedListener onTabSelectedListener;
|
private TabLayout.OnTabSelectedListener onTabSelectedListener;
|
||||||
private Call<List<Notification>> listCall;
|
private Call<List<Notification>> listCall;
|
||||||
|
private boolean hideFab;
|
||||||
|
|
||||||
public static NotificationsFragment newInstance() {
|
public static NotificationsFragment newInstance() {
|
||||||
NotificationsFragment fragment = new NotificationsFragment();
|
NotificationsFragment fragment = new NotificationsFragment();
|
||||||
|
@ -57,11 +61,6 @@ public class NotificationsFragment extends SFragment implements
|
||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
|
||||||
|
@ -73,7 +72,7 @@ public class NotificationsFragment extends SFragment implements
|
||||||
swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipe_refresh_layout);
|
swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipe_refresh_layout);
|
||||||
swipeRefreshLayout.setOnRefreshListener(this);
|
swipeRefreshLayout.setOnRefreshListener(this);
|
||||||
// Setup the RecyclerView.
|
// Setup the RecyclerView.
|
||||||
RecyclerView recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
|
recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
|
||||||
recyclerView.setHasFixedSize(true);
|
recyclerView.setHasFixedSize(true);
|
||||||
layoutManager = new LinearLayoutManager(context);
|
layoutManager = new LinearLayoutManager(context);
|
||||||
recyclerView.setLayoutManager(layoutManager);
|
recyclerView.setLayoutManager(layoutManager);
|
||||||
|
@ -83,19 +82,7 @@ public class NotificationsFragment extends SFragment implements
|
||||||
R.drawable.status_divider_dark);
|
R.drawable.status_divider_dark);
|
||||||
divider.setDrawable(drawable);
|
divider.setDrawable(drawable);
|
||||||
recyclerView.addItemDecoration(divider);
|
recyclerView.addItemDecoration(divider);
|
||||||
scrollListener = new EndlessOnScrollListener(layoutManager) {
|
|
||||||
@Override
|
|
||||||
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
|
|
||||||
NotificationsAdapter adapter = (NotificationsAdapter) view.getAdapter();
|
|
||||||
Notification notification = adapter.getItem(adapter.getItemCount() - 2);
|
|
||||||
if (notification != null) {
|
|
||||||
sendFetchNotificationsRequest(notification.id, null);
|
|
||||||
} else {
|
|
||||||
sendFetchNotificationsRequest();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
recyclerView.addOnScrollListener(scrollListener);
|
|
||||||
adapter = new NotificationsAdapter(this, this);
|
adapter = new NotificationsAdapter(this, this);
|
||||||
recyclerView.setAdapter(adapter);
|
recyclerView.setAdapter(adapter);
|
||||||
|
|
||||||
|
@ -118,10 +105,49 @@ public class NotificationsFragment extends SFragment implements
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||||
super.onResume();
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
/* This is delayed until onActivityCreated solely because MainActivity.composeButton isn't
|
||||||
|
* guaranteed to be set until then.
|
||||||
|
* Use a modified scroll listener that both loads more notifications as it goes, and hides
|
||||||
|
* the compose button on down-scroll. */
|
||||||
|
MainActivity activity = (MainActivity) getActivity();
|
||||||
|
final FloatingActionButton composeButton = activity.composeButton;
|
||||||
|
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(
|
||||||
|
activity);
|
||||||
|
preferences.registerOnSharedPreferenceChangeListener(this);
|
||||||
|
hideFab = preferences.getBoolean("fabHide", false);
|
||||||
|
scrollListener = new EndlessOnScrollListener(layoutManager) {
|
||||||
|
@Override
|
||||||
|
public void onScrolled(RecyclerView view, int dx, int dy) {
|
||||||
|
super.onScrolled(view, dx, dy);
|
||||||
|
|
||||||
|
if (hideFab) {
|
||||||
|
if (dy > 0 && composeButton.isShown()) {
|
||||||
|
composeButton.hide(); // hides the button if we're scrolling down
|
||||||
|
} else if (dy < 0 && !composeButton.isShown()) {
|
||||||
|
composeButton.show(); // shows it if we are scrolling up
|
||||||
|
}
|
||||||
|
} else if (!composeButton.isShown()) {
|
||||||
|
composeButton.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
|
||||||
|
NotificationsAdapter adapter = (NotificationsAdapter) view.getAdapter();
|
||||||
|
Notification notification = adapter.getItem(adapter.getItemCount() - 2);
|
||||||
|
if (notification != null) {
|
||||||
|
sendFetchNotificationsRequest(notification.id, null);
|
||||||
|
} else {
|
||||||
sendFetchNotificationsRequest();
|
sendFetchNotificationsRequest();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
recyclerView.addOnScrollListener(scrollListener);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
|
@ -142,9 +168,11 @@ public class NotificationsFragment extends SFragment implements
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendFetchNotificationsRequest(final String fromId, String uptoId) {
|
private void sendFetchNotificationsRequest(final String fromId, String uptoId) {
|
||||||
MastodonAPI api = ((BaseActivity) getActivity()).mastodonAPI;
|
if (fromId != null || adapter.getItemCount() <= 1) {
|
||||||
|
adapter.setFooterState(NotificationsAdapter.FooterState.LOADING);
|
||||||
|
}
|
||||||
|
|
||||||
listCall = api.notifications(fromId, uptoId, null);
|
listCall = mastodonAPI.notifications(fromId, uptoId, null);
|
||||||
|
|
||||||
listCall.enqueue(new Callback<List<Notification>>() {
|
listCall.enqueue(new Callback<List<Notification>>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -168,6 +196,10 @@ public class NotificationsFragment extends SFragment implements
|
||||||
sendFetchNotificationsRequest(null, null);
|
sendFetchNotificationsRequest(null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removePostsByUser(String accountId) {
|
||||||
|
adapter.removeAllByAccountId(accountId);
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean findNotification(List<Notification> notifications, String id) {
|
private static boolean findNotification(List<Notification> notifications, String id) {
|
||||||
for (Notification notification : notifications) {
|
for (Notification notification : notifications) {
|
||||||
if (notification.id.equals(id)) {
|
if (notification.id.equals(id)) {
|
||||||
|
@ -192,6 +224,11 @@ public class NotificationsFragment extends SFragment implements
|
||||||
} else {
|
} else {
|
||||||
adapter.update(notifications);
|
adapter.update(notifications);
|
||||||
}
|
}
|
||||||
|
if (notifications.size() == 0 && adapter.getItemCount() == 1) {
|
||||||
|
adapter.setFooterState(NotificationsAdapter.FooterState.EMPTY);
|
||||||
|
} else if (fromId != null) {
|
||||||
|
adapter.setFooterState(NotificationsAdapter.FooterState.END);
|
||||||
|
}
|
||||||
swipeRefreshLayout.setRefreshing(false);
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,4 +282,11 @@ public class NotificationsFragment extends SFragment implements
|
||||||
public void onViewAccount(String id) {
|
public void onViewAccount(String id) {
|
||||||
super.viewAccount(id);
|
super.viewAccount(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
|
if(key.equals("fabHide")) {
|
||||||
|
hideFab = sharedPreferences.getBoolean("fabHide", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,9 +37,12 @@ import javax.net.ssl.TrustManagerFactory;
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
import okhttp3.ConnectionSpec;
|
import okhttp3.ConnectionSpec;
|
||||||
|
import okhttp3.Interceptor;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
class OkHttpUtils {
|
public class OkHttpUtils {
|
||||||
static final String TAG = "OkHttpUtils"; // logging tag
|
static final String TAG = "OkHttpUtils"; // logging tag
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,8 +58,7 @@ class OkHttpUtils {
|
||||||
* TLS 1.1 and 1.2 have to be manually enabled on API levels 16-20.
|
* TLS 1.1 and 1.2 have to be manually enabled on API levels 16-20.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
static OkHttpClient.Builder getCompatibleClientBuilder() {
|
public static OkHttpClient.Builder getCompatibleClientBuilder() {
|
||||||
|
|
||||||
ConnectionSpec fallback = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
ConnectionSpec fallback = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
||||||
.allEnabledCipherSuites()
|
.allEnabledCipherSuites()
|
||||||
.supportsTlsExtensions(true)
|
.supportsTlsExtensions(true)
|
||||||
|
@ -69,16 +71,37 @@ class OkHttpUtils {
|
||||||
specList.add(ConnectionSpec.CLEARTEXT);
|
specList.add(ConnectionSpec.CLEARTEXT);
|
||||||
|
|
||||||
OkHttpClient.Builder builder = new OkHttpClient.Builder()
|
OkHttpClient.Builder builder = new OkHttpClient.Builder()
|
||||||
|
.addInterceptor(getUserAgentInterceptor())
|
||||||
.connectionSpecs(specList);
|
.connectionSpecs(specList);
|
||||||
|
|
||||||
return enableHigherTlsOnPreLollipop(builder);
|
return enableHigherTlsOnPreLollipop(builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
static OkHttpClient getCompatibleClient() {
|
public static OkHttpClient getCompatibleClient() {
|
||||||
return getCompatibleClientBuilder().build();
|
return getCompatibleClientBuilder().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a custom User-Agent that contains Tusky & Android Version to all requests
|
||||||
|
* Example:
|
||||||
|
* User-Agent: Tusky/1.1.2 Android/5.0.2
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
private static Interceptor getUserAgentInterceptor() {
|
||||||
|
return new Interceptor() {
|
||||||
|
@Override
|
||||||
|
public Response intercept(Chain chain) throws IOException {
|
||||||
|
Request originalRequest = chain.request();
|
||||||
|
Request requestWithUserAgent = originalRequest.newBuilder()
|
||||||
|
.header("User-Agent", "Tusky/"+BuildConfig.VERSION_NAME+" Android/"+Build.VERSION.RELEASE)
|
||||||
|
.build();
|
||||||
|
return chain.proceed(requestWithUserAgent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Android version Nougat has a regression where elliptic curve cipher suites are supported, but
|
* Android version Nougat has a regression where elliptic curve cipher suites are supported, but
|
||||||
* only the curve secp256r1 is allowed. So, first it's best to just disable all elliptic
|
* only the curve secp256r1 is allowed. So, first it's best to just disable all elliptic
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
|
|
||||||
package com.keylesspalace.tusky;
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -37,6 +36,7 @@ import java.util.List;
|
||||||
import okhttp3.ResponseBody;
|
import okhttp3.ResponseBody;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.Callback;
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
/* Note from Andrew on Jan. 22, 2017: This class is a design problem for me, so I left it with an
|
/* Note from Andrew on Jan. 22, 2017: This class is a design problem for me, so I left it with an
|
||||||
* awkward name. TimelineFragment and NotificationFragment have significant overlap but the nature
|
* awkward name. TimelineFragment and NotificationFragment have significant overlap but the nature
|
||||||
|
@ -44,22 +44,32 @@ import retrofit2.Callback;
|
||||||
* adapters. I feel like the profile pages and thread viewer, which I haven't made yet, will also
|
* adapters. I feel like the profile pages and thread viewer, which I haven't made yet, will also
|
||||||
* overlap functionality. So, I'm momentarily leaving it and hopefully working on those will clear
|
* overlap functionality. So, I'm momentarily leaving it and hopefully working on those will clear
|
||||||
* up what needs to be where. */
|
* up what needs to be where. */
|
||||||
public class SFragment extends BaseFragment {
|
public abstract class SFragment extends BaseFragment {
|
||||||
|
interface OnUserRemovedListener {
|
||||||
|
void onUserRemoved(String accountId);
|
||||||
|
}
|
||||||
|
|
||||||
protected String loggedInAccountId;
|
protected String loggedInAccountId;
|
||||||
protected String loggedInUsername;
|
protected String loggedInUsername;
|
||||||
|
protected MastodonAPI mastodonAPI;
|
||||||
|
protected OnUserRemovedListener userRemovedListener;
|
||||||
|
protected static int COMPOSE_RESULT = 1;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
SharedPreferences preferences = getContext().getSharedPreferences(
|
SharedPreferences preferences = getPrivatePreferences();
|
||||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
|
||||||
loggedInAccountId = preferences.getString("loggedInAccountId", null);
|
loggedInAccountId = preferences.getString("loggedInAccountId", null);
|
||||||
loggedInUsername = preferences.getString("loggedInAccountUsername", null);
|
loggedInUsername = preferences.getString("loggedInAccountUsername", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MastodonAPI getApi() {
|
@Override
|
||||||
return ((BaseActivity) getActivity()).mastodonAPI;
|
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
BaseActivity activity = (BaseActivity) getActivity();
|
||||||
|
mastodonAPI = activity.mastodonAPI;
|
||||||
|
userRemovedListener = (OnUserRemovedListener) activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void reply(Status status) {
|
protected void reply(Status status) {
|
||||||
|
@ -79,7 +89,18 @@ public class SFragment extends BaseFragment {
|
||||||
intent.putExtra("reply_visibility", replyVisibility);
|
intent.putExtra("reply_visibility", replyVisibility);
|
||||||
intent.putExtra("content_warning", contentWarning);
|
intent.putExtra("content_warning", contentWarning);
|
||||||
intent.putExtra("mentioned_usernames", mentionedUsernames.toArray(new String[0]));
|
intent.putExtra("mentioned_usernames", mentionedUsernames.toArray(new String[0]));
|
||||||
startActivity(intent);
|
startActivityForResult(intent, COMPOSE_RESULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSuccessfulStatus() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
if (requestCode == COMPOSE_RESULT && resultCode == ComposeActivity.RESULT_OK) {
|
||||||
|
onSuccessfulStatus();
|
||||||
|
} else {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void reblog(final Status status, final boolean reblog,
|
protected void reblog(final Status status, final boolean reblog,
|
||||||
|
@ -101,16 +122,14 @@ public class SFragment extends BaseFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Call<Status> call, Throwable t) {
|
public void onFailure(Call<Status> call, Throwable t) {}
|
||||||
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Call<Status> call;
|
Call<Status> call;
|
||||||
if (reblog) {
|
if (reblog) {
|
||||||
call = getApi().reblogStatus(id);
|
call = mastodonAPI.reblogStatus(id);
|
||||||
} else {
|
} else {
|
||||||
call = getApi().unreblogStatus(id);
|
call = mastodonAPI.unreblogStatus(id);
|
||||||
}
|
}
|
||||||
call.enqueue(cb);
|
call.enqueue(cb);
|
||||||
callList.add(call);
|
callList.add(call);
|
||||||
|
@ -135,54 +154,58 @@ public class SFragment extends BaseFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Call<Status> call, Throwable t) {
|
public void onFailure(Call<Status> call, Throwable t) {}
|
||||||
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Call<Status> call;
|
Call<Status> call;
|
||||||
if (favourite) {
|
if (favourite) {
|
||||||
call = getApi().favouriteStatus(id);
|
call = mastodonAPI.favouriteStatus(id);
|
||||||
} else {
|
} else {
|
||||||
call = getApi().unfavouriteStatus(id);
|
call = mastodonAPI.unfavouriteStatus(id);
|
||||||
}
|
}
|
||||||
call.enqueue(cb);
|
call.enqueue(cb);
|
||||||
callList.add(call);
|
callList.add(call);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void block(String id) {
|
private void mute(String id) {
|
||||||
Call<Relationship> call = getApi().blockAccount(id);
|
Call<Relationship> call = mastodonAPI.muteAccount(id);
|
||||||
call.enqueue(new Callback<Relationship>() {
|
call.enqueue(new Callback<Relationship>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call<Relationship> call, retrofit2.Response<Relationship> response) {
|
public void onResponse(Call<Relationship> call, Response<Relationship> response) {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Call<Relationship> call, Throwable t) {
|
public void onFailure(Call<Relationship> call, Throwable t) {}
|
||||||
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
callList.add(call);
|
callList.add(call);
|
||||||
|
userRemovedListener.onUserRemoved(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void block(String id) {
|
||||||
|
Call<Relationship> call = mastodonAPI.blockAccount(id);
|
||||||
|
call.enqueue(new Callback<Relationship>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<Relationship> call, retrofit2.Response<Relationship> response) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<Relationship> call, Throwable t) {}
|
||||||
|
});
|
||||||
|
callList.add(call);
|
||||||
|
userRemovedListener.onUserRemoved(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void delete(String id) {
|
private void delete(String id) {
|
||||||
Call<ResponseBody> call = getApi().deleteStatus(id);
|
Call<ResponseBody> call = mastodonAPI.deleteStatus(id);
|
||||||
call.enqueue(new Callback<ResponseBody>() {
|
call.enqueue(new Callback<ResponseBody>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
|
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Call<ResponseBody> call, Throwable t) {
|
public void onFailure(Call<ResponseBody> call, Throwable t) {}
|
||||||
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
callList.add(call);
|
callList.add(call);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void more(Status status, View view, final AdapterItemRemover adapter,
|
protected void more(final Status status, View view, final AdapterItemRemover adapter,
|
||||||
final int position) {
|
final int position) {
|
||||||
final String id = status.getActionableId();
|
final String id = status.getActionableId();
|
||||||
final String accountId = status.getActionableStatus().account.id;
|
final String accountId = status.getActionableStatus().account.id;
|
||||||
|
@ -201,12 +224,29 @@ public class SFragment extends BaseFragment {
|
||||||
@Override
|
@Override
|
||||||
public boolean onMenuItemClick(MenuItem item) {
|
public boolean onMenuItemClick(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.status_share: {
|
case R.id.status_share_content: {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(status.account.username);
|
||||||
|
sb.append(" - ");
|
||||||
|
sb.append(status.content.toString());
|
||||||
|
|
||||||
|
Intent sendIntent = new Intent();
|
||||||
|
sendIntent.setAction(Intent.ACTION_SEND);
|
||||||
|
sendIntent.putExtra(Intent.EXTRA_TEXT, sb.toString());
|
||||||
|
sendIntent.setType("text/plain");
|
||||||
|
startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_status_content_to)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case R.id.status_share_link: {
|
||||||
Intent sendIntent = new Intent();
|
Intent sendIntent = new Intent();
|
||||||
sendIntent.setAction(Intent.ACTION_SEND);
|
sendIntent.setAction(Intent.ACTION_SEND);
|
||||||
sendIntent.putExtra(Intent.EXTRA_TEXT, statusUrl);
|
sendIntent.putExtra(Intent.EXTRA_TEXT, statusUrl);
|
||||||
sendIntent.setType("text/plain");
|
sendIntent.setType("text/plain");
|
||||||
startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_status_to)));
|
startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_status_link_to)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case R.id.status_mute: {
|
||||||
|
mute(accountId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case R.id.status_block: {
|
case R.id.status_block: {
|
||||||
|
|
|
@ -25,7 +25,7 @@ import com.google.gson.JsonParseException;
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
class SpannedTypeAdapter implements JsonDeserializer<Spanned> {
|
public class SpannedTypeAdapter implements JsonDeserializer<Spanned> {
|
||||||
@Override
|
@Override
|
||||||
public Spanned deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
public Spanned deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||||
return HtmlUtils.fromHtml(Emojione.shortnameToUnicode(json.getAsString(), false));
|
return HtmlUtils.fromHtml(Emojione.shortnameToUnicode(json.getAsString(), false));
|
||||||
|
|
|
@ -19,13 +19,11 @@ import android.view.View;
|
||||||
|
|
||||||
import com.keylesspalace.tusky.entity.Status;
|
import com.keylesspalace.tusky.entity.Status;
|
||||||
|
|
||||||
interface StatusActionListener {
|
interface StatusActionListener extends LinkListener {
|
||||||
void onReply(int position);
|
void onReply(int position);
|
||||||
void onReblog(final boolean reblog, final int position);
|
void onReblog(final boolean reblog, final int position);
|
||||||
void onFavourite(final boolean favourite, final int position);
|
void onFavourite(final boolean favourite, final int position);
|
||||||
void onMore(View view, final int position);
|
void onMore(View view, final int position);
|
||||||
void onViewMedia(String url, Status.MediaAttachment.Type type);
|
void onViewMedia(String url, Status.MediaAttachment.Type type);
|
||||||
void onViewThread(int position);
|
void onViewThread(int position);
|
||||||
void onViewTag(String tag);
|
|
||||||
void onViewAccount(String id);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
/* 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;
|
||||||
|
|
||||||
|
interface StatusRemoveListener {
|
||||||
|
void removePostsByUser(String accountId);
|
||||||
|
}
|
|
@ -16,14 +16,9 @@
|
||||||
package com.keylesspalace.tusky;
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.text.SpannableStringBuilder;
|
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.method.LinkMovementMethod;
|
|
||||||
import android.text.style.ClickableSpan;
|
|
||||||
import android.text.style.URLSpan;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.CompoundButton;
|
import android.widget.CompoundButton;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
|
@ -102,57 +97,10 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setContent(Spanned content, Status.Mention[] mentions,
|
private void setContent(Spanned content, Status.Mention[] mentions,
|
||||||
final StatusActionListener listener) {
|
StatusActionListener listener) {
|
||||||
/* Redirect URLSpan's in the status content to the listener for viewing tag pages and
|
/* Redirect URLSpan's in the status content to the listener for viewing tag pages and
|
||||||
* account pages. */
|
* account pages. */
|
||||||
SpannableStringBuilder builder = new SpannableStringBuilder(content);
|
LinkHelper.setClickableText(this.content, content, mentions, listener);
|
||||||
boolean useCustomTabs = PreferenceManager.getDefaultSharedPreferences(container.getContext()).getBoolean("customTabs", true);
|
|
||||||
URLSpan[] urlSpans = content.getSpans(0, content.length(), URLSpan.class);
|
|
||||||
for (URLSpan span : urlSpans) {
|
|
||||||
int start = builder.getSpanStart(span);
|
|
||||||
int end = builder.getSpanEnd(span);
|
|
||||||
int flags = builder.getSpanFlags(span);
|
|
||||||
CharSequence text = builder.subSequence(start, end);
|
|
||||||
if (text.charAt(0) == '#') {
|
|
||||||
final String tag = text.subSequence(1, text.length()).toString();
|
|
||||||
ClickableSpan newSpan = new ClickableSpan() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View widget) {
|
|
||||||
listener.onViewTag(tag);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
builder.removeSpan(span);
|
|
||||||
builder.setSpan(newSpan, start, end, flags);
|
|
||||||
} else if (text.charAt(0) == '@') {
|
|
||||||
final String accountUsername = text.subSequence(1, text.length()).toString();
|
|
||||||
String id = null;
|
|
||||||
for (Status.Mention mention: mentions) {
|
|
||||||
if (mention.username.equals(accountUsername)) {
|
|
||||||
id = mention.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (id != null) {
|
|
||||||
final String accountId = id;
|
|
||||||
ClickableSpan newSpan = new ClickableSpan() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View widget) {
|
|
||||||
listener.onViewAccount(accountId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
builder.removeSpan(span);
|
|
||||||
builder.setSpan(newSpan, start, end, flags);
|
|
||||||
}
|
|
||||||
} else if (useCustomTabs) {
|
|
||||||
ClickableSpan newSpan = new CustomTabURLSpan(span.getURL());
|
|
||||||
builder.removeSpan(span);
|
|
||||||
builder.setSpan(newSpan, start, end, flags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Set the contents.
|
|
||||||
this.content.setText(builder);
|
|
||||||
// Make links clickable.
|
|
||||||
this.content.setLinksClickable(true);
|
|
||||||
this.content.setMovementMethod(LinkMovementMethod.getInstance());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAvatar(String url) {
|
private void setAvatar(String url) {
|
||||||
|
@ -249,14 +197,23 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
previews[i].setVisibility(View.VISIBLE);
|
previews[i].setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
if(previewUrl == null || previewUrl.isEmpty()) {
|
||||||
|
Picasso.with(context)
|
||||||
|
.load(mediaPreviewUnloadedId)
|
||||||
|
.into(previews[i]);
|
||||||
|
} else {
|
||||||
Picasso.with(context)
|
Picasso.with(context)
|
||||||
.load(previewUrl)
|
.load(previewUrl)
|
||||||
.placeholder(mediaPreviewUnloadedId)
|
.placeholder(mediaPreviewUnloadedId)
|
||||||
.into(previews[i]);
|
.into(previews[i]);
|
||||||
|
}
|
||||||
|
|
||||||
final String url = attachments[i].url;
|
final String url = attachments[i].url;
|
||||||
final Status.MediaAttachment.Type type = attachments[i].type;
|
final Status.MediaAttachment.Type type = attachments[i].type;
|
||||||
|
|
||||||
|
if(url == null || url.isEmpty()) {
|
||||||
|
previews[i].setOnClickListener(null);
|
||||||
|
} else {
|
||||||
previews[i].setOnClickListener(new View.OnClickListener() {
|
previews[i].setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
|
@ -265,6 +222,9 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if (sensitive) {
|
if (sensitive) {
|
||||||
sensitiveMediaWarning.setVisibility(View.VISIBLE);
|
sensitiveMediaWarning.setVisibility(View.VISIBLE);
|
||||||
sensitiveMediaWarning.setOnClickListener(new View.OnClickListener() {
|
sensitiveMediaWarning.setOnClickListener(new View.OnClickListener() {
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
/* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is just a wrapper class for a String.
|
||||||
|
*
|
||||||
|
* It was designed to get around the limitation of a Json deserializer which only allows custom
|
||||||
|
* deserializing based on types, when special handling for a specific field was what was actually
|
||||||
|
* desired (in this case, display names). So, it was most expedient to just make up a type.
|
||||||
|
*/
|
||||||
|
public class StringWithEmoji {
|
||||||
|
public String value;
|
||||||
|
|
||||||
|
public StringWithEmoji(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/* 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 com.emojione.Emojione;
|
||||||
|
import com.google.gson.JsonDeserializationContext;
|
||||||
|
import com.google.gson.JsonDeserializer;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
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> {
|
||||||
|
@Override
|
||||||
|
public StringWithEmoji deserialize(JsonElement json, Type typeOfT,
|
||||||
|
JsonDeserializationContext context) throws JsonParseException {
|
||||||
|
String value = json.getAsString();
|
||||||
|
if (value != null) {
|
||||||
|
return new StringWithEmoji(Emojione.shortnameToUnicode(value, false));
|
||||||
|
} else {
|
||||||
|
return new StringWithEmoji("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -65,20 +65,55 @@ class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
|
||||||
notifyItemRemoved(position);
|
notifyItemRemoved(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
int insertStatus(Status status) {
|
public void removeAllByAccountId(String accountId) {
|
||||||
|
for (int i = 0; i < statuses.size();) {
|
||||||
|
Status status = statuses.get(i);
|
||||||
|
if (accountId.equals(status.account.id)) {
|
||||||
|
statuses.remove(i);
|
||||||
|
notifyItemRemoved(i);
|
||||||
|
} else {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
return statusIndex;
|
||||||
|
}
|
||||||
int i = statusIndex;
|
int i = statusIndex;
|
||||||
statuses.add(i, status);
|
statuses.add(i, status);
|
||||||
notifyItemInserted(i);
|
notifyItemInserted(i);
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
void addAncestors(List<Status> ancestors) {
|
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) {
|
||||||
|
mainStatus = statuses.get(statusIndex);
|
||||||
|
statuses.clear();
|
||||||
|
notifyItemRangeRemoved(0, old_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert newly fetched ancestors
|
||||||
statusIndex = ancestors.size();
|
statusIndex = ancestors.size();
|
||||||
statuses.addAll(0, ancestors);
|
statuses.addAll(0, ancestors);
|
||||||
notifyItemRangeInserted(0, statusIndex);
|
notifyItemRangeInserted(0, statusIndex);
|
||||||
|
|
||||||
|
if (mainStatus != null) {
|
||||||
|
// In case we needed to delete everything (which is way easier than deleting
|
||||||
|
// everything except one), re-insert the remaining status here.
|
||||||
|
statuses.add(statusIndex, mainStatus);
|
||||||
|
notifyItemInserted(statusIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addDescendants(List<Status> descendants) {
|
// Insert newly fetched descendants
|
||||||
int end = statuses.size();
|
int end = statuses.size();
|
||||||
statuses.addAll(descendants);
|
statuses.addAll(descendants);
|
||||||
notifyItemRangeInserted(end, descendants.size());
|
notifyItemRangeInserted(end, descendants.size());
|
||||||
|
|
|
@ -30,8 +30,15 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover
|
||||||
private static final int VIEW_TYPE_STATUS = 0;
|
private static final int VIEW_TYPE_STATUS = 0;
|
||||||
private static final int VIEW_TYPE_FOOTER = 1;
|
private static final int VIEW_TYPE_FOOTER = 1;
|
||||||
|
|
||||||
|
enum FooterState {
|
||||||
|
EMPTY,
|
||||||
|
END,
|
||||||
|
LOADING
|
||||||
|
}
|
||||||
|
|
||||||
private List<Status> statuses;
|
private List<Status> statuses;
|
||||||
private StatusActionListener statusListener;
|
private StatusActionListener statusListener;
|
||||||
|
private FooterState footerState = FooterState.END;
|
||||||
|
|
||||||
TimelineAdapter(StatusActionListener statusListener) {
|
TimelineAdapter(StatusActionListener statusListener) {
|
||||||
super();
|
super();
|
||||||
|
@ -49,13 +56,37 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover
|
||||||
return new StatusViewHolder(view);
|
return new StatusViewHolder(view);
|
||||||
}
|
}
|
||||||
case VIEW_TYPE_FOOTER: {
|
case VIEW_TYPE_FOOTER: {
|
||||||
View view = LayoutInflater.from(viewGroup.getContext())
|
View view;
|
||||||
|
switch (footerState) {
|
||||||
|
default:
|
||||||
|
case LOADING:
|
||||||
|
view = LayoutInflater.from(viewGroup.getContext())
|
||||||
.inflate(R.layout.item_footer, viewGroup, false);
|
.inflate(R.layout.item_footer, viewGroup, false);
|
||||||
|
break;
|
||||||
|
case END: {
|
||||||
|
view = LayoutInflater.from(viewGroup.getContext())
|
||||||
|
.inflate(R.layout.item_footer_end, viewGroup, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EMPTY: {
|
||||||
|
view = LayoutInflater.from(viewGroup.getContext())
|
||||||
|
.inflate(R.layout.item_footer_empty, viewGroup, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
return new FooterViewHolder(view);
|
return new FooterViewHolder(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setFooterState(FooterState newFooterState) {
|
||||||
|
FooterState oldValue = footerState;
|
||||||
|
footerState = newFooterState;
|
||||||
|
if (footerState != oldValue) {
|
||||||
|
notifyItemChanged(statuses.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
|
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
|
||||||
if (position < statuses.size()) {
|
if (position < statuses.size()) {
|
||||||
|
@ -111,6 +142,18 @@ class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover
|
||||||
notifyItemRemoved(position);
|
notifyItemRemoved(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void removeAllByAccountId(String accountId) {
|
||||||
|
for (int i = 0; i < statuses.size();) {
|
||||||
|
Status status = statuses.get(i);
|
||||||
|
if (accountId.equals(status.account.id)) {
|
||||||
|
statuses.remove(i);
|
||||||
|
notifyItemRemoved(i);
|
||||||
|
} else {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
Status getItem(int position) {
|
Status getItem(int position) {
|
||||||
if (position >= 0 && position < statuses.size()) {
|
if (position >= 0 && position < statuses.size()) {
|
||||||
|
|
|
@ -39,7 +39,10 @@ import retrofit2.Call;
|
||||||
import retrofit2.Callback;
|
import retrofit2.Callback;
|
||||||
|
|
||||||
public class TimelineFragment extends SFragment implements
|
public class TimelineFragment extends SFragment implements
|
||||||
SwipeRefreshLayout.OnRefreshListener, StatusActionListener {
|
SwipeRefreshLayout.OnRefreshListener,
|
||||||
|
StatusActionListener,
|
||||||
|
StatusRemoveListener,
|
||||||
|
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
private static final String TAG = "Timeline"; // logging tag
|
private static final String TAG = "Timeline"; // logging tag
|
||||||
|
|
||||||
private Call<List<Status>> listCall;
|
private Call<List<Status>> listCall;
|
||||||
|
@ -61,6 +64,7 @@ public class TimelineFragment extends SFragment implements
|
||||||
private LinearLayoutManager layoutManager;
|
private LinearLayoutManager layoutManager;
|
||||||
private EndlessOnScrollListener scrollListener;
|
private EndlessOnScrollListener scrollListener;
|
||||||
private TabLayout.OnTabSelectedListener onTabSelectedListener;
|
private TabLayout.OnTabSelectedListener onTabSelectedListener;
|
||||||
|
private boolean hideFab;
|
||||||
|
|
||||||
public static TimelineFragment newInstance(Kind kind) {
|
public static TimelineFragment newInstance(Kind kind) {
|
||||||
TimelineFragment fragment = new TimelineFragment();
|
TimelineFragment fragment = new TimelineFragment();
|
||||||
|
@ -145,24 +149,28 @@ public class TimelineFragment extends SFragment implements
|
||||||
|
|
||||||
/* This is delayed until onActivityCreated solely because MainActivity.composeButton isn't
|
/* This is delayed until onActivityCreated solely because MainActivity.composeButton isn't
|
||||||
* guaranteed to be set until then. */
|
* guaranteed to be set until then. */
|
||||||
if (followButtonPresent()) {
|
if (composeButtonPresent()) {
|
||||||
/* Use a modified scroll listener that both loads more statuses as it goes, and hides
|
/* Use a modified scroll listener that both loads more statuses as it goes, and hides
|
||||||
* the follow button on down-scroll. */
|
* the follow button on down-scroll. */
|
||||||
MainActivity activity = (MainActivity) getActivity();
|
MainActivity activity = (MainActivity) getActivity();
|
||||||
final FloatingActionButton composeButton = activity.composeButton;
|
final FloatingActionButton composeButton = activity.composeButton;
|
||||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(
|
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(
|
||||||
activity);
|
activity);
|
||||||
|
preferences.registerOnSharedPreferenceChangeListener(this);
|
||||||
|
hideFab = preferences.getBoolean("fabHide", false);
|
||||||
scrollListener = new EndlessOnScrollListener(layoutManager) {
|
scrollListener = new EndlessOnScrollListener(layoutManager) {
|
||||||
@Override
|
@Override
|
||||||
public void onScrolled(RecyclerView view, int dx, int dy) {
|
public void onScrolled(RecyclerView view, int dx, int dy) {
|
||||||
super.onScrolled(view, dx, dy);
|
super.onScrolled(view, dx, dy);
|
||||||
|
|
||||||
if (preferences.getBoolean("fabHide", false)) {
|
if (hideFab) {
|
||||||
if (dy > 0 && composeButton.isShown()) {
|
if (dy > 0 && composeButton.isShown()) {
|
||||||
composeButton.hide(); // hides the button if we're scrolling down
|
composeButton.hide(); // hides the button if we're scrolling down
|
||||||
} else if (dy < 0 && !composeButton.isShown()) {
|
} else if (dy < 0 && !composeButton.isShown()) {
|
||||||
composeButton.show(); // shows it if we are scrolling up
|
composeButton.show(); // shows it if we are scrolling up
|
||||||
}
|
}
|
||||||
|
} else if (!composeButton.isShown()) {
|
||||||
|
composeButton.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,7 +210,7 @@ public class TimelineFragment extends SFragment implements
|
||||||
return kind != Kind.TAG && kind != Kind.FAVOURITES;
|
return kind != Kind.TAG && kind != Kind.FAVOURITES;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean followButtonPresent() {
|
private boolean composeButtonPresent() {
|
||||||
return kind != Kind.TAG && kind != Kind.FAVOURITES && kind != Kind.USER;
|
return kind != Kind.TAG && kind != Kind.FAVOURITES && kind != Kind.USER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,7 +220,9 @@ public class TimelineFragment extends SFragment implements
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendFetchTimelineRequest(@Nullable final String fromId, @Nullable String uptoId) {
|
private void sendFetchTimelineRequest(@Nullable final String fromId, @Nullable String uptoId) {
|
||||||
MastodonAPI api = ((BaseActivity) getActivity()).mastodonAPI;
|
if (fromId != null || adapter.getItemCount() <= 1) {
|
||||||
|
adapter.setFooterState(TimelineAdapter.FooterState.LOADING);
|
||||||
|
}
|
||||||
|
|
||||||
Callback<List<Status>> cb = new Callback<List<Status>>() {
|
Callback<List<Status>> cb = new Callback<List<Status>>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -233,27 +243,27 @@ public class TimelineFragment extends SFragment implements
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
default:
|
default:
|
||||||
case HOME: {
|
case HOME: {
|
||||||
listCall = api.homeTimeline(fromId, uptoId, null);
|
listCall = mastodonAPI.homeTimeline(fromId, uptoId, null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PUBLIC_FEDERATED: {
|
case PUBLIC_FEDERATED: {
|
||||||
listCall = api.publicTimeline(null, fromId, uptoId, null);
|
listCall = mastodonAPI.publicTimeline(null, fromId, uptoId, null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PUBLIC_LOCAL: {
|
case PUBLIC_LOCAL: {
|
||||||
listCall = api.publicTimeline(true, fromId, uptoId, null);
|
listCall = mastodonAPI.publicTimeline(true, fromId, uptoId, null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TAG: {
|
case TAG: {
|
||||||
listCall = api.hashtagTimeline(hashtagOrId, null, fromId, uptoId, null);
|
listCall = mastodonAPI.hashtagTimeline(hashtagOrId, null, fromId, uptoId, null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case USER: {
|
case USER: {
|
||||||
listCall = api.accountStatuses(hashtagOrId, fromId, uptoId, null);
|
listCall = mastodonAPI.accountStatuses(hashtagOrId, fromId, uptoId, null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FAVOURITES: {
|
case FAVOURITES: {
|
||||||
listCall = api.favourites(fromId, uptoId, null);
|
listCall = mastodonAPI.favourites(fromId, uptoId, null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -265,6 +275,10 @@ public class TimelineFragment extends SFragment implements
|
||||||
sendFetchTimelineRequest(null, null);
|
sendFetchTimelineRequest(null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removePostsByUser(String accountId) {
|
||||||
|
adapter.removeAllByAccountId(accountId);
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean findStatus(List<Status> statuses, String id) {
|
private static boolean findStatus(List<Status> statuses, String id) {
|
||||||
for (Status status : statuses) {
|
for (Status status : statuses) {
|
||||||
if (status.id.equals(id)) {
|
if (status.id.equals(id)) {
|
||||||
|
@ -282,6 +296,11 @@ public class TimelineFragment extends SFragment implements
|
||||||
} else {
|
} else {
|
||||||
adapter.update(statuses);
|
adapter.update(statuses);
|
||||||
}
|
}
|
||||||
|
if (statuses.size() == 0 && adapter.getItemCount() == 1) {
|
||||||
|
adapter.setFooterState(TimelineAdapter.FooterState.EMPTY);
|
||||||
|
} else if(fromId != null) {
|
||||||
|
adapter.setFooterState(TimelineAdapter.FooterState.END);
|
||||||
|
}
|
||||||
swipeRefreshLayout.setRefreshing(false);
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,6 +318,14 @@ public class TimelineFragment extends SFragment implements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccessfulStatus() {
|
||||||
|
if (kind == Kind.HOME || kind == Kind.PUBLIC_FEDERATED || kind == Kind.PUBLIC_LOCAL) {
|
||||||
|
onRefresh();
|
||||||
|
}
|
||||||
|
super.onSuccessfulStatus();
|
||||||
|
}
|
||||||
|
|
||||||
public void onReply(int position) {
|
public void onReply(int position) {
|
||||||
super.reply(adapter.getItem(position));
|
super.reply(adapter.getItem(position));
|
||||||
}
|
}
|
||||||
|
@ -339,4 +366,11 @@ public class TimelineFragment extends SFragment implements
|
||||||
}
|
}
|
||||||
super.viewAccount(id);
|
super.viewAccount(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
|
if(key.equals("fabHide")) {
|
||||||
|
hideFab = sharedPreferences.getBoolean("fabHide", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,35 @@ package com.keylesspalace.tusky;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.app.FragmentPagerAdapter;
|
import android.support.v4.app.FragmentPagerAdapter;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
class TimelinePagerAdapter extends FragmentPagerAdapter {
|
class TimelinePagerAdapter extends FragmentPagerAdapter {
|
||||||
|
private int currentFragmentIndex;
|
||||||
|
private List<Fragment> registeredFragments;
|
||||||
|
|
||||||
TimelinePagerAdapter(FragmentManager manager) {
|
TimelinePagerAdapter(FragmentManager manager) {
|
||||||
super(manager);
|
super(manager);
|
||||||
|
currentFragmentIndex = 0;
|
||||||
|
registeredFragments = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Fragment getCurrentFragment() {
|
||||||
|
return registeredFragments.get(currentFragmentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Fragment> getRegisteredFragments() {
|
||||||
|
return registeredFragments;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPrimaryItem(ViewGroup container, int position, Object object) {
|
||||||
|
if (position != currentFragmentIndex) {
|
||||||
|
currentFragmentIndex = position;
|
||||||
|
}
|
||||||
|
super.setPrimaryItem(container, position, object);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -54,4 +79,17 @@ class TimelinePagerAdapter extends FragmentPagerAdapter {
|
||||||
public CharSequence getPageTitle(int position) {
|
public CharSequence getPageTitle(int position) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object instantiateItem(ViewGroup container, int position) {
|
||||||
|
Fragment fragment = (Fragment) super.instantiateItem(container, position);
|
||||||
|
registeredFragments.add(fragment);
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroyItem(ViewGroup container, int position, Object object) {
|
||||||
|
registeredFragments.remove((Fragment) object);
|
||||||
|
super.destroyItem(container, position, object);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
/* 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.Application;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import com.squareup.picasso.Picasso;
|
||||||
|
import com.jakewharton.picasso.OkHttp3Downloader;
|
||||||
|
|
||||||
|
public class TuskyApplication extends Application {
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
// Initialize Picasso configuration
|
||||||
|
Picasso.Builder builder = new Picasso.Builder(this);
|
||||||
|
builder.downloader(new OkHttp3Downloader(this));
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
builder.listener(new Picasso.Listener() {
|
||||||
|
@Override
|
||||||
|
public void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception) {
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Picasso.setSingletonInstance(builder.build());
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Picasso.with(this).setLoggingEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,9 +15,21 @@
|
||||||
|
|
||||||
package com.keylesspalace.tusky;
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
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;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
|
import android.support.design.widget.Snackbar;
|
||||||
import android.support.v4.app.DialogFragment;
|
import android.support.v4.app.DialogFragment;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
@ -27,6 +39,8 @@ import android.view.WindowManager;
|
||||||
import com.squareup.picasso.Callback;
|
import com.squareup.picasso.Callback;
|
||||||
import com.squareup.picasso.Picasso;
|
import com.squareup.picasso.Picasso;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import uk.co.senab.photoview.PhotoView;
|
import uk.co.senab.photoview.PhotoView;
|
||||||
|
@ -35,6 +49,9 @@ import uk.co.senab.photoview.PhotoViewAttacher;
|
||||||
public class ViewMediaFragment extends DialogFragment {
|
public class ViewMediaFragment extends DialogFragment {
|
||||||
|
|
||||||
private PhotoViewAttacher attacher;
|
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;
|
||||||
|
|
||||||
|
@ -99,6 +116,25 @@ public class ViewMediaFragment extends DialogFragment {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
attacher.setOnLongClickListener(new View.OnLongClickListener() {
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Picasso.with(getContext())
|
Picasso.with(getContext())
|
||||||
.load(url)
|
.load(url)
|
||||||
.into(photoView, new Callback() {
|
.into(photoView, new Callback() {
|
||||||
|
@ -121,4 +157,63 @@ public class ViewMediaFragment extends DialogFragment {
|
||||||
attacher.cleanup();
|
attacher.cleanup();
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void downloadImage(){
|
||||||
|
|
||||||
|
//Permission stuff
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN &&
|
||||||
|
ContextCompat.checkSelfPermission(this.getContext(), android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
|
android.support.v4.app.ActivityCompat.requestPermissions(getActivity(),
|
||||||
|
new String[] { android.Manifest.permission.WRITE_EXTERNAL_STORAGE },
|
||||||
|
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.Request request = new DownloadManager.Request(uri);
|
||||||
|
request.allowScanningByMediaScanner();
|
||||||
|
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES, getString(R.string.app_name) + "/" + filename);
|
||||||
|
|
||||||
|
downloadManager.enqueue(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
|
||||||
|
@NonNull int[] grantResults) {
|
||||||
|
switch (requestCode) {
|
||||||
|
case PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE: {
|
||||||
|
if (grantResults.length > 0
|
||||||
|
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
downloadImage();
|
||||||
|
} else {
|
||||||
|
doErrorDialog(R.string.error_media_download_permission, R.string.action_retry,
|
||||||
|
new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
downloadImage();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,9 @@ import android.view.MenuItem;
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
public class ViewTagActivity extends BaseActivity {
|
public class ViewTagActivity extends BaseActivity implements SFragment.OnUserRemovedListener {
|
||||||
|
private Fragment timelineFragment;
|
||||||
|
|
||||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -51,6 +53,8 @@ public class ViewTagActivity extends BaseActivity {
|
||||||
Fragment fragment = TimelineFragment.newInstance(TimelineFragment.Kind.TAG, hashtag);
|
Fragment fragment = TimelineFragment.newInstance(TimelineFragment.Kind.TAG, hashtag);
|
||||||
fragmentTransaction.add(R.id.fragment_container, fragment);
|
fragmentTransaction.add(R.id.fragment_container, fragment);
|
||||||
fragmentTransaction.commit();
|
fragmentTransaction.commit();
|
||||||
|
|
||||||
|
timelineFragment = fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -63,4 +67,10 @@ public class ViewTagActivity extends BaseActivity {
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUserRemoved(String accountId) {
|
||||||
|
StatusRemoveListener listener = (StatusRemoveListener) timelineFragment;
|
||||||
|
listener.removePostsByUser(accountId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,9 @@ import android.support.v7.widget.Toolbar;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
public class ViewThreadActivity extends BaseActivity {
|
public class ViewThreadActivity extends BaseActivity implements SFragment.OnUserRemovedListener {
|
||||||
|
Fragment viewThreadFragment;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
@ -44,6 +46,8 @@ public class ViewThreadActivity extends BaseActivity {
|
||||||
Fragment fragment = ViewThreadFragment.newInstance(id);
|
Fragment fragment = ViewThreadFragment.newInstance(id);
|
||||||
fragmentTransaction.add(R.id.fragment_container, fragment);
|
fragmentTransaction.add(R.id.fragment_container, fragment);
|
||||||
fragmentTransaction.commit();
|
fragmentTransaction.commit();
|
||||||
|
|
||||||
|
viewThreadFragment = fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -62,4 +66,12 @@ public class ViewThreadActivity extends BaseActivity {
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUserRemoved(String accountId) {
|
||||||
|
if (viewThreadFragment instanceof StatusRemoveListener) {
|
||||||
|
StatusRemoveListener listener = (StatusRemoveListener) viewThreadFragment;
|
||||||
|
listener.removePostsByUser(accountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.support.v4.widget.SwipeRefreshLayout;
|
||||||
import android.support.v7.widget.DividerItemDecoration;
|
import android.support.v7.widget.DividerItemDecoration;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
@ -34,9 +35,11 @@ import com.keylesspalace.tusky.entity.StatusContext;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.Callback;
|
import retrofit2.Callback;
|
||||||
|
|
||||||
public class ViewThreadFragment extends SFragment implements StatusActionListener {
|
public class ViewThreadFragment extends SFragment implements
|
||||||
|
SwipeRefreshLayout.OnRefreshListener, StatusActionListener, StatusRemoveListener {
|
||||||
private static final String TAG = "ViewThreadFragment";
|
private static final String TAG = "ViewThreadFragment";
|
||||||
|
|
||||||
|
private SwipeRefreshLayout swipeRefreshLayout;
|
||||||
private RecyclerView recyclerView;
|
private RecyclerView recyclerView;
|
||||||
private ThreadAdapter adapter;
|
private ThreadAdapter adapter;
|
||||||
private String thisThreadsStatusId;
|
private String thisThreadsStatusId;
|
||||||
|
@ -56,6 +59,9 @@ public class ViewThreadFragment extends SFragment implements StatusActionListene
|
||||||
View rootView = inflater.inflate(R.layout.fragment_view_thread, container, false);
|
View rootView = inflater.inflate(R.layout.fragment_view_thread, container, false);
|
||||||
|
|
||||||
Context context = getContext();
|
Context context = getContext();
|
||||||
|
swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipe_refresh_layout);
|
||||||
|
swipeRefreshLayout.setOnRefreshListener(this);
|
||||||
|
|
||||||
recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
|
recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
|
||||||
recyclerView.setHasFixedSize(true);
|
recyclerView.setHasFixedSize(true);
|
||||||
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||||
|
@ -86,7 +92,7 @@ public class ViewThreadFragment extends SFragment implements StatusActionListene
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call<Status> call, retrofit2.Response<Status> response) {
|
public void onResponse(Call<Status> call, retrofit2.Response<Status> response) {
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
int position = adapter.insertStatus(response.body());
|
int position = adapter.setStatus(response.body());
|
||||||
recyclerView.scrollToPosition(position);
|
recyclerView.scrollToPosition(position);
|
||||||
} else {
|
} else {
|
||||||
onThreadRequestFailure(id);
|
onThreadRequestFailure(id);
|
||||||
|
@ -109,10 +115,10 @@ public class ViewThreadFragment extends SFragment implements StatusActionListene
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call<StatusContext> call, retrofit2.Response<StatusContext> response) {
|
public void onResponse(Call<StatusContext> call, retrofit2.Response<StatusContext> response) {
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
StatusContext context = response.body();
|
StatusContext context = response.body();
|
||||||
|
|
||||||
adapter.addAncestors(context.ancestors);
|
adapter.setContext(context.ancestors, context.descendants);
|
||||||
adapter.addDescendants(context.descendants);
|
|
||||||
} else {
|
} else {
|
||||||
onThreadRequestFailure(id);
|
onThreadRequestFailure(id);
|
||||||
}
|
}
|
||||||
|
@ -128,6 +134,7 @@ public class ViewThreadFragment extends SFragment implements StatusActionListene
|
||||||
|
|
||||||
private void onThreadRequestFailure(final String id) {
|
private void onThreadRequestFailure(final String id) {
|
||||||
View view = getView();
|
View view = getView();
|
||||||
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
Snackbar.make(view, R.string.error_generic, Snackbar.LENGTH_LONG)
|
Snackbar.make(view, R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||||
.setAction(R.string.action_retry, new View.OnClickListener() {
|
.setAction(R.string.action_retry, new View.OnClickListener() {
|
||||||
|
@ -143,6 +150,22 @@ public class ViewThreadFragment extends SFragment implements StatusActionListene
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removePostsByUser(String accountId) {
|
||||||
|
adapter.removeAllByAccountId(accountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRefresh() {
|
||||||
|
sendStatusRequest(thisThreadsStatusId);
|
||||||
|
sendThreadRequest(thisThreadsStatusId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccessfulStatus() {
|
||||||
|
onRefresh();
|
||||||
|
super.onSuccessfulStatus();
|
||||||
|
}
|
||||||
|
|
||||||
public void onReply(int position) {
|
public void onReply(int position) {
|
||||||
super.reply(adapter.getItem(position));
|
super.reply(adapter.getItem(position));
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import android.text.Spanned;
|
||||||
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion;
|
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion;
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
import com.keylesspalace.tusky.HtmlUtils;
|
import com.keylesspalace.tusky.HtmlUtils;
|
||||||
|
import com.keylesspalace.tusky.StringWithEmoji;
|
||||||
|
|
||||||
public class Account implements SearchSuggestion {
|
public class Account implements SearchSuggestion {
|
||||||
public String id;
|
public String id;
|
||||||
|
@ -32,7 +33,7 @@ public class Account implements SearchSuggestion {
|
||||||
public String username;
|
public String username;
|
||||||
|
|
||||||
@SerializedName("display_name")
|
@SerializedName("display_name")
|
||||||
public String displayName;
|
public StringWithEmoji displayName;
|
||||||
|
|
||||||
public Spanned note;
|
public Spanned note;
|
||||||
|
|
||||||
|
@ -70,11 +71,10 @@ public class Account implements SearchSuggestion {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDisplayName() {
|
public String getDisplayName() {
|
||||||
if (displayName.length() == 0) {
|
if (displayName.value.length() == 0) {
|
||||||
return localUsername;
|
return localUsername;
|
||||||
}
|
}
|
||||||
|
return displayName.value;
|
||||||
return displayName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -92,7 +92,7 @@ public class Account implements SearchSuggestion {
|
||||||
dest.writeString(id);
|
dest.writeString(id);
|
||||||
dest.writeString(localUsername);
|
dest.writeString(localUsername);
|
||||||
dest.writeString(username);
|
dest.writeString(username);
|
||||||
dest.writeString(displayName);
|
dest.writeString(displayName.value);
|
||||||
dest.writeString(HtmlUtils.toHtml(note));
|
dest.writeString(HtmlUtils.toHtml(note));
|
||||||
dest.writeString(url);
|
dest.writeString(url);
|
||||||
dest.writeString(avatar);
|
dest.writeString(avatar);
|
||||||
|
@ -111,7 +111,7 @@ public class Account implements SearchSuggestion {
|
||||||
id = in.readString();
|
id = in.readString();
|
||||||
localUsername = in.readString();
|
localUsername = in.readString();
|
||||||
username = in.readString();
|
username = in.readString();
|
||||||
displayName = in.readString();
|
displayName = new StringWithEmoji(in.readString());
|
||||||
note = HtmlUtils.fromHtml(in.readString());
|
note = HtmlUtils.fromHtml(in.readString());
|
||||||
url = in.readString();
|
url = in.readString();
|
||||||
avatar = in.readString();
|
avatar = in.readString();
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.keylesspalace.tusky.entity;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
public class Profile {
|
||||||
|
@SerializedName("display_name")
|
||||||
|
public String displayName;
|
||||||
|
|
||||||
|
@SerializedName("note")
|
||||||
|
public String note;
|
||||||
|
|
||||||
|
/** Encoded in Base-64 */
|
||||||
|
@SerializedName("avatar")
|
||||||
|
public String avatar;
|
||||||
|
|
||||||
|
/** Encoded in Base-64 */
|
||||||
|
@SerializedName("header")
|
||||||
|
public String header;
|
||||||
|
}
|
|
@ -146,5 +146,8 @@ public class Status {
|
||||||
|
|
||||||
@SerializedName("acct")
|
@SerializedName("acct")
|
||||||
public String username;
|
public String username;
|
||||||
|
|
||||||
|
@SerializedName("username")
|
||||||
|
public String localUsername;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
5
app/src/main/res/color/drawer_visibility_panel_item.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:color="@color/button_dark" android:state_checked="true" />
|
||||||
|
<item android:color="?material_drawer_primary_icon" />
|
||||||
|
</selector>
|
12
app/src/main/res/drawable/ic_camera_24dp.xml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<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="@color/toolbar_icon_dark"
|
||||||
|
android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/toolbar_icon_dark"
|
||||||
|
android:pathData="M9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2L9,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_check_24dp.xml
Normal file
|
@ -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="#FFFFFFFF"
|
||||||
|
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
|
||||||
|
</vector>
|
7
app/src/main/res/drawable/ic_check_in_box_24dp.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<vector android:height="24dp" android:viewportHeight="35.43307"
|
||||||
|
android:viewportWidth="35.43307" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillAlpha="1" android:fillColor="#ffffff"
|
||||||
|
android:pathData="M32.16,4.46L31.62,5.01L14.63,21.99L5.78,13.13L2.5,16.41L14.52,28.43L14.55,28.41L14.66,28.52L35.44,7.74L34.89,7.19C34.17,6.46 33.44,5.74 32.71,5.01L32.16,4.46z" />
|
||||||
|
<path android:fillAlpha="1" android:fillColor="#ffffff"
|
||||||
|
android:pathData="m1.1,6.19c-0.58,0 -1.07,0.49 -1.07,1.07l0,23.06c0,0.58 0.49,1.07 1.07,1.07l23.06,0c0.58,0 1.07,-0.49 1.07,-1.07l0,-18.89 -1.54,1.54 0,16.88 -22.12,0 0,-22.12 22.12,0 0,2.83 1.54,-1.54 0,-1.76c0,-0.58 -0.49,-1.07 -1.07,-1.07l-23.06,0z" />
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_email_24dp.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportHeight="24.0"
|
||||||
|
android:viewportWidth="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/toolbar_icon_dark"
|
||||||
|
android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,8l-8,5 -8,-5L4,6l8,5 8,-5v2z" />
|
||||||
|
</vector>
|
|
@ -1,27 +1,7 @@
|
||||||
<vector android:height="24dp" android:viewportHeight="42.519684"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:viewportWidth="42.519684" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:height="24dp"
|
||||||
<path android:fillAlpha="1" android:fillColor="#ffffff"
|
android:width="24dp"
|
||||||
android:pathData="M31.89,5.82m-5.31,0a5.31,5.31 0,1 1,10.63 0a5.31,5.31 0,1 1,-10.63 0"
|
android:viewportWidth="24"
|
||||||
android:strokeAlpha="1" android:strokeColor="#00000000"
|
android:viewportHeight="24">
|
||||||
android:strokeLineCap="square" android:strokeLineJoin="miter" android:strokeWidth="0.30000001"/>
|
<path android:fillColor="#000" android:pathData="M16,13C15.71,13 15.38,13 15.03,13.05C16.19,13.89 17,15 17,16.5V19H23V16.5C23,14.17 18.33,13 16,13M8,13C5.67,13 1,14.17 1,16.5V19H15V16.5C15,14.17 10.33,13 8,13M8,11A3,3 0 0,0 11,8A3,3 0 0,0 8,5A3,3 0 0,0 5,8A3,3 0 0,0 8,11M16,11A3,3 0 0,0 19,8A3,3 0 0,0 16,5A3,3 0 0,0 13,8A3,3 0 0,0 16,11Z" />
|
||||||
<path android:fillAlpha="1" android:fillColor="#ffffff"
|
|
||||||
android:pathData="M10.63,5.82m-5.31,0a5.31,5.31 0,1 1,10.63 0a5.31,5.31 0,1 1,-10.63 0"
|
|
||||||
android:strokeAlpha="1" android:strokeColor="#00000000"
|
|
||||||
android:strokeLineCap="square" android:strokeLineJoin="miter" android:strokeWidth="0.30000001"/>
|
|
||||||
<path android:fillAlpha="1" android:fillColor="#ffffff"
|
|
||||||
android:pathData="M21.26,23.03m-5.31,0a5.31,5.31 0,1 1,10.63 0a5.31,5.31 0,1 1,-10.63 0"
|
|
||||||
android:strokeAlpha="1" android:strokeColor="#00000000"
|
|
||||||
android:strokeLineCap="square" android:strokeLineJoin="miter" android:strokeWidth="0.30000001"/>
|
|
||||||
<path android:fillAlpha="1" android:fillColor="#ffffff"
|
|
||||||
android:pathData="m17.62,29.22c-2.07,1.24 -3.45,3.49 -3.45,6.08l0,3.54c0,3.93 -0.38,3.54 3.54,3.54l7.09,0c3.93,0 3.54,0.38 3.54,-3.54l0,-3.54c0,-2.59 -1.38,-4.84 -3.45,-6.08a7.19,7.19 0,0 1,-3.64 1,7.19 7.19,0 0,1 -3.64,-1z"
|
|
||||||
android:strokeAlpha="1" android:strokeColor="#00000000"
|
|
||||||
android:strokeLineCap="square" android:strokeLineJoin="miter" android:strokeWidth="0.30000001"/>
|
|
||||||
<path android:fillAlpha="1" android:fillColor="#ffffff"
|
|
||||||
android:pathData="m28.25,12.04c-1.69,1.01 -2.91,2.72 -3.3,4.72a7.28,7.28 0,0 1,3.59 6.27,7.28 7.28,0 0,1 -0.33,2.18c0.06,-0 0.08,0 0.14,0l7.09,0c3.93,0 3.54,0.38 3.54,-3.54l0,-3.54c0,-2.59 -1.38,-4.84 -3.45,-6.08a7.19,7.19 0,0 1,-3.64 1,7.19 7.19,0 0,1 -3.64,-1z"
|
|
||||||
android:strokeAlpha="1" android:strokeColor="#00000000"
|
|
||||||
android:strokeLineCap="square" android:strokeLineJoin="miter" android:strokeWidth="0.30000001"/>
|
|
||||||
<path android:fillAlpha="1" android:fillColor="#ffffff"
|
|
||||||
android:pathData="m6.99,12.04c-2.07,1.24 -3.45,3.49 -3.45,6.08l0,3.54c0,3.93 -0.38,3.54 3.54,3.54l7.09,0c0.07,0 0.08,-0 0.15,0a7.28,7.28 0,0 1,-0.34 -2.18,7.28 7.28,0 0,1 3.59,-6.27c-0.39,-2.01 -1.61,-3.71 -3.3,-4.72a7.19,7.19 0,0 1,-3.64 1,7.19 7.19,0 0,1 -3.64,-1z"
|
|
||||||
android:strokeAlpha="1" android:strokeColor="#00000000"
|
|
||||||
android:strokeLineCap="square" android:strokeLineJoin="miter" android:strokeWidth="0.30000001"/>
|
|
||||||
</vector>
|
</vector>
|
9
app/src/main/res/drawable/ic_lock_open_24dp.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportHeight="24.0"
|
||||||
|
android:viewportWidth="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/toolbar_icon_dark"
|
||||||
|
android:pathData="M12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6h1.9c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM18,20L6,20L6,10h12v10z" />
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_lock_outline_24dp.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportHeight="24.0"
|
||||||
|
android:viewportWidth="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/toolbar_icon_dark"
|
||||||
|
android:pathData="M12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1s3.1,1.39 3.1,3.1v2L8.9,8L8.9,6zM18,20L6,20L6,10h12v10z" />
|
||||||
|
</vector>
|
11
app/src/main/res/drawable/ic_mute_24dp.xml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<vector android:height="24dp" android:viewportHeight="35.43307"
|
||||||
|
android:viewportWidth="35.43307" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillAlpha="1" android:fillColor="#ffffff"
|
||||||
|
android:pathData="m17.72,3.54 l-8.86,8.86 -7.09,0 0,10.63 7.09,0 8.86,8.86z"
|
||||||
|
android:strokeAlpha="1" android:strokeColor="#00000000"
|
||||||
|
android:strokeLineCap="square" android:strokeLineJoin="miter" android:strokeWidth="1.54400003"/>
|
||||||
|
<path android:fillAlpha="1" android:fillColor="#ffffff"
|
||||||
|
android:pathData="m22.86,11.45 l-2.51,2.51 3.76,3.76 -3.76,3.76 2.51,2.51 3.76,-3.76 3.76,3.76 2.5,-2.51 -3.76,-3.76 3.76,-3.76 -2.5,-2.51 -3.76,3.76 -3.76,-3.76z"
|
||||||
|
android:strokeAlpha="1" android:strokeColor="#00000000"
|
||||||
|
android:strokeLineCap="square" android:strokeLineJoin="miter" android:strokeWidth="1.54400003"/>
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_reject_24dp.xml
Normal file
|
@ -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="#FFFFFFFF"
|
||||||
|
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
|
||||||
|
</vector>
|
19
app/src/main/res/drawable/ic_unmute_24dp.xml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<vector android:height="24dp" android:viewportHeight="35.43307"
|
||||||
|
android:viewportWidth="35.43307" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillAlpha="1" android:fillColor="#ffffff"
|
||||||
|
android:pathData="m17.72,3.54 l-8.86,8.86 -7.09,0 0,10.63 7.09,0 8.86,8.86z"
|
||||||
|
android:strokeAlpha="1" android:strokeColor="#00000000"
|
||||||
|
android:strokeLineCap="square" android:strokeLineJoin="miter" android:strokeWidth="1.54400003"/>
|
||||||
|
<path android:fillAlpha="1" android:fillColor="#00000000"
|
||||||
|
android:pathData="m21.47,13.96a5.31,5.31 0,0 1,1.56 3.76,5.31 5.31,0 0,1 -1.56,3.76"
|
||||||
|
android:strokeAlpha="1" android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="square" android:strokeLineJoin="miter" android:strokeWidth="2.65748031"/>
|
||||||
|
<path android:fillAlpha="1" android:fillColor="#00000000"
|
||||||
|
android:pathData="m28.99,6.44a15.94,15.94 0,0 1,4.67 11.27,15.94 15.94,0 0,1 -4.67,11.27"
|
||||||
|
android:strokeAlpha="1" android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="square" android:strokeLineJoin="miter" android:strokeWidth="2.65748031"/>
|
||||||
|
<path android:fillAlpha="1" android:fillColor="#00000000"
|
||||||
|
android:pathData="m25.23,10.2a10.63,10.63 0,0 1,3.11 7.52,10.63 10.63,0 0,1 -3.11,7.52"
|
||||||
|
android:strokeAlpha="1" android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="square" android:strokeLineJoin="miter" android:strokeWidth="2.65748031"/>
|
||||||
|
</vector>
|
BIN
app/src/main/res/drawable/tusky_logo.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
|
@ -5,7 +5,7 @@
|
||||||
android:id="@+id/activity_view_thread"
|
android:id="@+id/activity_view_thread"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context="com.keylesspalace.tusky.BlocksActivity">
|
tools:context="com.keylesspalace.tusky.AccountListActivity">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
|
@ -19,6 +19,33 @@
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:elevation="4dp" />
|
android:elevation="4dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/compose_content_warning_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginBottom="8dp">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/field_content_warning"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:ems="10"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:hint="@string/hint_content_warning"
|
||||||
|
android:inputType="text|textCapSentences" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="?android:attr/listDivider"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:id="@+id/compose_edit_area"
|
android:id="@+id/compose_edit_area"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -56,32 +83,6 @@
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/compose_content_warning_bar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_marginBottom="8dp">
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/field_content_warning"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:ems="10"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:paddingLeft="16dp"
|
|
||||||
android:paddingRight="16dp"
|
|
||||||
android:hint="@string/hint_content_warning"
|
|
||||||
android:inputType="text|textCapSentences" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:background="?android:attr/listDivider"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -91,6 +92,15 @@
|
||||||
android:paddingRight="16dp"
|
android:paddingRight="16dp"
|
||||||
android:paddingTop="4dp">
|
android:paddingTop="4dp">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/compose_photo_take"
|
||||||
|
style="?attr/image_button_style"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
app:srcCompat="@drawable/ic_camera_24dp"
|
||||||
|
android:contentDescription="@string/action_photo_take" />
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/compose_photo_pick"
|
android:id="@+id/compose_photo_pick"
|
||||||
style="?attr/image_button_style"
|
style="?attr/image_button_style"
|
||||||
|
|
137
app/src/main/res/layout/activity_edit_profile.xml
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context="com.keylesspalace.tusky.EditProfileActivity">
|
||||||
|
|
||||||
|
<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="@android:color/transparent"
|
||||||
|
android:elevation="4dp" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/edit_profile_display_name"
|
||||||
|
android:hint="@string/hint_display_name"
|
||||||
|
android:maxLength="30"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/edit_profile_note"
|
||||||
|
android:hint="@string/hint_note"
|
||||||
|
android:maxLength="160"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="16dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingLeft="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/label_avatar"
|
||||||
|
android:labelFor="@+id/edit_profile_avatar"
|
||||||
|
android:layout_marginRight="8dp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@id/edit_profile_avatar"
|
||||||
|
android:text="@string/action_photo_pick"
|
||||||
|
android:textColor="@color/text_color_primary_dark" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="80dp"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:id="@+id/edit_profile_avatar_preview"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/edit_profile_avatar_progress"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingLeft="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/label_header"
|
||||||
|
android:labelFor="@+id/edit_profile_header"
|
||||||
|
android:layout_marginRight="8dp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@id/edit_profile_header"
|
||||||
|
android:text="@string/action_photo_pick"
|
||||||
|
android:textColor="@color/text_color_primary_dark" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="167.2dp"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:id="@+id/edit_profile_header_preview"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/edit_profile_header_progress"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/edit_profile_save_progress"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_gravity="center" />
|
||||||
|
|
||||||
|
</android.support.design.widget.CoordinatorLayout>
|
|
@ -1,8 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<android.support.design.widget.CoordinatorLayout
|
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/activity_main"
|
android:id="@+id/activity_main"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
@ -28,10 +27,12 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?android:colorBackground"
|
android:background="?android:colorBackground"
|
||||||
android:paddingTop="?attr/actionBarSize"
|
android:paddingTop="?attr/actionBarSize"
|
||||||
app:tabTextAppearance="@style/TabLayoutTextStyle"
|
app:tabGravity="fill"
|
||||||
|
app:tabMaxWidth="0dp"
|
||||||
|
app:tabPaddingEnd="1dp"
|
||||||
app:tabPaddingStart="1dp"
|
app:tabPaddingStart="1dp"
|
||||||
app:tabPaddingTop="4dp"
|
app:tabPaddingTop="4dp"
|
||||||
app:tabPaddingEnd="1dp">
|
app:tabTextAppearance="@style/TabLayoutTextStyle">
|
||||||
|
|
||||||
<android.support.design.widget.TabItem
|
<android.support.design.widget.TabItem
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -63,34 +64,34 @@
|
||||||
android:id="@+id/floating_search_view"
|
android:id="@+id/floating_search_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:floatingSearch_searchBarMarginLeft="6dp"
|
app:floatingSearch_close_search_on_keyboard_dismiss="true"
|
||||||
app:floatingSearch_searchBarMarginTop="4dp"
|
|
||||||
app:floatingSearch_searchBarMarginRight="6dp"
|
|
||||||
app:floatingSearch_searchHint="@string/search"
|
|
||||||
app:floatingSearch_suggestionsListAnimDuration="250"
|
|
||||||
app:floatingSearch_showSearchKey="false"
|
|
||||||
app:floatingSearch_leftActionMode="showHamburger"
|
app:floatingSearch_leftActionMode="showHamburger"
|
||||||
app:floatingSearch_close_search_on_keyboard_dismiss="true"/>
|
app:floatingSearch_searchBarMarginLeft="6dp"
|
||||||
|
app:floatingSearch_searchBarMarginRight="6dp"
|
||||||
|
app:floatingSearch_searchBarMarginTop="4dp"
|
||||||
|
app:floatingSearch_searchHint="@string/search"
|
||||||
|
app:floatingSearch_showSearchKey="false"
|
||||||
|
app:floatingSearch_suggestionsListAnimDuration="250" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/tab_bottom_shadow"
|
android:id="@+id/tab_bottom_shadow"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="2dp"
|
android:layout_height="2dp"
|
||||||
app:layout_anchor="@id/tab_layout"
|
|
||||||
app:layout_anchorGravity="bottom"
|
|
||||||
android:background="@drawable/material_drawer_shadow_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.support.design.widget.FloatingActionButton
|
||||||
android:id="@+id/floating_btn"
|
android:id="@+id/floating_btn"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:contentDescription="@string/action_compose"
|
||||||
app:layout_anchor="@id/pager"
|
app:layout_anchor="@id/pager"
|
||||||
app:layout_anchorGravity="bottom|end"
|
app:layout_anchorGravity="bottom|end"
|
||||||
android:clickable="true"
|
app:srcCompat="@drawable/ic_create_24dp" />
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:srcCompat="@drawable/ic_create_24dp"
|
|
||||||
android:contentDescription="@string/action_compose" />
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/overlay_fragment_container"
|
android:id="@+id/overlay_fragment_container"
|
||||||
|
|
|
@ -15,23 +15,48 @@
|
||||||
|
|
||||||
<RadioButton
|
<RadioButton
|
||||||
android:text="@string/visibility_public"
|
android:text="@string/visibility_public"
|
||||||
|
android:button="@drawable/ic_public_24dp"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:id="@+id/radio_public"
|
android:id="@+id/radio_public"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
|
android:paddingLeft="10dp"
|
||||||
|
android:buttonTint="@color/drawer_visibility_panel_item"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
|
|
||||||
<RadioButton
|
<RadioButton
|
||||||
android:text="@string/visibility_unlisted"
|
android:text="@string/visibility_unlisted"
|
||||||
|
android:button="@drawable/ic_lock_open_24dp"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:id="@+id/radio_unlisted"
|
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" />
|
android:layout_weight="1" />
|
||||||
|
|
||||||
<RadioButton
|
<RadioButton
|
||||||
android:text="@string/visibility_private"
|
android:text="@string/visibility_private"
|
||||||
|
android:button="@drawable/ic_lock_outline_24dp"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:id="@+id/radio_private"
|
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" />
|
android:layout_weight="1" />
|
||||||
|
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<android.support.v7.widget.RecyclerView
|
<android.support.v4.widget.SwipeRefreshLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/swipe_refresh_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<android.support.v7.widget.RecyclerView
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/recycler_view"
|
android:id="@+id/recycler_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:scrollbars="vertical" />
|
android:scrollbars="vertical" />
|
||||||
|
</android.support.v4.widget.SwipeRefreshLayout>
|
|
@ -13,7 +13,8 @@
|
||||||
android:id="@+id/blocked_user_avatar"
|
android:id="@+id/blocked_user_avatar"
|
||||||
android:layout_alignParentLeft="true"
|
android:layout_alignParentLeft="true"
|
||||||
android:layout_marginRight="24dp"
|
android:layout_marginRight="24dp"
|
||||||
android:layout_centerVertical="true"/>
|
android:layout_centerVertical="true"
|
||||||
|
android:contentDescription="@string/action_view_profile" />
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
app:srcCompat="@drawable/ic_clear_24dp"
|
app:srcCompat="@drawable/ic_clear_24dp"
|
||||||
|
|
80
app/src/main/res/layout/item_follow_request.xml
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="72dp"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<com.pkmmte.view.CircularImageView
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:id="@+id/follow_request_avatar"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_marginRight="24dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:contentDescription="@string/action_view_profile" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_toRightOf="@id/follow_request_avatar"
|
||||||
|
android:layout_toEndOf="@id/follow_request_avatar"
|
||||||
|
android:layout_toLeftOf="@+id/follow_request_accept"
|
||||||
|
android:layout_toStartOf="@id/follow_request_accept">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/follow_request_display_name"
|
||||||
|
android:text="Display name"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="?android:textColorPrimary"
|
||||||
|
android:textStyle="normal|bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="\@username"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:id="@+id/follow_request_username"
|
||||||
|
android:textColor="?android:textColorSecondary" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
style="?attr/image_button_style"
|
||||||
|
android:id="@+id/follow_request_accept"
|
||||||
|
app:srcCompat="@drawable/ic_check_24dp"
|
||||||
|
android:layout_toLeftOf="@+id/follow_request_reject"
|
||||||
|
android:layout_toStartOf="@id/follow_request_reject"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:contentDescription="@string/action_accept" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
style="?attr/image_button_style"
|
||||||
|
android:id="@id/follow_request_reject"
|
||||||
|
app:srcCompat="@drawable/ic_reject_24dp"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:contentDescription="@string/action_reject" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
25
app/src/main/res/layout/item_footer_empty.xml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
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:padding="16dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:srcCompat="@drawable/elephant_friend" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="@string/footer_empty"
|
||||||
|
android:textAlignment="center" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
9
app/src/main/res/layout/item_footer_end.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
</LinearLayout>
|
62
app/src/main/res/layout/item_muted_user.xml
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<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="72dp"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<com.pkmmte.view.CircularImageView
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:id="@+id/muted_user_avatar"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_marginRight="24dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:contentDescription="@string/action_view_profile" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
app:srcCompat="@drawable/ic_unmute_24dp"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:id="@+id/muted_user_unmute"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
style="?attr/image_button_style"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:contentDescription="@string/action_unmute" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_toRightOf="@id/muted_user_avatar"
|
||||||
|
android:layout_toLeftOf="@id/muted_user_unmute">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/muted_user_display_name"
|
||||||
|
android:text="Display name"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="?android:textColorPrimary"
|
||||||
|
android:textStyle="normal|bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="\@username"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:id="@+id/muted_user_username"
|
||||||
|
android:textColor="?android:textColorSecondary" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
|
@ -9,6 +9,7 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:id="@+id/title"
|
android:id="@+id/title"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
android:textAllCaps="true"
|
android:textAllCaps="true"
|
||||||
android:textStyle="normal|bold" />
|
android:textStyle="normal|bold" />
|
||||||
|
|
9
app/src/main/res/menu/edit_profile_toolbar.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_save"
|
||||||
|
android:icon="@drawable/ic_check_in_box_24dp"
|
||||||
|
android:title="@string/action_save"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
</menu>
|
|
@ -2,7 +2,18 @@
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item
|
<item
|
||||||
android:id="@+id/status_share"
|
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_mute"
|
||||||
|
android:id="@+id/status_mute" />
|
||||||
<item android:title="@string/action_block"
|
<item android:title="@string/action_block"
|
||||||
android:id="@+id/status_block" />
|
android:id="@+id/status_block" />
|
||||||
<item android:title="@string/action_report"
|
<item android:title="@string/action_report"
|
||||||
|
|
Before Width: | Height: | Size: 5 KiB After Width: | Height: | Size: 5.3 KiB |
BIN
app/src/main/res/mipmap-ldpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 16 KiB |
160
app/src/main/res/values-ar/strings.xml
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<string name="error_generic">وقع هناك خطأ.</string>
|
||||||
|
<!-- <string name="error_empty">This cannot be empty.</string> -->
|
||||||
|
<string name="error_invalid_domain">اسم النطاق غير صالح</string>
|
||||||
|
<string name="error_failed_app_registration">اخفقت المصادقة مع مثيل الخادم هذا.</string>
|
||||||
|
<string name="error_no_web_browser_found">لم يتم العثور على متصفح قابل للإستعمال.</string>
|
||||||
|
<!-- <string name="error_authorization_unknown">An unidentified authorization error occurred.</string> -->
|
||||||
|
<string name="error_authorization_denied">تم رفض التصريح.</string>
|
||||||
|
<!-- <string name="error_retrieving_oauth_token">Failed getting a login token.</string> -->
|
||||||
|
<!-- <string name="error_compose_character_limit">The status is too long!</string> -->
|
||||||
|
<string name="error_media_upload_size">يجب أن يكون حجم الملف أقل من 4 ميغابايت.</string>
|
||||||
|
<string name="error_media_upload_type">لا يمكن رفع هذا النوع من الملفات.</string>
|
||||||
|
<string name="error_media_upload_opening">تعذر فتح ذاك الملف.</string>
|
||||||
|
<!-- <string name="error_media_upload_permission">Permission to read media is required.</string> -->
|
||||||
|
<!-- <string name="error_media_download_permission">Permission to store media is required.</string> -->
|
||||||
|
|
||||||
|
<!-- <string name="error_media_upload_image_or_video">Images and videos cannot both be attached to the same status.</string> -->
|
||||||
|
<string name="error_media_upload_sending">اخفقت عملية الرفع.</string>
|
||||||
|
<!-- <string name="error_report_too_few_statuses">At least one status must be reported.</string> -->
|
||||||
|
|
||||||
|
<string name="title_home">الرئيسية</string>
|
||||||
|
<string name="title_notifications">الاشعارات</string>
|
||||||
|
<string name="title_public_local">المحلية</string>
|
||||||
|
<string name="title_public_federated">الفدرالية</string>
|
||||||
|
<string name="title_thread">الخيط</string>
|
||||||
|
<string name="title_tag">#%s</string>
|
||||||
|
<string name="title_statuses">المشاركات</string>
|
||||||
|
<string name="title_follows">يتبع</string>
|
||||||
|
<string name="title_followers">المتابعون</string>
|
||||||
|
<string name="title_favourites">المفضلة</string>
|
||||||
|
<string name="title_blocks">المستخدمون المحظورون</string>
|
||||||
|
|
||||||
|
<string name="status_username_format">\@%s</string>
|
||||||
|
<string name="status_boosted_format">%s عزز</string>
|
||||||
|
<string name="status_sensitive_media_title">محتوى حساس</string>
|
||||||
|
<string name="status_sensitive_media_directions">اضغط للعرض</string>
|
||||||
|
<string name="status_content_warning_show_more">اعرض أكثر</string>
|
||||||
|
<string name="status_content_warning_show_less">اعرض أقل</string>
|
||||||
|
|
||||||
|
<string name="footer_end_of_statuses">نهاية الحالات</string>
|
||||||
|
<string name="footer_end_of_notifications">نهاية الاشعارات</string>
|
||||||
|
<string name="footer_end_of_accounts">نهاية الحسابات</string>
|
||||||
|
<!-- <string name="footer_empty">There are no toots here so far. Pull down to refresh!</string> -->
|
||||||
|
|
||||||
|
<string name="notification_reblog_format">%s عزز تبويقك</string>
|
||||||
|
<string name="notification_favourite_format">%s أعجب بتبويقك</string>
|
||||||
|
<string name="notification_follow_format">%s يتبعك</string>
|
||||||
|
|
||||||
|
<string name="report_username_format">أبلغ عن @%s</string>
|
||||||
|
<string name="report_comment_hint">تعليقات إضافية ؟</string>
|
||||||
|
|
||||||
|
<string name="action_reply">أجب</string>
|
||||||
|
<string name="action_reblog">عزز</string>
|
||||||
|
<string name="action_favourite">تفضيل</string>
|
||||||
|
<string name="action_more">المزيد</string>
|
||||||
|
<string name="action_compose">حرر</string>
|
||||||
|
<string name="action_login">التسجيل بواسطة ماستدون</string>
|
||||||
|
<string name="action_logout">خروج</string>
|
||||||
|
<string name="action_follow">إتبع</string>
|
||||||
|
<string name="action_unfollow">إلغاء التتبع</string>
|
||||||
|
<string name="action_block">حضر</string>
|
||||||
|
<string name="action_unblock">إلغاء الحظر</string>
|
||||||
|
<string name="action_report">أبلغ</string>
|
||||||
|
<string name="action_delete">إحذف</string>
|
||||||
|
<string name="action_send">تبويق</string>
|
||||||
|
<string name="action_send_public">بَوِّق</string>
|
||||||
|
<string name="action_retry">إعادة المحاولة</string>
|
||||||
|
<!-- <string name="action_mark_sensitive">Mark media sensitive</string> -->
|
||||||
|
<string name="action_hide_text">اخفي النص وراء تحذير</string>
|
||||||
|
<string name="action_ok">موافق</string>
|
||||||
|
<string name="action_cancel">إلغاء</string>
|
||||||
|
<string name="action_close">إغلاق</string>
|
||||||
|
<string name="action_back">عودة</string>
|
||||||
|
<string name="action_view_profile">الملف الشخصي</string>
|
||||||
|
<string name="action_view_preferences">التفضيلات</string>
|
||||||
|
<string name="action_view_favourites">المفضلة</string>
|
||||||
|
<string name="action_view_blocks">المستخدمون المحظورون</string>
|
||||||
|
<string name="action_view_thread">الخيط</string>
|
||||||
|
<string name="action_view_media">وسائط</string>
|
||||||
|
<string name="action_open_in_web">إفتح في متصفح</string>
|
||||||
|
<string name="action_submit">ارسل</string>
|
||||||
|
<string name="action_photo_pick">إضافة وسائط</string>
|
||||||
|
<!-- <string name="action_photo_take">Take photo</string> -->
|
||||||
|
<string name="action_share">شارك</string>
|
||||||
|
<string name="action_mute">أكتم</string>
|
||||||
|
<string name="action_unmute">إلغاء الكتم</string>
|
||||||
|
<string name="action_mention">أذكر</string>
|
||||||
|
<string name="toggle_nsfw">NSFW</string>
|
||||||
|
<string name="action_compose_options">خيارات</string>
|
||||||
|
<string name="action_open_drawer">إفتح الدرج</string>
|
||||||
|
<string name="action_clear">إمسح</string>
|
||||||
|
<!-- <string name="action_save">Save</string> -->
|
||||||
|
<!-- <string name="action_edit_profile">Edit profile</string> -->
|
||||||
|
|
||||||
|
<string name="send_status_link_to">شارك رابط التبويق إلى ...</string>
|
||||||
|
<!-- <string name="send_status_content_to">Share toot to…</string> -->
|
||||||
|
|
||||||
|
<string name="search">ابحث عن حسابات ...</string>
|
||||||
|
|
||||||
|
<string name="confirmation_send">بَوِّق</string>
|
||||||
|
<string name="confirmation_reported">تم الإرسال !</string>
|
||||||
|
|
||||||
|
<string name="hint_domain">أي سيرفر ؟</string>
|
||||||
|
<string name="hint_compose">ما الجديد ؟</string>
|
||||||
|
<string name="hint_content_warning">تحذير عن المحتوى</string>
|
||||||
|
<!-- <string name="hint_display_name">Display name</string> -->
|
||||||
|
<!-- <string name="hint_note">Bio</string> -->
|
||||||
|
|
||||||
|
<!-- <string name="label_avatar">Avatar</string> -->
|
||||||
|
<!-- <string name="label_header">Header</string> -->
|
||||||
|
|
||||||
|
<string name="link_whats_an_instance">ماذا نعني بمثيل الخادم ؟</string>
|
||||||
|
|
||||||
|
<!-- <string name="dialog_whats_an_instance">The address or domain of any instance can be entered -->
|
||||||
|
<!-- here, such as mastodon.social, icosahedron.website, social.tchncs.de, and -->
|
||||||
|
<!-- <a href="https://github.com/tootsuite/mastodon/blob/master/docs/Using-Mastodon/List-of-Mastodon-instances.md">more!</a> -->
|
||||||
|
<!-- \n\nIf you don\'t yet have an account, you can enter the name of the instance you\'d like to -->
|
||||||
|
<!-- join and create an account there.\n\nAn instance is a single place where your account is -->
|
||||||
|
<!-- hosted, but you can easily communicate with and follow folks on other instances as though -->
|
||||||
|
<!-- you were on the same site. -->
|
||||||
|
<!-- \n\nMore info can be found at <a href="https://mastodon.social/about">mastodon.social</a>. -->
|
||||||
|
<!-- </string> -->
|
||||||
|
<string name="dialog_title_finishing_media_upload">تتمة رفع الوسائط</string>
|
||||||
|
<string name="dialog_message_uploading_media">جاري الرفع ...</string>
|
||||||
|
<!-- <string name="dialog_download_image">Download</string> -->
|
||||||
|
|
||||||
|
<!-- <string name="visibility_public">Public: Post to public timelines</string> -->
|
||||||
|
<!-- <string name="visibility_unlisted">Unlisted: Do not show in public timelines</string> -->
|
||||||
|
<!-- <string name="visibility_private">Private: Post to followers only</string> -->
|
||||||
|
<!-- <string name="visibility_direct">Direct: Post to mentioned users only</string> -->
|
||||||
|
|
||||||
|
<string name="pref_title_notification_settings">الاشعارات</string>
|
||||||
|
<string name="pref_title_edit_notification_settings">تعديل الاشعارات</string>
|
||||||
|
<string name="pref_title_notifications_enabled">دفع الإخطارات</string>
|
||||||
|
<string name="pref_title_notification_alerts">التنبيهات</string>
|
||||||
|
<string name="pref_title_notification_alert_sound">إعلام بالصوت</string>
|
||||||
|
<string name="pref_title_notification_alert_vibrate">إعلام بالاهتزار</string>
|
||||||
|
<string name="pref_title_notification_alert_light">إعلام بالضوء</string>
|
||||||
|
<string name="pref_title_notification_filters">أخطرني عندما</string>
|
||||||
|
<string name="pref_title_notification_filter_mentions">يشار إلي</string>
|
||||||
|
<string name="pref_title_notification_filter_follows">يتبعني أحد</string>
|
||||||
|
<string name="pref_title_notification_filter_reblogs">تعزز وتدفع منشوراتي</string>
|
||||||
|
<string name="pref_title_notification_filter_favourites">يعجب أحد ما بمنشوراتي</string>
|
||||||
|
<string name="pref_title_appearance_settings">المظهر</string>
|
||||||
|
<string name="pref_title_light_theme">إستخدم سمةً فاتحة اللون</string>
|
||||||
|
<string name="pref_title_browser_settings">المتصفح</string>
|
||||||
|
<string name="pref_title_custom_tabs">إخفاء زر المتابعة أثناء تمرير الصفحة</string>
|
||||||
|
<!-- <string name="pref_title_hide_follow_button">Hide follow button while scrolling</string> -->
|
||||||
|
|
||||||
|
<string name="notification_mention_format">%s أشار إليك</string>
|
||||||
|
<string name="notification_summary_large">%1$s, %2$s, %3$s و %4$d أخرى</string>
|
||||||
|
<string name="notification_summary_medium">%1$s, %2$s, و %3$s</string>
|
||||||
|
<string name="notification_summary_small">%1$s و %2$s</string>
|
||||||
|
<string name="notification_title_summary">%d تفاعلات جديدة</string>
|
||||||
|
|
||||||
|
<string name="description_account_locked">حساب مقفل</string>
|
||||||
|
<!-- <string name="status_share_content">Share content of toot</string> -->
|
||||||
|
<!-- <string name="status_share_link">Share link to toot</string> -->
|
||||||
|
</resources>
|
|
@ -1,25 +1,35 @@
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
|
<string name="error_generic">Ein Fehler ist aufgetreten.</string>
|
||||||
|
<!-- <string name="error_empty">This cannot be empty.</string> -->
|
||||||
<string name="error_invalid_domain">Ungültige Domain angegeben</string>
|
<string name="error_invalid_domain">Ungültige Domain angegeben</string>
|
||||||
<string name="error_failed_app_registration">Diese App konnte sich auf dem Server nicht authentifizieren.</string>
|
<string name="error_failed_app_registration">Diese App konnte sich auf dem Server nicht authentifizieren.</string>
|
||||||
|
<string name="error_no_web_browser_found">Kein Webbrowser gefunden.</string>
|
||||||
<string name="error_authorization_unknown">Ein undefinierbarer Autorisierungsfehler ist aufgetreten.</string>
|
<string name="error_authorization_unknown">Ein undefinierbarer Autorisierungsfehler ist aufgetreten.</string>
|
||||||
|
<string name="error_authorization_denied">Autorisierung fehlgeschlagen.</string>
|
||||||
|
<string name="error_retrieving_oauth_token">Es konnte kein Login-Token abgerufen werden.</string>
|
||||||
<string name="error_compose_character_limit">Der Beitrag ist zu lang!</string>
|
<string name="error_compose_character_limit">Der Beitrag ist zu lang!</string>
|
||||||
<string name="error_media_upload_size">Die Datei muss kleiner als 4MB sein.</string>
|
<string name="error_media_upload_size">Die Datei muss kleiner als 4MB sein.</string>
|
||||||
<string name="error_media_upload_type">Dieser Dateityp darf nicht hochgeladen werden.</string>
|
<string name="error_media_upload_type">Dieser Dateityp darf nicht hochgeladen werden.</string>
|
||||||
<string name="error_media_upload_opening">Die Datei konnte nicht geöffnet werden.</string>
|
<string name="error_media_upload_opening">Die Datei konnte nicht geöffnet werden.</string>
|
||||||
<string name="error_media_upload_permission">Eine Leseberechtigung wird für das Hochladen der Mediendatei benötigt.</string>
|
<string name="error_media_upload_permission">Eine Leseberechtigung wird für das Hochladen der Mediendatei benötigt.</string>
|
||||||
|
<string name="error_media_download_permission">Eine Berechtigung wird zum Speichern des Mediums benötigt.</string>
|
||||||
|
|
||||||
<string name="error_media_upload_image_or_video">Bilder und Videos können beide nicht an den Beitrag angehängt werden.</string>
|
<string name="error_media_upload_image_or_video">Bilder und Videos können beide nicht an den Beitrag angehängt werden.</string>
|
||||||
<string name="error_media_upload_sending">Die Mediendatei konnte nicht hochgeladen werden.</string>
|
<string name="error_media_upload_sending">Die Mediendatei konnte nicht hochgeladen werden.</string>
|
||||||
<string name="error_report_too_few_statuses">Mindestens ein Beitrag muss berichtet werden.</string>
|
<string name="error_report_too_few_statuses">Mindestens ein Beitrag muss berichtet werden.</string>
|
||||||
|
|
||||||
<string name="title_home">Start</string>
|
<string name="title_home">Start</string>
|
||||||
<string name="title_notifications">Benachrichtigungen</string>
|
<string name="title_notifications">Benachrichtigungen</string>
|
||||||
|
<string name="title_public_local">Lokal</string>
|
||||||
|
<string name="title_public_federated">Föderiert</string>
|
||||||
<string name="title_thread">Unterhaltung</string>
|
<string name="title_thread">Unterhaltung</string>
|
||||||
<string name="title_tag">#%s</string>
|
<string name="title_tag">#%s</string>
|
||||||
<string name="title_statuses">Beiträge</string>
|
<string name="title_statuses">Beiträge</string>
|
||||||
<string name="title_follows">Folgt</string>
|
<string name="title_follows">Folgt</string>
|
||||||
<string name="title_followers">Folgende</string>
|
<string name="title_followers">Folgende</string>
|
||||||
<string name="title_favourites">Favoriten</string>
|
<string name="title_favourites">Favoriten</string>
|
||||||
<string name="title_blocks">Blockierte Nutzer</string>
|
<string name="title_blocks">Blockierte Accounts</string>
|
||||||
|
|
||||||
<string name="status_username_format">\@%s</string>
|
<string name="status_username_format">\@%s</string>
|
||||||
<string name="status_boosted_format">%s teilte</string>
|
<string name="status_boosted_format">%s teilte</string>
|
||||||
|
@ -31,6 +41,7 @@
|
||||||
<string name="footer_end_of_statuses">Ende des Beitrages</string>
|
<string name="footer_end_of_statuses">Ende des Beitrages</string>
|
||||||
<string name="footer_end_of_notifications">Ende der Benachrichtigungen</string>
|
<string name="footer_end_of_notifications">Ende der Benachrichtigungen</string>
|
||||||
<string name="footer_end_of_accounts">Ende der Accounts</string>
|
<string name="footer_end_of_accounts">Ende der Accounts</string>
|
||||||
|
<!-- <string name="footer_empty">There are no toots here so far. Pull down to refresh!</string> -->
|
||||||
|
|
||||||
<string name="notification_reblog_format">%s teilte deinen Beitrag</string>
|
<string name="notification_reblog_format">%s teilte deinen Beitrag</string>
|
||||||
<string name="notification_favourite_format">%s favorisierte deinen Beitrag</string>
|
<string name="notification_favourite_format">%s favorisierte deinen Beitrag</string>
|
||||||
|
@ -39,6 +50,10 @@
|
||||||
<string name="report_username_format">\@%s melden</string>
|
<string name="report_username_format">\@%s melden</string>
|
||||||
<string name="report_comment_hint">Irgendwelche Anmerkungen?</string>
|
<string name="report_comment_hint">Irgendwelche Anmerkungen?</string>
|
||||||
|
|
||||||
|
<string name="action_reply">Antworten</string>
|
||||||
|
<string name="action_reblog">Teilen</string>
|
||||||
|
<string name="action_favourite">Favorisieren</string>
|
||||||
|
<string name="action_more">Mehr</string>
|
||||||
<string name="action_compose">Schreiben</string>
|
<string name="action_compose">Schreiben</string>
|
||||||
<string name="action_login">Anmelden mit Mastodon</string>
|
<string name="action_login">Anmelden mit Mastodon</string>
|
||||||
<string name="action_logout">Ausloggen</string>
|
<string name="action_logout">Ausloggen</string>
|
||||||
|
@ -61,16 +76,25 @@
|
||||||
<string name="action_view_preferences">Einstellungen</string>
|
<string name="action_view_preferences">Einstellungen</string>
|
||||||
<string name="action_view_favourites">Favoriten</string>
|
<string name="action_view_favourites">Favoriten</string>
|
||||||
<string name="action_view_blocks">Blockierte Nutzer</string>
|
<string name="action_view_blocks">Blockierte Nutzer</string>
|
||||||
|
<string name="action_view_thread">Thread</string>
|
||||||
|
<string name="action_view_media">Medien</string>
|
||||||
<string name="action_open_in_web">Im Browser öffnen</string>
|
<string name="action_open_in_web">Im Browser öffnen</string>
|
||||||
<string name="action_submit">Senden</string>
|
<string name="action_submit">Senden</string>
|
||||||
<string name="action_photo_pick">Füge Medien hinzu</string>
|
<string name="action_photo_pick">Füge Medien hinzu</string>
|
||||||
|
<!-- <string name="action_photo_take">Take photo</string> -->
|
||||||
<string name="action_share">Teilen</string>
|
<string name="action_share">Teilen</string>
|
||||||
<string name="action_mute">Stummschalten</string>
|
<string name="action_mute">Stummschalten</string>
|
||||||
<string name="action_unmute">Lautschalten</string>
|
<string name="action_unmute">Lautschalten</string>
|
||||||
<string name="action_mention">Erwähnen</string>
|
<string name="action_mention">Erwähnen</string>
|
||||||
<string name="toggle_nsfw">NSFW</string>
|
<string name="toggle_nsfw">NSFW</string>
|
||||||
|
<string name="action_compose_options">Einstellungen</string>
|
||||||
|
<string name="action_open_drawer">Drawer öffnen</string>
|
||||||
|
<string name="action_clear">Löschen</string>
|
||||||
|
<!-- <string name="action_save">Save</string> -->
|
||||||
|
<!-- <string name="action_edit_profile">Edit profile</string> -->
|
||||||
|
|
||||||
<string name="send_status_to">Teile Toot-URL zu…</string>
|
<string name="send_status_link_to">Teile Toot-URL zu…</string>
|
||||||
|
<!-- <string name="send_status_content_to">Share toot to…</string> -->
|
||||||
|
|
||||||
<string name="search">Suche Accounts…</string>
|
<string name="search">Suche Accounts…</string>
|
||||||
|
|
||||||
|
@ -80,6 +104,11 @@
|
||||||
<string name="hint_domain">Welche Instanz?</string>
|
<string name="hint_domain">Welche Instanz?</string>
|
||||||
<string name="hint_compose">Was passiert gerade?</string>
|
<string name="hint_compose">Was passiert gerade?</string>
|
||||||
<string name="hint_content_warning">Inhaltswarnung</string>
|
<string name="hint_content_warning">Inhaltswarnung</string>
|
||||||
|
<!-- <string name="hint_display_name">Display name</string> -->
|
||||||
|
<!-- <string name="hint_note">Bio</string> -->
|
||||||
|
|
||||||
|
<!-- <string name="label_avatar">Avatar</string> -->
|
||||||
|
<!-- <string name="label_header">Header</string> -->
|
||||||
|
|
||||||
<string name="link_whats_an_instance">Was ist eine Instanz?</string>
|
<string name="link_whats_an_instance">Was ist eine Instanz?</string>
|
||||||
|
|
||||||
|
@ -91,35 +120,38 @@
|
||||||
</string>
|
</string>
|
||||||
<string name="dialog_title_finishing_media_upload">Stelle Medienupload fertig</string>
|
<string name="dialog_title_finishing_media_upload">Stelle Medienupload fertig</string>
|
||||||
<string name="dialog_message_uploading_media">Lade hoch…</string>
|
<string name="dialog_message_uploading_media">Lade hoch…</string>
|
||||||
|
<string name="dialog_download_image">Herunterladen</string>
|
||||||
|
|
||||||
<string name="visibility_public">Öffentlich sichtbar</string>
|
<string name="visibility_public">Öffentlich sichtbar</string>
|
||||||
<string name="visibility_unlisted">Öffentlich sichtbar, aber nicht in der öffentlichen Timeline</string>
|
<string name="visibility_unlisted">Öffentlich sichtbar, aber nicht in der öffentlichen Timeline</string>
|
||||||
<string name="visibility_private">Nur für Follower und Erwähnte sichtbar</string>
|
<string name="visibility_private">Nur für Follower und Erwähnte sichtbar</string>
|
||||||
|
<!-- <string name="visibility_direct">Direct: Post to mentioned users only</string> -->
|
||||||
|
|
||||||
<string name="pref_title_notification_settings">Benachrichtigungen</string>
|
<string name="pref_title_notification_settings">Benachrichtigungen</string>
|
||||||
|
<string name="pref_title_edit_notification_settings">Benachrichtigungseinstellungen</string>
|
||||||
<string name="pref_title_notifications_enabled">Push-Benachrichtigungen</string>
|
<string name="pref_title_notifications_enabled">Push-Benachrichtigungen</string>
|
||||||
|
<string name="pref_title_notification_alerts">Benachrichtigungen</string>
|
||||||
<string name="pref_title_notification_alert_sound">Benachrichtige mit Sound</string>
|
<string name="pref_title_notification_alert_sound">Benachrichtige mit Sound</string>
|
||||||
<string name="pref_title_notification_alert_vibrate">Benachrichtige mit Vibration</string>
|
<string name="pref_title_notification_alert_vibrate">Benachrichtige mit Vibration</string>
|
||||||
<string name="pref_title_notification_alert_light">Benachrichtige mit Licht</string>
|
<string name="pref_title_notification_alert_light">Benachrichtige mit Licht</string>
|
||||||
|
<string name="pref_title_notification_filters">Benachrichtigen wenn</string>
|
||||||
|
<string name="pref_title_notification_filter_mentions">Ich erwähnt werde</string>
|
||||||
|
<string name="pref_title_notification_filter_follows">Mir jemand folgt</string>
|
||||||
|
<string name="pref_title_notification_filter_reblogs">Jemand meine Posts teilt</string>
|
||||||
|
<string name="pref_title_notification_filter_favourites">Jemandem meine Posts gefallen</string>
|
||||||
<string name="pref_title_appearance_settings">Aussehen</string>
|
<string name="pref_title_appearance_settings">Aussehen</string>
|
||||||
<string name="pref_title_light_theme">Benutze helles Theme</string>
|
<string name="pref_title_light_theme">Benutze helles Theme</string>
|
||||||
|
<string name="pref_title_browser_settings">Browser</string>
|
||||||
|
<string name="pref_title_custom_tabs">Öffne Links in der App</string>
|
||||||
|
<string name="pref_title_hide_follow_button">Verstecke Button bei Bildlauf </string>
|
||||||
|
|
||||||
<string name="notification_mention_format">%s hat dich erwähnt</string>
|
<string name="notification_mention_format">%s hat dich erwähnt</string>
|
||||||
<string name="notification_summary_large">%1$s, %2$s, %3$s und %4$d andere</string>
|
<string name="notification_summary_large">%1$s, %2$s, %3$s und %4$d andere</string>
|
||||||
<string name="notification_summary_medium">%1$s, %2$s, und %3$s</string>
|
<string name="notification_summary_medium">%1$s, %2$s, und %3$s</string>
|
||||||
<string name="notification_summary_small">%1$s und %2$s</string>
|
<string name="notification_summary_small">%1$s und %2$s</string>
|
||||||
<string name="notification_title_summary">%d neue Interaktionen</string>
|
<string name="notification_title_summary">%d neue Interaktionen</string>
|
||||||
<string name="title_public_local">Lokal</string>
|
|
||||||
<string name="title_public_federated">Föderiert</string>
|
|
||||||
<string name="pref_title_notification_alerts">Benachrichtigungen</string>
|
|
||||||
<string name="pref_title_notification_filter_favourites">Jemandem meine Posts gefallen</string>
|
|
||||||
<string name="pref_title_notification_filters">Benachrichtigen wenn</string>
|
|
||||||
<string name="pref_title_notification_filter_follows">Mir jemand folgt</string>
|
|
||||||
<string name="pref_title_notification_filter_mentions">Ich erwähnt werde</string>
|
|
||||||
<string name="pref_title_notification_filter_reblogs">Jemand meine Posts boostet</string>
|
|
||||||
<string name="error_authorization_denied">Autorisierung fehlgeschlagen.</string>
|
|
||||||
<string name="error_generic">Ein Fehler ist aufgetreten.</string>
|
|
||||||
<string name="error_no_web_browser_found">Kein Webbrowser gefunden.</string>
|
|
||||||
<string name="error_retrieving_oauth_token">Es konnte kein Login-Token abgerufen werden.</string>
|
|
||||||
|
|
||||||
|
<string name="description_account_locked">Gesperrter Account</string>
|
||||||
|
<!-- <string name="status_share_content">Share content of toot</string> -->
|
||||||
|
<!-- <string name="status_share_link">Share link to toot</string> -->
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,32 +1,35 @@
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<string name="error_generic">Une erreur s\'est produite.</string>
|
<string name="error_generic">Une erreur s’est produite.</string>
|
||||||
|
<string name="error_empty">Ce champ ne peut pas être vide</string>
|
||||||
<string name="error_invalid_domain">Le domaine est invalide</string>
|
<string name="error_invalid_domain">Le domaine est invalide</string>
|
||||||
<string name="error_failed_app_registration">L\'application n\'a pu s\'authentifier avec l\'instance.</string>
|
<string name="error_failed_app_registration">L’application n’a pu s’authentifier auprès de l’instance.</string>
|
||||||
<string name="error_no_web_browser_found">Impossible de trouver un navigateur web.</string>
|
<string name="error_no_web_browser_found">Impossible de trouver un navigateur web.</string>
|
||||||
<string name="error_authorization_unknown">Une erreur d\'authorisation inconnu s\'est produite.</string>
|
<string name="error_authorization_unknown">Une erreur d’autorisation inconnue s’est produite.</string>
|
||||||
<string name="error_authorization_denied">Vous ne pouvez pas vous authentifier.</string>
|
<string name="error_authorization_denied">Vous ne pouvez pas vous authentifier.</string>
|
||||||
<string name="error_retrieving_oauth_token">Impossible de récupérer le jeton d\'authentification.</string>
|
<string name="error_retrieving_oauth_token">Impossible de récupérer le jeton d’authentification.</string>
|
||||||
<string name="error_compose_character_limit">Votre pouet est trop long!</string>
|
<string name="error_compose_character_limit">Votre pouet est trop long!</string>
|
||||||
<string name="error_media_upload_size">Le fichier doit faire moins de 4Mo.</string>
|
<string name="error_media_upload_size">Le fichier doit faire moins de 4Mo.</string>
|
||||||
<string name="error_media_upload_type">Ce type de fichier n\'est pas accepté.</string>
|
<string name="error_media_upload_type">Ce type de fichier n’est pas accepté.</string>
|
||||||
<string name="error_media_upload_opening">Le fichier ne peut être ouvert.</string>
|
<string name="error_media_upload_opening">Le fichier ne peut être ouvert.</string>
|
||||||
<string name="error_media_upload_permission">Une permision pour lire ce média est requis pour l\'uploader.</string>
|
<string name="error_media_upload_permission">Une permission pour lire ce média est requise pour le mettre en ligne.</string>
|
||||||
|
<string name="error_media_download_permission">Permission d’enregistrer le fichier requise.</string>
|
||||||
|
|
||||||
<string name="error_media_upload_image_or_video">Impossible de mettre une vidéo et une image sur le même pouet.</string>
|
<string name="error_media_upload_image_or_video">Impossible de mettre une vidéo et une image sur le même pouet.</string>
|
||||||
<string name="error_media_upload_sending">Ce média ne peut être uploadé.</string>
|
<string name="error_media_upload_sending">Ce média ne peut être mis en ligne.</string>
|
||||||
<string name="error_report_too_few_statuses">Au moins un pouet a été reporté.</string>
|
<string name="error_report_too_few_statuses">Au moins un pouet a été reporté.</string>
|
||||||
|
|
||||||
<string name="title_home">Accueil</string>
|
<string name="title_home">Accueil</string>
|
||||||
<string name="title_notifications">Notifications</string>
|
<string name="title_notifications">Notifications</string>
|
||||||
<string name="title_public_local">Local</string>
|
<string name="title_public_local">Local</string>
|
||||||
<string name="title_public_federated">Fédéré</string>
|
<string name="title_public_federated">Fédéré</string>
|
||||||
<string name="title_thread">Thread</string>
|
<string name="title_thread">Fil</string>
|
||||||
<string name="title_tag">#%s</string>
|
<string name="title_tag">#%s</string>
|
||||||
<string name="title_statuses">Pouets</string>
|
<string name="title_statuses">Pouets</string>
|
||||||
<string name="title_follows">Follows</string>
|
<string name="title_follows">Abonnements</string>
|
||||||
<string name="title_followers">Followers</string>
|
<string name="title_followers">Abonnés</string>
|
||||||
<string name="title_favourites">Favoris</string>
|
<string name="title_favourites">Favoris</string>
|
||||||
<string name="title_blocks">Utilisateur bloqués</string>
|
<string name="title_blocks">Utilisateurs bloqués</string>
|
||||||
|
|
||||||
<string name="status_username_format">\@%s</string>
|
<string name="status_username_format">\@%s</string>
|
||||||
<string name="status_boosted_format">%s boosté</string>
|
<string name="status_boosted_format">%s boosté</string>
|
||||||
|
@ -38,26 +41,31 @@
|
||||||
<string name="footer_end_of_statuses">fin du pouet</string>
|
<string name="footer_end_of_statuses">fin du pouet</string>
|
||||||
<string name="footer_end_of_notifications">fin des notifications</string>
|
<string name="footer_end_of_notifications">fin des notifications</string>
|
||||||
<string name="footer_end_of_accounts">fin des comptes</string>
|
<string name="footer_end_of_accounts">fin des comptes</string>
|
||||||
|
<string name="footer_empty">Il n’y a pas encore de pouets ici. Glissez vers le bas pour actualiser !</string>
|
||||||
|
|
||||||
<string name="notification_reblog_format">%s à boosté votre pouet</string>
|
<string name="notification_reblog_format">%s a boosté votre pouet</string>
|
||||||
<string name="notification_favourite_format">%s à ajouter votre pouet en favoris</string>
|
<string name="notification_favourite_format">%s a ajouté votre pouet dans ses favoris</string>
|
||||||
<string name="notification_follow_format">%s vous suit</string>
|
<string name="notification_follow_format">%s vous suit</string>
|
||||||
|
|
||||||
<string name="report_username_format">Signaler @%s</string>
|
<string name="report_username_format">Signaler @%s</string>
|
||||||
<string name="report_comment_hint">Plus de commentaire ?</string>
|
<string name="report_comment_hint">Davantage de commentaires ?</string>
|
||||||
|
|
||||||
<string name="action_compose">Écrire</string>
|
<string name="action_reply">Répondre</string>
|
||||||
|
<string name="action_reblog">Booster</string>
|
||||||
|
<string name="action_favourite">Favori</string>
|
||||||
|
<string name="action_more">Plus</string>
|
||||||
|
<string name="action_compose">Répondre</string>
|
||||||
<string name="action_login">Se connecter avec Mastodon</string>
|
<string name="action_login">Se connecter avec Mastodon</string>
|
||||||
<string name="action_logout">Deconnexion</string>
|
<string name="action_logout">Déconnexion</string>
|
||||||
<string name="action_follow">Follow</string>
|
<string name="action_follow">Suivre</string>
|
||||||
<string name="action_unfollow">Unfollow</string>
|
<string name="action_unfollow">Ne plus suivre</string>
|
||||||
<string name="action_block">Bloquer</string>
|
<string name="action_block">Bloquer</string>
|
||||||
<string name="action_unblock">Débloquer</string>
|
<string name="action_unblock">Débloquer</string>
|
||||||
<string name="action_report">Signaler</string>
|
<string name="action_report">Signaler</string>
|
||||||
<string name="action_delete">Supprimer</string>
|
<string name="action_delete">Supprimer</string>
|
||||||
<string name="action_send">POUET</string>
|
<string name="action_send">POUET</string>
|
||||||
<string name="action_send_public">POUET!</string>
|
<string name="action_send_public">POUET !</string>
|
||||||
<string name="action_retry">Ré-essayer</string>
|
<string name="action_retry">Essayer encore</string>
|
||||||
<string name="action_mark_sensitive">Définir le média comme sensible</string>
|
<string name="action_mark_sensitive">Définir le média comme sensible</string>
|
||||||
<string name="action_hide_text">Masquer le texte par une mise en garde</string>
|
<string name="action_hide_text">Masquer le texte par une mise en garde</string>
|
||||||
<string name="action_ok">Ok</string>
|
<string name="action_ok">Ok</string>
|
||||||
|
@ -65,63 +73,86 @@
|
||||||
<string name="action_close">Fermer</string>
|
<string name="action_close">Fermer</string>
|
||||||
<string name="action_back">Retour</string>
|
<string name="action_back">Retour</string>
|
||||||
<string name="action_view_profile">Profil</string>
|
<string name="action_view_profile">Profil</string>
|
||||||
<string name="action_view_preferences">Préferences</string>
|
<string name="action_view_preferences">Préférences</string>
|
||||||
<string name="action_view_favourites">Favoris</string>
|
<string name="action_view_favourites">Favoris</string>
|
||||||
<string name="action_view_blocks">Utilisateurs bloqués</string>
|
<string name="action_view_blocks">Utilisateurs bloqués</string>
|
||||||
|
<string name="action_view_thread">Fil</string>
|
||||||
|
<string name="action_view_media">Média</string>
|
||||||
<string name="action_open_in_web">Ouvrir avec votre navigateur</string>
|
<string name="action_open_in_web">Ouvrir avec votre navigateur</string>
|
||||||
<string name="action_submit">Envoyer</string>
|
<string name="action_submit">Envoyer</string>
|
||||||
<string name="action_photo_pick">Ajouter un média</string>
|
<string name="action_photo_pick">Ajouter un média</string>
|
||||||
|
<string name="action_photo_take">Prendre une photo</string>
|
||||||
<string name="action_share">Partager</string>
|
<string name="action_share">Partager</string>
|
||||||
<string name="action_mute">Rendre muet</string>
|
<string name="action_mute">Rendre muet</string>
|
||||||
<string name="action_unmute">Redonner la parole</string>
|
<string name="action_unmute">Redonner la parole</string>
|
||||||
<string name="action_mention">Mention</string>
|
<string name="action_mention">Mention</string>
|
||||||
<string name="toggle_nsfw">NSFW</string>
|
<string name="toggle_nsfw">NSFW</string>
|
||||||
|
<string name="action_compose_options">Option</string>
|
||||||
|
<string name="action_open_drawer">Ouvrir le menu</string>
|
||||||
|
<string name="action_clear">Nettoyer</string>
|
||||||
|
<string name="action_save">Sauvegarder</string>
|
||||||
|
<string name="action_edit_profile">Modifier le profil</string>
|
||||||
|
|
||||||
<string name="send_status_to">Partager l\'URL de votre pouet avec…</string>
|
<string name="send_status_link_to">Partager l’URL de votre pouet avec…</string>
|
||||||
|
<string name="send_status_content_to">Partager le pouet avec…</string>
|
||||||
|
|
||||||
<string name="search">Rechercher un compte…</string>
|
<string name="search">Rechercher un compte…</string>
|
||||||
|
|
||||||
<string name="confirmation_send">Toot!</string>
|
<string name="confirmation_send">Pouet !</string>
|
||||||
<string name="confirmation_reported">Envoyer!</string>
|
<string name="confirmation_reported">Envoyé !</string>
|
||||||
|
|
||||||
<string name="hint_domain">Quelle instance?</string>
|
<string name="hint_domain">Quelle instance ?</string>
|
||||||
<string name="hint_compose">Quoi de neuf ?</string>
|
<string name="hint_compose">Quoi de neuf ?</string>
|
||||||
<string name="hint_content_warning">Contenu mis en garde</string>
|
<string name="hint_content_warning">Contenu sensible</string>
|
||||||
|
<string name="hint_display_name">Afficher le nom</string>
|
||||||
|
<string name="hint_note">Bio</string>
|
||||||
|
|
||||||
<string name="link_whats_an_instance">Qu\'est ce qu\'une instance?</string>
|
<string name="label_avatar">Avatar</string>
|
||||||
|
<string name="label_header">En-tête</string>
|
||||||
|
|
||||||
<string name="dialog_whats_an_instance">L\'adresse ou le domaine d\'une instance peut être entré
|
<string name="link_whats_an_instance">Qu’est ce qu’une instance ?</string>
|
||||||
Ici, comme mastodon.social, icosahedron.website, social.tchncs.de, et
|
|
||||||
<a href="https://github.com/tootsuite/mastodon/blob/master/docs/Using-Mastodon/List-of-Mastodon-instances.md">plus (en anglais)!</a>
|
<string name="dialog_whats_an_instance">L’adresse ou le domaine d’une instance peut être entré
|
||||||
\n\nSi vous n\'avez pas de compte, Vous pouvez entrer le nom de l\'instance que vous voulez rejoindre et créer un compte ici.\n\n Une instance est l\'endroit où votre compte est
|
ici, comme mastodon.social, icosahedron.website, social.tchncs.de,
|
||||||
stocké, mais vous pouvez facilement communiquer et suivre d\'autre personne sur d\'autre instance bien que vous soyez sur le même site
|
<a href="https://github.com/tootsuite/mastodon/blob/master/docs/Using-Mastodon/List-of-Mastodon-instances.md">ou autre</a> (en anglais) !
|
||||||
\n\nPlus d\'info <a href="https://mastodon.social/about">mastodon.social</a> (anglais).
|
\n\nSi vous n’avez pas de compte, vous pouvez entrer le nom de l’instance que vous voulez rejoindre et créer un compte ici.\n\nUne instance est l’endroit où votre compte est
|
||||||
|
stocké, mais vous pouvez facilement communiquer et suivre d’autres personnes sur d’autres instances bien que vous soyez sur le même site
|
||||||
|
\n\nPlus d’info <a href="https://mastodon.social/about">mastodon.social</a> (anglais).
|
||||||
</string>
|
</string>
|
||||||
<string name="dialog_title_finishing_media_upload">Média uploadé avec succès</string>
|
<string name="dialog_title_finishing_media_upload">Média mis en ligne avec succès</string>
|
||||||
<string name="dialog_message_uploading_media">Téléversement…</string>
|
<string name="dialog_message_uploading_media">Mise en ligne…</string>
|
||||||
|
<string name="dialog_download_image">Télécharger</string>
|
||||||
|
|
||||||
<string name="visibility_public">Tout le monde peut voir</string>
|
<string name="visibility_public">Public: Afficher dans les fils publics</string>
|
||||||
<string name="visibility_unlisted">Tout le monde peut voir, mais cela ne sera pas listé sur votre timeline public</string>
|
<string name="visibility_unlisted">Non-listé: Ne pas afficher dans les fils publics</string>
|
||||||
<string name="visibility_private">Uniquement les followers et les mentionnés peuvent voir</string>
|
<string name="visibility_private">Privé: N’afficher que pour vos abonné⋅e⋅s</string>
|
||||||
|
<string name="visibility_direct">Direct: N’afficher que pour les personnes mentionnées</string>
|
||||||
|
|
||||||
<string name="pref_title_notification_settings">Notifications</string>
|
<string name="pref_title_notification_settings">Notifications</string>
|
||||||
|
<string name="pref_title_edit_notification_settings">Modifier la notification</string>
|
||||||
<string name="pref_title_notifications_enabled">Notifications push</string>
|
<string name="pref_title_notifications_enabled">Notifications push</string>
|
||||||
<string name="pref_title_notification_alerts">Alertes</string>
|
<string name="pref_title_notification_alerts">Alertes</string>
|
||||||
<string name="pref_title_notification_alert_sound">Sonner pour notifier</string>
|
<string name="pref_title_notification_alert_sound">Émettre un son pour notifier</string>
|
||||||
<string name="pref_title_notification_alert_vibrate">Vibrer pour notifier</string>
|
<string name="pref_title_notification_alert_vibrate">Vibrer pour notifier</string>
|
||||||
<string name="pref_title_notification_alert_light">Notifier avec une LED</string>
|
<string name="pref_title_notification_alert_light">Notifier avec une LED</string>
|
||||||
<string name="pref_title_notification_filters">Me notifier quand</string>
|
<string name="pref_title_notification_filters">Me notifier quand</string>
|
||||||
<string name="pref_title_notification_filter_mentions">mentionné</string>
|
<string name="pref_title_notification_filter_mentions">mentionné</string>
|
||||||
<string name="pref_title_notification_filter_follows">suivit</string>
|
<string name="pref_title_notification_filter_follows">suivi</string>
|
||||||
<string name="pref_title_notification_filter_reblogs">mes pouets boostés</string>
|
<string name="pref_title_notification_filter_reblogs">mes pouets sont boostés</string>
|
||||||
<string name="pref_title_notification_filter_favourites">mes pouets mis en favoris</string>
|
<string name="pref_title_notification_filter_favourites">mes pouets sont mis en favoris</string>
|
||||||
<string name="pref_title_appearance_settings">Apparence</string>
|
<string name="pref_title_appearance_settings">Apparence</string>
|
||||||
<string name="pref_title_light_theme">Utiliser le thème clair</string>
|
<string name="pref_title_light_theme">Utiliser le thème clair</string>
|
||||||
|
<string name="pref_title_browser_settings">Navigateur</string>
|
||||||
|
<string name="pref_title_custom_tabs">Utiliser le navigateur intégré</string>
|
||||||
|
<string name="pref_title_hide_follow_button">Cacher le bouton de suivi lors du défilement</string>
|
||||||
|
|
||||||
<string name="notification_mention_format">%s vous ont mentionnés</string>
|
<string name="notification_mention_format">%s vous ont mentionné</string>
|
||||||
<string name="notification_summary_large">%1$s, %2$s, %3$s et %4$d plus</string>
|
<string name="notification_summary_large">%1$s, %2$s, %3$s et %4$d plus</string>
|
||||||
<string name="notification_summary_medium">%1$s, %2$s, et %3$s</string>
|
<string name="notification_summary_medium">%1$s, %2$s, et %3$s</string>
|
||||||
<string name="notification_summary_small">%1$s et %2$s</string>
|
<string name="notification_summary_small">%1$s et %2$s</string>
|
||||||
<string name="notification_title_summary">%d nouvelles interactions</string>
|
<string name="notification_title_summary">%d nouvelles interactions</string>
|
||||||
|
|
||||||
|
<string name="description_account_locked">Compte bloqué</string>
|
||||||
|
<string name="status_share_content">Partager le contenu du pouet</string>
|
||||||
|
<string name="status_share_link">Partager le lien du pouet</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
164
app/src/main/res/values-ja/strings.xml
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<string name="error_generic">エラーが発生しました。</string>
|
||||||
|
<string name="error_empty">本文なしでは投稿できません。</string>
|
||||||
|
<string name="error_invalid_domain">無効なドメインです</string>
|
||||||
|
<string name="error_failed_app_registration">そのインスタンスでの認証に失敗しました。</string>
|
||||||
|
<string name="error_no_web_browser_found">ウェブブラウザが見つかりませんでした。</string>
|
||||||
|
<string name="error_authorization_unknown">不明な承認エラーが発生しました。</string>
|
||||||
|
<string name="error_authorization_denied">承認が拒否されました。</string>
|
||||||
|
<string name="error_retrieving_oauth_token">ログイントークンの取得に失敗しました。</string>
|
||||||
|
<string name="error_compose_character_limit">投稿文が長すぎます!</string>
|
||||||
|
<string name="error_media_upload_size">ファイルは4MB未満にしてください。</string>
|
||||||
|
<string name="error_media_upload_type">その形式のファイルはアップロードできません。</string>
|
||||||
|
<string name="error_media_upload_opening">ファイルを開けませんでした。</string>
|
||||||
|
<string name="error_media_upload_permission">メディアの読み取り許可が必要です。</string>
|
||||||
|
<string name="error_media_download_permission">メディアの書き込み許可が必要です。</string>
|
||||||
|
|
||||||
|
<string name="error_media_upload_image_or_video">画像と動画を同時に投稿することはできません。</string>
|
||||||
|
<string name="error_media_upload_sending">アップロードに失敗しました。</string>
|
||||||
|
<string name="error_report_too_few_statuses">少なくとも1つの投稿を報告してください。</string>
|
||||||
|
|
||||||
|
<string name="title_home">ホーム</string>
|
||||||
|
<string name="title_notifications">通知</string>
|
||||||
|
<string name="title_public_local">ローカル</string>
|
||||||
|
<string name="title_public_federated">連合</string>
|
||||||
|
<string name="title_thread">スレッド</string>
|
||||||
|
<string name="title_tag">#%s</string>
|
||||||
|
<string name="title_statuses">投稿</string>
|
||||||
|
<string name="title_follows">フォロー</string>
|
||||||
|
<string name="title_followers">フォロワー</string>
|
||||||
|
<string name="title_favourites">お気に入り</string>
|
||||||
|
<string name="title_mutes">ミュートしたユーザー</string>
|
||||||
|
<string name="title_blocks">ブロックしたユーザー</string>
|
||||||
|
|
||||||
|
<string name="status_username_format">\@%s</string>
|
||||||
|
<string name="status_boosted_format">%sさんがブーストしました</string>
|
||||||
|
<string name="status_sensitive_media_title">不適切なコンテンツ</string>
|
||||||
|
<string name="status_sensitive_media_directions">タップして表示</string>
|
||||||
|
<string name="status_content_warning_show_more">続きを表示</string>
|
||||||
|
<string name="status_content_warning_show_less">続きを隠す</string>
|
||||||
|
|
||||||
|
<string name="footer_end_of_statuses">これ以降に投稿はありません</string>
|
||||||
|
<string name="footer_end_of_notifications">これ以降に通知はありません</string>
|
||||||
|
<string name="footer_end_of_accounts">これ以降にアカウントはありません</string>
|
||||||
|
<string name="footer_empty">現在トゥートはありません。更新するにはプルダウンしてください!</string>
|
||||||
|
|
||||||
|
<string name="notification_reblog_format">%sさんがトゥートをブーストしました</string>
|
||||||
|
<string name="notification_favourite_format">%sさんがトゥートをお気に入りに登録しました</string>
|
||||||
|
<string name="notification_follow_format">%sさんにフォローされました</string>
|
||||||
|
|
||||||
|
<string name="report_username_format">\@%sさんを通報</string>
|
||||||
|
<string name="report_comment_hint">コメント</string>
|
||||||
|
|
||||||
|
<string name="action_reply">返信</string>
|
||||||
|
<string name="action_reblog">ブースト</string>
|
||||||
|
<string name="action_favourite">お気に入り</string>
|
||||||
|
<string name="action_more">その他</string>
|
||||||
|
<string name="action_compose">新規投稿</string>
|
||||||
|
<string name="action_login">Mastodonでログイン</string>
|
||||||
|
<string name="action_logout">ログアウト</string>
|
||||||
|
<string name="action_follow">フォローする</string>
|
||||||
|
<string name="action_unfollow">フォロー解除</string>
|
||||||
|
<string name="action_block">ブロック</string>
|
||||||
|
<string name="action_unblock">ブロック解除</string>
|
||||||
|
<string name="action_report">通報</string>
|
||||||
|
<string name="action_delete">削除</string>
|
||||||
|
<string name="action_send">トゥート</string>
|
||||||
|
<string name="action_send_public">トゥート!</string>
|
||||||
|
<string name="action_retry">再試行</string>
|
||||||
|
<string name="action_mark_sensitive">不適切なコンテンツとして設定</string>
|
||||||
|
<string name="action_hide_text">投稿文を注意書きで隠す</string>
|
||||||
|
<string name="action_ok">OK</string>
|
||||||
|
<string name="action_cancel">キャンセル</string>
|
||||||
|
<string name="action_close">閉じる</string>
|
||||||
|
<string name="action_back">戻る</string>
|
||||||
|
<string name="action_view_profile">プロフィール</string>
|
||||||
|
<string name="action_view_preferences">設定</string>
|
||||||
|
<string name="action_view_favourites">お気に入り</string>
|
||||||
|
<string name="action_view_mutes">ミュートしたユーザー</string>
|
||||||
|
<string name="action_view_blocks">ブロックしたユーザー</string>
|
||||||
|
<string name="action_view_thread">スレッド</string>
|
||||||
|
<string name="action_view_media">メディア</string>
|
||||||
|
<string name="action_open_in_web">ブラウザで開く</string>
|
||||||
|
<string name="action_submit">決定</string>
|
||||||
|
<string name="action_photo_pick">メディアを追加</string>
|
||||||
|
<string name="action_photo_take">写真を撮る</string>
|
||||||
|
<string name="action_share">共有</string>
|
||||||
|
<string name="action_mute">ミュート</string>
|
||||||
|
<string name="action_unmute">ミュート解除</string>
|
||||||
|
<string name="action_mention">返信</string>
|
||||||
|
<string name="toggle_nsfw">NSFW</string>
|
||||||
|
<string name="action_compose_options">オプション</string>
|
||||||
|
<string name="action_open_drawer">メニューを開く</string>
|
||||||
|
<string name="action_clear">消去</string>
|
||||||
|
<string name="action_save">保存</string>
|
||||||
|
<string name="action_edit_profile">プロフィールを編集</string>
|
||||||
|
<string name="action_undo">取り消す</string>
|
||||||
|
|
||||||
|
<string name="send_status_link_to">トゥートのURLを共有…</string>
|
||||||
|
<string name="send_status_content_to">トゥートを共有…</string>
|
||||||
|
|
||||||
|
<string name="search">ユーザーを検索…</string>
|
||||||
|
|
||||||
|
<string name="confirmation_send">トゥート!</string>
|
||||||
|
<string name="confirmation_reported">送信しました!</string>
|
||||||
|
<string name="confirmation_unblocked">ブロックを解除しました</string>
|
||||||
|
<string name="confirmation_unmuted">ミュートを解除しました</string>
|
||||||
|
|
||||||
|
<string name="hint_domain">どのインスタンス?</string>
|
||||||
|
<string name="hint_compose">今なにしてる?</string>
|
||||||
|
<string name="hint_content_warning">注意書き</string>
|
||||||
|
<string name="hint_display_name">表示名</string>
|
||||||
|
<string name="hint_note">プロフィール</string>
|
||||||
|
|
||||||
|
<string name="label_avatar">アイコン</string>
|
||||||
|
<string name="label_header">ヘッダー</string>
|
||||||
|
|
||||||
|
<string name="link_whats_an_instance">インスタンスとは?</string>
|
||||||
|
|
||||||
|
<string name="dialog_whats_an_instance">mastodon.social, mstdn.jp, pawoo.net や
|
||||||
|
<a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/List-of-Mastodon-instances.md">その他</a>
|
||||||
|
のような、あらゆるインスタンスのアドレスやドメインを入力できます。
|
||||||
|
\n\nまだアカウントをお持ちでない場合は、参加したいインスタンスの名前を入力することで
|
||||||
|
そのインスタンスにアカウントを作成できます。\n\nインスタンスはあなたのアカウントが提供される単独の場所ですが、
|
||||||
|
他のインスタンスのユーザーとあたかも同じ場所にいるように簡単にコミュニケーションをとったりフォローしたりできます。
|
||||||
|
\n\nさらに詳しい情報は<a href="https://mastodon.social/about">mastodon.social</a>でご覧いただけます。
|
||||||
|
</string>
|
||||||
|
<string name="dialog_title_finishing_media_upload">メディアをアップロードしています</string>
|
||||||
|
<string name="dialog_message_uploading_media">アップロード中…</string>
|
||||||
|
<string name="dialog_download_image">ダウンロード</string>
|
||||||
|
|
||||||
|
<string name="visibility_public">公開:公開タイムラインに投稿する</string>
|
||||||
|
<string name="visibility_unlisted">未収載:公開タイムラインには表示しない</string>
|
||||||
|
<string name="visibility_private">非公開:フォロワーだけに公開</string>
|
||||||
|
<string name="visibility_direct">ダイレクト:返信先のユーザーだけに公開</string>
|
||||||
|
|
||||||
|
<string name="pref_title_notification_settings">通知</string>
|
||||||
|
<string name="pref_title_edit_notification_settings">通知を設定</string>
|
||||||
|
<string name="pref_title_notifications_enabled">プッシュ通知</string>
|
||||||
|
<string name="pref_title_notification_alerts">通知時の動作</string>
|
||||||
|
<string name="pref_title_notification_alert_sound">着信音を鳴らす</string>
|
||||||
|
<string name="pref_title_notification_alert_vibrate">バイブレーションする</string>
|
||||||
|
<string name="pref_title_notification_alert_light">通知ランプ</string>
|
||||||
|
<string name="pref_title_notification_filters">通知の種類</string>
|
||||||
|
<string name="pref_title_notification_filter_mentions">返信</string>
|
||||||
|
<string name="pref_title_notification_filter_follows">フォロー</string>
|
||||||
|
<string name="pref_title_notification_filter_reblogs">投稿がブーストされた</string>
|
||||||
|
<string name="pref_title_notification_filter_favourites">投稿がお気に入りに登録された</string>
|
||||||
|
<string name="pref_title_appearance_settings">表示</string>
|
||||||
|
<string name="pref_title_light_theme">明るいテーマを使用</string>
|
||||||
|
<string name="pref_title_browser_settings">ブラウザ</string>
|
||||||
|
<string name="pref_title_custom_tabs">Chrome Custom Tabsを使用する</string>
|
||||||
|
<string name="pref_title_hide_follow_button">スクロール中はフォローボタンを隠す</string>
|
||||||
|
|
||||||
|
<string name="notification_mention_format">%sさんが返信しました</string>
|
||||||
|
<string name="notification_summary_large">%1$sさん、%2$sさん、%3$sさんと他%4$d人</string>
|
||||||
|
<string name="notification_summary_medium">%1$sさん、%2$sさん、%3$sさん</string>
|
||||||
|
<string name="notification_summary_small">%1$sさん、%2$sさん</string>
|
||||||
|
<string name="notification_title_summary">%d件の新しい通知</string>
|
||||||
|
|
||||||
|
<string name="description_account_locked">非公開アカウント</string>
|
||||||
|
<string name="status_share_content">トゥートの内容を共有</string>
|
||||||
|
<string name="status_share_link">トゥートへのリンクを共有</string>
|
||||||
|
</resources>
|
154
app/src/main/res/values-nl/strings.xml
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<string name="error_generic">Er ging iets verkeerd.</string>
|
||||||
|
<string name="error_empty">Dit kan niet leeg blijven.</string>
|
||||||
|
<string name="error_invalid_domain">Ongeldige domeinnaam ingevoerd</string>
|
||||||
|
<string name="error_failed_app_registration">Authenticatie met die server is mislukt.</string>
|
||||||
|
<string name="error_no_web_browser_found">Kon geen webbrowser vinden om te gebruiken.</string>
|
||||||
|
<string name="error_authorization_unknown">Onbekende autorisatiefout.</string>
|
||||||
|
<string name="error_authorization_denied">Autorisatie werd geweigerd.</string>
|
||||||
|
<string name="error_retrieving_oauth_token">Verkrijgen van inlogsleutel is mislukt.</string>
|
||||||
|
<string name="error_compose_character_limit">Tekst van deze toot is te lang!</string>
|
||||||
|
<string name="error_media_upload_size">Bestand moet kleiner zijn dan 4MB.</string>
|
||||||
|
<string name="error_media_upload_type">Bestandstype kan niet worden geüpload.</string>
|
||||||
|
<string name="error_media_upload_opening">Bestand kan niet worden geopend.</string>
|
||||||
|
<string name="error_media_upload_permission">Toestemming nodig om media te kunnen lezen.</string>
|
||||||
|
<!-- <string name="error_media_download_permission">Permission to store media is required.</string> -->
|
||||||
|
|
||||||
|
<string name="error_media_upload_image_or_video">Afbeeldingen en video\'s kunnen niet allebei aan dezelfde toot worden toegevoegd.</string>
|
||||||
|
<string name="error_media_upload_sending">Uploaden mislukt.</string>
|
||||||
|
<string name="error_report_too_few_statuses">Tenminste één toot moet worden gerapporteerd.</string>
|
||||||
|
|
||||||
|
<string name="title_home">Jouw tijdlijn</string>
|
||||||
|
<string name="title_notifications">Meldingen</string>
|
||||||
|
<string name="title_public_local">Lokale tijdlijn</string>
|
||||||
|
<string name="title_public_federated">Globale tijdlijn</string>
|
||||||
|
<string name="title_thread">Conversatie</string>
|
||||||
|
<string name="title_tag">#%s</string>
|
||||||
|
<string name="title_statuses">Toots</string>
|
||||||
|
<string name="title_follows">Volgt</string>
|
||||||
|
<string name="title_followers">Volgers</string>
|
||||||
|
<string name="title_favourites">Favorieten</string>
|
||||||
|
<string name="title_blocks">Geblokkeerde</string>
|
||||||
|
|
||||||
|
<string name="status_username_format">\@%s</string>
|
||||||
|
<string name="status_boosted_format">%s boostte</string>
|
||||||
|
<string name="status_sensitive_media_title">Gevoelige inhoud</string>
|
||||||
|
<string name="status_sensitive_media_directions">Klik om te zien</string>
|
||||||
|
<string name="status_content_warning_show_more">Meer tonen</string>
|
||||||
|
<string name="status_content_warning_show_less">Minder tonen</string>
|
||||||
|
|
||||||
|
<string name="footer_end_of_statuses">einde van alle toots</string>
|
||||||
|
<string name="footer_end_of_notifications">einde van alle meldingen</string>
|
||||||
|
<string name="footer_end_of_accounts">einde van alle accounts</string>
|
||||||
|
<string name="footer_empty">Hier bevinden zich nog geen toots. Trek omlaag om te vernieuwen!</string>
|
||||||
|
|
||||||
|
<string name="notification_reblog_format">%s boostte jouw toot</string>
|
||||||
|
<string name="notification_favourite_format">%s markeerde jouw toot als favoriet</string>
|
||||||
|
<string name="notification_follow_format">%s volgt jou</string>
|
||||||
|
|
||||||
|
<string name="report_username_format">Rapporteer @%s</string>
|
||||||
|
<string name="report_comment_hint">Additional comments?</string>
|
||||||
|
|
||||||
|
<string name="action_reply">Reageren</string>
|
||||||
|
<string name="action_reblog">Boost</string>
|
||||||
|
<string name="action_favourite">Favoriet</string>
|
||||||
|
<string name="action_more">Meer</string>
|
||||||
|
<string name="action_compose">Schrijven</string>
|
||||||
|
<string name="action_login">Inloggen bij Mastodon</string>
|
||||||
|
<string name="action_logout">Uitloggen</string>
|
||||||
|
<string name="action_follow">Volgen</string>
|
||||||
|
<string name="action_unfollow">Ontvolgen</string>
|
||||||
|
<string name="action_block">Blokkeren</string>
|
||||||
|
<string name="action_unblock">Deblokkeren</string>
|
||||||
|
<string name="action_report">Rapporteren</string>
|
||||||
|
<string name="action_delete">Verwijderen</string>
|
||||||
|
<string name="action_send">Toot</string>
|
||||||
|
<string name="action_send_public">TOOT!</string>
|
||||||
|
<string name="action_retry">Opnieuw proberen</string>
|
||||||
|
<string name="action_mark_sensitive">Media als gevoelig markeren</string>
|
||||||
|
<string name="action_hide_text">Tekst achter waarschuwing verbergen</string>
|
||||||
|
<string name="action_ok">OK</string>
|
||||||
|
<string name="action_cancel">Annuleren</string>
|
||||||
|
<string name="action_close">Sluiten</string>
|
||||||
|
<string name="action_back">Terug</string>
|
||||||
|
<string name="action_view_profile">Profiel</string>
|
||||||
|
<string name="action_view_preferences">Voorkeuren</string>
|
||||||
|
<string name="action_view_favourites">Favorieten</string>
|
||||||
|
<string name="action_view_blocks">Geblokkeerde gebruikers</string>
|
||||||
|
<string name="action_view_thread">Conversatie</string>
|
||||||
|
<string name="action_view_media">Media</string>
|
||||||
|
<string name="action_open_in_web">Open in webbrowser</string>
|
||||||
|
<string name="action_submit">Opslaan</string>
|
||||||
|
<string name="action_photo_pick">Media toevoegen</string>
|
||||||
|
<string name="action_share">Delen</string>
|
||||||
|
<string name="action_mute">Negeren</string>
|
||||||
|
<string name="action_unmute">Niet meer negeren</string>
|
||||||
|
<string name="action_mention">Vermelden</string>
|
||||||
|
<string name="toggle_nsfw">Gevoelig (NSFW)</string>
|
||||||
|
<string name="action_compose_options">Opties</string>
|
||||||
|
<string name="action_open_drawer">Menu openen</string>
|
||||||
|
<string name="action_clear">Leegmaken</string>
|
||||||
|
<!-- <string name="action_save">Save</string> -->
|
||||||
|
<!-- <string name="action_edit_profile">Edit profile</string> -->
|
||||||
|
|
||||||
|
<string name="send_status_link_to">Deel link van toot met…</string>
|
||||||
|
<string name="send_status_content_to">Deel toot met…</string>
|
||||||
|
|
||||||
|
<string name="search">Zoek accounts…</string>
|
||||||
|
|
||||||
|
<string name="confirmation_send">Toot!</string>
|
||||||
|
<string name="confirmation_reported">Verzenden!</string>
|
||||||
|
|
||||||
|
<string name="hint_domain">Welke Mastodon-server?</string>
|
||||||
|
<string name="hint_compose">Wat wil je kwijt?</string>
|
||||||
|
<string name="hint_content_warning">Waarschuwingstekst</string>
|
||||||
|
<!-- <string name="hint_display_name">Display name</string> -->
|
||||||
|
<!-- <string name="hint_note">Bio</string> -->
|
||||||
|
|
||||||
|
<!-- <string name="label_avatar">Avatar</string> -->
|
||||||
|
<!-- <string name="label_header">Header</string> -->
|
||||||
|
|
||||||
|
<string name="link_whats_an_instance">Wat is een Mastodon-server?</string>
|
||||||
|
|
||||||
|
<string name="dialog_whats_an_instance">Het adres of domein van elke Mastodon-server kan hier worden ingevoerd, zoals mastodon.social, mastodon.cloud, octodon.social en <a href="https://instances.mastodon.xyz/">veel meer!</a>
|
||||||
|
\n\nWanneer je nog geen account hebt, kun je de naam van de Mastodon-server waar jij je graag wil registeren invoeren, waarna je daarna daar een account kunt aanmaken.\n\nEen Mastodon-server is een computerserver waar jouw account zich bevindt, maar je kan eenvoudig mensen van andere servers volgen en met ze communiceren, alsof jullie met elkaar op dezelfde website zitten.
|
||||||
|
\n\nMeer informatie kun je vinden op <a href="https://mastodon.social/about">mastodon.social</a>.
|
||||||
|
</string>
|
||||||
|
<string name="dialog_title_finishing_media_upload">Uploaden media wordt voltooid</string>
|
||||||
|
<string name="dialog_message_uploading_media">Uploaden…</string>
|
||||||
|
<!-- <string name="dialog_download_image">Download</string> -->
|
||||||
|
|
||||||
|
<string name="visibility_public">Openbaar</string>
|
||||||
|
<string name="visibility_unlisted">Openbaar, maar niet op een openbare tijdlijn tonen</string>
|
||||||
|
<string name="visibility_private">Alleen aan volgers tonen</string>
|
||||||
|
<!-- <string name="visibility_direct">Direct: Post to mentioned users only</string> -->
|
||||||
|
|
||||||
|
<string name="pref_title_notification_settings">Meldingen</string>
|
||||||
|
<string name="pref_title_edit_notification_settings">Meldingen bewerken</string>
|
||||||
|
<string name="pref_title_notifications_enabled">Meldingen pushen</string>
|
||||||
|
<string name="pref_title_notification_alerts">Waarschuwingen</string>
|
||||||
|
<string name="pref_title_notification_alert_sound">Geluid</string>
|
||||||
|
<string name="pref_title_notification_alert_vibrate">Trillen</string>
|
||||||
|
<string name="pref_title_notification_alert_light">LED</string>
|
||||||
|
<string name="pref_title_notification_filters">Geef een melding wanneer</string>
|
||||||
|
<string name="pref_title_notification_filter_mentions">ik werd vermeld</string>
|
||||||
|
<string name="pref_title_notification_filter_follows">ik werd gevolgd</string>
|
||||||
|
<string name="pref_title_notification_filter_reblogs">mijn toots werden geboost</string>
|
||||||
|
<string name="pref_title_notification_filter_favourites">mijn toots favoriet zijn</string>
|
||||||
|
<string name="pref_title_appearance_settings">Uiterlijk</string>
|
||||||
|
<string name="pref_title_light_theme">Gebruik het lichte thema</string>
|
||||||
|
<string name="pref_title_browser_settings">Webrowser</string>
|
||||||
|
<string name="pref_title_custom_tabs">Gebruik Chrome aangepaste tabbladen</string>
|
||||||
|
<string name="pref_title_hide_follow_button">Verberg volgknop tijdens scrollen</string>
|
||||||
|
|
||||||
|
<string name="notification_mention_format">%s vermeldde jou</string>
|
||||||
|
<string name="notification_summary_large">%1$s, %2$s, %3$s en %4$d anderen</string>
|
||||||
|
<string name="notification_summary_medium">%1$s, %2$s en %3$s</string>
|
||||||
|
<string name="notification_summary_small">%1$s en %2$s</string>
|
||||||
|
<string name="notification_title_summary">%d nieuwe interacties</string>
|
||||||
|
|
||||||
|
<string name="description_account_locked">Geblokkeerde account</string>
|
||||||
|
<string name="status_share_content">Deel inhoud van toot</string>
|
||||||
|
<string name="status_share_link">Deel link met toot</string>
|
||||||
|
</resources>
|
159
app/src/main/res/values-tr/strings.xml
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<string name="error_generic">Bir hata oluştu.</string>
|
||||||
|
<string name="error_empty">Bu alan boş bırakılmaz.</string>
|
||||||
|
<string name="error_invalid_domain">Girilen etki alanı geçersiz.</string>
|
||||||
|
<string name="error_failed_app_registration">Kimliği bu sunucuda doğrulayamadı.</string>
|
||||||
|
<string name="error_no_web_browser_found">Kullanılabilen tarayıcı bulunmadı.</string>
|
||||||
|
<string name="error_authorization_unknown">Açıklanmayan kimlik doğrulama hata oluştu.</string>
|
||||||
|
<string name="error_authorization_denied">Kimlik doğrulama reddedildi.</string>
|
||||||
|
<string name="error_retrieving_oauth_token">Giriş jetonu alınamadı.</string>
|
||||||
|
<string name="error_compose_character_limit">İleti fazlasıyla uzun!</string>
|
||||||
|
<string name="error_media_upload_size">Dosya 4MB\'ten küçük olmalı.</string>
|
||||||
|
<string name="error_media_upload_type">O biçim dosya yüklenmez.</string>
|
||||||
|
<string name="error_media_upload_opening">O dosya açılamadı.</string>
|
||||||
|
<string name="error_media_upload_permission">Medya okuma izni gerekiyor.</string>
|
||||||
|
<string name="error_media_download_permission">Medya kaydetmek için izin gerekiyor.</string>
|
||||||
|
|
||||||
|
<string name="error_media_upload_image_or_video">Aynı iletiye kem video hem resim eklenemez.</string>
|
||||||
|
<string name="error_media_upload_sending">Yükleme başarsız.</string>
|
||||||
|
<string name="error_report_too_few_statuses">Her bildirme en azından bir iletiyi işaret etmeli.</string>
|
||||||
|
|
||||||
|
<string name="title_home">Ana sayfa</string>
|
||||||
|
<string name="title_notifications">Bildirimler</string>
|
||||||
|
<string name="title_public_local">Yerel</string>
|
||||||
|
<string name="title_public_federated">Birleşmiş</string>
|
||||||
|
<string name="title_thread">Dizi</string>
|
||||||
|
<string name="title_tag">#%s</string>
|
||||||
|
<string name="title_statuses">İletiler</string>
|
||||||
|
<string name="title_follows">Takip edilenler</string>
|
||||||
|
<string name="title_followers">Takipçiler</string>
|
||||||
|
<string name="title_favourites">Favoriler</string>
|
||||||
|
<string name="title_blocks">Engellenmiş kullanıcılar</string>
|
||||||
|
|
||||||
|
<string name="status_username_format">\@%s</string>
|
||||||
|
<string name="status_boosted_format">%s yükseltti</string>
|
||||||
|
<string name="status_sensitive_media_title">Hasas Medya</string>
|
||||||
|
<string name="status_sensitive_media_directions">Görmek için taklayın</string>
|
||||||
|
<string name="status_content_warning_show_more">Daha Fazla Göster</string>
|
||||||
|
<string name="status_content_warning_show_less">Daha Az Göster</string>
|
||||||
|
|
||||||
|
<string name="footer_end_of_statuses">iletilerin sonu</string>
|
||||||
|
<string name="footer_end_of_notifications">bildirmelerin sonu</string>
|
||||||
|
<string name="footer_end_of_accounts">hesapların sonu</string>
|
||||||
|
<string name="footer_empty">Henüz hiç ileti yoktur. Yenilemek için aşağıya çek!</string>
|
||||||
|
|
||||||
|
<string name="notification_reblog_format">%s iletini yükseltti</string>
|
||||||
|
<string name="notification_favourite_format">%s iletini favori etti</string>
|
||||||
|
<string name="notification_follow_format">%s seni takip etti</string>
|
||||||
|
|
||||||
|
<string name="report_username_format">\@%s bildir</string>
|
||||||
|
<string name="report_comment_hint">Daha fazla yorum?</string>
|
||||||
|
|
||||||
|
<string name="action_reply">Yanıtla</string>
|
||||||
|
<string name="action_reblog">Yükselt</string>
|
||||||
|
<string name="action_favourite">Favori edin</string>
|
||||||
|
<string name="action_more">Daha fazla</string>
|
||||||
|
<string name="action_compose">Oluştur</string>
|
||||||
|
<string name="action_login">Mastodon ile giriş yap</string>
|
||||||
|
<string name="action_logout">Çıkış Yap</string>
|
||||||
|
<string name="action_follow">Takip et</string>
|
||||||
|
<string name="action_unfollow">Takibi bırak</string>
|
||||||
|
<string name="action_block">Engelle</string>
|
||||||
|
<string name="action_unblock">Engeli kaldır</string>
|
||||||
|
<string name="action_report">Bildir</string>
|
||||||
|
<string name="action_delete">Sil</string>
|
||||||
|
<string name="action_send">İLET</string>
|
||||||
|
<string name="action_send_public">İLET!</string>
|
||||||
|
<string name="action_retry">Tekrar dene</string>
|
||||||
|
<string name="action_mark_sensitive">Medyayı hassas olarak etiketle</string>
|
||||||
|
<string name="action_hide_text">Metini uyarı ile sakla</string>
|
||||||
|
<string name="action_ok">Tamam</string>
|
||||||
|
<string name="action_cancel">İptal</string>
|
||||||
|
<string name="action_close">Kapat</string>
|
||||||
|
<string name="action_back">Geri</string>
|
||||||
|
<string name="action_view_profile">Profil</string>
|
||||||
|
<string name="action_view_preferences">Ayarlar</string>
|
||||||
|
<string name="action_view_favourites">Favoriler</string>
|
||||||
|
<string name="action_view_blocks">Engellenmiş kullanıcılar</string>
|
||||||
|
<string name="action_view_thread">Dizi</string>
|
||||||
|
<string name="action_view_media">Medya</string>
|
||||||
|
<string name="action_open_in_web">Tarayıcıda aç</string>
|
||||||
|
<string name="action_submit">Gönder</string>
|
||||||
|
<string name="action_photo_pick">Medya ekle</string>
|
||||||
|
<string name="action_photo_take">Resim çek</string>
|
||||||
|
<string name="action_share">Paylaş</string>
|
||||||
|
<string name="action_mute">Sesize al</string>
|
||||||
|
<string name="action_unmute">Sesizden kaldır</string>
|
||||||
|
<string name="action_mention">Bahset</string>
|
||||||
|
<string name="toggle_nsfw">UYARILI</string>
|
||||||
|
<string name="action_compose_options">Seçenekler</string>
|
||||||
|
<string name="action_open_drawer">Çekmece aç</string>
|
||||||
|
<string name="action_clear">Temizle</string>
|
||||||
|
<string name="action_save">Kaydet</string>
|
||||||
|
<string name="action_edit_profile">Profili düzelt</string>
|
||||||
|
|
||||||
|
<string name="send_status_link_to">İletinin adresini paylaş…</string>
|
||||||
|
<string name="send_status_content_to">İletiyi paylaş…</string>
|
||||||
|
|
||||||
|
<string name="search">Hesaplarda ara…</string>
|
||||||
|
|
||||||
|
<string name="confirmation_send">İlet!</string>
|
||||||
|
<string name="confirmation_reported">İletildi!</string>
|
||||||
|
|
||||||
|
<string name="hint_domain">Hangi sunucu?</string>
|
||||||
|
<string name="hint_compose">Neler oluyor?</string>
|
||||||
|
<string name="hint_content_warning">İçerik uyarı</string>
|
||||||
|
<string name="hint_display_name">Görünen ad</string>
|
||||||
|
<string name="hint_note">Biyo</string>
|
||||||
|
|
||||||
|
<string name="label_avatar">Simge</string>
|
||||||
|
<string name="label_header">Üstlük</string>
|
||||||
|
|
||||||
|
<string name="link_whats_an_instance">Sunucu nedir?</string>
|
||||||
|
|
||||||
|
<string name="dialog_whats_an_instance">Burada her hangi bir Mastodon sunucusunun adresi
|
||||||
|
(mastodon.social, icosahedron.website, social.tchncs.de, ve
|
||||||
|
<a href="https://github.com/tootsuite/mastodon/blob/master/docs/Using-Mastodon/List-of-Mastodon-instances.md">daha fazla!</a>) girilebiliri.
|
||||||
|
\n\nEğer hesabınız henüz yok ise katılmak istediğiniz sunucunun adresini girerek hesap yaratabilirsin.
|
||||||
|
\n\nHer bir sunucu hesaplar ağırlayan bir yer olur ancak diğer sunucularda bulunan insanlarla
|
||||||
|
aynı sitede olmuşcasına iletişime geçip takip edebilirsiniz.
|
||||||
|
\n\nDaha fazla bilgi için <a href="https://mastodon.social/about">mastodon.social</a>.
|
||||||
|
</string>
|
||||||
|
<string name="dialog_title_finishing_media_upload">Medya Yükleme Bittiriliyor</string>
|
||||||
|
<string name="dialog_message_uploading_media">Yükleniyor…</string>
|
||||||
|
<string name="dialog_download_image">İndir</string>
|
||||||
|
|
||||||
|
<string name="visibility_public">Kamu: Herkese açık ve sosyal çizelgelerinde çıkar</string>
|
||||||
|
<string name="visibility_unlisted">Saklı: Herkese açık ancık sosyal çizelgesinde çıkmaz</string>
|
||||||
|
<string name="visibility_private">Özel: Sadece takipçiler ve bahsedilenlere açık</string>
|
||||||
|
<string name="visibility_direct">Direkt: Sadece bahsedilen kullanıcılara açık</string>
|
||||||
|
|
||||||
|
<string name="pref_title_notification_settings">Bildirimler</string>
|
||||||
|
<string name="pref_title_edit_notification_settings">Bildirimleri düzelt</string>
|
||||||
|
<string name="pref_title_notifications_enabled">Anlık bildirimler</string>
|
||||||
|
<string name="pref_title_notification_alerts">Uyarılar</string>
|
||||||
|
<string name="pref_title_notification_alert_sound">Sesle bildir</string>
|
||||||
|
<string name="pref_title_notification_alert_vibrate">Titremeyle bildir</string>
|
||||||
|
<string name="pref_title_notification_alert_light">Işığıyla bildir</string>
|
||||||
|
<string name="pref_title_notification_filters">Beni bildir</string>
|
||||||
|
<string name="pref_title_notification_filter_mentions">bahsedilince</string>
|
||||||
|
<string name="pref_title_notification_filter_follows">takip edilince</string>
|
||||||
|
<string name="pref_title_notification_filter_reblogs">iletilerim yüksetilince</string>
|
||||||
|
<string name="pref_title_notification_filter_favourites">iletilerim favori edilince</string>
|
||||||
|
<string name="pref_title_appearance_settings">Görünüş</string>
|
||||||
|
<string name="pref_title_light_theme">Açık renkli temayı kullan</string>
|
||||||
|
<string name="pref_title_browser_settings">Tarayacı</string>
|
||||||
|
<string name="pref_title_custom_tabs">Chrome Özel Şekmelerini Kullan</string>
|
||||||
|
<string name="pref_title_hide_follow_button">Kaydırırken takip düğmesi gizlensin</string>
|
||||||
|
|
||||||
|
<string name="notification_mention_format">%s senden bahsetti</string>
|
||||||
|
<string name="notification_summary_large">%1$s, %2$s, %3$s ve %4$d daha</string>
|
||||||
|
<string name="notification_summary_medium">%1$s, %2$s ve %3$s</string>
|
||||||
|
<string name="notification_summary_small">%1$s ve %2$s</string>
|
||||||
|
<string name="notification_title_summary">%d yeni etkileşim</string>
|
||||||
|
|
||||||
|
<string name="description_account_locked">Kitli Hesap</string>
|
||||||
|
<string name="status_share_content">İletinin içeriğini paylaş</string>
|
||||||
|
<string name="status_share_link">İletinin adresini paylaş</string>
|
||||||
|
</resources>
|
|
@ -1,6 +1,7 @@
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<string name="error_generic">An error occurred.</string>
|
<string name="error_generic">An error occurred.</string>
|
||||||
|
<string name="error_empty">This cannot be empty.</string>
|
||||||
<string name="error_invalid_domain">Invalid domain entered</string>
|
<string name="error_invalid_domain">Invalid domain entered</string>
|
||||||
<string name="error_failed_app_registration">Failed authenticating with that instance.</string>
|
<string name="error_failed_app_registration">Failed authenticating with that instance.</string>
|
||||||
<string name="error_no_web_browser_found">Couldn\'t find a web browser to use.</string>
|
<string name="error_no_web_browser_found">Couldn\'t find a web browser to use.</string>
|
||||||
|
@ -12,6 +13,8 @@
|
||||||
<string name="error_media_upload_type">That type of file cannot be uploaded.</string>
|
<string name="error_media_upload_type">That type of file cannot be uploaded.</string>
|
||||||
<string name="error_media_upload_opening">That file could not be opened.</string>
|
<string name="error_media_upload_opening">That file could not be opened.</string>
|
||||||
<string name="error_media_upload_permission">Permission to read media is required.</string>
|
<string name="error_media_upload_permission">Permission to read media is required.</string>
|
||||||
|
<string name="error_media_download_permission">Permission to store media is required.</string>
|
||||||
|
|
||||||
<string name="error_media_upload_image_or_video">Images and videos cannot both be attached to the same status.</string>
|
<string name="error_media_upload_image_or_video">Images and videos cannot both be attached to the same status.</string>
|
||||||
<string name="error_media_upload_sending">The upload failed.</string>
|
<string name="error_media_upload_sending">The upload failed.</string>
|
||||||
<string name="error_report_too_few_statuses">At least one status must be reported.</string>
|
<string name="error_report_too_few_statuses">At least one status must be reported.</string>
|
||||||
|
@ -26,7 +29,9 @@
|
||||||
<string name="title_follows">Follows</string>
|
<string name="title_follows">Follows</string>
|
||||||
<string name="title_followers">Followers</string>
|
<string name="title_followers">Followers</string>
|
||||||
<string name="title_favourites">Favourites</string>
|
<string name="title_favourites">Favourites</string>
|
||||||
|
<string name="title_mutes">Muted users</string>
|
||||||
<string name="title_blocks">Blocked users</string>
|
<string name="title_blocks">Blocked users</string>
|
||||||
|
<string name="title_follow_requests">Follow Requests</string>
|
||||||
|
|
||||||
<string name="status_username_format">\@%s</string>
|
<string name="status_username_format">\@%s</string>
|
||||||
<string name="status_boosted_format">%s boosted</string>
|
<string name="status_boosted_format">%s boosted</string>
|
||||||
|
@ -38,6 +43,7 @@
|
||||||
<string name="footer_end_of_statuses">end of the statuses</string>
|
<string name="footer_end_of_statuses">end of the statuses</string>
|
||||||
<string name="footer_end_of_notifications">end of the notifications</string>
|
<string name="footer_end_of_notifications">end of the notifications</string>
|
||||||
<string name="footer_end_of_accounts">end of the accounts</string>
|
<string name="footer_end_of_accounts">end of the accounts</string>
|
||||||
|
<string name="footer_empty">There are no toots here so far. Pull down to refresh!</string>
|
||||||
|
|
||||||
<string name="notification_reblog_format">%s boosted your toot</string>
|
<string name="notification_reblog_format">%s boosted your toot</string>
|
||||||
<string name="notification_favourite_format">%s favourited your toot</string>
|
<string name="notification_favourite_format">%s favourited your toot</string>
|
||||||
|
@ -71,12 +77,15 @@
|
||||||
<string name="action_view_profile">Profile</string>
|
<string name="action_view_profile">Profile</string>
|
||||||
<string name="action_view_preferences">Preferences</string>
|
<string name="action_view_preferences">Preferences</string>
|
||||||
<string name="action_view_favourites">Favourites</string>
|
<string name="action_view_favourites">Favourites</string>
|
||||||
|
<string name="action_view_mutes">Muted users</string>
|
||||||
<string name="action_view_blocks">Blocked users</string>
|
<string name="action_view_blocks">Blocked users</string>
|
||||||
|
<string name="action_view_follow_requests">Follow Requests</string>
|
||||||
<string name="action_view_thread">Thread</string>
|
<string name="action_view_thread">Thread</string>
|
||||||
<string name="action_view_media">Media</string>
|
<string name="action_view_media">Media</string>
|
||||||
<string name="action_open_in_web">Open in browser</string>
|
<string name="action_open_in_web">Open in browser</string>
|
||||||
<string name="action_submit">Submit</string>
|
<string name="action_submit">Submit</string>
|
||||||
<string name="action_photo_pick">Add media</string>
|
<string name="action_photo_pick">Add media</string>
|
||||||
|
<string name="action_photo_take">Take photo</string>
|
||||||
<string name="action_share">Share</string>
|
<string name="action_share">Share</string>
|
||||||
<string name="action_mute">Mute</string>
|
<string name="action_mute">Mute</string>
|
||||||
<string name="action_unmute">Unmute</string>
|
<string name="action_unmute">Unmute</string>
|
||||||
|
@ -85,17 +94,30 @@
|
||||||
<string name="action_compose_options">Options</string>
|
<string name="action_compose_options">Options</string>
|
||||||
<string name="action_open_drawer">Open drawer</string>
|
<string name="action_open_drawer">Open drawer</string>
|
||||||
<string name="action_clear">Clear</string>
|
<string name="action_clear">Clear</string>
|
||||||
|
<string name="action_save">Save</string>
|
||||||
|
<string name="action_edit_profile">Edit profile</string>
|
||||||
|
<string name="action_undo">Undo</string>
|
||||||
|
<string name="action_accept">Accept</string>
|
||||||
|
<string name="action_reject">Reject</string>
|
||||||
|
|
||||||
<string name="send_status_to">Share toot URL to…</string>
|
<string name="send_status_link_to">Share toot URL to…</string>
|
||||||
|
<string name="send_status_content_to">Share toot to…</string>
|
||||||
|
|
||||||
<string name="search">Search accounts…</string>
|
<string name="search">Search accounts…</string>
|
||||||
|
|
||||||
<string name="confirmation_send">Toot!</string>
|
<string name="confirmation_send">Toot!</string>
|
||||||
<string name="confirmation_reported">Sent!</string>
|
<string name="confirmation_reported">Sent!</string>
|
||||||
|
<string name="confirmation_unblocked">User unblocked</string>
|
||||||
|
<string name="confirmation_unmuted">User unmuted</string>
|
||||||
|
|
||||||
<string name="hint_domain">Which instance?</string>
|
<string name="hint_domain">Which instance?</string>
|
||||||
<string name="hint_compose">What\'s happening?</string>
|
<string name="hint_compose">What\'s happening?</string>
|
||||||
<string name="hint_content_warning">Content warning</string>
|
<string name="hint_content_warning">Content warning</string>
|
||||||
|
<string name="hint_display_name">Display name</string>
|
||||||
|
<string name="hint_note">Bio</string>
|
||||||
|
|
||||||
|
<string name="label_avatar">Avatar</string>
|
||||||
|
<string name="label_header">Header</string>
|
||||||
|
|
||||||
<string name="link_whats_an_instance">What\'s an instance?</string>
|
<string name="link_whats_an_instance">What\'s an instance?</string>
|
||||||
|
|
||||||
|
@ -110,10 +132,12 @@
|
||||||
</string>
|
</string>
|
||||||
<string name="dialog_title_finishing_media_upload">Finishing Media Upload</string>
|
<string name="dialog_title_finishing_media_upload">Finishing Media Upload</string>
|
||||||
<string name="dialog_message_uploading_media">Uploading…</string>
|
<string name="dialog_message_uploading_media">Uploading…</string>
|
||||||
|
<string name="dialog_download_image">Download</string>
|
||||||
|
|
||||||
<string name="visibility_public">Everyone can view</string>
|
<string name="visibility_public">Public: Post to public timelines</string>
|
||||||
<string name="visibility_unlisted">Everyone can view, but not on public timelines</string>
|
<string name="visibility_unlisted">Unlisted: Do not show in public timelines</string>
|
||||||
<string name="visibility_private">Only followers and mentions can view</string>
|
<string name="visibility_private">Private: Post to followers only</string>
|
||||||
|
<string name="visibility_direct">Direct: Post to mentioned users only</string>
|
||||||
|
|
||||||
<string name="pref_title_notification_settings">Notifications</string>
|
<string name="pref_title_notification_settings">Notifications</string>
|
||||||
<string name="pref_title_edit_notification_settings">Edit Notifications</string>
|
<string name="pref_title_edit_notification_settings">Edit Notifications</string>
|
||||||
|
@ -140,5 +164,6 @@
|
||||||
<string name="notification_title_summary">%d new interactions</string>
|
<string name="notification_title_summary">%d new interactions</string>
|
||||||
|
|
||||||
<string name="description_account_locked">Locked Account</string>
|
<string name="description_account_locked">Locked Account</string>
|
||||||
|
<string name="status_share_content">Share content of toot</string>
|
||||||
|
<string name="status_share_link">Share link to toot</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
4
app/src/main/res/xml/file_paths.xml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<external-path name="my_images" />
|
||||||
|
</paths>
|