カスタム絵文字4k個のタンスで絵文字ピッカーの挙動を最適化した

This commit is contained in:
tateisu 2017-09-26 22:39:37 +09:00
parent 2d4b107879
commit 33584b5689
19 changed files with 13207 additions and 13032 deletions

View File

@ -16,6 +16,7 @@
<w>gifv</w> <w>gifv</w>
<w>hashtag</w> <w>hashtag</w>
<w>hashtags</w> <w>hashtags</w>
<w>hohoemi</w>
<w>idempotency</w> <w>idempotency</w>
<w>kenglxn</w> <w>kenglxn</w>
<w>mailto</w> <w>mailto</w>

View File

@ -443,16 +443,22 @@ for(@fix_name){
updateCodeMap(); updateCodeMap();
updateNameMap(); updateNameMap();
my %name_chars;
my $bad_name = 0; my $bad_name = 0;
for my $name (sort keys %name_map){ for my $name (sort keys %name_map){
for( split //,$name ){
$name_chars{$_}=1;
}
my $rh = $name_map{$name}; my $rh = $name_map{$name};
my @res_list = values %$rh; my @res_list = values %$rh;
next if @res_list == 1; next if @res_list == 1;
warn "name $name has multiple resource. ",join(',',map{ $_->{res_name} } @res_list),"\n"; warn "name $name has multiple resource. ",join(',',map{ $_->{res_name} } @res_list),"\n";
$bad_name = 1; $bad_name = 1;
} }
$bad_name and die "please fix name=>resource duplicate.\n"; $bad_name and die "please fix name=>resource duplicate.\n";
warn "name_chars: [",join('',sort keys %name_chars),"]\n";
sub decodeUnified($){ sub decodeUnified($){
my($chars) = @_; my($chars) = @_;
@ -552,6 +558,8 @@ my $utf8 = Encode::find_encoding("utf8");
my $utf16 = Encode::find_encoding("UTF-16BE"); my $utf16 = Encode::find_encoding("UTF-16BE");
my $utf16_max_length = 0; my $utf16_max_length = 0;
# 画像リソースIDとUnidoceシーケンスの関連付けを出力する # 画像リソースIDとUnidoceシーケンスの関連付けを出力する
for my $res_name ( sort keys %res_map ){ for my $res_name ( sort keys %res_map ){
my $res_info = $res_map{$res_name}; 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の関連付けを出力する # 画像リソースIDとshortcodeの関連付けを出力する
for my $res_name ( sort keys %res_map ){ # 投稿時にshortcodeをユニコードに変換するため、shortcodeとUTF-16シーケンスの関連付けを出力する
my $res_info = $res_map{$res_name}; for my $name (sort keys %name_map){
for my $short_name ( sort keys %{$res_info->{shortname_map}} ){ my $rh = $name_map{$name};
addCode( qq{name( R.drawable.$res_name, "$short_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

View File

@ -866,7 +866,7 @@ public class ActAccountSetting extends AppCompatActivity
return; return;
} }
} }
updateCredential( "display_name=" + Uri.encode(sv ) ); updateCredential( "display_name=" + Uri.encode(EmojiDecoder.decodeShortCode(sv) ) );
} }
private void sendNote(boolean bConfirmed){ private void sendNote(boolean bConfirmed){
@ -890,7 +890,7 @@ public class ActAccountSetting extends AppCompatActivity
return; return;
} }
} }
updateCredential( "note=" + Uri.encode(sv ) ); updateCredential( "note=" + Uri.encode(EmojiDecoder.decodeShortCode(sv) ) );
} }
private static final int PERMISSION_REQUEST_AVATAR = 1; private static final int PERMISSION_REQUEST_AVATAR = 1;

View File

@ -7,8 +7,9 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Typeface; import android.os.SystemClock;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder; 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.CustomEmojiLister;
import jp.juggler.subwaytooter.util.LogCategory; import jp.juggler.subwaytooter.util.LogCategory;
import okhttp3.Cache; import okhttp3.Cache;
import okhttp3.CacheControl;
import okhttp3.Call;
import okhttp3.CipherSuite; import okhttp3.CipherSuite;
import okhttp3.ConnectionSpec; import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Response;
import uk.co.chrisjenx.calligraphy.CalligraphyConfig; import uk.co.chrisjenx.calligraphy.CalligraphyConfig;
import uk.co.chrisjenx.calligraphy.TypefaceUtils;
public class App1 extends Application { 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_client;
public static OkHttpClient ok_http_client2; private static OkHttpClient ok_http_client2;
public static final boolean USE_OLD_EMOJIONE = false; // public static final boolean USE_OLD_EMOJIONE = false;
public static Typeface typeface_emoji; // public static Typeface typeface_emoji;
public static SharedPreferences pref; public static SharedPreferences pref;
@ -292,11 +295,11 @@ public class App1 extends Application {
AcctSet.deleteOld( System.currentTimeMillis() ); AcctSet.deleteOld( System.currentTimeMillis() );
} }
if( USE_OLD_EMOJIONE ){ // if( USE_OLD_EMOJIONE ){
if( typeface_emoji == null ){ // if( typeface_emoji == null ){
typeface_emoji = TypefaceUtils.load( app_context.getAssets(), "emojione_android.ttf" ); // typeface_emoji = TypefaceUtils.load( app_context.getAssets(), "emojione_android.ttf" );
} // }
} // }
// if( image_loader == null ){ // if( image_loader == null ){
// image_loader = new MyImageLoader( // 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;
}
}
} }

View File

@ -11,6 +11,7 @@ import jp.juggler.subwaytooter.api.entity.TootAccount;
import jp.juggler.subwaytooter.api.entity.TootStatus; import jp.juggler.subwaytooter.api.entity.TootStatus;
import jp.juggler.subwaytooter.table.UserRelation; import jp.juggler.subwaytooter.table.UserRelation;
import jp.juggler.subwaytooter.util.EmojiDecoder; import jp.juggler.subwaytooter.util.EmojiDecoder;
import jp.juggler.subwaytooter.util.EmojiMap201709;
import jp.juggler.subwaytooter.view.MyLinkMovementMethod; import jp.juggler.subwaytooter.view.MyLinkMovementMethod;
import jp.juggler.subwaytooter.view.MyNetworkImageView; 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 ) , 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 ); llProfile = viewRoot.findViewById( R.id.llProfile );
tvCreated = (TextView) viewRoot.findViewById( R.id.tvCreated ); tvCreated = viewRoot.findViewById( R.id.tvCreated );
ivAvatar = (MyNetworkImageView) viewRoot.findViewById( R.id.ivAvatar ); ivAvatar = viewRoot.findViewById( R.id.ivAvatar );
tvDisplayName = (TextView) viewRoot.findViewById( R.id.tvDisplayName ); tvDisplayName = viewRoot.findViewById( R.id.tvDisplayName );
tvAcct = (TextView) viewRoot.findViewById( R.id.tvAcct ); tvAcct = viewRoot.findViewById( R.id.tvAcct );
btnFollowing = (Button) viewRoot.findViewById( R.id.btnFollowing ); btnFollowing = viewRoot.findViewById( R.id.btnFollowing );
btnFollowers = (Button) viewRoot.findViewById( R.id.btnFollowers ); btnFollowers = viewRoot.findViewById( R.id.btnFollowers );
btnStatusCount = (Button) viewRoot.findViewById( R.id.btnStatusCount ); btnStatusCount = viewRoot.findViewById( R.id.btnStatusCount );
tvNote = (TextView) viewRoot.findViewById( R.id.tvNote ); tvNote = viewRoot.findViewById( R.id.tvNote );
View btnMore = viewRoot.findViewById( R.id.btnMore ); View btnMore = viewRoot.findViewById( R.id.btnMore );
btnFollow = (ImageButton) viewRoot.findViewById( R.id.btnFollow ); btnFollow = viewRoot.findViewById( R.id.btnFollow );
ivFollowedBy = (ImageView) viewRoot.findViewById( R.id.ivFollowedBy ); ivFollowedBy = viewRoot.findViewById( R.id.ivFollowedBy );
tvRemoteProfileWarning = (TextView) viewRoot.findViewById( R.id.tvRemoteProfileWarning ); tvRemoteProfileWarning = viewRoot.findViewById( R.id.tvRemoteProfileWarning );
ivBackground.setOnClickListener( this ); ivBackground.setOnClickListener( this );
btnFollowing.setOnClickListener( this ); btnFollowing.setOnClickListener( this );
@ -103,7 +104,7 @@ class HeaderViewHolderProfile extends HeaderViewHolderBase implements View.OnCli
String s = "@" + access_info.getFullAcct( who ); String s = "@" + access_info.getFullAcct( who );
if( who.locked ){ if( who.locked ){
s += " " + EmojiDecoder.map_name2unicode.get( "lock" ); s += " " + EmojiMap201709.sShortNameToImageId.get("lock" ).unified;
} }
tvAcct.setText( EmojiDecoder.decodeEmoji( activity, s ,null) ); tvAcct.setText( EmojiDecoder.decodeEmoji( activity, s ,null) );

