From 943498928b42036feaf4695702c32a507ef7d041 Mon Sep 17 00:00:00 2001 From: tateisu Date: Fri, 28 Apr 2017 14:19:49 +0900 Subject: [PATCH] =?UTF-8?q?=E3=82=AB=E3=83=A9=E3=83=A0=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=81=AB=E3=83=96=E3=83=BC=E3=82=B9=E3=83=88=E9=9D=9E=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=80=81=E8=BF=94=E4=BF=A1=E9=9D=9E=E8=A1=A8=E7=A4=BA?= =?UTF-8?q?=E3=80=81=E6=AD=A3=E8=A6=8F=E8=A1=A8=E7=8F=BE=E3=83=95=E3=82=A3?= =?UTF-8?q?=E3=83=AB=E3=82=BF=E3=80=81=E9=80=9A=E7=9F=A5=E3=81=AE=E5=89=8A?= =?UTF-8?q?=E9=99=A4=E3=82=92=E8=BF=BD=E5=8A=A0=E3=80=82=E7=99=BA=E8=A8=80?= =?UTF-8?q?=E3=81=AE=E5=85=AC=E9=96=8B=E7=AF=84=E5=9B=B2=E3=81=8C=E9=9D=9E?= =?UTF-8?q?=E5=85=AC=E9=96=8B=E4=BB=A5=E4=B8=8B=E3=81=A0=E3=81=A3=E3=81=9F?= =?UTF-8?q?=E5=A0=B4=E5=90=88=E3=81=AB=E3=83=96=E3=83=BC=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=AE=E8=A1=A8=E7=A4=BA=E3=82=92?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 4 +- app/src/main/AndroidManifest.xml | 2 +- .../java/jp/juggler/subwaytooter/ActMain.java | 80 +++++- .../jp/juggler/subwaytooter/AlarmService.java | 25 +- .../java/jp/juggler/subwaytooter/Column.java | 251 ++++++++++-------- .../subwaytooter/ColumnViewHolder.java | 209 +++++++++++---- .../subwaytooter/api/TootApiClient.java | 23 +- app/src/main/res/layout/page_column.xml | 131 ++++++--- app/src/main/res/values-ja/strings.xml | 8 +- app/src/main/res/values/attrs.xml | 3 + app/src/main/res/values/colors.xml | 2 + app/src/main/res/values/strings.xml | 6 + app/src/main/res/values/styles.xml | 4 + dumpFont.pl | 47 ++++ 14 files changed, 584 insertions(+), 211 deletions(-) create mode 100644 dumpFont.pl diff --git a/app/build.gradle b/app/build.gradle index b335c213..e50355cd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "jp.juggler.subwaytooter" minSdkVersion 21 targetSdkVersion 25 - versionCode 16 - versionName "0.1.6" + versionCode 17 + versionName "0.1.7" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index eebaf1ed..a76de8f5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -28,7 +28,7 @@ android:label="@string/app_name" android:launchMode="singleTask" - android:windowSoftInputMode="adjustPan|stateAlwaysHidden" + android:windowSoftInputMode="adjustResize|stateAlwaysHidden" > diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMain.java b/app/src/main/java/jp/juggler/subwaytooter/ActMain.java index 63269f03..93b006b4 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActMain.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ActMain.java @@ -8,10 +8,10 @@ import android.content.SharedPreferences; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; +import android.os.Handler; import android.support.annotation.NonNull; import android.support.customtabs.CustomTabsIntent; import android.support.design.widget.FloatingActionButton; -import android.support.v4.content.ContextCompat; import android.support.v4.os.AsyncTaskCompat; import android.support.v4.view.ViewPager; import android.support.v7.app.AlertDialog; @@ -72,6 +72,7 @@ public class ActMain extends AppCompatActivity float density; SharedPreferences pref; + Handler handler; @Override protected void onCreate( Bundle savedInstanceState ){ @@ -82,6 +83,7 @@ public class ActMain extends AppCompatActivity requestWindowFeature( Window.FEATURE_NO_TITLE ); pref = Pref.pref( this ); + handler = new Handler( ); initUI(); @@ -470,7 +472,7 @@ public class ActMain extends AppCompatActivity if( account != null ){ Column column = addColumn( account, Column.TYPE_NOTIFICATIONS ); if( ! column.bInitialLoading ){ - column.reload(); + column.startLoading(); } } }catch( Throwable ex ){ @@ -552,7 +554,7 @@ public class ActMain extends AppCompatActivity this.host = client.instance; - TootApiResult result = client.authorize2( uri.toString(), code ); + TootApiResult result = client.authorize2( code ); if( result != null && result.object != null ){ // taは使い捨てなので、生成に使うLinkClickContextはダミーで問題ない LinkClickContext lcc = new LinkClickContext() { @@ -592,7 +594,7 @@ public class ActMain extends AppCompatActivity // 自動でリロードする for( Column c : pager_adapter.column_list ){ if( c.access_info.acct.equals( sa.acct ) ){ - c.reload(); + c.startLoading(); } } } @@ -714,6 +716,7 @@ public class ActMain extends AppCompatActivity addColumn( access_info, Column.TYPE_HASHTAG, tag ); } + ////////////////////////////////////////////////////////////// interface GetAccountCallback { @@ -1944,4 +1947,73 @@ public class ActMain extends AppCompatActivity startActivityForResult( new Intent( this, ActAbout.class ), REQUEST_APP_ABOUT ); } + + public void deleteNotification( boolean bConfirmed, final SavedAccount target_account ){ + if( ! bConfirmed ){ + new AlertDialog.Builder( this ) + .setMessage( R.string.confirm_delete_notification ) + .setNegativeButton( R.string.cancel, null ) + .setPositiveButton( R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick( DialogInterface dialog, int which ){ + deleteNotification( true, target_account ); + } + } ) + .show(); + return; + } + new AsyncTask< Void, Void, TootApiResult >() { + + @Override + protected TootApiResult doInBackground( Void... params ){ + TootApiClient client = new TootApiClient( ActMain.this, new TootApiClient.Callback() { + @Override + public boolean isApiCancelled(){ + return isCancelled(); + } + + @Override + public void publishApiProgress( String s ){ + + } + } ); + client.setAccount( target_account ); + + Request.Builder request_builder = new Request.Builder().post( + RequestBody.create( + TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED + , "" // 空データ + ) ); + TootApiResult result = client.request( "/api/v1/notifications/clear" , request_builder ); + return result; + } + + @Override + protected void onCancelled( TootApiResult result ){ + onPostExecute( null ); + } + + @Override + protected void onPostExecute( TootApiResult result ){ + if( result == null ){ + //cancelled. + }else if( result.object != null ){ + // ok. empty object will be returned. + for( Column column : pager_adapter.column_list ){ + if( column.type == Column.TYPE_NOTIFICATIONS + && column.access_info.acct.equals( target_account.acct ) + ){ + column.removeNotifications(); + } + } + Utils.showToast( ActMain.this, false, R.string.delete_succeeded ); + }else{ + Utils.showToast( ActMain.this, false, result.error ); + } + } + + }.execute(); + } + + } diff --git a/app/src/main/java/jp/juggler/subwaytooter/AlarmService.java b/app/src/main/java/jp/juggler/subwaytooter/AlarmService.java index 155473a0..8f1ce61d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/AlarmService.java +++ b/app/src/main/java/jp/juggler/subwaytooter/AlarmService.java @@ -47,6 +47,7 @@ public class AlarmService extends IntentService { static final String KEY_TIME = "<>time"; private static final String ACTION_DATA_INJECTED = "data_injected"; private static final String EXTRA_DB_ID = "db_id"; + private static final String ACTION_DATA_DELETED = "data_deleted"; public AlarmService(){ // name: Used to name the worker thread, important only for debugging. @@ -94,7 +95,9 @@ public class AlarmService extends IntentService { String action = intent.getAction(); log.d("onHandleIntent action=%s",action); - if( ACTION_DATA_INJECTED.equals( action ) ){ + if( ACTION_DATA_DELETED.equals( action ) ){ + deleteCacheData(intent.getLongExtra( EXTRA_DB_ID ,-1L)); + }else if( ACTION_DATA_INJECTED.equals( action ) ){ processInjectedData(); }else if( AlarmReceiver.ACTION_FROM_RECEIVER.equals( action ) ){ WakefulBroadcastReceiver.completeWakefulIntent( intent ); @@ -181,7 +184,6 @@ public class AlarmService extends IntentService { } - private static class Data { SavedAccount access_info; TootNotification notification; @@ -502,4 +504,23 @@ public class AlarmService extends IntentService { nr.save(); } } + + public static void dataRemoved( Context context, long db_id ){ + Intent intent = new Intent( context, AlarmService.class ); + intent.putExtra( EXTRA_DB_ID,db_id ); + intent.setAction( ACTION_DATA_DELETED ); + context.startService( intent ); + } + private void deleteCacheData( long db_id ){ + + SavedAccount account = SavedAccount.loadAccount( log,db_id ); + if( account == null ) return; + + NotificationTracking nr = NotificationTracking.load( db_id ); + + nr.last_data = new JSONArray().toString(); + + nr.save(); + } + } diff --git a/app/src/main/java/jp/juggler/subwaytooter/Column.java b/app/src/main/java/jp/juggler/subwaytooter/Column.java index 0d2b20b4..71a30141 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/Column.java +++ b/app/src/main/java/jp/juggler/subwaytooter/Column.java @@ -35,7 +35,7 @@ import jp.juggler.subwaytooter.util.Utils; class Column { private static final LogCategory log = new LogCategory( "Column" ); - + private static Object getParamAt( Object[] params, int idx ){ if( params == null || idx >= params.length ){ throw new IndexOutOfBoundsException( "getParamAt idx=" + idx ); @@ -72,8 +72,12 @@ class Column { private static final String KEY_ACCOUNT_ROW_ID = "account_id"; private static final String KEY_TYPE = "type"; - private static final String KEY_WITH_ATTACHMENT = "with_attachment"; static final String KEY_DONT_CLOSE = "dont_close"; + private static final String KEY_WITH_ATTACHMENT = "with_attachment"; + private static final String KEY_DONT_SHOW_BOOST = "dont_show_boost"; + private static final String KEY_DONT_SHOW_REPLY = "dont_show_reply"; + private static final String KEY_REGEX_TEXT = "regex_text"; + private static final String KEY_PROFILE_ID = "profile_id"; @@ -105,8 +109,13 @@ class Column { final int type; - boolean with_attachment; boolean dont_close; + + boolean with_attachment; + boolean dont_show_boost; + boolean dont_show_reply; + String regex_text; + private long profile_id; volatile TootAccount who_account; @@ -156,8 +165,11 @@ class Column { void encodeJSON( JSONObject item, int old_index ) throws JSONException{ item.put( KEY_ACCOUNT_ROW_ID, access_info.db_id ); item.put( KEY_TYPE, type ); - item.put( KEY_WITH_ATTACHMENT, with_attachment ); item.put( KEY_DONT_CLOSE, dont_close ); + item.put( KEY_WITH_ATTACHMENT, with_attachment ); + item.put( KEY_DONT_SHOW_BOOST, dont_show_boost ); + item.put( KEY_DONT_SHOW_REPLY, dont_show_reply ); + item.put( KEY_REGEX_TEXT, regex_text ); switch( type ){ case TYPE_CONVERSATION: @@ -189,8 +201,11 @@ class Column { if( ac == null ) throw new RuntimeException( "missing account" ); this.access_info = ac; this.type = src.optInt( KEY_TYPE ); - this.with_attachment = src.optBoolean( KEY_WITH_ATTACHMENT ); this.dont_close = src.optBoolean( KEY_DONT_CLOSE ); + this.with_attachment = src.optBoolean( KEY_WITH_ATTACHMENT ); + this.dont_show_boost = src.optBoolean( KEY_DONT_SHOW_BOOST ); + this.dont_show_reply = src.optBoolean( KEY_DONT_SHOW_REPLY ); + this.regex_text = Utils.optStringX(src, KEY_REGEX_TEXT); switch( type ){ @@ -315,6 +330,7 @@ class Column { } } + interface StatusEntryCallback { void onIterate( TootStatus status ); } @@ -502,20 +518,62 @@ class Column { final ArrayList< Object > list_data = new ArrayList<>(); - void reload(){ - list_data.clear(); - startLoading(); - } - private static boolean hasMedia( TootStatus status ){ if( status == null ) return false; TootAttachment.List list = status.media_attachments; return ! ( list == null || list.isEmpty() ); } - private void startLoading(){ + private boolean isFiltered(){ + return ( with_attachment + || dont_show_boost + || dont_show_reply + || !TextUtils.isEmpty( regex_text ) + ); + } + + private void addWithFilter( ArrayList< Object > dst, TootStatus.List src ){ + if( ! isFiltered() ){ + dst.addAll( src ); + return; + } + Pattern pattern = null; + if( ! TextUtils.isEmpty( regex_text ) ){ + try{ + pattern = Pattern.compile( regex_text ); + }catch( Throwable ex ){ + ex.printStackTrace(); + } + } + for( TootStatus status : src ){ + if( with_attachment ){ + if( ! hasMedia( status ) && ! hasMedia( status.reblog ) ) continue; + } + if( dont_show_boost ){ + if( status.reblog != null ) continue; + } + if( dont_show_reply ){ + if( status.in_reply_to_id != null + || ( status.reblog != null && status.reblog.in_reply_to_id != null ) + ) continue; + } + if( pattern != null ){ + if( status.reblog != null ){ + if( pattern.matcher( status.reblog.decoded_content.toString() ).find() ) + continue; + }else{ + if( pattern.matcher( status.decoded_content.toString() ).find() ) continue; + + } + } + dst.add( status ); + } + } + + void startLoading(){ cancelLastTask(); + list_data.clear(); mRefreshLoadingError = null; bRefreshLoading = false; mInitialLoadingError = null; @@ -543,42 +601,27 @@ class Column { // TootStatus.List src = TootStatus.parseList( log, access_info, result.array ); list_tmp = new ArrayList<>( src.size() ); - if( ! with_attachment ){ - list_tmp.addAll( src ); - }else{ - for( TootStatus status : src ){ - if( hasMedia( status ) || hasMedia( status.reblog ) ) - list_tmp.add( status ); - } - } + addWithFilter(list_tmp,src); // - if( max_id != null && with_attachment ){ - char delimiter = ( - 1 != path_base.indexOf( '?' ) ? '&' : '?' ); - for( ; ; ){ - if( src.isEmpty() ){ - // 直前のリクエストが空のリストを返したら諦める - break; - } - if( SystemClock.elapsedRealtime() - time_start > LOOP_TIMEOUT ){ - // タイムアウト - break; - } - String path = path_base + delimiter + "max_id=" + max_id; - TootApiResult result2 = client.request( path ); - if( result2 == null || result2.array == null ) break; - if( ! saveRangeEnd( result2 ) ) break; - - src = TootStatus.parseList( log, access_info, result2.array ); - - if( ! with_attachment ){ - list_tmp.addAll( src ); - }else{ - for( TootStatus status : src ){ - if( hasMedia( status ) || hasMedia( status.reblog ) ) - list_tmp.add( status ); - } - } + char delimiter = ( - 1 != path_base.indexOf( '?' ) ? '&' : '?' ); + while( isFiltered() && max_id != null && list_tmp.size() < 50 ){ + if( client.isCancelled() ) break; + if( src.isEmpty() ){ + // 直前のリクエストが空のリストを返したら諦める + break; } + if( SystemClock.elapsedRealtime() - time_start > LOOP_TIMEOUT ){ + // タイムアウト + break; + } + String path = path_base + delimiter + "max_id=" + max_id; + TootApiResult result2 = client.request( path ); + if( result2 == null || result2.array == null ) break; + if( ! saveRangeEnd( result2 ) ) break; + + src = TootStatus.parseList( log, access_info, result2.array ); + + addWithFilter(list_tmp,src); } } return result; @@ -1022,57 +1065,46 @@ class Column { TootStatus.List src = TootStatus.parseList( log, access_info, result.array ); list_tmp = new ArrayList<>(); - if( ! with_attachment ){ - list_tmp.addAll( src ); - }else{ - for( TootStatus status : src ){ - if( hasMedia( status ) || hasMedia( status.reblog ) ) - list_tmp.add( status ); - } - } + addWithFilter(list_tmp,src); if( bBottom ){ - if( with_attachment ){ - for( ; ; ){ - // max_id だけを指定した場合、必ずlimit個のデータが帰ってくるとは限らない - // 直前のデータが0個なら終了とみなすしかなさそう - if( src.isEmpty() ){ - log.d( "refresh-status-bottom: previous size == 0." ); - break; - } - - if( SystemClock.elapsedRealtime() - time_start > LOOP_TIMEOUT ){ - // タイムアウト - log.d( "refresh-status-bottom: loop timeout." ); - break; - } - - String path = path_base + delimiter + "max_id=" + max_id; - TootApiResult result2 = client.request( path ); - if( result2 == null || result2.array == null ){ - log.d( "refresh-status-bottom: error or cancelled." ); - break; - } - - src = TootStatus.parseList( log, access_info, result2.array ); - - if( ! with_attachment ){ - list_tmp.addAll( src ); - }else{ - for( TootStatus status : src ){ - if( hasMedia( status ) || hasMedia( status.reblog ) ) - list_tmp.add( status ); - } - } - - if( ! saveRangeEnd( result2 ) ){ - log.d( "refresh-status-bottom: saveRangeEnd failed." ); - break; - } + while( list_tmp.size() < 50 ){ + if( client.isCancelled() ) break; + + // max_id だけを指定した場合、必ずlimit個のデータが帰ってくるとは限らない + // 直前のデータが0個なら終了とみなすしかなさそう + if( src.isEmpty() ){ + log.d( "refresh-status-bottom: previous size == 0." ); + break; + } + + if( SystemClock.elapsedRealtime() - time_start > LOOP_TIMEOUT ){ + // タイムアウト + log.d( "refresh-status-bottom: loop timeout." ); + break; + } + + String path = path_base + delimiter + "max_id=" + max_id; + TootApiResult result2 = client.request( path ); + if( result2 == null || result2.array == null ){ + log.d( "refresh-status-bottom: error or cancelled." ); + break; + } + + src = TootStatus.parseList( log, access_info, result2.array ); + + addWithFilter(list_tmp,src); + + if( ! saveRangeEnd( result2 ) ){ + log.d( "refresh-status-bottom: saveRangeEnd failed." ); + break; } } }else{ - for( ; ; ){ + // 頭の方を読む時は隙間を減らすため、フィルタの有無に関係なく繰り返しを行う + while( list_tmp.size() < 50 ){ + if( client.isCancelled() ) break; + // max_id だけを指定した場合、必ずlimit個のデータが帰ってくるとは限らない // 直前のデータが0個なら終了とみなすしかなさそう if( src.isEmpty() ){ @@ -1102,14 +1134,7 @@ class Column { } src = TootStatus.parseList( log, access_info, result2.array ); - if( ! with_attachment ){ - list_tmp.addAll( src ); - }else{ - for( TootStatus status : src ){ - if( hasMedia( status ) || hasMedia( status.reblog ) ) - list_tmp.add( status ); - } - } + addWithFilter( list_tmp,src ); } } } @@ -1409,6 +1434,8 @@ class Column { TootApiResult result = null; for( ; ; ){ + if( client.isCancelled() ) break; + if( result != null && SystemClock.elapsedRealtime() - time_start > LOOP_TIMEOUT ){ // タイムアウト // 隙間が残る @@ -1444,14 +1471,7 @@ class Column { // 隙間の最新のステータスIDは取得データ末尾のステータスIDである max_id = Long.toString( src.get( src.size() - 1 ).id ); - if( ! with_attachment ){ - list_tmp.addAll( src ); - }else{ - for( TootStatus status : src ){ - if( hasMedia( status ) || hasMedia( status.reblog ) ) - list_tmp.add( status ); - } - } + addWithFilter( list_tmp,src ); } return result; } @@ -1594,4 +1614,21 @@ class Column { AsyncTaskCompat.executeParallel( task ); return null; } + + void removeNotifications(){ + cancelLastTask(); + + list_data.clear(); + mRefreshLoadingError = null; + bRefreshLoading = false; + mInitialLoadingError = null; + bInitialLoading = false; + max_id = null; + since_id = null; + + fireVisualCallback(); + + AlarmService.dataRemoved(activity,access_info.db_id); + } + } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java index ddab7dfc..34c84733 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java @@ -3,7 +3,9 @@ package jp.juggler.subwaytooter; import android.content.Context; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; +import android.text.Editable; import android.text.TextUtils; +import android.text.TextWatcher; import android.text.method.LinkMovementMethod; import android.view.KeyEvent; import android.view.View; @@ -27,6 +29,8 @@ import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayoutDirec import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import jp.juggler.subwaytooter.api.entity.TootAccount; import jp.juggler.subwaytooter.api.entity.TootAttachment; @@ -53,7 +57,7 @@ class ColumnViewHolder implements View.OnClickListener, Column.VisualCallback, S this.column = column; } - public boolean isPageDestroyed(){ + private boolean isPageDestroyed(){ return is_destroyed.get() || activity.isFinishing(); } @@ -74,6 +78,8 @@ class ColumnViewHolder implements View.OnClickListener, Column.VisualCallback, S private EditText etSearch; private CheckBox cbResolve; private View llColumnSetting; + private EditText etRegexFilter; + private TextView tvRegexFilterError; void onPageCreate( View root, int page_idx, int page_count ){ log.d( "onPageCreate:%s", column.getColumnName( true ) ); @@ -104,10 +110,10 @@ class ColumnViewHolder implements View.OnClickListener, Column.VisualCallback, S listView.setFastScrollEnabled( ! Pref.pref( activity ).getBoolean( Pref.KEY_DISABLE_FAST_SCROLLER, false ) ); - boolean bAllowMediaOnly; + boolean bAllowFilter; switch( column.type ){ default: - bAllowMediaOnly = true; + bAllowFilter = true; break; case Column.TYPE_SEARCH: case Column.TYPE_CONVERSATION: @@ -115,23 +121,85 @@ class ColumnViewHolder implements View.OnClickListener, Column.VisualCallback, S case Column.TYPE_BLOCKS: case Column.TYPE_MUTES: case Column.TYPE_NOTIFICATIONS: - bAllowMediaOnly = false; + bAllowFilter = false; break; } + + boolean bAllowFilterBoost; + switch( column.type ){ + default: + bAllowFilterBoost = false; + break; + case Column.TYPE_HOME: + case Column.TYPE_PROFILE: + bAllowFilterBoost = true; + break; + } + View btnColumnSetting = root.findViewById( R.id.btnColumnSetting ); llColumnSetting = root.findViewById( R.id.llColumnSetting ); btnColumnSetting.setVisibility( View.VISIBLE ); btnColumnSetting.setOnClickListener( this ); llColumnSetting.setVisibility( View.GONE ); - CheckBox cbWithAttachment = (CheckBox) root.findViewById( R.id.cbWithAttachment ); - cbWithAttachment.setChecked( column.with_attachment ); - cbWithAttachment.setOnCheckedChangeListener( this ); - cbWithAttachment.setEnabled( bAllowMediaOnly ); + CheckBox cb; + cb = (CheckBox) root.findViewById( R.id.cbDontCloseColumn ); + cb.setChecked( column.dont_close ); + cb.setOnCheckedChangeListener( this ); - CheckBox cbDontCloseColumn = (CheckBox) root.findViewById( R.id.cbDontCloseColumn ); - cbDontCloseColumn.setChecked( column.dont_close ); - cbDontCloseColumn.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 ); + + 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.type != Column.TYPE_NOTIFICATIONS ){ + button.setVisibility( View.GONE ); + }else{ + button.setVisibility( View.VISIBLE ); + button.setOnClickListener( this ); + + } if( column.type != Column.TYPE_SEARCH ){ llSearch.setVisibility( View.GONE ); @@ -168,19 +236,61 @@ class ColumnViewHolder implements View.OnClickListener, Column.VisualCallback, S onVisualColumn(); } + private final Runnable proc_start_filter = new Runnable() { + @Override public void run(){ + if( isPageDestroyed() ) return; + if( isRegexValid() ){ + column.regex_text = etRegexFilter.getText().toString(); + activity.saveColumnList(); + column.startLoading(); + } + } + }; + + private boolean isRegexValid(){ + String s = etRegexFilter.getText().toString(); + if( s.length() == 0 ){ + tvRegexFilterError.setText( "" ); + return true; + } + try{ + @SuppressWarnings("unused") Matcher m = Pattern.compile( s ).matcher( "" ); + tvRegexFilterError.setText( "" ); + return true; + }catch( Throwable ex ){ + String message = ex.getMessage(); + if( TextUtils.isEmpty( message ) ) + message = Utils.formatError( ex, activity.getResources(), R.string.regex_error ); + tvRegexFilterError.setText( message ); + return false; + } + } + @Override public void onCheckedChanged( CompoundButton view, boolean isChecked ){ switch( view.getId() ){ - case R.id.cbWithAttachment: - column.with_attachment = isChecked; - activity.saveColumnList(); - column.reload(); - break; - case R.id.cbDontCloseColumn: column.dont_close = isChecked; activity.saveColumnList(); break; + + case R.id.cbWithAttachment: + column.with_attachment = isChecked; + activity.saveColumnList(); + column.startLoading(); + break; + + case R.id.cbDontShowBoost: + column.dont_show_boost = isChecked; + activity.saveColumnList(); + column.startLoading(); + break; + + case R.id.cbDontShowReply: + column.dont_show_reply = isChecked; + activity.saveColumnList(); + column.startLoading(); + break; } } @@ -198,14 +308,14 @@ class ColumnViewHolder implements View.OnClickListener, Column.VisualCallback, S etSearch.setText( column.search_query ); cbResolve.setChecked( column.search_resolve ); } - column.reload(); + column.startLoading(); break; case R.id.btnSearch: hideKeyboard( etSearch ); column.search_query = etSearch.getText().toString().trim(); column.search_resolve = cbResolve.isChecked(); - column.reload(); + column.startLoading(); break; case R.id.llColumnHeader: @@ -215,6 +325,10 @@ class ColumnViewHolder implements View.OnClickListener, Column.VisualCallback, S case R.id.btnColumnSetting: llColumnSetting.setVisibility( llColumnSetting.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE ); break; + + case R.id.btnDeleteNotification: + activity.deleteNotification( false, column.access_info ); + break; } } @@ -350,6 +464,7 @@ class ColumnViewHolder implements View.OnClickListener, Column.VisualCallback, S final Button btnStatusCount; final View btnMore; final TextView tvNote; + TootAccount who; SavedAccount access_info; @@ -420,17 +535,17 @@ class ColumnViewHolder implements View.OnClickListener, Column.VisualCallback, S case R.id.btnFollowing: column.profile_tab = Column.TAB_FOLLOWING; - column.reload(); + column.startLoading(); break; case R.id.btnFollowers: column.profile_tab = Column.TAB_FOLLOWERS; - column.reload(); + column.startLoading(); break; case R.id.btnStatusCount: column.profile_tab = Column.TAB_STATUS; - column.reload(); + column.startLoading(); break; case R.id.btnMore: @@ -696,7 +811,7 @@ class ColumnViewHolder implements View.OnClickListener, Column.VisualCallback, S void showBoost( TootAccount who, long time, int icon_attr_id, CharSequence text ){ account_boost = who; llBoosted.setVisibility( View.VISIBLE ); - ivBoosted.setImageResource( Styler.getAttributeResourceId(activity,icon_attr_id) ); + ivBoosted.setImageResource( Styler.getAttributeResourceId( activity, icon_attr_id ) ); tvBoostedTime.setText( TootStatus.formatTime( time ) ); tvBoostedAcct.setText( access_info.getFullAcct( who ) ); tvBoosted.setText( text ); @@ -709,7 +824,7 @@ class ColumnViewHolder implements View.OnClickListener, Column.VisualCallback, S tvFollowerName.setText( who.display_name ); tvFollowerAcct.setText( access_info.getFullAcct( who ) ); - btnFollow.setImageResource( Styler.getAttributeResourceId( activity,R.attr.ic_account_add )); + btnFollow.setImageResource( Styler.getAttributeResourceId( activity, R.attr.ic_account_add ) ); } private void showStatus( ActMain activity, TootStatus status, SavedAccount account ){ @@ -763,44 +878,38 @@ class ColumnViewHolder implements View.OnClickListener, Column.VisualCallback, S btnShowMedia.setVisibility( ! is_shown ? View.VISIBLE : View.GONE ); } - Drawable d; - int color_normal = Styler.getAttributeColor( activity,R.attr.colorImageButton ); - int color_accent = Styler.getAttributeColor( activity,R.attr.colorImageButtonAccent ); + int color_normal = Styler.getAttributeColor( activity, R.attr.colorImageButton ); + int color_accent = Styler.getAttributeColor( activity, R.attr.colorImageButtonAccent ); - if( activity.isBusyBoost( account, status ) ){ - d = Styler.getAttributeDrawable( activity,R.attr.btn_refresh ).mutate(); - d.setColorFilter( color_normal, PorterDuff.Mode.SRC_ATOP ); - btnBoost.setCompoundDrawablesRelativeWithIntrinsicBounds( d, null, null, null ); - btnBoost.setText( "?" ); - btnBoost.setTextColor( color_normal ); - + if( TootStatus.VISIBILITY_DIRECT.equals( status.visibility ) ){ + setButton( btnBoost, false, color_accent, R.attr.ic_mail, "" ); + }else if( TootStatus.VISIBILITY_PRIVATE.equals( status.visibility ) ){ + setButton( btnBoost, false, color_accent, R.attr.ic_lock, "" ); + }else if( activity.isBusyBoost( account, status ) ){ + setButton( btnBoost, false, color_normal, R.attr.btn_refresh, "?" ); }else{ int color = ( status.reblogged ? color_accent : color_normal ); - d = Styler.getAttributeDrawable( activity,R.attr.btn_boost ).mutate(); - d.setColorFilter( color, PorterDuff.Mode.SRC_ATOP ); - btnBoost.setCompoundDrawablesRelativeWithIntrinsicBounds( d, null, null, null ); - btnBoost.setText( Long.toString( status.reblogs_count ) ); - btnBoost.setTextColor( color ); - + setButton( btnBoost, true, color, R.attr.btn_boost, Long.toString( status.reblogs_count ) ); } if( activity.isBusyFav( account, status ) ){ - d = Styler.getAttributeDrawable( activity,R.attr.btn_refresh ).mutate(); - d.setColorFilter( color_normal, PorterDuff.Mode.SRC_ATOP ); - btnFavourite.setCompoundDrawablesRelativeWithIntrinsicBounds( d, null, null, null ); - btnFavourite.setText( "?" ); - btnFavourite.setTextColor( color_normal ); + setButton( btnFavourite, false, color_normal, R.attr.btn_refresh, "?" ); }else{ int color = ( status.favourited ? color_accent : color_normal ); - d = Styler.getAttributeDrawable( activity,R.attr.btn_favourite ).mutate(); - d.setColorFilter( color, PorterDuff.Mode.SRC_ATOP ); - btnFavourite.setCompoundDrawablesRelativeWithIntrinsicBounds( d, null, null, null ); - btnFavourite.setText( Long.toString( status.favourites_count ) ); - btnFavourite.setTextColor( color ); + setButton( btnFavourite, true, color, R.attr.btn_favourite, Long.toString( status.favourites_count ) ); } } + private void setButton( Button b, boolean enabled, int color, int icon_attr, String text ){ + Drawable d = Styler.getAttributeDrawable( activity, icon_attr ).mutate(); + d.setColorFilter( color, PorterDuff.Mode.SRC_ATOP ); + b.setCompoundDrawablesRelativeWithIntrinsicBounds( d, null, null, null ); + b.setText( text ); + b.setTextColor( color ); + b.setEnabled( enabled ); + } + private void showContent( boolean shown ){ btnContentWarning.setText( shown ? R.string.hide : R.string.show ); llContents.setVisibility( shown ? View.VISIBLE : View.GONE ); diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.java b/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.java index dbd6f752..d6dd69f0 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.java +++ b/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.java @@ -1,7 +1,6 @@ package jp.juggler.subwaytooter.api; import android.content.Context; -import android.content.Intent; import android.net.Uri; import android.support.annotation.NonNull; import android.text.TextUtils; @@ -25,7 +24,7 @@ import okhttp3.Response; public class TootApiClient { private static final LogCategory log = new LogCategory( "TootApiClient" ); - static final OkHttpClient ok_http_client = App1.ok_http_client; + private static final OkHttpClient ok_http_client = App1.ok_http_client; public interface Callback { boolean isApiCancelled(); @@ -57,6 +56,10 @@ public class TootApiClient { this.account = account; } + public boolean isCancelled(){ + return callback.isApiCancelled(); + } + public static final MediaType MEDIA_TYPE_FORM_URL_ENCODED = MediaType.parse( "application/x-www-form-urlencoded" ); public TootApiResult request( String path ){ @@ -66,17 +69,17 @@ public class TootApiClient { public TootApiResult request( String path, Request.Builder request_builder ){ log.d( "request: %s", path ); TootApiResult result = request_sub( path, request_builder ); - if( result.error != null ){ + if( result != null && result.error != null ){ log.d( "error: %s", result.error ); } return result; } - static final String KEY_AUTH_VERSION = "SubwayTooterAuthVersion"; - static final int AUTH_VERSION = 1; - static final String REDIRECT_URL = "subwaytooter://oauth"; + private static final String KEY_AUTH_VERSION = "SubwayTooterAuthVersion"; + private static final int AUTH_VERSION = 1; + private static final String REDIRECT_URL = "subwaytooter://oauth"; - public TootApiResult request_sub( String path, Request.Builder request_builder ){ + private TootApiResult request_sub( String path, Request.Builder request_builder ){ if( account == null ){ return new TootApiResult( "account is null" ); @@ -217,7 +220,7 @@ public class TootApiClient { + "&grant_type=authorization_code" // + "&username=" + Uri.encode( user_mail ) // + "&password=" + Uri.encode( password ) - +"&approval_prompt=force" + + "&approval_prompt=force" // +"&access_type=offline" ; // APIリクエストは失敗?する @@ -227,7 +230,7 @@ public class TootApiClient { } - public TootApiResult authorize2( String url, String code ){ + public TootApiResult authorize2( String code ){ JSONObject client_info = ClientInfo.load( instance ); if( client_info != null && client_info.optInt( KEY_AUTH_VERSION, 0 ) < AUTH_VERSION ){ @@ -246,7 +249,7 @@ public class TootApiClient { "grant_type=authorization_code" + "&code=" + Uri.encode( code ) + "&client_id=" + Uri.encode( Utils.optStringX( client_info, "client_id" ) ) - + "&redirect_uri=" + Uri.encode( REDIRECT_URL ) + + "&redirect_uri=" + Uri.encode( REDIRECT_URL ) + "&client_secret=" + Uri.encode( Utils.optStringX( client_info, "client_secret" ) ) + "&scope=read write follow" + "&scopes=read write follow"; diff --git a/app/src/main/res/layout/page_column.xml b/app/src/main/res/layout/page_column.xml index ea7c6054..2f53f939 100644 --- a/app/src/main/res/layout/page_column.xml +++ b/app/src/main/res/layout/page_column.xml @@ -1,22 +1,22 @@ + + - + + + + + + + + + + + + + + + + +