From 6178e99ea5a93ccb42622a474f2f9f5f37647558 Mon Sep 17 00:00:00 2001 From: tateisu Date: Sat, 9 Sep 2017 03:46:29 +0900 Subject: [PATCH] =?UTF-8?q?v1.4.3=20-=20=E3=83=88=E3=82=A5=E3=83=BC?= =?UTF-8?q?=E3=83=88=E4=B8=AD=E3=81=AE=E3=83=8F=E3=83=83=E3=82=B7=E3=83=A5?= =?UTF-8?q?=E3=82=BF=E3=82=B0=E3=82=92=E3=82=BF=E3=83=83=E3=83=97=E3=81=97?= =?UTF-8?q?=E3=81=9F=E6=99=82=E3=81=AB=E5=BC=95=E7=94=A8=E3=81=A7=E3=81=8D?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 4 +- .../subwaytooter/ActAccountSetting.java | 2 +- .../juggler/subwaytooter/ActAppSetting.java | 8 +- .../java/jp/juggler/subwaytooter/ActMain.java | 196 ++++++++++++------ .../java/jp/juggler/subwaytooter/ActPost.java | 21 +- .../java/jp/juggler/subwaytooter/ActText.java | 4 +- .../HeaderViewHolderInstance.java | 2 +- .../HeaderViewHolderSearchDesc.java | 2 +- .../subwaytooter/api/TootApiClient.java | 2 + .../subwaytooter/api/entity/NicoEnquete.java | 12 +- .../subwaytooter/api/entity/TootAccount.java | 2 +- .../subwaytooter/api/entity/TootStatus.java | 6 +- .../api_msp/entity/MSPAccount.java | 2 +- .../subwaytooter/api_msp/entity/MSPToot.java | 2 +- .../subwaytooter/dialog/AccountPicker.java | 6 +- .../subwaytooter/dialog/ActionsDialog.java | 30 ++- .../juggler/subwaytooter/table/AcctColor.java | 29 +++ .../subwaytooter/table/SavedAccount.java | 23 ++ .../subwaytooter/util/HTMLDecoder.java | 14 +- .../subwaytooter/util/MyClickableSpan.java | 36 +++- app/src/main/res/values-fr/strings.xml | 6 +- app/src/main/res/values-ja/strings.xml | 4 + app/src/main/res/values/strings.xml | 4 + 23 files changed, 289 insertions(+), 128 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1c2f5fa6..2e688335 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "jp.juggler.subwaytooter" minSdkVersion 21 targetSdkVersion 26 - versionCode 142 - versionName "1.4.2" + versionCode 143 + versionName "1.4.3" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.java b/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.java index cb91ecb8..bcfa2c81 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.java @@ -759,7 +759,7 @@ public class ActAccountSetting extends AppCompatActivity if( src.source != null && src.source.note != null ){ etNote.setText( Emojione.decodeEmoji( this, src.source.note ) ); }else if( src.note != null ){ - etNote.setText( HTMLDecoder.decodeHTML( ActAccountSetting.this, account, src.note, false, false, null ) ); + etNote.setText( HTMLDecoder.decodeHTML( ActAccountSetting.this, account, src.note, false, false, null ,null) ); }else{ etNote.setText( "" ); } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.java b/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.java index db100693..8ddafae5 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.java @@ -1006,13 +1006,7 @@ public class ActAppSetting extends AppCompatActivity if( a.isPseudo() ) continue; list.add( a ); } - Collections.sort( list, new Comparator< SavedAccount >() { - @Override - public int compare( SavedAccount a, SavedAccount b ){ - return String.CASE_INSENSITIVE_ORDER.compare( AcctColor.getNickname( a.acct ), AcctColor.getNickname( b.acct ) ); - - } - } ); + SavedAccount.sort( list ); } @Override public int getCount(){ diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMain.java b/app/src/main/java/jp/juggler/subwaytooter/ActMain.java index 2212beb9..27468f1e 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActMain.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ActMain.java @@ -22,6 +22,7 @@ import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.InputType; import android.text.Layout; +import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.util.DisplayMetrics; @@ -218,7 +219,7 @@ public class ActMain extends AppCompatActivity log.d( "onResume" ); super.onResume(); - MyClickableSpan.link_callback = link_click_listener; + MyClickableSpan.link_callback = new WeakReference<>( link_click_listener ); if( pref.getBoolean( Pref.KEY_DONT_SCREEN_OFF, false ) ){ getWindow().addFlags( WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON ); @@ -345,7 +346,6 @@ public class ActMain extends AppCompatActivity app_state.stream_reader.onPause(); - MyClickableSpan.link_callback = null; super.onPause(); } @@ -1867,7 +1867,7 @@ public class ActMain extends AppCompatActivity task.executeOnExecutor( App1.task_executor ); } - static final Pattern reHashTag = Pattern.compile( "\\Ahttps://([^/]+)/tags/([^?#]+)(?:\\z|\\?)" ); + static final Pattern reUrlHashTag = Pattern.compile( "\\Ahttps://([^/]+)/tags/([^?#]+)(?:\\z|\\?)" ); static final Pattern reUserPage = Pattern.compile( "\\Ahttps://([^/]+)/@([^?#/]+)(?:\\z|\\?)" ); static final Pattern reStatusPage = Pattern.compile( "\\Ahttps://([^/]+)/@([^?#/]+)/(\\d+)(?:\\z|\\?)" ); @@ -1879,7 +1879,7 @@ public class ActMain extends AppCompatActivity // トゥート検索カラムではaccess_infoは何にも紐ついていない // ハッシュタグをアプリ内で開く - Matcher m = reHashTag.matcher( url ); + Matcher m = reUrlHashTag.matcher( url ); if( m.find() ){ // https://mastodon.juggler.jp/tags/%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%82%BF%E3%82%B0 String host = m.group( 1 ); @@ -1918,7 +1918,7 @@ public class ActMain extends AppCompatActivity if( ! noIntercept && access_info != null ){ // ハッシュタグをアプリ内で開く - Matcher m = reHashTag.matcher( url ); + Matcher m = reUrlHashTag.matcher( url ); if( m.find() ){ // https://mastodon.juggler.jp/tags/%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%82%BF%E3%82%B0 String host = m.group( 1 ); @@ -1997,14 +1997,42 @@ public class ActMain extends AppCompatActivity ////////////////////////////////////////////////////////////////////////////////////////////////////// public void openHashTag( int pos, SavedAccount access_info, String tag ){ + while( tag.startsWith( "#" ) ) tag = tag.substring( 1 ); addColumn( pos, access_info, Column.TYPE_HASHTAG, tag ); } // 他インスタンスのハッシュタグの表示 - private void openHashTagOtherInstance( final int pos, final SavedAccount access_info, final String url, final String host, final String tag ){ + private void openHashTagOtherInstance( final int pos, final SavedAccount access_info, final String url, final String host, String tag ){ + while( tag.startsWith( "#" ) ) tag = tag.substring( 1 ); + openHashTagOtherInstance_sub(pos,access_info,url,host,tag); + } + + // 他インスタンスのハッシュタグの表示 + private void openHashTagOtherInstance_sub( final int pos, final SavedAccount access_info, final String url, final String host, final String tag ){ ActionsDialog dialog = new ActionsDialog(); + // 各アカウント + ArrayList< SavedAccount > account_list = SavedAccount.loadAccountList( ActMain.this, log ); + + // ソートする + SavedAccount.sort( account_list ); + + + ArrayList< SavedAccount > list_original = new ArrayList<>( ); + ArrayList< SavedAccount > list_original_pseudo = new ArrayList<>( ); + ArrayList< SavedAccount > list_other = new ArrayList<>( ); + for( SavedAccount a : account_list ){ + log.d("sort? %s",a.acct); + if( ! host.equalsIgnoreCase( a.host ) ){ + list_other.add( a ); + }else if( a.isPseudo() ){ + list_original_pseudo.add( a ); + }else{ + list_original.add( a ); + } + } + // ブラウザで表示する dialog.addAction( getString( R.string.open_web_on_host, host ), new Runnable() { @Override public void run(){ @@ -2012,33 +2040,8 @@ public class ActMain extends AppCompatActivity } } ); - // 各アカウント - ArrayList< SavedAccount > account_list = SavedAccount.loadAccountList( ActMain.this, log ); - - // ソートする - Collections.sort( account_list, new Comparator< SavedAccount >() { - @Override public int compare( SavedAccount a, SavedAccount b ){ - return String.CASE_INSENSITIVE_ORDER.compare( AcctColor.getNickname( a.acct ), AcctColor.getNickname( b.acct ) ); - } - } ); - - // 各アカウントで開く選択肢 - boolean has_host = false; - for( SavedAccount a : account_list ){ - - if( host.equalsIgnoreCase( a.host ) ){ - has_host = true; - } - - final SavedAccount _a = a; - dialog.addAction( getString( R.string.open_in_account, a.acct ), new Runnable() { - @Override public void run(){ - openHashTag( pos, _a, tag ); - } - } ); - } - - if( ! has_host ){ + if( list_original.isEmpty() && list_original_pseudo.isEmpty() ){ + // 疑似アカウントを作成して開く dialog.addAction( getString( R.string.open_in_pseudo_account, "?@" + host ), new Runnable() { @Override public void run(){ SavedAccount sa = addPseudoAccount( host ); @@ -2049,12 +2052,43 @@ public class ActMain extends AppCompatActivity } ); } - dialog.show( this, "#" + tag ); - + // + for( SavedAccount a : list_original ){ + final SavedAccount _a = a; + + dialog.addAction( AcctColor.getStringWithNickname( ActMain.this,R.string.open_in_account,a.acct ), new Runnable() { + @Override public void run(){ + openHashTag( pos, _a, tag ); + } + } ); + } + // + for( SavedAccount a : list_original_pseudo ){ + final SavedAccount _a = a; + dialog.addAction( AcctColor.getStringWithNickname( ActMain.this,R.string.open_in_account,a.acct ), new Runnable() { + @Override public void run(){ + openHashTag( pos, _a, tag ); + } + } ); + } + // + for( SavedAccount a : list_other ){ + final SavedAccount _a = a; + dialog.addAction( AcctColor.getStringWithNickname( ActMain.this,R.string.open_in_account,a.acct ), new Runnable() { + @Override public void run(){ + openHashTag( pos, _a, tag ); + } + } ); + } + + dialog.show( this,"#"+ tag ); } final MyClickableSpan.LinkClickCallback link_click_listener = new MyClickableSpan.LinkClickCallback() { - @Override public void onClickLink( View view, LinkClickContext lcc, String url ){ + @Override public void onClickLink( View view, @NonNull final MyClickableSpan span){ + + View view_orig = view; + Column column = null; while( view != null ){ Object tag = view.getTag(); @@ -2076,7 +2110,65 @@ public class ActMain extends AppCompatActivity } } } - openChromeTab( nextPosition( column ), (SavedAccount) lcc, url, false ); + final int pos = nextPosition( column ); + + // ハッシュタグはいきなり開くのではなくメニューがある + Matcher m = reUrlHashTag.matcher( span.url ); + if( m.find() ){ + // https://mastodon.juggler.jp/tags/%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%82%BF%E3%82%B0 + final String host = m.group( 1 ); + final String tag = span.text.startsWith( "#" )? span.text : "#"+ Uri.decode( m.group( 2 ) ); + + ActionsDialog d = new ActionsDialog() + .addAction( getString( R.string.open_hashtag_column ), new Runnable() { + @Override public void run(){ + openHashTagOtherInstance( pos, (SavedAccount) span.lcc, span.url, host, tag ); + } + } ) + .addAction( getString( R.string.quote_hashtag_of ,tag ), new Runnable() { + @Override public void run(){ + openPost( tag + " "); + } + } ); + + final ArrayList tag_list = new ArrayList<>( ); + try{ + //noinspection ConstantConditions + CharSequence cs = ((TextView)view_orig).getText(); + if( cs instanceof Spannable){ + Spannable content = (Spannable) cs; + for( MyClickableSpan s : content.getSpans( 0,content.length(), MyClickableSpan.class ) ){ + m = reUrlHashTag.matcher( s.url ); + if( m.find() ){ + String s_tag = s.text.startsWith( "#" )? s.text : "#"+Uri.decode( m.group( 2 ) ); + tag_list.add( s_tag ); + } + } + } + }catch(Throwable ex){ + log.trace(ex); + } + if(tag_list.size() > 1 ){ + StringBuilder sb = new StringBuilder( ); + for( String s : tag_list){ + if( sb.length() > 0 ) sb.append(' '); + sb.append( s ); + } + final String tag_all = sb.toString(); + d.addAction( getString( R.string.quote_all_hashtag_of ,tag_all), new Runnable() { + @Override public void run(){ + openPost( tag_all +" "); + } + } ); + } + + d.show(ActMain.this,tag); + return; + } + + + + openChromeTab( pos, (SavedAccount) span.lcc, span.url, false ); } }; @@ -2800,14 +2892,10 @@ public class ActMain extends AppCompatActivity } // ローカルアカウント - Collections.sort( local_account_list, new Comparator< SavedAccount >() { - @Override public int compare( SavedAccount a, SavedAccount b ){ - return String.CASE_INSENSITIVE_ORDER.compare( AcctColor.getNickname( a.acct ), AcctColor.getNickname( b.acct ) ); - } - } ); + SavedAccount.sort( local_account_list ); for( SavedAccount a : local_account_list ){ final SavedAccount _a = a; - dialog.addAction( getString( R.string.open_in_account, AcctColor.getNickname( a.acct ) ), new Runnable() { + dialog.addAction( AcctColor.getStringWithNickname( ActMain.this,R.string.open_in_account,a.acct ), new Runnable() { @Override public void run(){ openStatusLocal( pos, _a, status_id_original ); } @@ -2815,14 +2903,10 @@ public class ActMain extends AppCompatActivity } // アクセスしたアカウント - Collections.sort( access_account_list, new Comparator< SavedAccount >() { - @Override public int compare( SavedAccount a, SavedAccount b ){ - return String.CASE_INSENSITIVE_ORDER.compare( AcctColor.getNickname( a.acct ), AcctColor.getNickname( b.acct ) ); - } - } ); + SavedAccount.sort( access_account_list ); for( SavedAccount a : access_account_list ){ final SavedAccount _a = a; - dialog.addAction( getString( R.string.open_in_account, AcctColor.getNickname( a.acct ) ), new Runnable() { + dialog.addAction( AcctColor.getStringWithNickname( ActMain.this,R.string.open_in_account,a.acct ), new Runnable() { @Override public void run(){ openStatusLocal( pos, _a, status_id_access ); } @@ -2830,14 +2914,10 @@ public class ActMain extends AppCompatActivity } // その他の実アカウント - Collections.sort( other_account_list, new Comparator< SavedAccount >() { - @Override public int compare( SavedAccount a, SavedAccount b ){ - return String.CASE_INSENSITIVE_ORDER.compare( AcctColor.getNickname( a.acct ), AcctColor.getNickname( b.acct ) ); - } - } ); + SavedAccount.sort( other_account_list); for( SavedAccount a : other_account_list ){ final SavedAccount _a = a; - dialog.addAction( getString( R.string.open_in_account, AcctColor.getNickname( a.acct ) ), new Runnable() { + dialog.addAction( AcctColor.getStringWithNickname( ActMain.this,R.string.open_in_account,a.acct ), new Runnable() { @Override public void run(){ openStatusRemote( pos, _a, url ); } @@ -4075,11 +4155,7 @@ public class ActMain extends AppCompatActivity dst.add( a ); } } - Collections.sort( dst, new Comparator< SavedAccount >() { - @Override public int compare( SavedAccount a, SavedAccount b ){ - return String.CASE_INSENSITIVE_ORDER.compare( AcctColor.getNickname( a.acct ), AcctColor.getNickname( b.acct ) ); - } - } ); + SavedAccount.sort( dst ); return dst; } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPost.java b/app/src/main/java/jp/juggler/subwaytooter/ActPost.java index 1d765b4f..c578e195 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActPost.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ActPost.java @@ -51,6 +51,7 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -235,12 +236,11 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener, @Override protected void onResume(){ super.onResume(); - MyClickableSpan.link_callback = link_click_listener; + MyClickableSpan.link_callback = new WeakReference<>( link_click_listener ); } @Override protected void onPause(){ super.onPause(); - MyClickableSpan.link_callback = null; // 編集中にホーム画面を押したり他アプリに移動する場合は下書きを保存する // やや過剰な気がするが、自アプリに戻ってくるときにランチャーからアイコンタップされると @@ -619,13 +619,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener, ivReply = findViewById( R.id.ivReply ); account_list = SavedAccount.loadAccountList( ActPost.this, log ); - Collections.sort( account_list, new Comparator< SavedAccount >() { - @Override - public int compare( SavedAccount a, SavedAccount b ){ - return String.CASE_INSENSITIVE_ORDER.compare( AcctColor.getNickname( a.acct ), AcctColor.getNickname( b.acct ) ); - - } - } ); + SavedAccount.sort( account_list); btnAccount.setOnClickListener( this ); btnVisibility.setOnClickListener( this ); @@ -1494,7 +1488,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener, llReply.setVisibility( View.GONE ); }else{ llReply.setVisibility( View.VISIBLE ); - tvReplyTo.setText( HTMLDecoder.decodeHTML( ActPost.this, account, in_reply_to_text, true, true, null ) ); + tvReplyTo.setText( HTMLDecoder.decodeHTML( ActPost.this, account, in_reply_to_text, true, true, null ,null) ); ivReply.setImageUrl( pref, 16f, in_reply_to_image ); } } @@ -1899,7 +1893,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener, return null; } }; - CharSequence sv = HTMLDecoder.decodeHTML( ActPost.this, lcc, text, false, false, null ); + CharSequence sv = HTMLDecoder.decodeHTML( ActPost.this, lcc, text, false, false, null ,null); tvText.setText( sv ); tvText.setMovementMethod( LinkMovementMethod.getInstance() ); @@ -1921,11 +1915,10 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener, } final MyClickableSpan.LinkClickCallback link_click_listener = new MyClickableSpan.LinkClickCallback() { - @Override public void onClickLink( View view, LinkClickContext lcc, String url ){ - if( url == null ) return; + @Override public void onClickLink( View view, @NonNull MyClickableSpan span ){ // ブラウザで開く try{ - Intent intent = new Intent( Intent.ACTION_VIEW, Uri.parse( url ) ); + Intent intent = new Intent( Intent.ACTION_VIEW, Uri.parse( span.url ) ); startActivity( intent ); }catch( Throwable ex ){ log.trace( ex ); diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActText.java b/app/src/main/java/jp/juggler/subwaytooter/ActText.java index d934968e..8471a058 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActText.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ActText.java @@ -71,7 +71,7 @@ public class ActText extends AppCompatActivity implements View.OnClickListener { addAfterLine( sb, "\n" ); intent.putExtra( EXTRA_CONTENT_START, sb.length() ); - sb.append( HTMLDecoder.decodeHTML( context,access_info, status.content, false, false, null ) ); + sb.append( HTMLDecoder.decodeHTML( context,access_info, status.content, false, false, null ,null) ); intent.putExtra( EXTRA_CONTENT_END, sb.length() ); if( status instanceof TootStatus ){ @@ -121,7 +121,7 @@ public class ActText extends AppCompatActivity implements View.OnClickListener { addAfterLine( sb, "\n" ); - sb.append( HTMLDecoder.decodeHTML( context, access_info, ( who.note != null ? who.note : null ), false, false, null ) ); + sb.append( HTMLDecoder.decodeHTML( context, access_info, ( who.note != null ? who.note : null ), false, false, null ,null) ); addAfterLine( sb, "\n" ); diff --git a/app/src/main/java/jp/juggler/subwaytooter/HeaderViewHolderInstance.java b/app/src/main/java/jp/juggler/subwaytooter/HeaderViewHolderInstance.java index 2aaa12f1..bfcbaba7 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/HeaderViewHolderInstance.java +++ b/app/src/main/java/jp/juggler/subwaytooter/HeaderViewHolderInstance.java @@ -92,7 +92,7 @@ class HeaderViewHolderInstance extends HeaderViewHolderBase implements View.OnCl btnEmail.setText( supplyEmpty( instance.email ) ); btnEmail.setEnabled( ! TextUtils.isEmpty( instance.email ) ); - SpannableStringBuilder sb = HTMLDecoder.decodeHTML( activity, access_info, "

