diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml new file mode 100644 index 00000000..719bb8bc --- /dev/null +++ b/.idea/codeStyleSettings.xml @@ -0,0 +1,228 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMain.java b/app/src/main/java/jp/juggler/subwaytooter/ActMain.java index e9919284..1fd4d737 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActMain.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ActMain.java @@ -47,6 +47,7 @@ import jp.juggler.subwaytooter.dialog.AccountPicker; import jp.juggler.subwaytooter.dialog.LoginForm; import jp.juggler.subwaytooter.dialog.ReportForm; import jp.juggler.subwaytooter.table.SavedAccount; +import jp.juggler.subwaytooter.util.ActionsDialog; import jp.juggler.subwaytooter.util.HTMLDecoder; import jp.juggler.subwaytooter.util.LinkClickContext; import jp.juggler.subwaytooter.util.LogCategory; @@ -112,7 +113,7 @@ public class ActMain extends AppCompatActivity ArrayList< SavedAccount > done_list = new ArrayList<>(); for( Column column : pager_adapter.column_list ){ SavedAccount a = column.access_info; - if( a==null || done_list.contains( a ) ) continue; + if( a == null || done_list.contains( a ) ) continue; done_list.add( a ); a.reloadSetting(); } @@ -337,7 +338,7 @@ public class ActMain extends AppCompatActivity // taは使い捨てなので、生成に使うLinkClickContextはダミーで問題ない LinkClickContext lcc = new LinkClickContext() { }; - TootAccount ta = TootAccount.parse( log, lcc,result.object ); + TootAccount ta = TootAccount.parse( log, lcc, result.object ); String user = ta.username + "@" + instance; this.row_id = SavedAccount.insert( instance, user, result.object, result.token_info ); } @@ -449,36 +450,125 @@ public class ActMain extends AppCompatActivity ////////////////////////////////////////////////////////////// - public void openBrowser(SavedAccount account, String url ){ - openChromeTab( account,url,false ); + public interface GetAccountCallback { + // return account information + // if failed, account is null. + void onGetAccount( TootAccount account ); + } + + void startGetAccount( final SavedAccount access_info, final String host, final String user, final GetAccountCallback callback ){ + + final ProgressDialog progress = new ProgressDialog( this ); + final AsyncTask< Void, Void, TootAccount > task = new AsyncTask< Void, Void, TootAccount >() { + @Override + protected TootAccount doInBackground( Void... params ){ + TootApiClient client = new TootApiClient( ActMain.this, new TootApiClient.Callback() { + + @Override + public boolean isApiCancelled(){ + return isCancelled(); + } + + @Override + public void publishApiProgress( final String s ){ + Utils.runOnMainThread( new Runnable() { + @Override + public void run(){ + progress.setMessage( s ); + } + } ); + } + } ); + client.setAccount( access_info ); + String path = "/api/v1/accounts/search" + "?q=" + Uri.encode( user ); + + TootApiResult result = client.request( path ); + + if( result.array != null ){ + for( int i = 0, ie = result.array.length() ; i < ie ; ++ i ){ + + TootAccount item = TootAccount.parse( log, access_info, result.array.optJSONObject( i ) ); + + if( ! item.username.equals( user ) ) continue; + + if( ! item.acct.contains( "@" ) + || item.acct.equalsIgnoreCase( user + "@" + host ) ) + return item; + } + } + + return null; + + } + + @Override + protected void onCancelled( TootAccount result ){ + super.onPostExecute( result ); + } + + @Override + protected void onPostExecute( TootAccount result ){ + progress.dismiss(); + callback.onGetAccount( result ); + } + + }; + progress.setIndeterminate( true ); + progress.setCancelable( true ); + progress.setOnCancelListener( new DialogInterface.OnCancelListener() { + @Override + public void onCancel( DialogInterface dialog ){ + task.cancel( true ); + } + } ); + progress.show(); + AsyncTaskCompat.executeParallel( task ); + } + + public void openBrowser( SavedAccount account, String url ){ + openChromeTab( account, url, false ); } Pattern reHashTag = Pattern.compile( "\\Ahttps://([^/]+)/tags/([^?#]+)\\z" ); + Pattern reUserPage = Pattern.compile( "\\Ahttps://([^/]+)/@([^?#]+)\\z" ); - - public void openChromeTab( SavedAccount account, String url ,boolean noIntercept ){ + public void openChromeTab( final SavedAccount access_info, final String url, boolean noIntercept ){ try{ - log.d("openChromeTab url=%s",url); + log.d( "openChromeTab url=%s", url ); - if(!noIntercept){ + if( ! noIntercept ){ // ハッシュタグをアプリ内で開く Matcher m = reHashTag.matcher( url ); if( m.find() ){ // https://mastodon.juggler.jp/tags/%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%82%BF%E3%82%B0 String host = m.group( 1 ); - String tag = m.group( 2 ); - if( tag.length() > 0 ){ - if( host.equalsIgnoreCase( account.host ) ){ - openHashTag( account, Uri.decode( tag ) ); - return; - }else{ - openHashTagOtherInstance( account,url, host,Uri.decode(tag) ); - return; - - - } + String tag = Uri.decode( m.group( 2 ) ); + if( host.equalsIgnoreCase( access_info.host ) ){ + openHashTag( access_info, tag ); + return; + }else{ + openHashTagOtherInstance( access_info, url, host, tag ); + 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 ) ); + startGetAccount( access_info, host, user, new GetAccountCallback() { + @Override + public void onGetAccount( TootAccount who ){ + if( who != null ){ + performOpenUser( access_info, who ); + return; + } + openChromeTab( access_info, url, true ); + } + } ); + return; + } } // ビルダーを使って表示方法を指定する @@ -495,90 +585,76 @@ public class ActMain extends AppCompatActivity } } - static class Action{ - String caption; - Runnable runnable; - } - // 他インスタンスのハッシュタグの表示 - private void openHashTagOtherInstance( final SavedAccount access_info,final String url,String host, final String tag ){ - final ArrayList action_list = new ArrayList<>(); + private void openHashTagOtherInstance( final SavedAccount access_info, final String url, String host, final String tag ){ - ArrayList account_list = new ArrayList<>( ); - for(SavedAccount a : SavedAccount.loadAccountList( log )){ - if( a.host.equalsIgnoreCase( host )){ - account_list.add(a); + ActionsDialog dialog = new ActionsDialog(); + + ArrayList< SavedAccount > account_list = new ArrayList<>(); + for( SavedAccount a : SavedAccount.loadAccountList( log ) ){ + if( a.host.equalsIgnoreCase( host ) ){ + account_list.add( a ); } } - Collections.sort( account_list,new Comparator< SavedAccount >() { + Collections.sort( account_list, new Comparator< SavedAccount >() { @Override public int compare( SavedAccount a, SavedAccount b ){ return String.CASE_INSENSITIVE_ORDER.compare( a.getFullAcct( a ), b.getFullAcct( b ) ); } } ); for( SavedAccount a : account_list ){ - Action action = new Action(); - action.caption = getString(R.string.open_in_account,a.user); final SavedAccount _a = a; - action.runnable = new Runnable() { - @Override - public void run(){ - openHashTag( _a,tag ); + dialog.addAction( + getString( R.string.open_in_account, a.user ) + , new Runnable() { + @Override + public void run(){ + openHashTag( _a, tag ); + } } - }; - action_list.add( action); + ); + } if( account_list.isEmpty() ){ // TODO ログインなしアカウントで開く選択肢 } // カラムのアカウントで開く { - Action action = new Action(); - action.caption = getString(R.string.open_in_account,access_info.user); final SavedAccount _a = access_info; - action.runnable = new Runnable() { - @Override - public void run(){ - openHashTag( _a,tag ); - } - }; - action_list.add( action); - } - // ブラウザで表示する - { - Action action = new Action(); - action.caption = getString(R.string.open_web_on_host,host); - action.runnable = new Runnable() { - @Override - public void run(){ - openChromeTab( access_info,url,true); - } - }; - action_list.add( action); - } - - String[] caption_list = new String[action_list.size()]; - for(int i=0,ie=caption_list.length;i= 0 && which < action_list.size()){ - action_list.get(which).runnable.run(); + dialog.addAction( + getString( R.string.open_in_account, access_info.user ) + , new Runnable() { + @Override + public void run(){ + openHashTag( _a, tag ); } } - }) - .show(); + + ); + } + + // ブラウザで表示する + { + dialog.addAction( + getString( R.string.open_web_on_host, host ) + , new Runnable() { + @Override + public void run(){ + openChromeTab( access_info, url, true ); + } + } + ); + + } + + dialog.show( this, "#" + tag ); + } final HTMLDecoder.LinkClickCallback link_click_listener = new HTMLDecoder.LinkClickCallback() { @Override - public void onClickLink( LinkClickContext lcc,String url ){ - openChromeTab( (SavedAccount)lcc,url ,false ); + public void onClickLink( LinkClickContext lcc, String url ){ + openChromeTab( (SavedAccount) lcc, url, false ); } }; @@ -659,7 +735,7 @@ public class ActMain extends AppCompatActivity ) , request_builder ); if( result.object != null ){ - new_status = TootStatus.parse( log,account, result.object ); + new_status = TootStatus.parse( log, account, result.object ); } return result; @@ -782,7 +858,7 @@ public class ActMain extends AppCompatActivity // reblog,unreblog のレスポンスは信用ならんのでステータスを再取得する result = client.request( "/api/v1/statuses/" + status.id ); if( result.object != null ){ - new_status = TootStatus.parse( log, account,result.object ); + new_status = TootStatus.parse( log, account, result.object ); } } @@ -922,32 +998,11 @@ public class ActMain extends AppCompatActivity } } - public void performFollow( final SavedAccount account, final TootAccount who ){ - String[] caption_list = new String[]{ - getString( R.string.follow ), - getString( R.string.unfollow ), - }; - new AlertDialog.Builder( this ) - .setNegativeButton( R.string.cancel, null ) - .setItems( caption_list, new DialogInterface.OnClickListener() { - @Override - public void onClick( DialogInterface dialog, int which ){ - switch( which ){ - case 0: - performFollow( account, who, true ); - break; - case 1: - performFollow( account, who, false ); - break; - } - } - } ) - .show(); - } + //////////////////////////////////////////////////////////////////////////// - private void performFollow( final SavedAccount account, final TootAccount who, final boolean bFollow ){ + private void callFollow( final SavedAccount account, final TootAccount who, final boolean bFollow ){ + new AsyncTask< Void, Void, TootApiResult >() { - @Override protected TootApiResult doInBackground( Void... params ){ TootApiClient client = new TootApiClient( ActMain.this, new TootApiClient.Callback() { @@ -995,9 +1050,65 @@ public class ActMain extends AppCompatActivity }.execute(); } + // acct で指定したユーザをリモートフォローする + void callRemoteFollow( final SavedAccount access_info, final String acct, final boolean locked ){ + + new AsyncTask< Void, Void, TootApiResult >() { + + @Override + protected TootApiResult doInBackground( Void... params ){ + TootApiClient client = new TootApiClient( ActMain.this, new TootApiClient.Callback() { + @Override + public boolean isApiCancelled(){ + return isCancelled(); + } + + @Override + public void publishApiProgress( String s ){ + + } + } ); + client.setAccount( access_info ); + + Request.Builder request_builder = new Request.Builder().post( + RequestBody.create( + TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED + , "uri=" + Uri.encode( acct ) + ) ); + TootApiResult result = client.request( "/api/v1/follows", request_builder ); + + if( result != null ){ + if( result.object != null ){ + Utils.showToast( ActMain.this, false, R.string.follow_succeeded ); + }else if( locked && result.response.code() == 422 ){ + Utils.showToast( ActMain.this, false, R.string.cant_follow_locked_user ); + }else{ + Utils.showToast( ActMain.this, false, result.error ); + } + } + + return result; + } + }.execute(); + } + + // アカウントを選択してからユーザをフォローする + void followFromAnotherAccount( final SavedAccount access_info, final TootAccount who ){ + AccountPicker.pick( ActMain.this, new AccountPicker.AccountPickerCallback() { + @Override + public void onAccountPicked( SavedAccount ai ){ + String acct = who.acct; + if( ! acct.contains( "@" ) ){ + acct = acct + "@" + access_info.host; + } + callRemoteFollow( ai, acct, who.locked ); + } + } ); + } + //////////////////////////////////////// - private void performMute( final SavedAccount account, final TootAccount who, final boolean bMute ){ + private void callMute( final SavedAccount account, final TootAccount who, final boolean bMute ){ new AsyncTask< Void, Void, TootApiResult >() { @Override @@ -1041,7 +1152,7 @@ public class ActMain extends AppCompatActivity }.execute(); } - private void performBlock( final SavedAccount account, final TootAccount who, final boolean bBlock ){ + private void callBlock( final SavedAccount account, final TootAccount who, final boolean bBlock ){ new AsyncTask< Void, Void, TootApiResult >() { @Override @@ -1090,7 +1201,7 @@ public class ActMain extends AppCompatActivity } - private void performReport( final SavedAccount account, final TootAccount who, final TootStatus status + private void callReport( final SavedAccount account, final TootAccount who, final TootStatus status , final String comment, final ReportCompleteCallback callback ){ new AsyncTask< Void, Void, TootApiResult >() { @@ -1139,11 +1250,11 @@ public class ActMain extends AppCompatActivity }.execute(); } - private void performReport( final SavedAccount account, final TootAccount who, final TootStatus status ){ + private void openReportForm( final SavedAccount account, final TootAccount who, final TootStatus status ){ ReportForm.showReportForm( this, who, status, new ReportForm.ReportFormCallback() { @Override public void startReport( final Dialog dialog, String comment ){ - performReport( account, who, status, comment, new ReportCompleteCallback() { + callReport( account, who, status, comment, new ReportCompleteCallback() { @Override public void onReportComplete( TootApiResult result ){ if( result == null ){ @@ -1162,100 +1273,111 @@ public class ActMain extends AppCompatActivity } ); } - //////////////////////////////////////// + //////////////////////////////////////////////// // ステータスのmoreメニュー - public void performStatusMore( final SavedAccount account, final TootStatus status ){ - String[] caption_list = new String[]{ - getString( R.string.follow ), - getString( R.string.unfollow ), - getString( R.string.mute ), - getString( R.string.unmute ), - getString( R.string.block ), - getString( R.string.unblock ), - getString( R.string.report ), - }; - new AlertDialog.Builder( this ) - .setNegativeButton( R.string.cancel, null ) - .setItems( caption_list, new DialogInterface.OnClickListener() { - @Override - public void onClick( DialogInterface dialog, int which ){ - switch( which ){ - case 0: - performFollow( account, status.account, true ); - break; - case 1: - performFollow( account, status.account, false ); - break; - case 2: - performMute( account, status.account, true ); - break; - case 3: - performMute( account, status.account, false ); - break; - case 4: - performBlock( account, status.account, true ); - break; - case 5: - performBlock( account, status.account, false ); - break; - - case 6: - performReport( account, status.account, status ); - break; - } - } - } ) - .show(); + public void openStatusMoreMenu( final SavedAccount access_info, final TootStatus status ){ + ActionsDialog dialog = new ActionsDialog(); + dialog.addAction( getString( R.string. follow), new Runnable() { + @Override public void run(){ + callFollow( access_info, status.account, true ); + } + } ); + dialog.addAction( getString( R.string. follow_from_another_account), new Runnable() { + @Override public void run(){ + followFromAnotherAccount( access_info, status.account ); + } + } ); + dialog.addAction( getString( R.string.unfollow ), new Runnable() { + @Override public void run(){ + callFollow( access_info, status.account, false ); + } + } ); + dialog.addAction( getString( R.string. mute), new Runnable() { + @Override public void run(){ + callMute( access_info, status.account, true ); + } + } ); + dialog.addAction( getString( R.string.unmute ), new Runnable() { + @Override public void run(){ + callMute( access_info, status.account, false ); + } + } ); + dialog.addAction( getString( R.string.block ), new Runnable() { + @Override public void run(){ + callBlock( access_info, status.account, true ); + } + } ); + dialog.addAction( getString( R.string. unblock), new Runnable() { + @Override public void run(){ + callBlock( access_info, status.account, false ); + } + } ); + dialog.addAction( getString( R.string. report), new Runnable() { + @Override public void run(){ + openReportForm( access_info, status.account, status ); + } + } ); + dialog.addAction( getString( R.string. open_web_page), new Runnable() { + @Override public void run(){ + // 強制的にブラウザで開く + openChromeTab( access_info, status.url, true ); + } + } ); + dialog.show(this, null ); } - public void performAccountMore( final SavedAccount account, final TootAccount who ){ - String[] caption_list = new String[]{ - getString( R.string.mention ), - getString( R.string.follow ), - getString( R.string.unfollow ), - getString( R.string.mute ), - getString( R.string.unmute ), - getString( R.string.block ), - getString( R.string.unblock ), - getString( R.string.report ), - }; - new AlertDialog.Builder( this ) - .setNegativeButton( R.string.cancel, null ) - .setItems( caption_list, new DialogInterface.OnClickListener() { - @Override - public void onClick( DialogInterface dialog, int which ){ - switch( which ){ - case 0: - performMention( account, who ); - break; - case 1: - performFollow( account, who, true ); - break; - case 2: - performFollow( account, who, false ); - break; - case 3: - performMute( account, who, true ); - break; - case 4: - performMute( account, who, false ); - break; - case 5: - performBlock( account, who, true ); - break; - case 6: - performBlock( account, who, false ); - break; - - case 7: - performReport( account, who, null ); - break; - } - } - } ) - .show(); + public void openAccountMoreMenu( final SavedAccount access_info, final TootAccount who ){ + ActionsDialog dialog = new ActionsDialog(); + + dialog.addAction( getString( R.string.mention ), new Runnable() { + @Override public void run(){ + performMention( access_info, who ); + } + } ); + dialog.addAction( getString( R.string.follow ), new Runnable() { + @Override public void run(){ + callFollow( access_info, who, true ); + } + } ); + dialog.addAction( getString( R.string.follow_from_another_account ), new Runnable() { + @Override public void run(){ + followFromAnotherAccount( access_info, who ); + } + } ); + dialog.addAction( getString( R.string.unfollow ), new Runnable() { + @Override + public void run(){ + callFollow( access_info, who, false ); + } + } ); + dialog.addAction( getString( R.string.mute ), new Runnable() { + @Override public void run(){ + callMute( access_info, who, true ); + } + } ); + dialog.addAction( getString( R.string.unmute ), new Runnable() { + @Override + public void run(){ + callMute( access_info, who, false ); + } + } ); + dialog.addAction( getString( R.string.block ), new Runnable() { + @Override public void run(){ + callBlock( access_info, who, true ); + } + } ); + dialog.addAction( getString( R.string.unblock ), new Runnable() { + @Override public void run(){ + callBlock( access_info, who, false ); + } + } ); + dialog.addAction( getString( R.string.report ), new Runnable() { + @Override public void run(){ + openReportForm( access_info, who, null ); + } + } ); + dialog.show( this, null ); } - } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java index 5a3758fb..01c636fe 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java @@ -371,7 +371,8 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall switch( v.getId() ){ case R.id.ivBackground: if( who != null ){ - activity.openBrowser( access_info,who.url ); + // 強制的にブラウザで開く + activity.openChromeTab( access_info,who.url ,true); } break; case R.id.btnFollowing: @@ -384,7 +385,7 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall Utils.showToast( activity, false, "not implemented" ); break; case R.id.btnMore: - activity.performAccountMore( access_info,who ); + activity.openAccountMoreMenu( access_info,who ); break; } @@ -735,7 +736,7 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall activity.performFavourite( access_info, status ); break; case R.id.btnMore: - activity.performStatusMore( access_info, status ); + activity.openStatusMoreMenu( access_info, status ); break; case R.id.ivThumbnail: activity.performOpenUser( access_info, account_thumbnail ); @@ -747,7 +748,7 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall activity.performOpenUser( access_info, account_follow ); break; case R.id.btnFollow: - activity.performAccountMore( access_info,account_follow); + activity.openAccountMoreMenu( access_info,account_follow); } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/ActionsDialog.java b/app/src/main/java/jp/juggler/subwaytooter/util/ActionsDialog.java new file mode 100644 index 00000000..6e1ec861 --- /dev/null +++ b/app/src/main/java/jp/juggler/subwaytooter/util/ActionsDialog.java @@ -0,0 +1,48 @@ +package jp.juggler.subwaytooter.util; + +import android.content.Context; +import android.content.DialogInterface; +import android.support.v7.app.AlertDialog; +import android.text.TextUtils; + +import java.util.ArrayList; + +import jp.juggler.subwaytooter.R; + +public class ActionsDialog { + + static class Action { + String caption; + Runnable r; + } + + final ArrayList< Action > action_list = new ArrayList<>(); + + public void addAction( String caption, Runnable r ){ + Action action = new Action(); + action.caption = caption; + action.r = r; + action_list.add( action ); + } + + public void show( Context context, String title ){ + String[] caption_list = new String[ action_list.size() ]; + for( int i = 0, ie = caption_list.length ; i < ie ; ++ i ){ + caption_list[ i ] = action_list.get( i ).caption; + } + AlertDialog.Builder b = new AlertDialog.Builder( context ) + .setNegativeButton( R.string.cancel, null ) + .setItems( caption_list, new DialogInterface.OnClickListener() { + @Override public void onClick( DialogInterface dialog, int which ){ + if( which >= 0 && which < action_list.size() ){ + action_list.get( which ).r.run(); + } + } + } ); + + if( ! TextUtils.isEmpty( title ) ) b.setTitle( title ); + + b.show(); + + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1b5ccbdc..d65ebdaf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -154,4 +154,6 @@ open web page open web page on %1$s + follow_from_another_account + menu