- トゥート中のハッシュタグをタップした時に引用できるようにした
This commit is contained in:
tateisu 2017-09-09 03:46:29 +09:00
parent 40e10d1b0f
commit 6178e99ea5
23 changed files with 289 additions and 128 deletions

View File

@ -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"
}

View File

@ -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( "" );
}

View File

@ -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(){

View File

@ -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<String> 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;
}

View File

@ -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 );

View File

@ -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" );

View File

@ -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, "<p>"+supplyEmpty( instance.description )+"</p>", false, true, null );
SpannableStringBuilder sb = HTMLDecoder.decodeHTML( activity, access_info, "<p>"+supplyEmpty( instance.description )+"</p>", false, true, null ,null);
int previous_br_count =0;
for(int i=0;i<sb.length();++i){
char c = sb.charAt( i );

View File

@ -37,7 +37,7 @@ class HeaderViewHolderSearchDesc extends HeaderViewHolderBase {
}
} );
CharSequence sv = HTMLDecoder.decodeHTML( activity, access_info, html, false, true, null );
CharSequence sv = HTMLDecoder.decodeHTML( activity, access_info, html, false, true, null ,null);
TextView tvSearchDesc = viewRoot.findViewById( R.id.tvSearchDesc );
tvSearchDesc.setVisibility( View.VISIBLE );

View File

@ -134,6 +134,8 @@ public class TootApiClient {
}
try{
callback.publishApiProgress( context.getString( R.string.parsing_response ) );
//noinspection ConstantConditions
String json = response.body().string();

View File

@ -56,7 +56,15 @@ public class NicoEnquete {
long time_start;
long status_id;
public static NicoEnquete parse( @NonNull Context context, @NonNull SavedAccount access_info, @Nullable TootAttachment.List list_attachment, @Nullable String sv, long status_id, long time_start ){
public static NicoEnquete parse(
@NonNull Context context,
@NonNull SavedAccount access_info,
@Nullable TootAttachment.List list_attachment,
@Nullable String sv,
long status_id,
long time_start ,
@NonNull TootStatusLike status
){
try{
if( sv != null ){
JSONObject src = new JSONObject( sv );
@ -69,7 +77,7 @@ public class NicoEnquete {
dst.time_start = time_start;
dst.status_id = status_id;
if( dst.question != null ){
dst.question = HTMLDecoder.decodeHTML( context, access_info, dst.question.toString(), true, true, list_attachment );
dst.question = HTMLDecoder.decodeHTML( context, access_info, dst.question.toString(), true, true, list_attachment ,status);
}
if( dst.items != null ){
for( int i = 0, ie = dst.items.size() ; i < ie ; ++ i ){

View File

@ -126,7 +126,7 @@ public class TootAccount {
dst.statuses_count = src.optLong( "statuses_count" );
dst.note = Utils.optStringX( src, "note" );
dst.decoded_note = HTMLDecoder.decodeHTML( context, account, ( dst.note != null ? dst.note : null ), true, true, null );
dst.decoded_note = HTMLDecoder.decodeHTML( context, account, ( dst.note != null ? dst.note : null ), true, true, null ,null );
dst.url = Utils.optStringX( src, "url" );
dst.avatar = Utils.optStringX( src, "avatar" ); // "https:\/\/mastodon.juggler.jp\/system\/accounts\/avatars\/000\/000\/148\/original\/0a468974fac5a448.PNG?1492081886",

View File

@ -142,11 +142,11 @@ public class TootStatus extends TootStatusLike {
status.language = Utils.optStringX( src, "language" );
status.time_created_at = parseTime( status.created_at );
status.decoded_content = HTMLDecoder.decodeHTML( context, access_info, status.content, true, true, status.media_attachments );
status.decoded_content = HTMLDecoder.decodeHTML( context, access_info, status.content, true, true, status.media_attachments ,status);
// status.decoded_tags = HTMLDecoder.decodeTags( account,status.tags );
status.decoded_mentions = HTMLDecoder.decodeMentions( access_info, status.mentions );
status.decoded_mentions = HTMLDecoder.decodeMentions( access_info, status.mentions ,status);
status.enquete = NicoEnquete.parse( context,access_info , status.media_attachments , Utils.optStringX( src, "enquete"),status.id,status.time_created_at );
status.enquete = NicoEnquete.parse( context,access_info , status.media_attachments , Utils.optStringX( src, "enquete"),status.id,status.time_created_at,status );
return status;
}catch( Throwable ex ){

View File

@ -37,7 +37,7 @@ public class MSPAccount extends TootAccount {
dst.id = src.optLong( "id" );
dst.note = Utils.optStringX( src, "note" );
dst.decoded_note = HTMLDecoder.decodeHTML( context, access_info, ( dst.note != null ? dst.note : null ), true, true, null );
dst.decoded_note = HTMLDecoder.decodeHTML( context, access_info, ( dst.note != null ? dst.note : null ), true, true, null ,null);
if( TextUtils.isEmpty( dst.url ) ){
log.e( "parseAccount: missing url" );

View File

@ -80,7 +80,7 @@ public class MSPToot extends TootStatusLike {
dst.setSpoilerText( context, Utils.optStringX( src, "spoiler_text" ) );
dst.content = Utils.optStringX( src, "content" );
dst.decoded_content = HTMLDecoder.decodeHTML( context, access_info, dst.content, true, true, null );
dst.decoded_content = HTMLDecoder.decodeHTML( context, access_info, dst.content, true, true, null ,dst);
return dst;
}

View File

@ -100,11 +100,7 @@ public class AccountPicker {
return;
}
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 );
@SuppressLint("InflateParams") View viewRoot = activity.getLayoutInflater().inflate( R.layout.dlg_account_picker, null, false );

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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 );
}
}

View File

@ -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( "<img/>" );
}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 );
}

View File

@ -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<LinkClickCallback> 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 );
}
}

View File

@ -473,8 +473,12 @@
<string name="loading_notification_title">Loading notification…</string>
<string name="error_notification_title">Server Timeout</string>
<string name="error_notification_summary">If it\'s dead instance, please remove account on that server.</string>
<string name="open_hashtag_column">Open hashtag column</string>
<string name="quote_hashtag_of">Quote hashtag \"%1$s\"</string>
<string name="quote_all_hashtag_of">Quote all hashtags \"%1$s\"</string>
<string name="parsing_response">Parsing response…</string>
<!--<string name="abc_action_bar_home_description">Revenir à l\'accueil</string>-->
<!--<string name="abc_action_bar_home_description">Revenir à l\'accueil</string>-->
<!--<string name="abc_action_bar_home_description_format">%1$s, %2$s</string>-->
<!--<string name="abc_action_bar_home_subtitle_description_format">%1$s, %2$s, %3$s</string>-->
<!--<string name="abc_action_bar_up_description">Revenir en haut de la page</string>-->

View File

@ -760,4 +760,8 @@
<string name="loading_notification_title">通知の取得中…</string>
<string name="error_notification_title">サーバータイムアウト</string>
<string name="error_notification_summary">もし死んだインスタンスなら、そのサーバ上のアカウントを除去してください</string>
<string name="open_hashtag_column">ハッシュタグのカラムを開く</string>
<string name="quote_hashtag_of">ハッシュタグ \"%1$s\" を引用</string>
<string name="quote_all_hashtag_of">全てのハッシュタグ \"%1$s\" を引用</string>
<string name="parsing_response">応答の解析中…</string>
</resources>

View File

@ -467,5 +467,9 @@
<string name="loading_notification_title">Loading notification…</string>
<string name="error_notification_title">Server Timeout</string>
<string name="error_notification_summary">If it\'s dead instance, please remove account on that server.</string>
<string name="open_hashtag_column">Open hashtag column</string>
<string name="quote_hashtag_of">Quote hashtag \"%1$s\"</string>
<string name="quote_all_hashtag_of">Quote all hashtags \"%1$s\"</string>
<string name="parsing_response">Parsing response…</string>
</resources>