Merge branch 'main' into fork

This commit is contained in:
sk
2022-11-28 12:07:35 +01:00
61 changed files with 651 additions and 395 deletions

View File

@@ -100,7 +100,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
private void bindButton(TextView btn, long count){
if(GlobalUserPreferences.showInteractionCounts && count>0 && !item.hideCounts){
btn.setText(DecimalFormat.getIntegerInstance().format(count));
btn.setText(UiUtils.abbreviateNumber(count));
btn.setCompoundDrawablePadding(V.dp(8));
}else{
btn.setText("");

View File

@@ -5,6 +5,7 @@ import android.app.ProgressDialog;
import android.graphics.Outline;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
@@ -209,6 +210,8 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
});
}else if(id==R.id.block_domain){
UiUtils.confirmToggleBlockDomain(activity, item.parentFragment.getAccountID(), account.getDomain(), relationship!=null && relationship.domainBlocking, ()->{});
}else if(id==R.id.bookmark){
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setBookmarked(item.status, !item.status.bookmarked);
}
return true;
});
@@ -226,6 +229,9 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
if(item.hasVisibilityToggle){
visibility.setImageResource(item.status.spoilerRevealed ? R.drawable.ic_visibility_off : R.drawable.ic_visibility);
visibility.setContentDescription(item.parentFragment.getString(item.status.spoilerRevealed ? R.string.hide_content : R.string.reveal_content));
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
visibility.setTooltipText(visibility.getContentDescription());
}
}
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : 0);
if(TextUtils.isEmpty(item.extraText)){
@@ -306,6 +312,16 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
MenuItem block=menu.findItem(R.id.block);
MenuItem report=menu.findItem(R.id.report);
MenuItem follow=menu.findItem(R.id.follow);
MenuItem bookmark=menu.findItem(R.id.bookmark);
bookmark.setVisible(false);
/* disabled in megalodon: add/remove bookmark is already available through status footer
if(item.status!=null){
bookmark.setVisible(true);
bookmark.setTitle(item.status.bookmarked ? R.string.remove_bookmark : R.string.add_bookmark);
}else{
bookmark.setVisible(false);
}
*/
if(isOwnPost){
mute.setVisible(false);
block.setVisible(false);

View File

@@ -38,13 +38,13 @@ public class PollFooterStatusDisplayItem extends StatusDisplayItem{
@Override
public void onBind(PollFooterStatusDisplayItem item){
String text=item.parentFragment.getResources().getQuantityString(R.plurals.x_voters, item.poll.votersCount, item.poll.votersCount);
if(item.poll.expiresAt!=null && !item.poll.expired){
if(item.poll.expiresAt!=null && !item.poll.isExpired()){
text+=" · "+UiUtils.formatTimeLeft(itemView.getContext(), item.poll.expiresAt);
}else if(item.poll.expired){
}else if(item.poll.isExpired()){
text+=" · "+item.parentFragment.getString(R.string.poll_closed);
}
this.text.setText(text);
button.setVisibility(item.poll.expired || item.poll.voted || !item.poll.multiple ? View.GONE : View.VISIBLE);
button.setVisibility(item.poll.isExpired() || item.poll.voted || !item.poll.multiple ? View.GONE : View.VISIBLE);
button.setEnabled(item.poll.selectedOptions!=null && !item.poll.selectedOptions.isEmpty());
}
}

View File

@@ -33,7 +33,7 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
this.poll=poll;
text=HtmlParser.parseCustomEmoji(option.title, poll.emojis);
emojiHelper.setText(text);
showResults=poll.expired || poll.voted;
showResults=poll.isExpired() || poll.voted;
if(showResults && option.votesCount!=null && poll.votersCount>0){
votesFraction=(float)option.votesCount/(float)poll.votersCount;
int mostVotedCount=0;

View File

@@ -2,8 +2,11 @@ package org.joinmastodon.android.ui.text;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.widget.TextView;
import com.twitter.twittertext.Regex;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.Mention;
@@ -28,6 +31,21 @@ import androidx.annotation.NonNull;
public class HtmlParser{
private static final String TAG="HtmlParser";
private static final String VALID_URL_PATTERN_STRING =
"(" + // $1 total match
"(" + Regex.URL_VALID_PRECEDING_CHARS + ")" + // $2 Preceding character
"(" + // $3 URL
"(https?://)" + // $4 Protocol (optional)
"(" + Regex.URL_VALID_DOMAIN + ")" + // $5 Domain(s)
"(?::(" + Regex.URL_VALID_PORT_NUMBER + "))?" + // $6 Port number (optional)
"(/" +
Regex.URL_VALID_PATH + "*+" +
")?" + // $7 URL Path and anchor
"(\\?" + Regex.URL_VALID_URL_QUERY_CHARS + "*" + // $8 Query String
Regex.URL_VALID_URL_QUERY_ENDING_CHARS + ")?" +
")" +
")";
public static final Pattern URL_PATTERN=Pattern.compile(VALID_URL_PATTERN_STRING, Pattern.CASE_INSENSITIVE);
private static Pattern EMOJI_CODE_PATTERN=Pattern.compile(":([\\w]+):");
private HtmlParser(){}
@@ -172,4 +190,18 @@ public class HtmlParser{
public static String strip(String html){
return Jsoup.clean(html, Safelist.none());
}
public static CharSequence parseLinks(String text){
Matcher matcher=URL_PATTERN.matcher(text);
if(!matcher.find()) // Return the original string if there are no URLs
return text;
SpannableStringBuilder ssb=new SpannableStringBuilder(text);
do{
String url=matcher.group(3);
if(TextUtils.isEmpty(matcher.group(4)))
url="http://"+url;
ssb.setSpan(new LinkSpan(url, null, LinkSpan.Type.URL, null), matcher.start(3), matcher.end(3), 0);
}while(matcher.find()); // Find more URLs
return ssb;
}
}

View File

@@ -48,6 +48,7 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.FollowRequestHandledEvent;
import org.joinmastodon.android.events.NotificationDeletedEvent;
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
import org.joinmastodon.android.events.StatusDeletedEvent;
import org.joinmastodon.android.events.StatusUnpinnedEvent;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
@@ -345,6 +346,9 @@ public class UiUtils{
@Override
public void onSuccess(Relationship result){
resultCallback.accept(result);
if(!currentlyBlocked){
E.post(new RemoveAccountPostsEvent(accountID, account.id, false));
}
}
@Override
@@ -387,6 +391,9 @@ public class UiUtils{
@Override
public void onSuccess(Relationship result){
resultCallback.accept(result);
if(!currentlyMuted){
E.post(new RemoveAccountPostsEvent(accountID, account.id, false));
}
}
@Override
@@ -524,6 +531,9 @@ public class UiUtils{
public void onSuccess(Relationship result){
resultCallback.accept(result);
progressCallback.accept(false);
if(!result.following){
E.post(new RemoveAccountPostsEvent(accountID, account.id, true));
}
}
@Override
@@ -660,8 +670,7 @@ public class UiUtils{
public static void openURL(Context context, @Nullable String accountID, String url){
Uri uri=Uri.parse(url);
String accountDomain=accountID != null ? AccountSessionManager.getInstance().getAccount(accountID).domain : null;
if(accountDomain!=null && "https".equals(uri.getScheme()) && accountDomain.equalsIgnoreCase(uri.getAuthority())){
if(accountID!=null && "https".equals(uri.getScheme()) && AccountSessionManager.getInstance().getAccount(accountID).domain.equalsIgnoreCase(uri.getAuthority())){
List<String> path=uri.getPathSegments();
// Match URLs like https://mastodon.social/@Gargron/108132679274083591
if(path.size()==2 && path.get(0).matches("^@[a-zA-Z0-9_]+$") && path.get(1).matches("^[0-9]+$")){

View File

@@ -8,7 +8,6 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.DragEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
@@ -22,7 +21,6 @@ import androidx.annotation.RequiresApi;
public class ComposeEditText extends EditText{
private SelectionListener selectionListener;
private MediaAcceptingInputConnection inputConnectionWrapper=new MediaAcceptingInputConnection();
public ComposeEditText(Context context){
super(context);
@@ -54,11 +52,10 @@ public class ComposeEditText extends EditText{
// Support receiving images from keyboards
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs){
final var ic = super.onCreateInputConnection(outAttrs);
final InputConnection ic=super.onCreateInputConnection(outAttrs);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N_MR1){
outAttrs.contentMimeTypes=selectionListener.onGetAllowedMediaMimeTypes();
inputConnectionWrapper.setTarget(ic);
return inputConnectionWrapper;
return new MediaAcceptingInputConnection(ic);
}
return ic;
}
@@ -106,8 +103,8 @@ public class ComposeEditText extends EditText{
}
private class MediaAcceptingInputConnection extends InputConnectionWrapper{
public MediaAcceptingInputConnection(){
super(null, true);
public MediaAcceptingInputConnection(InputConnection conn){
super(conn, false);
}
@RequiresApi(api=Build.VERSION_CODES.N_MR1)

View File

@@ -0,0 +1,70 @@
package org.joinmastodon.android.ui.views;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
import androidx.annotation.Nullable;
import me.grishka.appkit.utils.V;
public class SplashLogoView extends ImageView{
private Bitmap shadow;
private Paint paint=new Paint();
public SplashLogoView(Context context){
this(context, null);
}
public SplashLogoView(Context context, AttributeSet attrs){
this(context, attrs, 0);
}
public SplashLogoView(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
}
@Override
protected void onDraw(Canvas canvas){
if(shadow!=null){
paint.setColor(0xBF000000);
canvas.drawBitmap(shadow, 0, 0, paint);
}
super.onDraw(canvas);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh){
super.onSizeChanged(w, h, oldw, oldh);
if(w!=oldw || h!=oldh)
updateShadow();
}
@Override
public void setImageDrawable(@Nullable Drawable drawable){
super.setImageDrawable(drawable);
updateShadow();
}
private void updateShadow(){
int w=getWidth();
int h=getHeight();
Drawable drawable=getDrawable();
if(w==0 || h==0 || drawable==null)
return;
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
Bitmap temp=Bitmap.createBitmap(w, h, Bitmap.Config.ALPHA_8);
shadow=Bitmap.createBitmap(w, h, Bitmap.Config.ALPHA_8);
Canvas c=new Canvas(temp);
c.translate(getWidth()/2f-drawable.getIntrinsicWidth()/2f, getHeight()/2f-drawable.getIntrinsicHeight()/2f);
drawable.draw(c);
c=new Canvas(shadow);
Paint paint=new Paint();
paint.setShadowLayer(V.dp(2), 0, 0, 0xff000000);
c.drawBitmap(temp, 0, 0, paint);
}
}