ステータスのリンクタップでハッシュタグのカラムを表示できるようにした

This commit is contained in:
tateisu 2017-04-24 03:05:29 +09:00
parent ad5ce44f9e
commit 67b3df2e5e
15 changed files with 351 additions and 123 deletions

View File

@ -219,7 +219,7 @@ public class ActAccountSetting extends AppCompatActivity implements View.OnClick
TootApiResult result = api_client.request( "/api/v1/accounts/verify_credentials" );
if( result != null && result.object != null ){
TootAccount ta = TootAccount.parse( log, result.object );
TootAccount ta = TootAccount.parse( log, account,result.object );
if( ! ta.username.equals( account.username ) ){
return new TootApiResult( getString( R.string.user_name_not_match ) );

View File

@ -33,7 +33,11 @@ import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jp.juggler.subwaytooter.api.TootApiClient;
import jp.juggler.subwaytooter.api.TootApiResult;
@ -44,6 +48,7 @@ import jp.juggler.subwaytooter.dialog.LoginForm;
import jp.juggler.subwaytooter.dialog.ReportForm;
import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.util.HTMLDecoder;
import jp.juggler.subwaytooter.util.LinkClickContext;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.Utils;
import okhttp3.Request;
@ -318,7 +323,10 @@ public class ActMain extends AppCompatActivity
TootApiResult result = api_client.request( "/api/v1/accounts/verify_credentials" );
if( result != null && result.object != null ){
TootAccount ta = TootAccount.parse( log, result.object );
// taは使い捨てなので生成に使うLinkClickContextはダミーで問題ない
LinkClickContext lcc = new LinkClickContext() {
};
TootAccount ta = TootAccount.parse( log, lcc,result.object );
String user = ta.username + "@" + instance;
this.row_id = SavedAccount.insert( instance, user, result.object, result.token_info );
}
@ -386,56 +394,82 @@ public class ActMain extends AppCompatActivity
//////////////////////////////////////////////////////////////
// カラム追加系
public void addColumn( SavedAccount ai, int type, long who, long status_id ){
public void addColumn( SavedAccount ai, int type, Object... params ){
// 既に同じカラムがあればそこに移動する
for( Column column : pager_adapter.column_list ){
if( ai.user.equals( column.access_info.user )
&& column.type == type
&& column.who_id == who
&& column.status_id == status_id
){
if( column.isSameSpec( ai, type, params ) ){
pager.setCurrentItem( pager_adapter.column_list.indexOf( column ), true );
return;
}
}
//
llEmpty.setVisibility( View.GONE );
//
Column col = new Column( ActMain.this, ai, type, who, status_id );
Column col = new Column( ActMain.this, ai, type, params );
int idx = pager_adapter.addColumn( pager, col );
pager.setCurrentItem( idx, true );
}
private void onAccountUpdated( SavedAccount data ){
Utils.showToast( this, false, R.string.account_confirmed );
addColumn( data, Column.TYPE_TL_HOME, data.id, 0L );
addColumn( data, Column.TYPE_TL_HOME );
}
void performOpenUser( SavedAccount access_info, TootAccount user ){
addColumn( access_info, Column.TYPE_TL_STATUSES, user.id, 0L );
addColumn( access_info, Column.TYPE_TL_STATUSES, user.id );
}
public void performConversation( SavedAccount access_info, TootStatus status ){
addColumn( access_info, Column.TYPE_TL_CONVERSATION, access_info.id, status.id );
addColumn( access_info, Column.TYPE_TL_CONVERSATION, status.id );
}
private void performAddTimeline( final int type ){
AccountPicker.pick( this, new AccountPicker.AccountPickerCallback() {
@Override
public void onAccountPicked( SavedAccount ai ){
addColumn( ai, type, ai.id, 0L );
addColumn( ai, type, ai.id );
}
} );
}
//////////////////////////////////////////////////////////////
public void openBrowser( String url ){
openChromeTab( url );
public void openHashTag( SavedAccount access_info, String tag ){
addColumn( access_info, Column.TYPE_TL_HASHTAG, tag );
}
public void openChromeTab( String url ){
//////////////////////////////////////////////////////////////
public void openBrowser(SavedAccount account, String url ){
openChromeTab( account,url,false );
}
Pattern reHashTag = Pattern.compile( "\\Ahttps://([^/]+)/tags/([^?#]+)\\z" );
public void openChromeTab( SavedAccount account, String url ,boolean noIntercept ){
try{
log.d("openChromeTab url=%s",url);
if(!noIntercept){
// ハッシュタグをアプリ内で開く
Matcher m = reHashTag.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 );
String tag = m.group( 2 );
if( tag.length() > 0 ){
if( host.equalsIgnoreCase( account.host ) ){
openHashTag( account, Uri.decode( tag ) );
return;
}else{
openHashTagOtherInstance( account,url, host,Uri.decode(tag) );
return;
}
}
}
}
// ビルダーを使って表示方法を指定する
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
builder.setToolbarColor( ContextCompat.getColor( this, R.color.colorPrimary ) ).setShowTitle( true );
@ -450,10 +484,90 @@ public class ActMain extends AppCompatActivity
}
}
static class Action{
String caption;
Runnable runnable;
}
// 他インスタンスのハッシュタグの表示
private void openHashTagOtherInstance( final SavedAccount access_info,final String url,String host, final String tag ){
final ArrayList<Action> action_list = new ArrayList<>();
ArrayList<SavedAccount> account_list = new ArrayList<>( );
for(SavedAccount a : SavedAccount.loadAccountList( log )){
if( a.host.equalsIgnoreCase( host )){
account_list.add(a);
}
}
Collections.sort( account_list,new Comparator< SavedAccount >() {
@Override
public int compare( SavedAccount a, SavedAccount b ){
return String.CASE_INSENSITIVE_ORDER.compare( a.getFullAcct( a ), b.getFullAcct( b ) );
}
} );
for( SavedAccount a : account_list ){
Action action = new Action();
action.caption = getString(R.string.open_in_account,a.user);
final SavedAccount _a = a;
action.runnable = new Runnable() {
@Override
public void run(){
openHashTag( _a,tag );
}
};
action_list.add( action);
}
if( account_list.isEmpty() ){
// TODO ログインなしアカウントで開く選択肢
}
// カラムのアカウントで開く
{
Action action = new Action();
action.caption = getString(R.string.open_in_account,access_info.user);
final SavedAccount _a = access_info;
action.runnable = new Runnable() {
@Override
public void run(){
openHashTag( _a,tag );
}
};
action_list.add( action);
}
// ブラウザで表示する
{
Action action = new Action();
action.caption = getString(R.string.open_web_on_host,host);
action.runnable = new Runnable() {
@Override
public void run(){
openChromeTab( access_info,url,true);
}
};
action_list.add( action);
}
String[] caption_list = new String[action_list.size()];
for(int i=0,ie=caption_list.length;i<ie;++i){
caption_list[i] = action_list.get(i).caption;
}
new AlertDialog.Builder( this )
.setTitle( "#"+tag)
.setNegativeButton( R.string.cancel,null )
.setItems( caption_list, new DialogInterface.OnClickListener() {
@Override
public void onClick( DialogInterface dialog, int which ){
if( which >= 0 && which < action_list.size()){
action_list.get(which).runnable.run();
}
}
})
.show();
}
final HTMLDecoder.LinkClickCallback link_click_listener = new HTMLDecoder.LinkClickCallback() {
@Override
public void onClickLink( String url ){
openChromeTab( url );
public void onClickLink( LinkClickContext lcc,String url ){
openChromeTab( (SavedAccount)lcc,url ,false );
}
};
@ -469,7 +583,7 @@ public class ActMain extends AppCompatActivity
}
public void performMention( SavedAccount account, TootAccount who ){
ActPost.open( this, account.db_id, account.getFullAcct( who ) +" " );
ActPost.open( this, account.db_id, account.getFullAcct( who ) + " " );
}
/////////////////////////////////////////////////////////////////////////
@ -534,7 +648,7 @@ public class ActMain extends AppCompatActivity
)
, request_builder );
if( result.object != null ){
new_status = TootStatus.parse( log, result.object );
new_status = TootStatus.parse( log,account, result.object );
}
return result;
@ -657,7 +771,7 @@ public class ActMain extends AppCompatActivity
// reblog,unreblog のレスポンスは信用ならんのでステータスを再取得する
result = client.request( "/api/v1/statuses/" + status.id );
if( result.object != null ){
new_status = TootStatus.parse( log, result.object );
new_status = TootStatus.parse( log, account,result.object );
}
}
@ -1125,7 +1239,7 @@ public class ActMain extends AppCompatActivity
break;
case 7:
performReport( account, who ,null );
performReport( account, who, null );
break;
}
}

