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 + "", new String[]{ Long.toString( expire ) } );
+
+ }catch( Throwable ex ){
+ log.e( ex, "deleteOld failed." );
+ }
+ }
+
+// public static void save1( long now, String acct ){
+// try{
+//
+// ContentValues cv = new ContentValues();
+// cv.put( COL_TIME_SAVE, now );
+// cv.put( COL_ACCT, acct );
+// App1.getDB().replace( table, null, cv );
+// }catch( Throwable ex ){
+// log.e( ex, "save failed." );
+// }
+// }
+
+ public static void saveList( long now, String[] src_list, int offset, int length ){
+
+ try{
+ ContentValues cv = new ContentValues();
+ cv.put( COL_TIME_SAVE, now );
+
+ boolean bOK = false;
+ SQLiteDatabase db = App1.getDB();
+ db.execSQL( "BEGIN TRANSACTION" );
+ try{
+ for( int i = 0 ; i < length ; ++ i ){
+ String acct = src_list[ i + offset ];
+ cv.put( COL_ACCT, acct );
+ db.replace( table, null, cv );
+ }
+ bOK = true;
+ }catch( Throwable ex ){
+ ex.printStackTrace();
+ log.e( ex, "saveList failed." );
+ }
+ if( bOK ){
+ db.execSQL( "COMMIT TRANSACTION" );
+ }else{
+ db.execSQL( "ROLLBACK TRANSACTION" );
+ }
+ }catch( Throwable ex ){
+ ex.printStackTrace();
+ log.e( ex, "saveList failed." );
+ }
+ }
+
+ private static final String prefix_search_where = COL_ACCT + " like ? escape '$'";
+
+ private static final ThreadLocal< String[] > 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