カラム設定にブースト非表示、返信非表示、正規表現フィルタ、通知の削除を追加。発言の公開範囲が非公開以下だった場合にブーストボタンの表示を変更

This commit is contained in:
tateisu 2017-04-28 14:19:49 +09:00
parent 6689da0bb3
commit 943498928b
14 changed files with 584 additions and 211 deletions

View File

@ -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"
}

View File

@ -28,7 +28,7 @@
android:label="@string/app_name"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustPan|stateAlwaysHidden"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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 );

View File

@ -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";

View File

@ -1,22 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<LinearLayout
android:id="@+id/llColumnHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:id="@+id/llColumnHeader"
android:background="@drawable/btn_bg_ddd"
android:orientation="vertical"
android:paddingBottom="3dp"
android:paddingEnd="12dp"
android:paddingStart="12dp"
android:paddingTop="3dp"
>
<LinearLayout
@ -52,8 +52,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:orientation="horizontal"
>
<TextView
@ -102,64 +102,127 @@
</LinearLayout>
<LinearLayout
android:id="@+id/llColumnSetting"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:background="?attr/colorColumnSettingBackground"
android:id="@+id/llColumnSetting"
android:orientation="vertical"
android:paddingBottom="3dp"
android:paddingEnd="12dp"
android:paddingStart="12dp"
android:paddingTop="3dp"
>
<CheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/with_attachment"
android:id="@+id/cbWithAttachment"
/>
<CheckBox
android:id="@+id/cbDontCloseColumn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dont_close_column"
android:id="@+id/cbDontCloseColumn"
/>
<CheckBox
android:id="@+id/cbWithAttachment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/with_attachment"
/>
<CheckBox
android:id="@+id/cbDontShowBoost"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dont_show_boost"
/>
<CheckBox
android:id="@+id/cbDontShowReply"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dont_show_reply"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:id="@+id/llRegexFilter"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:labelFor="@+id/etRegexFilter"
android:text="@string/regex_filter"
android:textColor="?attr/colorColumnHeaderPageNumber"
/>
<TextView
android:id="@+id/tvRegexFilterError"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_weight="1"
android:textColor="?attr/colorRegexFilterError"
/>
</LinearLayout>
<EditText
android:id="@+id/etRegexFilter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:scrollHorizontally="true"
android:inputType="text"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/notification_delete"
android:id="@+id/btnDeleteNotification"
android:textAllCaps="false"
/>
</LinearLayout>
<RelativeLayout
android:id="@+id/llSearch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingTop="3dp"
android:paddingBottom="3dp"
android:background="?attr/colorSearchFormBackground"
android:id="@+id/llSearch"
android:paddingBottom="3dp"
android:paddingEnd="12dp"
android:paddingStart="12dp"
android:paddingTop="3dp"
>
<ImageButton
android:id="@+id/btnSearch"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/btn_bg_transparent"
android:src="?attr/ic_search"
android:contentDescription="@string/search"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:id="@+id/btnSearch"
android:background="@drawable/btn_bg_transparent"
android:contentDescription="@string/search"
android:src="?attr/ic_search"
/>
<EditText
android:id="@+id/etSearch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toStartOf="@id/btnSearch"
android:inputType="text"
android:id="@+id/etSearch"
android:imeOptions="actionSearch"
android:inputType="text"
tools:ignore="LabelFor"/>
<CheckBox
android:id="@+id/cbResolve"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/etSearch"
android:text="@string/resolve_non_local_account"
android:id="@+id/cbResolve"
/>
</RelativeLayout>
@ -186,11 +249,11 @@
android:clipToPadding="false"
android:fadeScrollbars="false"
android:fastScrollEnabled="true"
android:paddingBottom="64dp"
android:paddingEnd="12dp"
android:paddingStart="12dp"
android:scrollbarStyle="outsideOverlay"
android:fastScrollEnabled="true"
/>
</com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayout>

View File

