diff --git a/.idea/dictionaries/tateisu.xml b/.idea/dictionaries/tateisu.xml
index b74115e2..f8ff9336 100644
--- a/.idea/dictionaries/tateisu.xml
+++ b/.idea/dictionaries/tateisu.xml
@@ -2,6 +2,7 @@
adamrocker
+ apikey
dont
emoji
emojione
@@ -30,6 +31,7 @@
unfollow
unmute
unreblog
+ utoken
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 4bf913df..8eaf2eb5 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -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"
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMain.java b/app/src/main/java/jp/juggler/subwaytooter/ActMain.java
index 414c74ba..013cec8c 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/ActMain.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/ActMain.java
@@ -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;
diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPost.java b/app/src/main/java/jp/juggler/subwaytooter/ActPost.java
index d8637f7b..4b19f5b4 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/ActPost.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/ActPost.java
@@ -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() {
diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActText.java b/app/src/main/java/jp/juggler/subwaytooter/ActText.java
index bcd3e577..93186917 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/ActText.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/ActText.java
@@ -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 );
diff --git a/app/src/main/java/jp/juggler/subwaytooter/AlarmService.java b/app/src/main/java/jp/juggler/subwaytooter/AlarmService.java
index eb682094..86e50b2b 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/AlarmService.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/AlarmService.java
@@ -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;
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/AppDataExporter.java b/app/src/main/java/jp/juggler/subwaytooter/AppDataExporter.java
index 05feab33..23702f68 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/AppDataExporter.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/AppDataExporter.java
@@ -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;
diff --git a/app/src/main/java/jp/juggler/subwaytooter/AppState.java b/app/src/main/java/jp/juggler/subwaytooter/AppState.java
index 56988d4a..1b3fb5cf 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/AppState.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/AppState.java
@@ -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 );
}
//////////////////////////////////////////////////////
diff --git a/app/src/main/java/jp/juggler/subwaytooter/Column.java b/app/src/main/java/jp/juggler/subwaytooter/Column.java
index 2fef09c9..f2546378 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/Column.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/Column.java
@@ -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." );
diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java
index 577f07d9..5f138b73 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java
@@ -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 );
diff --git a/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.java b/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.java
index ae788991..ea67be72 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.java
@@ -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;
diff --git a/app/src/main/java/jp/juggler/subwaytooter/ItemListAdapter.java b/app/src/main/java/jp/juggler/subwaytooter/ItemListAdapter.java
index ecd834a5..e7be8224 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/ItemListAdapter.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/ItemListAdapter.java
@@ -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;
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.java b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.java
index 5d78adc4..bcfb9d3d 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.java
@@ -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 );
}
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/Pref.java b/app/src/main/java/jp/juggler/subwaytooter/Pref.java
index 040730e8..5343b5ba 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/Pref.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/Pref.java
@@ -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文も更新すること
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/PrefDevice.java b/app/src/main/java/jp/juggler/subwaytooter/PrefDevice.java
index 6ae53969..3ebcbc8b 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/PrefDevice.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/PrefDevice.java
@@ -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 );
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/StatusButtons.java b/app/src/main/java/jp/juggler/subwaytooter/StatusButtons.java
index a4a74450..2be93e1a 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/StatusButtons.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/StatusButtons.java
@@ -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;
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/StatusButtonsPopup.java b/app/src/main/java/jp/juggler/subwaytooter/StatusButtonsPopup.java
index fb4e116d..b6354f16 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/StatusButtonsPopup.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/StatusButtonsPopup.java
@@ -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 );
diff --git a/app/src/main/java/jp/juggler/subwaytooter/StreamReader.java b/app/src/main/java/jp/juggler/subwaytooter/StreamReader.java
index dfb9d482..9e32cac2 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/StreamReader.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/StreamReader.java
@@ -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 );
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.java b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.java
index bb3b2b7f..3953f880 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.java
@@ -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;
+ }
+
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootContext.java b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootContext.java
index 45102adf..baed18cd 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootContext.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootContext.java
@@ -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();
diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootNotification.java b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootNotification.java
index a9098aba..34c50d09 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootNotification.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootNotification.java
@@ -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 );
}
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootResults.java b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootResults.java
index 89bb9f8b..74ad3235 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootResults.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootResults.java
@@ -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 ){
diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.java b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.java
index 5fd14cf4..2088937b 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.java
@@ -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 );
}
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatusLike.java b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatusLike.java
new file mode 100644
index 00000000..af92b156
--- /dev/null
+++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatusLike.java
@@ -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;
+
+
+}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/api_msp/MSPApiResult.java b/app/src/main/java/jp/juggler/subwaytooter/api_msp/MSPApiResult.java
new file mode 100644
index 00000000..427ae369
--- /dev/null
+++ b/app/src/main/java/jp/juggler/subwaytooter/api_msp/MSPApiResult.java
@@ -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;
+ }
+}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/api_msp/MSPClient.java b/app/src/main/java/jp/juggler/subwaytooter/api_msp/MSPClient.java
new file mode 100644
index 00000000..98e7fb28
--- /dev/null
+++ b/app/src/main/java/jp/juggler/subwaytooter/api_msp/MSPClient.java
@@ -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;
+ }
+
+}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/api_msp/entity/MSPToot.java b/app/src/main/java/jp/juggler/subwaytooter/api_msp/entity/MSPToot.java
new file mode 100644
index 00000000..fd784f16
--- /dev/null
+++ b/app/src/main/java/jp/juggler/subwaytooter/api_msp/entity/MSPToot.java
@@ -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 {
+
+ }
+
+ 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 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 mMemoryCache = new LruCache<>( 2048 );
diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/Utils.java b/app/src/main/java/jp/juggler/subwaytooter/util/Utils.java
index 1eefa8d1..f61d56e3 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/util/Utils.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/util/Utils.java
@@ -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;
+ }
}
diff --git a/app/src/main/res/layout/page_column.xml b/app/src/main/res/layout/page_column.xml
index 6de009f9..e773d96f 100644
--- a/app/src/main/res/layout/page_column.xml
+++ b/app/src/main/res/layout/page_column.xml
@@ -244,6 +244,17 @@
+
+
-->
+ -
+
+
+
+
-