View File

@ -133,8 +133,8 @@ public class EmojiPicker implements View.OnClickListener, CustomEmojiLister.Call
SkinTone tone = (SkinTone) viewRoot.findViewById( selected_tone ).getTag(); SkinTone tone = (SkinTone) viewRoot.findViewById( selected_tone ).getTag();
for( String suffix : tone.suffix_list ){ for( String suffix : tone.suffix_list ){
String new_name = name + suffix; String new_name = name + suffix;
Integer value = EmojiMap201709.sShortNameToImageId.get( new_name ); EmojiMap201709.EmojiInfo info = EmojiMap201709.sShortNameToImageId.get( new_name );
if( value != null ) return new_name; if( info != null ) return new_name;
} }
return name; return name;
} }
@ -334,9 +334,9 @@ public class EmojiPicker implements View.OnClickListener, CustomEmojiLister.Call
view.setTag( item ); view.setTag( item );
ImageView iv = (ImageView) view; ImageView iv = (ImageView) view;
if( page != null ){ if( page != null ){
Integer image_id = EmojiMap201709.sShortNameToImageId.get( item.name ); EmojiMap201709.EmojiInfo info = EmojiMap201709.sShortNameToImageId.get( item.name );
if( image_id != null ){ if( info != null ){
iv.setImageResource( image_id ); iv.setImageResource( info.image_id );
} }
} }
}else{ }else{
@ -361,8 +361,8 @@ public class EmojiPicker implements View.OnClickListener, CustomEmojiLister.Call
EmojiItem item = page.emoji_list.get( idx ); EmojiItem item = page.emoji_list.get( idx );
if( TextUtils.isEmpty( item.instance ) ){ if( TextUtils.isEmpty( item.instance ) ){
String name = item.name; String name = item.name;
Integer image_id = EmojiMap201709.sShortNameToImageId.get( name ); EmojiMap201709.EmojiInfo info = EmojiMap201709.sShortNameToImageId.get( name );
if( image_id == null ) return; if( info == null ) return;
if( selected_tone != 0 ){ if( selected_tone != 0 ){
name = applySkinTone( name ); name = applySkinTone( name );
} }

View File

@ -459,7 +459,6 @@ import java.util.ArrayList;
if( result != null ) result.onParseComplete(); if( result != null ) result.onParseComplete();
return result; return result;
}catch( Throwable ex ){ }catch( Throwable ex ){
log.trace( ex );
handler.dispose(); handler.dispose();
throw ex; throw ex;
} }

View File

@ -9,16 +9,16 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import jp.juggler.subwaytooter.App1; import jp.juggler.subwaytooter.App1;
import okhttp3.Call;
import okhttp3.Response;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public class CustomEmojiCache { public class CustomEmojiCache {
@ -62,24 +62,41 @@ public class CustomEmojiCache {
// リクエスト // リクエスト
public interface Callback { public interface Callback {
void onAPNGLoadComplete( APNGFrames b ); void onAPNGLoadComplete();
} }
static class Request { static class Request {
@NonNull String url; @NonNull final WeakReference<Object> refTarget;
@NonNull Callback callback; @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.url = url;
this.callback = callback; 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 ){ synchronized( cache ){
long now = getNow(); long now = getNow();
@ -97,7 +114,9 @@ public class CustomEmojiCache {
return null; return null;
} }
} }
queue.add( new Request( url, callback ) ); synchronized( queue ){
queue.addLast( new Request( target_tag, url, callback ) );
}
worker.notifyEx(); worker.notifyEx();
return null; return null;
} }
@ -124,19 +143,27 @@ public class CustomEmojiCache {
@Override public void run(){ @Override public void run(){
while( ! bCancelled.get() ){ while( ! bCancelled.get() ){
Request request = queue.poll(); Request request;
synchronized( queue ){
request = queue.isEmpty() ? null : queue.removeFirst();
}
if( request == null ){ if( request == null ){
waitEx( 86400000L ); waitEx( 86400000L );
continue; continue;
} }
if( request.refTarget.get() == null ){
continue;
}
long now = getNow(); long now = getNow();
synchronized( cache ){ synchronized( cache ){
// 成功キャッシュ // 成功キャッシュ
CacheItem item = cache.get( request.url ); CacheItem item = cache.get( request.url );
if( item != null ){ if( item != null ){
fireCallback( request.callback, item.frames ); fireCallback( request.callback );
continue; continue;
} }
@ -151,7 +178,7 @@ public class CustomEmojiCache {
APNGFrames frames = null; APNGFrames frames = null;
try{ try{
byte[] data = getHttp( request.url ); byte[] data = App1.getHttpCached( request.url );
if( data != null ){ if( data != null ){
frames = decodeAPNG( data, request.url ); frames = decodeAPNG( data, request.url );
} }
@ -169,7 +196,7 @@ public class CustomEmojiCache {
item.frames.dispose(); item.frames.dispose();
item.frames = frames; item.frames = frames;
} }
fireCallback( request.callback, frames ); fireCallback( request.callback );
}else{ }else{
cache_error.put( request.url, getNow() ); 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() { handler.post( new Runnable() {
@Override public void run(){ @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(){ private void sweep_cache(){
// キャッシュの掃除 // キャッシュの掃除
@ -244,7 +246,7 @@ public class CustomEmojiCache {
APNGFrames frames = APNGFrames.parseAPNG( new ByteArrayInputStream( data ), 64 ); APNGFrames frames = APNGFrames.parseAPNG( new ByteArrayInputStream( data ), 64 );
if( frames != null ) return frames; if( frames != null ) return frames;
}catch( Throwable ex ){ }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 // PngFeatureException Interlaced images are not yet supported
} }

View File

@ -1,8 +1,6 @@
package jp.juggler.subwaytooter.util; package jp.juggler.subwaytooter.util;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler; import android.os.Handler;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
@ -11,7 +9,6 @@ import android.text.TextUtils;
import org.json.JSONArray; import org.json.JSONArray;
import java.io.ByteArrayInputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
@ -21,8 +18,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import jp.juggler.subwaytooter.App1; import jp.juggler.subwaytooter.App1;
import jp.juggler.subwaytooter.api.entity.CustomEmoji; import jp.juggler.subwaytooter.api.entity.CustomEmoji;
import okhttp3.Call;
import okhttp3.Response;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public class CustomEmojiLister { public class CustomEmojiLister {
@ -175,7 +170,7 @@ public class CustomEmojiLister {
CustomEmoji.List list = null; CustomEmoji.List list = null;
try{ try{
String data = getHttp( request.instance ); String data = App1.getHttpCachedString("https://"+ request.instance + "/api/v1/custom_emojis");
if( data != null ){ if( data != null ){
list = decodeEmojiList( data, request.instance ); 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(){ private void sweep_cache(){

View File

@ -1,6 +1,7 @@
package jp.juggler.subwaytooter.util; package jp.juggler.subwaytooter.util;
import android.content.Context; import android.content.Context;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.Spannable; import android.text.Spannable;
@ -9,52 +10,20 @@ import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
import java.util.ArrayList; 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 java.util.regex.Pattern;
import jp.juggler.subwaytooter.App1;
import jp.juggler.subwaytooter.R; import jp.juggler.subwaytooter.R;
import jp.juggler.subwaytooter.api.entity.CustomEmoji; import jp.juggler.subwaytooter.api.entity.CustomEmoji;
public abstract class EmojiDecoder { @SuppressWarnings("WeakerAccess")
private static final Pattern SHORTNAME_PATTERN = Pattern.compile( ":([-+\\w]+):" ); public class EmojiDecoder {
public static final HashMap< String, String > map_name2unicode = EmojiMap._shortNameToUnicode;
private static final HashSet< String > set_unicode = EmojiMap._unicode_set;
private static class DecodeEnv { private static class DecodeEnv {
@NonNull final Context context; @NonNull final Context context;
@NonNull final SpannableStringBuilder sb = new SpannableStringBuilder();
SpannableStringBuilder sb = new SpannableStringBuilder(); DecodeEnv( @NonNull Context context ){
int last_span_start = - 1;
int last_span_end = - 1;
@Nullable CustomEmoji.Map custom_map;
DecodeEnv( @NonNull Context context, @Nullable CustomEmoji.Map custom_map ){
this.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 ){ void addUnicodeString( String s ){
@ -63,53 +32,36 @@ public abstract class EmojiDecoder {
while( i < end ){ while( i < end ){
int remain = end - i; int remain = end - i;
String emoji = null; String emoji = null;
if( App1.USE_OLD_EMOJIONE ){ Integer image_id = null;
for( int j = EmojiMap.max_length ; j > 0 ; -- j ){ for( int j = EmojiMap201709.utf16_max_length ; j > 0 ; -- j ){
if( j > remain ) continue; if( j > remain ) continue;
String check = s.substring( i, i + j ); String check = s.substring( i, i + j );
if( set_unicode.contains( check ) ){ image_id = EmojiMap201709.sUTF16ToImageId.get( 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+FE0EVS-15が直後にある場合
// その文字を絵文字化しない
emoji = s.substring( i, i + j +1);
image_id = 0;
}else{
emoji = check;
}
break;
}
}
if( image_id != null ){ if( image_id != null ){
if( image_id == 0 ){ if( j < remain && s.charAt( i + j ) == 0xFE0E ){
// 絵文字バリエーションシーケンスEVSのU+FE0EVS-15が直後にある場合 // 絵文字バリエーションシーケンスEVSのU+FE0EVS-15が直後にある場合
// その文字を絵文字化しない // その文字を絵文字化しない
closeSpan(); emoji = s.substring( i, i + j + 1 );
sb.append( emoji ); image_id = 0;
}else{ }else{
addImageSpan( emoji, image_id ); emoji = check;
} }
i += emoji.length(); break;
continue;
} }
} }
closeSpan();
if( image_id != null ){
if( image_id == 0 ){
// 絵文字バリエーションシーケンスEVSのU+FE0EVS-15が直後にある場合
// その文字を絵文字化しない
sb.append( emoji );
}else{
addImageSpan( emoji, image_id );
}
i += emoji.length();
continue;
}
int length = Character.charCount( s.codePointAt( i ) ); int length = Character.charCount( s.codePointAt( i ) );
if( length == 1 ){ if( length == 1 ){
sb.append( s.charAt( i ) ); sb.append( s.charAt( i ) );
@ -121,8 +73,7 @@ public abstract class EmojiDecoder {
} }
} }
void addImageSpan( String text, int res_id ){ void addImageSpan( String text, @DrawableRes int res_id ){
closeSpan();
int start = sb.length(); int start = sb.length();
sb.append( text ); sb.append( text );
int end = sb.length(); int end = sb.length();
@ -130,7 +81,6 @@ public abstract class EmojiDecoder {
} }
void addNetworkEmojiSpan( String text, @NonNull String url ){ void addNetworkEmojiSpan( String text, @NonNull String url ){
closeSpan();
int start = sb.length(); int start = sb.length();
sb.append( text ); sb.append( text );
int end = sb.length(); 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 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 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 ); final DecodeEnv decode_env = new DecodeEnv( context );
Matcher matcher = SHORTNAME_PATTERN.matcher( s ); splitShortCode( s, 0, s.length(), new ShortCodeSplitterCallback() {
int last_end = 0; @Override public void onString( @NonNull String part ){
while( matcher.find() ){ decode_env.addUnicodeString( part );
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;
}
} }
String url = ( custom_map == null ? null : custom_map.get( matcher.group( 1 ) ) ); @Override public void onShortCode( @NonNull String part, @NonNull String name ){
if( ! TextUtils.isEmpty( url ) ){ EmojiMap201709.EmojiInfo info = EmojiMap201709.sShortNameToImageId.get( name.toLowerCase().replace( '-', '_' ) );
decode_env.addNetworkEmojiSpan( s.substring( start, end ), url ); if( info != null ){
decode_env.addImageSpan( part, info.image_id );
return;
}
}else if( reHohoemi.matcher( matcher.group( 1 ) ).find() ){ String url = ( custom_map == null ? null : custom_map.get( name ) );
decode_env.addImageSpan( s.substring( start, end ), R.drawable.emoji_hohoemi ); if( ! TextUtils.isEmpty( url ) ){
}else if( reNicoru.matcher( matcher.group( 1 ) ).find() ){ decode_env.addNetworkEmojiSpan( part, url );
decode_env.addImageSpan( s.substring( start, end ), R.drawable.emoji_nicoru ); return;
}else{ }
decode_env.addUnicodeString( s.substring( start, end ) );
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; return decode_env.sb;
} }
public static ArrayList< CharSequence > searchShortCode( Context context, String prefix, int limit ){ // 投稿などの際表示は不要だがショートコード=>Unicodeの解決を行いたい場合がある
ArrayList< CharSequence > dst = new ArrayList<>(); // カスタム絵文字の変換も行わない
if( ! App1.USE_OLD_EMOJIONE ){ public static String decodeShortCode( @NonNull final String s ){
for( String shortCode : EmojiMap201709.sShortNameList ){
if( dst.size() >= limit ) break; final StringBuilder sb = new StringBuilder();
if( ! shortCode.contains( prefix )) continue;
splitShortCode( s, 0, s.length(), new ShortCodeSplitterCallback() {
SpannableStringBuilder sb = new SpannableStringBuilder(); @Override public void onString( @NonNull String part ){
sb.append( ' ' ); sb.append( part );
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 );
} }
@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; return dst;
} }

File diff suppressed because it is too large Load Diff

View File

@ -164,10 +164,10 @@ public class HTMLDecoder {
, LinkClickContext account , LinkClickContext account
, SpannableStringBuilder sb , SpannableStringBuilder sb
, @NonNull DecodeOptions options , @NonNull DecodeOptions options
){ ){
if( TAG_TEXT.equals( tag ) ){ if( TAG_TEXT.equals( tag ) ){
if( options.bDecodeEmoji ){ if( options.bDecodeEmoji ){
sb.append( EmojiDecoder.decodeEmoji( context, decodeEntity( text ) ,options.customEmojiMap ) ); sb.append( EmojiDecoder.decodeEmoji( context, decodeEntity( text ), options.customEmojiMap ) );
}else{ }else{
sb.append( decodeEntity( text ) ); sb.append( decodeEntity( text ) );
} }
@ -188,7 +188,7 @@ public class HTMLDecoder {
sb_tmp.append( "<img/>" ); sb_tmp.append( "<img/>" );
}else{ }else{
for( Node child : child_nodes ){ 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 ) ){ if( end > start && "a".equals( tag ) ){
String href = getHref(); String href = getHref();
if( href != null ){ if( href != null ){
String link_text = sb.subSequence( start,end ).toString(); String link_text = sb.subSequence( start, end ).toString();
MyClickableSpan span = new MyClickableSpan( account, link_text, href, account.findAcctColor( href ),options.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 ); sb.setSpan( span, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );
} }
} }
@ -281,16 +281,12 @@ public class HTMLDecoder {
} }
if( is_media_attachment( list_attachment, href ) ){ if( is_media_attachment( list_attachment, href ) ){
if( App1.USE_OLD_EMOJIONE ){ SpannableStringBuilder sb = new SpannableStringBuilder();
return EmojiDecoder.decodeEmoji( context, ":frame_photo:",null ); sb.append( href );
}else{ int start = 0;
SpannableStringBuilder sb = new SpannableStringBuilder(); int end = sb.length();
sb.append( href ); sb.setSpan( new EmojiImageSpan( context, R.drawable.emj_1f5bc ), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE );
int start = 0; return sb;
int end = sb.length();
sb.setSpan( new EmojiImageSpan( context, R.drawable.emj_1f5bc ), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE );
return sb;
}
} }
try{ try{
@ -319,12 +315,11 @@ public class HTMLDecoder {
return Character.isWhitespace( c ) || c == 0x0a || c == 0x0d; return Character.isWhitespace( c ) || c == 0x0a || c == 0x0d;
} }
public static SpannableStringBuilder decodeHTML( public static SpannableStringBuilder decodeHTML(
Context context Context context
, LinkClickContext account , LinkClickContext account
, String src , String src
,@NonNull DecodeOptions options , @NonNull DecodeOptions options
){ ){
prepareTagInformation(); prepareTagInformation();
SpannableStringBuilder sb = new SpannableStringBuilder(); SpannableStringBuilder sb = new SpannableStringBuilder();
@ -336,7 +331,7 @@ public class HTMLDecoder {
rootNode.addChild( tracker, "" ); rootNode.addChild( tracker, "" );
} }
rootNode.encodeSpan( context, account, sb,options); rootNode.encodeSpan( context, account, sb, options );
int end = sb.length(); int end = sb.length();
while( end > 0 && isWhitespace( sb.charAt( end - 1 ) ) ) -- end; while( end > 0 && isWhitespace( sb.charAt( end - 1 ) ) ) -- end;
if( end < sb.length() ){ if( end < sb.length() ){
@ -376,7 +371,7 @@ public class HTMLDecoder {
// return sb; // 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; if( src_list == null || src_list.isEmpty() ) return null;
SpannableStringBuilder sb = new SpannableStringBuilder(); SpannableStringBuilder sb = new SpannableStringBuilder();
for( TootMention item : src_list ){ for( TootMention item : src_list ){
@ -390,8 +385,8 @@ public class HTMLDecoder {
} }
int end = sb.length(); int end = sb.length();
if( end > start ){ if( end > start ){
String link_text = sb.subSequence( start,end ).toString(); 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 ); 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 ); sb.setSpan( span, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );
} }

View File

@ -1,6 +1,7 @@
package jp.juggler.subwaytooter.util; package jp.juggler.subwaytooter.util;
import android.os.Handler; import android.os.Handler;
import android.os.SystemClock;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.Spannable; import android.text.Spannable;
@ -36,4 +37,25 @@ public class NetworkEmojiInvalidator implements Runnable, NetworkEmojiSpan.Inval
view.postInvalidateOnAnimation(); 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;
}
} }

View File

@ -33,6 +33,7 @@ public class NetworkEmojiSpan extends ReplacementSpan implements CustomEmojiCach
} }
public interface InvalidateCallback { public interface InvalidateCallback {
long getTimeFromStart();
void delayInvalidate( long delay ); void delayInvalidate( long delay );
} }
@ -43,7 +44,7 @@ public class NetworkEmojiSpan extends ReplacementSpan implements CustomEmojiCach
} }
// implements CustomEmojiCache.Callback // implements CustomEmojiCache.Callback
@Override public void onAPNGLoadComplete( APNGFrames b ){ @Override public void onAPNGLoadComplete(){
if( invalidate_callback != null ){ if( invalidate_callback != null ){
invalidate_callback.delayInvalidate( 0 ); invalidate_callback.delayInvalidate( 0 );
} }
@ -73,11 +74,7 @@ public class NetworkEmojiSpan extends ReplacementSpan implements CustomEmojiCach
// フレーム探索結果を格納する構造体を確保しておく // フレーム探索結果を格納する構造体を確保しておく
private final APNGFrames.FindFrameResult mFrameFindResult = new APNGFrames.FindFrameResult(); private final APNGFrames.FindFrameResult mFrameFindResult = new APNGFrames.FindFrameResult();
// 最後に描画した時刻
private long t_last_draw;
// アニメーション開始時刻
private long t_start;
@Override public void draw( @Override public void draw(
@NonNull Canvas canvas @NonNull Canvas canvas
@ -88,19 +85,13 @@ public class NetworkEmojiSpan extends ReplacementSpan implements CustomEmojiCach
if( invalidate_callback == null ) return; if( invalidate_callback == null ) return;
// APNGデータの取得 // APNGデータの取得
APNGFrames frames = App1.custom_emoji_cache.get( url, this ); APNGFrames frames = App1.custom_emoji_cache.get( this, url, this );
if( frames == null ) return; if( frames == null ) return;
long now = SystemClock.elapsedRealtime(); long t = invalidate_callback.getTimeFromStart();
// アニメーション開始時刻を計算する
if( t_start == 0L || now - t_last_draw >= 60000L ){
t_start = now;
}
t_last_draw = now;
// アニメーション開始時刻からの経過時間に応じたフレームを探索 // アニメーション開始時刻からの経過時間に応じたフレームを探索
frames.findFrame( mFrameFindResult, now - t_start ); frames.findFrame( mFrameFindResult, t );
Bitmap b = mFrameFindResult.bitmap; Bitmap b = mFrameFindResult.bitmap;
if( b == null || b.isRecycled() ) return; if( b == null || b.isRecycled() ) return;

View File

@ -18,6 +18,7 @@ import java.util.ArrayList;
import jp.juggler.subwaytooter.R; import jp.juggler.subwaytooter.R;
import jp.juggler.subwaytooter.Styler; import jp.juggler.subwaytooter.Styler;
import jp.juggler.subwaytooter.view.MyEditText;
@SuppressWarnings("WeakerAccess") class PopupAutoCompleteAcct { @SuppressWarnings("WeakerAccess") class PopupAutoCompleteAcct {
final Activity activity; final Activity activity;
@ -61,7 +62,8 @@ import jp.juggler.subwaytooter.Styler;
} }
void setList( void setList(
final int sel_start final MyEditText et
, final int sel_start
, final int sel_end , final int sel_end
, @Nullable ArrayList< CharSequence > acct_list , @Nullable ArrayList< CharSequence > acct_list
, @Nullable String picker_caption , @Nullable String picker_caption
@ -115,11 +117,11 @@ import jp.juggler.subwaytooter.Styler;
} }
v.setOnClickListener( new View.OnClickListener() { v.setOnClickListener( new View.OnClickListener() {
@Override public void onClick( View v ){ @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 ); 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 ) ); s = s.substring( 0, sel_start ) + svInsert + " " + ( sel_end >= s.length() ? "" : s.substring( sel_end ) );
etContent.setText( s ); et.setText( s );
etContent.setSelection( sel_start + svInsert.length() + 1 ); et.setSelection( sel_start + svInsert.length() + 1 );
acct_popup.dismiss(); acct_popup.dismiss();
} }
} ); } );

View File

@ -251,7 +251,7 @@ public class PostHelper implements CustomEmojiLister.Callback, EmojiPicker.Callb
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append( "status=" ); sb.append( "status=" );
sb.append( Uri.encode( content ) ); sb.append( Uri.encode( EmojiDecoder.decodeShortCode(content ) ) );
if( visibility_checked != null ){ if( visibility_checked != null ){
sb.append( "&visibility=" ); sb.append( "&visibility=" );
@ -264,7 +264,7 @@ public class PostHelper implements CustomEmojiLister.Callback, EmojiPicker.Callb
if( spoiler_text != null ){ if( spoiler_text != null ){
sb.append( "&spoiler_text=" ); sb.append( "&spoiler_text=" );
sb.append( Uri.encode( spoiler_text ) ); sb.append( Uri.encode( EmojiDecoder.decodeShortCode(spoiler_text) ) );
} }
if( in_reply_to_id != - 1L ){ if( in_reply_to_id != - 1L ){
@ -289,12 +289,12 @@ public class PostHelper implements CustomEmojiLister.Callback, EmojiPicker.Callb
JSONObject json = new JSONObject(); JSONObject json = new JSONObject();
try{ try{
json.put( "status", content ); json.put( "status", EmojiDecoder.decodeShortCode(content) );
if( visibility_checked != null ){ if( visibility_checked != null ){
json.put( "visibility", visibility_checked ); json.put( "visibility", visibility_checked );
} }
json.put( "sensitive", bNSFW ); 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 ); json.put( "in_reply_to_id", in_reply_to_id == - 1L ? null : in_reply_to_id );
JSONArray array = new JSONArray(); JSONArray array = new JSONArray();
if( attachment_list != null ){ if( attachment_list != null ){
@ -308,7 +308,7 @@ public class PostHelper implements CustomEmojiLister.Callback, EmojiPicker.Callb
json.put( "isEnquete", true ); json.put( "isEnquete", true );
array = new JSONArray(); array = new JSONArray();
for( String item : enquete_items ){ for( String item : enquete_items ){
array.put( item ); array.put( EmojiDecoder.decodeShortCode(item) );
} }
json.put( "enquete_items", array ); json.put( "enquete_items", array );
}catch( JSONException ex ){ }catch( JSONException ex ){
@ -590,7 +590,7 @@ public class PostHelper implements CustomEmojiLister.Callback, EmojiPicker.Callb
if( popup == null || ! popup.isShowing() ){ if( popup == null || ! popup.isShowing() ){
popup = new PopupAutoCompleteAcct( activity, et, formRoot, bMainScreen ); 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() ){ if( popup == null || ! popup.isShowing() ){
popup = new PopupAutoCompleteAcct( activity, et, formRoot, bMainScreen ); 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(){ private void checkEmoji(){
int end = et.getSelectionEnd(); int end = et.getSelectionEnd();
@ -644,12 +646,20 @@ public class PostHelper implements CustomEmojiLister.Callback, EmojiPicker.Callb
return; 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( part.length() == 0 ){
if( popup == null || ! popup.isShowing() ){ if( popup == null || ! popup.isShowing() ){
popup = new PopupAutoCompleteAcct( activity, et, formRoot, bMainScreen ); popup = new PopupAutoCompleteAcct( activity, et, formRoot, bMainScreen );
} }
popup.setList( popup.setList(
last_colon, end et, last_colon, end
, null , null
, picker_caption_emoji , picker_caption_emoji
, open_picker_emoji , open_picker_emoji
@ -684,12 +694,10 @@ public class PostHelper implements CustomEmojiLister.Callback, EmojiPicker.Callb
} }
} }
if( code_list.isEmpty() ){ if( code_list.isEmpty() ){
closeAcctPopup();
}else{
if( popup == null || ! popup.isShowing() ){ if( popup == null || ! popup.isShowing() ){
popup = new PopupAutoCompleteAcct( activity, et, formRoot, bMainScreen ); 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 );
} }
} }
}; };

View File

@ -59,7 +59,7 @@ public class NetworkEmojiView extends View implements CustomEmojiCache.Callback
super.onDraw( canvas ); super.onDraw( canvas );
// APNGデータの取得 // APNGデータの取得
APNGFrames frames = App1.custom_emoji_cache.get( url, this ); APNGFrames frames = App1.custom_emoji_cache.get( this, url, this );
if( frames == null ) return; if( frames == null ) return;
long now = SystemClock.elapsedRealtime(); 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();; postInvalidateOnAnimation();;
} }
} }