parent
c89f14b737
commit
b0c1207f1a
|
@ -6,6 +6,7 @@
|
|||
<w>dont</w>
|
||||
<w>emoji</w>
|
||||
<w>emojione</w>
|
||||
<w>emojis</w>
|
||||
<w>enquete</w>
|
||||
<w>enty</w>
|
||||
<w>favourited</w>
|
||||
|
@ -26,6 +27,7 @@
|
|||
<w>reblogs</w>
|
||||
<w>sephiroth</w>
|
||||
<w>sharedpref</w>
|
||||
<w>shortname</w>
|
||||
<w>simeji</w>
|
||||
<w>styler</w>
|
||||
<w>subwaytooter</w>
|
||||
|
|
|
@ -9,8 +9,8 @@ android {
|
|||
applicationId "jp.juggler.subwaytooter"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 26
|
||||
versionCode 155
|
||||
versionName "1.5.5"
|
||||
versionCode 156
|
||||
versionName "1.5.6"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ package jp.juggler.subwaytooter;
|
|||
import jp.juggler.subwaytooter.dialog.ActionsDialog;
|
||||
import jp.juggler.subwaytooter.table.AcctColor;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions;
|
||||
import jp.juggler.subwaytooter.util.Emojione;
|
||||
import jp.juggler.subwaytooter.util.HTMLDecoder;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
|
@ -751,12 +752,13 @@ public class ActAccountSetting extends AppCompatActivity
|
|||
ivProfileAvatar.setImageUrl( App1.pref, 16f, src.avatar_static, src.avatar );
|
||||
ivProfileHeader.setImageUrl( App1.pref, 0f, src.header_static, src.header );
|
||||
|
||||
etDisplayName.setText( Emojione.decodeEmoji( this, src.display_name == null ? "" : src.display_name ) );
|
||||
etDisplayName.setText( Emojione.decodeEmoji( this, src.display_name == null ? "" : src.display_name ,null) );
|
||||
|
||||
if( src.source != null && src.source.note != null ){
|
||||
etNote.setText( Emojione.decodeEmoji( this, src.source.note ) );
|
||||
etNote.setText( Emojione.decodeEmoji( this, src.source.note ,null) );
|
||||
}else if( src.note != null ){
|
||||
etNote.setText( HTMLDecoder.decodeHTML( ActAccountSetting.this, account, src.note, false, false, null ,null) );
|
||||
|
||||
etNote.setText( new DecodeOptions().decodeHTML(ActAccountSetting.this, account, src.note) );
|
||||
}else{
|
||||
etNote.setText( "" );
|
||||
}
|
||||
|
|
|
@ -68,6 +68,7 @@ import jp.juggler.subwaytooter.table.AcctColor;
|
|||
import jp.juggler.subwaytooter.table.PostDraft;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.dialog.ActionsDialog;
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions;
|
||||
import jp.juggler.subwaytooter.util.HTMLDecoder;
|
||||
import jp.juggler.subwaytooter.util.LinkClickContext;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
|
@ -1553,7 +1554,10 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
|
|||
llReply.setVisibility( View.GONE );
|
||||
}else{
|
||||
llReply.setVisibility( View.VISIBLE );
|
||||
tvReplyTo.setText( HTMLDecoder.decodeHTML( ActPost.this, account, in_reply_to_text, true, true, null, null ) );
|
||||
tvReplyTo.setText( new DecodeOptions()
|
||||
.setShort( true )
|
||||
.setDecodeEmoji( true )
|
||||
.decodeHTML( ActPost.this, account, in_reply_to_text));
|
||||
ivReply.setImageUrl( pref, 16f, in_reply_to_image );
|
||||
}
|
||||
}
|
||||
|
@ -1960,7 +1964,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
|
|||
return null;
|
||||
}
|
||||
};
|
||||
CharSequence sv = HTMLDecoder.decodeHTML( ActPost.this, lcc, text, false, false, null, null );
|
||||
CharSequence sv = new DecodeOptions().decodeHTML( ActPost.this, lcc, text);
|
||||
tvText.setText( sv );
|
||||
tvText.setMovementMethod( LinkMovementMethod.getInstance() );
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import jp.juggler.subwaytooter.api_msp.entity.MSPAccount;
|
|||
import jp.juggler.subwaytooter.api_msp.entity.MSPToot;
|
||||
import jp.juggler.subwaytooter.table.MutedWord;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions;
|
||||
import jp.juggler.subwaytooter.util.HTMLDecoder;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
|
@ -71,7 +72,7 @@ public class ActText extends AppCompatActivity implements View.OnClickListener {
|
|||
addAfterLine( sb, "\n" );
|
||||
|
||||
intent.putExtra( EXTRA_CONTENT_START, sb.length() );
|
||||
sb.append( HTMLDecoder.decodeHTML( context,access_info, status.content, false, false, null ,null) );
|
||||
sb.append( new DecodeOptions().decodeHTML( context,access_info, status.content));
|
||||
intent.putExtra( EXTRA_CONTENT_END, sb.length() );
|
||||
|
||||
if( status instanceof TootStatus ){
|
||||
|
@ -121,7 +122,7 @@ public class ActText extends AppCompatActivity implements View.OnClickListener {
|
|||
|
||||
addAfterLine( sb, "\n" );
|
||||
|
||||
sb.append( HTMLDecoder.decodeHTML( context, access_info, ( who.note != null ? who.note : null ), false, false, null ,null) );
|
||||
sb.append( new DecodeOptions().decodeHTML( context, access_info, who.note != null ? who.note : null ) );
|
||||
|
||||
addAfterLine( sb, "\n" );
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ import jp.juggler.subwaytooter.table.PostDraft;
|
|||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.table.TagSet;
|
||||
import jp.juggler.subwaytooter.table.UserRelation;
|
||||
import jp.juggler.subwaytooter.util.CustomEmojiCache;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import okhttp3.CipherSuite;
|
||||
import okhttp3.ConnectionSpec;
|
||||
|
@ -200,6 +201,8 @@ public class App1 extends Application {
|
|||
|
||||
static OkHttpUrlLoader.Factory glide_okhttp3_factory;
|
||||
|
||||
public static CustomEmojiCache custom_emoji_cache;
|
||||
|
||||
private static boolean bPrepared = false;
|
||||
|
||||
public static void prepare( final Context app_context ){
|
||||
|
@ -340,6 +343,10 @@ public class App1 extends Application {
|
|||
Glide.get( app_context ).register( GlideUrl.class, InputStream.class, glide_okhttp3_factory );
|
||||
}
|
||||
|
||||
if( custom_emoji_cache == null ){
|
||||
custom_emoji_cache = new CustomEmojiCache(app_context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -57,6 +57,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
@SuppressWarnings("WeakerAccess") class Column implements StreamReader.Callback {
|
||||
private static final LogCategory log = new LogCategory( "Column" );
|
||||
|
||||
|
||||
interface Callback {
|
||||
boolean isActivityStart();
|
||||
}
|
||||
|
@ -956,7 +957,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
return _holder_list.size() > 1;
|
||||
}
|
||||
|
||||
private ColumnViewHolder getViewHolder(){
|
||||
ColumnViewHolder getViewHolder(){
|
||||
if( is_dispose.get() ) return null;
|
||||
// 複数のリスナがある場合、最も新しいものを返す
|
||||
return _holder_list.isEmpty() ? null : _holder_list.getFirst();
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.support.annotation.NonNull;
|
|||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.text.Editable;
|
||||
import android.text.Spannable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
|
@ -27,7 +28,9 @@ import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayoutDirec
|
|||
import java.util.regex.Pattern;
|
||||
|
||||
import jp.juggler.subwaytooter.table.AcctColor;
|
||||
import jp.juggler.subwaytooter.util.CustomEmojiCache;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.NetworkEmojiSpan;
|
||||
import jp.juggler.subwaytooter.view.MyListView;
|
||||
import jp.juggler.subwaytooter.util.ScrollPosition;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
|
@ -935,4 +938,17 @@ class ColumnViewHolder
|
|||
}
|
||||
}, 20L );
|
||||
}
|
||||
|
||||
private final CustomEmojiCache.Callback emoji_load_callback = new CustomEmojiCache.Callback() {
|
||||
@Override public void onComplete( Bitmap b ){
|
||||
showContent();
|
||||
}
|
||||
};
|
||||
void applyEmojiLoadCallback( @Nullable Spannable dst){
|
||||
if( dst == null ) return;
|
||||
|
||||
for( NetworkEmojiSpan span : dst.getSpans( 0,dst.length(), NetworkEmojiSpan.class ) ){
|
||||
span.setLoadCompleteCallback( emoji_load_callback );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import android.widget.Button;
|
|||
import android.widget.TextView;
|
||||
|
||||
import jp.juggler.subwaytooter.api.entity.TootInstance;
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions;
|
||||
import jp.juggler.subwaytooter.util.HTMLDecoder;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
|
@ -104,7 +105,10 @@ class HeaderViewHolderInstance extends HeaderViewHolderBase implements View.OnCl
|
|||
btnEmail.setText( supplyEmpty( instance.email ) );
|
||||
btnEmail.setEnabled( ! TextUtils.isEmpty( instance.email ) );
|
||||
|
||||
SpannableStringBuilder sb = HTMLDecoder.decodeHTML( activity, access_info, "<p>" + supplyEmpty( instance.description ) + "</p>", false, true, null, null );
|
||||
SpannableStringBuilder sb = new DecodeOptions()
|
||||
.setDecodeEmoji( true )
|
||||
.decodeHTML( activity, access_info, "<p>" + supplyEmpty( instance.description ) + "</p>");
|
||||
|
||||
int previous_br_count = 0;
|
||||
for( int i = 0 ; i < sb.length() ; ++ i ){
|
||||
char c = sb.charAt( i );
|
||||
|
|
|
@ -105,7 +105,7 @@ class HeaderViewHolderProfile extends HeaderViewHolderBase implements View.OnCli
|
|||
if( who.locked ){
|
||||
s += " " + Emojione.map_name2unicode.get( "lock" );
|
||||
}
|
||||
tvAcct.setText( Emojione.decodeEmoji( activity, s ) );
|
||||
tvAcct.setText( Emojione.decodeEmoji( activity, s ,null) );
|
||||
|
||||
tvNote.setText( who.decoded_note );
|
||||
btnStatusCount.setText( activity.getString( R.string.statuses ) + "\n" + who.statuses_count );
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.view.View;
|
|||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions;
|
||||
import jp.juggler.subwaytooter.util.HTMLDecoder;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
|
@ -37,7 +38,7 @@ class HeaderViewHolderSearchDesc extends HeaderViewHolderBase {
|
|||
}
|
||||
} );
|
||||
|
||||
CharSequence sv = HTMLDecoder.decodeHTML( activity, access_info, html, false, true, null ,null);
|
||||
CharSequence sv = new DecodeOptions().setDecodeEmoji( true ).decodeHTML( activity, access_info, html);
|
||||
|
||||
TextView tvSearchDesc = viewRoot.findViewById( R.id.tvSearchDesc );
|
||||
tvSearchDesc.setVisibility( View.VISIBLE );
|
||||
|
|
|
@ -75,7 +75,7 @@ class ItemListAdapter extends BaseAdapter implements AdapterView.OnItemClickList
|
|||
}else{
|
||||
holder = (ItemViewHolder) view.getTag();
|
||||
}
|
||||
holder.bind( o );
|
||||
holder.bind( o ,column.getViewHolder() );
|
||||
return view;
|
||||
}
|
||||
|
||||
|
|
|
@ -251,7 +251,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
|
|||
|
||||
}
|
||||
|
||||
void bind( Object item ){
|
||||
void bind( Object item ,@Nullable ColumnViewHolder cvh){
|
||||
this.status = null;
|
||||
this.account_thumbnail = null;
|
||||
this.account_boost = null;
|
||||
|
@ -293,7 +293,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
|
|||
}
|
||||
|
||||
if( item instanceof MSPToot ){
|
||||
showStatus( activity, (MSPToot) item );
|
||||
showStatus( activity, (MSPToot) item ,cvh );
|
||||
}else if( item instanceof String ){
|
||||
showSearchTag( (String) item );
|
||||
}else if( item instanceof TootAccount ){
|
||||
|
@ -307,7 +307,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
|
|||
, access_info.isNicoru( n.account ) ? R.attr.ic_nicoru : R.attr.btn_favourite
|
||||
, Utils.formatSpannable1( activity, R.string.display_name_favourited_by, n.account.decoded_display_name )
|
||||
);
|
||||
if( n.status != null ) showStatus( activity, n.status );
|
||||
if( n.status != null ) showStatus( activity, n.status,cvh );
|
||||
}else if( TootNotification.TYPE_REBLOG.equals( n.type ) ){
|
||||
showBoost(
|
||||
n.account
|
||||
|
@ -315,7 +315,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
|
|||
, R.attr.btn_boost
|
||||
, Utils.formatSpannable1( activity, R.string.display_name_boosted_by, n.account.decoded_display_name )
|
||||
);
|
||||
if( n.status != null ) showStatus( activity, n.status );
|
||||
if( n.status != null ) showStatus( activity, n.status,cvh );
|
||||
}else if( TootNotification.TYPE_FOLLOW.equals( n.type ) ){
|
||||
showBoost(
|
||||
n.account
|
||||
|
@ -334,7 +334,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
|
|||
, Utils.formatSpannable1( activity, R.string.display_name_replied_by, n.account.decoded_display_name )
|
||||
);
|
||||
}
|
||||
if( n.status != null ) showStatus( activity, n.status );
|
||||
if( n.status != null ) showStatus( activity, n.status ,cvh);
|
||||
}
|
||||
}else if( item instanceof TootStatus ){
|
||||
TootStatus status = (TootStatus) item;
|
||||
|
@ -347,9 +347,9 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
|
|||
, Utils.formatSpannable1( activity, R.string.display_name_boosted_by, status.account.decoded_display_name )
|
||||
);
|
||||
}
|
||||
showStatus( activity, status.reblog );
|
||||
showStatus( activity, status.reblog ,cvh);
|
||||
}else{
|
||||
showStatus( activity, status );
|
||||
showStatus( activity, status ,cvh);
|
||||
}
|
||||
}else if( item instanceof TootGap ){
|
||||
showGap( (TootGap) item );
|
||||
|
@ -399,10 +399,15 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
|
|||
Styler.setFollowIcon( activity, btnFollow, ivFollowedBy, relation ,who );
|
||||
}
|
||||
|
||||
private void showStatus( @NonNull ActMain activity, @NonNull TootStatusLike status ){
|
||||
private void showStatus( @NonNull ActMain activity, @NonNull TootStatusLike status ,@Nullable ColumnViewHolder cvh){
|
||||
this.status = status;
|
||||
llStatus.setVisibility( View.VISIBLE );
|
||||
|
||||
if( cvh != null ){
|
||||
cvh.applyEmojiLoadCallback( status.decoded_content );
|
||||
cvh.applyEmojiLoadCallback( status.decoded_spoiler_text );
|
||||
}
|
||||
|
||||
showStatusTime( activity, status );
|
||||
|
||||
TootAccount who = account_thumbnail = status.account;
|
||||
|
@ -463,6 +468,8 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
|
|||
|
||||
tvContent.setMinLines( r != null ? r.originalLineCount : - 1 );
|
||||
|
||||
|
||||
|
||||
if( ! TextUtils.isEmpty( status.spoiler_text ) ){
|
||||
// 元データに含まれるContent Warning を使う
|
||||
llContentWarning.setVisibility( View.VISIBLE );
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package jp.juggler.subwaytooter.api.entity;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
|
||||
public class CustomEmojiMap extends HashMap<String,String> {
|
||||
|
||||
// キー: shortcode (コロンを含まない)
|
||||
// 値: url
|
||||
|
||||
public static CustomEmojiMap parse( JSONArray src ){
|
||||
if( src==null ) return null;
|
||||
CustomEmojiMap dst = new CustomEmojiMap();
|
||||
for(int i=0,ie=src.length();i<ie;++i){
|
||||
JSONObject it = src.optJSONObject( i );
|
||||
String k = Utils.optStringX(it,"shortcode");
|
||||
String v = Utils.optStringX(it,"url");
|
||||
if( ! TextUtils.isEmpty( k ) && ! TextUtils.isEmpty( v ) ){
|
||||
dst.put( k,v);
|
||||
}
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@ import jp.juggler.subwaytooter.R;
|
|||
import jp.juggler.subwaytooter.api.TootApiClient;
|
||||
import jp.juggler.subwaytooter.api.TootApiResult;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions;
|
||||
import jp.juggler.subwaytooter.util.Emojione;
|
||||
import jp.juggler.subwaytooter.util.HTMLDecoder;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
|
@ -77,7 +78,13 @@ public class NicoEnquete {
|
|||
dst.time_start = time_start;
|
||||
dst.status_id = status_id;
|
||||
if( dst.question != null ){
|
||||
dst.question = HTMLDecoder.decodeHTML( context, access_info, dst.question.toString(), true, true, list_attachment ,status);
|
||||
dst.question = new DecodeOptions()
|
||||
.setShort(true)
|
||||
.setDecodeEmoji( true )
|
||||
.setAttachment( list_attachment )
|
||||
.setLinkTag( status )
|
||||
.setEmojiMap( status.emojis )
|
||||
.decodeHTML( context, access_info, dst.question.toString());
|
||||
}
|
||||
if( dst.items != null ){
|
||||
for( int i = 0, ie = dst.items.size() ; i < ie ; ++ i ){
|
||||
|
@ -86,7 +93,7 @@ public class NicoEnquete {
|
|||
// remove white spaces
|
||||
sv = reWhitespace.matcher( sv ).replaceAll( " " );
|
||||
// decode emoji code
|
||||
dst.items.set( i, Emojione.decodeEmoji( context, sv ) );
|
||||
dst.items.set( i, Emojione.decodeEmoji( context, sv ,status.emojis) );
|
||||
}
|
||||
}
|
||||
return dst;
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.support.annotation.Nullable;
|
|||
import android.text.Spannable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions;
|
||||
import jp.juggler.subwaytooter.util.Emojione;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
@ -126,7 +127,7 @@ public class TootAccount {
|
|||
dst.statuses_count = src.optLong( "statuses_count" );
|
||||
|
||||
dst.note = Utils.optStringX( src, "note" );
|
||||
dst.decoded_note = HTMLDecoder.decodeHTML( context, account, ( dst.note != null ? dst.note : null ), true, true, null ,null );
|
||||
dst.decoded_note = new DecodeOptions().setShort( true).setDecodeEmoji( true).decodeHTML( context, account, ( dst.note != null ? dst.note : null ));
|
||||
|
||||
dst.url = Utils.optStringX( src, "url" );
|
||||
dst.avatar = Utils.optStringX( src, "avatar" ); // "https:\/\/mastodon.juggler.jp\/system\/accounts\/avatars\/000\/000\/148\/original\/0a468974fac5a448.PNG?1492081886",
|
||||
|
@ -191,7 +192,7 @@ public class TootAccount {
|
|||
sv = reWhitespace.matcher( this.display_name ).replaceAll( " " );
|
||||
|
||||
// decode emoji code
|
||||
this.decoded_display_name = Emojione.decodeEmoji( context, sv );
|
||||
this.decoded_display_name = Emojione.decodeEmoji( context, sv ,null);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import jp.juggler.subwaytooter.App1;
|
|||
import jp.juggler.subwaytooter.Pref;
|
||||
import jp.juggler.subwaytooter.R;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions;
|
||||
import jp.juggler.subwaytooter.util.HTMLDecoder;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
|
@ -104,6 +105,10 @@ public class TootStatus extends TootStatusLike {
|
|||
TootStatus status = new TootStatus();
|
||||
status.json = src;
|
||||
|
||||
// 絵文字マップは割と最初の方で読み込んでおきたい
|
||||
status.emojis = CustomEmojiMap.parse( src.optJSONArray( "emojis" ));
|
||||
|
||||
|
||||
status.account = TootAccount.parse( context, access_info, src.optJSONObject( "account" ) );
|
||||
|
||||
if( status.account == null ) return null;
|
||||
|
@ -142,13 +147,22 @@ public class TootStatus extends TootStatusLike {
|
|||
status.muted = src.optBoolean( "muted" );
|
||||
status.language = Utils.optStringX( src, "language" );
|
||||
|
||||
|
||||
status.time_created_at = parseTime( status.created_at );
|
||||
status.decoded_content = HTMLDecoder.decodeHTML( context, access_info, status.content, true, true, status.media_attachments ,status);
|
||||
status.decoded_content = new DecodeOptions()
|
||||
.setShort( true )
|
||||
.setDecodeEmoji( true)
|
||||
.setAttachment( status.media_attachments )
|
||||
.setEmojiMap( status.emojis )
|
||||
.setLinkTag( status )
|
||||
.decodeHTML( context, access_info, status.content );
|
||||
|
||||
// status.decoded_tags = HTMLDecoder.decodeTags( account,status.tags );
|
||||
status.decoded_mentions = HTMLDecoder.decodeMentions( access_info, status.mentions ,status);
|
||||
|
||||
status.enquete = NicoEnquete.parse( context,access_info , status.media_attachments , Utils.optStringX( src, "enquete"),status.id,status.time_created_at,status );
|
||||
|
||||
|
||||
return status;
|
||||
}catch( Throwable ex ){
|
||||
log.trace( ex );
|
||||
|
|
|
@ -60,6 +60,12 @@ public abstract class TootStatusLike extends TootId {
|
|||
//Application from which the status was posted
|
||||
@Nullable public TootApplication application;
|
||||
|
||||
@Nullable public CustomEmojiMap emojis;
|
||||
|
||||
|
||||
/////////////////////////
|
||||
// 以下はアプリ内部で使用する
|
||||
|
||||
public long time_created_at;
|
||||
|
||||
public JSONObject json;
|
||||
|
@ -81,7 +87,7 @@ public abstract class TootStatusLike extends TootId {
|
|||
private static final Pattern reWhitespace = Pattern.compile( "[\\s\\t\\x0d\\x0a]+" );
|
||||
|
||||
|
||||
public void setSpoilerText( Context context, String sv){
|
||||
public void setSpoilerText( Context context, String sv ){
|
||||
if( TextUtils.isEmpty( sv ) ){
|
||||
this.spoiler_text = null;
|
||||
this.decoded_spoiler_text = null;
|
||||
|
@ -90,7 +96,7 @@ public abstract class TootStatusLike extends TootId {
|
|||
// remove white spaces
|
||||
sv = reWhitespace.matcher( this.spoiler_text ).replaceAll( " " );
|
||||
// decode emoji code
|
||||
this.decoded_spoiler_text = Emojione.decodeEmoji( context, sv );
|
||||
this.decoded_spoiler_text = Emojione.decodeEmoji( context, sv ,emojis);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import java.util.regex.Pattern;
|
|||
|
||||
import jp.juggler.subwaytooter.api.entity.TootAccount;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions;
|
||||
import jp.juggler.subwaytooter.util.HTMLDecoder;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
|
@ -37,7 +38,10 @@ public class MSPAccount extends TootAccount {
|
|||
dst.id = src.optLong( "id" );
|
||||
|
||||
dst.note = Utils.optStringX( src, "note" );
|
||||
dst.decoded_note = HTMLDecoder.decodeHTML( context, access_info, ( dst.note != null ? dst.note : null ), true, true, null ,null);
|
||||
dst.decoded_note = new DecodeOptions()
|
||||
.setShort( true )
|
||||
.setDecodeEmoji( true )
|
||||
.decodeHTML( context, access_info, dst.note != null ? dst.note : null );
|
||||
|
||||
if( TextUtils.isEmpty( dst.url ) ){
|
||||
log.e( "parseAccount: missing url" );
|
||||
|
|
|
@ -18,6 +18,7 @@ import java.util.regex.Pattern;
|
|||
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatusLike;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions;
|
||||
import jp.juggler.subwaytooter.util.HTMLDecoder;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
|
@ -80,7 +81,12 @@ public class MSPToot extends TootStatusLike {
|
|||
dst.setSpoilerText( context, Utils.optStringX( src, "spoiler_text" ) );
|
||||
|
||||
dst.content = Utils.optStringX( src, "content" );
|
||||
dst.decoded_content = HTMLDecoder.decodeHTML( context, access_info, dst.content, true, true, null ,dst);
|
||||
dst.decoded_content = new DecodeOptions()
|
||||
.setShort( true )
|
||||
.setDecodeEmoji( true )
|
||||
.setEmojiMap( dst.emojis )
|
||||
.setLinkTag( dst )
|
||||
.decodeHTML( context, access_info, dst.content);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,264 @@
|
|||
package jp.juggler.subwaytooter.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import jp.juggler.subwaytooter.App1;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Response;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class CustomEmojiCache {
|
||||
|
||||
private static final LogCategory log = new LogCategory( "CustomEmojiCache" );
|
||||
|
||||
static final int PIXEL_MAX = 64;
|
||||
static final int CACHE_MAX = 512; // 使用中のビットマップは掃除しないので、頻度によってはこれより多くなることもある
|
||||
static final long ERROR_EXPIRE = ( 60000L * 10 );
|
||||
|
||||
|
||||
private static long getNow(){
|
||||
return SystemClock.elapsedRealtime();
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// エラーキャッシュ
|
||||
|
||||
final ConcurrentHashMap< String, Long > cache_error = new ConcurrentHashMap<>();
|
||||
|
||||
////////////////////////////////
|
||||
// 成功キャッシュ
|
||||
|
||||
static class CacheItem {
|
||||
|
||||
@NonNull String url;
|
||||
|
||||
@NonNull Bitmap bitmap;
|
||||
|
||||
// 参照された時刻
|
||||
long time_used;
|
||||
|
||||
CacheItem( @NonNull String url, @NonNull Bitmap bitmap ){
|
||||
this.url = url;
|
||||
this.bitmap = bitmap;
|
||||
time_used = getNow();
|
||||
}
|
||||
}
|
||||
|
||||
final ConcurrentHashMap< String, CacheItem > cache = new ConcurrentHashMap<>();
|
||||
|
||||
////////////////////////////////
|
||||
// リクエスト
|
||||
|
||||
public interface Callback {
|
||||
void onComplete( Bitmap b );
|
||||
}
|
||||
|
||||
static class Request {
|
||||
@NonNull String url;
|
||||
@NonNull Callback callback;
|
||||
|
||||
public Request( @NonNull String url, @NonNull Callback callback ){
|
||||
this.url = url;
|
||||
this.callback = callback;
|
||||
}
|
||||
}
|
||||
|
||||
final ConcurrentLinkedQueue< Request > queue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
////////////////////////////////
|
||||
|
||||
@Nullable public Bitmap get( @NonNull String url, @NonNull Callback callback ){
|
||||
synchronized( cache ){
|
||||
long now = getNow();
|
||||
|
||||
// 成功キャッシュ
|
||||
CacheItem item = cache.get( url );
|
||||
if( item != null && ! item.bitmap.isRecycled() ){
|
||||
item.time_used = now;
|
||||
return item.bitmap;
|
||||
}
|
||||
|
||||
// エラーキャッシュ
|
||||
Long time_error = cache_error.get( url );
|
||||
if( time_error != null && now < time_error + ERROR_EXPIRE ){
|
||||
return null;
|
||||
}
|
||||
}
|
||||
queue.add( new Request( url, callback ) );
|
||||
worker.notifyEx();
|
||||
return null;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
|
||||
Handler handler;
|
||||
|
||||
public CustomEmojiCache( Context context ){
|
||||
this.handler = new Handler( context.getMainLooper() );
|
||||
this.worker = new Worker();
|
||||
worker.start();
|
||||
}
|
||||
|
||||
Worker worker;
|
||||
|
||||
class Worker extends WorkerBase {
|
||||
final AtomicBoolean bCancelled = new AtomicBoolean( false );
|
||||
|
||||
@Override public void cancel(){
|
||||
}
|
||||
|
||||
@Override public void run(){
|
||||
while( ! bCancelled.get() ){
|
||||
Request request = queue.poll();
|
||||
if( request == null ){
|
||||
waitEx(86400000L);
|
||||
continue;
|
||||
}
|
||||
|
||||
long now = getNow();
|
||||
synchronized( cache ){
|
||||
|
||||
// 成功キャッシュ
|
||||
CacheItem item = cache.get( request.url );
|
||||
if( item != null && ! item.bitmap.isRecycled() ){
|
||||
fireCallback( request.callback, item.bitmap );
|
||||
continue;
|
||||
}
|
||||
|
||||
// エラーキャッシュ
|
||||
Long time_error = cache_error.get( request.url );
|
||||
if( time_error != null && now < time_error + ERROR_EXPIRE ){
|
||||
continue;
|
||||
}
|
||||
|
||||
sweep_cache();
|
||||
}
|
||||
|
||||
Bitmap b = null;
|
||||
try{
|
||||
byte[] data = getHttp( request.url );
|
||||
if( data != null ){
|
||||
b = decode( data, request.url );
|
||||
}
|
||||
}catch( Throwable ex ){
|
||||
log.trace( ex );
|
||||
}
|
||||
|
||||
synchronized( cache ){
|
||||
if( b != null ){
|
||||
CacheItem item = cache.get( request.url );
|
||||
if( item == null ){
|
||||
item = new CacheItem( request.url, b );
|
||||
cache.put( request.url, item );
|
||||
}else{
|
||||
item.bitmap = b;
|
||||
}
|
||||
fireCallback( request.callback, b );
|
||||
}else{
|
||||
cache_error.put( request.url, getNow() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fireCallback( final Callback callback, final Bitmap bitmap ){
|
||||
handler.post( new Runnable() {
|
||||
@Override public void run(){
|
||||
callback.onComplete( bitmap );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
private byte[] getHttp( String url ){
|
||||
Response response;
|
||||
try{
|
||||
okhttp3.Request.Builder request_builder = new okhttp3.Request.Builder();
|
||||
request_builder.url( url );
|
||||
Call call = App1.ok_http_client.newCall( request_builder.build() );
|
||||
response = call.execute();
|
||||
}catch( Throwable ex ){
|
||||
log.e( ex, "getHttp network error." );
|
||||
return null;
|
||||
}
|
||||
|
||||
if( ! response.isSuccessful() ){
|
||||
log.e( "getHttp response error. %s", response );
|
||||
return null;
|
||||
}
|
||||
|
||||
try{
|
||||
//noinspection ConstantConditions
|
||||
return response.body().bytes();
|
||||
}catch( Throwable ex ){
|
||||
log.e( ex, "getHttp content error." );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void sweep_cache(){
|
||||
|
||||
// キャッシュの掃除
|
||||
if( cache.size() >= CACHE_MAX ){
|
||||
ArrayList< CacheItem > list = new ArrayList<>();
|
||||
list.addAll( cache.values() );
|
||||
|
||||
// 降順ソート
|
||||
Collections.sort( list, new Comparator< CacheItem >() {
|
||||
@Override
|
||||
public int compare( CacheItem a, CacheItem b ){
|
||||
long delta = b.time_used - a.time_used;
|
||||
return delta < 0L ? - 1 : delta > 0L ? 1 : 0;
|
||||
}
|
||||
} );
|
||||
// 古い物から順にチェック
|
||||
long now = getNow();
|
||||
for( int i = list.size()-1; i>= CACHE_MAX-1; --i){
|
||||
CacheItem item = list.get( i );
|
||||
// あまり古くないなら無理に掃除しない
|
||||
if( now - item.time_used < 1000L ) break;
|
||||
cache.remove( item.url );
|
||||
item.bitmap.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
|
||||
private Bitmap decode( byte[] data, String url ){
|
||||
options.inJustDecodeBounds = true;
|
||||
options.inScaled = false;
|
||||
options.outWidth = 0;
|
||||
options.outHeight = 0;
|
||||
BitmapFactory.decodeByteArray( data, 0, data.length, options );
|
||||
int w = options.outWidth;
|
||||
int h = options.outHeight;
|
||||
if( w <= 0 || h <= 0 ){
|
||||
log.e( "can't decode bounds. %s", url );
|
||||
return null;
|
||||
}
|
||||
int bits = 0;
|
||||
while( w > PIXEL_MAX || h > PIXEL_MAX ){
|
||||
++ bits;
|
||||
w >>= 1;
|
||||
h >>= 1;
|
||||
}
|
||||
options.inJustDecodeBounds = false;
|
||||
options.inSampleSize = 1 << bits;
|
||||
return BitmapFactory.decodeByteArray( data, 0, data.length, options );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package jp.juggler.subwaytooter.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
||||
import jp.juggler.subwaytooter.ActAccountSetting;
|
||||
import jp.juggler.subwaytooter.api.entity.CustomEmojiMap;
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class DecodeOptions {
|
||||
|
||||
boolean bShort = false;
|
||||
public DecodeOptions setShort(boolean b){
|
||||
bShort = b;
|
||||
return this;
|
||||
}
|
||||
|
||||
boolean bDecodeEmoji;
|
||||
public DecodeOptions setDecodeEmoji(boolean b){
|
||||
bDecodeEmoji = b;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nullable TootAttachment.List list_attachment;
|
||||
public DecodeOptions setAttachment(TootAttachment.List list_attachment){
|
||||
this.list_attachment = list_attachment;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Nullable Object link_tag;
|
||||
public DecodeOptions setLinkTag(Object link_tag){
|
||||
this.link_tag = link_tag;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nullable CustomEmojiMap customEmojiMap;
|
||||
public DecodeOptions setEmojiMap(CustomEmojiMap customEmojiMap){
|
||||
this.customEmojiMap = customEmojiMap;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SpannableStringBuilder decodeHTML( Context context, LinkClickContext lcc, String html ){
|
||||
return HTMLDecoder.decodeHTML( context,lcc,html ,this);
|
||||
}
|
||||
}
|
|
@ -1,9 +1,12 @@
|
|||
package jp.juggler.subwaytooter.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
@ -12,19 +15,24 @@ import java.util.regex.Pattern;
|
|||
|
||||
import jp.juggler.subwaytooter.App1;
|
||||
import jp.juggler.subwaytooter.R;
|
||||
import uk.co.chrisjenx.calligraphy.CalligraphyTypefaceSpan;
|
||||
import jp.juggler.subwaytooter.api.entity.CustomEmojiMap;
|
||||
|
||||
public abstract class Emojione
|
||||
{
|
||||
private static final Pattern SHORTNAME_PATTERN = Pattern.compile(":([-+\\w]+):");
|
||||
public abstract class Emojione {
|
||||
private static final Pattern SHORTNAME_PATTERN = Pattern.compile( ":([-+\\w]+):" );
|
||||
|
||||
public static final HashMap<String,String> map_name2unicode = EmojiMap._shortNameToUnicode;
|
||||
private static final HashSet<String> set_unicode = EmojiMap._unicode_set;
|
||||
|
||||
private static class DecodeEnv{
|
||||
public static final HashMap< String, String > map_name2unicode = EmojiMap._shortNameToUnicode;
|
||||
private static final HashSet< String > set_unicode = EmojiMap._unicode_set;
|
||||
|
||||
private static class DecodeEnv {
|
||||
SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
int last_span_start = -1;
|
||||
int last_span_end = -1;
|
||||
int last_span_start = - 1;
|
||||
int last_span_end = - 1;
|
||||
|
||||
@Nullable CustomEmojiMap custom_map;
|
||||
|
||||
DecodeEnv( @Nullable CustomEmojiMap custom_map ){
|
||||
this.custom_map = custom_map;
|
||||
}
|
||||
|
||||
void closeSpan(){
|
||||
if( last_span_start >= 0 ){
|
||||
|
@ -32,25 +40,25 @@ public abstract class Emojione
|
|||
EmojiSpan typefaceSpan = new EmojiSpan( App1.typeface_emoji );
|
||||
sb.setSpan( typefaceSpan, last_span_start, last_span_end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE );
|
||||
}
|
||||
last_span_start = -1;
|
||||
last_span_start = - 1;
|
||||
}
|
||||
}
|
||||
|
||||
void addEmoji(String s){
|
||||
|
||||
void addEmoji( String s ){
|
||||
if( last_span_start < 0 ){
|
||||
last_span_start = sb.length();
|
||||
}
|
||||
sb.append(s);
|
||||
sb.append( s );
|
||||
last_span_end = sb.length();
|
||||
}
|
||||
|
||||
void addUnicodeString(String s){
|
||||
void addUnicodeString( String s ){
|
||||
int i = 0;
|
||||
int end = s.length();
|
||||
while( i < end ){
|
||||
int remain = end - i;
|
||||
String emoji = null;
|
||||
for(int j = EmojiMap.max_length; j>0;--j ){
|
||||
for( int j = EmojiMap.max_length ; j > 0 ; -- j ){
|
||||
if( j > remain ) continue;
|
||||
String check = s.substring( i, i + j );
|
||||
if( ! set_unicode.contains( check ) ) continue;
|
||||
|
@ -63,48 +71,60 @@ public abstract class Emojione
|
|||
continue;
|
||||
}
|
||||
closeSpan();
|
||||
int length = Character.charCount( s.codePointAt( i ) );
|
||||
if( length == 1){
|
||||
int length = Character.charCount( s.codePointAt( i ) );
|
||||
if( length == 1 ){
|
||||
sb.append( s.charAt( i ) );
|
||||
++ i;
|
||||
}else{
|
||||
sb.append( s.substring( i,i+length ));
|
||||
i+= length;
|
||||
sb.append( s.substring( i, i + length ) );
|
||||
i += length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addImageSpan( String text,Context context, int res_id){
|
||||
void addImageSpan( String text, Context context, int res_id ){
|
||||
closeSpan();
|
||||
int start = sb.length();
|
||||
sb.append(text);
|
||||
sb.append( text );
|
||||
int end = sb.length();
|
||||
sb.setSpan( new EmojiImageSpan(context,res_id ), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE );
|
||||
sb.setSpan( new EmojiImageSpan( context, res_id ), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE );
|
||||
}
|
||||
|
||||
void addNetworkEmojiSpan( String text, @NonNull String url ){
|
||||
closeSpan();
|
||||
int start = sb.length();
|
||||
sb.append( text );
|
||||
int end = sb.length();
|
||||
sb.setSpan( new NetworkEmojiSpan( url ), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE );
|
||||
}
|
||||
}
|
||||
|
||||
private static final Pattern reNicoru = Pattern.compile( "\\Anicoru\\d*\\z",Pattern.CASE_INSENSITIVE );
|
||||
private static final Pattern reHohoemi = Pattern.compile( "\\Ahohoemi\\d*\\z",Pattern.CASE_INSENSITIVE );
|
||||
private static final Pattern reNicoru = Pattern.compile( "\\Anicoru\\d*\\z", Pattern.CASE_INSENSITIVE );
|
||||
private static final Pattern reHohoemi = Pattern.compile( "\\Ahohoemi\\d*\\z", Pattern.CASE_INSENSITIVE );
|
||||
|
||||
public static Spannable decodeEmoji( Context context, String s ){
|
||||
|
||||
DecodeEnv decode_env = new DecodeEnv();
|
||||
Matcher matcher = SHORTNAME_PATTERN.matcher(s);
|
||||
public static Spannable decodeEmoji( Context context, String s, @Nullable CustomEmojiMap custom_map ){
|
||||
|
||||
DecodeEnv decode_env = new DecodeEnv( custom_map );
|
||||
Matcher matcher = SHORTNAME_PATTERN.matcher( s );
|
||||
int last_end = 0;
|
||||
while( matcher.find() ){
|
||||
int start = matcher.start();
|
||||
int end = matcher.end();
|
||||
if( start > last_end ){
|
||||
decode_env.addUnicodeString(s.substring( last_end,start ));
|
||||
decode_env.addUnicodeString( s.substring( last_end, start ) );
|
||||
}
|
||||
last_end = end;
|
||||
//
|
||||
String unicode = map_name2unicode.get(matcher.group(1));
|
||||
String unicode = map_name2unicode.get( matcher.group( 1 ) );
|
||||
if( unicode == null ){
|
||||
if( reHohoemi.matcher( matcher.group(1) ).find() ){
|
||||
String url = ( custom_map == null ? null : custom_map.get( matcher.group( 1 ) ) );
|
||||
if( ! TextUtils.isEmpty( url ) ){
|
||||
decode_env.addNetworkEmojiSpan( s.substring( start, end ), url );
|
||||
|
||||
}else if( reHohoemi.matcher( matcher.group( 1 ) ).find() ){
|
||||
decode_env.addImageSpan( s.substring( start, end ), context, R.drawable.emoji_hohoemi );
|
||||
}else if( reNicoru.matcher( matcher.group(1) ).find() ){
|
||||
decode_env.addImageSpan( s.substring( start, end ), context, R.drawable.emoji_nicoru );
|
||||
}else if( reNicoru.matcher( matcher.group( 1 ) ).find() ){
|
||||
decode_env.addImageSpan( s.substring( start, end ), context, R.drawable.emoji_nicoru );
|
||||
}else{
|
||||
decode_env.addUnicodeString( s.substring( start, end ) );
|
||||
}
|
||||
|
@ -115,7 +135,7 @@ public abstract class Emojione
|
|||
// copy remain
|
||||
int end = s.length();
|
||||
if( end > last_end ){
|
||||
decode_env.addUnicodeString(s.substring( last_end, end ));
|
||||
decode_env.addUnicodeString( s.substring( last_end, end ) );
|
||||
}
|
||||
// close span
|
||||
decode_env.closeSpan();
|
||||
|
|
|
@ -2,6 +2,7 @@ package jp.juggler.subwaytooter.util;
|
|||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
@ -15,6 +16,7 @@ import java.util.regex.Pattern;
|
|||
|
||||
import jp.juggler.subwaytooter.App1;
|
||||
import jp.juggler.subwaytooter.Pref;
|
||||
import jp.juggler.subwaytooter.api.entity.CustomEmojiMap;
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment;
|
||||
import jp.juggler.subwaytooter.api.entity.TootMention;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
|
@ -160,14 +162,11 @@ public class HTMLDecoder {
|
|||
Context context
|
||||
, LinkClickContext account
|
||||
, SpannableStringBuilder sb
|
||||
, boolean bShort
|
||||
, boolean bDecodeEmoji
|
||||
, @Nullable TootAttachment.List list_attachment
|
||||
, @Nullable Object link_tag
|
||||
){
|
||||
, @NonNull DecodeOptions options
|
||||
){
|
||||
if( TAG_TEXT.equals( tag ) ){
|
||||
if( bDecodeEmoji ){
|
||||
sb.append( Emojione.decodeEmoji( context, decodeEntity( text ) ) );
|
||||
if( options.bDecodeEmoji ){
|
||||
sb.append( Emojione.decodeEmoji( context, decodeEntity( text ) ,options.customEmojiMap ) );
|
||||
}else{
|
||||
sb.append( decodeEntity( text ) );
|
||||
}
|
||||
|
@ -188,7 +187,7 @@ public class HTMLDecoder {
|
|||
sb_tmp.append( "<img/>" );
|
||||
}else{
|
||||
for( Node child : child_nodes ){
|
||||
child.encodeSpan( context, account, sb_tmp, bShort, bDecodeEmoji, list_attachment ,link_tag);
|
||||
child.encodeSpan( context, account, sb_tmp, options);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,7 +195,7 @@ public class HTMLDecoder {
|
|||
|
||||
if( "a".equals( tag ) ){
|
||||
start = sb.length();
|
||||
sb.append( encodeUrl( bShort, context, sb_tmp.toString(), getHref(), list_attachment ) );
|
||||
sb.append( encodeUrl( options.bShort, context, sb_tmp.toString(), getHref(), options.list_attachment ) );
|
||||
end = sb.length();
|
||||
}else if( sb_tmp != sb ){
|
||||
// style もscript も読み捨てる
|
||||
|
@ -206,7 +205,7 @@ public class HTMLDecoder {
|
|||
String href = getHref();
|
||||
if( href != null ){
|
||||
String link_text = sb.subSequence( start,end ).toString();
|
||||
MyClickableSpan span = new MyClickableSpan( account, link_text, href, account.findAcctColor( href ),link_tag );
|
||||
MyClickableSpan span = new MyClickableSpan( account, link_text, href, account.findAcctColor( href ),options.link_tag );
|
||||
sb.setSpan( span, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );
|
||||
}
|
||||
}
|
||||
|
@ -281,7 +280,7 @@ public class HTMLDecoder {
|
|||
}
|
||||
|
||||
if( is_media_attachment( list_attachment, href ) ){
|
||||
return Emojione.decodeEmoji( context, ":frame_photo:" );
|
||||
return Emojione.decodeEmoji( context, ":frame_photo:",null );
|
||||
}
|
||||
|
||||
try{
|
||||
|
@ -310,14 +309,12 @@ public class HTMLDecoder {
|
|||
return Character.isWhitespace( c ) || c == 0x0a || c == 0x0d;
|
||||
}
|
||||
|
||||
|
||||
public static SpannableStringBuilder decodeHTML(
|
||||
Context context
|
||||
, LinkClickContext account
|
||||
, String src
|
||||
, boolean bShort
|
||||
, boolean bDecodeEmoji
|
||||
, @Nullable TootAttachment.List list_attachment
|
||||
, @Nullable Object link_tag
|
||||
,@NonNull DecodeOptions options
|
||||
){
|
||||
prepareTagInformation();
|
||||
SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
|
@ -329,7 +326,7 @@ public class HTMLDecoder {
|
|||
rootNode.addChild( tracker, "" );
|
||||
}
|
||||
|
||||
rootNode.encodeSpan( context, account, sb, bShort, bDecodeEmoji, list_attachment , link_tag );
|
||||
rootNode.encodeSpan( context, account, sb,options);
|
||||
int end = sb.length();
|
||||
while( end > 0 && isWhitespace( sb.charAt( end - 1 ) ) ) -- end;
|
||||
if( end < sb.length() ){
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
package jp.juggler.subwaytooter.util;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.support.annotation.IntRange;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.style.ReplacementSpan;
|
||||
|
||||
import jp.juggler.subwaytooter.App1;
|
||||
|
||||
public class NetworkEmojiSpan extends ReplacementSpan {
|
||||
|
||||
private static final float scale_ratio = 1.14f;
|
||||
private static final float descent_ratio = 0.211f;
|
||||
|
||||
@NonNull private final String url;
|
||||
@NonNull private final Paint mPaint = new Paint();
|
||||
@NonNull private final Rect rect_src = new Rect();
|
||||
@NonNull private final RectF rect_dst = new RectF();
|
||||
|
||||
NetworkEmojiSpan( @NonNull String url ){
|
||||
super();
|
||||
this.url = url;
|
||||
mPaint.setFilterBitmap( true );
|
||||
}
|
||||
|
||||
private CustomEmojiCache.Callback load_callback;
|
||||
public void setLoadCompleteCallback(CustomEmojiCache.Callback load_callback){
|
||||
this.load_callback = load_callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize(
|
||||
@NonNull Paint paint
|
||||
, CharSequence text
|
||||
, @IntRange(from = 0) int start
|
||||
, @IntRange(from = 0) int end
|
||||
, @Nullable Paint.FontMetricsInt fm
|
||||
){
|
||||
int size = (int) ( 0.5f + scale_ratio * paint.getTextSize() );
|
||||
|
||||
if( fm != null ){
|
||||
int c_descent = (int) ( 0.5f + size * descent_ratio );
|
||||
int c_ascent = c_descent - size;
|
||||
if( fm.ascent > c_ascent ) fm.ascent = c_ascent;
|
||||
if( fm.top > c_ascent ) fm.top = c_ascent;
|
||||
if( fm.descent < c_descent ) fm.descent = c_descent;
|
||||
if( fm.bottom < c_descent ) fm.bottom = c_descent;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override public void draw(
|
||||
@NonNull Canvas canvas
|
||||
, CharSequence text, int start, int end
|
||||
, float x, int top, int baseline, int bottom
|
||||
, @NonNull Paint textPaint
|
||||
){
|
||||
if(load_callback == null ) return;
|
||||
|
||||
int size = (int) ( 0.5f + scale_ratio * textPaint.getTextSize() );
|
||||
int c_descent = (int) ( 0.5f + size * descent_ratio );
|
||||
|
||||
Bitmap b = App1.custom_emoji_cache.get( url ,load_callback);
|
||||
if( b != null && ! b.isRecycled() ){
|
||||
rect_src.set(0,0,b.getWidth(),b.getHeight() );
|
||||
rect_dst.set(0,0,size,size);
|
||||
|
||||
int transY = baseline - size + c_descent;
|
||||
|
||||
canvas.save();
|
||||
canvas.translate( x, transY );
|
||||
canvas.drawBitmap( b, rect_src,rect_dst,mPaint );
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue