diff --git a/.idea/misc.xml b/.idea/misc.xml index 5d199810..fbb68289 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -37,7 +37,7 @@ - + diff --git a/app/build.gradle b/app/build.gradle index 50cf9a4c..d43d4989 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "jp.juggler.subwaytooter" minSdkVersion 21 targetSdkVersion 25 - versionCode 25 - versionName "0.2.5" + versionCode 26 + versionName "0.2.6" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMain.java b/app/src/main/java/jp/juggler/subwaytooter/ActMain.java index 76d13e67..9bc03365 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActMain.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ActMain.java @@ -140,7 +140,17 @@ public class ActMain extends AppCompatActivity } } + ColumnViewHolder.ListItemPopup list_item_popup; + void closeListItemPopup(){ + if( list_item_popup != null ){ + list_item_popup.dismiss(); + list_item_popup = null; + } + } + @Override protected void onPause(){ + closeListItemPopup(); + HTMLDecoder.link_callback = null; super.onPause(); } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPost.java b/app/src/main/java/jp/juggler/subwaytooter/ActPost.java index ac307e46..0430e21d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActPost.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ActPost.java @@ -4,24 +4,34 @@ import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Resources; import android.database.Cursor; +import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; +import android.os.Handler; import android.provider.OpenableColumns; import android.support.annotation.Nullable; +import android.support.v4.content.ContextCompat; import android.support.v4.os.AsyncTaskCompat; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.text.Editable; +import android.text.Layout; import android.text.TextUtils; import android.text.TextWatcher; +import android.view.Gravity; import android.view.View; +import android.view.WindowManager; import android.widget.Button; import android.widget.CheckBox; +import android.widget.CheckedTextView; import android.widget.CompoundButton; -import android.widget.EditText; import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.PopupWindow; import android.widget.TextView; import com.android.volley.toolbox.NetworkImageView; @@ -41,9 +51,11 @@ import jp.juggler.subwaytooter.api.TootApiResult; import jp.juggler.subwaytooter.api.entity.TootAttachment; import jp.juggler.subwaytooter.api.entity.TootMention; import jp.juggler.subwaytooter.api.entity.TootStatus; +import jp.juggler.subwaytooter.table.AcctSet; import jp.juggler.subwaytooter.table.SavedAccount; import jp.juggler.subwaytooter.util.HTMLDecoder; import jp.juggler.subwaytooter.util.LogCategory; +import jp.juggler.subwaytooter.util.MyEditText; import jp.juggler.subwaytooter.util.Utils; import okhttp3.MediaType; import okhttp3.MultipartBody; @@ -141,10 +153,13 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { super.onActivityResult( requestCode, resultCode, data ); } + SharedPreferences pref; + @Override protected void onCreate( @Nullable Bundle savedInstanceState ){ super.onCreate( savedInstanceState ); App1.setActivityTheme( this, true ); + pref = Pref.pref( this ); initUI(); if( account_list.isEmpty() ){ @@ -296,6 +311,12 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { showReplyTo(); } + @Override protected void onDestroy(){ + handler.removeCallbacks( proc_text_changed ); + closeAcctPopup(); + super.onDestroy(); + } + @Override protected void onSaveInstanceState( Bundle outState ){ if( account != null ){ @@ -326,12 +347,13 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { View btnAttachment; View btnPost; View llAttachment; - final NetworkImageView[] ivMedia = new NetworkImageView[4]; + final NetworkImageView[] ivMedia = new NetworkImageView[ 4 ]; CheckBox cbNSFW; CheckBox cbContentWarning; - EditText etContentWarning; - EditText etContent; + MyEditText etContentWarning; + MyEditText etContent; TextView tvCharCount; + Handler handler; ArrayList< SavedAccount > account_list; @@ -342,20 +364,21 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { private void initUI(){ setContentView( R.layout.act_post ); + handler = new Handler(); btnAccount = (Button) findViewById( R.id.btnAccount ); btnVisibility = (ImageButton) findViewById( R.id.btnVisibility ); btnAttachment = findViewById( R.id.btnAttachment ); btnPost = findViewById( R.id.btnPost ); llAttachment = findViewById( R.id.llAttachment ); - ivMedia[0] = (NetworkImageView) findViewById( R.id.ivMedia1 ); - ivMedia[1] = (NetworkImageView) findViewById( R.id.ivMedia2 ); - ivMedia[2] = (NetworkImageView) findViewById( R.id.ivMedia3 ); - ivMedia[3] = (NetworkImageView) findViewById( R.id.ivMedia4 ); + ivMedia[ 0 ] = (NetworkImageView) findViewById( R.id.ivMedia1 ); + ivMedia[ 1 ] = (NetworkImageView) findViewById( R.id.ivMedia2 ); + ivMedia[ 2 ] = (NetworkImageView) findViewById( R.id.ivMedia3 ); + ivMedia[ 3 ] = (NetworkImageView) findViewById( R.id.ivMedia4 ); cbNSFW = (CheckBox) findViewById( R.id.cbNSFW ); cbContentWarning = (CheckBox) findViewById( R.id.cbContentWarning ); - etContentWarning = (EditText) findViewById( R.id.etContentWarning ); - etContent = (EditText) findViewById( R.id.etContent ); + etContentWarning = (MyEditText) findViewById( R.id.etContentWarning ); + etContent = (MyEditText) findViewById( R.id.etContent ); tvCharCount = (TextView) findViewById( R.id.tvCharCount ); llReply = findViewById( R.id.llReply ); @@ -377,12 +400,12 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { btnPost.setOnClickListener( this ); btnRemoveReply.setOnClickListener( this ); - for( NetworkImageView iv :ivMedia){ + for( NetworkImageView iv : ivMedia ){ iv.setOnClickListener( this ); - iv.setDefaultImageResId( Styler.getAttributeResourceId( this,R.attr.btn_refresh )); - // iv.setErrorImageResId( Styler.getAttributeResourceId( this,R.attr.btn_refresh )); + iv.setDefaultImageResId( Styler.getAttributeResourceId( this, R.attr.btn_refresh ) ); + // iv.setErrorImageResId( Styler.getAttributeResourceId( this,R.attr.btn_refresh )); } - + cbContentWarning.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged( CompoundButton buttonView, boolean isChecked ){ @@ -398,7 +421,11 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { @Override public void onTextChanged( CharSequence s, int start, int before, int count ){ - + if( count > 0 ){ + log.d( "onTextChanged" ); + handler.removeCallbacks( proc_text_changed ); + handler.postDelayed( proc_text_changed, 1500L ); + } } @Override @@ -406,13 +433,176 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { updateTextCount(); } } ); + etContent.setOnSelectionChangeListener( new MyEditText.OnSelectionChangeListener() { + int last_selection = - 1; + + @Override public void onSelectionChanged( int selStart, int selEnd ){ + if( selStart != selEnd ){ + // 範囲選択されてるならポップアップは閉じる + log.d( "onSelectionChanged: range selected" ); + closeAcctPopup(); + }else if( selStart > last_selection ){ + // 文字挿入の直後かもしれないので何もしない + log.d( "onSelectionChanged: may after text input? " ); + }else{ + // 前方への移動ではないならポップアップは閉じる + log.d( "onSelectionChanged: not forward change" ); + closeAcctPopup(); + } + last_selection = selStart; + } + } ); + } + + final Runnable proc_text_changed = new Runnable() { + @Override public void run(){ + int ss = etContent.getSelectionStart(); + int se = etContent.getSelectionEnd(); + if( ss != se ){ + closeAcctPopup(); + return; + } + int end = ss; + String src = etContent.getText().toString(); + int start = ss; + int count_atMark = 0; + int[] pos_atMark = new int[ 2 ]; + for( ; ; ){ + if( start == 0 ) break; + if( count_atMark >= 2 ) break; + char c = src.charAt( start - 1 ); + if( ( '0' <= c && c <= '9' ) + || ( 'A' <= c && c <= 'Z' ) + || ( 'a' <= c && c <= 'z' ) + || c == '_' + ){ + -- start; + continue; + }else if( c == '@' ){ + -- start; + pos_atMark[ count_atMark++ ] = start; + continue; + } + break; + } + if( count_atMark == 0 ){ + closeAcctPopup(); + return; + }else if( count_atMark == 1 ){ + start = pos_atMark[ 0 ]; + }else if( count_atMark == 2 ){ + start = pos_atMark[ 1 ]; + } + if( end - start < 2 ){ + closeAcctPopup(); + return; + } + int limit = 10; + String s = src.substring( start, end ); + ArrayList< String > acct_list = AcctSet.searchPrefix( s, limit ); + log.d( "search for %s, result=%d", s, acct_list.size() ); + if( acct_list.isEmpty() || acct_list.size() >= limit ){ + closeAcctPopup(); + return; + } + openAcctPopup( acct_list, start, end ); + } + }; + + PopupWindow acct_popup; + + private void closeAcctPopup(){ + if( acct_popup != null ){ + acct_popup.dismiss(); + acct_popup = null; + } + } + + private void openAcctPopup( ArrayList< String > acct_list, final int start, final int end ){ + closeAcctPopup(); + View viewRoot = getLayoutInflater().inflate( R.layout.acct_complete_popup, null, false ); + LinearLayout llItems = (LinearLayout) viewRoot.findViewById( R.id.llItems ); + { + CheckedTextView v = (CheckedTextView) getLayoutInflater().inflate( R.layout.lv_spinner_dropdown, llItems, false ); + v.setTextColor( Styler.getAttributeColor( this, android.R.attr.textColorPrimary ) ); + v.setText( R.string.close ); + v.setOnClickListener( new View.OnClickListener() { + @Override public void onClick( View v ){ + closeAcctPopup(); + } + } ); + llItems.addView( v ); + } + + for( int i = 0 ; ; ++ i ){ + if( i >= acct_list.size() ) break; + final String acct = acct_list.get( i ); + CheckedTextView v = (CheckedTextView) getLayoutInflater().inflate( R.layout.lv_spinner_dropdown, llItems, false ); + v.setTextColor( Styler.getAttributeColor( this, android.R.attr.textColorPrimary ) ); + v.setText( acct ); + v.setOnClickListener( new View.OnClickListener() { + @Override public void onClick( View v ){ + String s = etContent.getText().toString(); + s = s.substring( 0, start ) + acct + " " + ( end >= s.length() ? "" : s.substring( end ) ); + etContent.setText( s ); + etContent.setSelection( start + acct.length() + 1 ); + closeAcctPopup(); + } + } ); + llItems.addView( v ); + } + + // + acct_popup = new PopupWindow( this ); + acct_popup.setBackgroundDrawable( ContextCompat.getDrawable( this, R.drawable.acct_popup_bg ) ); + +// Resources.Theme popupTheme = getResources().newTheme(); +// +// int theme_idx = pref.getInt(Pref.KEY_UI_THEME,0); +// switch(theme_idx){ +// +// default: +// case 0: +// popupTheme.applyStyle( R.style.Theme_AppCompat_Light_Dialog, true); +// break; +// +// case 1: +// popupTheme.applyStyle( R.style.Theme_AppCompat_Dialog, true); +// break; +// +// } + + acct_popup.setWidth( WindowManager.LayoutParams.WRAP_CONTENT ); + acct_popup.setHeight( WindowManager.LayoutParams.WRAP_CONTENT ); + acct_popup.setContentView( viewRoot ); + acct_popup.setTouchable( true ); + + int[] location = new int[ 2 ]; + + etContent.getLocationOnScreen( location ); + int y = location[ 1 ]; + y += etContent.getTotalPaddingTop(); + y -= etContent.getScrollY(); + Layout layout = etContent.getLayout(); + y += layout.getLineBottom( layout.getLineCount() - 1 ); + + acct_popup.showAtLocation( + etContent + , Gravity.CENTER_HORIZONTAL | Gravity.TOP + , 0 + , y + ); + } private void updateTextCount(){ String s = etContent.getText().toString(); - int count = s.codePointCount( 0,s.length() ); - int remain = 500 - count; - tvCharCount.setText( Integer.toString( remain ) ); + int count_content = s.codePointCount( 0, s.length() ); + s = cbContentWarning.isChecked() ? etContentWarning.getText().toString() : ""; + int count_spoiler = s.codePointCount( 0, s.length() ); + + int remain = 500 - count_content - count_spoiler; + tvCharCount.setText( Integer.toString( remain ) ); int color = Styler.getAttributeColor( this, remain < 0 ? R.attr.colorRegexFilterError : android.R.attr.textColorPrimary ); tvCharCount.setTextColor( color ); } @@ -451,7 +641,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { }else{ tmp_account_list.addAll( account_list ); } - + String[] caption_list = new String[ tmp_account_list.size() ]; for( int i = 0, ie = tmp_account_list.size() ; i < ie ; ++ i ){ caption_list[ i ] = tmp_account_list.get( i ).acct; @@ -463,7 +653,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { @Override public void onClick( DialogInterface dialog, int which ){ if( which >= 0 && which < tmp_account_list.size() ){ - SavedAccount account =tmp_account_list.get( which ); + SavedAccount account = tmp_account_list.get( which ); setAccount( account ); try{ if( account.visibility != null && TootStatus.compareVisibility( visibility, account.visibility ) > 0 ){ @@ -471,8 +661,8 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { visibility = account.visibility; showVisibility(); } - }catch(Throwable ex){ - ex.printStackTrace( ); + }catch( Throwable ex ){ + ex.printStackTrace(); } } } @@ -499,8 +689,8 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { llAttachment.setVisibility( View.GONE ); }else{ llAttachment.setVisibility( View.VISIBLE ); - for(int i=0,ie=ivMedia.length;i2 SavedAccount に通知設定を追加 // 2017/4/25 v10 1=>2 NotificationTracking テーブルを追加 // 2017/4/29 v20 2=>5 MediaShown,ContentWarningのインデクスが間違っていたので貼り直す // 2017/4/29 v23 5=>6 MutedAppテーブルの追加、UserRelationテーブルの追加 + // 2017/5/01 v26 6=>7 AcctSetテーブルの追加 static DBOpenHelper db_open_helper; public static SQLiteDatabase getDB(){ @@ -77,6 +79,7 @@ public class App1 extends Application { NotificationTracking.onDBCreate(db); MutedApp.onDBCreate(db); UserRelation.onDBCreate(db); + AcctSet.onDBCreate( db ); } @Override @@ -90,6 +93,7 @@ public class App1 extends Application { NotificationTracking.onDBUpgrade( db, oldVersion, newVersion ); MutedApp.onDBUpgrade( db, oldVersion, newVersion ); UserRelation.onDBUpgrade( db, oldVersion, newVersion ); + AcctSet.onDBUpgrade( db, oldVersion, newVersion ); } } @@ -182,6 +186,8 @@ public class App1 extends Application { // SQLiteDatabase db = db_open_helper.getWritableDatabase(); // db_open_helper.onCreate( db ); // } + UserRelation.deleteOld(System.currentTimeMillis()); + AcctSet.deleteOld(System.currentTimeMillis()); } if( image_loader == null ){ diff --git a/app/src/main/java/jp/juggler/subwaytooter/Column.java b/app/src/main/java/jp/juggler/subwaytooter/Column.java index 7cafd287..ddfee5f4 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/Column.java +++ b/app/src/main/java/jp/juggler/subwaytooter/Column.java @@ -31,6 +31,7 @@ import jp.juggler.subwaytooter.api.entity.TootRelationShip; import jp.juggler.subwaytooter.api.entity.TootReport; import jp.juggler.subwaytooter.api.entity.TootResults; import jp.juggler.subwaytooter.api.entity.TootStatus; +import jp.juggler.subwaytooter.table.AcctSet; import jp.juggler.subwaytooter.table.MutedApp; import jp.juggler.subwaytooter.table.SavedAccount; import jp.juggler.subwaytooter.table.UserRelation; @@ -51,6 +52,7 @@ class Column { private static final long LOOP_TIMEOUT = 10000L; private static final int LOOP_READ_ENOUGH = 30; // フィルタ後のデータ数がコレ以上ならループを諦めます private static final int RELATIONSHIP_LOAD_STEP = 40; + private static final int ACCT_DB_STEP = 100; // ステータスのリストを返すAPI private static final String PATH_HOME = "/api/v1/timelines/home?limit=" + READ_LIMIT; @@ -1879,6 +1881,7 @@ class Column { private void updateRelation( TootApiClient client, ArrayList< Object > list_tmp ){ if( list_tmp == null || list_tmp.isEmpty() ) return; HashSet< Long > who_set = new HashSet<>(); + HashSet acct_set = new HashSet<>(); { TootAccount a; TootStatus s; @@ -1887,29 +1890,45 @@ class Column { if( o instanceof TootAccount ){ a = (TootAccount) o; who_set.add( a.id ); + acct_set.add( "@" + access_info.getFullAcct( a )); }else if( o instanceof TootStatus ){ s = (TootStatus) o; a = s.account; - if( a != null ) who_set.add( a.id ); + if( a != null ){ + who_set.add( a.id ); + acct_set.add( "@" + access_info.getFullAcct( a )); + } s = s.reblog; if( s != null ){ a = s.account; - if( a != null ) who_set.add( a.id ); + if( a != null ){ + who_set.add( a.id ); + acct_set.add( "@" + access_info.getFullAcct( a )); + } } }else if( o instanceof TootNotification ){ n = (TootNotification) o; // a = n.account; - if( a != null ) who_set.add( a.id ); + if( a != null ){ + who_set.add( a.id ); + acct_set.add( "@" + access_info.getFullAcct( a )); + } // s = n.status; if( s != null ){ a = s.account; - if( a != null ) who_set.add( a.id ); + if( a != null ){ + who_set.add( a.id ); + acct_set.add( "@" + access_info.getFullAcct( a )); + } s = s.reblog; if( s != null ){ a = s.account; - if( a != null ) who_set.add( a.id ); + if( a != null ){ + who_set.add( a.id ); + acct_set.add( "@" + access_info.getFullAcct( a )); + } } } } @@ -1925,6 +1944,7 @@ class Column { } } + long now = System.currentTimeMillis(); int n = 0; while( n < size ){ StringBuilder sb = new StringBuilder(); @@ -1941,12 +1961,34 @@ class Column { break; }else if( result.array != null ){ TootRelationShip.List list = TootRelationShip.parseList( log, result.array ); - long now = System.currentTimeMillis(); UserRelation.saveList( now, access_info.db_id, list ); } } log.d( "updateRelation: update %d relations.", n ); + } + size = acct_set.size(); + if( size > 0 ){ + String[] acct_list = new String[ size ]; + { + int n = 0; + for( String l : acct_set ){ + acct_list[ n++ ] = l; + } + } + long now = System.currentTimeMillis(); + int n = 0; + while( n < size ){ + int length = size-n; + if( length > ACCT_DB_STEP ) length = ACCT_DB_STEP; + AcctSet.saveList( now, acct_list, n,length ); + n += length; + } + log.d( "updateRelation: update %d acct.", n ); + + } + + } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java index 8cc9cdfb..ada2d867 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java @@ -79,6 +79,8 @@ class ColumnViewHolder implements View.OnClickListener, Column.VisualCallback, S saveScrollPosition(); log.d( "onPageDestroy:%s", column.getColumnName( true ) ); column.removeVisualListener( this ); + + activity.closeListItemPopup(); } private TextView tvLoading; @@ -1047,13 +1049,15 @@ class ColumnViewHolder implements View.OnClickListener, Column.VisualCallback, S void onItemClick( View anchor ){ if( status != null ){ + activity.closeListItemPopup(); // ポップアップを表示する - ListItemPopup popup = new ListItemPopup(); - popup.show( anchor, status ); + activity.list_item_popup = new ListItemPopup(); + activity.list_item_popup.show( anchor, status ); } } } + private final ActMain.RelationChangedCallback favourite_complete_callback = new ActMain.RelationChangedCallback() { @Override public void onRelationChanged(){ Utils.showToast( activity, false, R.string.favourite_succeeded ); @@ -1147,7 +1151,7 @@ class ColumnViewHolder implements View.OnClickListener, Column.VisualCallback, S } } - private class ListItemPopup { + class ListItemPopup { final View viewRoot; final ButtonsForStatus buttons_for_status; @@ -1215,5 +1219,9 @@ class ColumnViewHolder implements View.OnClickListener, Column.VisualCallback, S , popup_y ); } + + public void dismiss(){ + + } } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/table/AcctSet.java b/app/src/main/java/jp/juggler/subwaytooter/table/AcctSet.java new file mode 100644 index 00000000..73203715 --- /dev/null +++ b/app/src/main/java/jp/juggler/subwaytooter/table/AcctSet.java @@ -0,0 +1,145 @@ +package jp.juggler.subwaytooter.table; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.support.annotation.NonNull; + +import java.util.ArrayList; +import java.util.Collection; + +import jp.juggler.subwaytooter.App1; +import jp.juggler.subwaytooter.util.LogCategory; + +public class AcctSet { + + private static final LogCategory log = new LogCategory( "AcctSet" ); + + private static final String table = "acct_set"; + private static final String COL_TIME_SAVE = "time_save"; + private static final String COL_ACCT = "acct"; //@who@host ascii文字の大文字小文字は(sqliteにより)同一視される + + public static void onDBCreate( SQLiteDatabase db ){ + log.d( "onDBCreate!" ); + db.execSQL( + "create table if not exists " + table + + "(_id INTEGER PRIMARY KEY" + + "," + COL_TIME_SAVE + " integer not null" + + "," + COL_ACCT + " text not null" + + ")" + ); + db.execSQL( + "create unique index if not exists " + table + "_acct on " + table + "(" + COL_ACCT + ")" + ); + db.execSQL( + "create index if not exists " + table + "_time on " + table + "(" + COL_TIME_SAVE + ")" + ); + } + + public static void onDBUpgrade( SQLiteDatabase db, int oldVersion, int newVersion ){ + if( oldVersion < 7 && newVersion >= 7 ){ + onDBCreate( db ); + } + } + + public static void deleteOld( long now ){ + try{ + // 古いデータを掃除する + long expire = now - 86400000L * 365; + App1.getDB().delete( table, COL_TIME_SAVE + " prefix_search_where_arg = new ThreadLocal< String[] >() { + @Override protected String[] initialValue(){ + return new String[ 1 ]; + } + }; + + private static String makePattern( String src ){ + StringBuilder sb = new StringBuilder(); + for( int i = 0, ie = src.length() ; i < ie ; ++ i ){ + char c = src.charAt( i ); + if( c == '%' || c == '_' || c == '$' ){ + sb.append( '$' ); + } + sb.append( c ); + } + // 前方一致検索にするため、末尾に%をつける + sb.append( '%' ); + return sb.toString(); + } + + @NonNull public static ArrayList< String > searchPrefix( @NonNull String prefix ,int limit){ + try{ + String[] where_arg = prefix_search_where_arg.get(); + where_arg[ 0 ] = makePattern( prefix ); + Cursor cursor = App1.getDB().query( table, null, prefix_search_where, where_arg, null, null, COL_ACCT + " asc limit "+limit ); + if( cursor != null ){ + try{ + ArrayList< String > dst = new ArrayList<>( cursor.getCount() ); + int idx_acct = cursor.getColumnIndex( COL_ACCT ); + while( cursor.moveToNext() ){ + dst.add( cursor.getString( idx_acct ) ); + } + return dst; + }finally{ + cursor.close(); + } + } + }catch( Throwable ex ){ + ex.printStackTrace(); + log.e( ex, "searchPrefix failed." ); + } + return new ArrayList<>(); + } + +} diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/MyEditText.java b/app/src/main/java/jp/juggler/subwaytooter/util/MyEditText.java new file mode 100644 index 00000000..d4c2c20a --- /dev/null +++ b/app/src/main/java/jp/juggler/subwaytooter/util/MyEditText.java @@ -0,0 +1,42 @@ +package jp.juggler.subwaytooter.util; + +import android.content.Context; +import android.support.v7.widget.AppCompatEditText; +import android.util.AttributeSet; +import android.widget.EditText; + +/** + * Created by tateisu on 2017/05/01. + */ + +public class MyEditText extends AppCompatEditText { + public MyEditText( Context context ){ + super( context ); + } + + public MyEditText( Context context, AttributeSet attrs ){ + super( context, attrs ); + } + + public MyEditText( Context context, AttributeSet attrs, int defStyleAttr ){ + super( context, attrs, defStyleAttr ); + } + + public interface OnSelectionChangeListener { + void onSelectionChanged( int selStart, int selEnd ); + } + + OnSelectionChangeListener mOnSelectionChangeListener; + + public void setOnSelectionChangeListener( OnSelectionChangeListener listener ){ + mOnSelectionChangeListener = listener; + } + + @Override + protected void onSelectionChanged( int selStart, int selEnd ){ + super.onSelectionChanged( selStart, selEnd ); + if( mOnSelectionChangeListener != null ){ + mOnSelectionChangeListener.onSelectionChanged( selStart, selEnd ); + } + } +} diff --git a/app/src/main/res/drawable/acct_popup_bg.xml b/app/src/main/res/drawable/acct_popup_bg.xml new file mode 100644 index 00000000..8076a793 --- /dev/null +++ b/app/src/main/res/drawable/acct_popup_bg.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/acct_complete_popup.xml b/app/src/main/res/layout/acct_complete_popup.xml new file mode 100644 index 00000000..5666da5f --- /dev/null +++ b/app/src/main/res/layout/acct_complete_popup.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/app/src/main/res/layout/act_post.xml b/app/src/main/res/layout/act_post.xml index ef751c3a..797a6fdf 100644 --- a/app/src/main/res/layout/act_post.xml +++ b/app/src/main/res/layout/act_post.xml @@ -174,7 +174,7 @@ android:background="?attr/colorPostFormBackground" > - - Reject for follow request %1$s\'s follow request is rejected. %1$s\'s follow request is authorized. + Close