投稿画面でメンションの入力補完
This commit is contained in:
parent
a4ef01503e
commit
92fa93fedd
|
@ -37,7 +37,7 @@
|
|||
<ConfirmationsSetting value="0" id="Add" />
|
||||
<ConfirmationsSetting value="0" id="Remove" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;i<ie;++i){
|
||||
showAttachment_sub( ivMedia[i], i );
|
||||
for( int i = 0, ie = ivMedia.length ; i < ie ; ++ i ){
|
||||
showAttachment_sub( ivMedia[ i ], i );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -624,7 +814,8 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener {
|
|||
@Override
|
||||
public void writeTo( BufferedSink sink ) throws IOException{
|
||||
InputStream is = getContentResolver().openInputStream( uri );
|
||||
if( is == null ) throw new IOException( "openInputStream() failed. uri="+uri );
|
||||
if( is == null )
|
||||
throw new IOException( "openInputStream() failed. uri=" + uri );
|
||||
try{
|
||||
byte[] tmp = new byte[ 4096 ];
|
||||
for( ; ; ){
|
||||
|
|
|
@ -17,6 +17,7 @@ import com.android.volley.RequestQueue;
|
|||
import com.android.volley.toolbox.ImageLoader;
|
||||
import com.android.volley.toolbox.Volley;
|
||||
|
||||
import jp.juggler.subwaytooter.table.AcctSet;
|
||||
import jp.juggler.subwaytooter.table.MutedApp;
|
||||
import jp.juggler.subwaytooter.table.ClientInfo;
|
||||
import jp.juggler.subwaytooter.table.ContentWarning;
|
||||
|
@ -33,11 +34,12 @@ public class App1 extends Application {
|
|||
|
||||
|
||||
static final String DB_NAME = "app_db";
|
||||
static final int DB_VERSION = 6;
|
||||
static final int DB_VERSION = 7;
|
||||
// 2017/4/25 v10 1=>2 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 ){
|
||||
|
|
|
@ -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<String> 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 );
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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(){
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<>();
|
||||
}
|
||||
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle"
|
||||
>
|
||||
<solid android:color="?android:attr/colorBackground" />
|
||||
<stroke
|
||||
android:color="?android:attr/colorForeground"
|
||||
android:width="1dp"
|
||||
/>
|
||||
|
||||
</shape>
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llItems"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
/>
|
||||
|
||||
</ScrollView>
|
|
@ -174,7 +174,7 @@
|
|||
android:background="?attr/colorPostFormBackground"
|
||||
>
|
||||
|
||||
<EditText
|
||||
<jp.juggler.subwaytooter.util.MyEditText
|
||||
android:id="@+id/etContentWarning"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -196,7 +196,7 @@
|
|||
android:background="?attr/colorPostFormBackground"
|
||||
>
|
||||
|
||||
<EditText
|
||||
<jp.juggler.subwaytooter.util.MyEditText
|
||||
android:id="@+id/etContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -202,4 +202,5 @@
|
|||
<string name="follow_request_ng">Reject for follow request</string>
|
||||
<string name="follow_request_rejected">%1$s\'s follow request is rejected.</string>
|
||||
<string name="follow_request_authorized">%1$s\'s follow request is authorized.</string>
|
||||
<string name="close">Close</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue