Merge remote-tracking branch 'upstream/master' into upstream-release
This commit is contained in:
commit
a3b6decb72
11
README.md
11
README.md
|
@ -3,10 +3,17 @@ Mastodon for Android
|
||||||
|
|
||||||
[![Crowdin](https://badges.crowdin.net/mastodon-for-android/localized.svg)](https://crowdin.com/project/mastodon-for-android)
|
[![Crowdin](https://badges.crowdin.net/mastodon-for-android/localized.svg)](https://crowdin.com/project/mastodon-for-android)
|
||||||
|
|
||||||
<a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android"><img src="img/google-play-badge.png" height="50"></a>
|
|
||||||
|
|
||||||
This is the repository for the official Android app for Mastodon.
|
This is the repository for the official Android app for Mastodon.
|
||||||
|
|
||||||
|
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||||
|
alt="Get it on F-Droid"
|
||||||
|
height="80">](https://f-droid.org/packages/org.joinmastodon.android/)
|
||||||
|
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png"
|
||||||
|
alt="Get it on Google Play"
|
||||||
|
height="80">](https://play.google.com/store/apps/details?id=org.joinmastodon.android)
|
||||||
|
|
||||||
|
Or get the APK from the [The Releases Section](https://github.com/mastodon/mastodon-android/releases/latest).
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Our goal is delivering a polished, professionally designed and user-friendly app. We proceed according to wireframes provided by a professional UX designer that works with Mastodon gGmbH. This means that any outside contributions that change the app visually must first be coordinated with the UX designer. *This can take time.* Furthermore, we work off of an internal roadmap and aim for feature-parity and consistency with our iOS app. The iOS app is designated as the "primary" between the two, therefore, if you want to request features, please do so in the [Mastodon for iOS](https://github.com/mastodon/mastodon-ios) repository, as you are requesting a feature to be both in iOS and Android (exceptions being system integrations specific to Android). On the other hand, any contributions that improve existing functionality, performance, or accessibility should not have any roadblocks to being merged.
|
Our goal is delivering a polished, professionally designed and user-friendly app. We proceed according to wireframes provided by a professional UX designer that works with Mastodon gGmbH. This means that any outside contributions that change the app visually must first be coordinated with the UX designer. *This can take time.* Furthermore, we work off of an internal roadmap and aim for feature-parity and consistency with our iOS app. The iOS app is designated as the "primary" between the two, therefore, if you want to request features, please do so in the [Mastodon for iOS](https://github.com/mastodon/mastodon-ios) repository, as you are requesting a feature to be both in iOS and Android (exceptions being system integrations specific to Android). On the other hand, any contributions that improve existing functionality, performance, or accessibility should not have any roadblocks to being merged.
|
||||||
|
|
|
@ -162,6 +162,8 @@ public class PushSubscriptionManager{
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(PushSubscription result){
|
public void onSuccess(PushSubscription result){
|
||||||
MastodonAPIController.runInBackground(()->{
|
MastodonAPIController.runInBackground(()->{
|
||||||
|
result.serverKey=result.serverKey.replace('/','_');
|
||||||
|
result.serverKey=result.serverKey.replace('+','-');
|
||||||
serverKey=deserializeRawPublicKey(Base64.decode(result.serverKey, Base64.URL_SAFE));
|
serverKey=deserializeRawPublicKey(Base64.decode(result.serverKey, Base64.URL_SAFE));
|
||||||
|
|
||||||
AccountSession session=AccountSessionManager.getInstance().tryGetAccount(accountID);
|
AccountSession session=AccountSessionManager.getInstance().tryGetAccount(accountID);
|
||||||
|
|
|
@ -8,9 +8,11 @@ import org.joinmastodon.android.model.Status;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class GetTrendingStatuses extends MastodonAPIRequest<List<Status>>{
|
public class GetTrendingStatuses extends MastodonAPIRequest<List<Status>>{
|
||||||
public GetTrendingStatuses(int limit){
|
public GetTrendingStatuses(int offset, int limit){
|
||||||
super(HttpMethod.GET, "/trends/statuses", new TypeToken<>(){});
|
super(HttpMethod.GET, "/trends/statuses", new TypeToken<>(){});
|
||||||
if(limit>0)
|
if(limit>0)
|
||||||
addQueryParameter("limit", ""+limit);
|
addQueryParameter("limit", ""+limit);
|
||||||
|
if(offset>0)
|
||||||
|
addQueryParameter("offset", ""+offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import static android.os.ext.SdkExtensions.getExtensionVersion;
|
||||||
|
|
||||||
import android.animation.ObjectAnimator;
|
import android.animation.ObjectAnimator;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
@ -20,6 +22,7 @@ import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
import android.provider.MediaStore;
|
||||||
import android.provider.OpenableColumns;
|
import android.provider.OpenableColumns;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.InputFilter;
|
import android.text.InputFilter;
|
||||||
|
@ -410,6 +413,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||||
|
|
||||||
mainEditText.setSelectionListener(this);
|
mainEditText.setSelectionListener(this);
|
||||||
mainEditText.addTextChangedListener(new TextWatcher(){
|
mainEditText.addTextChangedListener(new TextWatcher(){
|
||||||
|
private int lastChangeStart, lastChangeCount;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after){
|
public void beforeTextChanged(CharSequence s, int start, int count, int after){
|
||||||
|
|
||||||
|
@ -419,6 +424,16 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count){
|
public void onTextChanged(CharSequence s, int start, int before, int count){
|
||||||
if(s.length()==0)
|
if(s.length()==0)
|
||||||
return;
|
return;
|
||||||
|
lastChangeStart=start;
|
||||||
|
lastChangeCount=count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s){
|
||||||
|
if(s.length()==0)
|
||||||
|
return;
|
||||||
|
int start=lastChangeStart;
|
||||||
|
int count=lastChangeCount;
|
||||||
// offset one char back to catch an already typed '@' or '#' or ':'
|
// offset one char back to catch an already typed '@' or '#' or ':'
|
||||||
int realStart=start;
|
int realStart=start;
|
||||||
start=Math.max(0, start-1);
|
start=Math.max(0, start-1);
|
||||||
|
@ -464,10 +479,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||||
editable.removeSpan(span);
|
editable.removeSpan(span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable s){
|
|
||||||
updateCharCounter();
|
updateCharCounter();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -514,7 +526,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||||
DraftMediaAttachment da=new DraftMediaAttachment();
|
DraftMediaAttachment da=new DraftMediaAttachment();
|
||||||
da.serverAttachment=att;
|
da.serverAttachment=att;
|
||||||
da.description=att.description;
|
da.description=att.description;
|
||||||
da.uri=Uri.parse(att.previewUrl);
|
da.uri=att.previewUrl!=null ? Uri.parse(att.previewUrl) : null;
|
||||||
da.state=AttachmentUploadState.DONE;
|
da.state=AttachmentUploadState.DONE;
|
||||||
attachmentsView.addView(createMediaAttachmentView(da));
|
attachmentsView.addView(createMediaAttachmentView(da));
|
||||||
attachments.add(da);
|
attachments.add(da);
|
||||||
|
@ -782,14 +794,50 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check to see if Android platform photopicker is available on the device\
|
||||||
|
* @return whether the device supports photopicker intents.
|
||||||
|
*/
|
||||||
|
private boolean isPhotoPickerAvailable() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
return true;
|
||||||
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
return getExtensionVersion(Build.VERSION_CODES.R) >= 2;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the correct intent for the device version to select media.
|
||||||
|
*
|
||||||
|
* <p>For Device version > T or R_SDK_v2, use the android platform photopicker via
|
||||||
|
* {@link MediaStore#ACTION_PICK_IMAGES}
|
||||||
|
*
|
||||||
|
* <p>For earlier versions use the built in docs ui via {@link Intent#ACTION_GET_CONTENT}
|
||||||
|
*/
|
||||||
private void openFilePicker(){
|
private void openFilePicker(){
|
||||||
Intent intent=new Intent(Intent.ACTION_GET_CONTENT);
|
Intent intent;
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
boolean usePhotoPicker = isPhotoPickerAvailable();
|
||||||
intent.setType("*/*");
|
if (usePhotoPicker) {
|
||||||
if(instance.configuration!=null && instance.configuration.mediaAttachments!=null && instance.configuration.mediaAttachments.supportedMimeTypes!=null && !instance.configuration.mediaAttachments.supportedMimeTypes.isEmpty()){
|
intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
|
||||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, instance.configuration.mediaAttachments.supportedMimeTypes.toArray(new String[0]));
|
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
|
||||||
}else{
|
} else {
|
||||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*"});
|
intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
intent.setType("*/*");
|
||||||
|
}
|
||||||
|
if (!usePhotoPicker && instance.configuration != null &&
|
||||||
|
instance.configuration.mediaAttachments != null &&
|
||||||
|
instance.configuration.mediaAttachments.supportedMimeTypes != null &&
|
||||||
|
!instance.configuration.mediaAttachments.supportedMimeTypes.isEmpty()) {
|
||||||
|
intent.putExtra(Intent.EXTRA_MIME_TYPES,
|
||||||
|
instance.configuration.mediaAttachments.supportedMimeTypes.toArray(
|
||||||
|
new String[0]));
|
||||||
|
} else {
|
||||||
|
if (!usePhotoPicker) {
|
||||||
|
// If photo picker is being used these are the default mimetypes.
|
||||||
|
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*"});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
|
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
|
||||||
startActivityForResult(intent, MEDIA_RESULT);
|
startActivityForResult(intent, MEDIA_RESULT);
|
||||||
|
@ -871,7 +919,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||||
View thumb=getActivity().getLayoutInflater().inflate(R.layout.compose_media_thumb, attachmentsView, false);
|
View thumb=getActivity().getLayoutInflater().inflate(R.layout.compose_media_thumb, attachmentsView, false);
|
||||||
ImageView img=thumb.findViewById(R.id.thumb);
|
ImageView img=thumb.findViewById(R.id.thumb);
|
||||||
if(draft.serverAttachment!=null){
|
if(draft.serverAttachment!=null){
|
||||||
ViewImageLoader.load(img, draft.serverAttachment.blurhashPlaceholder, new UrlImageLoaderRequest(draft.serverAttachment.previewUrl, V.dp(250), V.dp(250)));
|
if(draft.serverAttachment.previewUrl!=null)
|
||||||
|
ViewImageLoader.load(img, draft.serverAttachment.blurhashPlaceholder, new UrlImageLoaderRequest(draft.serverAttachment.previewUrl, V.dp(250), V.dp(250)));
|
||||||
}else{
|
}else{
|
||||||
if(draft.mimeType.startsWith("image/")){
|
if(draft.mimeType.startsWith("image/")){
|
||||||
ViewImageLoader.load(img, null, new UrlImageLoaderRequest(draft.uri, V.dp(250), V.dp(250)));
|
ViewImageLoader.load(img, null, new UrlImageLoaderRequest(draft.uri, V.dp(250), V.dp(250)));
|
||||||
|
|
|
@ -17,11 +17,11 @@ public class DiscoverPostsFragment extends StatusListFragment{
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
currentRequest=new GetTrendingStatuses(count)
|
currentRequest=new GetTrendingStatuses(offset, count)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
onDataLoaded(result, false);
|
onDataLoaded(result, !result.isEmpty());
|
||||||
}
|
}
|
||||||
}).exec(accountID);
|
}).exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue