お気に入り操作時に変更中の表示が出なくなっていたバグの修正。トゥート検索機能を追加。

This commit is contained in:
tateisu 2017-07-19 03:19:05 +09:00
parent e5626c5539
commit 6f0454e76f
39 changed files with 1150 additions and 327 deletions

View File

@ -2,6 +2,7 @@
<dictionary name="tateisu"> <dictionary name="tateisu">
<words> <words>
<w>adamrocker</w> <w>adamrocker</w>
<w>apikey</w>
<w>dont</w> <w>dont</w>
<w>emoji</w> <w>emoji</w>
<w>emojione</w> <w>emojione</w>
@ -30,6 +31,7 @@
<w>unfollow</w> <w>unfollow</w>
<w>unmute</w> <w>unmute</w>
<w>unreblog</w> <w>unreblog</w>
<w>utoken</w>
</words> </words>
</dictionary> </dictionary>
</component> </component>

View File

@ -9,8 +9,8 @@ android {
applicationId "jp.juggler.subwaytooter" applicationId "jp.juggler.subwaytooter"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 25 targetSdkVersion 25
versionCode 93 versionCode 95
versionName "0.9.3" versionName "0.9.5"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }

View File

@ -64,6 +64,7 @@ import jp.juggler.subwaytooter.api.entity.TootApplication;
import jp.juggler.subwaytooter.api.entity.TootRelationShip; import jp.juggler.subwaytooter.api.entity.TootRelationShip;
import jp.juggler.subwaytooter.api.entity.TootResults; import jp.juggler.subwaytooter.api.entity.TootResults;
import jp.juggler.subwaytooter.api.entity.TootStatus; import jp.juggler.subwaytooter.api.entity.TootStatus;
import jp.juggler.subwaytooter.api.entity.TootStatusLike;
import jp.juggler.subwaytooter.dialog.AccountPicker; import jp.juggler.subwaytooter.dialog.AccountPicker;
import jp.juggler.subwaytooter.dialog.DlgConfirm; import jp.juggler.subwaytooter.dialog.DlgConfirm;
import jp.juggler.subwaytooter.dialog.LoginForm; import jp.juggler.subwaytooter.dialog.LoginForm;
@ -198,11 +199,16 @@ public class ActMain extends AppCompatActivity
boolean bRemoved = false; boolean bRemoved = false;
for( int i = 0, ie = app_state.column_list.size() ; i < ie ; ++ i ){ for( int i = 0, ie = app_state.column_list.size() ; i < ie ; ++ i ){
Column column = app_state.column_list.get( i ); Column column = app_state.column_list.get( i );
SavedAccount sa = SavedAccount.loadAccount( log, column.access_info.db_id );
if( sa == null ){ if( column.access_info.isNA() ){
bRemoved = true; // 検索カラムはアカウント削除とか無関係
}else{ }else{
new_order.add( i ); SavedAccount sa = SavedAccount.loadAccount( log, column.access_info.db_id );
if( sa == null ){
bRemoved = true;
}else{
new_order.add( i );
}
} }
} }
if( bRemoved ){ if( bRemoved ){
@ -607,6 +613,9 @@ public class ActMain extends AppCompatActivity
}else if( id == R.id.nav_muted_word ){ }else if( id == R.id.nav_muted_word ){
startActivity( new Intent( this, ActMutedWord.class ) ); startActivity( new Intent( this, ActMutedWord.class ) );
}else if( id == R.id.mastodon_search_portal ){
addColumn( getDefaultInsertPosition(), SavedAccount.getNA(),Column.TYPE_SEARCH_PORTAL , "" );
// }else if( id == R.id.nav_translation ){ // }else if( id == R.id.nav_translation ){
// Intent intent = new Intent(this, TransCommuActivity.class); // Intent intent = new Intent(this, TransCommuActivity.class);
// intent.putExtra(TransCommuActivity.APPLICATION_CODE_EXTRA, "FJlDoBKitg"); // intent.putExtra(TransCommuActivity.APPLICATION_CODE_EXTRA, "FJlDoBKitg");
@ -1356,7 +1365,7 @@ public class ActMain extends AppCompatActivity
SavedAccount a = column.access_info; SavedAccount a = column.access_info;
if( done_list.contains( a ) ) continue; if( done_list.contains( a ) ) continue;
done_list.add( a ); done_list.add( a );
a.reloadSetting(); if( !a.isNA() ) a.reloadSetting();
column.fireShowColumnHeader(); column.fireShowColumnHeader();
} }
} }
@ -1368,7 +1377,7 @@ public class ActMain extends AppCompatActivity
if( ! Utils.equalsNullable( a.acct, account.acct ) ) continue; if( ! Utils.equalsNullable( a.acct, account.acct ) ) continue;
if( done_list.contains( a ) ) continue; if( done_list.contains( a ) ) continue;
done_list.add( a ); done_list.add( a );
a.reloadSetting(); if( !a.isNA() ) a.reloadSetting();
column.fireShowColumnHeader(); column.fireShowColumnHeader();
} }
} }
@ -1463,6 +1472,7 @@ public class ActMain extends AppCompatActivity
} }
} ); } );
} }
public void performMuteApp( @NonNull TootApplication application ){ public void performMuteApp( @NonNull TootApplication application ){
MutedApp.save( application.name ); MutedApp.save( application.name );
@ -1557,6 +1567,47 @@ public class ActMain extends AppCompatActivity
try{ try{
log.d( "openChromeTab url=%s", url ); log.d( "openChromeTab url=%s", url );
if( !noIntercept && access_info != null && access_info.isNA() ){
// トゥート検索カラムではaccess_infoは何にも紐ついていない
// ハッシュタグをアプリ内で開く
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 = Uri.decode( m.group( 2 ) );
openHashTagOtherInstance( pos, access_info, url, host, tag );
return;
}
// ステータスページをアプリから開く
m = reStatusPage.matcher( url );
if( m.find() ){
try{
// https://mastodon.juggler.jp/@SubwayTooter/(status_id)
final String host = m.group( 1 );
final long status_id = Long.parseLong( m.group( 3 ), 10 );
openStatusOtherInstance( pos, access_info, url, host, status_id );
return;
}catch( Throwable ex ){
Utils.showToast( this, ex, "can't parse status id." );
}
return;
}
// ユーザページをアプリ内で開く
m = reUserPage.matcher( url );
if( m.find() ){
// https://mastodon.juggler.jp/@SubwayTooter
final String host = m.group( 1 );
final String user = Uri.decode( m.group( 2 ) );
openProfileByHostUser( pos,access_info,url,host,user );
return;
}
}
if( ! noIntercept && access_info != null ){ if( ! noIntercept && access_info != null ){
// ハッシュタグをアプリ内で開く // ハッシュタグをアプリ内で開く
Matcher m = reHashTag.matcher( url ); Matcher m = reHashTag.matcher( url );
@ -1635,15 +1686,19 @@ public class ActMain extends AppCompatActivity
} }
} }
public void openStatus( int pos, @NonNull SavedAccount access_info, @NonNull TootStatus status ){ public void openStatus( int pos, @NonNull SavedAccount access_info, @NonNull TootStatusLike status ){
openStatus( pos, access_info, status.id ); if( access_info.host.equalsIgnoreCase( status.status_host ) ){
openStatus( pos, access_info, status.id );
}else{
openStatusOtherInstance( pos, access_info,status.url,status.status_host,status.id);
}
} }
public void openStatus( int pos, @NonNull SavedAccount access_info, long status_id ){ public void openStatus( int pos, @NonNull SavedAccount access_info, long status_id ){
addColumn( pos, access_info, Column.TYPE_CONVERSATION, status_id ); addColumn( pos, access_info, Column.TYPE_CONVERSATION, status_id );
} }
private void openStatusOtherInstance( final int pos, final SavedAccount access_info, final String url, final String host, final long status_id ){ void openStatusOtherInstance( final int pos, final SavedAccount access_info, final String url, final String host, final long status_id ){
ActionsDialog dialog = new ActionsDialog(); ActionsDialog dialog = new ActionsDialog();
// ブラウザで表示する // ブラウザで表示する
@ -1851,7 +1906,7 @@ public class ActMain extends AppCompatActivity
if( result != null && result.object != null ){ if( result != null && result.object != null ){
TootResults tmp = TootResults.parse( log, access_info, result.object ); TootResults tmp = TootResults.parse( log, access_info, access_info.host, result.object );
if( tmp != null ){ if( tmp != null ){
if( tmp.accounts != null && ! tmp.accounts.isEmpty() ){ if( tmp.accounts != null && ! tmp.accounts.isEmpty() ){
who_local = tmp.accounts.get( 0 ); who_local = tmp.accounts.get( 0 );
@ -1974,7 +2029,7 @@ public class ActMain extends AppCompatActivity
public void performFavourite( public void performFavourite(
final SavedAccount access_info final SavedAccount access_info
, final TootStatus arg_status , final TootStatusLike arg_status
, final int nCrossAccountMode , final int nCrossAccountMode
, final boolean bSet , final boolean bSet
, final RelationChangedCallback callback , final RelationChangedCallback callback
@ -2002,7 +2057,7 @@ public class ActMain extends AppCompatActivity
client.setAccount( access_info ); client.setAccount( access_info );
TootApiResult result; TootApiResult result;
TootStatus target_status; TootStatusLike target_status;
if( nCrossAccountMode == CROSS_ACCOUNT_REMOTE_INSTANCE ){ if( nCrossAccountMode == CROSS_ACCOUNT_REMOTE_INSTANCE ){
// 検索APIに他タンスのステータスのURLを投げると自タンスのステータスを得られる // 検索APIに他タンスのステータスのURLを投げると自タンスのステータスを得られる
String path = String.format( Locale.JAPAN, Column.PATH_SEARCH, Uri.encode( arg_status.url ) ); String path = String.format( Locale.JAPAN, Column.PATH_SEARCH, Uri.encode( arg_status.url ) );
@ -2013,7 +2068,7 @@ public class ActMain extends AppCompatActivity
return result; return result;
} }
target_status = null; target_status = null;
TootResults tmp = TootResults.parse( log, access_info, result.object ); TootResults tmp = TootResults.parse( log, access_info, access_info.host,result.object );
if( tmp != null ){ if( tmp != null ){
if( tmp.statuses != null && ! tmp.statuses.isEmpty() ){ if( tmp.statuses != null && ! tmp.statuses.isEmpty() ){
target_status = tmp.statuses.get( 0 ); target_status = tmp.statuses.get( 0 );
@ -2043,7 +2098,7 @@ public class ActMain extends AppCompatActivity
) )
, request_builder ); , request_builder );
if( result != null && result.object != null ){ if( result != null && result.object != null ){
new_status = TootStatus.parse( log, access_info, result.object ); new_status = TootStatus.parse( log, access_info, access_info.host, result.object );
} }
return result; return result;
@ -2108,7 +2163,7 @@ public class ActMain extends AppCompatActivity
public void performBoost( public void performBoost(
final SavedAccount access_info final SavedAccount access_info
, final TootStatus arg_status , final TootStatusLike arg_status
, final int nCrossAccountMode , final int nCrossAccountMode
, final boolean bSet , final boolean bSet
, final boolean bConfirmed , final boolean bConfirmed
@ -2172,7 +2227,7 @@ public class ActMain extends AppCompatActivity
TootApiResult result; TootApiResult result;
TootStatus target_status; TootStatusLike target_status;
if( nCrossAccountMode == CROSS_ACCOUNT_REMOTE_INSTANCE ){ if( nCrossAccountMode == CROSS_ACCOUNT_REMOTE_INSTANCE ){
// 検索APIに他タンスのステータスのURLを投げると自タンスのステータスを得られる // 検索APIに他タンスのステータスのURLを投げると自タンスのステータスを得られる
String path = String.format( Locale.JAPAN, Column.PATH_SEARCH, Uri.encode( arg_status.url ) ); String path = String.format( Locale.JAPAN, Column.PATH_SEARCH, Uri.encode( arg_status.url ) );
@ -2183,7 +2238,7 @@ public class ActMain extends AppCompatActivity
return result; return result;
} }
target_status = null; target_status = null;
TootResults tmp = TootResults.parse( log, access_info, result.object ); TootResults tmp = TootResults.parse( log, access_info, access_info.host,result.object );
if( tmp != null ){ if( tmp != null ){
if( tmp.statuses != null && ! tmp.statuses.isEmpty() ){ if( tmp.statuses != null && ! tmp.statuses.isEmpty() ){
target_status = tmp.statuses.get( 0 ); target_status = tmp.statuses.get( 0 );
@ -2213,7 +2268,7 @@ public class ActMain extends AppCompatActivity
// reblog,unreblog のレスポンスは信用ならんのでステータスを再取得する // reblog,unreblog のレスポンスは信用ならんのでステータスを再取得する
result = client.request( "/api/v1/statuses/" + target_status.id ); result = client.request( "/api/v1/statuses/" + target_status.id );
if( result != null && result.object != null ){ if( result != null && result.object != null ){
new_status = TootStatus.parse( log, access_info, result.object ); new_status = TootStatus.parse( log, access_info, access_info.host,result.object );
} }
} }
@ -2276,14 +2331,18 @@ public class ActMain extends AppCompatActivity
public void performReply( public void performReply(
final SavedAccount access_info final SavedAccount access_info
, final TootStatus arg_status , final TootStatus arg_status
, final boolean bRemote
){ ){
if( ! bRemote ){ ActPost.open( this, REQUEST_CODE_POST, access_info.db_id, arg_status );
ActPost.open( this, REQUEST_CODE_POST, access_info.db_id, arg_status ); }
return;
} public void performReplyRemote(
final SavedAccount access_info
,final String remote_status_url
,final long remote_status_id
){
final ProgressDialog progress = new ProgressDialog( this );
new AsyncTask< Void, Void, TootApiResult >() { final AsyncTask< Void, Void, TootApiResult > task = new AsyncTask< Void, Void, TootApiResult >() {
TootStatus target_status; TootStatus target_status;
@Override protected TootApiResult doInBackground( Void... params ){ @Override protected TootApiResult doInBackground( Void... params ){
@ -2298,15 +2357,15 @@ public class ActMain extends AppCompatActivity
client.setAccount( access_info ); client.setAccount( access_info );
// 検索APIに他タンスのステータスのURLを投げると自タンスのステータスを得られる // 検索APIに他タンスのステータスのURLを投げると自タンスのステータスを得られる
String path = String.format( Locale.JAPAN, Column.PATH_SEARCH, Uri.encode( arg_status.url ) ); String path = String.format( Locale.JAPAN, Column.PATH_SEARCH, Uri.encode( remote_status_url ) );
path = path + "&resolve=1"; path = path + "&resolve=1";
TootApiResult result = client.request( path ); TootApiResult result = client.request( path );
if( result != null && result.object != null ){ if( result != null && result.object != null ){
TootResults tmp = TootResults.parse( log, access_info, result.object ); TootResults tmp = TootResults.parse( log, access_info, access_info.host,result.object );
if( tmp != null && tmp.statuses != null && ! tmp.statuses.isEmpty() ){ if( tmp != null && tmp.statuses != null && ! tmp.statuses.isEmpty() ){
target_status = tmp.statuses.get( 0 ); target_status = tmp.statuses.get( 0 );
log.d( "status id conversion %s => %s", arg_status.id, target_status.id ); log.d( "status id conversion %s => %s", remote_status_id, target_status.id );
} }
if( target_status == null ){ if( target_status == null ){
return new TootApiResult( getString( R.string.status_id_conversion_failed ) ); return new TootApiResult( getString( R.string.status_id_conversion_failed ) );
@ -2322,6 +2381,7 @@ public class ActMain extends AppCompatActivity
@Override @Override
protected void onPostExecute( TootApiResult result ){ protected void onPostExecute( TootApiResult result ){
progress.dismiss();
if( result == null ){ if( result == null ){
// cancelled. // cancelled.
}else if( target_status != null ){ }else if( target_status != null ){
@ -2330,7 +2390,18 @@ public class ActMain extends AppCompatActivity
Utils.showToast( ActMain.this, true, result.error ); Utils.showToast( ActMain.this, true, result.error );
} }
} }
}.executeOnExecutor( App1.task_executor ); };
progress.setIndeterminate( true );
progress.setCancelable( true );
progress.setMessage( getString(R.string.progress_synchronize_toot) );
progress.setOnCancelListener( new DialogInterface.OnCancelListener() {
@Override public void onCancel( DialogInterface dialog ){
task.cancel( true );
}
} );
progress.show();
task.executeOnExecutor( App1.task_executor );
} }
//////////////////////////////////////// ////////////////////////////////////////
@ -3251,7 +3322,7 @@ public class ActMain extends AppCompatActivity
} }
} }
void openBoostFromAnotherAccount( @NonNull final SavedAccount timeline_account, final TootStatus status ){ void openBoostFromAnotherAccount( @NonNull final SavedAccount timeline_account, @Nullable final TootStatusLike status ){
if( status == null ) return; if( status == null ) return;
AccountPicker.pick( this, false, false AccountPicker.pick( this, false, false
, getString( R.string.account_picker_boost ) , getString( R.string.account_picker_boost )
@ -3270,7 +3341,7 @@ public class ActMain extends AppCompatActivity
} ); } );
} }
void openFavouriteFromAnotherAccount( @NonNull final SavedAccount timeline_account, final TootStatus status ){ void openFavouriteFromAnotherAccount( @NonNull final SavedAccount timeline_account, final TootStatusLike status ){
if( status == null ) return; if( status == null ) return;
AccountPicker.pick( this, false, false AccountPicker.pick( this, false, false
, getString( R.string.account_picker_favourite ) , getString( R.string.account_picker_favourite )
@ -3288,25 +3359,38 @@ public class ActMain extends AppCompatActivity
} ); } );
} }
void openReplyFromAnotherAccount( @NonNull final SavedAccount access_info, final TootStatus status ){
if( status == null ) return; void openReplyFromAnotherAccount( final TootStatusLike status){
AccountPicker.pick( this, false, false AccountPicker.pick( this, false, false
, getString( R.string.account_picker_reply ) , getString( R.string.account_picker_reply )
, makeAccountListNonPseudo( log ), new AccountPicker.AccountPickerCallback() { , makeAccountListNonPseudo( log ), new AccountPicker.AccountPickerCallback() {
@Override public void onAccountPicked( @NonNull SavedAccount ai ){ @Override public void onAccountPicked( @NonNull SavedAccount ai ){
performReply( if( (status instanceof TootStatus) && ai.host.equalsIgnoreCase( status.status_host ) ){
ai performReply( ai, (TootStatus)status );
, status }else{
, ! ai.host.equalsIgnoreCase( access_info.host ) performReplyRemote( ai,status.url,status.id );
); }
} }
} ); } );
} }
void openFollowFromAnotherAccount( @NonNull SavedAccount access_info, TootStatus status ){ // void openReplyFromAnotherAccount( @NonNull final SavedAccount access_info, final String status_url,final long status_id ){
if( status == null ) return; //
openFollowFromAnotherAccount( access_info, status.account ); // final String status_host = getHostFromStatusUrl(status_url);
} // if( status_host ==null ) return;
//
// AccountPicker.pick( this, false, false
// , getString( R.string.account_picker_reply )
// , makeAccountListNonPseudo( log ), new AccountPicker.AccountPickerCallback() {
// @Override public void onAccountPicked( @NonNull SavedAccount ai ){
// performReplyRemote( ai,status_url,status_id );
// }
// } );
// }
// void openFollowFromAnotherAccount( @NonNull SavedAccount access_info, TootStatus status ){
// if( status == null ) return;
// openFollowFromAnotherAccount( access_info, status.account );
// }
void openFollowFromAnotherAccount( @NonNull SavedAccount access_info, final TootAccount account ){ void openFollowFromAnotherAccount( @NonNull SavedAccount access_info, final TootAccount account ){
if( account == null ) return; if( account == null ) return;

View File

@ -1,6 +1,7 @@
package jp.juggler.subwaytooter; package jp.juggler.subwaytooter;
import android.Manifest; import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.ClipData; import android.content.ClipData;
@ -25,14 +26,12 @@ import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.text.Editable; import android.text.Editable;
import android.text.Html;
import android.text.Spannable; import android.text.Spannable;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import android.widget.Button; import android.widget.Button;
import android.widget.CheckBox; import android.widget.CheckBox;
@ -47,7 +46,6 @@ import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
@ -397,7 +395,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
sv = intent.getStringExtra( KEY_REPLY_STATUS ); sv = intent.getStringExtra( KEY_REPLY_STATUS );
if( sv != null ){ if( sv != null ){
try{ try{
TootStatus reply_status = TootStatus.parse( log, account, new JSONObject( sv ) ); TootStatus reply_status = TootStatus.parse( log, account, account.host,new JSONObject( sv ) );
// CW をリプライ元に合わせる // CW をリプライ元に合わせる
if( ! TextUtils.isEmpty( reply_status.spoiler_text ) ){ if( ! TextUtils.isEmpty( reply_status.spoiler_text ) ){
@ -898,7 +896,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
TootApiResult result = client.request( path ); TootApiResult result = client.request( path );
if( result != null && result.object != null ){ if( result != null && result.object != null ){
TootResults tmp = TootResults.parse( log, access_info, result.object ); TootResults tmp = TootResults.parse( log, access_info, access_info.host,result.object );
if( tmp != null && tmp.statuses != null && ! tmp.statuses.isEmpty() ){ if( tmp != null && tmp.statuses != null && ! tmp.statuses.isEmpty() ){
target_status = tmp.statuses.get( 0 ); target_status = tmp.statuses.get( 0 );
} }
@ -1607,7 +1605,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
TootApiResult result = client.request( "/api/v1/statuses", request_builder ); TootApiResult result = client.request( "/api/v1/statuses", request_builder );
if( result != null && result.object != null ){ if( result != null && result.object != null ){
status = TootStatus.parse( log, account, result.object ); status = TootStatus.parse( log, account, account.host,result.object );
Spannable s = status.decoded_content; Spannable s = status.decoded_content;
MyClickableSpan[] span_list = s.getSpans( 0, s.length(), MyClickableSpan.class ); MyClickableSpan[] span_list = s.getSpans( 0, s.length(), MyClickableSpan.class );
@ -1982,7 +1980,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append( src.substring( 0, mushroom_start ) ); sb.append( src.substring( 0, mushroom_start ) );
int new_sel_start = sb.length(); // int new_sel_start = sb.length();
sb.append( text ); sb.append( text );
int new_sel_end = sb.length(); int new_sel_end = sb.length();
sb.append( src.substring( mushroom_end ) ); sb.append( src.substring( mushroom_end ) );
@ -2028,6 +2026,8 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
} }
} }
private void showRecommendedPlugin( String title ){ private void showRecommendedPlugin( String title ){
String language_code = getString( R.string.language_code ); String language_code = getString( R.string.language_code );
int res_id; int res_id;
@ -2038,46 +2038,37 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
}else{ }else{
res_id = R.raw.recommended_plugin_en; res_id = R.raw.recommended_plugin_en;
} }
try{ byte[] data = Utils.loadRawResource(this,res_id);
InputStream is = getResources().openRawResource( res_id ); if( data != null ){
try{ String text = Utils.decodeUTF8( data );
ByteArrayOutputStream bao = new ByteArrayOutputStream(); @SuppressLint("InflateParams")
IOUtils.copy( is, bao ); View viewRoot = getLayoutInflater().inflate( R.layout.dlg_plugin_missing, null, false );
String text = Utils.decodeUTF8( bao.toByteArray() );
TextView tvText = (TextView) viewRoot.findViewById( R.id.tvText );
View viewRoot = getLayoutInflater().inflate( R.layout.dlg_plugin_missing, null, false ); LinkClickContext lcc = new LinkClickContext() {
@Override public AcctColor findAcctColor( String url ){
TextView tvText = (TextView) viewRoot.findViewById( R.id.tvText ); return null;
LinkClickContext lcc = new LinkClickContext() {
@Override public AcctColor findAcctColor( String url ){
return null;
}
};
CharSequence sv = HTMLDecoder.decodeHTML( lcc, text, false, null );
tvText.setText( sv );
tvText.setMovementMethod( LinkMovementMethod.getInstance() );
TextView tvTitle = (TextView) viewRoot.findViewById( R.id.tvTitle );
if( TextUtils.isEmpty( title ) ){
tvTitle.setVisibility( View.GONE );
}else{
tvTitle.setText( title );
} }
};
CharSequence sv = HTMLDecoder.decodeHTML( lcc, text, false, null );
tvText.setText( sv );
tvText.setMovementMethod( LinkMovementMethod.getInstance() );
TextView tvTitle = (TextView) viewRoot.findViewById( R.id.tvTitle );
if( TextUtils.isEmpty( title ) ){
tvTitle.setVisibility( View.GONE );
}else{
tvTitle.setText( title );
new AlertDialog.Builder( this )
.setView( viewRoot )
.setCancelable( true )
.setNeutralButton( R.string.close, null )
.show();
}finally{
IOUtils.closeQuietly( is );
} }
}catch( Throwable ex ){ new AlertDialog.Builder( this )
ex.printStackTrace(); .setView( viewRoot )
.setCancelable( true )
.setNeutralButton( R.string.close, null )
.show();
} }
} }
final MyClickableSpan.LinkClickCallback link_click_listener = new MyClickableSpan.LinkClickCallback() { final MyClickableSpan.LinkClickCallback link_click_listener = new MyClickableSpan.LinkClickCallback() {

View File

@ -13,6 +13,8 @@ import android.view.View;
import android.widget.EditText; import android.widget.EditText;
import jp.juggler.subwaytooter.api.entity.TootStatus; import jp.juggler.subwaytooter.api.entity.TootStatus;
import jp.juggler.subwaytooter.api.entity.TootStatusLike;
import jp.juggler.subwaytooter.api_msp.entity.MSPToot;
import jp.juggler.subwaytooter.table.MutedWord; import jp.juggler.subwaytooter.table.MutedWord;
import jp.juggler.subwaytooter.table.SavedAccount; import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.util.HTMLDecoder; import jp.juggler.subwaytooter.util.HTMLDecoder;
@ -25,7 +27,7 @@ public class ActText extends AppCompatActivity implements View.OnClickListener {
static final String EXTRA_TEXT = "text"; static final String EXTRA_TEXT = "text";
static final String EXTRA_CONTENT_START = "content_start"; static final String EXTRA_CONTENT_START = "content_start";
static void encodeStatus( Intent intent, Context context, SavedAccount access_info, TootStatus status ){ static void encodeStatus( Intent intent, Context context, SavedAccount access_info, TootStatusLike status ){
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append( context.getString( R.string.send_header_url ) ); sb.append( context.getString( R.string.send_header_url ) );
sb.append( ": " ); sb.append( ": " );
@ -33,7 +35,15 @@ public class ActText extends AppCompatActivity implements View.OnClickListener {
sb.append( "\n" ); sb.append( "\n" );
sb.append( context.getString( R.string.send_header_date ) ); sb.append( context.getString( R.string.send_header_date ) );
sb.append( ": " ); sb.append( ": " );
sb.append( TootStatus.formatTime( status.time_created_at ) );
if( status instanceof TootStatus ){
TootStatus ts = (TootStatus)status;
sb.append( TootStatus.formatTime( ts.time_created_at ) );
}else if( status instanceof MSPToot ){
MSPToot ts = (MSPToot)status;
sb.append( ts.created_at );
}
sb.append( "\n" ); sb.append( "\n" );
sb.append( context.getString( R.string.send_header_from_acct ) ); sb.append( context.getString( R.string.send_header_from_acct ) );
sb.append( ": " ); sb.append( ": " );
@ -63,7 +73,7 @@ public class ActText extends AppCompatActivity implements View.OnClickListener {
} }
public static void open( ActMain activity, SavedAccount access_info, TootStatus status ){ public static void open( ActMain activity, SavedAccount access_info, TootStatusLike status ){
Intent intent = new Intent( activity, ActText.class ); Intent intent = new Intent( activity, ActText.class );
encodeStatus( intent,activity, access_info, status ); encodeStatus( intent,activity, access_info, status );

View File

@ -636,7 +636,7 @@ public class AlarmService extends IntentService {
return; return;
} }
TootNotification notification = TootNotification.parse( log, account, src ); TootNotification notification = TootNotification.parse( log, account,account.host ,src );
if( notification == null ){ if( notification == null ){
return; return;
} }

View File

@ -328,6 +328,7 @@ public class AppDataExporter {
case Pref.KEY_STREAM_LISTENER_SECRET: case Pref.KEY_STREAM_LISTENER_SECRET:
case Pref.KEY_STREAM_LISTENER_CONFIG_DATA: case Pref.KEY_STREAM_LISTENER_CONFIG_DATA:
case Pref.KEY_CLIENT_NAME: case Pref.KEY_CLIENT_NAME:
case Pref.KEY_MASTODON_SEARCH_PORTAL_USER_TOKEN:
String sv = reader.nextString(); String sv = reader.nextString();
e.putString( k, sv ); e.putString( k, sv );
break; break;

View File

@ -27,6 +27,7 @@ import java.util.LinkedList;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import jp.juggler.subwaytooter.api.entity.TootStatus; import jp.juggler.subwaytooter.api.entity.TootStatus;
import jp.juggler.subwaytooter.api.entity.TootStatusLike;
import jp.juggler.subwaytooter.table.SavedAccount; import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.util.LogCategory; import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.MyClickableSpan; import jp.juggler.subwaytooter.util.MyClickableSpan;
@ -134,34 +135,34 @@ class AppState {
private final HashSet< String > map_busy_fav = new HashSet<>(); private final HashSet< String > map_busy_fav = new HashSet<>();
boolean isBusyFav( SavedAccount account, TootStatus status ){ boolean isBusyFav( SavedAccount account, @NonNull TootStatusLike status ){
String busy_key = account.host + ":" + status.id; final String key = account.acct +":" + status.status_host + ":" + status.id;
return map_busy_fav.contains( busy_key ); return map_busy_fav.contains( key );
} }
boolean setBusyFav( SavedAccount account, TootStatus status ){ boolean setBusyFav( SavedAccount account, @NonNull TootStatusLike status ){
final String busy_key = account.acct +":" + status.uri; final String key = account.acct +":" + status.status_host + ":" + status.id;
return map_busy_fav.add( busy_key ); return map_busy_fav.add( key );
} }
boolean resetBusyFav( SavedAccount account, TootStatus status ){ boolean resetBusyFav( SavedAccount account, @NonNull TootStatusLike status ){
final String busy_key = account.acct +":" + status.uri; final String key = account.acct +":" + status.status_host + ":" + status.id;
return map_busy_fav.remove( busy_key ); return map_busy_fav.remove( key );
} }
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
private final HashSet< String > map_busy_boost = new HashSet<>(); private final HashSet< String > map_busy_boost = new HashSet<>();
boolean isBusyBoost( @NonNull SavedAccount account, @NonNull TootStatus status ){ boolean isBusyBoost( @NonNull SavedAccount account, @NonNull TootStatusLike status ){
final String busy_key = account.acct +":" + status.uri; final String key = account.acct +":" + status.status_host + ":" + status.id;
return map_busy_boost.contains( busy_key ); return map_busy_boost.contains( key );
} }
boolean setBusyBoost( SavedAccount account, TootStatus status ){ boolean setBusyBoost( SavedAccount account, @NonNull TootStatusLike status ){
final String busy_key = account.acct +":" + status.uri; final String key = account.acct +":" + status.status_host + ":" + status.id;
return map_busy_boost.add( busy_key ); return map_busy_boost.add( key );
} }
boolean resetBusyBoost( SavedAccount account, TootStatus status ){ boolean resetBusyBoost( SavedAccount account, @NonNull TootStatusLike status ){
final String busy_key = account.acct +":" + status.uri; final String key = account.acct +":" + status.status_host + ":" + status.id;
return map_busy_boost.remove( busy_key ); return map_busy_boost.remove( key );
} }
////////////////////////////////////////////////////// //////////////////////////////////////////////////////

View File

@ -35,6 +35,9 @@ import jp.juggler.subwaytooter.api.entity.TootReport;
import jp.juggler.subwaytooter.api.entity.TootResults; import jp.juggler.subwaytooter.api.entity.TootResults;
import jp.juggler.subwaytooter.api.entity.TootStatus; import jp.juggler.subwaytooter.api.entity.TootStatus;
import jp.juggler.subwaytooter.api.entity.TootTag; import jp.juggler.subwaytooter.api.entity.TootTag;
import jp.juggler.subwaytooter.api_msp.MSPApiResult;
import jp.juggler.subwaytooter.api_msp.MSPClient;
import jp.juggler.subwaytooter.api_msp.entity.MSPToot;
import jp.juggler.subwaytooter.table.AcctColor; import jp.juggler.subwaytooter.table.AcctColor;
import jp.juggler.subwaytooter.table.AcctSet; import jp.juggler.subwaytooter.table.AcctSet;
import jp.juggler.subwaytooter.table.MutedApp; import jp.juggler.subwaytooter.table.MutedApp;
@ -162,6 +165,7 @@ class Column implements StreamReader.Callback {
static final int TYPE_BOOSTED_BY = 14; static final int TYPE_BOOSTED_BY = 14;
static final int TYPE_FAVOURITED_BY = 15; static final int TYPE_FAVOURITED_BY = 15;
static final int TYPE_DOMAIN_BLOCKS = 16; static final int TYPE_DOMAIN_BLOCKS = 16;
static final int TYPE_SEARCH_PORTAL = 17;
@NonNull final Context context; @NonNull final Context context;
@NonNull private final AppState app_state; @NonNull private final AppState app_state;
@ -229,7 +233,10 @@ class Column implements StreamReader.Callback {
this.search_query = (String) getParamAt( params, 0 ); this.search_query = (String) getParamAt( params, 0 );
this.search_resolve = (Boolean) getParamAt( params, 1 ); this.search_resolve = (Boolean) getParamAt( params, 1 );
break; break;
case TYPE_SEARCH_PORTAL:
this.search_query = (String) getParamAt( params, 0 );
break;
} }
init(); init();
} }
@ -271,6 +278,9 @@ class Column implements StreamReader.Callback {
item.put( KEY_SEARCH_QUERY, search_query ); item.put( KEY_SEARCH_QUERY, search_query );
item.put( KEY_SEARCH_RESOLVE, search_resolve ); item.put( KEY_SEARCH_RESOLVE, search_resolve );
break; break;
case TYPE_SEARCH_PORTAL:
item.put( KEY_SEARCH_QUERY, search_query );
break;
} }
// 以下は保存には必要ないがカラムリスト画面で使う // 以下は保存には必要ないがカラムリスト画面で使う
@ -286,9 +296,15 @@ class Column implements StreamReader.Callback {
this.app_state = app_state; this.app_state = app_state;
this.context = app_state.context; this.context = app_state.context;
SavedAccount ac = SavedAccount.loadAccount( log, src.optLong( KEY_ACCOUNT_ROW_ID ) ); long account_db_id = src.optLong( KEY_ACCOUNT_ROW_ID );
if( ac == null ) throw new RuntimeException( "missing account" ); if( account_db_id >= 0 ){
this.access_info = ac; SavedAccount ac = SavedAccount.loadAccount( log, account_db_id );
if( ac == null ) throw new RuntimeException( "missing account" );
this.access_info = ac;
}else{
this.access_info = SavedAccount.getNA();
}
this.column_type = src.optInt( KEY_TYPE ); this.column_type = src.optInt( KEY_TYPE );
this.dont_close = src.optBoolean( KEY_DONT_CLOSE ); this.dont_close = src.optBoolean( KEY_DONT_CLOSE );
this.with_attachment = src.optBoolean( KEY_WITH_ATTACHMENT ); this.with_attachment = src.optBoolean( KEY_WITH_ATTACHMENT );
@ -328,7 +344,9 @@ class Column implements StreamReader.Callback {
this.search_query = src.optString( KEY_SEARCH_QUERY ); this.search_query = src.optString( KEY_SEARCH_QUERY );
this.search_resolve = src.optBoolean( KEY_SEARCH_RESOLVE, false ); this.search_resolve = src.optBoolean( KEY_SEARCH_RESOLVE, false );
break; break;
case TYPE_SEARCH_PORTAL:
this.search_query = src.optString( KEY_SEARCH_QUERY );
break;
} }
init(); init();
} }
@ -375,7 +393,13 @@ class Column implements StreamReader.Callback {
}catch( Throwable ex ){ }catch( Throwable ex ){
return false; return false;
} }
case TYPE_SEARCH_PORTAL:
try{
String q = (String) getParamAt( params, 0 );
return Utils.equalsNullable( q, this.search_query );
}catch( Throwable ex ){
return false;
}
} }
} }
@ -417,6 +441,13 @@ class Column implements StreamReader.Callback {
return getColumnTypeName( context, column_type ); return getColumnTypeName( context, column_type );
} }
case TYPE_SEARCH_PORTAL:
if( bLong ){
return context.getString( R.string.toot_search_of, search_query );
}else{
return getColumnTypeName( context, column_type );
}
} }
} }
@ -471,6 +502,9 @@ class Column implements StreamReader.Callback {
case TYPE_SEARCH: case TYPE_SEARCH:
return context.getString( R.string.search ); return context.getString( R.string.search );
case TYPE_SEARCH_PORTAL:
return context.getString( R.string.toot_search );
case TYPE_FOLLOW_REQUESTS: case TYPE_FOLLOW_REQUESTS:
return context.getString( R.string.follow_requests ); return context.getString( R.string.follow_requests );
} }
@ -525,6 +559,9 @@ class Column implements StreamReader.Callback {
case TYPE_SEARCH: case TYPE_SEARCH:
return R.attr.ic_search; return R.attr.ic_search;
case TYPE_SEARCH_PORTAL:
return R.attr.ic_search;
case TYPE_FOLLOW_REQUESTS: case TYPE_FOLLOW_REQUESTS:
return R.attr.ic_account_add; return R.attr.ic_account_add;
} }
@ -580,7 +617,7 @@ class Column implements StreamReader.Callback {
for( Object o : list_data ){ for( Object o : list_data ){
if( o instanceof TootStatus ){ if( o instanceof TootStatus ){
TootStatus item = (TootStatus) o; TootStatus item = (TootStatus) o;
if( (item.account != null && item.account.id == who_id) if( ( item.account != null && item.account.id == who_id )
|| ( item.reblog != null && item.reblog.account != null && item.reblog.account.id == who_id ) || ( item.reblog != null && item.reblog.account != null && item.reblog.account.id == who_id )
){ ){
continue; continue;
@ -590,7 +627,8 @@ class Column implements StreamReader.Callback {
TootNotification item = (TootNotification) o; TootNotification item = (TootNotification) o;
if( item.account.id == who_id ) continue; if( item.account.id == who_id ) continue;
if( item.status != null ){ if( item.status != null ){
if( (item.status.account != null && item.status.account.id == who_id) ) continue; if( ( item.status.account != null && item.status.account.id == who_id ) )
continue;
if( item.status.reblog != null && item.status.reblog.account != null && item.status.reblog.account.id == who_id ) if( item.status.reblog != null && item.status.reblog.account != null && item.status.reblog.account.id == who_id )
continue; continue;
} }
@ -739,8 +777,9 @@ class Column implements StreamReader.Callback {
for( Object o : list_data ){ for( Object o : list_data ){
if( o instanceof TootStatus ){ if( o instanceof TootStatus ){
TootStatus item = (TootStatus) o; TootStatus item = (TootStatus) o;
if( item.account != null && reDomain.matcher( item.account.acct ).find() ) continue; if( item.account != null && reDomain.matcher( item.account.acct ).find() )
if( item.reblog != null && item.reblog.account !=null && reDomain.matcher( item.reblog.account.acct ).find() ) continue;
if( item.reblog != null && item.reblog.account != null && reDomain.matcher( item.reblog.account.acct ).find() )
continue; continue;
}else if( o instanceof TootNotification ){ }else if( o instanceof TootNotification ){
TootNotification item = (TootNotification) o; TootNotification item = (TootNotification) o;
@ -748,8 +787,9 @@ class Column implements StreamReader.Callback {
if( reDomain.matcher( item.account.acct ).find() ) continue; if( reDomain.matcher( item.account.acct ).find() ) continue;
} }
if( item.status != null ){ if( item.status != null ){
if( item.status.account != null && reDomain.matcher( item.status.account.acct ).find() ) continue; if( item.status.account != null && reDomain.matcher( item.status.account.acct ).find() )
if( item.status.reblog != null && item.status.reblog.account !=null && reDomain.matcher( item.status.reblog.account.acct ).find() ) continue;
if( item.status.reblog != null && item.status.reblog.account != null && reDomain.matcher( item.status.reblog.account.acct ).find() )
continue; continue;
} }
} }
@ -1022,7 +1062,7 @@ class Column implements StreamReader.Callback {
if( result != null && result.array != null ){ if( result != null && result.array != null ){
saveRange( result, true, true ); saveRange( result, true, true );
// //
TootStatus.List src = TootStatus.parseList( log, access_info, result.array ); TootStatus.List src = TootStatus.parseList( log, access_info, access_info.host,result.array );
list_tmp = new ArrayList<>( src.size() ); list_tmp = new ArrayList<>( src.size() );
addWithFilter( list_tmp, src ); addWithFilter( list_tmp, src );
// //
@ -1059,7 +1099,7 @@ class Column implements StreamReader.Callback {
break; break;
} }
src = TootStatus.parseList( log, access_info, result2.array ); src = TootStatus.parseList( log, access_info, access_info.host,result2.array );
addWithFilter( list_tmp, src ); addWithFilter( list_tmp, src );
@ -1106,7 +1146,7 @@ class Column implements StreamReader.Callback {
TootApiResult result = client.request( path_base ); TootApiResult result = client.request( path_base );
if( result != null ){ if( result != null ){
saveRange( result, true, true ); saveRange( result, true, true );
TootNotification.List src = TootNotification.parseList( log, access_info, result.array ); TootNotification.List src = TootNotification.parseList( log, access_info, access_info.host, result.array );
list_tmp = new ArrayList<>(); list_tmp = new ArrayList<>();
addWithFilter( list_tmp, src ); addWithFilter( list_tmp, src );
@ -1223,7 +1263,7 @@ class Column implements StreamReader.Callback {
result = client.request( result = client.request(
String.format( Locale.JAPAN, PATH_STATUSES, status_id ) ); String.format( Locale.JAPAN, PATH_STATUSES, status_id ) );
if( result == null || result.object == null ) return result; if( result == null || result.object == null ) return result;
TootStatus target_status = TootStatus.parse( log, access_info, result.object ); TootStatus target_status = TootStatus.parse( log, access_info, access_info.host,result.object );
target_status.conversation_main = true; target_status.conversation_main = true;
// 前後の会話 // 前後の会話
@ -1232,13 +1272,13 @@ class Column implements StreamReader.Callback {
if( result == null || result.object == null ) return result; if( result == null || result.object == null ) return result;
// 一つのリストにまとめる // 一つのリストにまとめる
TootContext context = TootContext.parse( log, access_info, result.object ); TootContext conversation_context = TootContext.parse( log, access_info, access_info.host,result.object );
list_tmp = new ArrayList<>( 1 + context.ancestors.size() + context.descendants.size() ); list_tmp = new ArrayList<>( 1 + conversation_context.ancestors.size() + conversation_context.descendants.size() );
if( context.ancestors != null ) if( conversation_context.ancestors != null )
addWithFilter( list_tmp, context.ancestors ); addWithFilter( list_tmp, conversation_context.ancestors );
list_tmp.add( target_status ); list_tmp.add( target_status );
if( context.descendants != null ) if( conversation_context.descendants != null )
addWithFilter( list_tmp, context.descendants ); addWithFilter( list_tmp, conversation_context.descendants );
// //
return result; return result;
@ -1250,7 +1290,7 @@ class Column implements StreamReader.Callback {
result = client.request( path ); result = client.request( path );
if( result == null || result.object == null ) return result; if( result == null || result.object == null ) return result;
TootResults tmp = TootResults.parse( log, access_info, result.object ); TootResults tmp = TootResults.parse( log, access_info, access_info.host,result.object );
if( tmp != null ){ if( tmp != null ){
list_tmp = new ArrayList<>(); list_tmp = new ArrayList<>();
list_tmp.addAll( tmp.hashtags ); list_tmp.addAll( tmp.hashtags );
@ -1258,7 +1298,45 @@ class Column implements StreamReader.Callback {
list_tmp.addAll( tmp.statuses ); list_tmp.addAll( tmp.statuses );
} }
return result; return result;
case TYPE_SEARCH_PORTAL:
max_id = "";
String q = search_query.trim();
if( q.length() <= 0 ){
list_tmp = new ArrayList<>();
result = new TootApiResult( context.getString( R.string.list_empty ) );
}else{
result = MSPClient.search( context, search_query, max_id, new MSPClient.Callback() {
@Override
public boolean isApiCancelled(){
return isCancelled() || is_dispose.get();
}
@Override
public void publishApiProgress( final String s ){
Utils.runOnMainThread( new Runnable() {
@Override
public void run(){
if( isCancelled() ) return;
task_progress = s;
fireShowContent();
}
} );
}
} );
if( result != null && result.array != null ){
// max_id の更新
max_id = MSPClient.getMaxId( result.array, max_id );
// リストデータの用意
MSPToot.List search_result = MSPToot.parseList( log, access_info, result.array );
if( search_result != null ){
list_tmp = new ArrayList<>();
list_tmp.addAll( search_result );
}
}
}
return result;
} }
}finally{ }finally{
try{ try{
@ -1749,7 +1827,7 @@ class Column implements StreamReader.Callback {
if( result != null && result.array != null ){ if( result != null && result.array != null ){
saveRange( result, bBottom, ! bBottom ); saveRange( result, bBottom, ! bBottom );
list_tmp = new ArrayList<>(); list_tmp = new ArrayList<>();
TootNotification.List src = TootNotification.parseList( log, access_info, result.array ); TootNotification.List src = TootNotification.parseList( log, access_info,access_info.host, result.array );
addWithFilter( list_tmp, src ); addWithFilter( list_tmp, src );
if( ! src.isEmpty() ){ if( ! src.isEmpty() ){
@ -1793,7 +1871,7 @@ class Column implements StreamReader.Callback {
break; break;
} }
src = TootNotification.parseList( log, access_info, result2.array ); src = TootNotification.parseList( log, access_info, access_info.host,result2.array );
if( ! src.isEmpty() ){ if( ! src.isEmpty() ){
addWithFilter( list_tmp, src ); addWithFilter( list_tmp, src );
AlarmService.injectData( context, access_info.db_id, src ); AlarmService.injectData( context, access_info.db_id, src );
@ -1815,7 +1893,7 @@ class Column implements StreamReader.Callback {
TootApiResult result = client.request( addRange( bBottom, path_base ) ); TootApiResult result = client.request( addRange( bBottom, path_base ) );
if( result != null && result.array != null ){ if( result != null && result.array != null ){
saveRange( result, bBottom, ! bBottom ); saveRange( result, bBottom, ! bBottom );
TootStatus.List src = TootStatus.parseList( log, access_info, result.array ); TootStatus.List src = TootStatus.parseList( log, access_info, access_info.host,result.array );
list_tmp = new ArrayList<>(); list_tmp = new ArrayList<>();
addWithFilter( list_tmp, src ); addWithFilter( list_tmp, src );
@ -1859,7 +1937,7 @@ class Column implements StreamReader.Callback {
break; break;
} }
src = TootStatus.parseList( log, access_info, result2.array ); src = TootStatus.parseList( log, access_info, access_info.host,result2.array );
addWithFilter( list_tmp, src ); addWithFilter( list_tmp, src );
@ -1915,7 +1993,7 @@ class Column implements StreamReader.Callback {
break; break;
} }
src = TootStatus.parseList( log, access_info, result2.array ); src = TootStatus.parseList( log, access_info, access_info.host,result2.array );
addWithFilter( list_tmp, src ); addWithFilter( list_tmp, src );
} }
} }
@ -2016,6 +2094,49 @@ class Column implements StreamReader.Callback {
case TYPE_HASHTAG: case TYPE_HASHTAG:
return getStatusList( client, return getStatusList( client,
String.format( Locale.JAPAN, PATH_HASHTAG, Uri.encode( hashtag ) ) ); String.format( Locale.JAPAN, PATH_HASHTAG, Uri.encode( hashtag ) ) );
case TYPE_SEARCH_PORTAL:
if(!bBottom){
return new TootApiResult( "head of list.");
}
TootApiResult result;
String q = search_query.trim();
if( q.length() <= 0 ){
list_tmp = new ArrayList<>();
result = new TootApiResult( context.getString( R.string.list_empty ) );
}else{
result = MSPClient.search( context, search_query, max_id, new MSPClient.Callback() {
@Override
public boolean isApiCancelled(){
return isCancelled() || is_dispose.get();
}
@Override
public void publishApiProgress( final String s ){
Utils.runOnMainThread( new Runnable() {
@Override
public void run(){
if( isCancelled() ) return;
task_progress = s;
fireShowContent();
}
} );
}
} );
if( result != null && result.array != null ){
// max_id の更新
max_id = MSPClient.getMaxId( result.array, max_id );
// リストデータの用意
MSPToot.List search_result = MSPToot.parseList( log, access_info, result.array );
if( search_result != null ){
list_tmp = new ArrayList<>();
list_tmp.addAll( search_result );
}
}
}
return result;
} }
}finally{ }finally{
try{ try{
@ -2271,7 +2392,7 @@ class Column implements StreamReader.Callback {
} }
result = r2; result = r2;
TootNotification.List src = TootNotification.parseList( log, access_info, r2.array ); TootNotification.List src = TootNotification.parseList( log, access_info, access_info.host,r2.array );
if( src.isEmpty() ){ if( src.isEmpty() ){
log.d( "gap-notification: empty." ); log.d( "gap-notification: empty." );
@ -2327,7 +2448,7 @@ class Column implements StreamReader.Callback {
// 成功した場合はそれを返したい // 成功した場合はそれを返したい
result = r2; result = r2;
TootStatus.List src = TootStatus.parseList( log, access_info, r2.array ); TootStatus.List src = TootStatus.parseList( log, access_info,access_info.host, r2.array );
if( src.size() == 0 ){ if( src.size() == 0 ){
// 直前の取得でカラのデータが帰ってきたら終了 // 直前の取得でカラのデータが帰ってきたら終了
log.d( "gap-statuses: empty." ); log.d( "gap-statuses: empty." );
@ -2698,7 +2819,8 @@ class Column implements StreamReader.Callback {
}else if( o instanceof TootStatus ){ }else if( o instanceof TootStatus ){
TootStatus status = (TootStatus) o; TootStatus status = (TootStatus) o;
if( column_type == TYPE_NOTIFICATIONS ) return; if( column_type == TYPE_NOTIFICATIONS ) return;
if( column_type == TYPE_LOCAL && status.account != null && status.account.acct.indexOf( '@' ) != - 1 ) return; if( column_type == TYPE_LOCAL && status.account != null && status.account.acct.indexOf( '@' ) != - 1 )
return;
if( isFiltered( status ) ) return; if( isFiltered( status ) ) return;
if( this.enable_speech ){ if( this.enable_speech ){
@ -2788,6 +2910,9 @@ class Column implements StreamReader.Callback {
// リフレッシュしてからストリーミング開始 // リフレッシュしてからストリーミング開始
log.d( "onResume: start auto refresh." ); log.d( "onResume: start auto refresh." );
startRefresh( true, false, - 1L, - 1 ); startRefresh( true, false, - 1L, - 1 );
}else if( column_type == TYPE_SEARCH || column_type == TYPE_SEARCH_PORTAL ){
// 検索カラムはリフレッシュもストリーミングもないがresumeのタイミングでリストの再描画を行いたい
fireShowContent();
}else{ }else{
// ギャップつきでストリーミング開始 // ギャップつきでストリーミング開始
log.d( "onResume: start streaming with gap." ); log.d( "onResume: start streaming with gap." );

View File

@ -9,6 +9,8 @@ import android.support.v4.view.ViewCompat;
import android.text.Editable; import android.text.Editable;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.text.method.MovementMethod;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
@ -27,7 +29,9 @@ import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayoutDirec
import java.util.regex.Pattern; import java.util.regex.Pattern;
import jp.juggler.subwaytooter.table.AcctColor; import jp.juggler.subwaytooter.table.AcctColor;
import jp.juggler.subwaytooter.util.HTMLDecoder;
import jp.juggler.subwaytooter.util.LogCategory; import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.view.MyLinkMovementMethod;
import jp.juggler.subwaytooter.view.MyListView; import jp.juggler.subwaytooter.view.MyListView;
import jp.juggler.subwaytooter.util.ScrollPosition; import jp.juggler.subwaytooter.util.ScrollPosition;
import jp.juggler.subwaytooter.util.Utils; import jp.juggler.subwaytooter.util.Utils;
@ -81,6 +85,8 @@ class ColumnViewHolder
private final View llRegexFilter; private final View llRegexFilter;
private final Button btnDeleteNotification; private final Button btnDeleteNotification;
private final TextView tvSearchDesc;
ColumnViewHolder( ActMain arg_activity, View root ){ ColumnViewHolder( ActMain arg_activity, View root ){
this.activity = arg_activity; this.activity = arg_activity;
@ -137,8 +143,8 @@ class ColumnViewHolder
etRegexFilter = (EditText) root.findViewById( R.id.etRegexFilter ); etRegexFilter = (EditText) root.findViewById( R.id.etRegexFilter );
llRegexFilter = root.findViewById( R.id.llRegexFilter ); llRegexFilter = root.findViewById( R.id.llRegexFilter );
tvRegexFilterError = (TextView) root.findViewById( R.id.tvRegexFilterError ); tvRegexFilterError = (TextView) root.findViewById( R.id.tvRegexFilterError );
tvSearchDesc = (TextView) root.findViewById( R.id.tvSearchDesc );
btnDeleteNotification = (Button) root.findViewById( R.id.btnDeleteNotification ); btnDeleteNotification = (Button) root.findViewById( R.id.btnDeleteNotification );
@ -223,7 +229,7 @@ class ColumnViewHolder
private boolean loading_busy; private boolean loading_busy;
void onPageCreate( Column column, int page_idx, int page_count ){ void onPageCreate( @NonNull Column column, int page_idx, int page_count ){
loading_busy = true; loading_busy = true;
try{ try{
this.column = column; this.column = column;
@ -250,6 +256,7 @@ class ColumnViewHolder
bAllowFilter = true; bAllowFilter = true;
break; break;
case Column.TYPE_SEARCH: case Column.TYPE_SEARCH:
case Column.TYPE_SEARCH_PORTAL:
case Column.TYPE_CONVERSATION: case Column.TYPE_CONVERSATION:
case Column.TYPE_REPORTS: case Column.TYPE_REPORTS:
case Column.TYPE_BLOCKS: case Column.TYPE_BLOCKS:
@ -299,7 +306,8 @@ class ColumnViewHolder
vg( llRegexFilter, bAllowFilter ); vg( llRegexFilter, bAllowFilter );
vg( btnDeleteNotification, column.column_type == Column.TYPE_NOTIFICATIONS ); vg( btnDeleteNotification, column.column_type == Column.TYPE_NOTIFICATIONS );
vg( llSearch, column.column_type == Column.TYPE_SEARCH ); vg( llSearch, (column.column_type == Column.TYPE_SEARCH || column.column_type == Column.TYPE_SEARCH_PORTAL ) );
vg( cbResolve, (column.column_type == Column.TYPE_SEARCH ) );
// tvRegexFilterErrorの表示を更新 // tvRegexFilterErrorの表示を更新
if( bAllowFilter ){ if( bAllowFilter ){
@ -307,12 +315,32 @@ class ColumnViewHolder
} }
switch( column.column_type ){ switch( column.column_type ){
default:
swipyRefreshLayout.setEnabled( true );
swipyRefreshLayout.setDirection( SwipyRefreshLayoutDirection.BOTH );
break;
case Column.TYPE_CONVERSATION: case Column.TYPE_CONVERSATION:
case Column.TYPE_SEARCH: case Column.TYPE_SEARCH:
swipyRefreshLayout.setEnabled( false ); swipyRefreshLayout.setEnabled( false );
break; break;
default:
case Column.TYPE_SEARCH_PORTAL:
swipyRefreshLayout.setEnabled( true ); swipyRefreshLayout.setEnabled( true );
swipyRefreshLayout.setDirection( SwipyRefreshLayoutDirection.BOTTOM );
break;
}
switch( column.column_type ){
default:
tvSearchDesc.setVisibility( View.GONE );
break;
case Column.TYPE_SEARCH:
showSearchDesc( activity.getString( R.string.search_desc_mastodon_api ) );
break;
case Column.TYPE_SEARCH_PORTAL:
showSearchDesc( getSearchDescPortal() );
break; break;
} }
@ -331,6 +359,27 @@ class ColumnViewHolder
} }
} }
private String getSearchDescPortal(){
String language_code = activity.getString( R.string.language_code );
int res_id;
if( "ja".equals( language_code ) ){
res_id = R.raw.search_desc_portal_ja;
}else{
res_id = R.raw.search_desc_portal_en;
}
byte[] data = Utils.loadRawResource(activity,res_id);
return data == null ? null : Utils.decodeUTF8( data );
}
private void showSearchDesc( String html ){
if( column==null) return;
log.d("showSearchDesc: html=%s",html);
tvSearchDesc.setVisibility( View.VISIBLE );
tvSearchDesc.setMovementMethod( MyLinkMovementMethod.getInstance() );
CharSequence sv = HTMLDecoder.decodeHTML( column.access_info, html, false, null );
tvSearchDesc.setText( sv );
}
void showColumnColor(){ void showColumnColor(){
if( column == null ) return; if( column == null ) return;
@ -595,7 +644,7 @@ class ColumnViewHolder
break; break;
case R.id.btnColumnReload: case R.id.btnColumnReload:
if( column.column_type == Column.TYPE_SEARCH ){ if( column.column_type == Column.TYPE_SEARCH || column.column_type == Column.TYPE_SEARCH_PORTAL ){
Utils.hideKeyboard( activity, etSearch ); Utils.hideKeyboard( activity, etSearch );
etSearch.setText( column.search_query ); etSearch.setText( column.search_query );
cbResolve.setChecked( column.search_resolve ); cbResolve.setChecked( column.search_resolve );

View File

@ -18,6 +18,7 @@ import java.util.ArrayList;
import jp.juggler.subwaytooter.api.entity.TootAccount; import jp.juggler.subwaytooter.api.entity.TootAccount;
import jp.juggler.subwaytooter.api.entity.TootStatus; import jp.juggler.subwaytooter.api.entity.TootStatus;
import jp.juggler.subwaytooter.api.entity.TootStatusLike;
import jp.juggler.subwaytooter.dialog.DlgQRCode; import jp.juggler.subwaytooter.dialog.DlgQRCode;
import jp.juggler.subwaytooter.table.SavedAccount; import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.table.UserRelation; import jp.juggler.subwaytooter.table.UserRelation;
@ -30,7 +31,7 @@ class DlgContextMenu implements View.OnClickListener, View.OnLongClickListener {
@NonNull final ActMain activity; @NonNull final ActMain activity;
@NonNull private final SavedAccount access_info; @NonNull private final SavedAccount access_info;
@Nullable private final TootAccount who; @Nullable private final TootAccount who;
@Nullable private final TootStatus status; @Nullable private final TootStatusLike status;
@NonNull private final UserRelation relation; @NonNull private final UserRelation relation;
@NonNull private final Column column; @NonNull private final Column column;
@ -43,7 +44,7 @@ class DlgContextMenu implements View.OnClickListener, View.OnLongClickListener {
@NonNull ActMain activity @NonNull ActMain activity
, @NonNull Column column , @NonNull Column column
, @Nullable TootAccount who , @Nullable TootAccount who
, @Nullable TootStatus status , @Nullable TootStatusLike status
){ ){
this.activity = activity; this.activity = activity;
this.column = column; this.column = column;
@ -123,11 +124,11 @@ class DlgContextMenu implements View.OnClickListener, View.OnLongClickListener {
}else{ }else{
btnDelete.setVisibility( View.GONE ); btnDelete.setVisibility( View.GONE );
btnReport.setOnClickListener( this ); btnReport.setOnClickListener( this );
if( status.application == null || TextUtils.isEmpty( status.application.name ) ){ if( status.application != null && !TextUtils.isEmpty( status.application.name ) ){
btnMuteApp.setVisibility( View.GONE );
}else{
btnMuteApp.setText( activity.getString( R.string.mute_app_of, status.application.name ) ); btnMuteApp.setText( activity.getString( R.string.mute_app_of, status.application.name ) );
btnMuteApp.setOnClickListener( this ); btnMuteApp.setOnClickListener( this );
}else{
btnMuteApp.setVisibility( View.GONE );
} }
} }
} }
@ -225,11 +226,20 @@ class DlgContextMenu implements View.OnClickListener, View.OnLongClickListener {
v = viewRoot.findViewById( R.id.btnCancel ); v = viewRoot.findViewById( R.id.btnCancel );
v.setOnClickListener( this ); v.setOnClickListener( this );
v = viewRoot.findViewById( R.id.btnBoostedBy ); if( access_info.isNA() ){
v.setOnClickListener( this ); v = viewRoot.findViewById( R.id.btnBoostedBy );
v.setVisibility( View.GONE);
v = viewRoot.findViewById( R.id.btnFavouritedBy );
v.setOnClickListener( this ); v = viewRoot.findViewById( R.id.btnFavouritedBy );
v.setVisibility( View.GONE);
}else{
v = viewRoot.findViewById( R.id.btnBoostedBy );
v.setOnClickListener( this );
v = viewRoot.findViewById( R.id.btnFavouritedBy );
v.setOnClickListener( this );
}
v = viewRoot.findViewById( R.id.btnAccountQrCode ); v = viewRoot.findViewById( R.id.btnAccountQrCode );
v.setOnClickListener( this ); v.setOnClickListener( this );
@ -275,7 +285,7 @@ class DlgContextMenu implements View.OnClickListener, View.OnLongClickListener {
break; break;
case R.id.btnReplyAnotherAccount: case R.id.btnReplyAnotherAccount:
activity.openReplyFromAnotherAccount( access_info, status ); activity.openReplyFromAnotherAccount( status );
break; break;
case R.id.btnDelete: case R.id.btnDelete:
@ -293,8 +303,8 @@ class DlgContextMenu implements View.OnClickListener, View.OnLongClickListener {
break; break;
case R.id.btnReport: case R.id.btnReport:
if( status != null && who != null ){ if( who != null && status instanceof TootStatus ){
activity.openReportForm( access_info, who, status ); activity.openReportForm( access_info, who, (TootStatus)status );
} }
break; break;

View File

@ -75,7 +75,7 @@ class ItemListAdapter extends BaseAdapter implements AdapterView.OnItemClickList
}else{ }else{
holder = (ItemViewHolder) view.getTag(); holder = (ItemViewHolder) view.getTag();
} }
holder.bind( o, position ); holder.bind( o );
return view; return view;
} }

View File

@ -19,6 +19,8 @@ import jp.juggler.subwaytooter.api.entity.TootDomainBlock;
import jp.juggler.subwaytooter.api.entity.TootGap; import jp.juggler.subwaytooter.api.entity.TootGap;
import jp.juggler.subwaytooter.api.entity.TootNotification; import jp.juggler.subwaytooter.api.entity.TootNotification;
import jp.juggler.subwaytooter.api.entity.TootStatus; import jp.juggler.subwaytooter.api.entity.TootStatus;
import jp.juggler.subwaytooter.api.entity.TootStatusLike;
import jp.juggler.subwaytooter.api_msp.entity.MSPToot;
import jp.juggler.subwaytooter.table.AcctColor; import jp.juggler.subwaytooter.table.AcctColor;
import jp.juggler.subwaytooter.table.ContentWarning; import jp.juggler.subwaytooter.table.ContentWarning;
import jp.juggler.subwaytooter.table.MediaShown; import jp.juggler.subwaytooter.table.MediaShown;
@ -79,18 +81,17 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
private final TextView tvApplication; private final TextView tvApplication;
private TootStatus status; private TootStatusLike status;
private TootAccount account_thumbnail; private TootAccount account_thumbnail;
private TootAccount account_boost; private TootAccount account_boost;
private TootAccount account_follow; private TootAccount account_follow;
private String search_tag; private String search_tag;
private TootGap gap; private TootGap gap;
private TootDomainBlock domain_block; private TootDomainBlock domain_block;
private int position;
private final boolean bSimpleList; private final boolean bSimpleList;
ItemViewHolder( ActMain arg_activity, Column column, ItemListAdapter list_adapter, View view ,boolean bSimpleList ){ ItemViewHolder( ActMain arg_activity, Column column, ItemListAdapter list_adapter, View view, boolean bSimpleList ){
this.activity = arg_activity; this.activity = arg_activity;
this.column = column; this.column = column;
this.access_info = column.access_info; this.access_info = column.access_info;
@ -101,7 +102,6 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
this.tvFollowerName = (TextView) view.findViewById( R.id.tvFollowerName ); this.tvFollowerName = (TextView) view.findViewById( R.id.tvFollowerName );
this.tvBoosted = (TextView) view.findViewById( R.id.tvBoosted ); this.tvBoosted = (TextView) view.findViewById( R.id.tvBoosted );
if( activity.timeline_font != null ){ if( activity.timeline_font != null ){
Utils.scanView( view, new Utils.ScanViewCallback() { Utils.scanView( view, new Utils.ScanViewCallback() {
@Override public void onScanView( View v ){ @Override public void onScanView( View v ){
@ -111,7 +111,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
}else if( v instanceof TextView ){ }else if( v instanceof TextView ){
( (TextView) v ).setTypeface( activity.timeline_font ); ( (TextView) v ).setTypeface( activity.timeline_font );
} }
}catch(Throwable ex){ }catch( Throwable ex ){
ex.printStackTrace(); ex.printStackTrace();
} }
} }
@ -147,7 +147,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
this.tvContent = (MyTextView) view.findViewById( R.id.tvContent ); this.tvContent = (MyTextView) view.findViewById( R.id.tvContent );
this.tvMentions = (MyTextView) view.findViewById( R.id.tvMentions ); this.tvMentions = (MyTextView) view.findViewById( R.id.tvMentions );
this.buttons_for_status = bSimpleList ? null : new StatusButtons( activity, column, view , false ); this.buttons_for_status = bSimpleList ? null : new StatusButtons( activity, column, view, false );
this.flMedia = view.findViewById( R.id.flMedia ); this.flMedia = view.findViewById( R.id.flMedia );
this.btnShowMedia = view.findViewById( R.id.btnShowMedia ); this.btnShowMedia = view.findViewById( R.id.btnShowMedia );
@ -193,8 +193,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
lp.height = activity.app_state.media_thumb_height; lp.height = activity.app_state.media_thumb_height;
} }
void bind( Object item, int position ){ void bind( Object item ){
this.position = position;
this.status = null; this.status = null;
this.account_thumbnail = null; this.account_thumbnail = null;
this.account_boost = null; this.account_boost = null;
@ -210,7 +209,9 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
if( item == null ) return; if( item == null ) return;
if( item instanceof String ){ if( item instanceof MSPToot ){
showStatus( activity, (MSPToot) item );
}else if( item instanceof String ){
showSearchTag( (String) item ); showSearchTag( (String) item );
}else if( item instanceof TootAccount ){ }else if( item instanceof TootAccount ){
showFollow( (TootAccount) item ); showFollow( (TootAccount) item );
@ -270,7 +271,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
}else if( item instanceof TootGap ){ }else if( item instanceof TootGap ){
showGap( (TootGap) item ); showGap( (TootGap) item );
}else if( item instanceof TootDomainBlock ){ }else if( item instanceof TootDomainBlock ){
showDomainBlock( (TootDomainBlock)item ); showDomainBlock( (TootDomainBlock) item );
} }
} }
@ -316,24 +317,30 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
Styler.setFollowIcon( activity, btnFollow, ivFollowedBy, relation ); Styler.setFollowIcon( activity, btnFollow, ivFollowedBy, relation );
} }
private void showStatus( @NonNull ActMain activity, @NonNull TootStatus status ){ private void showStatus( @NonNull ActMain activity, @NonNull TootStatusLike status ){
this.status = status; this.status = status;
llStatus.setVisibility( View.VISIBLE ); llStatus.setVisibility( View.VISIBLE );
tvTime.setText( TootStatus.formatTime( status.time_created_at ) ); if( status instanceof TootStatus ){
tvTime.setText( TootStatus.formatTime( ( (TootStatus) status ).time_created_at ) );
}else if( status instanceof MSPToot ){
tvTime.setText( ( (MSPToot) status ).created_at );
}
ivThumbnail.setCornerRadius( activity.pref, 16f ); ivThumbnail.setCornerRadius( activity.pref, 16f );
account_thumbnail = status.account; account_thumbnail = status.account;
setAcct( tvAcct, access_info.getFullAcct( status.account ), R.attr.colorAcctSmall ); setAcct( tvAcct, access_info.getFullAcct( status.account ), R.attr.colorAcctSmall );
if(status.account == null ){ if( status.account == null ){
tvName.setText( "?" ); tvName.setText( "?" );
ivThumbnail.setImageUrl(null,null); ivThumbnail.setImageUrl( null, null );
}else{ }else{
tvName.setText( status.account.display_name ); tvName.setText( status.account.display_name );
ivThumbnail.setImageUrl( ivThumbnail.setImageUrl(
access_info.supplyBaseUrl( status.account.avatar_static ) access_info.supplyBaseUrl( status.account.avatar_static )
,access_info.supplyBaseUrl( status.account.avatar ) , access_info.supplyBaseUrl( status.account.avatar )
); );
} }
tvContent.setText( status.decoded_content ); tvContent.setText( status.decoded_content );
@ -345,11 +352,16 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
// tvTags.setText( status.decoded_tags ); // tvTags.setText( status.decoded_tags );
// } // }
if( status.decoded_mentions == null ){ if( status instanceof TootStatus ){
tvMentions.setVisibility( View.GONE ); TootStatus ts = (TootStatus) status;
if( ts.decoded_mentions == null ){
tvMentions.setVisibility( View.GONE );
}else{
tvMentions.setVisibility( View.VISIBLE );
tvMentions.setText( ts.decoded_mentions );
}
}else{ }else{
tvMentions.setVisibility( View.VISIBLE ); tvMentions.setVisibility( View.GONE );
tvMentions.setText( status.decoded_mentions );
} }
// Content warning // Content warning
@ -359,28 +371,52 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
}else{ }else{
llContentWarning.setVisibility( View.VISIBLE ); llContentWarning.setVisibility( View.VISIBLE );
tvContentWarning.setText( status.decoded_spoiler_text ); tvContentWarning.setText( status.decoded_spoiler_text );
boolean cw_shown = ContentWarning.isShown( access_info.host, status.id, false ); boolean cw_shown = ContentWarning.isShown( status, false );
showContent( cw_shown ); showContent( cw_shown );
} }
if( status.media_attachments == null || status.media_attachments.isEmpty() ){ if( status instanceof TootStatus ){
flMedia.setVisibility( View.GONE ); TootStatus ts = (TootStatus) status;
}else{ if( ts.media_attachments == null || ts.media_attachments.isEmpty() ){
flMedia.setVisibility( View.VISIBLE ); flMedia.setVisibility( View.GONE );
setMedia( ivMedia1, status, 0 ); }else{
setMedia( ivMedia2, status, 1 ); flMedia.setVisibility( View.VISIBLE );
setMedia( ivMedia3, status, 2 ); setMedia( ivMedia1, ts, 0 );
setMedia( ivMedia4, status, 3 ); setMedia( ivMedia2, ts, 1 );
setMedia( ivMedia3, ts, 2 );
@SuppressWarnings("SimplifiableConditionalExpression") setMedia( ivMedia4, ts, 3 );
boolean default_shown =
column.hide_media_default ? false : @SuppressWarnings("SimplifiableConditionalExpression")
access_info.dont_hide_nsfw ? true : boolean default_shown =
! status.sensitive; column.hide_media_default ? false :
access_info.dont_hide_nsfw ? true :
// hide sensitive media ! status.sensitive;
boolean is_shown = MediaShown.isShown( access_info.host, status.id, default_shown );
btnShowMedia.setVisibility( ! is_shown ? View.VISIBLE : View.GONE ); // hide sensitive media
boolean is_shown = MediaShown.isShown( status, default_shown );
btnShowMedia.setVisibility( ! is_shown ? View.VISIBLE : View.GONE );
}
}else if( status instanceof MSPToot ){
MSPToot ts = (MSPToot) status;
if( ts.media_attachments == null || ts.media_attachments.isEmpty() ){
flMedia.setVisibility( View.GONE );
}else{
flMedia.setVisibility( View.VISIBLE );
setMedia( ivMedia1, ts, 0 );
setMedia( ivMedia2, ts, 1 );
setMedia( ivMedia3, ts, 2 );
setMedia( ivMedia4, ts, 3 );
@SuppressWarnings("SimplifiableConditionalExpression")
boolean default_shown =
column.hide_media_default ? false :
access_info.dont_hide_nsfw ? true :
! status.sensitive;
// hide sensitive media
boolean is_shown = MediaShown.isShown( status, default_shown );
btnShowMedia.setVisibility( ! is_shown ? View.VISIBLE : View.GONE );
}
} }
if( buttons_for_status != null ){ if( buttons_for_status != null ){
@ -432,11 +468,11 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
}else{ }else{
iv.setVisibility( View.VISIBLE ); iv.setVisibility( View.VISIBLE );
iv.setScaleType( activity.dont_crop_media_thumbnail ? ImageView.ScaleType.FIT_CENTER : ImageView.ScaleType.CENTER_CROP ); iv.setScaleType( activity.dont_crop_media_thumbnail ? ImageView.ScaleType.FIT_CENTER : ImageView.ScaleType.CENTER_CROP );
TootAttachment ta = status.media_attachments.get( idx ); TootAttachment ta = status.media_attachments.get( idx );
if( TextUtils.isEmpty( ta.type )){ if( TextUtils.isEmpty( ta.type ) ){
iv.setMediaType(0); iv.setMediaType( 0 );
}else{ }else{
switch( ta.type ){ switch( ta.type ){
default: default:
@ -463,22 +499,36 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
url = ta.url; url = ta.url;
} }
} }
iv.setCornerRadius( activity.pref,0f ); // 正方形じゃないせいかうまく動かない activity.density * 4f ); iv.setCornerRadius( activity.pref, 0f ); // 正方形じゃないせいかうまく動かない activity.density * 4f );
iv.setImageUrl( access_info.supplyBaseUrl( url ) );
}
}
private void setMedia( MyNetworkImageView iv, MSPToot msp_toot, int idx ){
if( idx >= msp_toot.media_attachments.size() ){
iv.setVisibility( View.GONE );
}else{
iv.setVisibility( View.VISIBLE );
iv.setScaleType( activity.dont_crop_media_thumbnail ? ImageView.ScaleType.FIT_CENTER : ImageView.ScaleType.CENTER_CROP );
String url = msp_toot.media_attachments.get( idx );
iv.setMediaType( 0 );
iv.setCornerRadius( activity.pref, 0f ); // 正方形じゃないせいかうまく動かない activity.density * 4f );
iv.setImageUrl( access_info.supplyBaseUrl( url ) ); iv.setImageUrl( access_info.supplyBaseUrl( url ) );
} }
} }
@Override public void onClick( View v ){ @Override public void onClick( View v ){
int pos = activity.nextPosition( column ) ; int pos = activity.nextPosition( column );
switch( v.getId() ){ switch( v.getId() ){
case R.id.btnHideMedia: case R.id.btnHideMedia:
MediaShown.save( access_info.host, status.id, false ); MediaShown.save( status, false );
btnShowMedia.setVisibility( View.VISIBLE ); btnShowMedia.setVisibility( View.VISIBLE );
break; break;
case R.id.btnShowMedia: case R.id.btnShowMedia:
MediaShown.save( access_info.host, status.id, true ); MediaShown.save( status, true );
btnShowMedia.setVisibility( View.GONE ); btnShowMedia.setVisibility( View.GONE );
break; break;
case R.id.ivMedia1: case R.id.ivMedia1:
@ -495,7 +545,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
break; break;
case R.id.btnContentWarning:{ case R.id.btnContentWarning:{
boolean new_shown = ( llContents.getVisibility() == View.GONE ); boolean new_shown = ( llContents.getVisibility() == View.GONE );
ContentWarning.save( access_info.host, status.id, new_shown ); ContentWarning.save( status, new_shown );
list_adapter.notifyDataSetChanged(); list_adapter.notifyDataSetChanged();
break; break;
} }
@ -504,7 +554,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
if( access_info.isPseudo() ){ if( access_info.isPseudo() ){
new DlgContextMenu( activity, column, account_thumbnail, null ).show(); new DlgContextMenu( activity, column, account_thumbnail, null ).show();
}else{ }else{
activity.openProfile( pos,access_info, account_thumbnail ); activity.openProfile( pos, access_info, account_thumbnail );
} }
break; break;
@ -512,14 +562,14 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
if( access_info.isPseudo() ){ if( access_info.isPseudo() ){
new DlgContextMenu( activity, column, account_boost, null ).show(); new DlgContextMenu( activity, column, account_boost, null ).show();
}else{ }else{
activity.openProfile( pos,access_info, account_boost ); activity.openProfile( pos, access_info, account_boost );
} }
break; break;
case R.id.llFollow: case R.id.llFollow:
if( access_info.isPseudo() ){ if( access_info.isPseudo() ){
new DlgContextMenu( activity, column, account_follow, null ).show(); new DlgContextMenu( activity, column, account_follow, null ).show();
}else{ }else{
activity.openProfile( pos,access_info, account_follow ); activity.openProfile( pos, access_info, account_follow );
} }
break; break;
case R.id.btnFollow: case R.id.btnFollow:
@ -528,13 +578,13 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
case R.id.btnSearchTag: case R.id.btnSearchTag:
if( search_tag != null ){ if( search_tag != null ){
activity.openHashTag( activity.nextPosition( column ),access_info, search_tag ); activity.openHashTag( activity.nextPosition( column ), access_info, search_tag );
}else if( gap != null ){ }else if( gap != null ){
column.startGap( gap ); column.startGap( gap );
}else if( domain_block != null ){ }else if( domain_block != null ){
final String domain = domain_block.domain; final String domain = domain_block.domain;
new AlertDialog.Builder( activity ) new AlertDialog.Builder( activity )
.setMessage( activity.getString( R.string.confirm_unblock_domain, domain) ) .setMessage( activity.getString( R.string.confirm_unblock_domain, domain ) )
.setNegativeButton( R.string.cancel, null ) .setNegativeButton( R.string.cancel, null )
.setPositiveButton( R.string.ok, new DialogInterface.OnClickListener() { .setPositiveButton( R.string.ok, new DialogInterface.OnClickListener() {
@Override public void onClick( DialogInterface dialog, int which ){ @Override public void onClick( DialogInterface dialog, int which ){
@ -544,7 +594,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
.show(); .show();
} }
break; break;
} }
} }
@ -566,22 +616,28 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
private void clickMedia( int i ){ private void clickMedia( int i ){
try{ try{
TootAttachment a = status.media_attachments.get( i ); if( status instanceof MSPToot ){
activity.openStatus( activity.nextPosition( column ), access_info, status );
String sv; }else if( status instanceof TootStatus ){
if( Pref.pref( activity ).getBoolean( Pref.KEY_PRIOR_LOCAL_URL, false ) ){ TootStatus ts = (TootStatus) status;
sv = a.url;
if( TextUtils.isEmpty( sv ) ){ TootAttachment a = ts.media_attachments.get( i );
sv = a.remote_url;
} String sv;
}else{ if( Pref.pref( activity ).getBoolean( Pref.KEY_PRIOR_LOCAL_URL, false ) ){
sv = a.remote_url;
if( TextUtils.isEmpty( sv ) ){
sv = a.url; sv = a.url;
if( TextUtils.isEmpty( sv ) ){
sv = a.remote_url;
}
}else{
sv = a.remote_url;
if( TextUtils.isEmpty( sv ) ){
sv = a.url;
}
} }
int pos = activity.nextPosition( column );
activity.openChromeTab( pos, access_info, sv, false );
} }
int pos = activity.nextPosition( column ) ;
activity.openChromeTab(pos, access_info, sv, false );
}catch( Throwable ex ){ }catch( Throwable ex ){
ex.printStackTrace(); ex.printStackTrace();
} }
@ -590,11 +646,9 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
// 簡略ビューの時だけ呼ばれる // 簡略ビューの時だけ呼ばれる
// StatusButtonsPopupを表示する // StatusButtonsPopupを表示する
void onItemClick( MyListView listView, View anchor ){ void onItemClick( MyListView listView, View anchor ){
if( status != null ){ activity.closeListItemPopup();
activity.closeListItemPopup(); activity.list_item_popup = new StatusButtonsPopup( activity, column, bSimpleList );
activity.list_item_popup = new StatusButtonsPopup( activity, column ,bSimpleList); activity.list_item_popup.show( listView, anchor, status );
activity.list_item_popup.show( listView, anchor, status );
}
} }
} }

View File

@ -6,7 +6,8 @@ import android.preference.PreferenceManager;
public class Pref { public class Pref {
static SharedPreferences pref( Context context ){
public static SharedPreferences pref( Context context ){
return PreferenceManager.getDefaultSharedPreferences( context ); return PreferenceManager.getDefaultSharedPreferences( context );
} }
@ -60,6 +61,8 @@ public class Pref {
static final String KEY_CLIENT_NAME = "client_name"; static final String KEY_CLIENT_NAME = "client_name";
public static final String KEY_MASTODON_SEARCH_PORTAL_USER_TOKEN = "mastodon_search_portal_user_token";
// 項目を追加したらAppDataExporter#importPref のswitch文も更新すること // 項目を追加したらAppDataExporter#importPref のswitch文も更新すること
} }

View File

@ -7,7 +7,7 @@ public class PrefDevice {
private static String file_name = "device"; private static String file_name = "device";
static SharedPreferences prefDevice( Context context ){ public static SharedPreferences prefDevice( Context context ){
return context.getSharedPreferences( file_name, Context.MODE_PRIVATE ); return context.getSharedPreferences( file_name, Context.MODE_PRIVATE );
} }

View File

@ -10,6 +10,8 @@ import android.widget.ImageView;
import android.widget.PopupWindow; import android.widget.PopupWindow;
import jp.juggler.subwaytooter.api.entity.TootStatus; import jp.juggler.subwaytooter.api.entity.TootStatus;
import jp.juggler.subwaytooter.api.entity.TootStatusLike;
import jp.juggler.subwaytooter.api_msp.entity.MSPToot;
import jp.juggler.subwaytooter.table.SavedAccount; import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.table.UserRelation; import jp.juggler.subwaytooter.table.UserRelation;
import jp.juggler.subwaytooter.util.LogCategory; import jp.juggler.subwaytooter.util.LogCategory;
@ -27,7 +29,7 @@ class StatusButtons implements View.OnClickListener, View.OnLongClickListener {
private final ImageView ivFollowedBy2; private final ImageView ivFollowedBy2;
private final View llFollow2; private final View llFollow2;
final boolean bSimpleList; private final boolean bSimpleList;
StatusButtons( @NonNull ActMain activity, @NonNull Column column, @NonNull View viewRoot ,boolean bSimpleList){ StatusButtons( @NonNull ActMain activity, @NonNull Column column, @NonNull View viewRoot ,boolean bSimpleList){
this.activity = activity; this.activity = activity;
@ -63,32 +65,40 @@ class StatusButtons implements View.OnClickListener, View.OnLongClickListener {
} }
private TootStatus status;
private UserRelation relation; private UserRelation relation;
private TootStatusLike status;
void bind( @NonNull TootStatus status ){ void bind( @NonNull TootStatusLike status ){
this.status = status; this.status = status;
int color_normal = Styler.getAttributeColor( activity, R.attr.colorImageButton ); int color_normal = Styler.getAttributeColor( activity, R.attr.colorImageButton );
int color_accent = Styler.getAttributeColor( activity, R.attr.colorImageButtonAccent ); int color_accent = Styler.getAttributeColor( activity, R.attr.colorImageButtonAccent );
if( TootStatus.VISIBILITY_DIRECT.equals( status.visibility ) ){ if( status instanceof MSPToot ){
setButton( btnBoost, false, color_accent, R.attr.ic_mail, "" ); setButton( btnBoost, true, color_normal, R.attr.btn_boost, "" );
}else if( TootStatus.VISIBILITY_PRIVATE.equals( status.visibility ) ){ setButton( btnFavourite, true, color_normal, R.attr.btn_favourite, "");
setButton( btnBoost, false, color_accent, R.attr.ic_lock, "" ); }else if( status instanceof TootStatus ){
}else if( activity.app_state.isBusyBoost( access_info, status ) ){ TootStatus ts = (TootStatus)status;
setButton( btnBoost, false, color_normal, R.attr.btn_refresh, "?" );
}else{ if( TootStatus.VISIBILITY_DIRECT.equals( ts.visibility ) ){
int color = ( status.reblogged ? color_accent : color_normal ); setButton( btnBoost, false, color_accent, R.attr.ic_mail, "" );
setButton( btnBoost, true, color, R.attr.btn_boost, Long.toString( status.reblogs_count ) ); }else if( TootStatus.VISIBILITY_PRIVATE.equals( ts.visibility ) ){
setButton( btnBoost, false, color_accent, R.attr.ic_lock, "" );
}else if( activity.app_state.isBusyBoost( access_info, status ) ){
setButton( btnBoost, false, color_normal, R.attr.btn_refresh, "?" );
}else{
int color = ( ts.reblogged ? color_accent : color_normal );
setButton( btnBoost, true, color, R.attr.btn_boost, Long.toString( ts.reblogs_count ) );
}
if( activity.app_state.isBusyFav( access_info, status ) ){
setButton( btnFavourite, false, color_normal, R.attr.btn_refresh, "?" );
}else{
int color = ( ts.favourited ? color_accent : color_normal );
setButton( btnFavourite, true, color, R.attr.btn_favourite, Long.toString( ts.favourites_count ) );
}
} }
if( activity.app_state.isBusyFav( access_info, status ) ){
setButton( btnFavourite, false, color_normal, R.attr.btn_refresh, "?" );
}else{
int color = ( status.favourited ? color_accent : color_normal );
setButton( btnFavourite, true, color, R.attr.btn_favourite, Long.toString( status.favourites_count ) );
}
if( status.account == null || ! activity.pref.getBoolean( Pref.KEY_SHOW_FOLLOW_BUTTON_IN_BUTTON_BAR, false ) ){ if( status.account == null || ! activity.pref.getBoolean( Pref.KEY_SHOW_FOLLOW_BUTTON_IN_BUTTON_BAR, false ) ){
llFollow2.setVisibility( View.GONE ); llFollow2.setVisibility( View.GONE );
@ -101,6 +111,7 @@ class StatusButtons implements View.OnClickListener, View.OnLongClickListener {
} }
private void setButton( Button b, boolean enabled, int color, int icon_attr, String text ){ private void setButton( Button b, boolean enabled, int color, int icon_attr, String text ){
Drawable d = Styler.getAttributeDrawable( activity, icon_attr ).mutate(); Drawable d = Styler.getAttributeDrawable( activity, icon_attr ).mutate();
d.setColorFilter( color, PorterDuff.Mode.SRC_ATOP ); d.setColorFilter( color, PorterDuff.Mode.SRC_ATOP );
@ -119,12 +130,13 @@ class StatusButtons implements View.OnClickListener, View.OnLongClickListener {
activity.openStatus( activity.nextPosition( column ), access_info, status ); activity.openStatus( activity.nextPosition( column ), access_info, status );
break; break;
case R.id.btnReply: case R.id.btnReply:
if( access_info.isPseudo() ){ if( status instanceof TootStatus && !access_info.isPseudo() ){
activity.openReplyFromAnotherAccount( access_info, status ); activity.performReply( access_info, (TootStatus)status );
}else{ }else{
activity.performReply( access_info, status ,false); activity.openReplyFromAnotherAccount( status );
} }
break; break;
case R.id.btnBoost: case R.id.btnBoost:
if( access_info.isPseudo() ){ if( access_info.isPseudo() ){
activity.openBoostFromAnotherAccount( access_info, status ); activity.openBoostFromAnotherAccount( access_info, status );
@ -159,8 +171,10 @@ class StatusButtons implements View.OnClickListener, View.OnLongClickListener {
break; break;
case R.id.btnFollow2: case R.id.btnFollow2:
if( access_info.isPseudo() ){ if( status == null || status.account == null ){
activity.openFollowFromAnotherAccount( access_info, status ); // 何もしない
}else if( access_info.isPseudo() ){
activity.openFollowFromAnotherAccount( access_info, status.account );
}else if( relation.blocking || relation.muting ){ }else if( relation.blocking || relation.muting ){
// 何もしない // 何もしない
}else if( relation.following || relation.requested ){ }else if( relation.following || relation.requested ){
@ -184,11 +198,13 @@ class StatusButtons implements View.OnClickListener, View.OnLongClickListener {
break; break;
case R.id.btnFollow2: case R.id.btnFollow2:
activity.openFollowFromAnotherAccount( access_info, status ); if( status != null && status.account != null ){
activity.openFollowFromAnotherAccount( access_info, status.account );
}
break; break;
case R.id.btnReply: case R.id.btnReply:
activity.openReplyFromAnotherAccount( access_info, status ); activity.openReplyFromAnotherAccount( status );
break; break;
} }

View File

@ -8,10 +8,11 @@ import android.view.Gravity;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.ListView;
import android.widget.PopupWindow; import android.widget.PopupWindow;
import jp.juggler.subwaytooter.api.entity.TootStatus; import jp.juggler.subwaytooter.api.entity.TootStatus;
import jp.juggler.subwaytooter.api.entity.TootStatusLike;
import jp.juggler.subwaytooter.api_msp.entity.MSPToot;
import jp.juggler.subwaytooter.view.MyListView; import jp.juggler.subwaytooter.view.MyListView;
class StatusButtonsPopup { class StatusButtonsPopup {
@ -34,7 +35,7 @@ class StatusButtonsPopup {
} }
} }
void show( final MyListView listView, View anchor, TootStatus status ){ void show( final MyListView listView, View anchor, TootStatusLike status ){
// //
window = new PopupWindow( activity ); window = new PopupWindow( activity );

View File

@ -119,9 +119,9 @@ class StreamReader {
private Object parsePayload( String event, JSONObject obj ){ private Object parsePayload( String event, JSONObject obj ){
try{ try{
if( "update".equals( event ) ){ if( "update".equals( event ) ){
return TootStatus.parse( log, access_info, new JSONObject( obj.optString( "payload" ) ) ); return TootStatus.parse( log, access_info, access_info.host,new JSONObject( obj.optString( "payload" ) ) );
}else if( "notification".equals( event ) ){ }else if( "notification".equals( event ) ){
return TootNotification.parse( log, access_info, new JSONObject( obj.optString( "payload" ) ) ); return TootNotification.parse( log, access_info, access_info.host,new JSONObject( obj.optString( "payload" ) ) );
}else if( "delete".equals( event ) ){ }else if( "delete".equals( event ) ){
return obj.optLong( "payload", - 1L ); return obj.optLong( "payload", - 1L );
} }

View File

@ -143,7 +143,7 @@ public class TootAccount {
return result; return result;
} }
private static CharSequence filterDisplayName( String sv ){ public static CharSequence filterDisplayName( String sv ){
// decode HTML entity // decode HTML entity
sv = HTMLDecoder.decodeEntity(sv ); sv = HTMLDecoder.decodeEntity(sv );
@ -155,4 +155,14 @@ public class TootAccount {
return Emojione.decodeEmoji( sv ) ; return Emojione.decodeEmoji( sv ) ;
} }
public String getAcctHost(){
try{
int pos = acct.indexOf( '@' );
if( pos != - 1 ) return acct.substring( pos + 1 );
}catch(Throwable ignored){
}
return null;
}
} }

View File

@ -13,12 +13,12 @@ public class TootContext {
// descendants The descendants of the status in the conversation, as a list of Statuses // descendants The descendants of the status in the conversation, as a list of Statuses
public TootStatus.List descendants; public TootStatus.List descendants;
public static TootContext parse( LogCategory log, LinkClickContext account,JSONObject src ){ public static TootContext parse( LogCategory log, LinkClickContext lcc,String status_host,JSONObject src ){
if( src==null) return null; if( src==null) return null;
try{ try{
TootContext dst = new TootContext(); TootContext dst = new TootContext();
dst.ancestors = TootStatus.parseList( log, account,src.optJSONArray( "ancestors" ) ); dst.ancestors = TootStatus.parseList( log, lcc,status_host,src.optJSONArray( "ancestors" ) );
dst.descendants = TootStatus.parseList(log, account, src.optJSONArray( "descendants" ) ); dst.descendants = TootStatus.parseList(log, lcc, status_host,src.optJSONArray( "descendants" ) );
return dst; return dst;
}catch( Throwable ex ){ }catch( Throwable ex ){
ex.printStackTrace(); ex.printStackTrace();

View File

@ -37,7 +37,7 @@ public class TootNotification extends TootId {
public JSONObject json; public JSONObject json;
public static TootNotification parse( LogCategory log, LinkClickContext accopunt, JSONObject src ){ public static TootNotification parse( LogCategory log, LinkClickContext lcc,String status_host, JSONObject src ){
if( src == null ) return null; if( src == null ) return null;
try{ try{
TootNotification dst = new TootNotification(); TootNotification dst = new TootNotification();
@ -45,8 +45,8 @@ public class TootNotification extends TootId {
dst.id = src.optLong( "id" ); dst.id = src.optLong( "id" );
dst.type = Utils.optStringX( src, "type" ); dst.type = Utils.optStringX( src, "type" );
dst.created_at = Utils.optStringX( src, "created_at" ); dst.created_at = Utils.optStringX( src, "created_at" );
dst.account = TootAccount.parse( log, accopunt, src.optJSONObject( "account" ) ); dst.account = TootAccount.parse( log, lcc, src.optJSONObject( "account" ) );
dst.status = TootStatus.parse( log, accopunt, src.optJSONObject( "status" ) ); dst.status = TootStatus.parse( log, lcc, status_host,src.optJSONObject( "status" ) );
dst.time_created_at = TootStatus.parseTime( log, dst.created_at ); dst.time_created_at = TootStatus.parseTime( log, dst.created_at );
@ -69,7 +69,7 @@ public class TootNotification extends TootId {
} }
@NonNull @NonNull
public static List parseList( LogCategory log, LinkClickContext account, JSONArray array ){ public static List parseList( LogCategory log, LinkClickContext lcc,String status_host, JSONArray array ){
List result = new List(); List result = new List();
if( array != null ){ if( array != null ){
int array_size = array.length(); int array_size = array.length();
@ -77,7 +77,7 @@ public class TootNotification extends TootId {
for( int i = 0 ; i < array_size ; ++ i ){ for( int i = 0 ; i < array_size ; ++ i ){
JSONObject src = array.optJSONObject( i ); JSONObject src = array.optJSONObject( i );
if( src == null ) continue; if( src == null ) continue;
TootNotification item = parse( log, account, src ); TootNotification item = parse( log, lcc,status_host, src );
if( item != null ) result.add( item ); if( item != null ) result.add( item );
} }
} }

View File

@ -18,12 +18,12 @@ public class TootResults {
// An array of matched hashtags, as strings // An array of matched hashtags, as strings
public ArrayList< String > hashtags; public ArrayList< String > hashtags;
public static TootResults parse( LogCategory log, LinkClickContext account, JSONObject src ){ public static TootResults parse( LogCategory log, LinkClickContext lcc,String status_host, JSONObject src ){
try{ try{
if( src == null ) return null; if( src == null ) return null;
TootResults dst = new TootResults(); TootResults dst = new TootResults();
dst.accounts = TootAccount.parseList( log, account, src.optJSONArray( "accounts" ) ); dst.accounts = TootAccount.parseList( log, lcc, src.optJSONArray( "accounts" ) );
dst.statuses = TootStatus.parseList( log, account, src.optJSONArray( "statuses" ) ); dst.statuses = TootStatus.parseList( log, lcc, status_host,src.optJSONArray( "statuses" ) );
dst.hashtags = Utils.parseStringArray( log, src.optJSONArray( "hashtags" ) ); dst.hashtags = Utils.parseStringArray( log, src.optJSONArray( "hashtags" ) );
return dst; return dst;
}catch( Throwable ex ){ }catch( Throwable ex ){

View File

@ -2,6 +2,7 @@ package jp.juggler.subwaytooter.api.entity;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.design.widget.NavigationView;
import android.text.Spannable; import android.text.Spannable;
import android.text.TextUtils; import android.text.TextUtils;
@ -19,13 +20,14 @@ import java.util.TimeZone;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.util.HTMLDecoder; import jp.juggler.subwaytooter.util.HTMLDecoder;
import jp.juggler.subwaytooter.util.LinkClickContext; import jp.juggler.subwaytooter.util.LinkClickContext;
import jp.juggler.subwaytooter.util.LogCategory; import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.Utils; import jp.juggler.subwaytooter.util.Utils;
import jp.juggler.subwaytooter.util.WordTrieTree; import jp.juggler.subwaytooter.util.WordTrieTree;
public class TootStatus extends TootId { public class TootStatus extends TootStatusLike {
public static class List extends ArrayList< TootStatus > { public static class List extends ArrayList< TootStatus > {
@ -44,12 +46,9 @@ public class TootStatus extends TootId {
// A Fediverse-unique resource ID // A Fediverse-unique resource ID
public String uri; public String uri;
//URL to the status page (can be remote)
public String url;
// The TootAccount which posted the status
@Nullable public TootAccount account;
// null or the ID of the status it replies to // null or the ID of the status it replies to
public String in_reply_to_id; public String in_reply_to_id;
@ -58,31 +57,12 @@ public class TootStatus extends TootId {
// null or the reblogged Status // null or the reblogged Status
public TootStatus reblog; public TootStatus reblog;
// Body of the status; this will contain HTML (remote HTML already sanitized)
public String content;
// The time the status was created // The time the status was created
public String created_at; public String created_at;
//The number of reblogs for the status
public long reblogs_count;
//The number of favourites for the status
public long favourites_count;
// Whether the authenticated user has reblogged the status
public boolean reblogged;
// Whether the authenticated user has favourited the status
public boolean favourited;
//Whether media attachments should be hidden by default
public boolean sensitive;
//If not empty, warning text that should be displayed before the actual content
public String spoiler_text;
//One of: public, unlisted, private, direct //One of: public, unlisted, private, direct
public String visibility; public String visibility;
public static final String VISIBILITY_PUBLIC = "public"; public static final String VISIBILITY_PUBLIC = "public";
@ -99,13 +79,9 @@ public class TootStatus extends TootId {
//An array of Tags //An array of Tags
public TootTag.List tags; public TootTag.List tags;
//Application from which the status was posted
public TootApplication application;
public long time_created_at; public long time_created_at;
public Spannable decoded_content;
public Spannable decoded_spoiler_text;
// public Spannable decoded_tags; // public Spannable decoded_tags;
public Spannable decoded_mentions; public Spannable decoded_mentions;
@ -113,7 +89,7 @@ public class TootStatus extends TootId {
public boolean conversation_main; public boolean conversation_main;
public static TootStatus parse( LogCategory log, LinkClickContext account, JSONObject src ){ public static TootStatus parse( @NonNull LogCategory log, @NonNull LinkClickContext lcc, @NonNull String status_host, JSONObject src ){
if( src == null ) return null; if( src == null ) return null;
@ -122,12 +98,13 @@ public class TootStatus extends TootId {
status.json = src; status.json = src;
// log.d( "parse: %s", src.toString() ); // log.d( "parse: %s", src.toString() );
status.id = src.optLong( "id" ); status.id = src.optLong( "id" );
status.status_host = status_host;
status.uri = Utils.optStringX( src, "uri" ); status.uri = Utils.optStringX( src, "uri" );
status.url = Utils.optStringX( src, "url" ); status.url = Utils.optStringX( src, "url" );
status.account = TootAccount.parse( log, account, src.optJSONObject( "account" ) ); status.account = TootAccount.parse( log, lcc, src.optJSONObject( "account" ) );
status.in_reply_to_id = Utils.optStringX( src, "in_reply_to_id" ); // null 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.in_reply_to_account_id = Utils.optStringX( src, "in_reply_to_account_id" ); // null
status.reblog = TootStatus.parse( log, account, src.optJSONObject( "reblog" ) ); status.reblog = TootStatus.parse( log, lcc,status_host, src.optJSONObject( "reblog" ) );
status.content = Utils.optStringX( src, "content" ); status.content = Utils.optStringX( src, "content" );
status.created_at = Utils.optStringX( src, "created_at" ); // "2017-04-16T09:37:14.000Z" status.created_at = Utils.optStringX( src, "created_at" ); // "2017-04-16T09:37:14.000Z"
status.reblogs_count = src.optLong( "reblogs_count" ); status.reblogs_count = src.optLong( "reblogs_count" );
@ -143,12 +120,12 @@ public class TootStatus extends TootId {
status.application = TootApplication.parse( log, src.optJSONObject( "application" ) ); // null status.application = TootApplication.parse( log, src.optJSONObject( "application" ) ); // null
status.time_created_at = parseTime( log, status.created_at ); status.time_created_at = parseTime( log, status.created_at );
status.decoded_content = HTMLDecoder.decodeHTML( account, status.content ,true,status.media_attachments ); status.decoded_content = HTMLDecoder.decodeHTML( lcc, status.content ,true,status.media_attachments );
// status.decoded_tags = HTMLDecoder.decodeTags( account,status.tags ); // status.decoded_tags = HTMLDecoder.decodeTags( account,status.tags );
status.decoded_mentions = HTMLDecoder.decodeMentions( account, status.mentions ); status.decoded_mentions = HTMLDecoder.decodeMentions( lcc, status.mentions );
if( ! TextUtils.isEmpty( status.spoiler_text ) ){ if( ! TextUtils.isEmpty( status.spoiler_text ) ){
status.decoded_spoiler_text = HTMLDecoder.decodeHTML( account, status.spoiler_text ,true,status.media_attachments); status.decoded_spoiler_text = HTMLDecoder.decodeHTML( lcc, status.spoiler_text ,true,status.media_attachments);
} }
return status; return status;
}catch( Throwable ex ){ }catch( Throwable ex ){
@ -159,7 +136,7 @@ public class TootStatus extends TootId {
} }
@NonNull @NonNull
public static List parseList( LogCategory log, LinkClickContext account, JSONArray array ){ public static List parseList( @NonNull LogCategory log, @NonNull LinkClickContext lcc, @NonNull String status_host, JSONArray array ){
List result = new List(); List result = new List();
if( array != null ){ if( array != null ){
int array_size = array.length(); int array_size = array.length();
@ -167,7 +144,7 @@ public class TootStatus extends TootId {
for( int i = 0 ; i < array_size ; ++ i ){ for( int i = 0 ; i < array_size ; ++ i ){
JSONObject src = array.optJSONObject( i ); JSONObject src = array.optJSONObject( i );
if( src == null ) continue; if( src == null ) continue;
TootStatus item = parse( log, account, src ); TootStatus item = parse( log, lcc,status_host, src );
if( item != null ) result.add( item ); if( item != null ) result.add( item );
} }
} }

View File

@ -0,0 +1,45 @@
package jp.juggler.subwaytooter.api.entity;
import android.support.annotation.Nullable;
import android.text.Spannable;
public abstract class TootStatusLike extends TootId{
//URL to the status page (can be remote)
public String url;
public String status_host;
// The TootAccount which posted the status
@Nullable public TootAccount account;
//The number of reblogs for the status
public long reblogs_count;
//The number of favourites for the status
public long favourites_count;
// Whether the authenticated user has reblogged the status
public boolean reblogged;
// Whether the authenticated user has favourited the status
public boolean favourited;
//Whether media attachments should be hidden by default
public boolean sensitive;
//If not empty, warning text that should be displayed before the actual content
public String spoiler_text;
public Spannable decoded_spoiler_text;
// Body of the status; this will contain HTML (remote HTML already sanitized)
public String content;
public Spannable decoded_content;
//Application from which the status was posted
public TootApplication application;
}

View File

@ -0,0 +1,21 @@
package jp.juggler.subwaytooter.api_msp;
import org.json.JSONArray;
import jp.juggler.subwaytooter.api.TootApiResult;
import okhttp3.Response;
public class MSPApiResult extends TootApiResult {
MSPApiResult( String error ){
super( error );
}
MSPApiResult( Response response, String json, JSONArray array ){
super(null);
this.json = json;
this.array = array;
this.response = response;
}
}

View File

@ -0,0 +1,176 @@
package jp.juggler.subwaytooter.api_msp;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import jp.juggler.subwaytooter.App1;
import jp.juggler.subwaytooter.Pref;
import jp.juggler.subwaytooter.R;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.Utils;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class MSPClient {
static final LogCategory log = new LogCategory("MSPClient");
private static final String url_token = "http://mastodonsearch.jp/api/v1.0.1/utoken";
private static final String url_search = "http://mastodonsearch.jp/api/v1.0.1/cross";
private static final String api_key = "e53de7f66130208f62d1808672bf6320523dcd0873dc69bc";
private static final OkHttpClient ok_http_client = App1.ok_http_client;
public interface Callback {
boolean isApiCancelled();
void publishApiProgress( String s );
}
public static MSPApiResult search( @NonNull Context context, @NonNull String query, @NonNull String max_id ,@NonNull Callback callback ){
// ユーザトークンを読む
SharedPreferences pref = Pref.pref(context);
String user_token = pref.getString(Pref.KEY_MASTODON_SEARCH_PORTAL_USER_TOKEN,null);
Response response;
for(;;){
// ユーザトークンがなければ取得する
if( TextUtils.isEmpty( user_token ) ){
callback.publishApiProgress( "get MSP user token..." );
String url = url_token + "?apikey=" + Uri.encode( api_key );
try{
Request request = new Request.Builder()
.url( url )
.build();
Call call = ok_http_client.newCall( request );
response = call.execute();
}catch( Throwable ex ){
ex.printStackTrace();
return new MSPApiResult( Utils.formatError( ex, context.getResources(), R.string.network_error ) );
}
if( callback.isApiCancelled() ) return null;
if( ! response.isSuccessful() ){
if( response.code() >= 400 ){
try{
String json = response.body().string();
JSONObject object = new JSONObject( json );
JSONObject error = object.getJSONObject( "error" );
return new MSPApiResult( String.format( "API returns error. %s: %s"
, error.optString( "type" )
, error.optString( "detail" )
) );
}catch( Throwable ex ){
ex.printStackTrace();
return new MSPApiResult( Utils.formatError( ex, "API returns error response %s, but can't parse response body.", response.code() ) );
}
}else{
return new MSPApiResult( context.getString( R.string.network_error_arg, response ) );
}
}
try{
//noinspection ConstantConditions
String json = response.body().string();
JSONObject object = new JSONObject( json );
user_token = object.getJSONObject( "result" ).getString( "token" );
if( TextUtils.isEmpty( user_token ) ){
return new MSPApiResult( String.format( "Can't get MSP user token. response=%s", json ) );
}else{
pref.edit().putString( Pref.KEY_MASTODON_SEARCH_PORTAL_USER_TOKEN, user_token ).apply();
}
}catch( Throwable ex ){
ex.printStackTrace();
return new MSPApiResult( Utils.formatError( ex, "API data error" ) );
}
}
// ユーザトークンを使って検索APIを呼び出す
{
callback.publishApiProgress( "waiting search result..." );
String url = url_search
+ "?apikey=" + Uri.encode( api_key )
+ "&utoken=" + Uri.encode( user_token )
+ "&max=" + Uri.encode( max_id )
+ "&q=" + Uri.encode( query );
try{
Request request = new Request.Builder()
.url( url )
.build();
Call call = ok_http_client.newCall( request );
response = call.execute();
}catch( Throwable ex ){
ex.printStackTrace();
return new MSPApiResult( Utils.formatError( ex, context.getResources(), R.string.network_error ) );
}
if( callback.isApiCancelled() ) return null;
if( ! response.isSuccessful() ){
if( response.code() >= 400 ){
try{
String json = response.body().string();
JSONObject object = new JSONObject( json );
JSONObject error = object.getJSONObject( "error" );
// ユーザトークンがダメなら生成しなおす
if( "utoken".equals( error.optString( "detail" ) ) ){
user_token = null;
continue;
}
return new MSPApiResult( String.format( "API returns error. %s: %s"
, error.optString( "type" )
, error.optString( "detail" )
) );
}catch( Throwable ex ){
ex.printStackTrace();
return new MSPApiResult( Utils.formatError( ex, "API returns error response %s, but can't parse response body.", response.code() ) );
}
}else{
return new MSPApiResult( context.getString( R.string.network_error_arg, response ) );
}
}
try{
//noinspection ConstantConditions
String json = response.body().string();
JSONArray array = new JSONArray( json );
return new MSPApiResult( response, json, array );
}catch( Throwable ex ){
ex.printStackTrace();
return new MSPApiResult( Utils.formatError( ex, "API data error" ) );
}
}
}
}
public static String getMaxId( JSONArray array, String max_id ){
// max_id の更新
int size = array.length();
if( size > 0 ){
JSONObject item = array.optJSONObject( size - 1 );
if( item != null ){
String sv = item.optString( "msp_id" );
if( ! TextUtils.isEmpty( sv ) ){
return sv;
}
}
}
return max_id;
}
}

View File

@ -0,0 +1,159 @@
package jp.juggler.subwaytooter.api_msp.entity;
import android.text.TextUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jp.juggler.subwaytooter.api.entity.TootAccount;
import jp.juggler.subwaytooter.api.entity.TootStatusLike;
import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.util.HTMLDecoder;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.Utils;
public class MSPToot extends TootStatusLike {
public static class List extends ArrayList<MSPToot> {
}
private static final Pattern reAccountUrl = Pattern.compile("\\Ahttps://([^/#?]+)/@([^/#?]+)\\z");
private static TootAccount parseAccount( LogCategory log, SavedAccount access_info, JSONObject src ){
if( src == null ) return null;
TootAccount dst = new TootAccount();
dst.url = Utils.optStringX( src, "url" );
dst.username = Utils.optStringX( src, "username" );
dst.avatar = dst.avatar_static = Utils.optStringX( src, "avatar" );
String sv = Utils.optStringX( src, "display_name" );
if( TextUtils.isEmpty( sv ) ){
dst.display_name = dst.username;
}else{
dst.display_name = TootAccount.filterDisplayName( sv );
}
dst.id = src.optLong( "id" );
dst.note = HTMLDecoder.decodeHTML( access_info, Utils.optStringX( src, "note" ), true, null );
if( TextUtils.isEmpty( dst.url ) ){
log.e( "parseAccount: missing url" );
return null;
}
Matcher m = reAccountUrl.matcher( dst.url );
if( ! m.find() ){
log.e( "parseAccount: not account url: %s", dst.url );
return null;
}else{
dst.acct = dst.username + "@" + m.group( 1 );
}
return dst;
}
// private static final Pattern reTime = Pattern.compile( "\\A(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)" );
//
// private static final TimeZone tz_tokyo = TimeZone.getTimeZone( "Asia/Tokyo" );
//
// private static long parseMSPTime( LogCategory log, String strTime ){
// if( ! TextUtils.isEmpty( strTime ) ){
// try{
// Matcher m = reTime.matcher( strTime );
// if( ! m.find() ){
// log.d( "!!invalid time format: %s", strTime );
// }else{
// GregorianCalendar g = new GregorianCalendar( tz_tokyo );
// g.set(
// Utils.parse_int( m.group( 1 ), 1 ),
// Utils.parse_int( m.group( 2 ), 1 ) - 1,
// Utils.parse_int( m.group( 3 ), 1 ),
// Utils.parse_int( m.group( 4 ), 0 ),
// Utils.parse_int( m.group( 5 ), 0 ),
// Utils.parse_int( m.group( 6 ), 0 )
// );
// g.set( Calendar.MILLISECOND, 0 );
// return g.getTimeInMillis();
// }
// }catch( Throwable ex ){// ParseException, ArrayIndexOutOfBoundsException
// ex.printStackTrace();
// log.e( ex, "parseMSPTime failed. src=%s", strTime );
// }
// }
// return 0L;
// }
public String created_at;
public ArrayList<String> media_attachments;
public long msp_id;
private static MSPToot parse( LogCategory log, SavedAccount access_info,JSONObject src ){
if( src == null ) return null;
MSPToot dst = new MSPToot();
dst.account =parseAccount( log, access_info, src.optJSONObject( "account" ));
if( dst.account == null ){
log.e("missing status account");
return null;
}
dst.url = Utils.optStringX( src, "url" );
dst.status_host = dst.account.getAcctHost();
dst.id = src.optLong( "id" ,-1L );
if( TextUtils.isEmpty( dst.url ) || TextUtils.isEmpty( dst.status_host ) || dst.id == -1L ){
log.e("missing status url or host or id");
return null;
}
dst.created_at = Utils.optStringX( src, "created_at" );
JSONArray a = src.optJSONArray( "media_attachments" );
if( a != null && a.length() > 0 ){
dst.media_attachments = new ArrayList<>();
for(int i=0,ie=a.length();i<ie;++i){
String sv = Utils.optStringX( a,i );
if(!TextUtils.isEmpty( sv )){
dst.media_attachments.add( sv );
}
}
}
dst.msp_id = src.optLong("msp_id");
dst.sensitive = (src.optInt( "sensitive" ,0) != 0);
dst.spoiler_text = Utils.optStringX( src, "spoiler_text" );
if( ! TextUtils.isEmpty( dst.spoiler_text ) ){
dst.decoded_spoiler_text = HTMLDecoder.decodeHTML( access_info, dst.spoiler_text ,true,null);
}
dst.content = Utils.optStringX( src, "content" );
dst.decoded_content = HTMLDecoder.decodeHTML( access_info, dst.content ,true,null );
return dst;
}
public static List parseList( LogCategory log, SavedAccount access_info,JSONArray array ){
List list = new List();
for(int i=0,ie=array.length();i<ie;++i){
JSONObject src = array.optJSONObject( i );
if( src == null ) continue;
MSPToot item = parse( log, access_info,src );
if( item == null ) continue;
list.add( item );
}
return list;
}
}

View File

@ -3,8 +3,10 @@ package jp.juggler.subwaytooter.table;
import android.content.ContentValues; import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.NonNull;
import jp.juggler.subwaytooter.App1; import jp.juggler.subwaytooter.App1;
import jp.juggler.subwaytooter.api.entity.TootStatusLike;
import jp.juggler.subwaytooter.util.LogCategory; import jp.juggler.subwaytooter.util.LogCategory;
public class ContentWarning { public class ContentWarning {
@ -40,9 +42,9 @@ public class ContentWarning {
} }
private static final String[] projection_shown = new String[]{COL_SHOWN}; private static final String[] projection_shown = new String[]{COL_SHOWN};
public static boolean isShown( String host,long status_id ,boolean default_value ){ public static boolean isShown( @NonNull TootStatusLike status , boolean default_value ){
try{ try{
Cursor cursor = App1.getDB().query( table, projection_shown, "h=? and si=?", new String[]{host, Long.toString(status_id) }, null, null, null ); Cursor cursor = App1.getDB().query( table, projection_shown, "h=? and si=?", new String[]{status.status_host, Long.toString(status.id) }, null, null, null );
try{ try{
if( cursor.moveToFirst() ){ if( cursor.moveToFirst() ){
int iv = cursor.getInt( cursor.getColumnIndex( COL_SHOWN ) ); int iv = cursor.getInt( cursor.getColumnIndex( COL_SHOWN ) );
@ -57,13 +59,13 @@ public class ContentWarning {
return default_value ; return default_value ;
} }
public static void save( String host,long status_id ,boolean is_shown ){ public static void save( @NonNull TootStatusLike status ,boolean is_shown ){
try{ try{
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
ContentValues cv = new ContentValues(); ContentValues cv = new ContentValues();
cv.put( COL_HOST, host ); cv.put( COL_HOST, status.status_host );
cv.put( COL_STATUS_ID, status_id ); cv.put( COL_STATUS_ID, status.id );
cv.put( COL_SHOWN, is_shown ? 1:0 ); cv.put( COL_SHOWN, is_shown ? 1:0 );
cv.put( COL_TIME_SAVE, now ); cv.put( COL_TIME_SAVE, now );
App1.getDB().replace( table, null, cv ); App1.getDB().replace( table, null, cv );

View File

@ -3,8 +3,10 @@ package jp.juggler.subwaytooter.table;
import android.content.ContentValues; import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.NonNull;
import jp.juggler.subwaytooter.App1; import jp.juggler.subwaytooter.App1;
import jp.juggler.subwaytooter.api.entity.TootStatusLike;
import jp.juggler.subwaytooter.util.LogCategory; import jp.juggler.subwaytooter.util.LogCategory;
public class MediaShown { public class MediaShown {
@ -41,9 +43,9 @@ public class MediaShown {
private static final String[] projection_shown = new String[]{COL_SHOWN}; private static final String[] projection_shown = new String[]{COL_SHOWN};
public static boolean isShown( String host,long status_id ,boolean default_value ){ public static boolean isShown( @NonNull TootStatusLike status , boolean default_value ){
try{ try{
Cursor cursor = App1.getDB().query( table, projection_shown, "h=? and si=?", new String[]{host, Long.toString(status_id) }, null, null, null ); Cursor cursor = App1.getDB().query( table, projection_shown, "h=? and si=?", new String[]{ status.status_host, Long.toString(status.id) }, null, null, null );
try{ try{
if( cursor.moveToFirst() ){ if( cursor.moveToFirst() ){
return ( 0 != cursor.getInt( cursor.getColumnIndex( COL_SHOWN ) ) ); return ( 0 != cursor.getInt( cursor.getColumnIndex( COL_SHOWN ) ) );
@ -57,13 +59,13 @@ public class MediaShown {
return default_value ; return default_value ;
} }
public static void save( String host,long status_id ,boolean is_shown ){ public static void save( @NonNull TootStatusLike status ,boolean is_shown ){
try{ try{
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
ContentValues cv = new ContentValues(); ContentValues cv = new ContentValues();
cv.put( COL_HOST, host ); cv.put( COL_HOST, status.status_host );
cv.put( COL_STATUS_ID, status_id ); cv.put( COL_STATUS_ID, status.id );
cv.put( COL_SHOWN, is_shown ? 1:0 ); cv.put( COL_SHOWN, is_shown ? 1:0 );
cv.put( COL_TIME_SAVE, now ); cv.put( COL_TIME_SAVE, now );
App1.getDB().replace( table, null, cv ); App1.getDB().replace( table, null, cv );

View File

@ -191,6 +191,30 @@ public class SavedAccount extends TootAccount implements LinkClickContext {
private SavedAccount(){ private SavedAccount(){
} }
// 横断検索用の何とも紐ついていないアカウント
// 保存しない
private static SavedAccount na_account;
public static SavedAccount getNA(){
if( na_account == null ){
SavedAccount dst = new SavedAccount();
dst.db_id = - 1L;
dst.username = "?";
dst.acct = "?@?";
dst.host = "?";
dst.notification_follow = false;
dst.notification_favourite = false;
dst.notification_boost = false;
dst.notification_mention = false;
na_account = dst;
}
return na_account;
}
public boolean isNA(){
return acct.equals( "?@?" );
}
private static SavedAccount parse( Cursor cursor ) throws JSONException{ private static SavedAccount parse( Cursor cursor ) throws JSONException{
JSONObject src = new JSONObject( cursor.getString( cursor.getColumnIndex( COL_ACCOUNT ) ) ); JSONObject src = new JSONObject( cursor.getString( cursor.getColumnIndex( COL_ACCOUNT ) ) );
@ -475,4 +499,5 @@ public class SavedAccount extends TootAccount implements LinkClickContext {
return 0L; return 0L;
} }
} }

View File

@ -128,7 +128,7 @@ public class UserRelation {
public boolean muting; public boolean muting;
public boolean requested; public boolean requested;
private UserRelation(){ public UserRelation(){
} }
private static final LruCache< String, UserRelation > mMemoryCache = new LruCache<>( 2048 ); private static final LruCache< String, UserRelation > mMemoryCache = new LruCache<>( 2048 );

View File

@ -6,6 +6,7 @@ import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
@ -1107,4 +1108,23 @@ public class Utils {
} }
return sb.toString(); return sb.toString();
} }
public static byte[] loadRawResource(Context context,int res_id){
try{
InputStream is = context.getResources().openRawResource( res_id );
try{
ByteArrayOutputStream bao = new ByteArrayOutputStream();
IOUtils.copy( is, bao );
return bao.toByteArray();
}finally{
IOUtils.closeQuietly( is );
}
}catch( Throwable ex ){
ex.printStackTrace();
}
return null;
}
} }

View File

@ -244,6 +244,17 @@
</LinearLayout> </LinearLayout>
</jp.juggler.subwaytooter.view.MaxHeightScrollView> </jp.juggler.subwaytooter.view.MaxHeightScrollView>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="0dp"
android:paddingEnd="12dp"
android:paddingStart="12dp"
android:paddingTop="3dp"
android:background="?attr/colorSearchFormBackground"
android:id="@+id/tvSearchDesc"
/>
<RelativeLayout <RelativeLayout
android:id="@+id/llSearch" android:id="@+id/llSearch"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -92,6 +92,16 @@
<!--android:title="Tools"/>--> <!--android:title="Tools"/>-->
</menu> </menu>
</item> </item>
<item android:title="@string/toot_search">
<menu>
<item
android:id="@+id/mastodon_search_portal"
android:icon="?attr/ic_search"
android:title="@string/mastodon_search_portal"/>
</menu>
</item>
<item android:title="@string/setting"> <item android:title="@string/setting">
<menu> <menu>

View File

@ -0,0 +1 @@
Toot search Powered by <a href="http://mastodonsearch.jp/">Mastodon Search Portal</a>, it indexes instances in Japan.<br/>If you want to remove your toot from search result, please contact to <a href="https://mstdn.jp/@mastodonsearch">@mastodonsearch@mstdn.jp</a>.

View File

@ -0,0 +1 @@
powered by <a href="http://mastodonsearch.jp/">マストドン検索ポータル</a><br/>検索結果からあなたのトゥートを除外したい場合、<a href="https://mstdn.jp/@mastodonsearch">@mastodonsearch@mstdn.jp</a>に連絡してください。

View File

@ -366,8 +366,13 @@
<string name="text_to_speech_shutdown">TextToSpeech shutdown…</string> <string name="text_to_speech_shutdown">TextToSpeech shutdown…</string>
<string name="show_post_button_bar_top">Show buttons bar at the top of posting screen</string> <string name="show_post_button_bar_top">Show buttons bar at the top of posting screen</string>
<string name="client_name">Client name (access token update required)</string> <string name="client_name">Client name (access token update required)</string>
<string name="toot_search">Toot search</string>
<string name="mastodon_search_portal">Mastodon Search Portal(JP)</string>
<string name="search_desc_mastodon_api">Account/Hashtag search using Mastodon API.</string>
<string name="toot_search_of">Toot Rechercher \"%1$s\"</string>
<string name="progress_synchronize_toot">Syncing Toot…</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_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_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>--> <!--<string name="abc_action_bar_up_description">Revenir en haut de la page</string>-->

View File

@ -653,5 +653,10 @@
<string name="text_to_speech_shutdown">TextToSpeechの後処理…</string> <string name="text_to_speech_shutdown">TextToSpeechの後処理…</string>
<string name="show_post_button_bar_top">投稿画面のボタンバーを上端に表示</string> <string name="show_post_button_bar_top">投稿画面のボタンバーを上端に表示</string>
<string name="client_name">クライアント名(アクセストークンの更新が必要)</string> <string name="client_name">クライアント名(アクセストークンの更新が必要)</string>
<string name="toot_search">トゥート検索</string>
<string name="mastodon_search_portal">マストドン検索ポータル(JP)</string>
<string name="search_desc_mastodon_api">アカウントやハッシュタグをマストドンのAPIで検索します。</string>
<string name="toot_search_of">トゥート検索:%1$s</string>
<string name="progress_synchronize_toot">トゥートを同期してます…</string>
</resources> </resources>

View File

@ -361,4 +361,10 @@
<string name="text_to_speech_shutdown">TextToSpeech shutdown…</string> <string name="text_to_speech_shutdown">TextToSpeech shutdown…</string>
<string name="show_post_button_bar_top">Show buttons bar at the top of posting screen</string> <string name="show_post_button_bar_top">Show buttons bar at the top of posting screen</string>
<string name="client_name">Client name (access token update required)</string> <string name="client_name">Client name (access token update required)</string>
<string name="toot_search">Toot search</string>
<string name="mastodon_search_portal">Mastodon Search Portal(JP)</string>
<string name="search_desc_mastodon_api">Account/Hashtag search using Mastodon API.</string>
<string name="toot_search_of">Toot search \"%1$s\"</string>
<string name="progress_synchronize_toot">Syncing Toot…</string>
</resources> </resources>