カスタム絵文字4k個のタンスで絵文字ピッカーの挙動を最適化した
This commit is contained in:
parent
2d4b107879
commit
33584b5689
|
@ -16,6 +16,7 @@
|
|||
<w>gifv</w>
|
||||
<w>hashtag</w>
|
||||
<w>hashtags</w>
|
||||
<w>hohoemi</w>
|
||||
<w>idempotency</w>
|
||||
<w>kenglxn</w>
|
||||
<w>mailto</w>
|
||||
|
|
|
@ -443,16 +443,22 @@ for(@fix_name){
|
|||
updateCodeMap();
|
||||
updateNameMap();
|
||||
|
||||
my %name_chars;
|
||||
my $bad_name = 0;
|
||||
for my $name (sort keys %name_map){
|
||||
for( split //,$name ){
|
||||
$name_chars{$_}=1;
|
||||
}
|
||||
|
||||
my $rh = $name_map{$name};
|
||||
my @res_list = values %$rh;
|
||||
|
||||
next if @res_list == 1;
|
||||
warn "name $name has multiple resource. ",join(',',map{ $_->{res_name} } @res_list),"\n";
|
||||
$bad_name = 1;
|
||||
}
|
||||
$bad_name and die "please fix name=>resource duplicate.\n";
|
||||
|
||||
warn "name_chars: [",join('',sort keys %name_chars),"]\n";
|
||||
|
||||
sub decodeUnified($){
|
||||
my($chars) = @_;
|
||||
|
@ -552,6 +558,8 @@ my $utf8 = Encode::find_encoding("utf8");
|
|||
my $utf16 = Encode::find_encoding("UTF-16BE");
|
||||
my $utf16_max_length = 0;
|
||||
|
||||
|
||||
|
||||
# 画像リソースIDとUnidoceシーケンスの関連付けを出力する
|
||||
for my $res_name ( sort keys %res_map ){
|
||||
my $res_info = $res_map{$res_name};
|
||||
|
@ -576,12 +584,29 @@ for my $res_name ( sort keys %res_map ){
|
|||
}
|
||||
}
|
||||
|
||||
#for my $res_name ( sort keys %res_map ){
|
||||
# my $res_info = $res_map{$res_name};
|
||||
# for my $short_name ( sort keys %{$res_info->{shortname_map}} ){
|
||||
# addCode( qq{name( R.drawable.$res_name, "$short_name" );});
|
||||
# }
|
||||
#}
|
||||
|
||||
# 画像リソースIDとshortcodeの関連付けを出力する
|
||||
for my $res_name ( sort keys %res_map ){
|
||||
my $res_info = $res_map{$res_name};
|
||||
for my $short_name ( sort keys %{$res_info->{shortname_map}} ){
|
||||
addCode( qq{name( R.drawable.$res_name, "$short_name" );});
|
||||
}
|
||||
# 投稿時にshortcodeをユニコードに変換するため、shortcodeとUTF-16シーケンスの関連付けを出力する
|
||||
for my $name (sort keys %name_map){
|
||||
my $rh = $name_map{$name};
|
||||
my @res_list = values %$rh;
|
||||
my $res_info = $res_list[0];
|
||||
|
||||
my $chars = parseCodePoint( $res_info->{unified} );
|
||||
# コードポイントのリストからperl内部表現の文字列にする
|
||||
my $str = join '',map{ chr hex $_ } @$chars;
|
||||
# perl内部表現からUTF-16に変換する
|
||||
my $str_utf16 = $utf16->encode( $str );
|
||||
my @utf16_chars = unpack("n*",$str_utf16);
|
||||
# UTF-16の文字のリストをJavaのエスケープ表現に直す
|
||||
my $java_chars = join('',map{ sprintf qq(\\u%04x),$_} @utf16_chars );
|
||||
addCode( qq{name( "$name", R.drawable.$res_info->{res_name}, "$java_chars" );});
|
||||
}
|
||||
|
||||
# カテゴリを書きだす
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -866,7 +866,7 @@ public class ActAccountSetting extends AppCompatActivity
|
|||
return;
|
||||
}
|
||||
}
|
||||
updateCredential( "display_name=" + Uri.encode(sv ) );
|
||||
updateCredential( "display_name=" + Uri.encode(EmojiDecoder.decodeShortCode(sv) ) );
|
||||
}
|
||||
|
||||
private void sendNote(boolean bConfirmed){
|
||||
|
@ -890,7 +890,7 @@ public class ActAccountSetting extends AppCompatActivity
|
|||
return;
|
||||
}
|
||||
}
|
||||
updateCredential( "note=" + Uri.encode(sv ) );
|
||||
updateCredential( "note=" + Uri.encode(EmojiDecoder.decodeShortCode(sv) ) );
|
||||
}
|
||||
|
||||
private static final int PERMISSION_REQUEST_AVATAR = 1;
|
||||
|
|
|
@ -7,8 +7,9 @@ import android.content.Context;
|
|||
import android.content.SharedPreferences;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.GlideBuilder;
|
||||
|
@ -43,11 +44,13 @@ import jp.juggler.subwaytooter.util.CustomEmojiCache;
|
|||
import jp.juggler.subwaytooter.util.CustomEmojiLister;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import okhttp3.Cache;
|
||||
import okhttp3.CacheControl;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.CipherSuite;
|
||||
import okhttp3.ConnectionSpec;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Response;
|
||||
import uk.co.chrisjenx.calligraphy.CalligraphyConfig;
|
||||
import uk.co.chrisjenx.calligraphy.TypefaceUtils;
|
||||
|
||||
public class App1 extends Application {
|
||||
|
||||
|
@ -215,10 +218,10 @@ public class App1 extends Application {
|
|||
|
||||
public static OkHttpClient ok_http_client;
|
||||
|
||||
public static OkHttpClient ok_http_client2;
|
||||
private static OkHttpClient ok_http_client2;
|
||||
|
||||
public static final boolean USE_OLD_EMOJIONE = false;
|
||||
public static Typeface typeface_emoji;
|
||||
// public static final boolean USE_OLD_EMOJIONE = false;
|
||||
// public static Typeface typeface_emoji;
|
||||
|
||||
public static SharedPreferences pref;
|
||||
|
||||
|
@ -292,11 +295,11 @@ public class App1 extends Application {
|
|||
AcctSet.deleteOld( System.currentTimeMillis() );
|
||||
}
|
||||
|
||||
if( USE_OLD_EMOJIONE ){
|
||||
if( typeface_emoji == null ){
|
||||
typeface_emoji = TypefaceUtils.load( app_context.getAssets(), "emojione_android.ttf" );
|
||||
}
|
||||
}
|
||||
// if( USE_OLD_EMOJIONE ){
|
||||
// if( typeface_emoji == null ){
|
||||
// typeface_emoji = TypefaceUtils.load( app_context.getAssets(), "emojione_android.ttf" );
|
||||
// }
|
||||
// }
|
||||
|
||||
// if( image_loader == null ){
|
||||
// image_loader = new MyImageLoader(
|
||||
|
@ -406,4 +409,74 @@ public class App1 extends Application {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
static final CacheControl CACHE_5MIN = new CacheControl.Builder()
|
||||
.maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS) // キャッシュをいつまで保持するか
|
||||
//s .minFresh( 1, TimeUnit.HOURS ) // キャッシュが新鮮であると考えられる時間
|
||||
.maxAge( 1, TimeUnit.HOURS ) // キャッシュが新鮮であると考えられる時間
|
||||
.build();
|
||||
|
||||
@Nullable public static byte[] getHttpCached( @NonNull String url ){
|
||||
Response response;
|
||||
long t_start = SystemClock.elapsedRealtime();
|
||||
try{
|
||||
okhttp3.Request.Builder request_builder = new okhttp3.Request.Builder();
|
||||
request_builder.url( url );
|
||||
request_builder.cacheControl( CACHE_5MIN );
|
||||
|
||||
Call call = App1.ok_http_client2.newCall( request_builder.build() );
|
||||
response = call.execute();
|
||||
}catch( Throwable ex ){
|
||||
log.e( ex, "getHttp network error." );
|
||||
return null;
|
||||
}finally{
|
||||
long t_delta = SystemClock.elapsedRealtime() -t_start;
|
||||
log.d("getHttp: time=%dms",t_delta);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable public static String getHttpCachedString( @NonNull String url ){
|
||||
Response response;
|
||||
long t_start = SystemClock.elapsedRealtime();
|
||||
try{
|
||||
okhttp3.Request.Builder request_builder = new okhttp3.Request.Builder();
|
||||
request_builder.url( url );
|
||||
request_builder.cacheControl( CACHE_5MIN );
|
||||
|
||||
Call call = App1.ok_http_client2.newCall( request_builder.build() );
|
||||
response = call.execute();
|
||||
}catch( Throwable ex ){
|
||||
log.e( ex, "getHttp network error." );
|
||||
return null;
|
||||
}finally{
|
||||
long t_delta = SystemClock.elapsedRealtime() -t_start;
|
||||
log.d("getHttp: time=%dms",t_delta);
|
||||
}
|
||||
|
||||
if( ! response.isSuccessful() ){
|
||||
log.e( "getHttp response error. %s", response );
|
||||
return null;
|
||||
}
|
||||
|
||||
try{
|
||||
//noinspection ConstantConditions
|
||||
return response.body().string();
|
||||
}catch( Throwable ex ){
|
||||
log.e( ex, "getHttp content error." );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import jp.juggler.subwaytooter.api.entity.TootAccount;
|
|||
import jp.juggler.subwaytooter.api.entity.TootStatus;
|
||||
import jp.juggler.subwaytooter.table.UserRelation;
|
||||
import jp.juggler.subwaytooter.util.EmojiDecoder;
|
||||
import jp.juggler.subwaytooter.util.EmojiMap201709;
|
||||
import jp.juggler.subwaytooter.view.MyLinkMovementMethod;
|
||||
import jp.juggler.subwaytooter.view.MyNetworkImageView;
|
||||
|
||||
|
@ -37,20 +38,20 @@ class HeaderViewHolderProfile extends HeaderViewHolderBase implements View.OnCli
|
|||
, arg_activity.getLayoutInflater().inflate( R.layout.lv_header_account, parent, false )
|
||||
);
|
||||
|
||||
ivBackground = (MyNetworkImageView) viewRoot.findViewById( R.id.ivBackground );
|
||||
ivBackground = viewRoot.findViewById( R.id.ivBackground );
|
||||
llProfile = viewRoot.findViewById( R.id.llProfile );
|
||||
tvCreated = (TextView) viewRoot.findViewById( R.id.tvCreated );
|
||||
ivAvatar = (MyNetworkImageView) viewRoot.findViewById( R.id.ivAvatar );
|
||||
tvDisplayName = (TextView) viewRoot.findViewById( R.id.tvDisplayName );
|
||||
tvAcct = (TextView) viewRoot.findViewById( R.id.tvAcct );
|
||||
btnFollowing = (Button) viewRoot.findViewById( R.id.btnFollowing );
|
||||
btnFollowers = (Button) viewRoot.findViewById( R.id.btnFollowers );
|
||||
btnStatusCount = (Button) viewRoot.findViewById( R.id.btnStatusCount );
|
||||
tvNote = (TextView) viewRoot.findViewById( R.id.tvNote );
|
||||
tvCreated = viewRoot.findViewById( R.id.tvCreated );
|
||||
ivAvatar = viewRoot.findViewById( R.id.ivAvatar );
|
||||
tvDisplayName = viewRoot.findViewById( R.id.tvDisplayName );
|
||||
tvAcct = viewRoot.findViewById( R.id.tvAcct );
|
||||
btnFollowing = viewRoot.findViewById( R.id.btnFollowing );
|
||||
btnFollowers = viewRoot.findViewById( R.id.btnFollowers );
|
||||
btnStatusCount = viewRoot.findViewById( R.id.btnStatusCount );
|
||||
tvNote = viewRoot.findViewById( R.id.tvNote );
|
||||
View btnMore = viewRoot.findViewById( R.id.btnMore );
|
||||
btnFollow = (ImageButton) viewRoot.findViewById( R.id.btnFollow );
|
||||
ivFollowedBy = (ImageView) viewRoot.findViewById( R.id.ivFollowedBy );
|
||||
tvRemoteProfileWarning = (TextView) viewRoot.findViewById( R.id.tvRemoteProfileWarning );
|
||||
btnFollow = viewRoot.findViewById( R.id.btnFollow );
|
||||
ivFollowedBy = viewRoot.findViewById( R.id.ivFollowedBy );
|
||||
tvRemoteProfileWarning = viewRoot.findViewById( R.id.tvRemoteProfileWarning );
|
||||
|
||||
ivBackground.setOnClickListener( this );
|
||||
btnFollowing.setOnClickListener( this );
|
||||
|
@ -103,7 +104,7 @@ class HeaderViewHolderProfile extends HeaderViewHolderBase implements View.OnCli
|
|||
|
||||
String s = "@" + access_info.getFullAcct( who );
|
||||
if( who.locked ){
|
||||
s += " " + EmojiDecoder.map_name2unicode.get( "lock" );
|
||||
s += " " + EmojiMap201709.sShortNameToImageId.get("lock" ).unified;
|
||||
}
|
||||
tvAcct.setText( EmojiDecoder.decodeEmoji( activity, s ,null) );
|
||||
|
||||
|
|
|
@ -133,8 +133,8 @@ public class EmojiPicker implements View.OnClickListener, CustomEmojiLister.Call
|
|||
SkinTone tone = (SkinTone) viewRoot.findViewById( selected_tone ).getTag();
|
||||
for( String suffix : tone.suffix_list ){
|
||||
String new_name = name + suffix;
|
||||
Integer value = EmojiMap201709.sShortNameToImageId.get( new_name );
|
||||
if( value != null ) return new_name;
|
||||
EmojiMap201709.EmojiInfo info = EmojiMap201709.sShortNameToImageId.get( new_name );
|
||||
if( info != null ) return new_name;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
@ -334,9 +334,9 @@ public class EmojiPicker implements View.OnClickListener, CustomEmojiLister.Call
|
|||
view.setTag( item );
|
||||
ImageView iv = (ImageView) view;
|
||||
if( page != null ){
|
||||
Integer image_id = EmojiMap201709.sShortNameToImageId.get( item.name );
|
||||
if( image_id != null ){
|
||||
iv.setImageResource( image_id );
|
||||
EmojiMap201709.EmojiInfo info = EmojiMap201709.sShortNameToImageId.get( item.name );
|
||||
if( info != null ){
|
||||
iv.setImageResource( info.image_id );
|
||||
}
|
||||
}
|
||||
}else{
|
||||
|
@ -361,8 +361,8 @@ public class EmojiPicker implements View.OnClickListener, CustomEmojiLister.Call
|
|||
EmojiItem item = page.emoji_list.get( idx );
|
||||
if( TextUtils.isEmpty( item.instance ) ){
|
||||
String name = item.name;
|
||||
Integer image_id = EmojiMap201709.sShortNameToImageId.get( name );
|
||||
if( image_id == null ) return;
|
||||
EmojiMap201709.EmojiInfo info = EmojiMap201709.sShortNameToImageId.get( name );
|
||||
if( info == null ) return;
|
||||
if( selected_tone != 0 ){
|
||||
name = applySkinTone( name );
|
||||
}
|
||||
|
|
|
@ -459,7 +459,6 @@ import java.util.ArrayList;
|
|||
if( result != null ) result.onParseComplete();
|
||||
return result;
|
||||
}catch( Throwable ex ){
|
||||
log.trace( ex );
|
||||
handler.dispose();
|
||||
throw ex;
|
||||
}
|
||||
|
|
|
@ -9,16 +9,16 @@ import android.support.annotation.NonNull;
|
|||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
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 {
|
||||
|
@ -62,24 +62,41 @@ public class CustomEmojiCache {
|
|||
// リクエスト
|
||||
|
||||
public interface Callback {
|
||||
void onAPNGLoadComplete( APNGFrames b );
|
||||
void onAPNGLoadComplete();
|
||||
}
|
||||
|
||||
static class Request {
|
||||
@NonNull String url;
|
||||
@NonNull Callback callback;
|
||||
@NonNull final WeakReference<Object> refTarget;
|
||||
@NonNull final String url;
|
||||
@NonNull final Callback callback;
|
||||
|
||||
public Request( @NonNull String url, @NonNull Callback callback ){
|
||||
public Request( @NonNull Object target_tag, @NonNull String url, @NonNull Callback callback ){
|
||||
this.refTarget = new WeakReference<>( target_tag );
|
||||
this.url = url;
|
||||
this.callback = callback;
|
||||
}
|
||||
}
|
||||
|
||||
final ConcurrentLinkedQueue< Request > queue = new ConcurrentLinkedQueue<>();
|
||||
final LinkedList< Request > queue = new LinkedList<>();
|
||||
|
||||
////////////////////////////////
|
||||
|
||||
@Nullable public APNGFrames get( @NonNull String url, @NonNull Callback callback ){
|
||||
public void cancelRequest( @NonNull Object target_tag){
|
||||
synchronized( queue ){
|
||||
Iterator<Request> it = queue.iterator();
|
||||
while( it.hasNext() ){
|
||||
Request request = it.next();
|
||||
Object tag = request.refTarget.get();
|
||||
if( tag == null || tag == target_tag ){
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable public APNGFrames get( @NonNull Object target_tag,@NonNull String url, @NonNull Callback callback ){
|
||||
|
||||
cancelRequest( target_tag );
|
||||
|
||||
synchronized( cache ){
|
||||
long now = getNow();
|
||||
|
@ -97,7 +114,9 @@ public class CustomEmojiCache {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
queue.add( new Request( url, callback ) );
|
||||
synchronized( queue ){
|
||||
queue.addLast( new Request( target_tag, url, callback ) );
|
||||
}
|
||||
worker.notifyEx();
|
||||
return null;
|
||||
}
|
||||
|
@ -124,19 +143,27 @@ public class CustomEmojiCache {
|
|||
|
||||
@Override public void run(){
|
||||
while( ! bCancelled.get() ){
|
||||
Request request = queue.poll();
|
||||
Request request;
|
||||
synchronized( queue ){
|
||||
request = queue.isEmpty() ? null : queue.removeFirst();
|
||||
}
|
||||
|
||||
if( request == null ){
|
||||
waitEx( 86400000L );
|
||||
continue;
|
||||
}
|
||||
|
||||
if( request.refTarget.get() == null ){
|
||||
continue;
|
||||
}
|
||||
|
||||
long now = getNow();
|
||||
synchronized( cache ){
|
||||
|
||||
// 成功キャッシュ
|
||||
CacheItem item = cache.get( request.url );
|
||||
if( item != null ){
|
||||
fireCallback( request.callback, item.frames );
|
||||
fireCallback( request.callback );
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -151,7 +178,7 @@ public class CustomEmojiCache {
|
|||
|
||||
APNGFrames frames = null;
|
||||
try{
|
||||
byte[] data = getHttp( request.url );
|
||||
byte[] data = App1.getHttpCached( request.url );
|
||||
if( data != null ){
|
||||
frames = decodeAPNG( data, request.url );
|
||||
}
|
||||
|
@ -169,7 +196,7 @@ public class CustomEmojiCache {
|
|||
item.frames.dispose();
|
||||
item.frames = frames;
|
||||
}
|
||||
fireCallback( request.callback, frames );
|
||||
fireCallback( request.callback );
|
||||
}else{
|
||||
cache_error.put( request.url, getNow() );
|
||||
}
|
||||
|
@ -177,40 +204,15 @@ public class CustomEmojiCache {
|
|||
}
|
||||
}
|
||||
|
||||
private void fireCallback( final Callback callback, final APNGFrames frames ){
|
||||
private void fireCallback( final Callback callback ){
|
||||
handler.post( new Runnable() {
|
||||
@Override public void run(){
|
||||
callback.onAPNGLoadComplete( frames );
|
||||
callback.onAPNGLoadComplete();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
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_client2.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(){
|
||||
|
||||
// キャッシュの掃除
|
||||
|
@ -244,7 +246,7 @@ public class CustomEmojiCache {
|
|||
APNGFrames frames = APNGFrames.parseAPNG( new ByteArrayInputStream( data ), 64 );
|
||||
if( frames != null ) return frames;
|
||||
}catch( Throwable ex ){
|
||||
log.e( ex, "PNG decode failed. %s", url );
|
||||
log.e( ex, "PNG decode failed. %s ", url );
|
||||
// PngFeatureException Interlaced images are not yet supported
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
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;
|
||||
|
@ -11,7 +9,6 @@ import android.text.TextUtils;
|
|||
|
||||
import org.json.JSONArray;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
|
@ -21,8 +18,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
|
||||
import jp.juggler.subwaytooter.App1;
|
||||
import jp.juggler.subwaytooter.api.entity.CustomEmoji;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Response;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class CustomEmojiLister {
|
||||
|
@ -175,7 +170,7 @@ public class CustomEmojiLister {
|
|||
|
||||
CustomEmoji.List list = null;
|
||||
try{
|
||||
String data = getHttp( request.instance );
|
||||
String data = App1.getHttpCachedString("https://"+ request.instance + "/api/v1/custom_emojis");
|
||||
if( data != null ){
|
||||
list = decodeEmojiList( data, request.instance );
|
||||
}
|
||||
|
@ -209,31 +204,7 @@ public class CustomEmojiLister {
|
|||
} );
|
||||
}
|
||||
|
||||
private String getHttp( String instance ){
|
||||
Response response;
|
||||
try{
|
||||
okhttp3.Request.Builder request_builder = new okhttp3.Request.Builder();
|
||||
request_builder.url( "https://"+ instance + "/api/v1/custom_emojis" );
|
||||
Call call = App1.ok_http_client2.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().string();
|
||||
}catch( Throwable ex ){
|
||||
log.e( ex, "getHttp content error." );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void sweep_cache(){
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package jp.juggler.subwaytooter.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.Spannable;
|
||||
|
@ -9,52 +10,20 @@ import android.text.Spanned;
|
|||
import android.text.TextUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import jp.juggler.subwaytooter.App1;
|
||||
import jp.juggler.subwaytooter.R;
|
||||
import jp.juggler.subwaytooter.api.entity.CustomEmoji;
|
||||
|
||||
public abstract class EmojiDecoder {
|
||||
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;
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class EmojiDecoder {
|
||||
|
||||
private static class DecodeEnv {
|
||||
@NonNull final Context context;
|
||||
@NonNull final SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
|
||||
SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
int last_span_start = - 1;
|
||||
int last_span_end = - 1;
|
||||
|
||||
@Nullable CustomEmoji.Map custom_map;
|
||||
|
||||
DecodeEnv( @NonNull Context context, @Nullable CustomEmoji.Map custom_map ){
|
||||
DecodeEnv( @NonNull Context context ){
|
||||
this.context = context;
|
||||
this.custom_map = custom_map;
|
||||
}
|
||||
|
||||
void closeSpan(){
|
||||
if( last_span_start >= 0 ){
|
||||
if( last_span_end > last_span_start && App1.typeface_emoji != null ){
|
||||
EmojiSpan typefaceSpan = new EmojiSpan( App1.typeface_emoji );
|
||||
sb.setSpan( typefaceSpan, last_span_start, last_span_end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE );
|
||||
}
|
||||
last_span_start = - 1;
|
||||
}
|
||||
}
|
||||
|
||||
void addEmoji( String s ){
|
||||
if( last_span_start < 0 ){
|
||||
last_span_start = sb.length();
|
||||
}
|
||||
sb.append( s );
|
||||
last_span_end = sb.length();
|
||||
}
|
||||
|
||||
void addUnicodeString( String s ){
|
||||
|
@ -63,53 +32,36 @@ public abstract class EmojiDecoder {
|
|||
while( i < end ){
|
||||
int remain = end - i;
|
||||
String emoji = null;
|
||||
if( App1.USE_OLD_EMOJIONE ){
|
||||
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 ) ){
|
||||
emoji = check;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( emoji != null ){
|
||||
addEmoji( emoji );
|
||||
i += emoji.length();
|
||||
continue;
|
||||
}
|
||||
}else{
|
||||
Integer image_id = null;
|
||||
for( int j = EmojiMap201709.utf16_max_length ; j > 0 ; -- j ){
|
||||
if( j > remain ) continue;
|
||||
String check = s.substring( i, i + j );
|
||||
image_id = EmojiMap201709.sUTF16ToImageId.get( check );
|
||||
if( image_id != null ){
|
||||
if( j< remain && s.charAt( i+j ) == 0xFE0E){
|
||||
// 絵文字バリエーション・シーケンス(EVS)のU+FE0E(VS-15)が直後にある場合
|
||||
// その文字を絵文字化しない
|
||||
emoji = s.substring( i, i + j +1);
|
||||
image_id = 0;
|
||||
}else{
|
||||
emoji = check;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Integer image_id = null;
|
||||
for( int j = EmojiMap201709.utf16_max_length ; j > 0 ; -- j ){
|
||||
if( j > remain ) continue;
|
||||
String check = s.substring( i, i + j );
|
||||
image_id = EmojiMap201709.sUTF16ToImageId.get( check );
|
||||
if( image_id != null ){
|
||||
if( image_id == 0 ){
|
||||
if( j < remain && s.charAt( i + j ) == 0xFE0E ){
|
||||
// 絵文字バリエーション・シーケンス(EVS)のU+FE0E(VS-15)が直後にある場合
|
||||
// その文字を絵文字化しない
|
||||
closeSpan();
|
||||
sb.append( emoji );
|
||||
emoji = s.substring( i, i + j + 1 );
|
||||
image_id = 0;
|
||||
}else{
|
||||
addImageSpan( emoji, image_id );
|
||||
emoji = check;
|
||||
}
|
||||
i += emoji.length();
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
closeSpan();
|
||||
|
||||
if( image_id != null ){
|
||||
if( image_id == 0 ){
|
||||
// 絵文字バリエーション・シーケンス(EVS)のU+FE0E(VS-15)が直後にある場合
|
||||
// その文字を絵文字化しない
|
||||
sb.append( emoji );
|
||||
}else{
|
||||
addImageSpan( emoji, image_id );
|
||||
}
|
||||
i += emoji.length();
|
||||
continue;
|
||||
}
|
||||
|
||||
int length = Character.charCount( s.codePointAt( i ) );
|
||||
if( length == 1 ){
|
||||
sb.append( s.charAt( i ) );
|
||||
|
@ -121,8 +73,7 @@ public abstract class EmojiDecoder {
|
|||
}
|
||||
}
|
||||
|
||||
void addImageSpan( String text, int res_id ){
|
||||
closeSpan();
|
||||
void addImageSpan( String text, @DrawableRes int res_id ){
|
||||
int start = sb.length();
|
||||
sb.append( text );
|
||||
int end = sb.length();
|
||||
|
@ -130,7 +81,6 @@ public abstract class EmojiDecoder {
|
|||
}
|
||||
|
||||
void addNetworkEmojiSpan( String text, @NonNull String url ){
|
||||
closeSpan();
|
||||
int start = sb.length();
|
||||
sb.append( text );
|
||||
int end = sb.length();
|
||||
|
@ -138,78 +88,191 @@ public abstract class EmojiDecoder {
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean isWhitespaceBeforeEmoji( int cp ){
|
||||
switch( cp ){
|
||||
case 0x0009: // HORIZONTAL TABULATION
|
||||
case 0x000A: // LINE FEED
|
||||
case 0x000B: // VERTICAL TABULATION
|
||||
case 0x000C: // FORM FEED
|
||||
case 0x000D: // CARRIAGE RETURN
|
||||
case 0x001C: // FILE SEPARATOR
|
||||
case 0x001D: // GROUP SEPARATOR
|
||||
case 0x001E: // RECORD SEPARATOR
|
||||
case 0x001F: // UNIT SEPARATOR
|
||||
case 0x0020:
|
||||
case 0x00A0: //非区切りスペース
|
||||
case 0x1680:
|
||||
case 0x180E:
|
||||
case 0x2000:
|
||||
case 0x2001:
|
||||
case 0x2002:
|
||||
case 0x2003:
|
||||
case 0x2004:
|
||||
case 0x2005:
|
||||
case 0x2006:
|
||||
case 0x2007: //非区切りスペース
|
||||
case 0x2008:
|
||||
case 0x2009:
|
||||
case 0x200A:
|
||||
case 0x200B:
|
||||
case 0x202F: //非区切りスペース
|
||||
case 0x205F:
|
||||
case 0x2060:
|
||||
case 0x3000:
|
||||
case 0x3164:
|
||||
case 0xFEFF:
|
||||
return true;
|
||||
default:
|
||||
return Character.isWhitespace( cp );
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isShortCodeCharacter( int cp ){
|
||||
return ( 'A' <= cp && cp <= 'Z' )
|
||||
|| ( 'a' <= cp && cp <= 'z' )
|
||||
|| ( '0' <= cp && cp <= '9' )
|
||||
|| cp == '-'
|
||||
|| cp == '+'
|
||||
|| cp == '_'
|
||||
;
|
||||
}
|
||||
|
||||
interface ShortCodeSplitterCallback {
|
||||
void onString( @NonNull String part );
|
||||
|
||||
void onShortCode( @NonNull String part, @NonNull String name );
|
||||
}
|
||||
|
||||
static void splitShortCode( @NonNull String s, int start, int end, @NonNull ShortCodeSplitterCallback callback ){
|
||||
int i = start;
|
||||
while( i < end ){
|
||||
// 絵文字パターンの開始位置を探索する
|
||||
start = i;
|
||||
while( i < end ){
|
||||
int c = s.codePointAt( i );
|
||||
if( c == ':' ){
|
||||
// ショートコードの手前は始端か改行か空白文字でないとならない
|
||||
// 空白文字の判定はサーバサイドのそれにあわせる
|
||||
if( i == 0 || isWhitespaceBeforeEmoji( s.codePointBefore( i ) ) ){
|
||||
break;
|
||||
}
|
||||
}
|
||||
i += Character.charCount( c );
|
||||
}
|
||||
if( i > start ){
|
||||
callback.onString( s.substring( start, i ) );
|
||||
}
|
||||
if( i >= end ) break;
|
||||
|
||||
start = i++; // start=コロンの位置 i=その次の位置
|
||||
|
||||
int emoji_end = - 1;
|
||||
while( i < end ){
|
||||
int c = s.codePointAt( i );
|
||||
if( c == ':' ){
|
||||
emoji_end = i;
|
||||
break;
|
||||
}
|
||||
if( ! isShortCodeCharacter( c ) ){
|
||||
break;
|
||||
}
|
||||
i += Character.charCount( c );
|
||||
}
|
||||
|
||||
// 絵文字がみつからなかったら、startの位置のコロンだけを処理して残りは次のループで処理する
|
||||
if( emoji_end == - 1 || emoji_end - start < 3 ){
|
||||
callback.onString( ":" );
|
||||
i = start + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
callback.onShortCode(
|
||||
s.substring( start, emoji_end + 1 ) // ":shortcode:"
|
||||
, s.substring( start + 1, emoji_end ) // "shortcode"
|
||||
);
|
||||
|
||||
i = emoji_end + 1;// コロンの次の位置
|
||||
}
|
||||
}
|
||||
|
||||
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, @Nullable CustomEmoji.Map custom_map ){
|
||||
public static Spannable decodeEmoji( @NonNull final Context context, @NonNull final String s, @Nullable final CustomEmoji.Map custom_map ){
|
||||
|
||||
DecodeEnv decode_env = new DecodeEnv( context, 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 ) );
|
||||
}
|
||||
last_end = end;
|
||||
//
|
||||
if( App1.USE_OLD_EMOJIONE ){
|
||||
String unicode = map_name2unicode.get( matcher.group( 1 ) );
|
||||
if( unicode != null ){
|
||||
decode_env.addEmoji( unicode );
|
||||
continue;
|
||||
}
|
||||
}else{
|
||||
String name = matcher.group( 1 ).toLowerCase().replace( '-', '_' );
|
||||
Integer image_id = EmojiMap201709.sShortNameToImageId.get( name );
|
||||
if( image_id != null ){
|
||||
decode_env.addImageSpan( s.substring( start, end ), image_id );
|
||||
continue;
|
||||
}
|
||||
final DecodeEnv decode_env = new DecodeEnv( context );
|
||||
splitShortCode( s, 0, s.length(), new ShortCodeSplitterCallback() {
|
||||
@Override public void onString( @NonNull String part ){
|
||||
decode_env.addUnicodeString( part );
|
||||
}
|
||||
|
||||
String url = ( custom_map == null ? null : custom_map.get( matcher.group( 1 ) ) );
|
||||
if( ! TextUtils.isEmpty( url ) ){
|
||||
decode_env.addNetworkEmojiSpan( s.substring( start, end ), url );
|
||||
@Override public void onShortCode( @NonNull String part, @NonNull String name ){
|
||||
EmojiMap201709.EmojiInfo info = EmojiMap201709.sShortNameToImageId.get( name.toLowerCase().replace( '-', '_' ) );
|
||||
if( info != null ){
|
||||
decode_env.addImageSpan( part, info.image_id );
|
||||
return;
|
||||
}
|
||||
|
||||
}else if( reHohoemi.matcher( matcher.group( 1 ) ).find() ){
|
||||
decode_env.addImageSpan( s.substring( start, end ), R.drawable.emoji_hohoemi );
|
||||
}else if( reNicoru.matcher( matcher.group( 1 ) ).find() ){
|
||||
decode_env.addImageSpan( s.substring( start, end ), R.drawable.emoji_nicoru );
|
||||
}else{
|
||||
decode_env.addUnicodeString( s.substring( start, end ) );
|
||||
String url = ( custom_map == null ? null : custom_map.get( name ) );
|
||||
if( ! TextUtils.isEmpty( url ) ){
|
||||
decode_env.addNetworkEmojiSpan( part, url );
|
||||
return;
|
||||
}
|
||||
|
||||
if( reHohoemi.matcher( name ).find() ){
|
||||
decode_env.addImageSpan( part, R.drawable.emoji_hohoemi );
|
||||
}else if( reNicoru.matcher( name ).find() ){
|
||||
decode_env.addImageSpan( part, R.drawable.emoji_nicoru );
|
||||
}else{
|
||||
decode_env.addUnicodeString( part );
|
||||
}
|
||||
}
|
||||
}
|
||||
// copy remain
|
||||
int end = s.length();
|
||||
if( end > last_end ){
|
||||
decode_env.addUnicodeString( s.substring( last_end, end ) );
|
||||
}
|
||||
// close span
|
||||
decode_env.closeSpan();
|
||||
} );
|
||||
|
||||
return decode_env.sb;
|
||||
}
|
||||
|
||||
public static ArrayList< CharSequence > searchShortCode( Context context, String prefix, int limit ){
|
||||
ArrayList< CharSequence > dst = new ArrayList<>();
|
||||
if( ! App1.USE_OLD_EMOJIONE ){
|
||||
for( String shortCode : EmojiMap201709.sShortNameList ){
|
||||
if( dst.size() >= limit ) break;
|
||||
if( ! shortCode.contains( prefix )) continue;
|
||||
|
||||
SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
sb.append( ' ' );
|
||||
int start = 0;
|
||||
int end = sb.length();
|
||||
sb.setSpan( new EmojiImageSpan( context, EmojiMap201709.sShortNameToImageId.get( shortCode ) ), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE );
|
||||
sb.append( ' ' );
|
||||
sb.append( ':' );
|
||||
sb.append( shortCode );
|
||||
sb.append( ':' );
|
||||
dst.add( sb );
|
||||
// 投稿などの際、表示は不要だがショートコード=>Unicodeの解決を行いたい場合がある
|
||||
// カスタム絵文字の変換も行わない
|
||||
public static String decodeShortCode( @NonNull final String s ){
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
splitShortCode( s, 0, s.length(), new ShortCodeSplitterCallback() {
|
||||
@Override public void onString( @NonNull String part ){
|
||||
sb.append( part );
|
||||
}
|
||||
|
||||
@Override public void onShortCode( @NonNull String part, @NonNull String name ){
|
||||
EmojiMap201709.EmojiInfo info = EmojiMap201709.sShortNameToImageId.get( name.toLowerCase().replace( '-', '_' ) );
|
||||
sb.append( info != null ? info.unified : part );
|
||||
}
|
||||
} );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
// 入力補完用。絵文字ショートコード一覧を部分一致で絞り込む
|
||||
static ArrayList< CharSequence > searchShortCode( Context context, String prefix, int limit ){
|
||||
ArrayList< CharSequence > dst = new ArrayList<>();
|
||||
for( String shortCode : EmojiMap201709.sShortNameList ){
|
||||
if( dst.size() >= limit ) break;
|
||||
if( ! shortCode.contains( prefix ) ) continue;
|
||||
|
||||
EmojiMap201709.EmojiInfo info = EmojiMap201709.sShortNameToImageId.get( shortCode );
|
||||
if( info == null ) continue;
|
||||
|
||||
SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
sb.append( ' ' );
|
||||
int start = 0;
|
||||
int end = sb.length();
|
||||
|
||||
sb.setSpan( new EmojiImageSpan( context, info.image_id ), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE );
|
||||
sb.append( ' ' );
|
||||
sb.append( ':' );
|
||||
sb.append( shortCode );
|
||||
sb.append( ':' );
|
||||
dst.add( sb );
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -164,10 +164,10 @@ public class HTMLDecoder {
|
|||
, LinkClickContext account
|
||||
, SpannableStringBuilder sb
|
||||
, @NonNull DecodeOptions options
|
||||
){
|
||||
){
|
||||
if( TAG_TEXT.equals( tag ) ){
|
||||
if( options.bDecodeEmoji ){
|
||||
sb.append( EmojiDecoder.decodeEmoji( context, decodeEntity( text ) ,options.customEmojiMap ) );
|
||||
sb.append( EmojiDecoder.decodeEmoji( context, decodeEntity( text ), options.customEmojiMap ) );
|
||||
}else{
|
||||
sb.append( decodeEntity( text ) );
|
||||
}
|
||||
|
@ -188,7 +188,7 @@ public class HTMLDecoder {
|
|||
sb_tmp.append( "<img/>" );
|
||||
}else{
|
||||
for( Node child : child_nodes ){
|
||||
child.encodeSpan( context, account, sb_tmp, options);
|
||||
child.encodeSpan( context, account, sb_tmp, options );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,8 +205,8 @@ public class HTMLDecoder {
|
|||
if( end > start && "a".equals( tag ) ){
|
||||
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 ),options.link_tag );
|
||||
String link_text = sb.subSequence( start, end ).toString();
|
||||
MyClickableSpan span = new MyClickableSpan( account, link_text, href, account.findAcctColor( href ), options.link_tag );
|
||||
sb.setSpan( span, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );
|
||||
}
|
||||
}
|
||||
|
@ -281,16 +281,12 @@ public class HTMLDecoder {
|
|||
}
|
||||
|
||||
if( is_media_attachment( list_attachment, href ) ){
|
||||
if( App1.USE_OLD_EMOJIONE ){
|
||||
return EmojiDecoder.decodeEmoji( context, ":frame_photo:",null );
|
||||
}else{
|
||||
SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
sb.append( href );
|
||||
int start = 0;
|
||||
int end = sb.length();
|
||||
sb.setSpan( new EmojiImageSpan( context, R.drawable.emj_1f5bc ), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE );
|
||||
return sb;
|
||||
}
|
||||
SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
sb.append( href );
|
||||
int start = 0;
|
||||
int end = sb.length();
|
||||
sb.setSpan( new EmojiImageSpan( context, R.drawable.emj_1f5bc ), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE );
|
||||
return sb;
|
||||
}
|
||||
|
||||
try{
|
||||
|
@ -319,12 +315,11 @@ public class HTMLDecoder {
|
|||
return Character.isWhitespace( c ) || c == 0x0a || c == 0x0d;
|
||||
}
|
||||
|
||||
|
||||
public static SpannableStringBuilder decodeHTML(
|
||||
Context context
|
||||
, LinkClickContext account
|
||||
, String src
|
||||
,@NonNull DecodeOptions options
|
||||
, @NonNull DecodeOptions options
|
||||
){
|
||||
prepareTagInformation();
|
||||
SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
|
@ -336,7 +331,7 @@ public class HTMLDecoder {
|
|||
rootNode.addChild( tracker, "" );
|
||||
}
|
||||
|
||||
rootNode.encodeSpan( context, account, sb,options);
|
||||
rootNode.encodeSpan( context, account, sb, options );
|
||||
int end = sb.length();
|
||||
while( end > 0 && isWhitespace( sb.charAt( end - 1 ) ) ) -- end;
|
||||
if( end < sb.length() ){
|
||||
|
@ -376,7 +371,7 @@ public class HTMLDecoder {
|
|||
// return sb;
|
||||
// }
|
||||
|
||||
public static Spannable decodeMentions( final SavedAccount access_info, TootMention.List src_list ,@Nullable Object link_tag ){
|
||||
public static Spannable decodeMentions( final SavedAccount access_info, TootMention.List src_list, @Nullable Object link_tag ){
|
||||
if( src_list == null || src_list.isEmpty() ) return null;
|
||||
SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
for( TootMention item : src_list ){
|
||||
|
@ -390,8 +385,8 @@ public class HTMLDecoder {
|
|||
}
|
||||
int end = sb.length();
|
||||
if( end > start ){
|
||||
String link_text = sb.subSequence( start,end ).toString();
|
||||
MyClickableSpan span = new MyClickableSpan( access_info, link_text,item.url, access_info.findAcctColor( item.url ) ,link_tag );
|
||||
String link_text = sb.subSequence( start, end ).toString();
|
||||
MyClickableSpan span = new MyClickableSpan( access_info, link_text, item.url, access_info.findAcctColor( item.url ), link_tag );
|
||||
|
||||
sb.setSpan( span, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package jp.juggler.subwaytooter.util;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.Spannable;
|
||||
|
@ -36,4 +37,25 @@ public class NetworkEmojiInvalidator implements Runnable, NetworkEmojiSpan.Inval
|
|||
view.postInvalidateOnAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
// 最後に描画した時刻
|
||||
private long t_last_draw;
|
||||
|
||||
// アニメーション開始時刻
|
||||
private long t_start;
|
||||
|
||||
@Override public long getTimeFromStart(){
|
||||
|
||||
long now = SystemClock.elapsedRealtime();
|
||||
|
||||
// アニメーション開始時刻を計算する
|
||||
if( t_start == 0L || now - t_last_draw >= 60000L ){
|
||||
t_start = now;
|
||||
}
|
||||
t_last_draw = now;
|
||||
|
||||
return now - t_start;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -33,6 +33,7 @@ public class NetworkEmojiSpan extends ReplacementSpan implements CustomEmojiCach
|
|||
}
|
||||
|
||||
public interface InvalidateCallback {
|
||||
long getTimeFromStart();
|
||||
void delayInvalidate( long delay );
|
||||
}
|
||||
|
||||
|
@ -43,7 +44,7 @@ public class NetworkEmojiSpan extends ReplacementSpan implements CustomEmojiCach
|
|||
}
|
||||
|
||||
// implements CustomEmojiCache.Callback
|
||||
@Override public void onAPNGLoadComplete( APNGFrames b ){
|
||||
@Override public void onAPNGLoadComplete(){
|
||||
if( invalidate_callback != null ){
|
||||
invalidate_callback.delayInvalidate( 0 );
|
||||
}
|
||||
|
@ -73,11 +74,7 @@ public class NetworkEmojiSpan extends ReplacementSpan implements CustomEmojiCach
|
|||
// フレーム探索結果を格納する構造体を確保しておく
|
||||
private final APNGFrames.FindFrameResult mFrameFindResult = new APNGFrames.FindFrameResult();
|
||||
|
||||
// 最後に描画した時刻
|
||||
private long t_last_draw;
|
||||
|
||||
// アニメーション開始時刻
|
||||
private long t_start;
|
||||
|
||||
|
||||
@Override public void draw(
|
||||
@NonNull Canvas canvas
|
||||
|
@ -88,19 +85,13 @@ public class NetworkEmojiSpan extends ReplacementSpan implements CustomEmojiCach
|
|||
if( invalidate_callback == null ) return;
|
||||
|
||||
// APNGデータの取得
|
||||
APNGFrames frames = App1.custom_emoji_cache.get( url, this );
|
||||
APNGFrames frames = App1.custom_emoji_cache.get( this, url, this );
|
||||
if( frames == null ) return;
|
||||
|
||||
long now = SystemClock.elapsedRealtime();
|
||||
|
||||
// アニメーション開始時刻を計算する
|
||||
if( t_start == 0L || now - t_last_draw >= 60000L ){
|
||||
t_start = now;
|
||||
}
|
||||
t_last_draw = now;
|
||||
long t = invalidate_callback.getTimeFromStart();
|
||||
|
||||
// アニメーション開始時刻からの経過時間に応じたフレームを探索
|
||||
frames.findFrame( mFrameFindResult, now - t_start );
|
||||
frames.findFrame( mFrameFindResult, t );
|
||||
|
||||
Bitmap b = mFrameFindResult.bitmap;
|
||||
if( b == null || b.isRecycled() ) return;
|
||||
|
|
|
@ -18,6 +18,7 @@ import java.util.ArrayList;
|
|||
|
||||
import jp.juggler.subwaytooter.R;
|
||||
import jp.juggler.subwaytooter.Styler;
|
||||
import jp.juggler.subwaytooter.view.MyEditText;
|
||||
|
||||
@SuppressWarnings("WeakerAccess") class PopupAutoCompleteAcct {
|
||||
final Activity activity;
|
||||
|
@ -61,7 +62,8 @@ import jp.juggler.subwaytooter.Styler;
|
|||
}
|
||||
|
||||
void setList(
|
||||
final int sel_start
|
||||
final MyEditText et
|
||||
, final int sel_start
|
||||
, final int sel_end
|
||||
, @Nullable ArrayList< CharSequence > acct_list
|
||||
, @Nullable String picker_caption
|
||||
|
@ -115,11 +117,11 @@ import jp.juggler.subwaytooter.Styler;
|
|||
}
|
||||
v.setOnClickListener( new View.OnClickListener() {
|
||||
@Override public void onClick( View v ){
|
||||
String s = etContent.getText().toString();
|
||||
String s = et.getText().toString();
|
||||
CharSequence svInsert = ( acct.charAt( 0 ) == ' ' ? acct.subSequence( 2, acct.length() ) : acct );
|
||||
s = s.substring( 0, sel_start ) + svInsert + " " + ( sel_end >= s.length() ? "" : s.substring( sel_end ) );
|
||||
etContent.setText( s );
|
||||
etContent.setSelection( sel_start + svInsert.length() + 1 );
|
||||
et.setText( s );
|
||||
et.setSelection( sel_start + svInsert.length() + 1 );
|
||||
acct_popup.dismiss();
|
||||
}
|
||||
} );
|
||||
|
|
|
@ -251,7 +251,7 @@ public class PostHelper implements CustomEmojiLister.Callback, EmojiPicker.Callb
|
|||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "status=" );
|
||||
sb.append( Uri.encode( content ) );
|
||||
sb.append( Uri.encode( EmojiDecoder.decodeShortCode(content ) ) );
|
||||
|
||||
if( visibility_checked != null ){
|
||||
sb.append( "&visibility=" );
|
||||
|
@ -264,7 +264,7 @@ public class PostHelper implements CustomEmojiLister.Callback, EmojiPicker.Callb
|
|||
|
||||
if( spoiler_text != null ){
|
||||
sb.append( "&spoiler_text=" );
|
||||
sb.append( Uri.encode( spoiler_text ) );
|
||||
sb.append( Uri.encode( EmojiDecoder.decodeShortCode(spoiler_text) ) );
|
||||
}
|
||||
|
||||
if( in_reply_to_id != - 1L ){
|
||||
|
@ -289,12 +289,12 @@ public class PostHelper implements CustomEmojiLister.Callback, EmojiPicker.Callb
|
|||
|
||||
JSONObject json = new JSONObject();
|
||||
try{
|
||||
json.put( "status", content );
|
||||
json.put( "status", EmojiDecoder.decodeShortCode(content) );
|
||||
if( visibility_checked != null ){
|
||||
json.put( "visibility", visibility_checked );
|
||||
}
|
||||
json.put( "sensitive", bNSFW );
|
||||
json.put( "spoiler_text", TextUtils.isEmpty( spoiler_text ) ? "" : spoiler_text );
|
||||
json.put( "spoiler_text", TextUtils.isEmpty( spoiler_text ) ? "" : EmojiDecoder.decodeShortCode(spoiler_text) );
|
||||
json.put( "in_reply_to_id", in_reply_to_id == - 1L ? null : in_reply_to_id );
|
||||
JSONArray array = new JSONArray();
|
||||
if( attachment_list != null ){
|
||||
|
@ -308,7 +308,7 @@ public class PostHelper implements CustomEmojiLister.Callback, EmojiPicker.Callb
|
|||
json.put( "isEnquete", true );
|
||||
array = new JSONArray();
|
||||
for( String item : enquete_items ){
|
||||
array.put( item );
|
||||
array.put( EmojiDecoder.decodeShortCode(item) );
|
||||
}
|
||||
json.put( "enquete_items", array );
|
||||
}catch( JSONException ex ){
|
||||
|
@ -590,7 +590,7 @@ public class PostHelper implements CustomEmojiLister.Callback, EmojiPicker.Callb
|
|||
if( popup == null || ! popup.isShowing() ){
|
||||
popup = new PopupAutoCompleteAcct( activity, et, formRoot, bMainScreen );
|
||||
}
|
||||
popup.setList( start, end, acct_list, null, null );
|
||||
popup.setList( et, start, end, acct_list, null, null );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -622,10 +622,12 @@ public class PostHelper implements CustomEmojiLister.Callback, EmojiPicker.Callb
|
|||
if( popup == null || ! popup.isShowing() ){
|
||||
popup = new PopupAutoCompleteAcct( activity, et, formRoot, bMainScreen );
|
||||
}
|
||||
popup.setList( last_sharp, end, tag_list, null, null );
|
||||
popup.setList( et, last_sharp, end, tag_list, null, null );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void checkEmoji(){
|
||||
int end = et.getSelectionEnd();
|
||||
|
||||
|
@ -644,12 +646,20 @@ public class PostHelper implements CustomEmojiLister.Callback, EmojiPicker.Callb
|
|||
return;
|
||||
}
|
||||
|
||||
// : の手前は始端か改行か空白でなければならない
|
||||
if( last_colon > 0 && ! EmojiDecoder.isWhitespaceBeforeEmoji( src.codePointBefore( last_colon ) ) ){
|
||||
log.d( "checkEmoji: invalid character before shortcode." );
|
||||
closeAcctPopup();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if( part.length() == 0 ){
|
||||
if( popup == null || ! popup.isShowing() ){
|
||||
popup = new PopupAutoCompleteAcct( activity, et, formRoot, bMainScreen );
|
||||
}
|
||||
popup.setList(
|
||||
last_colon, end
|
||||
et, last_colon, end
|
||||
, null
|
||||
, picker_caption_emoji
|
||||
, open_picker_emoji
|
||||
|
@ -684,12 +694,10 @@ public class PostHelper implements CustomEmojiLister.Callback, EmojiPicker.Callb
|
|||
}
|
||||
}
|
||||
if( code_list.isEmpty() ){
|
||||
closeAcctPopup();
|
||||
}else{
|
||||
if( popup == null || ! popup.isShowing() ){
|
||||
popup = new PopupAutoCompleteAcct( activity, et, formRoot, bMainScreen );
|
||||
}
|
||||
popup.setList( last_colon, end, code_list, picker_caption_emoji, open_picker_emoji );
|
||||
popup.setList( et, last_colon, end, code_list, picker_caption_emoji, open_picker_emoji );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -59,7 +59,7 @@ public class NetworkEmojiView extends View implements CustomEmojiCache.Callback
|
|||
super.onDraw( canvas );
|
||||
|
||||
// APNGデータの取得
|
||||
APNGFrames frames = App1.custom_emoji_cache.get( url, this );
|
||||
APNGFrames frames = App1.custom_emoji_cache.get( this, url, this );
|
||||
if( frames == null ) return;
|
||||
|
||||
long now = SystemClock.elapsedRealtime();
|
||||
|
@ -87,7 +87,7 @@ public class NetworkEmojiView extends View implements CustomEmojiCache.Callback
|
|||
}
|
||||
}
|
||||
|
||||
@Override public void onAPNGLoadComplete( APNGFrames b ){
|
||||
@Override public void onAPNGLoadComplete(){
|
||||
postInvalidateOnAnimation();;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue