diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMain.java b/app/src/main/java/jp/juggler/subwaytooter/ActMain.java index 2fa13b8a..d30377c0 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActMain.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ActMain.java @@ -10,12 +10,13 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; +import android.os.SystemClock; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.customtabs.CustomTabsIntent; import android.support.v4.text.BidiFormatter; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPager; -import android.support.v4.view.ViewParentCompat; import android.support.v7.app.AlertDialog; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -131,10 +132,10 @@ public class ActMain extends AppCompatActivity @Override protected void onSaveInstanceState( Bundle outState ){ super.onSaveInstanceState( outState ); - if(pager_adapter != null){ + if( pager_adapter != null ){ outState.putInt( STATE_CURRENT_PAGE, pager.getCurrentItem() ); }else{ - int ve =tablet_layout_manager.findLastVisibleItemPosition(); + int ve = tablet_layout_manager.findLastVisibleItemPosition(); if( ve != RecyclerView.NO_POSITION ){ outState.putInt( STATE_CURRENT_PAGE, ve ); } @@ -143,12 +144,12 @@ public class ActMain extends AppCompatActivity @Override protected void onRestoreInstanceState( Bundle savedInstanceState ){ super.onRestoreInstanceState( savedInstanceState ); - int pos = savedInstanceState.getInt( STATE_CURRENT_PAGE); + int pos = savedInstanceState.getInt( STATE_CURRENT_PAGE ); if( pos > 0 && pos < app_state.column_list.size() ){ - if(pager_adapter != null){ - pager.setCurrentItem(pos); + if( pager_adapter != null ){ + pager.setCurrentItem( pos ); }else{ - tablet_layout_manager.smoothScrollToPosition( tablet_pager,null,pos ); + tablet_layout_manager.smoothScrollToPosition( tablet_pager, null, pos ); } } } @@ -440,7 +441,7 @@ public class ActMain extends AppCompatActivity if( vs == ve && vs != RecyclerView.NO_POSITION ){ column = app_state.column_list.get( vs ); }else{ - Utils.showToast( this,false,getString(R.string.cant_close_column_by_back_button_when_multiple_column_shown) ); + Utils.showToast( this, false, getString( R.string.cant_close_column_by_back_button_when_multiple_column_shown ) ); } } if( column != null ){ @@ -626,43 +627,41 @@ public class ActMain extends AppCompatActivity float density = dm.density; int media_thumb_height = 64; - sv = pref.getString(Pref.KEY_MEDIA_THUMB_HEIGHT,""); - if( !TextUtils.isEmpty( sv )){ + sv = pref.getString( Pref.KEY_MEDIA_THUMB_HEIGHT, "" ); + if( ! TextUtils.isEmpty( sv ) ){ try{ int iv = Integer.parseInt( sv ); if( iv >= 32 ){ media_thumb_height = iv; } - }catch(Throwable ex){ - ex.printStackTrace( ); + }catch( Throwable ex ){ + ex.printStackTrace(); } } - app_state.media_thumb_height = (int)(0.5f + media_thumb_height *density); + app_state.media_thumb_height = (int) ( 0.5f + media_thumb_height * density ); int column_w_min_dp = COLUMN_WIDTH_MIN_DP; - sv = pref.getString(Pref.KEY_COLUMN_WIDTH,""); - if( !TextUtils.isEmpty( sv )){ + sv = pref.getString( Pref.KEY_COLUMN_WIDTH, "" ); + if( ! TextUtils.isEmpty( sv ) ){ try{ int iv = Integer.parseInt( sv ); if( iv >= 100 ){ column_w_min_dp = iv; } - }catch(Throwable ex){ - ex.printStackTrace( ); + }catch( Throwable ex ){ + ex.printStackTrace(); } } int column_w_min = (int) ( 0.5f + column_w_min_dp * density ); - + int sw = dm.widthPixels; - - pager = (ViewPager) findViewById( R.id.viewPager ); tablet_pager = (RecyclerView) findViewById( R.id.rvPager ); - if( pref.getBoolean(Pref.KEY_DISABLE_TABLET_MODE,false) || sw < column_w_min * 2 ){ + if( pref.getBoolean( Pref.KEY_DISABLE_TABLET_MODE, false ) || sw < column_w_min * 2 ){ tablet_pager.setVisibility( View.GONE ); - + // SmartPhone mode pager_adapter = new ColumnPagerAdapter( this ); pager.setAdapter( pager_adapter ); @@ -914,9 +913,10 @@ public class ActMain extends AppCompatActivity // プロフURL if( "https".equals( uri.getScheme() ) ){ if( uri.getPath().startsWith( "/@" ) ){ - // ステータスをアプリ内で開く + Matcher m = reStatusPage.matcher( uri.toString() ); if( m.find() ){ + // ステータスをアプリ内で開く try{ // https://mastodon.juggler.jp/@SubwayTooter/(status_id) final String host = m.group( 1 ); @@ -957,38 +957,47 @@ public class ActMain extends AppCompatActivity return; } - // ユーザページをアプリ内で開く m = reUserPage.matcher( uri.toString() ); if( m.find() ){ + // ユーザページをアプリ内で開く + // https://mastodon.juggler.jp/@SubwayTooter final String host = m.group( 1 ); final String user = Uri.decode( m.group( 2 ) ); - ArrayList< SavedAccount > account_list_same_host = new ArrayList<>(); - for( SavedAccount a : SavedAccount.loadAccountList( log ) ){ + // 1: 検索APIは疑似アカウントでは開けない => startFindAccountが使えない + // 2: かりに検索できたとしてもユーザページは疑似アカウントでは開けない + + ArrayList< SavedAccount > account_list = SavedAccount.loadAccountList( log ); + ArrayList< SavedAccount > account_list_filtered = new ArrayList<>(); + for( SavedAccount a : account_list ){ + if( a.isPseudo() ) continue; if( host.equalsIgnoreCase( a.host ) ){ - account_list_same_host.add( a ); + account_list_filtered.add( a ); } } - // ソートする - Collections.sort( account_list_same_host, new Comparator< SavedAccount >() { - @Override public int compare( SavedAccount a, SavedAccount b ){ - return String.CASE_INSENSITIVE_ORDER.compare( AcctColor.getNickname( a.acct ), AcctColor.getNickname( b.acct ) ); + if( account_list_filtered.isEmpty() ){ + + for( SavedAccount a : account_list ){ + if( a.isPseudo() ) continue; + account_list_filtered.add( a ); + } + + if( account_list_filtered.isEmpty() ){ + // 認証されたアカウントが全くないので、ブラウザで開くしかない + openChromeTab( getDefaultInsertPosition(), null, uri.toString(), true ); + return; } - } ); - - if( account_list_same_host.isEmpty() ){ - account_list_same_host.add( addPseudoAccount( host ) ); } - AccountPicker.pick( this, true, true + AccountPicker.pick( this, false, true , getString( R.string.account_picker_open_user_who, user + "@" + host ) - , account_list_same_host + , account_list_filtered , new AccountPicker.AccountPickerCallback() { @Override public void onAccountPicked( @NonNull final SavedAccount ai ){ - startGetAccount( ai, host, user, new GetAccountCallback() { - @Override public void onGetAccount( TootAccount who ){ + startFindAccount( ai, host, user, new FindAccountCallback() { + @Override public void onFindAccount( TootAccount who ){ if( who != null ){ performOpenUser( getDefaultInsertPosition(), ai, who ); return; @@ -1016,7 +1025,10 @@ public class ActMain extends AppCompatActivity SavedAccount account = SavedAccount.loadAccount( log, db_id ); if( account != null ){ Column column = addColumn( getDefaultInsertPosition(), account, Column.TYPE_NOTIFICATIONS ); - + // 通知を読み直す + if( ! column.bInitialLoading ){ + column.startLoading(); + } } }catch( Throwable ex ){ ex.printStackTrace(); @@ -1024,6 +1036,8 @@ public class ActMain extends AppCompatActivity return; } + // OAuth2 認証コールバック + final ProgressDialog progress = new ProgressDialog( ActMain.this ); final AsyncTask< Void, Void, TootApiResult > task = new AsyncTask< Void, Void, TootApiResult >() { @@ -1227,8 +1241,8 @@ public class ActMain extends AppCompatActivity }else if( page_delete > 0 && page_showing == page_delete ){ int idx = page_delete - 1; scrollToColumn( idx ); - Column c = app_state.column_list.get(idx); - if( ! c.bInitialLoading ){ + Column c = app_state.column_list.get( idx ); + if( ! c.bFirstInitialized ){ c.startLoading(); } } @@ -1241,8 +1255,8 @@ public class ActMain extends AppCompatActivity }else if( page_delete > 0 ){ int idx = page_delete - 1; scrollToColumn( idx ); - Column c = app_state.column_list.get(idx); - if( ! c.bInitialLoading ){ + Column c = app_state.column_list.get( idx ); + if( ! c.bFirstInitialized ){ c.startLoading(); } } @@ -1268,7 +1282,7 @@ public class ActMain extends AppCompatActivity Column col = new Column( app_state, ai, this, type, params ); index = addColumn( col, index ); scrollToColumn( index ); - if( ! col.bInitialLoading ){ + if( ! col.bFirstInitialized ){ col.startLoading(); } return col; @@ -1321,13 +1335,13 @@ public class ActMain extends AppCompatActivity ////////////////////////////////////////////////////////////// - interface GetAccountCallback { + interface FindAccountCallback { // return account information // if failed, account is null. - void onGetAccount( TootAccount account ); + void onFindAccount( TootAccount account ); } - void startGetAccount( final SavedAccount access_info, final String host, final String user, final GetAccountCallback callback ){ + void startFindAccount( final SavedAccount access_info, final String host, final String user, final FindAccountCallback callback ){ final ProgressDialog progress = new ProgressDialog( this ); final AsyncTask< Void, Void, TootAccount > task = new AsyncTask< Void, Void, TootAccount >() { @@ -1380,7 +1394,7 @@ public class ActMain extends AppCompatActivity @Override protected void onPostExecute( TootAccount result ){ progress.dismiss(); - callback.onGetAccount( result ); + callback.onFindAccount( result ); } }; @@ -1396,15 +1410,15 @@ public class ActMain extends AppCompatActivity task.executeOnExecutor( App1.task_executor ); } - static final Pattern reHashTag = Pattern.compile( "\\Ahttps://([^/]+)/tags/([^?#]+)\\z" ); - static final Pattern reUserPage = Pattern.compile( "\\Ahttps://([^/]+)/@([^?#/]+)\\z" ); - static final Pattern reStatusPage = Pattern.compile( "\\Ahttps://([^/]+)/@([^?#/]+)/(\\d+)\\z" ); + static final Pattern reHashTag = Pattern.compile( "\\Ahttps://([^/]+)/tags/([^?#]+)(?:\\z|\\?)" ); + static final Pattern reUserPage = Pattern.compile( "\\Ahttps://([^/]+)/@([^?#/]+)(?:\\z|\\?)" ); + static final Pattern reStatusPage = Pattern.compile( "\\Ahttps://([^/]+)/@([^?#/]+)/(\\d+)(?:\\z|\\?)" ); - public void openChromeTab( final int pos, final SavedAccount access_info, final String url, boolean noIntercept ){ + public void openChromeTab( final int pos, @Nullable final SavedAccount access_info, final String url, boolean noIntercept ){ try{ log.d( "openChromeTab url=%s", url ); - if( ! noIntercept ){ + if( ! noIntercept && access_info != null ){ // ハッシュタグをアプリ内で開く Matcher m = reHashTag.matcher( url ); if( m.find() ){ @@ -1446,18 +1460,64 @@ public class ActMain extends AppCompatActivity // 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( pos, access_info, who ); - return; + + if( ! access_info.isPseudo() ){ + startFindAccount( access_info, host, user, new FindAccountCallback() { + @Override public void onFindAccount( TootAccount who ){ + if( who != null ){ + performOpenUser( pos, access_info, who ); + return; + } + openChromeTab( pos, access_info, url, true ); } - openChromeTab( pos, access_info, url, true ); + } ); + return; + } + // 1: 検索APIは疑似アカウントでは開けない => startFindAccountが使えない + // 2: かりに検索できたとしてもユーザページは疑似アカウントでは開けない + + ArrayList< SavedAccount > account_list = SavedAccount.loadAccountList( log ); + ArrayList< SavedAccount > account_list_filtered = new ArrayList<>(); + for( SavedAccount a : account_list ){ + if( a.isPseudo() ) continue; + if( host.equalsIgnoreCase( a.host ) ){ + account_list_filtered.add( a ); } - } ); + } + + if( account_list_filtered.isEmpty() ){ + + for( SavedAccount a : account_list ){ + if( a.isPseudo() ) continue; + account_list_filtered.add( a ); + } + + if( account_list_filtered.isEmpty() ){ + // 認証されたアカウントが全くないので、ブラウザで開くしかない + openChromeTab( getDefaultInsertPosition(), null, url, true ); + return; + } + } + + AccountPicker.pick( this, false, true + , getString( R.string.account_picker_open_user_who, user + "@" + host ) + , account_list_filtered + , new AccountPicker.AccountPickerCallback() { + @Override public void onAccountPicked( @NonNull final SavedAccount ai ){ + startFindAccount( ai, host, user, new FindAccountCallback() { + @Override public void onFindAccount( TootAccount who ){ + if( who != null ){ + performOpenUser( getDefaultInsertPosition(), ai, who ); + return; + } + openChromeTab( getDefaultInsertPosition(), ai, url, true ); + } + } ); + } + } ); return; + } - } try{ @@ -1641,10 +1701,6 @@ public class ActMain extends AppCompatActivity } ); } - public void performReply( SavedAccount account, TootStatus status ){ - ActPost.open( this, REQUEST_CODE_POST, account.db_id, status ); - } - public void performMention( SavedAccount account, TootAccount who ){ ActPost.open( this, REQUEST_CODE_POST, account.db_id, "@" + account.getFullAcct( who ) + " " ); } @@ -1969,6 +2025,66 @@ public class ActMain extends AppCompatActivity showColumnMatchAccount( access_info ); } + 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; + } + + new AsyncTask< Void, Void, TootApiResult >() { + TootStatus target_status; + + @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( final String s ){ + } + } ); + client.setAccount( access_info ); + + // 検索APIに他タンスのステータスのURLを投げると、自タンスのステータスを得られる + String path = String.format( Locale.JAPAN, Column.PATH_SEARCH, Uri.encode( arg_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 ); + 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 ); + } + if( target_status == null ){ + return new TootApiResult( getString( R.string.status_id_conversion_failed ) ); + } + } + return result; + } + + @Override + protected void onCancelled( TootApiResult result ){ + super.onPostExecute( result ); + } + + @Override + protected void onPostExecute( TootApiResult result ){ + if( result == null ){ + // cancelled. + }else if( target_status != null ){ + ActPost.open( ActMain.this, REQUEST_CODE_POST, access_info.db_id, target_status ); + }else{ + Utils.showToast( ActMain.this, true, result.error ); + } + } + }.executeOnExecutor( App1.task_executor ); + } + //////////////////////////////////////// private void performAccountSetting(){ @@ -2838,6 +2954,21 @@ public class ActMain extends AppCompatActivity } ); } + void openReplyFromAnotherAccount( @NonNull final SavedAccount access_info, final TootStatus status ){ + if( status == 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 ){ + performReply( + ai + , status + , ! ai.host.equalsIgnoreCase( access_info.host ) + ); + } + } ); + } + void openFollowFromAnotherAccount( @NonNull SavedAccount access_info, TootStatus status ){ if( status == null ) return; openFollowFromAnotherAccount( access_info, status.account ); @@ -2855,6 +2986,9 @@ public class ActMain extends AppCompatActivity } ); } + ///////////////////////////////////////////////////////////////////////// + // タブレット対応で必要になった関数など + private boolean closeColumnSetting(){ if( pager_adapter != null ){ ColumnViewHolder vh = pager_adapter.getColumnViewHolder( pager.getCurrentItem() ); @@ -2911,7 +3045,6 @@ public class ActMain extends AppCompatActivity updateColumnStrip(); return index; - } private void removeColumn( Column column ){ @@ -2966,22 +3099,22 @@ public class ActMain extends AppCompatActivity private void resizeColumnWidth(){ int column_w_min_dp = COLUMN_WIDTH_MIN_DP; - String sv = pref.getString(Pref.KEY_COLUMN_WIDTH,""); - if( !TextUtils.isEmpty( sv )){ + String sv = pref.getString( Pref.KEY_COLUMN_WIDTH, "" ); + if( ! TextUtils.isEmpty( sv ) ){ try{ int iv = Integer.parseInt( sv ); if( iv >= 100 ){ column_w_min_dp = iv; } - }catch(Throwable ex){ - ex.printStackTrace( ); + }catch( Throwable ex ){ + ex.printStackTrace(); } } DisplayMetrics dm = getResources().getDisplayMetrics(); - + final int sw = dm.widthPixels; - + float density = dm.density; int column_w_min = (int) ( 0.5f + column_w_min_dp * density ); @@ -2989,7 +3122,7 @@ public class ActMain extends AppCompatActivity tablet_pager_adapter.setColumnWidth( sw ); }else{ nScreenColumn = sw / column_w_min; - + // 2つは表示できるが3つは表示できないかもしれない int column_w_max = (int) ( 0.5f + column_w_min * 1.5f ); diff --git a/app/src/main/java/jp/juggler/subwaytooter/AlarmService.java b/app/src/main/java/jp/juggler/subwaytooter/AlarmService.java index 012c19bc..cea603f8 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/AlarmService.java +++ b/app/src/main/java/jp/juggler/subwaytooter/AlarmService.java @@ -23,11 +23,12 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; +import java.util.LinkedList; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; import jp.juggler.subwaytooter.api.TootApiClient; import jp.juggler.subwaytooter.api.TootApiResult; -import jp.juggler.subwaytooter.api.entity.TootApplication; import jp.juggler.subwaytooter.api.entity.TootNotification; import jp.juggler.subwaytooter.api.entity.TootStatus; import jp.juggler.subwaytooter.table.MutedApp; @@ -146,44 +147,57 @@ public class AlarmService extends IntentService { } } - TootApiClient client = new TootApiClient( this, new TootApiClient.Callback() { - @Override public boolean isApiCancelled(){ - return false; - } - - @Override public void publishApiProgress( String s ){ - - } - } ); - boolean bAlarmRequired = false; - HashSet< String > muted_app = MutedApp.getNameSet(); - HashSet< String > muted_word = MutedWord.getNameSet(); + final AtomicBoolean bAlarmRequired = new AtomicBoolean( false ); + final HashSet< String > muted_app = MutedApp.getNameSet(); + final HashSet< String > muted_word = MutedWord.getNameSet(); + + LinkedList thread_list = new LinkedList<>( ); + for( SavedAccount _a : account_list ){ + final SavedAccount account = _a; + Thread t = new Thread( new Runnable() { + @Override public void run(){ - for( SavedAccount account : account_list ){ - try{ - if( account.notification_mention - || account.notification_boost - || account.notification_favourite - || account.notification_follow - ){ - bAlarmRequired = true; - - ArrayList< Data > data_list = new ArrayList<>(); - - checkAccount( client, data_list, account, muted_app ,muted_word); - - showNotification( account.db_id, data_list ); - + try{ + if( account.notification_mention + || account.notification_boost + || account.notification_favourite + || account.notification_follow + ){ + bAlarmRequired.set(true); + + TootApiClient client = new TootApiClient( AlarmService.this, new TootApiClient.Callback() { + @Override public boolean isApiCancelled(){ + return false; + } + @Override public void publishApiProgress( String s ){ + } + } ); + + ArrayList< Data > data_list = new ArrayList<>(); + checkAccount( client, data_list, account, muted_app ,muted_word); + showNotification( account.db_id, data_list ); + } + }catch( Throwable ex ){ + ex.printStackTrace(); + } } - }catch( Throwable ex ){ - ex.printStackTrace(); + } ); + thread_list.add( t); + t.start(); + } + + for( Thread t : thread_list ){ + try{ + t.join(); + }catch(Throwable ex){ + ex.printStackTrace( ); } } alarm_manager.cancel( pi_next ); - if( bAlarmRequired ){ + if( bAlarmRequired.get() ){ long now = SystemClock.elapsedRealtime(); alarm_manager.setWindow( AlarmManager.ELAPSED_REALTIME_WAKEUP diff --git a/app/src/main/java/jp/juggler/subwaytooter/Column.java b/app/src/main/java/jp/juggler/subwaytooter/Column.java index 0ff1446f..dbc1fd18 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/Column.java +++ b/app/src/main/java/jp/juggler/subwaytooter/Column.java @@ -48,7 +48,6 @@ import jp.juggler.subwaytooter.util.Utils; class Column implements StreamReader.Callback { private static final LogCategory log = new LogCategory( "Column" ); - interface Callback { boolean isActivityResume(); } @@ -236,7 +235,7 @@ class Column implements StreamReader.Callback { item.put( KEY_DONT_STREAMING, dont_streaming ); item.put( KEY_DONT_AUTO_REFRESH, dont_auto_refresh ); item.put( KEY_HIDE_MEDIA_DEFAULT, hide_media_default ); - + item.put( KEY_REGEX_TEXT, regex_text ); item.put( KEY_HEADER_BACKGROUND_COLOR, header_bg_color ); @@ -374,6 +373,13 @@ class Column implements StreamReader.Callback { void dispose(){ is_dispose.set( true ); stopStreaming(); + + if( _holder != null ){ + try{ + _holder.getListView().setAdapter( null ); + }catch( Throwable ignored ){ + } + } } String getColumnName( boolean bLong ){ @@ -698,41 +704,43 @@ class Column implements StreamReader.Callback { AlarmService.dataRemoved( context, access_info.db_id ); } - private ColumnViewHolder holder; + ////////////////////////////////////////////////////////////////////////////////////// - void setColumnViewHolder( ColumnViewHolder holder ){ - this.holder = holder; + private ColumnViewHolder _holder; + + void setColumnViewHolder( ColumnViewHolder new_holder ){ + this._holder = new_holder; } - private final Runnable proc_showContent = new Runnable() { - @Override public void run(){ - if( holder != null ) holder.showContent(); - } - }; - private final Runnable proc_showColumnHeader = new Runnable() { - @Override public void run(){ - if( holder != null ) holder.showColumnHeader(); - } - }; - private final Runnable proc_showColumnColor = new Runnable() { - @Override public void run(){ - if( holder != null ) holder.showColumnColor(); - } - }; + private ColumnViewHolder getViewHolder(){ + return is_dispose.get() ? null : _holder; + } void fireShowContent(){ - Utils.runOnMainThread( proc_showContent ); + if( ! Utils.isMainThread() ){ + throw new RuntimeException( "fireShowColumnHeader: not on main thread." ); + } + ColumnViewHolder holder = getViewHolder(); + if( holder != null ) holder.showContent(); } - // カラムヘッダ部分だけ更新する void fireShowColumnHeader(){ - Utils.runOnMainThread( proc_showColumnHeader ); + if( ! Utils.isMainThread() ){ + throw new RuntimeException( "fireShowColumnHeader: not on main thread." ); + } + ColumnViewHolder holder = getViewHolder(); + if( holder != null ) holder.showColumnHeader(); } void fireColumnColor(){ - Utils.runOnMainThread( proc_showColumnColor ); + if( ! Utils.isMainThread() ){ + throw new RuntimeException( "fireShowColumnHeader: not on main thread." ); + } + ColumnViewHolder holder = getViewHolder(); + if( holder != null ) holder.showColumnColor(); } + ////////////////////////////////////////////////////////////////////////////////////// private AsyncTask< Void, Void, TootApiResult > last_task; @@ -907,6 +915,13 @@ class Column implements StreamReader.Callback { fireShowContent(); + try{ + ColumnViewHolder holder = getViewHolder(); + if( holder != null ) holder.getRefreshLayout().setRefreshing( false ); + }catch(Throwable ignored){ + + } + AsyncTask< Void, Void, TootApiResult > task = this.last_task = new AsyncTask< Void, Void, TootApiResult >() { TootApiResult parseAccount1( TootApiClient client, String path_base ){ @@ -1166,6 +1181,7 @@ class Column implements StreamReader.Callback { @Override protected void onPostExecute( TootApiResult result ){ + if( is_dispose.get() ) return; if( isCancelled() || result == null ){ return; @@ -1186,11 +1202,12 @@ class Column implements StreamReader.Callback { resumeStreaming( false ); } fireShowContent(); - + // 初期ロードの直後は先頭に移動する try{ - holder.getListView().setSelection( 0 ); - }catch(Throwable ignored){ + ColumnViewHolder holder = getViewHolder(); + if( holder != null ) holder.getListView().setSelection( 0 ); + }catch( Throwable ignored ){ } } }; @@ -1362,14 +1379,15 @@ class Column implements StreamReader.Callback { case TYPE_FEDERATE: startRefresh( true, false, status_id, refresh_after_toot ); break; + case TYPE_PROFILE: if( profile_tab == TAB_STATUS && profile_id == access_info.id ){ startRefresh( true, false, status_id, refresh_after_toot ); } break; + case TYPE_CONVERSATION: startLoading(); - break; } } @@ -1381,22 +1399,26 @@ class Column implements StreamReader.Callback { if( last_task != null ){ if( ! bSilent ){ Utils.showToast( context, true, R.string.column_is_busy ); + ColumnViewHolder holder = getViewHolder(); if( holder != null ) holder.getRefreshLayout().setRefreshing( false ); } return; }else if( bBottom && max_id == null ){ if( ! bSilent ){ Utils.showToast( context, true, R.string.end_of_list ); + ColumnViewHolder holder = getViewHolder(); if( holder != null ) holder.getRefreshLayout().setRefreshing( false ); } return; }else if( ! bBottom && since_id == null ){ + ColumnViewHolder holder = getViewHolder(); if( holder != null ) holder.getRefreshLayout().setRefreshing( false ); startLoading(); return; } if( bSilent ){ + ColumnViewHolder holder = getViewHolder(); if( holder != null ){ holder.getRefreshLayout().setRefreshing( true ); } @@ -1820,6 +1842,8 @@ class Column implements StreamReader.Callback { @Override protected void onPostExecute( TootApiResult result ){ + if( is_dispose.get() ) return; + if( isCancelled() || result == null ){ return; } @@ -1846,6 +1870,7 @@ class Column implements StreamReader.Callback { // 事前にスクロール位置を覚えておく ScrollPosition sp = null; + ColumnViewHolder holder = getViewHolder(); if( holder != null ){ sp = holder.getScrollPosition(); } @@ -1911,6 +1936,7 @@ class Column implements StreamReader.Callback { return; } + ColumnViewHolder holder = getViewHolder(); if( holder != null ){ holder.getRefreshLayout().setRefreshing( true ); } @@ -2223,10 +2249,12 @@ class Column implements StreamReader.Callback { @Override protected void onPostExecute( TootApiResult result ){ + if( is_dispose.get() ) return; if( isCancelled() || result == null ){ return; } + last_task = null; bRefreshLoading = false; @@ -2243,16 +2271,18 @@ class Column implements StreamReader.Callback { ArrayList< Object > list_new = duplicate_map.filterDuplicate( list_tmp ); + ColumnViewHolder holder = getViewHolder(); + // idx番目の要素がListViewのtopから何ピクセル下にあるか int restore_idx = position + 1; int restore_y = 0; if( holder != null ){ try{ - restore_y = getItemTop( restore_idx ); + restore_y = getItemTop( holder, restore_idx ); }catch( IndexOutOfBoundsException ex ){ restore_idx = position; try{ - restore_y = getItemTop( restore_idx ); + restore_y = getItemTop( holder, restore_idx ); }catch( IndexOutOfBoundsException ex2 ){ restore_idx = - 1; } @@ -2267,7 +2297,7 @@ class Column implements StreamReader.Callback { if( holder != null ){ //noinspection StatementWithEmptyBody if( restore_idx >= 0 ){ - setItemTop( restore_idx + added - 1, restore_y ); + setItemTop( holder, restore_idx + added - 1, restore_y ); }else{ // ギャップが画面内にない場合、何もしない } @@ -2293,7 +2323,8 @@ class Column implements StreamReader.Callback { } // 特定の要素が特定の位置に来るようにスクロール位置を調整する - private void setItemTop( int idx, int y ){ + private void setItemTop( @NonNull ColumnViewHolder holder, int idx, int y ){ + MyListView listView = holder.getListView(); boolean hasHeader = holder.hasHeaderView(); if( hasHeader ){ @@ -2309,7 +2340,8 @@ class Column implements StreamReader.Callback { listView.setSelectionFromTop( idx, y ); } - private int getItemTop( int idx ){ + private int getItemTop( @NonNull ColumnViewHolder holder, int idx ){ + MyListView listView = holder.getListView(); boolean hasHeader = holder.hasHeaderView(); @@ -2382,6 +2414,7 @@ class Column implements StreamReader.Callback { log.e( ex, "getId() failed. o=", list_new.get( 0 ) ); } } + ColumnViewHolder holder = getViewHolder(); // 事前にスクロール位置を覚えておく ScrollPosition sp = null; @@ -2396,7 +2429,7 @@ class Column implements StreamReader.Callback { if( list_data.size() > 0 ){ try{ restore_idx = holder.getListView().getFirstVisiblePosition(); - restore_y = getItemTop( restore_idx ); + restore_y = getItemTop( holder, restore_idx ); }catch( IndexOutOfBoundsException ex ){ restore_idx = - 1; restore_y = 0; @@ -2426,11 +2459,11 @@ class Column implements StreamReader.Callback { if( holder != null ){ //noinspection StatementWithEmptyBody - if( sp == null || ( sp.pos == 0 && sp.top == 0 ) ){ + if( sp.pos == 0 && sp.top == 0 ){ // スクロール位置が先頭なら先頭のまま }else if( restore_idx >= 0 ){ // - setItemTop( restore_idx + added, restore_y ); + setItemTop( holder, restore_idx + added, restore_y ); }else{ // ギャップが画面内にない場合、何もしない } @@ -2446,6 +2479,7 @@ class Column implements StreamReader.Callback { }; @Override public void onStreamingMessage( String event_type, Object o ){ + if( is_dispose.get() ) return; if( o instanceof Long ){ removeStatus( access_info, (Long) o ); @@ -2510,7 +2544,7 @@ class Column implements StreamReader.Callback { log.d( "onResume: column was disposed." ); return; } - + // 未初期化なら何もしない if( ! bFirstInitialized ){ log.d( "onResume: column is not initialized." ); @@ -2529,9 +2563,9 @@ class Column implements StreamReader.Callback { log.d( "onResume: bRefreshingTop is true." ); }else if( ! bRefreshLoading - && canAutoRefresh() - && ! App1.getAppState( context ).pref.getBoolean( Pref.KEY_DONT_REFRESH_ON_RESUME, false ) - && ! dont_auto_refresh + && canAutoRefresh() + && ! App1.getAppState( context ).pref.getBoolean( Pref.KEY_DONT_REFRESH_ON_RESUME, false ) + && ! dont_auto_refresh ){ // リフレッシュしてからストリーミング開始 @@ -2589,7 +2623,6 @@ class Column implements StreamReader.Callback { private boolean bPutGap; - private void resumeStreaming( boolean bPutGap ){ if( ! canStreaming() ){ diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnPagerAdapter.java b/app/src/main/java/jp/juggler/subwaytooter/ColumnPagerAdapter.java index 6fb7d3ed..66529a9e 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnPagerAdapter.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnPagerAdapter.java @@ -14,7 +14,7 @@ class ColumnPagerAdapter extends PagerAdapter { private final ActMain activity; private final LayoutInflater inflater; - final ArrayList< Column > column_list; + private final ArrayList< Column > column_list; private final SparseArray< ColumnViewHolder > holder_list = new SparseArray<>(); @@ -51,11 +51,11 @@ class ColumnPagerAdapter extends PagerAdapter { container.addView( root, 0 ); Column column = column_list.get( page_idx ); - ColumnViewHolder holder = new ColumnViewHolder( activity, column ); + ColumnViewHolder holder = new ColumnViewHolder( activity,root ); // holder_list.put( page_idx, holder ); // - holder.onPageCreate( root, page_idx, column_list.size() ); + holder.onPageCreate( column, page_idx, column_list.size() ); return root; } @@ -68,8 +68,7 @@ class ColumnPagerAdapter extends PagerAdapter { ColumnViewHolder holder = holder_list.get( page_idx ); holder_list.remove( page_idx ); if( holder != null ){ - - holder.onPageDestroy( view ); + holder.onPageDestroy(); } } } \ No newline at end of file diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java index fa360da3..f8b3a5f4 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java @@ -3,6 +3,7 @@ package jp.juggler.subwaytooter; import android.graphics.Bitmap; import android.net.Uri; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v4.view.ViewCompat; import android.text.Editable; import android.text.TextUtils; @@ -16,12 +17,12 @@ import android.widget.CompoundButton; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; +import android.widget.ListAdapter; import android.widget.TextView; import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayout; import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayoutDirection; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; import jp.juggler.subwaytooter.table.AcctColor; @@ -38,66 +39,54 @@ class ColumnViewHolder private static final LogCategory log = new LogCategory( "ColumnViewHolder" ); final ActMain activity; - final Column column; - private final AtomicBoolean is_destroyed = new AtomicBoolean( false ); - private final ItemListAdapter status_adapter; - - ColumnViewHolder( ActMain activity, Column column ){ - this.activity = activity; - this.column = column; - this.status_adapter = new ItemListAdapter( activity, column ); - } - - private boolean isPageDestroyed(){ - return is_destroyed.get() || activity.isFinishing(); - } - - void onPageDestroy( @SuppressWarnings("UnusedParameters") View root ){ - log.d( "onPageDestroy:%s", column.getColumnName( true ) ); - is_destroyed.set( true ); - saveScrollPosition(); - - column.setColumnViewHolder( null ); - - closeBitmaps(); - - activity.closeListItemPopup(); - - } + @Nullable Column column; + @Nullable private HeaderViewHolder vh_header; + @Nullable private ItemListAdapter status_adapter; - private TextView tvLoading; - private MyListView listView; - private TextView tvColumnContext; - private TextView tvColumnName; - private HeaderViewHolder vh_header; - private SwipyRefreshLayout swipyRefreshLayout; - private View btnSearch; - private EditText etSearch; - private CheckBox cbResolve; - private View llColumnSetting; - private EditText etRegexFilter; - private TextView tvRegexFilterError; - private ImageView ivColumnIcon; + private final TextView tvLoading; + private final MyListView listView; + private final SwipyRefreshLayout swipyRefreshLayout; - private View llColumnHeader; - private TextView tvColumnIndex; - private ImageButton btnColumnSetting; - private ImageButton btnColumnReload; - private ImageButton btnColumnClose; + private final View llColumnHeader; + private final TextView tvColumnIndex; + private final TextView tvColumnContext; + private final ImageView ivColumnIcon; + private final TextView tvColumnName; - private View flColumnBackground; - private ImageView ivColumnBackgroundImage; + private final View llColumnSetting; + + private final View btnSearch; + private final EditText etSearch; + private final CheckBox cbResolve; + private final EditText etRegexFilter; + private final TextView tvRegexFilterError; - void onPageCreate( View root, int page_idx, int page_count ){ - log.d( "onPageCreate:%s", column.getColumnName( true ) ); + private final ImageButton btnColumnSetting; + private final ImageButton btnColumnReload; + private final ImageButton btnColumnClose; + + private final View flColumnBackground; + private final ImageView ivColumnBackgroundImage; + private final View llSearch; + private final CheckBox cbDontCloseColumn; + private final CheckBox cbWithAttachment; + private final CheckBox cbDontShowBoost; + private final CheckBox cbDontShowReply; + private final CheckBox cbDontStreaming; + private final CheckBox cbDontAutoRefresh; + private final CheckBox cbHideMediaDefault; + private final View llRegexFilter; + private final Button btnDeleteNotification; + + ColumnViewHolder( ActMain arg_activity, View root ){ + this.activity = arg_activity; flColumnBackground = root.findViewById( R.id.flColumnBackground ); ivColumnBackgroundImage = (ImageView) root.findViewById( R.id.ivColumnBackgroundImage ); llColumnHeader = root.findViewById( R.id.llColumnHeader ); tvColumnIndex = (TextView) root.findViewById( R.id.tvColumnIndex ); - tvColumnIndex.setText( activity.getString( R.string.column_index, page_idx + 1, page_count ) ); tvColumnName = (TextView) root.findViewById( R.id.tvColumnName ); tvColumnContext = (TextView) root.findViewById( R.id.tvColumnContext ); @@ -107,207 +96,224 @@ class ColumnViewHolder btnColumnReload = (ImageButton) root.findViewById( R.id.btnColumnReload ); btnColumnClose = (ImageButton) root.findViewById( R.id.btnColumnClose ); + tvLoading = (TextView) root.findViewById( R.id.tvLoading ); + listView = (MyListView) root.findViewById( R.id.listView ); + + btnSearch = root.findViewById( R.id.btnSearch ); + etSearch = (EditText) root.findViewById( R.id.etSearch ); + cbResolve = (CheckBox) root.findViewById( R.id.cbResolve ); + + llSearch = root.findViewById( R.id.llSearch ); + + llColumnSetting = root.findViewById( R.id.llColumnSetting ); + + cbDontCloseColumn = (CheckBox) root.findViewById( R.id.cbDontCloseColumn ); + cbWithAttachment = (CheckBox) root.findViewById( R.id.cbWithAttachment ); + cbDontShowBoost = (CheckBox) root.findViewById( R.id.cbDontShowBoost ); + cbDontShowReply = (CheckBox) root.findViewById( R.id.cbDontShowReply ); + cbDontStreaming = (CheckBox) root.findViewById( R.id.cbDontStreaming ); + cbDontAutoRefresh = (CheckBox) root.findViewById( R.id.cbDontAutoRefresh ); + cbHideMediaDefault = (CheckBox) root.findViewById( R.id.cbHideMediaDefault ); + etRegexFilter = (EditText) root.findViewById( R.id.etRegexFilter ); + llRegexFilter = root.findViewById( R.id.llRegexFilter ); + tvRegexFilterError = (TextView) root.findViewById( R.id.tvRegexFilterError ); + listView.setOnItemClickListener( status_adapter ); + + btnDeleteNotification = (Button) root.findViewById( R.id.btnDeleteNotification ); + + llColumnHeader.setOnClickListener( this ); btnColumnSetting.setOnClickListener( this ); btnColumnReload.setOnClickListener( this ); btnColumnClose.setOnClickListener( this ); - - llColumnHeader.setOnClickListener( this ); + btnDeleteNotification.setOnClickListener( this ); root.findViewById( R.id.btnColor ).setOnClickListener( this ); - tvLoading = (TextView) root.findViewById( R.id.tvLoading ); - listView = (MyListView) root.findViewById( R.id.listView ); - listView.setAdapter( null ); - if( column.column_type == Column.TYPE_PROFILE ){ - vh_header = new HeaderViewHolder( activity, column, listView ); - status_adapter.header = vh_header; - }else{ - status_adapter.header = null; - } - listView.setAdapter( status_adapter ); - this.swipyRefreshLayout = (SwipyRefreshLayout) root.findViewById( R.id.swipyRefreshLayout ); swipyRefreshLayout.setOnRefreshListener( this ); swipyRefreshLayout.setDistanceToTriggerSync( (int) ( 0.5f + 20f * activity.density ) ); - View llSearch = root.findViewById( R.id.llSearch ); - btnSearch = root.findViewById( R.id.btnSearch ); - etSearch = (EditText) root.findViewById( R.id.etSearch ); - cbResolve = (CheckBox) root.findViewById( R.id.cbResolve ); + cbDontCloseColumn.setOnCheckedChangeListener( this ); + cbWithAttachment.setOnCheckedChangeListener( this ); + cbDontShowBoost.setOnCheckedChangeListener( this ); + cbDontShowReply.setOnCheckedChangeListener( this ); + cbDontStreaming.setOnCheckedChangeListener( this ); + cbDontAutoRefresh.setOnCheckedChangeListener( this ); + cbHideMediaDefault.setOnCheckedChangeListener( this ); - listView.setFastScrollEnabled( ! Pref.pref( activity ).getBoolean( Pref.KEY_DISABLE_FAST_SCROLLER, true ) ); - - boolean bAllowFilter; - switch( column.column_type ){ - default: - bAllowFilter = true; - break; - case Column.TYPE_SEARCH: - case Column.TYPE_CONVERSATION: - case Column.TYPE_REPORTS: - case Column.TYPE_BLOCKS: - case Column.TYPE_MUTES: - case Column.TYPE_FOLLOW_REQUESTS: - case Column.TYPE_NOTIFICATIONS: - bAllowFilter = false; - break; - } - - boolean bAllowFilterBoost; - switch( column.column_type ){ - default: - bAllowFilterBoost = false; - break; - case Column.TYPE_HOME: - case Column.TYPE_PROFILE: - bAllowFilterBoost = true; - break; - } - - llColumnSetting = root.findViewById( R.id.llColumnSetting ); - llColumnSetting.setVisibility( View.GONE ); - - CheckBox cb; - cb = (CheckBox) root.findViewById( R.id.cbDontCloseColumn ); - cb.setChecked( column.dont_close ); - cb.setOnCheckedChangeListener( this ); - - cb = (CheckBox) root.findViewById( R.id.cbWithAttachment ); - cb.setChecked( column.with_attachment ); - cb.setOnCheckedChangeListener( this ); - cb.setEnabled( bAllowFilter ); - cb.setVisibility( bAllowFilter ? View.VISIBLE : View.GONE ); - - cb = (CheckBox) root.findViewById( R.id.cbDontShowBoost ); - cb.setChecked( column.dont_show_boost ); - cb.setOnCheckedChangeListener( this ); - cb.setEnabled( bAllowFilter ); - cb.setVisibility( bAllowFilterBoost ? View.VISIBLE : View.GONE ); - - cb = (CheckBox) root.findViewById( R.id.cbDontShowReply ); - cb.setChecked( column.dont_show_reply ); - cb.setOnCheckedChangeListener( this ); - cb.setEnabled( bAllowFilter ); - cb.setVisibility( bAllowFilterBoost ? View.VISIBLE : View.GONE ); - - cb = (CheckBox) root.findViewById( R.id.cbDontStreaming ); - if( ! column.canStreaming() ){ - cb.setVisibility( View.GONE ); - }else{ - cb.setVisibility( View.VISIBLE ); - cb.setChecked( column.dont_streaming ); - cb.setOnCheckedChangeListener( this ); - } - - cb = (CheckBox) root.findViewById( R.id.cbDontAutoRefresh ); - if( ! column.canAutoRefresh() ){ - cb.setVisibility( View.GONE ); - }else{ - cb.setVisibility(View.VISIBLE ); - cb.setChecked( column.dont_auto_refresh ); - cb.setOnCheckedChangeListener( this ); - } - - cb = (CheckBox) root.findViewById( R.id.cbHideMediaDefault ); - if( ! column.canShowMedia() ){ - cb.setVisibility( View.GONE ); - }else{ - cb.setVisibility(View.VISIBLE ); - cb.setChecked( column.hide_media_default ); - cb.setOnCheckedChangeListener( this ); - } - - etRegexFilter = (EditText) root.findViewById( R.id.etRegexFilter ); - if( ! bAllowFilter ){ - etRegexFilter.setVisibility( View.GONE ); - root.findViewById( R.id.llRegexFilter ).setVisibility( View.GONE ); - }else{ - etRegexFilter.setText( column.regex_text ); - // tvRegexFilterErrorの表示を更新 - tvRegexFilterError = (TextView) root.findViewById( R.id.tvRegexFilterError ); - isRegexValid(); - // 入力の追跡 - etRegexFilter.addTextChangedListener( new TextWatcher() { - @Override - public void beforeTextChanged( CharSequence s, int start, int count, int after ){ - } - - @Override - public void onTextChanged( CharSequence s, int start, int before, int count ){ - } - - @Override public void afterTextChanged( Editable s ){ - activity.handler.removeCallbacks( proc_start_filter ); - if( isRegexValid() ){ - activity.handler.postDelayed( proc_start_filter, 1500L ); - } - } - } ); - } - Button button = (Button) root.findViewById( R.id.btnDeleteNotification ); - if( column.column_type != Column.TYPE_NOTIFICATIONS ){ - button.setVisibility( View.GONE ); - }else{ - button.setVisibility( View.VISIBLE ); - button.setOnClickListener( this ); + // 入力の追跡 + etRegexFilter.addTextChangedListener( new TextWatcher() { + @Override + public void beforeTextChanged( CharSequence s, int start, int count, int after ){ + } - } + @Override + public void onTextChanged( CharSequence s, int start, int before, int count ){ + } + + @Override public void afterTextChanged( Editable s ){ + if( loading_busy ) return; + activity.handler.removeCallbacks( proc_start_filter ); + if( isRegexValid() ){ + activity.handler.postDelayed( proc_start_filter, 1500L ); + } + } + } ); - if( column.column_type != Column.TYPE_SEARCH ){ - llSearch.setVisibility( View.GONE ); - }else{ - llSearch.setVisibility( View.VISIBLE ); - etSearch.setText( column.search_query ); - cbResolve.setChecked( column.search_resolve ); - btnSearch.setOnClickListener( this ); - etSearch.setOnEditorActionListener( new TextView.OnEditorActionListener() { - @Override - public boolean onEditorAction( TextView v, int actionId, KeyEvent event ){ + btnSearch.setOnClickListener( this ); + etSearch.setOnEditorActionListener( new TextView.OnEditorActionListener() { + @Override public boolean onEditorAction( TextView v, int actionId, KeyEvent event ){ + if( !loading_busy ){ if( actionId == EditorInfo.IME_ACTION_SEARCH ){ btnSearch.performClick(); return true; } - return false; } - } ); - } + return false; + } + } ); + } + + private boolean isPageDestroyed(){ + return column ==null || activity.isFinishing(); + } + + void onPageDestroy(){ + // タブレットモードの場合、onPageCreateより前に呼ばれる + + if( column != null ){ + log.d( "onPageDestroy #%s", tvColumnName.getText() ); + saveScrollPosition(); + listView.setAdapter( null ); + column.setColumnViewHolder( null ); + column = null; + } + closeBitmaps(); - switch( column.column_type ){ - case Column.TYPE_CONVERSATION: - case Column.TYPE_SEARCH: - swipyRefreshLayout.setEnabled( false ); - break; - default: - swipyRefreshLayout.setEnabled( true ); - break; + activity.closeListItemPopup(); + } + + private static void vg( View v, boolean visible ){ + v.setVisibility( visible ? View.VISIBLE : View.GONE ); + } + + private boolean loading_busy; + + void onPageCreate( Column column, int page_idx, int page_count ){ + loading_busy = true; + try{ + this.column = column; + + log.d( "onPageCreate:%s", column.getColumnName( true ) ); + + tvColumnIndex.setText( activity.getString( R.string.column_index, page_idx + 1, page_count ) ); + + listView.setAdapter( null ); + + this.status_adapter = new ItemListAdapter( activity, column ); + if( column.column_type == Column.TYPE_PROFILE ){ + vh_header = new HeaderViewHolder( activity, column, listView ); + status_adapter.header = vh_header; + }else{ + status_adapter.header = null; + } + + boolean bAllowFilter; + switch( column.column_type ){ + default: + bAllowFilter = true; + break; + case Column.TYPE_SEARCH: + case Column.TYPE_CONVERSATION: + case Column.TYPE_REPORTS: + case Column.TYPE_BLOCKS: + case Column.TYPE_MUTES: + case Column.TYPE_FOLLOW_REQUESTS: + case Column.TYPE_NOTIFICATIONS: + bAllowFilter = false; + break; + } + + boolean bAllowFilterBoost; + switch( column.column_type ){ + default: + bAllowFilterBoost = false; + break; + case Column.TYPE_HOME: + case Column.TYPE_PROFILE: + bAllowFilterBoost = true; + break; + } + + llColumnSetting.setVisibility( View.GONE ); + + cbDontCloseColumn.setChecked( column.dont_close ); + cbWithAttachment.setChecked( column.with_attachment ); + cbDontShowBoost.setChecked( column.dont_show_boost ); + cbDontShowReply.setChecked( column.dont_show_reply ); + cbDontStreaming.setChecked( column.dont_streaming ); + cbDontAutoRefresh.setChecked( column.dont_auto_refresh ); + cbHideMediaDefault.setChecked( column.hide_media_default ); + + etRegexFilter.setText( column.regex_text ); + etSearch.setText( column.search_query ); + cbResolve.setChecked( column.search_resolve ); + + vg( cbWithAttachment, bAllowFilter ); + vg( cbDontShowBoost, bAllowFilterBoost ); + vg( cbDontShowReply, bAllowFilterBoost ); + vg( cbDontStreaming, column.canStreaming() ); + vg( cbDontAutoRefresh, column.canAutoRefresh() ); + vg( cbHideMediaDefault, column.canShowMedia() ); + + vg( etRegexFilter, bAllowFilter ); + vg( llRegexFilter, bAllowFilter ); + + vg( btnDeleteNotification, column.column_type == Column.TYPE_NOTIFICATIONS ); + vg( llSearch, column.column_type == Column.TYPE_SEARCH ); + + // tvRegexFilterErrorの表示を更新 + if( bAllowFilter ){ + isRegexValid(); + } + + switch( column.column_type ){ + case Column.TYPE_CONVERSATION: + case Column.TYPE_SEARCH: + swipyRefreshLayout.setEnabled( false ); + break; + default: + swipyRefreshLayout.setEnabled( true ); + break; + } + + // + listView.setAdapter( status_adapter ); + listView.setFastScrollEnabled( ! Pref.pref( activity ).getBoolean( Pref.KEY_DISABLE_FAST_SCROLLER, true ) ); + + column.setColumnViewHolder( this ); + + showColumnColor(); + + showContent(); + }finally{ + loading_busy = false; } - - if( column.bSimpleList ){ - listView.setOnItemClickListener( status_adapter ); - }else{ - listView.setOnItemClickListener( null ); - } - - // - - column.setColumnViewHolder( this ); - - showColumnColor(); - - showContent(); } void showColumnColor(){ + if( column == null ) return; + int c = column.header_bg_color; if( c == 0 ){ llColumnHeader.setBackgroundResource( R.drawable.btn_bg_ddd ); }else{ - ViewCompat.setBackground( llColumnHeader,Styler.getAdaptiveRippleDrawable( + ViewCompat.setBackground( llColumnHeader, Styler.getAdaptiveRippleDrawable( c, - (column.header_fg_color != 0 ? column.header_fg_color : - Styler.getAttributeColor( activity,R.attr.colorRippleEffect )) + ( column.header_fg_color != 0 ? column.header_fg_color : + Styler.getAttributeColor( activity, R.attr.colorRippleEffect ) ) ) ); } @@ -393,6 +399,8 @@ class ColumnViewHolder private final Runnable proc_start_filter = new Runnable() { @Override public void run(){ if( isPageDestroyed() ) return; + if( column == null ) return; + if( isRegexValid() ){ column.regex_text = etRegexFilter.getText().toString(); activity.app_state.saveColumnList(); @@ -430,10 +438,13 @@ class ColumnViewHolder } @Override public void onRefresh( SwipyRefreshLayoutDirection direction ){ + if( column == null ) return; column.startRefresh( false, direction == SwipyRefreshLayoutDirection.BOTTOM, - 1L, - 1 ); } @Override public void onCheckedChanged( CompoundButton view, boolean isChecked ){ + if( loading_busy || column ==null || status_adapter ==null ) return; + switch( view.getId() ){ case R.id.cbDontCloseColumn: @@ -454,7 +465,6 @@ class ColumnViewHolder column.startLoading(); break; - case R.id.cbDontShowReply: column.dont_show_reply = isChecked; activity.app_state.saveColumnList(); @@ -470,7 +480,7 @@ class ColumnViewHolder column.stopStreaming(); } break; - + case R.id.cbDontAutoRefresh: column.dont_auto_refresh = isChecked; activity.app_state.saveColumnList(); @@ -487,6 +497,8 @@ class ColumnViewHolder @Override public void onClick( View v ){ + if( loading_busy || column ==null || status_adapter ==null ) return; + switch( v.getId() ){ case R.id.btnColumnClose: activity.closeColumn( false, column ); @@ -535,13 +547,10 @@ class ColumnViewHolder swipyRefreshLayout.setVisibility( View.GONE ); - // ロード完了後に先頭から表示させる - if( status_adapter.getCount() > 0 ){ - listView.setSelectionFromTop( 0, 0 ); - } } private void showColumnCloseButton(){ + if( column == null ) return; // カラム保護の状態 btnColumnClose.setEnabled( ! column.dont_close ); btnColumnClose.setAlpha( column.dont_close ? 0.3f : 1f ); @@ -564,6 +573,7 @@ class ColumnViewHolder // カラムヘッダなど、負荷が低い部分の表示更新 void showColumnHeader(){ + if( column == null ) return; String acct = column.access_info.acct; AcctColor ac = AcctColor.load( acct ); @@ -592,14 +602,14 @@ class ColumnViewHolder // クラッシュレポートにadapterとリストデータの状態不整合が多かったので、 // とりあえずリストデータ変更の通知だけは最優先で行っておく try{ - status_adapter.notifyDataSetChanged(); - }catch(Throwable ex){ - ex.printStackTrace( ); + if( status_adapter != null ) status_adapter.notifyDataSetChanged(); + }catch( Throwable ex ){ + ex.printStackTrace(); } showColumnHeader(); - if( column.is_dispose.get() ){ + if( column == null || column.is_dispose.get() ){ showError( "column was disposed." ); return; } @@ -645,8 +655,11 @@ class ColumnViewHolder } private void restoreScrollPosition(){ + if( column == null ) return; + ScrollPosition sp = column.scroll_save; if( sp == null ) return; + column.scroll_save = null; if( listView.getVisibility() == View.VISIBLE ){ @@ -656,11 +669,12 @@ class ColumnViewHolder } private void saveScrollPosition(){ - - if( listView.getVisibility() == View.VISIBLE ){ - column.scroll_save = new ScrollPosition( listView ); - }else{ - column.scroll_save = new ScrollPosition( 0, 0 ); + if( column != null && ! column.is_dispose.get() ){ + if( listView.getVisibility() == View.VISIBLE ){ + column.scroll_save = new ScrollPosition( listView ); + }else{ + column.scroll_save = new ScrollPosition( 0, 0 ); + } } } @@ -669,14 +683,16 @@ class ColumnViewHolder } void setScrollPosition( @NonNull ScrollPosition sp, final float delta ){ + final ListAdapter last_adapter = listView.getAdapter(); + if( column == null || last_adapter == null ) return; + sp.restore( listView ); + listView.postDelayed( new Runnable() { @Override public void run(){ - if( isPageDestroyed() ) return; + if( column == null || listView.getAdapter() != last_adapter ) return; listView.scrollListBy( (int) ( delta * activity.density ) ); } }, 20L ); - } - } diff --git a/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.java b/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.java index 167614c7..bda7bd6b 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.java +++ b/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.java @@ -66,6 +66,7 @@ class DlgContextMenu implements View.OnClickListener, View.OnLongClickListener { View btnText = viewRoot.findViewById( R.id.btnText ); View btnFavouriteAnotherAccount = viewRoot.findViewById( R.id.btnFavouriteAnotherAccount ); View btnBoostAnotherAccount = viewRoot.findViewById( R.id.btnBoostAnotherAccount ); + View btnReplyAnotherAccount = viewRoot.findViewById( R.id.btnReplyAnotherAccount ); View btnDelete = viewRoot.findViewById( R.id.btnDelete ); View btnReport = viewRoot.findViewById( R.id.btnReport ); Button btnMuteApp = (Button) viewRoot.findViewById( R.id.btnMuteApp ); @@ -105,9 +106,11 @@ class DlgContextMenu implements View.OnClickListener, View.OnLongClickListener { if( account_list_non_pseudo_same_instance.isEmpty() ){ btnFavouriteAnotherAccount.setVisibility( View.GONE ); btnBoostAnotherAccount.setVisibility( View.GONE ); + btnReplyAnotherAccount.setVisibility( View.GONE ); }else{ btnFavouriteAnotherAccount.setOnClickListener( this ); btnBoostAnotherAccount.setOnClickListener( this ); + btnReplyAnotherAccount.setOnClickListener( this ); } if( access_info.isPseudo() ){ btnDelete.setVisibility( View.GONE ); @@ -254,44 +257,15 @@ class DlgContextMenu implements View.OnClickListener, View.OnLongClickListener { break; case R.id.btnFavouriteAnotherAccount: - if( status != null ){ - AccountPicker.pick( activity, false, false - , activity.getString( R.string.account_picker_favourite ) - // , account_list_non_pseudo_same_instance - , account_list_non_pseudo - , new AccountPicker.AccountPickerCallback() { - @Override public void onAccountPicked( @NonNull SavedAccount ai ){ - activity.performFavourite( - ai - , ! ai.host.equalsIgnoreCase( access_info.host ) - , true - , status - , activity.favourite_complete_callback - ); - } - } ); - } + activity.openFavouriteFromAnotherAccount( access_info,status ); break; case R.id.btnBoostAnotherAccount: - if( status != null ){ - AccountPicker.pick( activity, false, false - , activity.getString( R.string.account_picker_boost ) - // , account_list_non_pseudo_same_instance - , account_list_non_pseudo - , new AccountPicker.AccountPickerCallback() { - @Override public void onAccountPicked( @NonNull SavedAccount ai ){ - activity.performBoost( - ai - , ! ai.host.equalsIgnoreCase( access_info.host ) - , true - , status - , false - , activity.boost_complete_callback - ); - } - } ); - } + activity.openBoostFromAnotherAccount( access_info,status ); + break; + + case R.id.btnReplyAnotherAccount: + activity.openReplyFromAnotherAccount( access_info,status ); break; case R.id.btnDelete: diff --git a/app/src/main/java/jp/juggler/subwaytooter/ItemListAdapter.java b/app/src/main/java/jp/juggler/subwaytooter/ItemListAdapter.java index 8a0f9f96..328df6fc 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ItemListAdapter.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ItemListAdapter.java @@ -78,9 +78,11 @@ class ItemListAdapter extends BaseAdapter implements AdapterView.OnItemClickList @Override public void onItemClick( AdapterView< ? > parent, View view, int position, long id ){ - Object tag = view.getTag(); - if( tag instanceof ItemViewHolder ){ - ( (ItemViewHolder) tag ).onItemClick( (MyListView) parent, view ); + if( column.bSimpleList ){ + Object tag = view.getTag(); + if( tag instanceof ItemViewHolder ){ + ( (ItemViewHolder) tag ).onItemClick( (MyListView) parent, view ); + } } } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/StatusButtons.java b/app/src/main/java/jp/juggler/subwaytooter/StatusButtons.java index 071142c2..28d0a8f5 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/StatusButtons.java +++ b/app/src/main/java/jp/juggler/subwaytooter/StatusButtons.java @@ -57,6 +57,7 @@ class StatusButtons implements View.OnClickListener, View.OnLongClickListener { // v = viewRoot.findViewById( R.id.btnReply ); v.setOnClickListener( this ); + v.setOnLongClickListener( this ); } @@ -117,9 +118,9 @@ class StatusButtons implements View.OnClickListener, View.OnLongClickListener { break; case R.id.btnReply: if( access_info.isPseudo() ){ - Utils.showToast( activity, false, R.string.not_available_for_pseudo_account ); + activity.openReplyFromAnotherAccount( access_info, status ); }else{ - activity.performReply( access_info, status ); + activity.performReply( access_info, status ,false); } break; case R.id.btnBoost: @@ -184,6 +185,10 @@ class StatusButtons implements View.OnClickListener, View.OnLongClickListener { activity.openFollowFromAnotherAccount( access_info, status ); break; + case R.id.btnReply: + activity.openReplyFromAnotherAccount( access_info, status ); + break; + } return true; } diff --git a/app/src/main/java/jp/juggler/subwaytooter/TabletColumnPagerAdapter.java b/app/src/main/java/jp/juggler/subwaytooter/TabletColumnPagerAdapter.java index 951091d2..ed424839 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/TabletColumnPagerAdapter.java +++ b/app/src/main/java/jp/juggler/subwaytooter/TabletColumnPagerAdapter.java @@ -34,7 +34,7 @@ class TabletColumnPagerAdapter extends RecyclerView.Adapter %d ",old_position,position); + + vh.onPageDestroy(); + + vh.onPageCreate( column, position,column_count ); if( ! column.bFirstInitialized ){ column.startLoading(); diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/TootApiResult.java b/app/src/main/java/jp/juggler/subwaytooter/api/TootApiResult.java index 22f1e9db..cff389fe 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/TootApiResult.java +++ b/app/src/main/java/jp/juggler/subwaytooter/api/TootApiResult.java @@ -67,7 +67,7 @@ public class TootApiResult { while( m.find()){ String url = m.group(1); String rel = m.group(2); - log.d("Link %s,%s",rel,url); + // log.d("Link %s,%s",rel,url); if( "next".equals( rel )) link_older = url; if( "prev".equals( rel )) link_newer = url; } 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 94677694..c5f5f7bd 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 @@ -10,7 +10,9 @@ import org.json.JSONObject; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Calendar; import java.util.Date; +import java.util.GregorianCalendar; import java.util.HashSet; import java.util.Locale; import java.util.TimeZone; @@ -138,7 +140,7 @@ public class TootStatus extends TootId { status.media_attachments = TootAttachment.parseList( log, src.optJSONArray( "media_attachments" ) ); status.mentions = TootMention.parseList( log, src.optJSONArray( "mentions" ) ); status.tags = TootTag.parseList( log, src.optJSONArray( "tags" ) ); - status.application = TootApplication.parse( log,src.optJSONObject( "application" )); // null + status.application = TootApplication.parse( log, src.optJSONObject( "application" ) ); // null status.time_created_at = parseTime( log, status.created_at ); status.decoded_content = HTMLDecoder.decodeHTML( account, status.content ); @@ -172,22 +174,32 @@ public class TootStatus extends TootId { return result; } - private static final Pattern reTime = Pattern.compile("\\A(\\d+\\D+\\d+\\D+\\d+\\D+\\d+\\D+\\d+\\D+\\d+\\D+\\d+)"); - private static final SimpleDateFormat date_format_utc = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.getDefault() ); + private static final Pattern reTime = Pattern.compile( "\\A(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)" ); + + private static final TimeZone tz_utc = TimeZone.getTimeZone( "UTC" ); static long parseTime( LogCategory log, String strTime ){ if( ! TextUtils.isEmpty( strTime ) ){ try{ Matcher m = reTime.matcher( strTime ); - if(!m.find() ){ - log.d("!!invalid time format: %s",strTime); + if( ! m.find() ){ + log.d( "!!invalid time format: %s", strTime ); }else{ - date_format_utc.setTimeZone( TimeZone.getTimeZone( "GMT" ) ); - return date_format_utc.parse( m.group( 1 ) ).getTime(); + GregorianCalendar g = new GregorianCalendar( tz_utc ); + 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, Utils.parse_int( m.group( 7 ), 0 ) ); + return g.getTimeInMillis(); } - }catch( Throwable ex ){// ParseException, ArrayIndexOutOfBoundsException + }catch( Throwable ex ){// ParseException, ArrayIndexOutOfBoundsException ex.printStackTrace(); - log.e( ex, "TootStatus.parseTime failed. src=%s",strTime ); + log.e( ex, "TootStatus.parseTime failed. src=%s", strTime ); } } return 0L; @@ -230,7 +242,6 @@ public class TootStatus extends TootId { } } - public boolean checkMuted( HashSet< String > muted_app, HashSet< String > muted_word ){ // app mute @@ -244,7 +255,7 @@ public class TootStatus extends TootId { } // word mute - for( String word: muted_word ){ + for( String word : muted_word ){ if( decoded_content != null && decoded_content.toString().contains( word ) ){ return true; } @@ -255,13 +266,12 @@ public class TootStatus extends TootId { // reblog //noinspection RedundantIfStatement - if( reblog != null && reblog.checkMuted( muted_app,muted_word ) ){ + if( reblog != null && reblog.checkMuted( muted_app, muted_word ) ){ return true; } return false; - + } - } 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 2f34a66f..39a5a752 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/Utils.java +++ b/app/src/main/java/jp/juggler/subwaytooter/util/Utils.java @@ -886,6 +886,10 @@ public class Utils { return resources.getString( string_id, args ) + String.format( " :%s %s", ex.getClass().getSimpleName(), ex.getMessage() ); } + public static boolean isMainThread( ){ + return Looper.getMainLooper().getThread() == Thread.currentThread(); + } + public static void runOnMainThread( @NonNull Runnable proc ){ if( Looper.getMainLooper().getThread() == Thread.currentThread() ){ proc.run(); diff --git a/app/src/main/res/layout/dlg_context_menu.xml b/app/src/main/res/layout/dlg_context_menu.xml index 1f8d94b8..dfec359b 100644 --- a/app/src/main/res/layout/dlg_context_menu.xml +++ b/app/src/main/res/layout/dlg_context_menu.xml @@ -133,7 +133,20 @@ android:text="@string/boost_from_another_account" android:textAllCaps="false" /> - +