@ -170,8 +170,14 @@
<string name="send_header_content_warning">Content-Warning</string>
<string name="send_header_url">Status-URL</string>
<string name="column_has_dont_close_option">保護されたカラムは閉じられません</string>
<string name="dont_close_column">保護(閉じることができない)</string>
<string name="dont_close_column">保護(カラムを閉じない)</string>
<string name="theme_dark">暗い</string>
<string name="theme_light">明るい</string>
<string name="ui_theme">UIテーマ(アプリ再起動が必要)</string>
<string name="dont_show_boost">ブーストを表示しない</string>
<string name="dont_show_reply">返信を表示しない</string>
<string name="regex_error">正規表現エラー</string>
<string name="regex_filter">正規表現フィルタ(上級者向け)</string>
<string name="confirm_delete_notification">ユーザへの通知データがタンスのサーバから削除されます。よろしいですか?</string>
<string name="notification_delete">通知の削除</string>
</resources>

View File

@ -64,6 +64,9 @@
<!-- 設定画面のdividerの色 #ddd -->
<attr name="colorSettingDivider" format="color"/>
<!-- カラム設定の正規表現フィルタのエラー表示 #f00 -->
<attr name="colorRegexFilterError" format="color"/>
<attr name="btn_attachment" format="reference" />
<attr name="btn_boost" format="reference" />

View File

@ -35,6 +35,7 @@
<color name="Light_colorShowMediaText">#fff</color>
<color name="Light_colorColumnListDeleteBackground">#FF0000</color>
<color name="Light_colorColumnListDeleteText">#fff</color>
<color name="Light_colorRegexFilterError">#f00</color>
<!-- Dark theme -->
@ -75,5 +76,6 @@
<color name="Dark_colorShowMediaText">#ccFFFFFF</color>
<color name="Dark_colorColumnListDeleteBackground">#FF0000</color>
<color name="Dark_colorColumnListDeleteText">#fff</color>
<color name="Dark_colorRegexFilterError">#f00</color>
</resources>

View File

@ -173,4 +173,10 @@
<string name="ui_theme">UI theme (app restart required)</string>
<string name="theme_light">light</string>
<string name="theme_dark">dark</string>
<string name="dont_show_boost">dont show boost</string>
<string name="dont_show_reply">dont show reply</string>
<string name="regex_filter">regex filter</string>
<string name="regex_error">regex error</string>
<string name="notification_delete">delete notification</string>
<string name="confirm_delete_notification">your notification data on instance sarver will be deleted. Are you sure?</string>
</resources>

View File

@ -32,6 +32,9 @@
<item name="colorSearchFormBackground">@color/Light_colorSearchFormBackground</item>
<item name="colorSettingDivider">@color/Light_colorSettingDivider</item>
<item name="colorRegexFilterError">@color/Light_colorRegexFilterError</item>
<item name="btn_attachment">@drawable/btn_attachment</item>
<item name="btn_boost">@drawable/btn_boost</item>
<item name="btn_close">@drawable/btn_close</item>
@ -105,6 +108,7 @@
<item name="colorColumnSettingBackground">@color/Dark_colorColumnSettingBackground</item>
<item name="colorSearchFormBackground">@color/Dark_colorSearchFormBackground</item>
<item name="colorSettingDivider">@color/Dark_colorSettingDivider</item>
<item name="colorRegexFilterError">@color/Dark_colorRegexFilterError</item>
<item name="btn_attachment">@drawable/btn_attachment_dark</item>
<item name="btn_boost">@drawable/btn_boost_dark</item>

47
dumpFont.pl Normal file
View File

@ -0,0 +1,47 @@
#!perl --
use strict;
use warnings;
use Font::FreeType;
my $face = Font::FreeType->new->face('emojione_android.ttf');
my $f =0;
my $l =0;
my $n =0;
$face->foreach_char(sub{
my $codepoint = $_->char_code;
if( $codepoint < 80 ){
return;
}
if($n ==0 ){
if( $l == 0 ){
print "\tprivate static void initForFont",(++$f),"(){\n";
}
print "\t\taddFontCode(new int[]{";
}
printf "0x%x,",$codepoint;
if( ++$n >= 5 ){
$n =0;
print "});\n";
if( ++$l >= 100 ){
$l = 0;
print "\t}\n";
}
}
});
if( $n > 0 ){
print "});\n";
print "\t}\n";
}
print "\tstatic{\n";
for(my $i=1;$i<=$f;++$i){
print"\t\tinitForFont$i();\n";
}
print "\t}\n";