"+supplyEmpty( instance.description )+"

", false, true, null ); + SpannableStringBuilder sb = HTMLDecoder.decodeHTML( activity, access_info, "

"+supplyEmpty( instance.description )+"

", false, true, null ,null); int previous_br_count =0; for(int i=0;i() { - @Override public int compare( SavedAccount a, SavedAccount b ){ - return String.CASE_INSENSITIVE_ORDER.compare( AcctColor.getNickname( a.acct ), AcctColor.getNickname( b.acct ) ); - } - } ); + SavedAccount.sort( account_list ); @SuppressLint("InflateParams") View viewRoot = activity.getLayoutInflater().inflate( R.layout.dlg_account_picker, null, false ); diff --git a/app/src/main/java/jp/juggler/subwaytooter/dialog/ActionsDialog.java b/app/src/main/java/jp/juggler/subwaytooter/dialog/ActionsDialog.java index 5e3c07f2..eae6a209 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/dialog/ActionsDialog.java +++ b/app/src/main/java/jp/juggler/subwaytooter/dialog/ActionsDialog.java @@ -2,6 +2,8 @@ package jp.juggler.subwaytooter.dialog; import android.content.Context; import android.content.DialogInterface; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v7.app.AlertDialog; import android.text.TextUtils; @@ -11,22 +13,27 @@ import jp.juggler.subwaytooter.R; public class ActionsDialog { - static class Action { - String caption; - Runnable r; + private static class Action { + @NonNull final CharSequence caption; + @NonNull final Runnable r; + + Action( @NonNull CharSequence caption, @NonNull Runnable r ){ + this.caption = caption; + this.r = r; + } } - final ArrayList< Action > action_list = new ArrayList<>(); + private final ArrayList< Action > action_list = new ArrayList<>(); - public void addAction( String caption, Runnable r ){ - Action action = new Action(); - action.caption = caption; - action.r = r; - action_list.add( action ); + public ActionsDialog addAction( @NonNull CharSequence caption, @NonNull Runnable r ){ + + action_list.add( new Action( caption, r ) ); + + return this; } - public void show( Context context, String title ){ - String[] caption_list = new String[ action_list.size() ]; + public ActionsDialog show( @NonNull Context context, @Nullable CharSequence title ){ + CharSequence[] caption_list = new CharSequence[ action_list.size() ]; for( int i = 0, ie = caption_list.length ; i < ie ; ++ i ){ caption_list[ i ] = action_list.get( i ).caption; } @@ -44,5 +51,6 @@ public class ActionsDialog { b.show(); + return this; } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/table/AcctColor.java b/app/src/main/java/jp/juggler/subwaytooter/table/AcctColor.java index 38ac1bc4..9378a50e 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/table/AcctColor.java +++ b/app/src/main/java/jp/juggler/subwaytooter/table/AcctColor.java @@ -1,18 +1,24 @@ package jp.juggler.subwaytooter.table; import android.content.ContentValues; +import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import jp.juggler.subwaytooter.App1; import jp.juggler.subwaytooter.AppDataExporter; +import jp.juggler.subwaytooter.Styler; import jp.juggler.subwaytooter.util.LogCategory; import jp.juggler.subwaytooter.util.Utils; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.util.LruCache; +import android.text.Spannable; +import android.text.SpannableStringBuilder; import android.text.TextUtils; +import android.text.style.BackgroundColorSpan; +import android.text.style.ForegroundColorSpan; import android.util.JsonWriter; import java.io.IOException; @@ -187,4 +193,27 @@ public class AcctColor { mMemoryCache.evictAll (); } + private static final char CHAR_REPLACE = 0x328A; + + @NonNull public static CharSequence getStringWithNickname( @NonNull Context context, @NonNull int string_id , @NonNull String acct ){ + AcctColor ac = load( acct ); + if( ac == null ) return context.getString( string_id,acct ); + String name = ! TextUtils.isEmpty( ac.nickname ) ? Utils.sanitizeBDI( ac.nickname ) : acct ; + int color_fg = hasColorForeground( ac ) ? ac.color_fg : Styler.getAttributeColor( context, android.R.attr.textColorPrimary ); + int color_bg = hasColorBackground( ac ) ? ac.color_bg : 0; + SpannableStringBuilder sb = new SpannableStringBuilder( context.getString( string_id,new String(new char[]{CHAR_REPLACE})) ); + for(int i=sb.length()-1;i>=0;--i){ + char c = sb.charAt( i ); + if( c != CHAR_REPLACE) continue; + sb.replace( i,i+1,name ); + if( ac.color_fg != 0){ + sb.setSpan( new ForegroundColorSpan( ac.color_fg ), i, i + name.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ); + } + if( ac.color_bg != 0){ + sb.setSpan( new BackgroundColorSpan( ac.color_bg ), i, i + name.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ); + } + } + return sb; + } + } diff --git a/app/src/main/java/jp/juggler/subwaytooter/table/SavedAccount.java b/app/src/main/java/jp/juggler/subwaytooter/table/SavedAccount.java index 40c5a917..933db5e7 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/table/SavedAccount.java +++ b/app/src/main/java/jp/juggler/subwaytooter/table/SavedAccount.java @@ -13,6 +13,8 @@ import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -622,4 +624,25 @@ public class SavedAccount extends TootAccount implements LinkClickContext { return true; } + private static final Comparator< SavedAccount > account_comparator = new Comparator< SavedAccount >() { + @Override public int compare( SavedAccount a, SavedAccount b ){ + int i; + + // NA > !NA + i = (a.isNA()? 1:0 ) - (b.isNA()? 1:0); + if(i!=0) return i; + + // pseudo > real + i = (a.isPseudo()? 1:0 ) - (b.isPseudo()? 1:0); + if(i!=0) return i; + + String sa = AcctColor.getNickname( a.acct ); + String sb = AcctColor.getNickname( b.acct ); + return sa.compareToIgnoreCase( sb ); + } + }; + + public static void sort( ArrayList< SavedAccount > account_list ){ + Collections.sort( account_list, account_comparator ); + } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.java b/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.java index 27a6bf57..9ab05da4 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.java +++ b/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.java @@ -163,6 +163,7 @@ public class HTMLDecoder { , boolean bShort , boolean bDecodeEmoji , @Nullable TootAttachment.List list_attachment + , @Nullable Object link_tag ){ if( TAG_TEXT.equals( tag ) ){ if( bDecodeEmoji ){ @@ -187,7 +188,7 @@ public class HTMLDecoder { sb_tmp.append( "" ); }else{ for( Node child : child_nodes ){ - child.encodeSpan( context, account, sb_tmp, bShort, bDecodeEmoji, list_attachment ); + child.encodeSpan( context, account, sb_tmp, bShort, bDecodeEmoji, list_attachment ,link_tag); } } @@ -204,7 +205,8 @@ public class HTMLDecoder { if( end > start && "a".equals( tag ) ){ String href = getHref(); if( href != null ){ - MyClickableSpan span = new MyClickableSpan( account, href, account.findAcctColor( href ) ); + String link_text = sb.subSequence( start,end ).toString(); + MyClickableSpan span = new MyClickableSpan( account, link_text, href, account.findAcctColor( href ),link_tag ); sb.setSpan( span, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ); } } @@ -315,6 +317,7 @@ public class HTMLDecoder { , boolean bShort , boolean bDecodeEmoji , @Nullable TootAttachment.List list_attachment + , @Nullable Object link_tag ){ prepareTagInformation(); SpannableStringBuilder sb = new SpannableStringBuilder(); @@ -326,7 +329,7 @@ public class HTMLDecoder { rootNode.addChild( tracker, "" ); } - rootNode.encodeSpan( context, account, sb, bShort, bDecodeEmoji, list_attachment ); + rootNode.encodeSpan( context, account, sb, bShort, bDecodeEmoji, list_attachment , link_tag ); int end = sb.length(); while( end > 0 && isWhitespace( sb.charAt( end - 1 ) ) ) -- end; if( end < sb.length() ){ @@ -366,7 +369,7 @@ public class HTMLDecoder { // return sb; // } - public static Spannable decodeMentions( final SavedAccount access_info, TootMention.List src_list ){ + 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 ){ @@ -380,7 +383,8 @@ public class HTMLDecoder { } int end = sb.length(); if( end > start ){ - MyClickableSpan span = new MyClickableSpan( access_info, item.url, access_info.findAcctColor( item.url ) ); + 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 ); } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/MyClickableSpan.java b/app/src/main/java/jp/juggler/subwaytooter/util/MyClickableSpan.java index 54f15966..f59e4446 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/MyClickableSpan.java +++ b/app/src/main/java/jp/juggler/subwaytooter/util/MyClickableSpan.java @@ -1,27 +1,42 @@ package jp.juggler.subwaytooter.util; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.text.TextPaint; import android.text.style.ClickableSpan; import android.view.View; +import java.lang.ref.WeakReference; + import jp.juggler.subwaytooter.table.AcctColor; public class MyClickableSpan extends ClickableSpan { public interface LinkClickCallback { - void onClickLink( View widget,LinkClickContext account, String url ); + void onClickLink( View widget,@NonNull MyClickableSpan span); } - public static LinkClickCallback link_callback; + public static WeakReference link_callback = null; - public LinkClickContext account; - public String url; - private int color_fg; - private int color_bg; + public @NonNull LinkClickContext lcc; + public @NonNull String text; + public @NonNull String url; + public @Nullable Object tag; + public int color_fg; + public int color_bg; - MyClickableSpan( LinkClickContext account, String url, AcctColor ac ){ - this.account = account; + MyClickableSpan( + @NonNull LinkClickContext lcc + ,@NonNull String text + ,@NonNull String url + , AcctColor ac + ,@Nullable Object tag + ){ + this.lcc = lcc; + this.text = text; this.url = url; + this.tag = tag; + if( ac != null ){ this.color_fg = ac.color_fg; this.color_bg = ac.color_bg; @@ -29,8 +44,9 @@ public class MyClickableSpan extends ClickableSpan { } @Override public void onClick( View widget ){ - if( link_callback != null ){ - link_callback.onClickLink( widget,this.account, url ); + LinkClickCallback cb = (link_callback == null ? null : link_callback.get() ); + if( cb != null ){ + cb.onClickLink( widget, this ); } } diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 03ea692f..3c1d7a2e 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -473,8 +473,12 @@ Loading notification… Server Timeout If it\'s dead instance, please remove account on that server. + Open hashtag column + Quote hashtag \"%1$s\" + Quote all hashtags \"%1$s\" + Parsing response… - + diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index d36df853..6e022f43 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -760,4 +760,8 @@ 通知の取得中… サーバータイムアウト もし死んだインスタンスなら、そのサーバ上のアカウントを除去してください + ハッシュタグのカラムを開く + ハッシュタグ \"%1$s\" を引用 + 全てのハッシュタグ \"%1$s\" を引用 + 応答の解析中… diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b2e03fef..3a8c2d79 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -467,5 +467,9 @@ Loading notification… Server Timeout If it\'s dead instance, please remove account on that server. + Open hashtag column + Quote hashtag \"%1$s\" + Quote all hashtags \"%1$s\" + Parsing response…