アカウント追加時に「疑似アカウント(ログインなし、公開データのみ読める)」を選択可能

This commit is contained in:
tateisu 2017-05-01 19:30:38 +09:00
parent dad9a200a4
commit 0ac0f90d54
17 changed files with 374 additions and 218 deletions

View File

@ -9,8 +9,8 @@ android {
applicationId "jp.juggler.subwaytooter"
minSdkVersion 21
targetSdkVersion 25
versionCode 27
versionName "0.2.7"
versionCode 28
versionName "0.2.8"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

View File

@ -41,7 +41,7 @@ public class ActAccountSetting extends AppCompatActivity implements View.OnClick
@Override
protected void onCreate( @Nullable Bundle savedInstanceState ){
super.onCreate( savedInstanceState );
App1.setActivityTheme(this,false);
App1.setActivityTheme( this, false );
initUI();
account = SavedAccount.loadAccount( log, getIntent().getLongExtra( KEY_ACCOUNT_DB_ID, - 1L ) );
if( account == null ) finish();
@ -113,11 +113,20 @@ public class ActAccountSetting extends AppCompatActivity implements View.OnClick
loading = false;
boolean enabled = ! a.isPseudo();
btnAccessToken.setEnabled( enabled );
btnVisibility.setEnabled( enabled );
swConfirmBeforeBoost.setEnabled( enabled );
cbNotificationMention.setEnabled( enabled );
cbNotificationBoost.setEnabled( enabled );
cbNotificationFavourite.setEnabled( enabled );
cbNotificationFollow.setEnabled( enabled );
updateVisibility();
}
@Override protected void onStop(){
AlarmService.startCheck(this);
AlarmService.startCheck( this );
super.onStop();
}

View File

@ -198,7 +198,7 @@ public class ActMain extends AppCompatActivity
if( data != null ){
String search = data.getStringExtra( ActAbout.EXTRA_SEARCH );
if( ! TextUtils.isEmpty( search ) ){
performAddTimeline( Column.TYPE_SEARCH, search, true );
performAddTimeline( true, Column.TYPE_SEARCH, search, true );
}
return;
}
@ -291,20 +291,22 @@ public class ActMain extends AppCompatActivity
if( id == R.id.nav_account_add ){
performAccountAdd();
}else if( id == R.id.nav_add_tl_home ){
performAddTimeline( Column.TYPE_HOME );
performAddTimeline( false, Column.TYPE_HOME );
}else if( id == R.id.nav_add_tl_local ){
performAddTimeline( Column.TYPE_LOCAL );
performAddTimeline( true, Column.TYPE_LOCAL );
}else if( id == R.id.nav_add_tl_federate ){
performAddTimeline( Column.TYPE_FEDERATE );
performAddTimeline( true, Column.TYPE_FEDERATE );
}else if( id == R.id.nav_add_favourites ){
performAddTimeline( Column.TYPE_FAVOURITES );
performAddTimeline( false, Column.TYPE_FAVOURITES );
// }else if( id == R.id.nav_add_reports ){
// performAddTimeline(Column.TYPE_REPORTS );
}else if( id == R.id.nav_add_statuses ){
performAddTimeline( Column.TYPE_PROFILE );
performAddTimeline( false, Column.TYPE_PROFILE );
}else if( id == R.id.nav_add_notifications ){
performAddTimeline( Column.TYPE_NOTIFICATIONS );
performAddTimeline( false, Column.TYPE_NOTIFICATIONS );
}else if( id == R.id.nav_app_setting ){
performAppSetting();
@ -314,7 +316,7 @@ public class ActMain extends AppCompatActivity
performColumnList();
}else if( id == R.id.nav_add_tl_search ){
performAddTimeline( Column.TYPE_SEARCH, "", false );
performAddTimeline( true, Column.TYPE_SEARCH, "", false );
}else if( id == R.id.nav_app_about ){
openAppAbout();
@ -326,13 +328,13 @@ public class ActMain extends AppCompatActivity
finish();
}else if( id == R.id.nav_add_mutes ){
performAddTimeline( Column.TYPE_MUTES );
performAddTimeline( false, Column.TYPE_MUTES );
}else if( id == R.id.nav_add_blocks ){
performAddTimeline( Column.TYPE_BLOCKS );
performAddTimeline( false, Column.TYPE_BLOCKS );
}else if( id == R.id.nav_follow_requests ){
performAddTimeline( Column.TYPE_FOLLOW_REQUESTS );
performAddTimeline( false, Column.TYPE_FOLLOW_REQUESTS );
}else if( id == R.id.nav_muted_app ){
startActivity( new Intent( this, ActMutedApp.class ) );
@ -404,22 +406,19 @@ public class ActMain extends AppCompatActivity
public void performAccountAdd(){
LoginForm.showLoginForm( this, null, new LoginForm.LoginFormCallback() {
@Override
public void startLogin( final Dialog dialog, final String instance ){
public void startLogin( final Dialog dialog, final String instance, final boolean bPseudoAccount ){
final ProgressDialog progress = new ProgressDialog( ActMain.this );
final AsyncTask< Void, String, TootApiResult > task = new AsyncTask< Void, String, TootApiResult >() {
@Override
protected TootApiResult doInBackground( Void... params ){
@Override protected TootApiResult doInBackground( Void... params ){
TootApiClient api_client = new TootApiClient( ActMain.this, new TootApiClient.Callback() {
@Override
public boolean isApiCancelled(){
@Override public boolean isApiCancelled(){
return isCancelled();
}
@Override
public void publishApiProgress( final String s ){
@Override public void publishApiProgress( final String s ){
Utils.runOnMainThread( new Runnable() {
@Override
public void run(){
@ -430,7 +429,13 @@ public class ActMain extends AppCompatActivity
} );
api_client.setInstance( instance );
return api_client.authorize1();
if( bPseudoAccount ){
return api_client.checkInstance();
}else{
return api_client.authorize1();
}
}
@Override
@ -441,8 +446,9 @@ public class ActMain extends AppCompatActivity
if( result == null ){
// cancelled.
}else if( result.error != null ){
// エラー
String sv = result.error;
// エラーはブラウザ用URLかもしれない
if( sv.startsWith( "https" ) ){
// OAuth認証が必要
Intent data = new Intent();
@ -451,12 +457,36 @@ public class ActMain extends AppCompatActivity
dialog.dismiss();
return;
}
// 他のエラー
Utils.showToast( ActMain.this, true, sv );
log.e( result.error );
}else{
// 多分ここは通らないはず
Utils.showToast( ActMain.this, false, R.string.access_token_updated_for );
// 疑似アカウントが追加された
try{
String username = "?";
String full_acct = username + "@" + instance;
JSONObject account_info = new JSONObject();
account_info.put( "username", username );
account_info.put( "acct", username );
long row_id = SavedAccount.insert( instance, full_acct, account_info, new JSONObject() );
SavedAccount account = SavedAccount.loadAccount( log, row_id );
if( account != null ){
account.notification_follow = false;
account.notification_favourite = false;
account.notification_boost = false;
account.notification_mention = false;
account.saveSetting();
Utils.showToast( ActMain.this, false, R.string.server_confirmed );
addColumn( account, Column.TYPE_LOCAL );
dialog.dismiss();
}
}catch( JSONException ex ){
ex.printStackTrace();
}
}
}
};
@ -513,7 +543,6 @@ public class ActMain extends AppCompatActivity
final AsyncTask< Void, Void, TootApiResult > task = new AsyncTask< Void, Void, TootApiResult >() {
long row_id;
TootAccount ta;
SavedAccount sa;
String host;
@ -629,7 +658,7 @@ public class ActMain extends AppCompatActivity
}else{
// アカウント追加時
String user = ta.username + "@" + host;
this.row_id = SavedAccount.insert( host, user, result.object, result.token_info );
long row_id = SavedAccount.insert( host, user, result.object, result.token_info );
SavedAccount account = SavedAccount.loadAccount( log, row_id );
if( account != null ){
if( account.locked ){
@ -717,15 +746,19 @@ public class ActMain extends AppCompatActivity
}
void performOpenUser( SavedAccount access_info, TootAccount user ){
addColumn( access_info, Column.TYPE_PROFILE, user.id );
if( access_info.isPseudo() ){
Utils.showToast( this,false,R.string.not_available_for_pseudo_account );
}else{
addColumn( access_info, Column.TYPE_PROFILE, user.id );
}
}
public void performConversation( SavedAccount access_info, TootStatus status ){
addColumn( access_info, Column.TYPE_CONVERSATION, status.id );
}
private void performAddTimeline( final int type, final Object... args ){
AccountPicker.pick( this, true, new AccountPicker.AccountPickerCallback() {
private void performAddTimeline( boolean bAllowPseudo, final int type, final Object... args ){
AccountPicker.pick( this, bAllowPseudo, true, new AccountPicker.AccountPickerCallback() {
@Override public void onAccountPicked( SavedAccount ai ){
switch( type ){
default:
@ -1191,9 +1224,8 @@ public class ActMain extends AppCompatActivity
////////////////////////////////////////
private void performAccountSetting(){
AccountPicker.pick( this, true, new AccountPicker.AccountPickerCallback() {
@Override
public void onAccountPicked( SavedAccount ai ){
AccountPicker.pick( this, true, true, new AccountPicker.AccountPickerCallback() {
@Override public void onAccountPicked( SavedAccount ai ){
ActAccountSetting.open( ActMain.this, ai, REQUEST_CODE_ACCOUNT_SETTING );
}
} );
@ -1490,7 +1522,7 @@ public class ActMain extends AppCompatActivity
// アカウントを選択してからユーザをフォローする
void followFromAccount( final SavedAccount access_info, final TootAccount who, final RelationChangedCallback callback ){
AccountPicker.pick( ActMain.this, true, new AccountPicker.AccountPickerCallback() {
AccountPicker.pick( ActMain.this, false, true, new AccountPicker.AccountPickerCallback() {
@Override public void onAccountPicked( SavedAccount ai ){
String acct = who.acct;
if( ! acct.contains( "@" ) ){
@ -1761,19 +1793,14 @@ public class ActMain extends AppCompatActivity
} );
client.setAccount( access_info );
StringBuilder sb = new StringBuilder();
sb.append( "account_id=" )
.append( Long.toString( who.id ) )
.append( "&comment=" )
.append( Uri.encode( comment ) );
sb.append( "&status_ids[]=" )
.append( Long.toString( status.id ) );
String sb = "account_id=" + Long.toString( who.id )
+ "&comment=" + Uri.encode( comment )
+ "&status_ids[]=" + Long.toString( status.id );
Request.Builder request_builder = new Request.Builder().post(
RequestBody.create(
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED
, sb.toString()
, sb
) );
return client.request( "/api/v1/reports", request_builder );
@ -1900,6 +1927,7 @@ public class ActMain extends AppCompatActivity
} );
final ArrayList< SavedAccount > tmp_list = new ArrayList<>();
for( SavedAccount a : SavedAccount.loadAccountList( log ) ){
if( a.isPseudo() ) continue;
if( a.host.equalsIgnoreCase( access_info.host ) ){
// 同じホストを収集
tmp_list.add( a );
@ -1908,7 +1936,7 @@ public class ActMain extends AppCompatActivity
if( ! tmp_list.isEmpty() ){
dialog.addAction( getString( R.string.favourite_from_another_account ), new Runnable() {
@Override public void run(){
AccountPicker.pick( ActMain.this, false, tmp_list, new AccountPicker.AccountPickerCallback() {
AccountPicker.pick( ActMain.this, false, false, tmp_list, new AccountPicker.AccountPickerCallback() {
@Override public void onAccountPicked( SavedAccount ai ){
if( ai != null )
performFavourite( ai, status, favourite_complete_callback );
@ -1918,7 +1946,7 @@ public class ActMain extends AppCompatActivity
} );
dialog.addAction( getString( R.string.boost_from_another_account ), new Runnable() {
@Override public void run(){
AccountPicker.pick( ActMain.this, false, tmp_list, new AccountPicker.AccountPickerCallback() {
AccountPicker.pick( ActMain.this, false, false, tmp_list, new AccountPicker.AccountPickerCallback() {
@Override public void onAccountPicked( SavedAccount ai ){
if( ai != null )
performBoost( ai, status, false, boost_complete_callback );
@ -1934,50 +1962,53 @@ public class ActMain extends AppCompatActivity
}
} );
dialog.addAction( getString( R.string.follow ), new Runnable() {
@Override public void run(){
callFollow( access_info, status.account, true, follow_complete_callback );
}
} );
dialog.addAction( getString( R.string.unfollow ), new Runnable() {
@Override public void run(){
callFollow( access_info, status.account, false, unfollow_complete_callback );
}
} );
dialog.addAction( getString( R.string.mute ), new Runnable() {
@Override public void run(){
callMute( access_info, status.account, true, null );
}
} );
dialog.addAction( getString( R.string.unmute ), new Runnable() {
@Override public void run(){
callMute( access_info, status.account, false, null );
}
} );
dialog.addAction( getString( R.string.block ), new Runnable() {
@Override public void run(){
callBlock( access_info, status.account, true, null );
}
} );
dialog.addAction( getString( R.string.unblock ), new Runnable() {
@Override public void run(){
callBlock( access_info, status.account, false, null );
}
} );
if( access_info.isMe( status.account ) ){
dialog.addAction( getString( R.string.delete ), new Runnable() {
if( ! access_info.isPseudo() ){
dialog.addAction( getString( R.string.follow ), new Runnable() {
@Override public void run(){
deleteStatus( access_info, status.id );
callFollow( access_info, status.account, true, follow_complete_callback );
}
} );
}else{
dialog.addAction( getString( R.string.report ), new Runnable() {
dialog.addAction( getString( R.string.unfollow ), new Runnable() {
@Override public void run(){
openReportForm( access_info, status.account, status );
callFollow( access_info, status.account, false, unfollow_complete_callback );
}
} );
dialog.addAction( getString( R.string.mute ), new Runnable() {
@Override public void run(){
callMute( access_info, status.account, true, null );
}
} );
dialog.addAction( getString( R.string.unmute ), new Runnable() {
@Override public void run(){
callMute( access_info, status.account, false, null );
}
} );
dialog.addAction( getString( R.string.block ), new Runnable() {
@Override public void run(){
callBlock( access_info, status.account, true, null );
}
} );
dialog.addAction( getString( R.string.unblock ), new Runnable() {
@Override public void run(){
callBlock( access_info, status.account, false, null );
}
} );
if( access_info.isMe( status.account ) ){
dialog.addAction( getString( R.string.delete ), new Runnable() {
@Override public void run(){
deleteStatus( access_info, status.id );
}
} );
}else{
dialog.addAction( getString( R.string.report ), new Runnable() {
@Override public void run(){
openReportForm( access_info, status.account, status );
}
} );
}
}
if( column_type == Column.TYPE_CONVERSATION && status.application != null ){
@ -1998,67 +2029,68 @@ public class ActMain extends AppCompatActivity
public void openAccountMoreMenu( final SavedAccount access_info, final TootAccount who, int column_type ){
ActionsDialog dialog = new ActionsDialog();
if( column_type == Column.TYPE_FOLLOW_REQUESTS ){
dialog.addAction( getString( R.string.follow_request_ok ), new Runnable() {
@Override public void run(){
callFollowRequestAuthorize( access_info, who, true );
}
} );
dialog.addAction( getString( R.string.follow_request_ng ), new Runnable() {
@Override public void run(){
callFollowRequestAuthorize( access_info, who, false );
}
} );
if( ! access_info.isPseudo() ){
if( column_type == Column.TYPE_FOLLOW_REQUESTS ){
dialog.addAction( getString( R.string.follow_request_ok ), new Runnable() {
@Override public void run(){
callFollowRequestAuthorize( access_info, who, true );
}
} );
dialog.addAction( getString( R.string.follow_request_ng ), new Runnable() {
@Override public void run(){
callFollowRequestAuthorize( access_info, who, false );
}
} );
}
}
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, follow_complete_callback );
}
} );
if( ! access_info.isPseudo() ){
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, follow_complete_callback );
}
} );
}
dialog.addAction( getString( R.string.follow_from_another_account ), new Runnable() {
@Override public void run(){
followFromAccount( access_info, who, follow_complete_callback );
}
} );
dialog.addAction( getString( R.string.unfollow ), new Runnable() {
@Override
public void run(){
callFollow( access_info, who, false, unfollow_complete_callback );
}
} );
dialog.addAction( getString( R.string.mute ), new Runnable() {
@Override public void run(){
callMute( access_info, who, true, null );
}
} );
dialog.addAction( getString( R.string.unmute ), new Runnable() {
@Override
public void run(){
callMute( access_info, who, false, null );
}
} );
dialog.addAction( getString( R.string.block ), new Runnable() {
@Override public void run(){
callBlock( access_info, who, true, null );
}
} );
dialog.addAction( getString( R.string.unblock ), new Runnable() {
@Override public void run(){
callBlock( access_info, who, false, null );
}
} );
// dialog.addAction( getString( R.string.report ), new Runnable() {
// @Override public void run(){
// openReportForm( access_info, who, null );
// }
// } );
if( ! access_info.isPseudo() ){
dialog.addAction( getString( R.string.unfollow ), new Runnable() {
@Override
public void run(){
callFollow( access_info, who, false, unfollow_complete_callback );
}
} );
dialog.addAction( getString( R.string.mute ), new Runnable() {
@Override public void run(){
callMute( access_info, who, true, null );
}
} );
dialog.addAction( getString( R.string.unmute ), new Runnable() {
@Override
public void run(){
callMute( access_info, who, false, null );
}
} );
dialog.addAction( getString( R.string.block ), new Runnable() {
@Override public void run(){
callBlock( access_info, who, true, null );
}
} );
dialog.addAction( getString( R.string.unblock ), new Runnable() {
@Override public void run(){
callBlock( access_info, who, false, null );
}
} );
}
dialog.show( this, null );
}
@ -2106,8 +2138,7 @@ public class ActMain extends AppCompatActivity
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED
, "" // 空データ
) );
TootApiResult result = client.request( "/api/v1/notifications/clear", request_builder );
return result;
return client.request( "/api/v1/notifications/clear", request_builder );
}
@Override
@ -2117,6 +2148,7 @@ public class ActMain extends AppCompatActivity
@Override
protected void onPostExecute( TootApiResult result ){
//noinspection StatementWithEmptyBody
if( result == null ){
//cancelled.
}else if( result.object != null ){

View File

@ -78,6 +78,7 @@ class Column {
private static final String PATH_STATUSES = "/api/v1/statuses/%d"; // 1:status_id
private static final String PATH_STATUSES_CONTEXT = "/api/v1/statuses/%d/context"; // 1:status_id
private static final String PATH_SEARCH = "/api/v1/search?q=%s"; // 1: query(urlencoded) , also, append "&resolve=1" if resolve non-local accounts
private static final String PATH_INSTANCE="/api/v1/instance";
private static final String KEY_ACCOUNT_ROW_ID = "account_id";
private static final String KEY_TYPE = "type";
@ -852,9 +853,16 @@ class Column {
default:
case TAB_STATUS:
String s = String.format( Locale.JAPAN, PATH_ACCOUNT_STATUSES, profile_id );
if( with_attachment ) s = s + "&only_media=1";
return getStatuses( client, s );
if( access_info.isPseudo()){
return client.request(PATH_INSTANCE );
}else{
String s = String.format( Locale.JAPAN, PATH_ACCOUNT_STATUSES, profile_id );
if( with_attachment ) s = s + "&only_media=1";
return getStatuses( client, s );
}
case TAB_FOLLOWING:
return parseAccountList( client,
@ -1387,10 +1395,13 @@ class Column {
default:
case TAB_STATUS:
String s = String.format( Locale.JAPAN, PATH_ACCOUNT_STATUSES, profile_id );
if( with_attachment ) s = s + "&only_media=1";
return getStatusList( client, s );
if( access_info.isPseudo()){
return client.request(PATH_INSTANCE );
}else{
String s = String.format( Locale.JAPAN, PATH_ACCOUNT_STATUSES, profile_id );
if( with_attachment ) s = s + "&only_media=1";
return getStatusList( client, s );
}
case TAB_FOLLOWING:
return getAccountList( client,
String.format( Locale.JAPAN, PATH_ACCOUNT_FOLLOWING, profile_id ) );
@ -1765,9 +1776,16 @@ class Column {
default:
case TAB_STATUS:
String s = String.format( Locale.JAPAN, PATH_ACCOUNT_STATUSES, profile_id );
if( with_attachment ) s = s + "&only_media=1";
return getStatusList( client, s );
if( access_info.isPseudo()){
return client.request(PATH_INSTANCE );
}else{
String s = String.format( Locale.JAPAN, PATH_ACCOUNT_STATUSES, profile_id );
if( with_attachment ) s = s + "&only_media=1";
return getStatusList( client, s );
}
case TAB_FOLLOWING:
return getAccountList( client,

View File

@ -81,32 +81,26 @@ public class TootApiClient {
private TootApiResult request_sub( String path, Request.Builder request_builder ){
if( callback.isApiCancelled() ) return null;
// アクセストークンを使ってAPIを呼び出す
callback.publishApiProgress( context.getString( R.string.request_api, path ) );
if( account == null ){
return new TootApiResult( "account is null" );
}
JSONObject token_info = account.token_info;
// if( token_info != null && token_info.optInt( KEY_AUTH_VERSION, 0 ) < AUTH_VERSION ){
// // このトークンは形式が古くて使えないよ
// token_info = null;
// }
if( callback.isApiCancelled() ) return null;
if( token_info == null ){
// アクセストークンの更新が必要
return new TootApiResult( context.getString( R.string.login_required ) );
request_builder.url( "https://" + instance + path );
String access_token = Utils.optStringX( token_info, "access_token" );
if( !TextUtils.isEmpty( access_token ) ){
request_builder.header( "Authorization", "Bearer " + access_token );
}
// アクセストークンを使ってAPIを呼び出す
callback.publishApiProgress( context.getString( R.string.request_api, path ) );
Request request = request_builder
.url( "https://" + instance + path )
.header( "Authorization", "Bearer " + Utils.optStringX( token_info, "access_token" ) )
.build();
Call call = ok_http_client.newCall( request );
Call call = ok_http_client.newCall( request_builder.build() );
Response response;
try{
response = call.execute();
@ -119,8 +113,6 @@ public class TootApiClient {
if( callback.isApiCancelled() ) return null;
// TODO: アクセストークンが無効な場合はどうなる
if( ! response.isSuccessful() ){
return new TootApiResult( context.getString( R.string.network_error_arg, response ) );
}
@ -148,6 +140,58 @@ public class TootApiClient {
}
}
// 疑似アカウントの追加時にインスタンスの検証を行う
public TootApiResult checkInstance(){
// サーバ情報APIを使う
String path = "/api/v1/instance";
callback.publishApiProgress( context.getString( R.string.request_api, path ) );
Request request = new Request.Builder()
.url( "https://" + instance + path )
.build();
Call call = ok_http_client.newCall( request );
Response response;
try{
response = call.execute();
}catch( Throwable ex ){
ex.printStackTrace( );
return new TootApiResult( Utils.formatError( ex, context.getResources(), R.string.network_error ) );
}
if( callback.isApiCancelled() ) return null;
if( ! response.isSuccessful() ){
return new TootApiResult( context.getString( R.string.network_error_arg, response ) );
}
try{
String json = response.body().string();
if( TextUtils.isEmpty( json ) || json.startsWith( "<" ) ){
return new TootApiResult( context.getString( R.string.response_not_json ) + "\n" + json );
}else if( json.startsWith( "[" ) ){
JSONArray array = new JSONArray( json );
return new TootApiResult( log,response, null, json, array );
}else{
JSONObject object = new JSONObject( json );
String error = Utils.optStringX( object, "error" );
if( ! TextUtils.isEmpty( error ) ){
return new TootApiResult( context.getString( R.string.api_error, error ) );
}
return new TootApiResult( response, null, json, object );
}
}catch( Throwable ex ){
ex.printStackTrace();
return new TootApiResult( Utils.formatError( ex, "API data error" ) );
}
}
public TootApiResult authorize1(){
JSONObject client_info = null;
@ -341,4 +385,7 @@ public class TootApiClient {
return new TootApiResult( Utils.formatError( ex, "API data error" ) );
}
}
}

View File

@ -18,6 +18,7 @@ import jp.juggler.subwaytooter.util.Utils;
public class TootAccount {
public static class List extends ArrayList< TootAccount > {
}
@ -72,10 +73,20 @@ public class TootAccount {
public long time_created_at;
public TootAccount(){
}
// 疑似アカウントの作成
public TootAccount( String username ){
this.acct = username;
this.username = username;
}
public static TootAccount parse( LogCategory log, LinkClickContext account, JSONObject src, TootAccount dst ){
if( src == null ) return null;
try{
dst.id = src.optLong( "id" );
dst.id = src.optLong( "id",-1L );
dst.username = Utils.optStringX( src, "username" );
dst.acct = Utils.optStringX( src, "acct" );

View File

@ -178,7 +178,7 @@ public class TootStatus extends TootId {
return date_format_utc.parse( strTime ).getTime();
}catch( ParseException ex ){
ex.printStackTrace();
log.e( ex, "TootStatus.parseTime failed." );
log.e( ex, "TootStatus.parseTime failed. src=%s",strTime );
}
}
return 0L;

View File

@ -2,6 +2,7 @@ package jp.juggler.subwaytooter.dialog;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.support.annotation.NonNull;
import java.util.ArrayList;
import java.util.Collections;
@ -14,48 +15,67 @@ import jp.juggler.subwaytooter.util.Utils;
public class AccountPicker {
public interface AccountPickerCallback{
void onAccountPicked(SavedAccount ai);
}
public static void pick( ActMain activity, boolean bAuto,final AccountPickerCallback callback){
final ArrayList< SavedAccount > account_list = SavedAccount.loadAccountList( ActMain.log );
pick( activity,bAuto,account_list,callback);
public interface AccountPickerCallback {
void onAccountPicked( SavedAccount ai );
}
public static void pick( ActMain activity, boolean bAuto,final ArrayList<SavedAccount > account_list,final AccountPickerCallback callback){
if( account_list == null || account_list.isEmpty() ){
Utils.showToast(activity,false,R.string.account_empty);
public static void pick( @NonNull ActMain activity, boolean bAllowPseudo, boolean bAuto
, @NonNull final AccountPickerCallback callback
){
ArrayList< SavedAccount > account_list = SavedAccount.loadAccountList( ActMain.log );
pick( activity, bAllowPseudo, bAuto, account_list, callback );
}
public static void pick( @NonNull ActMain activity, boolean bAllowPseudo, boolean bAuto
, @NonNull final ArrayList< SavedAccount > account_list
, @NonNull final AccountPickerCallback callback
){
if( account_list.isEmpty() ){
Utils.showToast( activity, false, R.string.account_empty );
return;
}
if( ! bAllowPseudo ){
ArrayList< SavedAccount > tmp_list = new ArrayList<>();
for( SavedAccount a : account_list ){
if( a.isPseudo() ) continue;
tmp_list.add( a );
}
account_list.clear();
account_list.addAll( tmp_list );
if( account_list.isEmpty() ){
Utils.showToast( activity, false, R.string.not_available_for_pseudo_account );
return;
}
}
if( bAuto && account_list.size() == 1 ){
callback.onAccountPicked(account_list.get(0));
callback.onAccountPicked( account_list.get( 0 ) );
return;
}
Collections.sort( account_list, new Comparator< SavedAccount >() {
@Override
public int compare( SavedAccount o1, SavedAccount o2 ){
return String.CASE_INSENSITIVE_ORDER.compare( o1.acct, o2.acct );
}
} );
String[] caption_list = new String[ account_list.size() ];
for(int i=0,ie=account_list.size();i<ie;++i){
SavedAccount ai = account_list.get(i);
caption_list[i] = ai.acct;
for( int i = 0, ie = account_list.size() ; i < ie ; ++ i ){
SavedAccount ai = account_list.get( i );
caption_list[ i ] = ai.acct;
}
new AlertDialog.Builder(activity)
.setNegativeButton( R.string.cancel,null )
new AlertDialog.Builder( activity )
.setNegativeButton( R.string.cancel, null )
.setItems( caption_list, new DialogInterface.OnClickListener() {
@Override
public void onClick( DialogInterface dialog, int which ){
if( which >= 0 && which < account_list.size() ){
callback.onAccountPicked(account_list.get(which));
callback.onAccountPicked( account_list.get( which ) );
dialog.dismiss();
}
}

View File

@ -10,6 +10,7 @@ import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.CheckBox;
import android.widget.TextView;
import java.io.BufferedReader;
@ -23,14 +24,15 @@ import jp.juggler.subwaytooter.util.Utils;
public class LoginForm {
public interface LoginFormCallback{
void startLogin(Dialog dialog,String instance);
void startLogin(Dialog dialog,String instance,boolean bPseudoAccount);
}
public static void showLoginForm( final Activity activity, String instance , final LoginFormCallback callback){
final View view = activity.getLayoutInflater().inflate( R.layout.dlg_account_add, null, false );
final AutoCompleteTextView etInstance = (AutoCompleteTextView) view.findViewById( R.id.etInstance );
final View btnOk = view.findViewById( R.id.btnOk );
final CheckBox cbPseudoAccount = (CheckBox) view.findViewById( R.id.cbPseudoAccount );
if( !TextUtils.isEmpty( instance ) ){
etInstance.setText(instance);
etInstance.setInputType( InputType.TYPE_NULL );
@ -61,7 +63,7 @@ public class LoginForm {
Utils.showToast( activity, true, R.string.instance_not_need_slash );
return;
}
callback.startLogin( dialog,instance );
callback.startLogin( dialog,instance ,cbPseudoAccount.isChecked() );
}
} );
view.findViewById( R.id.btnCancel ).setOnClickListener( new View.OnClickListener() {

View File

@ -268,4 +268,8 @@ public class SavedAccount extends TootAccount implements LinkClickContext {
if( url.charAt( 0 )=='/') return "https://"+host+url;
return url;
}
public boolean isPseudo(){
return "?".equals( username );
}
}

View File

@ -213,29 +213,29 @@ public class HTMLDecoder {
}
public static SpannableStringBuilder decodeHTML( LinkClickContext account, String src ){
SpannableStringBuilder sb = new SpannableStringBuilder();
try{
TokenParser tracker = new TokenParser( src );
Node rootNode = new Node();
rootNode.parseChild( tracker, "" );
SpannableStringBuilder sb = new SpannableStringBuilder();
rootNode.encodeSpan( account, sb );
int end = sb.length();
while( end > 0 && isWhitespace( sb.charAt( end - 1 ) ) ) -- end;
if( end < sb.length() ){
sb.delete( end, sb.length() );
}
if( src != null ){
TokenParser tracker = new TokenParser( src );
Node rootNode = new Node();
rootNode.parseChild( tracker, "" );
rootNode.encodeSpan( account, sb );
int end = sb.length();
while( end > 0 && isWhitespace( sb.charAt( end - 1 ) ) ) -- end;
if( end < sb.length() ){
sb.delete( end, sb.length() );
}
// sb.append( "\n" );
// sb.append( src );
return sb;
}
}catch( Throwable ex ){
ex.printStackTrace();
return null;
}
return sb;
}
// public static Spannable decodeTags( final LinkClickContext account, TootTag.List src_list ){

View File

@ -163,7 +163,7 @@ public class Utils {
public static String optStringX( JSONArray src, int key ){
return src.isNull( key ) ? null : src.optString( key );
}
public static ArrayList< String > parseStringArray( LogCategory log, JSONArray array ){
ArrayList< String > dst_list = new ArrayList<>();
if( array != null ){

View File

@ -115,7 +115,7 @@
android:text="@string/confirm_before_boost"
/>
<LinearLayout style="@style/setting_row_form">
<LinearLayout style="@style/setting_row_form" >
<Switch
android:id="@+id/swConfirmBeforeBoost"

View File

@ -10,8 +10,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:text="@string/instance"
@ -23,13 +21,19 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginStart="12dp"
android:inputType="textUri"
android:hint="@string/instance_hint"
android:imeOptions="actionDone"
/>
<CheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:text="@string/pseudo_account"
android:id="@+id/cbPseudoAccount"
/>
<LinearLayout
style="?android:attr/buttonBarStyle"

View File

@ -206,5 +206,8 @@
<string name="sound">sound</string>
<string name="vibration">vibration</string>
<string name="close">Close</string>
<string name="not_available_for_pseudo_account">not available for pseudo account</string>
<string name="pseudo_account">pseudo account (non-login,only reading public data)</string>
<string name="server_confirmed">server confirmed.</string>
</resources>

View File

@ -202,4 +202,7 @@
<string name="sound"></string>
<string name="vibration">振動</string>
<string name="close">閉じる</string>
<string name="not_available_for_pseudo_account">疑似アカウントでは利用できません</string>
<string name="pseudo_account">疑似アカウント(ログインなし、公開データを読めるだけ)</string>
<string name="server_confirmed">サーバを確認しました</string>
</resources>

View File

@ -203,4 +203,7 @@
<string name="follow_request_rejected">%1$s\'s follow request is rejected.</string>
<string name="follow_request_authorized">%1$s\'s follow request is authorized.</string>
<string name="close">Close</string>
<string name="pseudo_account">pseudo account (non-login,only reading public data)</string>
<string name="server_confirmed">server confirmed.</string>
<string name="not_available_for_pseudo_account">not available for pseudo account</string>
</resources>