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

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">
<words>
<w>adamrocker</w>
<w>apikey</w>
<w>dont</w>
<w>emoji</w>
<w>emojione</w>
@ -30,6 +31,7 @@
<w>unfollow</w>
<w>unmute</w>
<w>unreblog</w>
<w>utoken</w>
</words>
</dictionary>
</component>

View File

@ -9,8 +9,8 @@ android {
applicationId "jp.juggler.subwaytooter"
minSdkVersion 21
targetSdkVersion 25
versionCode 93
versionName "0.9.3"
versionCode 95
versionName "0.9.5"
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.TootResults;
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.DlgConfirm;
import jp.juggler.subwaytooter.dialog.LoginForm;
@ -198,11 +199,16 @@ public class ActMain extends AppCompatActivity
boolean bRemoved = false;
for( int i = 0, ie = app_state.column_list.size() ; i < ie ; ++ i ){
Column column = app_state.column_list.get( i );
SavedAccount sa = SavedAccount.loadAccount( log, column.access_info.db_id );
if( sa == null ){
bRemoved = true;
if( column.access_info.isNA() ){
// 検索カラムはアカウント削除とか無関係
}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 ){
@ -607,6 +613,9 @@ public class ActMain extends AppCompatActivity
}else if( id == R.id.nav_muted_word ){
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 ){
// Intent intent = new Intent(this, TransCommuActivity.class);
// intent.putExtra(TransCommuActivity.APPLICATION_CODE_EXTRA, "FJlDoBKitg");
@ -1356,7 +1365,7 @@ public class ActMain extends AppCompatActivity
SavedAccount a = column.access_info;
if( done_list.contains( a ) ) continue;
done_list.add( a );
a.reloadSetting();
if( !a.isNA() ) a.reloadSetting();
column.fireShowColumnHeader();
}
}
@ -1368,7 +1377,7 @@ public class ActMain extends AppCompatActivity
if( ! Utils.equalsNullable( a.acct, account.acct ) ) continue;
if( done_list.contains( a ) ) continue;
done_list.add( a );
a.reloadSetting();
if( !a.isNA() ) a.reloadSetting();
column.fireShowColumnHeader();
}
}
@ -1463,6 +1472,7 @@ public class ActMain extends AppCompatActivity
}
} );
}
public void performMuteApp( @NonNull TootApplication application ){
MutedApp.save( application.name );
@ -1557,6 +1567,47 @@ public class ActMain extends AppCompatActivity
try{
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 ){
// ハッシュタグをアプリ内で開く
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 ){
openStatus( pos, access_info, status.id );
public void openStatus( int pos, @NonNull SavedAccount access_info, @NonNull TootStatusLike status ){
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 ){
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();
// ブラウザで表示する
@ -1851,7 +1906,7 @@ public class ActMain extends AppCompatActivity
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.accounts != null && ! tmp.accounts.isEmpty() ){
who_local = tmp.accounts.get( 0 );
@ -1974,7 +2029,7 @@ public class ActMain extends AppCompatActivity
public void performFavourite(
final SavedAccount access_info
, final TootStatus arg_status
, final TootStatusLike arg_status
, final int nCrossAccountMode
, final boolean bSet
, final RelationChangedCallback callback
@ -2002,7 +2057,7 @@ public class ActMain extends AppCompatActivity
client.setAccount( access_info );
TootApiResult result;
TootStatus target_status;
TootStatusLike target_status;
if( nCrossAccountMode == CROSS_ACCOUNT_REMOTE_INSTANCE ){
// 検索APIに他タンスのステータスの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;
}
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.statuses != null && ! tmp.statuses.isEmpty() ){
target_status = tmp.statuses.get( 0 );
@ -2043,7 +2098,7 @@ public class ActMain extends AppCompatActivity
)
, request_builder );
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;
@ -2108,7 +2163,7 @@ public class ActMain extends AppCompatActivity
public void performBoost(
final SavedAccount access_info
, final TootStatus arg_status
, final TootStatusLike arg_status
, final int nCrossAccountMode
, final boolean bSet
, final boolean bConfirmed
@ -2172,7 +2227,7 @@ public class ActMain extends AppCompatActivity
TootApiResult result;
TootStatus target_status;
TootStatusLike target_status;
if( nCrossAccountMode == CROSS_ACCOUNT_REMOTE_INSTANCE ){
// 検索APIに他タンスのステータスの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;
}
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.statuses != null && ! tmp.statuses.isEmpty() ){
target_status = tmp.statuses.get( 0 );
@ -2213,7 +2268,7 @@ public class ActMain extends AppCompatActivity
// reblog,unreblog のレスポンスは信用ならんのでステータスを再取得する
result = client.request( "/api/v1/statuses/" + target_status.id );
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(
final SavedAccount access_info
, final TootStatus arg_status
, final boolean bRemote
){
if( ! bRemote ){
ActPost.open( this, REQUEST_CODE_POST, access_info.db_id, arg_status );
return;
}
ActPost.open( this, REQUEST_CODE_POST, access_info.db_id, arg_status );
}
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;
@Override protected TootApiResult doInBackground( Void... params ){
@ -2298,15 +2357,15 @@ public class ActMain extends AppCompatActivity
client.setAccount( access_info );
// 検索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";
TootApiResult result = client.request( path );
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() ){
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 ){
return new TootApiResult( getString( R.string.status_id_conversion_failed ) );
@ -2322,6 +2381,7 @@ public class ActMain extends AppCompatActivity
@Override
protected void onPostExecute( TootApiResult result ){
progress.dismiss();
if( result == null ){
// cancelled.
}else if( target_status != null ){
@ -2330,7 +2390,18 @@ public class ActMain extends AppCompatActivity
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;
AccountPicker.pick( this, false, false
, 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;
AccountPicker.pick( this, false, false
, 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
, getString( R.string.account_picker_reply )
, makeAccountListNonPseudo( log ), new AccountPicker.AccountPickerCallback() {
@Override public void onAccountPicked( @NonNull SavedAccount ai ){
performReply(
ai
, status
, ! ai.host.equalsIgnoreCase( access_info.host )
);
if( (status instanceof TootStatus) && ai.host.equalsIgnoreCase( status.status_host ) ){
performReply( ai, (TootStatus)status );
}else{
performReplyRemote( ai,status.url,status.id );
}
}
} );
}
void openFollowFromAnotherAccount( @NonNull SavedAccount access_info, TootStatus status ){
if( status == null ) return;
openFollowFromAnotherAccount( access_info, status.account );
}
// void openReplyFromAnotherAccount( @NonNull final SavedAccount access_info, final String status_url,final long status_id ){
//
// 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 ){
if( account == null ) return;

View File

@ -1,6 +1,7 @@
package jp.juggler.subwaytooter;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ProgressDialog;
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.AppCompatActivity;
import android.text.Editable;
import android.text.Html;
import android.text.Spannable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.widget.Button;
import android.widget.CheckBox;
@ -47,7 +46,6 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@ -397,7 +395,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
sv = intent.getStringExtra( KEY_REPLY_STATUS );
if( sv != null ){
try{
TootStatus reply_status = TootStatus.parse( log, account, new JSONObject( sv ) );
TootStatus reply_status = TootStatus.parse( log, account, account.host,new JSONObject( sv ) );
// CW をリプライ元に合わせる
if( ! TextUtils.isEmpty( reply_status.spoiler_text ) ){
@ -898,7 +896,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
TootApiResult result = client.request( path );
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() ){
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 );
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;
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();
sb.append( src.substring( 0, mushroom_start ) );
int new_sel_start = sb.length();
// int new_sel_start = sb.length();
sb.append( text );
int new_sel_end = sb.length();
sb.append( src.substring( mushroom_end ) );
@ -2028,6 +2026,8 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
}
}
private void showRecommendedPlugin( String title ){
String language_code = getString( R.string.language_code );
int res_id;
@ -2038,46 +2038,37 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
}else{
res_id = R.raw.recommended_plugin_en;
}
try{
InputStream is = getResources().openRawResource( res_id );
try{
ByteArrayOutputStream bao = new ByteArrayOutputStream();
IOUtils.copy( is, bao );
String text = Utils.decodeUTF8( bao.toByteArray() );
View viewRoot = getLayoutInflater().inflate( R.layout.dlg_plugin_missing, null, false );
TextView tvText = (TextView) viewRoot.findViewById( R.id.tvText );
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 );
byte[] data = Utils.loadRawResource(this,res_id);
if( data != null ){
String text = Utils.decodeUTF8( data );
@SuppressLint("InflateParams")
View viewRoot = getLayoutInflater().inflate( R.layout.dlg_plugin_missing, null, false );
TextView tvText = (TextView) viewRoot.findViewById( R.id.tvText );
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 );
new AlertDialog.Builder( this )
.setView( viewRoot )
.setCancelable( true )
.setNeutralButton( R.string.close, null )
.show();
}finally{
IOUtils.closeQuietly( is );
}
}catch( Throwable ex ){
ex.printStackTrace();
new AlertDialog.Builder( this )
.setView( viewRoot )
.setCancelable( true )
.setNeutralButton( R.string.close, null )
.show();
}
}
final MyClickableSpan.LinkClickCallback link_click_listener = new MyClickableSpan.LinkClickCallback() {

View File

@ -13,6 +13,8 @@ import android.view.View;
import android.widget.EditText;
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.SavedAccount;
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_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();
sb.append( context.getString( R.string.send_header_url ) );
sb.append( ": " );
@ -33,7 +35,15 @@ public class ActText extends AppCompatActivity implements View.OnClickListener {
sb.append( "\n" );
sb.append( context.getString( R.string.send_header_date ) );
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( context.getString( R.string.send_header_from_acct ) );
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 );
encodeStatus( intent,activity, access_info, status );

View File

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

View File

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

View File

@ -27,6 +27,7 @@ import java.util.LinkedList;
import java.util.regex.Pattern;
import jp.juggler.subwaytooter.api.entity.TootStatus;
import jp.juggler.subwaytooter.api.entity.TootStatusLike;
import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.MyClickableSpan;
@ -134,34 +135,34 @@ class AppState {
private final HashSet< String > map_busy_fav = new HashSet<>();
boolean isBusyFav( SavedAccount account, TootStatus status ){
String busy_key = account.host + ":" + status.id;
return map_busy_fav.contains( busy_key );
boolean isBusyFav( SavedAccount account, @NonNull TootStatusLike status ){
final String key = account.acct +":" + status.status_host + ":" + status.id;
return map_busy_fav.contains( key );
}
boolean setBusyFav( SavedAccount account, TootStatus status ){
final String busy_key = account.acct +":" + status.uri;
return map_busy_fav.add( busy_key );
boolean setBusyFav( SavedAccount account, @NonNull TootStatusLike status ){
final String key = account.acct +":" + status.status_host + ":" + status.id;
return map_busy_fav.add( key );
}
boolean resetBusyFav( SavedAccount account, TootStatus status ){
final String busy_key = account.acct +":" + status.uri;
return map_busy_fav.remove( busy_key );
boolean resetBusyFav( SavedAccount account, @NonNull TootStatusLike status ){
final String key = account.acct +":" + status.status_host + ":" + status.id;
return map_busy_fav.remove( key );
}
//////////////////////////////////////////////////////
private final HashSet< String > map_busy_boost = new HashSet<>();
boolean isBusyBoost( @NonNull SavedAccount account, @NonNull TootStatus status ){
final String busy_key = account.acct +":" + status.uri;
return map_busy_boost.contains( busy_key );
boolean isBusyBoost( @NonNull SavedAccount account, @NonNull TootStatusLike status ){
final String key = account.acct +":" + status.status_host + ":" + status.id;
return map_busy_boost.contains( key );
}
boolean setBusyBoost( SavedAccount account, TootStatus status ){
final String busy_key = account.acct +":" + status.uri;
return map_busy_boost.add( busy_key );
boolean setBusyBoost( SavedAccount account, @NonNull TootStatusLike status ){
final String key = account.acct +":" + status.status_host + ":" + status.id;
return map_busy_boost.add( key );
}
boolean resetBusyBoost( SavedAccount account, TootStatus status ){
final String busy_key = account.acct +":" + status.uri;
return map_busy_boost.remove( busy_key );
boolean resetBusyBoost( SavedAccount account, @NonNull TootStatusLike status ){
final String key = account.acct +":" + status.status_host + ":" + status.id;
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.TootStatus;
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.AcctSet;
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_FAVOURITED_BY = 15;
static final int TYPE_DOMAIN_BLOCKS = 16;
static final int TYPE_SEARCH_PORTAL = 17;
@NonNull final Context context;
@NonNull private final AppState app_state;
@ -229,7 +233,10 @@ class Column implements StreamReader.Callback {
this.search_query = (String) getParamAt( params, 0 );
this.search_resolve = (Boolean) getParamAt( params, 1 );
break;
case TYPE_SEARCH_PORTAL:
this.search_query = (String) getParamAt( params, 0 );
break;
}
init();
}
@ -271,6 +278,9 @@ class Column implements StreamReader.Callback {
item.put( KEY_SEARCH_QUERY, search_query );
item.put( KEY_SEARCH_RESOLVE, search_resolve );
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.context = app_state.context;
SavedAccount ac = SavedAccount.loadAccount( log, src.optLong( KEY_ACCOUNT_ROW_ID ) );
if( ac == null ) throw new RuntimeException( "missing account" );
this.access_info = ac;
long account_db_id = src.optLong( KEY_ACCOUNT_ROW_ID );
if( account_db_id >= 0 ){
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.dont_close = src.optBoolean( KEY_DONT_CLOSE );
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_resolve = src.optBoolean( KEY_SEARCH_RESOLVE, false );
break;
case TYPE_SEARCH_PORTAL:
this.search_query = src.optString( KEY_SEARCH_QUERY );
break;
}
init();
}
@ -375,7 +393,13 @@ class Column implements StreamReader.Callback {
}catch( Throwable ex ){
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 );
}
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:
return context.getString( R.string.search );
case TYPE_SEARCH_PORTAL:
return context.getString( R.string.toot_search );
case TYPE_FOLLOW_REQUESTS:
return context.getString( R.string.follow_requests );
}
@ -525,6 +559,9 @@ class Column implements StreamReader.Callback {
case TYPE_SEARCH:
return R.attr.ic_search;
case TYPE_SEARCH_PORTAL:
return R.attr.ic_search;
case TYPE_FOLLOW_REQUESTS:
return R.attr.ic_account_add;
}
@ -580,7 +617,7 @@ class Column implements StreamReader.Callback {
for( Object o : list_data ){
if( o instanceof TootStatus ){
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 )
){
continue;
@ -590,7 +627,8 @@ class Column implements StreamReader.Callback {
TootNotification item = (TootNotification) o;
if( item.account.id == who_id ) continue;
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 )
continue;
}
@ -739,8 +777,9 @@ class Column implements StreamReader.Callback {
for( Object o : list_data ){
if( o instanceof TootStatus ){
TootStatus item = (TootStatus) o;
if( item.account != null && reDomain.matcher( item.account.acct ).find() ) continue;
if( item.reblog != null && item.reblog.account !=null && reDomain.matcher( item.reblog.account.acct ).find() )
if( item.account != null && reDomain.matcher( item.account.acct ).find() )
continue;
if( item.reblog != null && item.reblog.account != null && reDomain.matcher( item.reblog.account.acct ).find() )
continue;
}else if( o instanceof TootNotification ){
TootNotification item = (TootNotification) o;
@ -748,8 +787,9 @@ class Column implements StreamReader.Callback {
if( reDomain.matcher( item.account.acct ).find() ) continue;
}
if( item.status != null ){
if( item.status.account != null && reDomain.matcher( item.status.account.acct ).find() ) continue;
if( item.status.reblog != null && item.status.reblog.account !=null && reDomain.matcher( item.status.reblog.account.acct ).find() )
if( item.status.account != null && reDomain.matcher( item.status.account.acct ).find() )
continue;
if( item.status.reblog != null && item.status.reblog.account != null && reDomain.matcher( item.status.reblog.account.acct ).find() )
continue;
}
}
@ -1022,7 +1062,7 @@ class Column implements StreamReader.Callback {
if( result != null && result.array != null ){
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() );
addWithFilter( list_tmp, src );
//
@ -1059,7 +1099,7 @@ class Column implements StreamReader.Callback {
break;
}
src = TootStatus.parseList( log, access_info, result2.array );
src = TootStatus.parseList( log, access_info, access_info.host,result2.array );
addWithFilter( list_tmp, src );
@ -1106,7 +1146,7 @@ class Column implements StreamReader.Callback {
TootApiResult result = client.request( path_base );
if( result != null ){
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<>();
addWithFilter( list_tmp, src );
@ -1223,7 +1263,7 @@ class Column implements StreamReader.Callback {
result = client.request(
String.format( Locale.JAPAN, PATH_STATUSES, status_id ) );
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;
// 前後の会話
@ -1232,13 +1272,13 @@ class Column implements StreamReader.Callback {
if( result == null || result.object == null ) return result;
// 一つのリストにまとめる
TootContext context = TootContext.parse( log, access_info, result.object );
list_tmp = new ArrayList<>( 1 + context.ancestors.size() + context.descendants.size() );
if( context.ancestors != null )
addWithFilter( list_tmp, context.ancestors );
TootContext conversation_context = TootContext.parse( log, access_info, access_info.host,result.object );
list_tmp = new ArrayList<>( 1 + conversation_context.ancestors.size() + conversation_context.descendants.size() );
if( conversation_context.ancestors != null )
addWithFilter( list_tmp, conversation_context.ancestors );
list_tmp.add( target_status );
if( context.descendants != null )
addWithFilter( list_tmp, context.descendants );
if( conversation_context.descendants != null )
addWithFilter( list_tmp, conversation_context.descendants );
//
return result;
@ -1250,7 +1290,7 @@ class Column implements StreamReader.Callback {
result = client.request( path );
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 ){
list_tmp = new ArrayList<>();
list_tmp.addAll( tmp.hashtags );
@ -1258,7 +1298,45 @@ class Column implements StreamReader.Callback {
list_tmp.addAll( tmp.statuses );
}
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{
try{
@ -1749,7 +1827,7 @@ class Column implements StreamReader.Callback {
if( result != null && result.array != null ){
saveRange( result, bBottom, ! bBottom );
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 );
if( ! src.isEmpty() ){
@ -1793,7 +1871,7 @@ class Column implements StreamReader.Callback {
break;
}
src = TootNotification.parseList( log, access_info, result2.array );
src = TootNotification.parseList( log, access_info, access_info.host,result2.array );
if( ! src.isEmpty() ){
addWithFilter( list_tmp, 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 ) );
if( result != null && result.array != null ){
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<>();
addWithFilter( list_tmp, src );
@ -1859,7 +1937,7 @@ class Column implements StreamReader.Callback {
break;
}
src = TootStatus.parseList( log, access_info, result2.array );
src = TootStatus.parseList( log, access_info, access_info.host,result2.array );
addWithFilter( list_tmp, src );
@ -1915,7 +1993,7 @@ class Column implements StreamReader.Callback {
break;
}
src = TootStatus.parseList( log, access_info, result2.array );
src = TootStatus.parseList( log, access_info, access_info.host,result2.array );
addWithFilter( list_tmp, src );
}
}
@ -2016,6 +2094,49 @@ class Column implements StreamReader.Callback {
case TYPE_HASHTAG:
return getStatusList( client,
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{
try{
@ -2271,7 +2392,7 @@ class Column implements StreamReader.Callback {
}
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() ){
log.d( "gap-notification: empty." );
@ -2327,7 +2448,7 @@ class Column implements StreamReader.Callback {
// 成功した場合はそれを返したい
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 ){
// 直前の取得でカラのデータが帰ってきたら終了
log.d( "gap-statuses: empty." );
@ -2698,7 +2819,8 @@ class Column implements StreamReader.Callback {
}else if( o instanceof TootStatus ){
TootStatus status = (TootStatus) o;
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( this.enable_speech ){
@ -2788,6 +2910,9 @@ class Column implements StreamReader.Callback {
// リフレッシュしてからストリーミング開始
log.d( "onResume: start auto refresh." );
startRefresh( true, false, - 1L, - 1 );
}else if( column_type == TYPE_SEARCH || column_type == TYPE_SEARCH_PORTAL ){
// 検索カラムはリフレッシュもストリーミングもないがresumeのタイミングでリストの再描画を行いたい
fireShowContent();
}else{
// ギャップつきでストリーミング開始
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.TextUtils;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.text.method.MovementMethod;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
@ -27,7 +29,9 @@ import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayoutDirec
import java.util.regex.Pattern;
import jp.juggler.subwaytooter.table.AcctColor;
import jp.juggler.subwaytooter.util.HTMLDecoder;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.view.MyLinkMovementMethod;
import jp.juggler.subwaytooter.view.MyListView;
import jp.juggler.subwaytooter.util.ScrollPosition;
import jp.juggler.subwaytooter.util.Utils;
@ -81,6 +85,8 @@ class ColumnViewHolder
private final View llRegexFilter;
private final Button btnDeleteNotification;
private final TextView tvSearchDesc;
ColumnViewHolder( ActMain arg_activity, View root ){
this.activity = arg_activity;
@ -137,8 +143,8 @@ class ColumnViewHolder
etRegexFilter = (EditText) root.findViewById( R.id.etRegexFilter );
llRegexFilter = root.findViewById( R.id.llRegexFilter );
tvRegexFilterError = (TextView) root.findViewById( R.id.tvRegexFilterError );
tvSearchDesc = (TextView) root.findViewById( R.id.tvSearchDesc );
btnDeleteNotification = (Button) root.findViewById( R.id.btnDeleteNotification );
@ -223,7 +229,7 @@ class ColumnViewHolder
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;
try{
this.column = column;
@ -250,6 +256,7 @@ class ColumnViewHolder
bAllowFilter = true;
break;
case Column.TYPE_SEARCH:
case Column.TYPE_SEARCH_PORTAL:
case Column.TYPE_CONVERSATION:
case Column.TYPE_REPORTS:
case Column.TYPE_BLOCKS:
@ -299,7 +306,8 @@ class ColumnViewHolder
vg( llRegexFilter, bAllowFilter );
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の表示を更新
if( bAllowFilter ){
@ -307,12 +315,32 @@ class ColumnViewHolder
}
switch( column.column_type ){
default:
swipyRefreshLayout.setEnabled( true );
swipyRefreshLayout.setDirection( SwipyRefreshLayoutDirection.BOTH );
break;
case Column.TYPE_CONVERSATION:
case Column.TYPE_SEARCH:
swipyRefreshLayout.setEnabled( false );
break;
default:
case Column.TYPE_SEARCH_PORTAL:
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;
}
@ -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(){
if( column == null ) return;
@ -595,7 +644,7 @@ class ColumnViewHolder
break;
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 );
etSearch.setText( column.search_query );
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.TootStatus;
import jp.juggler.subwaytooter.api.entity.TootStatusLike;
import jp.juggler.subwaytooter.dialog.DlgQRCode;
import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.table.UserRelation;
@ -30,7 +31,7 @@ class DlgContextMenu implements View.OnClickListener, View.OnLongClickListener {
@NonNull final ActMain activity;
@NonNull private final SavedAccount access_info;
@Nullable private final TootAccount who;
@Nullable private final TootStatus status;
@Nullable private final TootStatusLike status;
@NonNull private final UserRelation relation;
@NonNull private final Column column;
@ -43,7 +44,7 @@ class DlgContextMenu implements View.OnClickListener, View.OnLongClickListener {
@NonNull ActMain activity
, @NonNull Column column
, @Nullable TootAccount who
, @Nullable TootStatus status
, @Nullable TootStatusLike status
){
this.activity = activity;
this.column = column;
@ -123,11 +124,11 @@ class DlgContextMenu implements View.OnClickListener, View.OnLongClickListener {
}else{
btnDelete.setVisibility( View.GONE );
btnReport.setOnClickListener( this );
if( status.application == null || TextUtils.isEmpty( status.application.name ) ){
btnMuteApp.setVisibility( View.GONE );
}else{
if( status.application != null && !TextUtils.isEmpty( status.application.name ) ){
btnMuteApp.setText( activity.getString( R.string.mute_app_of, status.application.name ) );
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.setOnClickListener( this );
v = viewRoot.findViewById( R.id.btnBoostedBy );
v.setOnClickListener( this );
v = viewRoot.findViewById( R.id.btnFavouritedBy );
v.setOnClickListener( this );
if( access_info.isNA() ){
v = viewRoot.findViewById( R.id.btnBoostedBy );
v.setVisibility( View.GONE);
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.setOnClickListener( this );
@ -275,7 +285,7 @@ class DlgContextMenu implements View.OnClickListener, View.OnLongClickListener {
break;
case R.id.btnReplyAnotherAccount:
activity.openReplyFromAnotherAccount( access_info, status );
activity.openReplyFromAnotherAccount( status );
break;
case R.id.btnDelete:
@ -293,8 +303,8 @@ class DlgContextMenu implements View.OnClickListener, View.OnLongClickListener {
break;
case R.id.btnReport:
if( status != null && who != null ){
activity.openReportForm( access_info, who, status );
if( who != null && status instanceof TootStatus ){
activity.openReportForm( access_info, who, (TootStatus)status );
}
break;

View File

@ -75,7 +75,7 @@ class ItemListAdapter extends BaseAdapter implements AdapterView.OnItemClickList
}else{
holder = (ItemViewHolder) view.getTag();
}
holder.bind( o, position );
holder.bind( o );
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.TootNotification;
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.ContentWarning;
import jp.juggler.subwaytooter.table.MediaShown;
@ -79,18 +81,17 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
private final TextView tvApplication;
private TootStatus status;
private TootStatusLike status;
private TootAccount account_thumbnail;
private TootAccount account_boost;
private TootAccount account_follow;
private String search_tag;
private TootGap gap;
private TootDomainBlock domain_block;
private int position;
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.column = column;
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.tvBoosted = (TextView) view.findViewById( R.id.tvBoosted );
if( activity.timeline_font != null ){
Utils.scanView( view, new Utils.ScanViewCallback() {
@Override public void onScanView( View v ){
@ -111,7 +111,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
}else if( v instanceof TextView ){
( (TextView) v ).setTypeface( activity.timeline_font );
}
}catch(Throwable ex){
}catch( Throwable ex ){
ex.printStackTrace();
}
}
@ -147,7 +147,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
this.tvContent = (MyTextView) view.findViewById( R.id.tvContent );
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.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;
}
void bind( Object item, int position ){
this.position = position;
void bind( Object item ){
this.status = null;
this.account_thumbnail = null;
this.account_boost = null;
@ -210,7 +209,9 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
if( item == null ) return;
if( item instanceof String ){
if( item instanceof MSPToot ){
showStatus( activity, (MSPToot) item );
}else if( item instanceof String ){
showSearchTag( (String) item );
}else if( item instanceof TootAccount ){
showFollow( (TootAccount) item );
@ -270,7 +271,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
}else if( item instanceof TootGap ){
showGap( (TootGap) item );
}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 );
}
private void showStatus( @NonNull ActMain activity, @NonNull TootStatus status ){
private void showStatus( @NonNull ActMain activity, @NonNull TootStatusLike status ){
this.status = status;
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 );
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( "?" );
ivThumbnail.setImageUrl(null,null);
ivThumbnail.setImageUrl( null, null );
}else{
tvName.setText( status.account.display_name );
ivThumbnail.setImageUrl(
access_info.supplyBaseUrl( status.account.avatar_static )
,access_info.supplyBaseUrl( status.account.avatar )
, access_info.supplyBaseUrl( status.account.avatar )
);
}
tvContent.setText( status.decoded_content );
@ -345,11 +352,16 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
// tvTags.setText( status.decoded_tags );
// }
if( status.decoded_mentions == null ){
tvMentions.setVisibility( View.GONE );
if( status instanceof TootStatus ){
TootStatus ts = (TootStatus) status;
if( ts.decoded_mentions == null ){
tvMentions.setVisibility( View.GONE );
}else{
tvMentions.setVisibility( View.VISIBLE );
tvMentions.setText( ts.decoded_mentions );
}
}else{
tvMentions.setVisibility( View.VISIBLE );
tvMentions.setText( status.decoded_mentions );
tvMentions.setVisibility( View.GONE );
}
// Content warning
@ -359,28 +371,52 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
}else{
llContentWarning.setVisibility( View.VISIBLE );
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 );
}
if( status.media_attachments == null || status.media_attachments.isEmpty() ){
flMedia.setVisibility( View.GONE );
}else{
flMedia.setVisibility( View.VISIBLE );
setMedia( ivMedia1, status, 0 );
setMedia( ivMedia2, status, 1 );
setMedia( ivMedia3, status, 2 );
setMedia( ivMedia4, status, 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( access_info.host, status.id, default_shown );
btnShowMedia.setVisibility( ! is_shown ? View.VISIBLE : View.GONE );
if( status instanceof TootStatus ){
TootStatus ts = (TootStatus) 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 );
}
}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 ){
@ -432,11 +468,11 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
}else{
iv.setVisibility( View.VISIBLE );
iv.setScaleType( activity.dont_crop_media_thumbnail ? ImageView.ScaleType.FIT_CENTER : ImageView.ScaleType.CENTER_CROP );
TootAttachment ta = status.media_attachments.get( idx );
if( TextUtils.isEmpty( ta.type )){
iv.setMediaType(0);
if( TextUtils.isEmpty( ta.type ) ){
iv.setMediaType( 0 );
}else{
switch( ta.type ){
default:
@ -463,22 +499,36 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
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 ) );
}
}
@Override public void onClick( View v ){
int pos = activity.nextPosition( column ) ;
int pos = activity.nextPosition( column );
switch( v.getId() ){
case R.id.btnHideMedia:
MediaShown.save( access_info.host, status.id, false );
MediaShown.save( status, false );
btnShowMedia.setVisibility( View.VISIBLE );
break;
case R.id.btnShowMedia:
MediaShown.save( access_info.host, status.id, true );
MediaShown.save( status, true );
btnShowMedia.setVisibility( View.GONE );
break;
case R.id.ivMedia1:
@ -495,7 +545,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
break;
case R.id.btnContentWarning:{
boolean new_shown = ( llContents.getVisibility() == View.GONE );
ContentWarning.save( access_info.host, status.id, new_shown );
ContentWarning.save( status, new_shown );
list_adapter.notifyDataSetChanged();
break;
}
@ -504,7 +554,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
if( access_info.isPseudo() ){
new DlgContextMenu( activity, column, account_thumbnail, null ).show();
}else{
activity.openProfile( pos,access_info, account_thumbnail );
activity.openProfile( pos, access_info, account_thumbnail );
}
break;
@ -512,14 +562,14 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
if( access_info.isPseudo() ){
new DlgContextMenu( activity, column, account_boost, null ).show();
}else{
activity.openProfile( pos,access_info, account_boost );
activity.openProfile( pos, access_info, account_boost );
}
break;
case R.id.llFollow:
if( access_info.isPseudo() ){
new DlgContextMenu( activity, column, account_follow, null ).show();
}else{
activity.openProfile( pos,access_info, account_follow );
activity.openProfile( pos, access_info, account_follow );
}
break;
case R.id.btnFollow:
@ -528,13 +578,13 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
case R.id.btnSearchTag:
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 ){
column.startGap( gap );
}else if( domain_block != null ){
final String domain = domain_block.domain;
final String domain = domain_block.domain;
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 )
.setPositiveButton( R.string.ok, new DialogInterface.OnClickListener() {
@Override public void onClick( DialogInterface dialog, int which ){
@ -544,7 +594,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
.show();
}
break;
}
}
@ -566,22 +616,28 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
private void clickMedia( int i ){
try{
TootAttachment a = status.media_attachments.get( i );
String sv;
if( Pref.pref( activity ).getBoolean( Pref.KEY_PRIOR_LOCAL_URL, false ) ){
sv = a.url;
if( TextUtils.isEmpty( sv ) ){
sv = a.remote_url;
}
}else{
sv = a.remote_url;
if( TextUtils.isEmpty( sv ) ){
if( status instanceof MSPToot ){
activity.openStatus( activity.nextPosition( column ), access_info, status );
}else if( status instanceof TootStatus ){
TootStatus ts = (TootStatus) status;
TootAttachment a = ts.media_attachments.get( i );
String sv;
if( Pref.pref( activity ).getBoolean( Pref.KEY_PRIOR_LOCAL_URL, false ) ){
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 ){
ex.printStackTrace();
}
@ -590,11 +646,9 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
// 簡略ビューの時だけ呼ばれる
// StatusButtonsPopupを表示する
void onItemClick( MyListView listView, View anchor ){
if( status != null ){
activity.closeListItemPopup();
activity.list_item_popup = new StatusButtonsPopup( activity, column ,bSimpleList);
activity.list_item_popup.show( listView, anchor, status );
}
activity.closeListItemPopup();
activity.list_item_popup = new StatusButtonsPopup( activity, column, bSimpleList );
activity.list_item_popup.show( listView, anchor, status );
}
}

View File

@ -6,7 +6,8 @@ import android.preference.PreferenceManager;
public class Pref {
static SharedPreferences pref( Context context ){
public static SharedPreferences pref( Context context ){
return PreferenceManager.getDefaultSharedPreferences( context );
}
@ -60,6 +61,8 @@ public class Pref {
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文も更新すること
}

View File

@ -7,7 +7,7 @@ public class PrefDevice {
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 );
}

View File

@ -10,6 +10,8 @@ import android.widget.ImageView;
import android.widget.PopupWindow;
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.UserRelation;
import jp.juggler.subwaytooter.util.LogCategory;
@ -27,7 +29,7 @@ class StatusButtons implements View.OnClickListener, View.OnLongClickListener {
private final ImageView ivFollowedBy2;
private final View llFollow2;
final boolean bSimpleList;
private final boolean bSimpleList;
StatusButtons( @NonNull ActMain activity, @NonNull Column column, @NonNull View viewRoot ,boolean bSimpleList){
this.activity = activity;
@ -63,32 +65,40 @@ class StatusButtons implements View.OnClickListener, View.OnLongClickListener {
}
private TootStatus status;
private UserRelation relation;
private TootStatusLike status;
void bind( @NonNull TootStatus status ){
void bind( @NonNull TootStatusLike status ){
this.status = status;
int color_normal = Styler.getAttributeColor( activity, R.attr.colorImageButton );
int color_accent = Styler.getAttributeColor( activity, R.attr.colorImageButtonAccent );
if( TootStatus.VISIBILITY_DIRECT.equals( status.visibility ) ){
setButton( btnBoost, false, color_accent, R.attr.ic_mail, "" );
}else if( TootStatus.VISIBILITY_PRIVATE.equals( status.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 = ( status.reblogged ? color_accent : color_normal );
setButton( btnBoost, true, color, R.attr.btn_boost, Long.toString( status.reblogs_count ) );
if( status instanceof MSPToot ){
setButton( btnBoost, true, color_normal, R.attr.btn_boost, "" );
setButton( btnFavourite, true, color_normal, R.attr.btn_favourite, "");
}else if( status instanceof TootStatus ){
TootStatus ts = (TootStatus)status;
if( TootStatus.VISIBILITY_DIRECT.equals( ts.visibility ) ){
setButton( btnBoost, false, color_accent, R.attr.ic_mail, "" );
}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 ) ){
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 ){
Drawable d = Styler.getAttributeDrawable( activity, icon_attr ).mutate();
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 );
break;
case R.id.btnReply:
if( access_info.isPseudo() ){
activity.openReplyFromAnotherAccount( access_info, status );
if( status instanceof TootStatus && !access_info.isPseudo() ){
activity.performReply( access_info, (TootStatus)status );
}else{
activity.performReply( access_info, status ,false);
activity.openReplyFromAnotherAccount( status );
}
break;
case R.id.btnBoost:
if( access_info.isPseudo() ){
activity.openBoostFromAnotherAccount( access_info, status );
@ -159,8 +171,10 @@ class StatusButtons implements View.OnClickListener, View.OnLongClickListener {
break;
case R.id.btnFollow2:
if( access_info.isPseudo() ){
activity.openFollowFromAnotherAccount( access_info, status );
if( status == null || status.account == null ){
// 何もしない
}else if( access_info.isPseudo() ){
activity.openFollowFromAnotherAccount( access_info, status.account );
}else if( relation.blocking || relation.muting ){
// 何もしない
}else if( relation.following || relation.requested ){
@ -184,11 +198,13 @@ class StatusButtons implements View.OnClickListener, View.OnLongClickListener {
break;
case R.id.btnFollow2:
activity.openFollowFromAnotherAccount( access_info, status );
if( status != null && status.account != null ){
activity.openFollowFromAnotherAccount( access_info, status.account );
}
break;
case R.id.btnReply:
activity.openReplyFromAnotherAccount( access_info, status );
activity.openReplyFromAnotherAccount( status );
break;
}

View File

@ -8,10 +8,11 @@ import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ListView;
import android.widget.PopupWindow;
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;
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 );

View File

@ -119,9 +119,9 @@ class StreamReader {
private Object parsePayload( String event, JSONObject obj ){
try{
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 ) ){
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 ) ){
return obj.optLong( "payload", - 1L );
}

View File

@ -143,7 +143,7 @@ public class TootAccount {
return result;
}
private static CharSequence filterDisplayName( String sv ){
public static CharSequence filterDisplayName( String sv ){
// decode HTML entity
sv = HTMLDecoder.decodeEntity(sv );
@ -155,4 +155,14 @@ public class TootAccount {
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
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;
try{
TootContext dst = new TootContext();
dst.ancestors = TootStatus.parseList( log, account,src.optJSONArray( "ancestors" ) );
dst.descendants = TootStatus.parseList(log, account, src.optJSONArray( "descendants" ) );
dst.ancestors = TootStatus.parseList( log, lcc,status_host,src.optJSONArray( "ancestors" ) );
dst.descendants = TootStatus.parseList(log, lcc, status_host,src.optJSONArray( "descendants" ) );
return dst;
}catch( Throwable ex ){
ex.printStackTrace();

View File

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

View File

@ -18,12 +18,12 @@ public class TootResults {
// An array of matched hashtags, as strings
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{
if( src == null ) return null;
TootResults dst = new TootResults();
dst.accounts = TootAccount.parseList( log, account, src.optJSONArray( "accounts" ) );
dst.statuses = TootStatus.parseList( log, account, src.optJSONArray( "statuses" ) );
dst.accounts = TootAccount.parseList( log, lcc, src.optJSONArray( "accounts" ) );
dst.statuses = TootStatus.parseList( log, lcc, status_host,src.optJSONArray( "statuses" ) );
dst.hashtags = Utils.parseStringArray( log, src.optJSONArray( "hashtags" ) );
return dst;
}catch( Throwable ex ){

View File

@ -2,6 +2,7 @@ package jp.juggler.subwaytooter.api.entity;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.NavigationView;
import android.text.Spannable;
import android.text.TextUtils;
@ -19,13 +20,14 @@ import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.util.HTMLDecoder;
import jp.juggler.subwaytooter.util.LinkClickContext;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.Utils;
import jp.juggler.subwaytooter.util.WordTrieTree;
public class TootStatus extends TootId {
public class TootStatus extends TootStatusLike {
public static class List extends ArrayList< TootStatus > {
@ -44,12 +46,9 @@ public class TootStatus extends TootId {
// A Fediverse-unique resource ID
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
public String in_reply_to_id;
@ -58,31 +57,12 @@ public class TootStatus extends TootId {
// null or the reblogged Status
public TootStatus reblog;
// Body of the status; this will contain HTML (remote HTML already sanitized)
public String content;
// The time the status was created
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
public String visibility;
public static final String VISIBILITY_PUBLIC = "public";
@ -99,13 +79,9 @@ public class TootStatus extends TootId {
//An array of Tags
public TootTag.List tags;
//Application from which the status was posted
public TootApplication application;
public long time_created_at;
public Spannable decoded_content;
public Spannable decoded_spoiler_text;
// public Spannable decoded_tags;
public Spannable decoded_mentions;
@ -113,7 +89,7 @@ public class TootStatus extends TootId {
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;
@ -122,12 +98,13 @@ public class TootStatus extends TootId {
status.json = src;
// log.d( "parse: %s", src.toString() );
status.id = src.optLong( "id" );
status.status_host = status_host;
status.uri = Utils.optStringX( src, "uri" );
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_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.created_at = Utils.optStringX( src, "created_at" ); // "2017-04-16T09:37:14.000Z"
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.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_mentions = HTMLDecoder.decodeMentions( account, status.mentions );
status.decoded_mentions = HTMLDecoder.decodeMentions( lcc, status.mentions );
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;
}catch( Throwable ex ){
@ -159,7 +136,7 @@ public class TootStatus extends TootId {
}
@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();
if( array != null ){
int array_size = array.length();
@ -167,7 +144,7 @@ public class TootStatus extends TootId {
for( int i = 0 ; i < array_size ; ++ i ){
JSONObject src = array.optJSONObject( i );
if( src == null ) continue;
TootStatus item = parse( log, account, src );
TootStatus item = parse( log, lcc,status_host, src );
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.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.NonNull;
import jp.juggler.subwaytooter.App1;
import jp.juggler.subwaytooter.api.entity.TootStatusLike;
import jp.juggler.subwaytooter.util.LogCategory;
public class ContentWarning {
@ -40,9 +42,9 @@ public class ContentWarning {
}
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{
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{
if( cursor.moveToFirst() ){
int iv = cursor.getInt( cursor.getColumnIndex( COL_SHOWN ) );
@ -57,13 +59,13 @@ public class ContentWarning {
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{
long now = System.currentTimeMillis();
ContentValues cv = new ContentValues();
cv.put( COL_HOST, host );
cv.put( COL_STATUS_ID, status_id );
cv.put( COL_HOST, status.status_host );
cv.put( COL_STATUS_ID, status.id );
cv.put( COL_SHOWN, is_shown ? 1:0 );
cv.put( COL_TIME_SAVE, now );
App1.getDB().replace( table, null, cv );

View File

@ -3,8 +3,10 @@ package jp.juggler.subwaytooter.table;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.NonNull;
import jp.juggler.subwaytooter.App1;
import jp.juggler.subwaytooter.api.entity.TootStatusLike;
import jp.juggler.subwaytooter.util.LogCategory;
public class MediaShown {
@ -41,9 +43,9 @@ public class MediaShown {
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{
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{
if( cursor.moveToFirst() ){
return ( 0 != cursor.getInt( cursor.getColumnIndex( COL_SHOWN ) ) );
@ -57,13 +59,13 @@ public class MediaShown {
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{
long now = System.currentTimeMillis();
ContentValues cv = new ContentValues();
cv.put( COL_HOST, host );
cv.put( COL_STATUS_ID, status_id );
cv.put( COL_HOST, status.status_host );
cv.put( COL_STATUS_ID, status.id );
cv.put( COL_SHOWN, is_shown ? 1:0 );
cv.put( COL_TIME_SAVE, now );
App1.getDB().replace( table, null, cv );

View File

@ -191,6 +191,30 @@ public class SavedAccount extends TootAccount implements LinkClickContext {
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{
JSONObject src = new JSONObject( cursor.getString( cursor.getColumnIndex( COL_ACCOUNT ) ) );
@ -475,4 +499,5 @@ public class SavedAccount extends TootAccount implements LinkClickContext {
return 0L;
}
}

View File

@ -128,7 +128,7 @@ public class UserRelation {
public boolean muting;
public boolean requested;
private UserRelation(){
public UserRelation(){
}
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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@ -1107,4 +1108,23 @@ public class Utils {
}
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>
</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
android:id="@+id/llSearch"
android:layout_width="match_parent"

View File

@ -92,6 +92,16 @@
<!--android:title="Tools"/>-->
</menu>
</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">
<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="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="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_subtitle_description_format">%1$s, %2$s, %3$s</string>-->
<!--<string name="abc_action_bar_up_description">Revenir en haut de la page</string>-->

View File

@ -653,5 +653,10 @@
<string name="text_to_speech_shutdown">TextToSpeechの後処理…</string>
<string name="show_post_button_bar_top">投稿画面のボタンバーを上端に表示</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>

View File

@ -361,4 +361,10 @@
<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="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>