View File

@ -215,7 +215,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener {
sv = intent.getStringExtra( KEY_REPLY_STATUS );
if( sv != null ){
try{
TootStatus repley_status = TootStatus.parse( log, new JSONObject( sv ) );
TootStatus repley_status = TootStatus.parse( log, account,new JSONObject( sv ) );
// CW をリプライ元に合わせる
if( ! TextUtils.isEmpty( repley_status.spoiler_text ) ){
@ -830,7 +830,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener {
TootApiResult result = client.request( "/api/v1/statuses", request_builder );
if( result.object != null ){
status = TootStatus.parse( log, result.object );
status = TootStatus.parse( log, account,result.object );
}
return result;
@ -879,7 +879,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener {
llReply.setVisibility( View.GONE );
}else{
llReply.setVisibility( View.VISIBLE );
tvReplyTo.setText( HTMLDecoder.decodeHTML( in_reply_to_text ) );
tvReplyTo.setText( HTMLDecoder.decodeHTML( account,in_reply_to_text ) );
ivReply.setImageUrl( in_reply_to_image,App1.getImageLoader() );
}
}

View File

@ -37,21 +37,23 @@ public class Column {
static final String KEY_TYPE = "type";
static final String KEY_WHO_ID = "who_id";
static final String KEY_STATUS_ID = "status_id";
static final String KEY_HASHTAG = "hashtag";
static final String KEY_COLUMN_ACCESS = "column_access";
static final String KEY_COLUMN_NAME = "column_name";
static final String KEY_OLD_INDEX = "old_index";
final ActMain activity;
private final ActMain activity;
final SavedAccount access_info;
final int type;
final long who_id;
long who_id;
long status_id;
String hashtag;
int profile_tab = 0;
public int scroll_pos;
public int scroll_y;
int scroll_pos;
int scroll_y;
static final int TYPE_TL_HOME = 1;
static final int TYPE_TL_LOCAL = 2;
@ -61,45 +63,43 @@ public class Column {
static final int TYPE_TL_REPORTS = 6;
static final int TYPE_TL_NOTIFICATIONS = 7;
static final int TYPE_TL_CONVERSATION = 8;
static final int TYPE_TL_HASHTAG = 9;
public Column( ActMain activity, SavedAccount access_info, int type ){
this( activity, access_info, type, access_info.id );
}
public Column( ActMain activity, SavedAccount access_info, int type, long who_id, Object... params ){
Column( ActMain activity, SavedAccount access_info, int type, Object... params ){
this.activity = activity;
this.access_info = access_info;
this.type = type;
this.who_id = who_id;
if( type == TYPE_TL_CONVERSATION ){
if( params==null || params.length < 1 ) throw new IndexOutOfBoundsException( "TYPE_TL_CONVERSATION requires status_id as Long" );
if( !( params[0] instanceof Long ) )throw new IllegalArgumentException( "TYPE_TL_CONVERSATION status_id is not Long" );
status_id = (Long) params[0];
switch(type){
case TYPE_TL_CONVERSATION:
this.status_id = (Long)getParamAt( params,0 );
break;
case TYPE_TL_STATUSES:
this.who_id = (Long)getParamAt( params,0 );
break;
case TYPE_TL_HASHTAG:
this.hashtag = (String)getParamAt( params,0 );
break;
}
startLoading();
}
public Column( ActMain activity, JSONObject src ){
this.activity = activity;
this.access_info = SavedAccount.loadAccount( log, src.optLong( KEY_ACCOUNT_ROW_ID ) );
if( access_info == null ) throw new RuntimeException( "missing account" );
this.type = src.optInt( KEY_TYPE );
this.who_id = src.optLong( KEY_WHO_ID );
this.status_id = src.optLong( KEY_STATUS_ID );
startLoading();
}
final AtomicBoolean is_dispose = new AtomicBoolean();
void dispose(){
is_dispose.set( true );
}
public void encodeJSON( JSONObject item, int old_index ) throws JSONException{
item.put( KEY_ACCOUNT_ROW_ID, access_info.db_id );
item.put( KEY_TYPE, type );
item.put( KEY_WHO_ID, who_id );
item.put( KEY_STATUS_ID,status_id);
switch(type){
case TYPE_TL_CONVERSATION:
item.put( KEY_STATUS_ID,status_id);
break;
case TYPE_TL_STATUSES:
item.put( KEY_WHO_ID, who_id );
break;
case TYPE_TL_HASHTAG:
item.put( KEY_HASHTAG,hashtag );
break;
}
// 以下は保存には必要ないがカラムリスト画面で使う
item.put( KEY_COLUMN_ACCESS, access_info.user );
@ -107,14 +107,44 @@ public class Column {
item.put( KEY_OLD_INDEX, old_index );
}
Column( ActMain activity, JSONObject src ){
this.activity = activity;
this.access_info = SavedAccount.loadAccount( log, src.optLong( KEY_ACCOUNT_ROW_ID ) );
if( access_info == null ) throw new RuntimeException( "missing account" );
this.type = src.optInt( KEY_TYPE );
switch(type){
case TYPE_TL_CONVERSATION:
this.status_id = src.optLong( KEY_STATUS_ID );
break;
case TYPE_TL_STATUSES:
this.who_id = src.optLong( KEY_WHO_ID );
break;
case TYPE_TL_HASHTAG:
this.hashtag = src.optString( KEY_HASHTAG );
break;
}
startLoading();
}
final AtomicBoolean is_dispose = new AtomicBoolean();
void dispose(){
is_dispose.set( true );
}
public String getColumnName(){
switch( type ){
default:
return "?";
case TYPE_TL_HOME:
return activity.getString( R.string.home );
case TYPE_TL_LOCAL:
return activity.getString( R.string.local_timeline );
case TYPE_TL_FEDERATE:
return activity.getString( R.string.federate_timeline );
@ -131,12 +161,59 @@ public class Column {
case TYPE_TL_NOTIFICATIONS:
return activity.getString( R.string.notifications );
case TYPE_TL_CONVERSATION:
return activity.getString( R.string.conversation_around,status_id );
case TYPE_TL_HASHTAG:
return activity.getString( R.string.hashtag_of ,hashtag );
}
}
Object getParamAt(Object[] params,int idx){
if( params == null || idx >= params.length){
throw new IndexOutOfBoundsException( "getParamAt idx="+idx );
}
return params[idx];
}
public boolean isSameSpec( SavedAccount ai, int type, Object[] params ){
if( type != this.type || ! Utils.equalsNullable(ai.user,access_info.user ) ) return false;
switch( type ){
default:
return true;
case TYPE_TL_STATUSES:
// プロフィール画面
try{
long who_id = (Long)getParamAt( params, 0 );
return who_id == this.who_id;
}catch(Throwable ex){
return false;
}
case TYPE_TL_CONVERSATION:
// 会話画面
try{
long status_id = (Long)getParamAt( params, 0 );
return status_id == this.status_id;
}catch(Throwable ex){
return false;
}
case TYPE_TL_HASHTAG:
// 会話画面
try{
long status_id = (Long)getParamAt( params, 0 );
return status_id == this.status_id;
}catch(Throwable ex){
return false;
}
}
}
public interface StatusEntryCallback {
void onIterate( TootStatus status );
}
@ -292,7 +369,7 @@ public class Column {
TootApiResult parseStatuses( TootApiResult result ){
if( result != null ){
saveRange( result, true, true );
tmp_list_status = TootStatus.parseList( log, result.array );
tmp_list_status = TootStatus.parseList( log, access_info,result.array );
}
return result;
}
@ -300,7 +377,7 @@ public class Column {
TootApiResult parseAccount( TootApiResult result ){
if( result != null ){
saveRange( result, true, true );
who_account = TootAccount.parse( log, result.object );
who_account = TootAccount.parse( log, access_info,result.object );
}
return result;
}
@ -316,7 +393,7 @@ public class Column {
TootApiResult parseNotifications( TootApiResult result ){
if( result != null ){
saveRange( result, true, true );
tmp_list_notification = TootNotification.parseList( log, result.array );
tmp_list_notification = TootNotification.parseList( log, access_info,result.array );
}
return result;
}
@ -365,6 +442,9 @@ public class Column {
case TYPE_TL_FAVOURITES:
return parseStatuses( client.request( PATH_TL_FAVOURITES ) );
case TYPE_TL_HASHTAG:
return parseStatuses( client.request( "/api/v1/timelines/tag/"+hashtag+"?limit=80" ) );
case TYPE_TL_REPORTS:
return parseReports( client.request( PATH_TL_REPORTS ) );
@ -375,12 +455,12 @@ public class Column {
case TYPE_TL_CONVERSATION:
TootApiResult result = client.request( "/api/v1/statuses/"+status_id );
if( result== null || result.object == null ) return result;
TootStatus target_status = TootStatus.parse( log,result.object );
TootStatus target_status = TootStatus.parse( log, access_info,result.object );
target_status.conversation_main = true;
//
result = client.request( "/api/v1/statuses/"+status_id+"/context" );
if( result== null || result.object == null ) return result;
TootContext context = TootContext.parse( log,result.object );
TootContext context = TootContext.parse( log,access_info,result.object );
tmp_list_status = new TootStatus.List();
if( context.ancestors != null ) tmp_list_status.addAll( context.ancestors);
tmp_list_status.add(target_status);
@ -415,6 +495,7 @@ public class Column {
case TYPE_TL_STATUSES:
case TYPE_TL_FAVOURITES:
case TYPE_TL_CONVERSATION:
case TYPE_TL_HASHTAG:
initList( status_list, tmp_list_status );
break;
@ -526,7 +607,7 @@ public class Column {
TootApiResult parseStatuses( TootApiResult result ){
if( result != null ){
saveRange( result, bBottom, ! bBottom );
tmp_list_status = TootStatus.parseList( log, result.array );
tmp_list_status = TootStatus.parseList( log, access_info, result.array );
}
return result;
}
@ -534,7 +615,7 @@ public class Column {
TootApiResult parseAccount( TootApiResult result ){
if( result != null ){
saveRange( result, bBottom, ! bBottom );
who_account = TootAccount.parse( log, result.object );
who_account = TootAccount.parse( log, access_info,result.object );
}
return result;
}
@ -550,7 +631,7 @@ public class Column {
TootApiResult parseNotifications( TootApiResult result ){
if( result != null ){
saveRange( result, bBottom, ! bBottom );
tmp_list_notification = TootNotification.parseList( log, result.array );
tmp_list_notification = TootNotification.parseList( log, access_info,result.array );
}
return result;
}
@ -600,6 +681,9 @@ public class Column {
case TYPE_TL_FAVOURITES:
return parseStatuses( client.request( addRange( bBottom, PATH_TL_FAVOURITES ) ) );
case TYPE_TL_HASHTAG:
return parseStatuses( client.request( addRange( bBottom,"/api/v1/timelines/tag/"+hashtag+"?limit=80" ) ) );
case TYPE_TL_REPORTS:
return parseReports( client.request( addRange( bBottom, PATH_TL_REPORTS ) ) );
@ -633,6 +717,7 @@ public class Column {
case TYPE_TL_FEDERATE:
case TYPE_TL_STATUSES:
case TYPE_TL_FAVOURITES:
case TYPE_TL_HASHTAG:
mergeList( status_list, tmp_list_status, bBottom );
break;

View File

@ -371,7 +371,7 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
switch( v.getId() ){
case R.id.ivBackground:
if( who != null ){
activity.openBrowser( who.url );
activity.openBrowser( access_info,who.url );
}
break;
case R.id.btnFollowing:
@ -433,7 +433,7 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
final ImageButton btnMore;
TootStatus status;
SavedAccount account;
SavedAccount access_info;
TootAccount account_thumbnail;
TootAccount account_boost;
TootAccount account_follow;
@ -508,7 +508,7 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
}
public void bind( ActMain activity, View view, Object item, SavedAccount access_info ){
this.account = access_info;
this.access_info = access_info;
llBoosted.setVisibility( View.GONE );
llFollow.setVisibility( View.GONE );
@ -696,11 +696,11 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
public void onClick( View v ){
switch( v.getId() ){
case R.id.btnHideMedia:
MediaShown.save( account.host, status.id, false );
MediaShown.save( access_info.host, status.id, false );
btnShowMedia.setVisibility( View.VISIBLE );
break;
case R.id.btnShowMedia:
MediaShown.save( account.host, status.id, true );
MediaShown.save( access_info.host, status.id, true );
btnShowMedia.setVisibility( View.GONE );
break;
case R.id.ivMedia1:
@ -717,37 +717,37 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
break;
case R.id.btnContentWarning:{
boolean new_shown = ( llContents.getVisibility() == View.GONE );
ContentWarning.save( account.host, status.id, new_shown );
ContentWarning.save( access_info.host, status.id, new_shown );
showContent( new_shown );
break;
}
case R.id.btnConversation:
activity.performConversation( account, status );
activity.performConversation( access_info, status );
break;
case R.id.btnReply:
activity.performReply( account, status );
activity.performReply( access_info, status );
break;
case R.id.btnBoost:
activity.performBoost( account, status, false );
activity.performBoost( access_info, status, false );
break;
case R.id.btnFavourite:
activity.performFavourite( account, status );
activity.performFavourite( access_info, status );
break;
case R.id.btnMore:
activity.performStatusMore( account, status );
activity.performStatusMore( access_info, status );
break;
case R.id.ivThumbnail:
activity.performOpenUser( account, account_thumbnail );
activity.performOpenUser( access_info, account_thumbnail );
break;
case R.id.llBoosted:
activity.performOpenUser( account, account_boost );
activity.performOpenUser( access_info, account_boost );
break;
case R.id.llFollow:
activity.performOpenUser( account, account_follow );
activity.performOpenUser( access_info, account_follow );
break;
case R.id.btnFollow:
activity.performAccountMore( account,account_follow);
activity.performAccountMore( access_info,account_follow);
}
}
@ -759,14 +759,14 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
if( TextUtils.isEmpty( sv ) ){
sv = a.url;
}
activity.openChromeTab( sv );
activity.openChromeTab( access_info,sv ,false);
}catch( Throwable ex ){
ex.printStackTrace();
}
}
public void onItemClick(){
activity.performConversation( account, status );
activity.performConversation( access_info, status );
}
}

View File

@ -3,6 +3,7 @@ package jp.juggler.subwaytooter.api.entity;
import android.text.Spannable;
import android.text.TextUtils;
import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.util.Emojione;
import org.json.JSONArray;
@ -11,6 +12,7 @@ import org.json.JSONObject;
import java.util.ArrayList;
import jp.juggler.subwaytooter.util.HTMLDecoder;
import jp.juggler.subwaytooter.util.LinkClickContext;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.Utils;
@ -70,7 +72,7 @@ public class TootAccount {
public long time_created_at;
public static TootAccount parse( LogCategory log, JSONObject src, TootAccount dst ){
public static TootAccount parse( LogCategory log, LinkClickContext account, JSONObject src, TootAccount dst ){
if( src == null ) return null;
try{
dst.id = src.optLong( "id" );
@ -89,7 +91,7 @@ public class TootAccount {
dst.followers_count = src.optLong( "followers_count" );
dst.following_count = src.optLong( "following_count" );
dst.statuses_count = src.optLong( "statuses_count" );
dst.note = HTMLDecoder.decodeHTML( Utils.optStringX( src, "note" ) );
dst.note = HTMLDecoder.decodeHTML( account,Utils.optStringX( src, "note" ) );
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",
dst.avatar_static = Utils.optStringX( src, "avatar_static" ); // "https:\/\/mastodon.juggler.jp\/system\/accounts\/avatars\/000\/000\/148\/original\/0a468974fac5a448.PNG?1492081886",
@ -107,17 +109,17 @@ public class TootAccount {
}
}
public static TootAccount parse( LogCategory log, JSONObject src ){
return parse( log, src, new TootAccount() );
public static TootAccount parse( LogCategory log, LinkClickContext account,JSONObject src ){
return parse( log, account, src, new TootAccount() );
}
public static List parseList( LogCategory log, JSONArray array ){
public static List parseList( LogCategory log, LinkClickContext account,JSONArray array ){
List result = new List();
if( array != null ){
for( int i = array.length() - 1 ; i >= 0 ; -- i ){
JSONObject src = array.optJSONObject( i );
if( src == null ) continue;
TootAccount item = parse( log, src );
TootAccount item = parse( log, account,src );
if( item != null ) result.add( 0, item );
}
}

View File

@ -2,6 +2,7 @@ package jp.juggler.subwaytooter.api.entity;
import org.json.JSONObject;
import jp.juggler.subwaytooter.util.LinkClickContext;
import jp.juggler.subwaytooter.util.LogCategory;
public class TootContext {
@ -12,12 +13,12 @@ public class TootContext {
// descendants The descendants of the status in the conversation, as a list of Statuses
public TootStatus.List descendants;
public static TootContext parse( LogCategory log, JSONObject src ){
public static TootContext parse( LogCategory log, LinkClickContext account,JSONObject src ){
if( src==null) return null;
try{
TootContext dst = new TootContext();
dst.ancestors = TootStatus.parseList( log,src.optJSONArray( "ancestors" ) );
dst.descendants = TootStatus.parseList(log, src.optJSONArray( "descendants" ) );
dst.ancestors = TootStatus.parseList( log, account,src.optJSONArray( "ancestors" ) );
dst.descendants = TootStatus.parseList(log, account, src.optJSONArray( "descendants" ) );
return dst;
}catch( Throwable ex ){
ex.printStackTrace();

View File

@ -5,6 +5,7 @@ import org.json.JSONObject;
import java.util.ArrayList;
import jp.juggler.subwaytooter.util.LinkClickContext;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.Utils;
@ -32,15 +33,15 @@ public class TootNotification extends TootId {
public long time_created_at;
public static TootNotification parse( LogCategory log, JSONObject src ){
public static TootNotification parse( LogCategory log, LinkClickContext accopunt, JSONObject src ){
if( src == null ) return null;
try{
TootNotification dst = new TootNotification();
dst.id = src.optLong( "id" );
dst.type = Utils.optStringX( src, "type" );
dst.created_at = Utils.optStringX( src, "created_at" );
dst.account = TootAccount.parse( log, src.optJSONObject( "account" ) );
dst.status = TootStatus.parse( log, src.optJSONObject( "status" ) );
dst.account = TootAccount.parse( log, accopunt, src.optJSONObject( "account" ) );
dst.status = TootStatus.parse( log, accopunt, src.optJSONObject( "status" ) );
dst.time_created_at = TootStatus.parseTime( log, dst.created_at );
@ -62,13 +63,13 @@ public class TootNotification extends TootId {
}
}
public static List parseList( LogCategory log, JSONArray array ){
public static List parseList( LogCategory log, LinkClickContext accopunt,JSONArray array ){
List result = new List();
if( array != null ){
for( int i = array.length() - 1 ; i >= 0 ; -- i ){
JSONObject src = array.optJSONObject( i );
if( src == null ) continue;
TootNotification item = parse( log, src );
TootNotification item = parse( log, accopunt,src );
if( item != null ) result.add( 0, item );
}
}

View File

@ -4,6 +4,7 @@ import org.json.JSONObject;
import java.util.ArrayList;
import jp.juggler.subwaytooter.util.LinkClickContext;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.Utils;
@ -17,12 +18,12 @@ public class TootResults {
// An array of matched hashtags, as strings
public ArrayList< String > hashtags;
public static TootResults parse( LogCategory log, JSONObject src ){
public static TootResults parse( LogCategory log, LinkClickContext account,JSONObject src ){
if( src == null ) return null;
try{
TootResults dst = new TootResults();
dst.accounts = TootAccount.parseList( log, src.optJSONArray( "accounts" ) );
dst.statuses = TootStatus.parseList( log, src.optJSONArray( "statuses" ) );
dst.accounts = TootAccount.parseList( log, account, src.optJSONArray( "accounts" ) );
dst.statuses = TootStatus.parseList( log, account, src.optJSONArray( "statuses" ) );
dst.hashtags = Utils.parseStringArray( log, src.optJSONArray( "hashtags" ) );
return dst;
}catch( Throwable ex ){

View File

@ -14,6 +14,7 @@ import java.util.Locale;
import java.util.TimeZone;
import jp.juggler.subwaytooter.util.HTMLDecoder;
import jp.juggler.subwaytooter.util.LinkClickContext;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.Utils;
@ -104,7 +105,7 @@ public class TootStatus extends TootId {
public boolean conversation_main;
public static TootStatus parse( LogCategory log, JSONObject src ){
public static TootStatus parse( LogCategory log, LinkClickContext account, JSONObject src ){
if( src == null ) return null;
@ -115,10 +116,10 @@ public class TootStatus extends TootId {
status.id = src.optLong( "id" );
status.uri = Utils.optStringX( src, "uri" );
status.url = Utils.optStringX( src, "url" );
status.account = TootAccount.parse( log, src.optJSONObject( "account" ) );
status.account = TootAccount.parse( log, account,src.optJSONObject( "account" ) );
status.in_reply_to_id = Utils.optStringX( src, "in_reply_to_id" ); // null
status.in_reply_to_account_id = Utils.optStringX( src, "in_reply_to_account_id" ); // null
status.reblog = TootStatus.parse( log, src.optJSONObject( "reblog" ) );
status.reblog = TootStatus.parse( log, account,src.optJSONObject( "reblog" ) );
status.content = Utils.optStringX( src, "content" );
status.created_at = Utils.optStringX( src, "created_at" ); // "2017-04-16T09:37:14.000Z"
status.reblogs_count = src.optLong( "reblogs_count" );
@ -134,9 +135,9 @@ public class TootStatus extends TootId {
status.application = Utils.optStringX( src, "application" ); // null
status.time_created_at = parseTime( log, status.created_at );
status.decoded_content = HTMLDecoder.decodeHTML( status.content );
status.decoded_tags = HTMLDecoder.decodeTags( status.tags );
status.decoded_mentions = HTMLDecoder.decodeMentions( status.mentions );
status.decoded_content = HTMLDecoder.decodeHTML( account,status.content );
status.decoded_tags = HTMLDecoder.decodeTags( account,status.tags );
status.decoded_mentions = HTMLDecoder.decodeMentions(account, status.mentions );
return status;
}catch( Throwable ex ){
@ -146,13 +147,13 @@ public class TootStatus extends TootId {
}
}
public static List parseList( LogCategory log, JSONArray array ){
public static List parseList( LogCategory log, LinkClickContext account,JSONArray array ){
List result = new List();
if( array != null ){
for( int i = array.length() - 1 ; i >= 0 ; -- i ){
JSONObject src = array.optJSONObject( i );
if( src == null ) continue;
TootStatus item = parse( log, src );
TootStatus item = parse( log,account, src );
if( item != null ) result.add( 0, item );
}
}

View File

@ -12,9 +12,10 @@ import java.util.ArrayList;
import jp.juggler.subwaytooter.App1;
import jp.juggler.subwaytooter.api.entity.TootAccount;
import jp.juggler.subwaytooter.util.LinkClickContext;
import jp.juggler.subwaytooter.util.LogCategory;
public class SavedAccount extends TootAccount{
public class SavedAccount extends TootAccount implements LinkClickContext{
private static final LogCategory log = new LogCategory( "SavedAccount" );
private static final String table = "access_info";
@ -63,7 +64,8 @@ public class SavedAccount extends TootAccount{
private static SavedAccount parse( Cursor cursor ) throws JSONException{
JSONObject src = new JSONObject( cursor.getString( cursor.getColumnIndex( COL_ACCOUNT ) ) );
SavedAccount dst = (SavedAccount)parse(log,src,new SavedAccount());
SavedAccount dst = new SavedAccount();
dst = (SavedAccount)parse(log,dst,src,dst);
if( dst != null){
dst.db_id = cursor.getLong( cursor.getColumnIndex( COL_ID ) );
dst.host = cursor.getString( cursor.getColumnIndex( COL_HOST ) );

View File

@ -13,6 +13,7 @@ import java.util.regex.Pattern;
import jp.juggler.subwaytooter.api.entity.TootMention;
import jp.juggler.subwaytooter.api.entity.TootTag;
import jp.juggler.subwaytooter.table.SavedAccount;
public class HTMLDecoder {
static final LogCategory log = new LogCategory( "HTMLDecoder" );
@ -81,6 +82,7 @@ public class HTMLDecoder {
is_openclose = ! TextUtils.isEmpty( m2.group( 1 ) );
}
open_type = is_close ? OPEN_TYPE_CLOSE : is_openclose ? OPEN_TYPE_OPENCLOSE : OPEN_TYPE_OPEN;
if( tag.equals( "br" )) open_type = OPEN_TYPE_OPENCLOSE;
}else{
tag = TAG_TEXT;
this.open_type = OPEN_TYPE_OPENCLOSE;
@ -89,7 +91,7 @@ public class HTMLDecoder {
}
public interface LinkClickCallback {
void onClickLink( String url );
void onClickLink( LinkClickContext account,String url );
}
public static LinkClickCallback link_callback;
@ -139,7 +141,7 @@ public class HTMLDecoder {
public void encodeSpan( SpannableStringBuilder sb ){
public void encodeSpan( final LinkClickContext account,SpannableStringBuilder sb ){
if( TAG_TEXT.equals( tag ) ){
sb.append( Emojione.decodeEmoji( decodeEntity( text ) ) );
return;
@ -149,7 +151,7 @@ public class HTMLDecoder {
int start = sb.length();
for( Node child : child_nodes ){
child.encodeSpan( sb );
child.encodeSpan( account,sb );
}
if( "a".equals( tag ) ){
@ -161,7 +163,7 @@ public class HTMLDecoder {
@Override
public void onClick( View widget ){
if( link_callback != null ){
link_callback.onClickLink( href );
link_callback.onClickLink( account,href );
}
}
}, start, sb.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );
@ -180,7 +182,7 @@ public class HTMLDecoder {
public static SpannableStringBuilder decodeHTML( String src ){
public static SpannableStringBuilder decodeHTML( LinkClickContext account, String src ){
try{
TokenParser tracker = new TokenParser( src );
Node rootNode = new Node();
@ -188,13 +190,15 @@ public class HTMLDecoder {
SpannableStringBuilder sb = new SpannableStringBuilder();
rootNode.encodeSpan( sb );
rootNode.encodeSpan( account, sb );
int end = sb.length();
while( end > 0 && Character.isWhitespace( sb.charAt( end-1 ) ) ) --end;
if( end < sb.length() ){
sb.delete( end,sb.length() );
}
// sb.append( "\n" );
// sb.append( src );
return sb;
}catch( Throwable ex ){
@ -203,7 +207,7 @@ public class HTMLDecoder {
return null;
}
public static Spannable decodeTags( TootTag.List src_list ){
public static Spannable decodeTags( final LinkClickContext account,TootTag.List src_list ){
if( src_list == null || src_list.isEmpty()) return null;
SpannableStringBuilder sb = new SpannableStringBuilder();
for(TootTag item : src_list){
@ -215,7 +219,7 @@ public class HTMLDecoder {
sb.setSpan( new ClickableSpan() {
@Override public void onClick( View widget ){
if( link_callback != null ){
link_callback.onClickLink( item_url );
link_callback.onClickLink( account,item_url );
}
}
}, start, sb.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );
@ -223,7 +227,7 @@ public class HTMLDecoder {
return sb;
}
public static Spannable decodeMentions( TootMention.List src_list ){
public static Spannable decodeMentions( final LinkClickContext account, TootMention.List src_list ){
if( src_list == null || src_list.isEmpty()) return null;
SpannableStringBuilder sb = new SpannableStringBuilder();
for(TootMention item : src_list){
@ -235,7 +239,7 @@ public class HTMLDecoder {
sb.setSpan( new ClickableSpan() {
@Override public void onClick( View widget ){
if( link_callback != null ){
link_callback.onClickLink( item_url );
link_callback.onClickLink( account,item_url );
}
}
}, start, sb.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );

View File

@ -0,0 +1,8 @@
package jp.juggler.subwaytooter.util;
/**
* Created by tateisu on 2017/04/24.
*/
public interface LinkClickContext {
}

View File

@ -175,6 +175,10 @@ public class Utils {
return dst_list;
}
public static <T> boolean equalsNullable(T a,T b){
return a == null ? b == null : a.equals( b );
}
static final char[] hex = new char[]{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
public static void addHex( StringBuilder sb, byte b ){

View File

@ -149,4 +149,9 @@
<string name="comment_empty">please comment for any reason to report</string>
<string name="report_completed">report completed.</string>
<string name="mention">mention</string>
<string name="hashtag_of">hashtag :#%1$s</string>
<string name="open_in_account">open with %1$s</string>
<string name="open_in_browser"/>
<string name="open_web_page">open web page</string>
<string name="open_web_on_host">open web page on %1$s</string>
</resources>