diff --git a/app/build.gradle b/app/build.gradle
index dc922ed0..980a2e39 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -9,8 +9,8 @@ android {
applicationId "jp.juggler.subwaytooter"
minSdkVersion 21
targetSdkVersion 25
- versionCode 101
- versionName "1.0.1"
+ versionCode 102
+ versionName "1.0.2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.java b/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.java
index 0c4626f3..a24105cd 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.java
@@ -97,6 +97,7 @@ public class ActAppSetting extends AppCompatActivity
Switch swPriorChrome;
Switch swPostButtonBarTop;
Switch swDontDuplicationCheck ;
+ Switch swQuickTootBar;
Spinner spBackButtonAction;
Spinner spUITheme;
@@ -191,6 +192,9 @@ public class ActAppSetting extends AppCompatActivity
swDontDuplicationCheck = (Switch) findViewById( R.id.swDontDuplicationCheck );
swDontDuplicationCheck.setOnCheckedChangeListener( this );
+ swQuickTootBar = (Switch) findViewById( R.id.swQuickTootBar );
+ swQuickTootBar.setOnCheckedChangeListener( this );
+
cbNotificationSound = (CheckBox) findViewById( R.id.cbNotificationSound );
cbNotificationVibration = (CheckBox) findViewById( R.id.cbNotificationVibration );
cbNotificationLED = (CheckBox) findViewById( R.id.cbNotificationLED );
@@ -327,6 +331,7 @@ public class ActAppSetting extends AppCompatActivity
swDontCropMediaThumb.setChecked( pref.getBoolean( Pref.KEY_DONT_CROP_MEDIA_THUMBNAIL, false ) );
swPostButtonBarTop.setChecked( pref.getBoolean( Pref.KEY_POST_BUTTON_BAR_AT_TOP, false ) );
swDontDuplicationCheck.setChecked( pref.getBoolean( Pref.KEY_DONT_DUPLICATION_CHECK, false ) );
+ swQuickTootBar.setChecked( pref.getBoolean( Pref.KEY_QUICK_TOOT_BAR, false ) );
// Switch with default true
swDisableFastScroller.setChecked( pref.getBoolean( Pref.KEY_DISABLE_FAST_SCROLLER, true ) );
@@ -387,8 +392,9 @@ public class ActAppSetting extends AppCompatActivity
.putBoolean( Pref.KEY_PRIOR_CHROME, swPriorChrome.isChecked() )
.putBoolean( Pref.KEY_POST_BUTTON_BAR_AT_TOP, swPostButtonBarTop.isChecked() )
.putBoolean( Pref.KEY_DONT_DUPLICATION_CHECK, swDontDuplicationCheck.isChecked() )
+ .putBoolean( Pref.KEY_QUICK_TOOT_BAR, swQuickTootBar.isChecked() )
-
+
.putBoolean( Pref.KEY_NOTIFICATION_SOUND, cbNotificationSound.isChecked() )
.putBoolean( Pref.KEY_NOTIFICATION_VIBRATION, cbNotificationVibration.isChecked() )
.putBoolean( Pref.KEY_NOTIFICATION_LED, cbNotificationLED.isChecked() )
diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMain.java b/app/src/main/java/jp/juggler/subwaytooter/ActMain.java
index b84b064b..30bc18db 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/ActMain.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/ActMain.java
@@ -25,6 +25,7 @@ import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.JsonReader;
import android.view.Gravity;
+import android.view.KeyEvent;
import android.view.View;
import android.support.design.widget.NavigationView;
import android.support.v4.view.GravityCompat;
@@ -35,9 +36,11 @@ import android.view.MenuItem;
import android.view.ViewParent;
import android.view.Window;
import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
import android.widget.HorizontalScrollView;
import android.widget.ImageButton;
import android.widget.ImageView;
+import android.widget.TextView;
import org.apache.commons.io.IOUtils;
import org.json.JSONException;
@@ -77,9 +80,11 @@ import jp.juggler.subwaytooter.dialog.ActionsDialog;
import jp.juggler.subwaytooter.util.LinkClickContext;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.MyClickableSpan;
+import jp.juggler.subwaytooter.util.PostHelper;
import jp.juggler.subwaytooter.util.Utils;
import jp.juggler.subwaytooter.view.ColumnStripLinearLayout;
import jp.juggler.subwaytooter.view.GravitySnapHelper;
+import jp.juggler.subwaytooter.view.MyEditText;
import okhttp3.Request;
import okhttp3.RequestBody;
@@ -160,6 +165,8 @@ public class ActMain extends AppCompatActivity
@Override protected void onDestroy(){
log.d( "onDestroy" );
super.onDestroy();
+ post_helper.onDestroy();
+
// このアクティビティに関連する ColumnViewHolder への参照を全カラムから除去する
for( Column c : app_state.column_list ){
c.removeColumnViewHolderByActivity( this );
@@ -239,17 +246,7 @@ public class ActMain extends AppCompatActivity
// 各カラムのアカウント設定を読み直す
reloadAccountSetting();
- if( ! TextUtils.isEmpty( posted_acct ) ){
- int refresh_after_toot = pref.getInt( Pref.KEY_REFRESH_AFTER_TOOT, 0 );
- if( refresh_after_toot != Pref.RAT_DONT_REFRESH ){
- for( Column column : app_state.column_list ){
- SavedAccount a = column.access_info;
- if( ! Utils.equalsNullable( a.acct, posted_acct ) ) continue;
- column.startRefreshForPost( posted_status_id, refresh_after_toot );
- }
- }
- posted_acct = null;
- }
+ refreshAfterPost();
Uri uri = ActCallback.last_uri.getAndSet( null );
if( uri != null ){
@@ -268,6 +265,20 @@ public class ActMain extends AppCompatActivity
updateColumnStripSelection( - 1, - 1f );
}
+ void refreshAfterPost(){
+ if( ! TextUtils.isEmpty( posted_acct ) ){
+ int refresh_after_toot = pref.getInt( Pref.KEY_REFRESH_AFTER_TOOT, 0 );
+ if( refresh_after_toot != Pref.RAT_DONT_REFRESH ){
+ for( Column column : app_state.column_list ){
+ SavedAccount a = column.access_info;
+ if( ! Utils.equalsNullable( a.acct, posted_acct ) ) continue;
+ column.startRefreshForPost( posted_status_id, refresh_after_toot );
+ }
+ }
+ posted_acct = null;
+ }
+ }
+
static Intent sent_intent2;
private void handleSentIntent( final Intent intent ){
@@ -333,10 +344,57 @@ public class ActMain extends AppCompatActivity
case R.id.btnToot:
performTootButton();
break;
-
+
+ case R.id.btnQuickToot:
+ performQuickPost(null);
+ break;
}
}
+ private void performQuickPost(SavedAccount account){
+
+ if( account == null ){
+ if( pager_adapter != null ){
+ Column c = app_state.column_list.get( pager.getCurrentItem() );
+ if( ! c.access_info.isPseudo() ){
+ account = c.access_info;
+ }
+ }
+ if( account == null ){
+ AccountPicker.pick( this, false, true, getString( R.string.account_picker_toot ), new AccountPicker.AccountPickerCallback() {
+ @Override public void onAccountPicked( @NonNull SavedAccount ai ){
+ performQuickPost( ai );
+ }
+ } );
+ return;
+ }
+ }
+
+ String visibility = account.visibility;
+ if( TextUtils.isEmpty( visibility ) ){
+ visibility = TootStatus.VISIBILITY_PUBLIC;
+ }
+
+
+
+ post_helper.content = etQuickToot.getText().toString().trim();
+ post_helper.spoiler_text = null;
+ post_helper.visibility = visibility;
+ post_helper.bNSFW = false;
+ post_helper.in_reply_to_id = - 1L;
+ post_helper.attachment_list = null;
+
+ Utils.hideKeyboard( this,etQuickToot );
+ post_helper.post( account, false, false, new PostHelper.Callback() {
+ @Override public void onPostComplete( SavedAccount target_account, TootStatus status ){
+ etQuickToot.setText("");
+ posted_acct = target_account.acct;
+ posted_status_id =status.id;
+ refreshAfterPost();
+ }
+ });
+ }
+
@Override
public void onPageScrolled( int position, float positionOffset, int positionOffsetPixels ){
updateColumnStripSelection( position, positionOffset );
@@ -430,6 +488,7 @@ public class ActMain extends AppCompatActivity
}else if( requestCode == REQUEST_CODE_POST ){
if( data != null ){
+ etQuickToot.setText("");
posted_acct = data.getStringExtra( ActPost.EXTRA_POSTED_ACCT );
posted_status_id = data.getLongExtra( ActPost.EXTRA_POSTED_STATUS_ID, 0L );
}
@@ -690,6 +749,12 @@ public class ActMain extends AppCompatActivity
boolean dont_crop_media_thumbnail;
+ View llQuickTootBar;
+ MyEditText etQuickToot;
+ ImageButton btnQuickToot;
+ PostHelper post_helper;
+
+
void initUI(){
setContentView( R.layout.act_main );
@@ -727,12 +792,30 @@ public class ActMain extends AppCompatActivity
vFooterDivider2 = findViewById( R.id.vFooterDivider2 );
llColumnStrip = (ColumnStripLinearLayout) findViewById( R.id.llColumnStrip );
svColumnStrip = (HorizontalScrollView) findViewById( R.id.svColumnStrip );
+ llQuickTootBar = findViewById( R.id.llQuickTootBar );
+ etQuickToot = (MyEditText) findViewById( R.id. etQuickToot );
+ btnQuickToot = (ImageButton)findViewById( R.id. btnQuickToot );
+
+ if( !pref.getBoolean( Pref.KEY_QUICK_TOOT_BAR ,false ) ){
+ llQuickTootBar.setVisibility( View.GONE );
+ }
btnToot.setOnClickListener( this );
btnMenu.setOnClickListener( this );
-
+ btnQuickToot.setOnClickListener( this );
+ etQuickToot.setOnEditorActionListener( new TextView.OnEditorActionListener() {
+ @Override public boolean onEditorAction( TextView v, int actionId, KeyEvent event ){
+ if( actionId == EditorInfo.IME_ACTION_SEND ){
+ btnQuickToot.performClick();
+ return true;
+ }
+ return false;
+ }
+ } );
svColumnStrip.setHorizontalFadingEdgeEnabled( true );
+ post_helper = new PostHelper( this,pref,app_state.handler );
+
DisplayMetrics dm = getResources().getDisplayMetrics();
float density = dm.density;
@@ -814,6 +897,11 @@ public class ActMain extends AppCompatActivity
}
showFooterColor();
+
+ post_helper.attachEditText( findViewById( R.id.llFormRoot ), etQuickToot, true,new PostHelper.Callback2() {
+ @Override public void onTextUpdate(){
+ }
+ } );
}
void updateColumnStrip(){
@@ -1859,24 +1947,26 @@ public class ActMain extends AppCompatActivity
};
private void performTootButton(){
+ post_helper.closeAcctPopup();
+ final String initial_text = etQuickToot.getText().toString();
if( pager_adapter != null ){
Column c = pager_adapter.getColumn( pager.getCurrentItem() );
if( c != null && ! c.access_info.isPseudo() ){
- ActPost.open( this, REQUEST_CODE_POST, c.access_info.db_id, "" );
+ ActPost.open( this, REQUEST_CODE_POST, c.access_info.db_id, initial_text );
return;
}
}else{
long db_id = pref.getLong( Pref.KEY_TABLET_TOOT_DEFAULT_ACCOUNT, - 1L );
SavedAccount a = SavedAccount.loadAccount( log, db_id );
if( a != null ){
- ActPost.open( this, REQUEST_CODE_POST, a.db_id, "" );
+ ActPost.open( this, REQUEST_CODE_POST, a.db_id, initial_text );
return;
}
}
AccountPicker.pick( this, false, true, getString( R.string.account_picker_toot ), new AccountPicker.AccountPickerCallback() {
@Override public void onAccountPicked( @NonNull SavedAccount ai ){
- ActPost.open( ActMain.this, REQUEST_CODE_POST, ai.db_id, "" );
+ ActPost.open( ActMain.this, REQUEST_CODE_POST, ai.db_id, initial_text );
}
} );
}
@@ -3289,28 +3379,34 @@ public class ActMain extends AppCompatActivity
if( c == 0 ){
btnMenu.setBackgroundResource( R.drawable.btn_bg_ddd );
btnToot.setBackgroundResource( R.drawable.btn_bg_ddd );
+ btnQuickToot.setBackgroundResource( R.drawable.btn_bg_ddd );
}else{
int fg = ( footer_button_fg_color != 0
? footer_button_fg_color
: Styler.getAttributeColor( this, R.attr.colorRippleEffect ) );
ViewCompat.setBackground( btnToot, Styler.getAdaptiveRippleDrawable( c, fg ) );
ViewCompat.setBackground( btnMenu, Styler.getAdaptiveRippleDrawable( c, fg ) );
+ ViewCompat.setBackground( btnQuickToot, Styler.getAdaptiveRippleDrawable( c, fg ) );
}
c = footer_button_fg_color;
if( c == 0 ){
Styler.setIconDefaultColor( this, btnToot, R.attr.ic_edit );
Styler.setIconDefaultColor( this, btnMenu, R.attr.ic_hamburger );
+ Styler.setIconDefaultColor( this, btnQuickToot, R.attr.btn_post );
}else{
Styler.setIconCustomColor( this, btnToot, c, R.attr.ic_edit );
Styler.setIconCustomColor( this, btnMenu, c, R.attr.ic_hamburger );
+ Styler.setIconCustomColor( this, btnQuickToot, c,R.attr.btn_post );
}
c = footer_tab_bg_color;
if( c == 0 ){
svColumnStrip.setBackgroundColor( Styler.getAttributeColor( this, R.attr.colorColumnStripBackground ) );
+ llQuickTootBar.setBackgroundColor( Styler.getAttributeColor( this, R.attr.colorColumnStripBackground ) );
}else{
svColumnStrip.setBackgroundColor( c );
+ svColumnStrip.setBackgroundColor( Styler.getAttributeColor( this, R.attr.colorColumnStripBackground ) );
}
c = footer_tab_divider_color;
diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPost.java b/app/src/main/java/jp/juggler/subwaytooter/ActPost.java
index 7ce7804d..3d6cec94 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/ActPost.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/ActPost.java
@@ -25,10 +25,7 @@ import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
-import android.text.Editable;
-import android.text.Spannable;
import android.text.TextUtils;
-import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.view.View;
import android.view.ViewGroup;
@@ -55,8 +52,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import jp.juggler.subwaytooter.api.TootApiClient;
import jp.juggler.subwaytooter.api.TootApiResult;
@@ -64,18 +59,16 @@ import jp.juggler.subwaytooter.api.entity.TootAttachment;
import jp.juggler.subwaytooter.api.entity.TootMention;
import jp.juggler.subwaytooter.api.entity.TootResults;
import jp.juggler.subwaytooter.api.entity.TootStatus;
-import jp.juggler.subwaytooter.dialog.DlgConfirm;
import jp.juggler.subwaytooter.dialog.DlgDraftPicker;
import jp.juggler.subwaytooter.table.AcctColor;
-import jp.juggler.subwaytooter.table.AcctSet;
import jp.juggler.subwaytooter.table.PostDraft;
import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.dialog.ActionsDialog;
-import jp.juggler.subwaytooter.table.TagSet;
import jp.juggler.subwaytooter.util.HTMLDecoder;
import jp.juggler.subwaytooter.util.LinkClickContext;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.MyClickableSpan;
+import jp.juggler.subwaytooter.util.PostHelper;
import jp.juggler.subwaytooter.view.MyEditText;
import jp.juggler.subwaytooter.view.MyNetworkImageView;
import jp.juggler.subwaytooter.util.PostAttachment;
@@ -162,7 +155,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
break;
case R.id.btnPost:
- performPost( false, false );
+ performPost();
break;
case R.id.btnRemoveReply:
@@ -258,6 +251,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
ArrayList< PostAttachment > attachment_list;
AppState app_state;
boolean isPostComplete;
+ PostHelper post_helper;
@Override
protected void onCreate( @Nullable Bundle savedInstanceState ){
@@ -269,6 +263,8 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
initUI();
+
+
if( account_list.isEmpty() ){
Utils.showToast( this, true, R.string.please_add_account );
finish();
@@ -492,8 +488,9 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
}
@Override protected void onDestroy(){
- handler.removeCallbacks( proc_text_changed );
- closeAcctPopup();
+ post_helper.onDestroy();
+
+
super.onDestroy();
}
@@ -631,34 +628,14 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
}
} );
- etContent.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 ){
- handler.removeCallbacks( proc_text_changed );
- handler.postDelayed( proc_text_changed, ( popup != null && popup.isShowing() ? 100L : 1000L ) );
- }
-
- @Override
- public void afterTextChanged( Editable s ){
+ post_helper = new PostHelper( this, pref ,app_state.handler );
+ post_helper.attachEditText( formRoot,etContent,false, new PostHelper.Callback2() {
+ @Override public void onTextUpdate(){
updateTextCount();
}
} );
- etContent.setOnSelectionChangeListener( new MyEditText.OnSelectionChangeListener() {
-
- @Override public void onSelectionChanged( int selStart, int selEnd ){
- if( selStart != selEnd ){
- // 範囲選択されてるならポップアップは閉じる
- log.d( "onSelectionChanged: range selected" );
- closeAcctPopup();
- }
- }
- } );
+
scrollView.getViewTreeObserver().addOnScrollChangedListener( scroll_listener );
@@ -668,116 +645,11 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
final ViewTreeObserver.OnScrollChangedListener scroll_listener = new ViewTreeObserver.OnScrollChangedListener() {
@Override public void onScrollChanged(){
- if( popup != null && popup.isShowing() ){
- popup.updatePosition();
- }
+ post_helper.onScrollChanged();
+
}
};
-
- static final Pattern reCharsNotTag = Pattern.compile( "[\\s\\-+.,:;/]" );
-
- final Runnable proc_text_changed = new Runnable() {
- @Override public void run(){
- int start = etContent.getSelectionStart();
- int end = etContent.getSelectionEnd();
- if( start != end ){
- closeAcctPopup();
- return;
- }
- String src = etContent.getText().toString();
- int count_atMark = 0;
- int[] pos_atMark = new int[ 2 ];
- for( ; ; ){
- if( count_atMark >= 2 ) break;
-
- if( start == 0 ) break;
- char c = src.charAt( start - 1 );
-
- if( c == '@' ){
- -- start;
- pos_atMark[ count_atMark++ ] = start;
- continue;
- }else if( ( '0' <= c && c <= '9' )
- || ( 'A' <= c && c <= 'Z' )
- || ( 'a' <= c && c <= 'z' )
- || c == '_' || c == '-' || c == '.'
- ){
- -- start;
- continue;
- }
- // その他の文字種が出たら探索打ち切り
- break;
- }
- // 登場した@の数
- if( count_atMark == 0 ){
- // 次はAcctじゃなくてHashtagの補完を試みる
- checkTag();
- return;
- }else if( count_atMark == 1 ){
- start = pos_atMark[ 0 ];
- }else if( count_atMark == 2 ){
- start = pos_atMark[ 1 ];
- }
- // 最低でも2文字ないと補完しない
- if( end - start < 2 ){
- closeAcctPopup();
- return;
- }
- int limit = 100;
- 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() ){
- closeAcctPopup();
- }else{
- if( popup == null || ! popup.isShowing() ){
- popup = new PopupAutoCompleteAcct( ActPost.this, etContent, formRoot );
- }
- popup.setList( acct_list, start, end );
- }
- }
-
- private void checkTag(){
- int end = etContent.getSelectionEnd();
-
- String src = etContent.getText().toString();
- int last_sharp = src.lastIndexOf( '#', end - 1 );
-
- if( last_sharp == - 1 || end - last_sharp < 3 ){
- closeAcctPopup();
- return;
- }
-
- String part = src.substring( last_sharp + 1, end );
- if( reCharsNotTag.matcher( part ).find() ){
- closeAcctPopup();
- return;
- }
-
- int limit = 100;
- String s = src.substring( last_sharp + 1, end );
- ArrayList< String > tag_list = TagSet.searchPrefix( s, limit );
- log.d( "search for %s, result=%d", s, tag_list.size() );
- if( tag_list.isEmpty() ){
- closeAcctPopup();
- }else{
- if( popup == null || ! popup.isShowing() ){
- popup = new PopupAutoCompleteAcct( ActPost.this, etContent, formRoot );
- }
- popup.setList( tag_list, last_sharp, end );
- }
- }
- };
-
- PopupAutoCompleteAcct popup;
-
- private void closeAcctPopup(){
- if( popup != null ){
- popup.dismiss();
- popup = null;
- }
- }
-
+
private void updateTextCount(){
String s = etContent.getText().toString();
int count_content = s.codePointCount( 0, s.length() );
@@ -1474,219 +1346,34 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
///////////////////////////////////////////////////////////////////////////////////////
// post
- // [:word:] 単語構成文字 (Letter | Mark | Decimal_Number | Connector_Punctuation)
- // [:alpha:] 英字 (Letter | Mark)
-
- static final String word = "[_\\p{L}\\p{M}\\p{Nd}\\p{Pc}]";
- static final String alpha = "[_\\p{L}\\p{M}]";
-
- static final Pattern reTag = Pattern.compile(
- "(?:^|[^/)\\w])#(" + word + "*" + alpha + word + "*)"
- , Pattern.CASE_INSENSITIVE
- );
-
- private void performPost( final boolean bConfirmTag, final boolean bConfirmAccount ){
- final String content = etContent.getText().toString().trim();
- if( TextUtils.isEmpty( content ) ){
- Utils.showToast( this, true, R.string.post_error_contents_empty );
- return;
- }
+ private void performPost(){
+ post_helper.content = etContent.getText().toString().trim();
- final String spoiler_text;
if( ! cbContentWarning.isChecked() ){
- spoiler_text = null;
+ post_helper.spoiler_text = null;
}else{
- spoiler_text = etContentWarning.getText().toString().trim();
- if( TextUtils.isEmpty( spoiler_text ) ){
- Utils.showToast( this, true, R.string.post_error_contents_warning_empty );
- return;
- }
+ post_helper.spoiler_text = etContentWarning.getText().toString().trim();
}
- if( ! bConfirmAccount ){
- DlgConfirm.open( this
- , getString( R.string.confirm_post_from, AcctColor.getNickname( account.acct ) )
- , new DlgConfirm.Callback() {
- @Override public boolean isConfirmEnabled(){
- return account.confirm_post;
- }
-
- @Override public void setConfirmEnabled( boolean bv ){
- account.confirm_post = bv;
- account.saveSetting();
- }
-
- @Override public void onOK(){
- performPost( bConfirmTag, true );
- }
- } );
- return;
- }
+ post_helper.visibility = this.visibility;
+ post_helper.bNSFW = cbNSFW.isChecked();
- if( ! bConfirmTag ){
- Matcher m = reTag.matcher( content );
- if( m.find() && ! TootStatus.VISIBILITY_PUBLIC.equals( visibility ) ){
- new AlertDialog.Builder( this )
- .setCancelable( true )
- .setMessage( R.string.hashtag_and_visibility_not_match )
- .setNegativeButton( R.string.cancel, null )
- .setPositiveButton( R.string.ok, new DialogInterface.OnClickListener() {
- @Override public void onClick( DialogInterface dialog, int which ){
- //noinspection ConstantConditions
- performPost( true, bConfirmAccount );
- }
- } )
- .show();
- return;
- }
- }
+ post_helper.in_reply_to_id = this.in_reply_to_id;
- final StringBuilder sb = new StringBuilder();
+ post_helper.attachment_list = this.attachment_list;
- sb.append( "status=" );
- sb.append( Uri.encode( content ) );
-
- sb.append( "&visibility=" );
- sb.append( Uri.encode( visibility ) );
-
- if( cbNSFW.isChecked() ){
- sb.append( "&sensitive=1" );
- }
-
- if( spoiler_text != null ){
- sb.append( "&spoiler_text=" );
- sb.append( Uri.encode( spoiler_text ) );
- }
-
- if( in_reply_to_id != - 1L ){
- sb.append( "&in_reply_to_id=" );
- sb.append( Long.toString( in_reply_to_id ) );
- }
- if( attachment_list != null ){
- for( PostAttachment pa : attachment_list ){
- if( pa.attachment != null ){
- sb.append( "&media_ids[]=" ).append( pa.attachment.id );
- }
- }
- }
-
- final ProgressDialog progress = new ProgressDialog( this );
-
- final AsyncTask< Void, Void, TootApiResult > task = new AsyncTask< Void, Void, TootApiResult >() {
- final SavedAccount target_account = account;
+ post_helper.post( account, false, false, new PostHelper.Callback() {
- TootStatus status;
-
- @Override protected TootApiResult doInBackground( Void... params ){
- TootApiClient client = new TootApiClient( ActPost.this, new TootApiClient.Callback() {
- @Override public boolean isApiCancelled(){
- return isCancelled();
- }
-
- @Override public void publishApiProgress( final String s ){
- Utils.runOnMainThread( new Runnable() {
- @Override public void run(){
- progress.setMessage( s );
- }
- } );
- }
- } );
+ @Override public void onPostComplete( SavedAccount target_account, TootStatus status ){
+ Intent data = new Intent();
+ data.putExtra( EXTRA_POSTED_ACCT, target_account.acct );
+ data.putExtra( EXTRA_POSTED_STATUS_ID, status.id );
- client.setAccount( target_account );
- String post_content = sb.toString();
- String digest = Utils.digestSHA256( post_content + target_account.acct );
-
- Request.Builder request_builder = new Request.Builder()
- .post( RequestBody.create(
- TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED
- , post_content
- ) );
-
- if( ! pref.getBoolean( Pref.KEY_DONT_DUPLICATION_CHECK, false ) ){
- request_builder.header( "Idempotency-Key", digest );
- }
-
- TootApiResult result = client.request( "/api/v1/statuses", request_builder );
- if( result != null && result.object != null ){
- status = TootStatus.parse( log, account, account.host, result.object );
-
- Spannable s = status.decoded_content;
- MyClickableSpan[] span_list = s.getSpans( 0, s.length(), MyClickableSpan.class );
- if( span_list != null ){
- ArrayList< String > tag_list = new ArrayList<>();
- for( MyClickableSpan span : span_list ){
- int start = s.getSpanStart( span );
- int end = s.getSpanEnd( span );
- String text = s.subSequence( start, end ).toString();
- if( text.startsWith( "#" ) ){
- tag_list.add( text.substring( 1 ) );
- }
- }
- int count = tag_list.size();
- if( count > 0 ){
- TagSet.saveList(
- System.currentTimeMillis()
- , tag_list.toArray( new String[ count ] )
- , 0
- , count
- );
- }
-
- }
-
- }
- return result;
-
- }
-
- @Override
- protected void onCancelled(){
- onPostExecute( null );
- }
-
- @Override
- protected void onPostExecute( TootApiResult result ){
- try{
- progress.dismiss();
- }catch( Throwable ignored ){
- // java.lang.IllegalArgumentException:
- // at android.view.WindowManagerGlobal.findViewLocked(WindowManagerGlobal.java:396)
- // at android.view.WindowManagerGlobal.removeView(WindowManagerGlobal.java:322)
- // at android.view.WindowManagerImpl.removeViewImmediate(WindowManagerImpl.java:116)
- // at android.app.Dialog.dismissDialog(Dialog.java:341)
- // at android.app.Dialog.dismiss(Dialog.java:324)
- // at jp.juggler.subwaytooter.ActMain$10$1.onPostExecute(ActMain.java:867)
- // at jp.juggler.subwaytooter.ActMain$10$1.onPostExecute(ActMain.java:837)
- }
-
- //noinspection StatementWithEmptyBody
- if( result == null ){
- // cancelled.
- }else if( status != null ){
- // 連投してIdempotency が同じだった場合もエラーにはならず、ここを通る
- Intent data = new Intent();
- data.putExtra( EXTRA_POSTED_ACCT, target_account.acct );
- data.putExtra( EXTRA_POSTED_STATUS_ID, status.id );
-
- setResult( RESULT_OK, data );
- isPostComplete = true;
- ActPost.this.finish();
- }else{
- Utils.showToast( ActPost.this, true, result.error );
- }
- }
- };
-
- progress.setIndeterminate( true );
- progress.setCancelable( true );
- progress.setOnCancelListener( new DialogInterface.OnCancelListener() {
- @Override
- public void onCancel( DialogInterface dialog ){
- task.cancel( true );
+ setResult( RESULT_OK, data );
+ isPostComplete = true;
+ ActPost.this.finish();
}
} );
- progress.show();
- task.executeOnExecutor( App1.task_executor );
}
/////////////////////////////////////////////////
diff --git a/app/src/main/java/jp/juggler/subwaytooter/AppDataExporter.java b/app/src/main/java/jp/juggler/subwaytooter/AppDataExporter.java
index 00087280..af7c2065 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/AppDataExporter.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/AppDataExporter.java
@@ -298,6 +298,7 @@ public class AppDataExporter {
case Pref.KEY_PRIOR_CHROME:
case Pref.KEY_POST_BUTTON_BAR_AT_TOP:
case Pref.KEY_DONT_DUPLICATION_CHECK:
+ case Pref.KEY_QUICK_TOOT_BAR:
boolean bv = reader.nextBoolean();
e.putBoolean( k, bv );
break;
diff --git a/app/src/main/java/jp/juggler/subwaytooter/Pref.java b/app/src/main/java/jp/juggler/subwaytooter/Pref.java
index 5281271d..51b920ed 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/Pref.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/Pref.java
@@ -67,6 +67,8 @@ public class Pref {
public static final String KEY_ACCT_FONT_SIZE = "acct_font_size";
public static final String KEY_DONT_DUPLICATION_CHECK = "dont_duplication_check";
+ public static final String KEY_QUICK_TOOT_BAR = "quick_toot_bar";
+
// 項目を追加したらAppDataExporter#importPref のswitch文も更新すること
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/PopupAutoCompleteAcct.java b/app/src/main/java/jp/juggler/subwaytooter/util/PopupAutoCompleteAcct.java
similarity index 68%
rename from app/src/main/java/jp/juggler/subwaytooter/PopupAutoCompleteAcct.java
rename to app/src/main/java/jp/juggler/subwaytooter/util/PopupAutoCompleteAcct.java
index 1d78f190..3e1c16f0 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/PopupAutoCompleteAcct.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/util/PopupAutoCompleteAcct.java
@@ -1,4 +1,4 @@
-package jp.juggler.subwaytooter;
+package jp.juggler.subwaytooter.util;
import android.annotation.SuppressLint;
import android.app.Activity;
@@ -6,7 +6,6 @@ import android.support.v4.content.ContextCompat;
import android.text.Layout;
import android.view.Gravity;
import android.view.View;
-import android.view.WindowManager;
import android.widget.CheckedTextView;
import android.widget.EditText;
import android.widget.LinearLayout;
@@ -14,6 +13,9 @@ import android.widget.PopupWindow;
import java.util.ArrayList;
+import jp.juggler.subwaytooter.R;
+import jp.juggler.subwaytooter.Styler;
+
class PopupAutoCompleteAcct {
final Activity activity;
private final EditText etContent;
@@ -22,9 +24,11 @@ class PopupAutoCompleteAcct {
private final View formRoot;
private final float density;
private final int popup_width;
-
+
private int popup_rows;
+ private boolean bMainScreen;
+
void dismiss(){
acct_popup.dismiss();
}
@@ -33,13 +37,14 @@ class PopupAutoCompleteAcct {
return acct_popup.isShowing();
}
- PopupAutoCompleteAcct( Activity activity, EditText etContent, View formRoot ){
+ PopupAutoCompleteAcct( Activity activity, EditText etContent, View formRoot, boolean bMainScreen ){
this.activity = activity;
this.etContent = etContent;
this.formRoot = formRoot;
+ this.bMainScreen = bMainScreen;
this.density = activity.getResources().getDisplayMetrics().density;
- popup_width = (int)(0.5f +240f * density );
+ popup_width = (int) ( 0.5f + 240f * density );
@SuppressLint("InflateParams") View viewRoot =
activity.getLayoutInflater().inflate( R.layout.acct_complete_popup, null, false );
@@ -102,26 +107,40 @@ class PopupAutoCompleteAcct {
etContent.getLocationOnScreen( location );
int text_top = location[ 1 ];
- formRoot.getLocationOnScreen( location );
- int form_top = location[ 1 ];
- int form_bottom = location[ 1 ] + formRoot.getHeight();
+ int popup_top;
+ int popup_height;
- Layout layout = etContent.getLayout();
-
- int popup_top = text_top
- + etContent.getTotalPaddingTop()
- + layout.getLineBottom( layout.getLineCount() - 1 )
- - etContent.getScrollY();
-
- if( popup_top < form_top ) popup_top = form_top;
-
- int popup_height = form_bottom - popup_top;
-
- int min = (int) ( 0.5f + 48f * 2f * density );
- if( popup_height < min ) popup_height = min;
-
- int max = (int) ( 0.5f + 48f * popup_rows * density );
- if( popup_height > max ) popup_height = max;
+ if( bMainScreen ){
+ int popup_bottom = text_top + etContent.getTotalPaddingTop() - etContent.getScrollY();
+ int max = popup_bottom-(int) ( 0.5f + 48f * 1f * density );
+ int min = (int) ( 0.5f + 48f * 2f * density );
+ popup_height = (int) ( 0.5f + 48f * popup_rows * density );
+ if( popup_height < min ) popup_height = min;
+ if( popup_height > max ) popup_height = max;
+ popup_top = popup_bottom - popup_height;
+
+ }else{
+ formRoot.getLocationOnScreen( location );
+ int form_top = location[ 1 ];
+ int form_bottom = location[ 1 ] + formRoot.getHeight();
+
+ Layout layout = etContent.getLayout();
+
+ popup_top = text_top
+ + etContent.getTotalPaddingTop()
+ + layout.getLineBottom( layout.getLineCount() - 1 )
+ - etContent.getScrollY();
+
+ if( popup_top < form_top ) popup_top = form_top;
+
+ popup_height = form_bottom - popup_top;
+
+ int min = (int) ( 0.5f + 48f * 2f * density );
+ int max = (int) ( 0.5f + 48f * popup_rows * density );
+
+ if( popup_height < min ) popup_height = min;
+ if( popup_height > max ) popup_height = max;
+ }
if( acct_popup.isShowing() ){
acct_popup.update( 0, popup_top, popup_width, popup_height );
diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.java b/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.java
new file mode 100644
index 00000000..f2ee11ac
--- /dev/null
+++ b/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.java
@@ -0,0 +1,433 @@
+package jp.juggler.subwaytooter.util;
+
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
+import android.text.Editable;
+import android.text.Spannable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import jp.juggler.subwaytooter.App1;
+import jp.juggler.subwaytooter.Pref;
+import jp.juggler.subwaytooter.R;
+import jp.juggler.subwaytooter.api.TootApiClient;
+import jp.juggler.subwaytooter.api.TootApiResult;
+import jp.juggler.subwaytooter.api.entity.TootStatus;
+import jp.juggler.subwaytooter.dialog.DlgConfirm;
+import jp.juggler.subwaytooter.table.AcctColor;
+import jp.juggler.subwaytooter.table.AcctSet;
+import jp.juggler.subwaytooter.table.SavedAccount;
+import jp.juggler.subwaytooter.table.TagSet;
+import jp.juggler.subwaytooter.view.MyEditText;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+
+
+public class PostHelper {
+ private static final LogCategory log = new LogCategory( "PostHelper" );
+
+
+ public interface Callback{
+ void onPostComplete(SavedAccount target_account,TootStatus status);
+ }
+
+ private final AppCompatActivity activity;
+ private final SharedPreferences pref;
+ private final Handler handler;
+
+ public PostHelper( AppCompatActivity activity ,SharedPreferences pref,Handler handler ){
+ this.activity = activity;
+ this.pref = pref;
+ this.handler = handler;
+ }
+
+ // [:word:] 単語構成文字 (Letter | Mark | Decimal_Number | Connector_Punctuation)
+ // [:alpha:] 英字 (Letter | Mark)
+
+ private static final String word = "[_\\p{L}\\p{M}\\p{Nd}\\p{Pc}]";
+ private static final String alpha = "[_\\p{L}\\p{M}]";
+
+ private static final Pattern reTag = Pattern.compile(
+ "(?:^|[^/)\\w])#(" + word + "*" + alpha + word + "*)"
+ , Pattern.CASE_INSENSITIVE
+ );
+
+ public String content;
+ public String spoiler_text;
+ public String visibility;
+ public boolean bNSFW;
+ public long in_reply_to_id;
+ public ArrayList< PostAttachment > attachment_list;
+
+ public void post( final SavedAccount account,final boolean bConfirmTag, final boolean bConfirmAccount ,final Callback callback){
+ if( TextUtils.isEmpty( content ) ){
+ Utils.showToast( activity, true, R.string.post_error_contents_empty );
+ return;
+ }
+
+ if( spoiler_text != null && spoiler_text.isEmpty() ){
+ Utils.showToast( activity, true, R.string.post_error_contents_warning_empty );
+ return;
+ }
+
+ if( ! bConfirmAccount ){
+ DlgConfirm.open( activity
+ , activity.getString( R.string.confirm_post_from, AcctColor.getNickname( account.acct ) )
+ , new DlgConfirm.Callback() {
+ @Override public boolean isConfirmEnabled(){
+ return account.confirm_post;
+ }
+
+ @Override public void setConfirmEnabled( boolean bv ){
+ account.confirm_post = bv;
+ account.saveSetting();
+ }
+
+ @Override public void onOK(){
+ post( account,bConfirmTag, true ,callback);
+ }
+ } );
+ return;
+ }
+
+ if( ! bConfirmTag ){
+ Matcher m = reTag.matcher( content );
+ if( m.find() && ! TootStatus.VISIBILITY_PUBLIC.equals( visibility ) ){
+ new AlertDialog.Builder( activity )
+ .setCancelable( true )
+ .setMessage( R.string.hashtag_and_visibility_not_match )
+ .setNegativeButton( R.string.cancel, null )
+ .setPositiveButton( R.string.ok, new DialogInterface.OnClickListener() {
+ @Override public void onClick( DialogInterface dialog, int which ){
+ //noinspection ConstantConditions
+ post( account,true, bConfirmAccount ,callback);
+ }
+ } )
+ .show();
+ return;
+ }
+ }
+
+ final StringBuilder sb = new StringBuilder();
+
+ sb.append( "status=" );
+ sb.append( Uri.encode( content ) );
+
+ sb.append( "&visibility=" );
+ sb.append( Uri.encode( visibility ) );
+
+ if( bNSFW ){
+ sb.append( "&sensitive=1" );
+ }
+
+ if( spoiler_text != null ){
+ sb.append( "&spoiler_text=" );
+ sb.append( Uri.encode( spoiler_text ) );
+ }
+
+ if( in_reply_to_id != - 1L ){
+ sb.append( "&in_reply_to_id=" );
+ sb.append( Long.toString( in_reply_to_id ) );
+ }
+
+ if( attachment_list != null ){
+ for( PostAttachment pa : attachment_list ){
+ if( pa.attachment != null ){
+ sb.append( "&media_ids[]=" ).append( pa.attachment.id );
+ }
+ }
+ }
+
+ final ProgressDialog progress = new ProgressDialog( activity );
+
+ final AsyncTask< Void, Void, TootApiResult > task = new AsyncTask< Void, Void, TootApiResult >() {
+ final SavedAccount target_account = account;
+
+ TootStatus status;
+
+ @Override protected TootApiResult doInBackground( Void... params ){
+ TootApiClient client = new TootApiClient( activity, new TootApiClient.Callback() {
+ @Override public boolean isApiCancelled(){
+ return isCancelled();
+ }
+
+ @Override public void publishApiProgress( final String s ){
+ Utils.runOnMainThread( new Runnable() {
+ @Override public void run(){
+ progress.setMessage( s );
+ }
+ } );
+ }
+ } );
+
+ client.setAccount( target_account );
+ String post_content = sb.toString();
+ String digest = Utils.digestSHA256( post_content + target_account.acct );
+
+ Request.Builder request_builder = new Request.Builder()
+ .post( RequestBody.create(
+ TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED
+ , post_content
+ ) );
+
+ if( ! pref.getBoolean( Pref.KEY_DONT_DUPLICATION_CHECK, false ) ){
+ request_builder.header( "Idempotency-Key", digest );
+ }
+
+ TootApiResult result = client.request( "/api/v1/statuses", request_builder );
+ if( result != null && result.object != null ){
+ status = TootStatus.parse( log, account, account.host, result.object );
+
+ Spannable s = status.decoded_content;
+ MyClickableSpan[] span_list = s.getSpans( 0, s.length(), MyClickableSpan.class );
+ if( span_list != null ){
+ ArrayList< String > tag_list = new ArrayList<>();
+ for( MyClickableSpan span : span_list ){
+ int start = s.getSpanStart( span );
+ int end = s.getSpanEnd( span );
+ String text = s.subSequence( start, end ).toString();
+ if( text.startsWith( "#" ) ){
+ tag_list.add( text.substring( 1 ) );
+ }
+ }
+ int count = tag_list.size();
+ if( count > 0 ){
+ TagSet.saveList(
+ System.currentTimeMillis()
+ , tag_list.toArray( new String[ count ] )
+ , 0
+ , count
+ );
+ }
+
+ }
+
+ }
+ return result;
+
+ }
+
+ @Override
+ protected void onCancelled(){
+ onPostExecute( null );
+ }
+
+ @Override
+ protected void onPostExecute( TootApiResult result ){
+ try{
+ progress.dismiss();
+ }catch( Throwable ignored ){
+ // java.lang.IllegalArgumentException:
+ // at android.view.WindowManagerGlobal.findViewLocked(WindowManagerGlobal.java:396)
+ // at android.view.WindowManagerGlobal.removeView(WindowManagerGlobal.java:322)
+ // at android.view.WindowManagerImpl.removeViewImmediate(WindowManagerImpl.java:116)
+ // at android.app.Dialog.dismissDialog(Dialog.java:341)
+ // at android.app.Dialog.dismiss(Dialog.java:324)
+ // at jp.juggler.subwaytooter.ActMain$10$1.onPostExecute(ActMain.java:867)
+ // at jp.juggler.subwaytooter.ActMain$10$1.onPostExecute(ActMain.java:837)
+ }
+
+ //noinspection StatementWithEmptyBody
+ if( result == null ){
+ // cancelled.
+ }else if( status != null ){
+ // 連投してIdempotency が同じだった場合もエラーにはならず、ここを通る
+ callback.onPostComplete( target_account,status );
+ }else{
+ Utils.showToast( activity, true, result.error );
+ }
+ }
+ };
+
+ progress.setIndeterminate( true );
+ progress.setCancelable( true );
+ progress.setOnCancelListener( new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel( DialogInterface dialog ){
+ task.cancel( true );
+ }
+ } );
+ progress.show();
+ task.executeOnExecutor( App1.task_executor );
+ }
+
+ public interface Callback2{
+ void onTextUpdate();
+ }
+
+ private Callback2 callback2;
+
+ private MyEditText et;
+ private PopupAutoCompleteAcct popup;
+ private View formRoot;
+ private boolean bMainScreen;
+
+ public void closeAcctPopup(){
+ if( popup != null ){
+ popup.dismiss();
+ popup = null;
+ }
+ }
+
+ public void onScrollChanged(){
+ if( popup != null && popup.isShowing() ){
+ popup.updatePosition();
+ }
+ }
+
+ public void onDestroy(){
+ handler.removeCallbacks( proc_text_changed );
+ closeAcctPopup();
+ }
+
+
+
+ public void attachEditText( View _formRoot, MyEditText _et, boolean bMainScreen, Callback2 _callback2){
+ this.formRoot = _formRoot;
+ this.et = _et;
+ this.callback2 = _callback2;
+ this.bMainScreen = bMainScreen;
+
+ et.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 ){
+ handler.removeCallbacks( proc_text_changed );
+ handler.postDelayed( proc_text_changed, ( popup != null && popup.isShowing() ? 100L : 1000L ) );
+ }
+
+ @Override
+ public void afterTextChanged( Editable s ){
+ callback2.onTextUpdate();
+ }
+ } );
+
+ et.setOnSelectionChangeListener( new MyEditText.OnSelectionChangeListener() {
+ @Override public void onSelectionChanged( int selStart, int selEnd ){
+ if( selStart != selEnd ){
+ // 範囲選択されてるならポップアップは閉じる
+ log.d( "onSelectionChanged: range selected" );
+ closeAcctPopup();
+ }
+ }
+ } );
+ }
+
+
+
+
+ private static final Pattern reCharsNotTag = Pattern.compile( "[\\s\\-+.,:;/]" );
+
+ private final Runnable proc_text_changed = new Runnable() {
+ @Override public void run(){
+ int start = et.getSelectionStart();
+ int end = et.getSelectionEnd();
+ if( start != end ){
+ closeAcctPopup();
+ return;
+ }
+ String src = et.getText().toString();
+ int count_atMark = 0;
+ int[] pos_atMark = new int[ 2 ];
+ for( ; ; ){
+ if( count_atMark >= 2 ) break;
+
+ if( start == 0 ) break;
+ char c = src.charAt( start - 1 );
+
+ if( c == '@' ){
+ -- start;
+ pos_atMark[ count_atMark++ ] = start;
+ continue;
+ }else if( ( '0' <= c && c <= '9' )
+ || ( 'A' <= c && c <= 'Z' )
+ || ( 'a' <= c && c <= 'z' )
+ || c == '_' || c == '-' || c == '.'
+ ){
+ -- start;
+ continue;
+ }
+ // その他の文字種が出たら探索打ち切り
+ break;
+ }
+ // 登場した@の数
+ if( count_atMark == 0 ){
+ // 次はAcctじゃなくてHashtagの補完を試みる
+ checkTag();
+ return;
+ }else if( count_atMark == 1 ){
+ start = pos_atMark[ 0 ];
+ }else if( count_atMark == 2 ){
+ start = pos_atMark[ 1 ];
+ }
+ // 最低でも2文字ないと補完しない
+ if( end - start < 2 ){
+ closeAcctPopup();
+ return;
+ }
+ int limit = 100;
+ 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() ){
+ closeAcctPopup();
+ }else{
+ if( popup == null || ! popup.isShowing() ){
+ popup = new PopupAutoCompleteAcct( activity, et, formRoot,bMainScreen );
+ }
+ popup.setList( acct_list, start, end );
+ }
+ }
+
+ private void checkTag(){
+ int end = et.getSelectionEnd();
+
+ String src = et.getText().toString();
+ int last_sharp = src.lastIndexOf( '#', end - 1 );
+
+ if( last_sharp == - 1 || end - last_sharp < 3 ){
+ closeAcctPopup();
+ return;
+ }
+
+ String part = src.substring( last_sharp + 1, end );
+ if( reCharsNotTag.matcher( part ).find() ){
+ closeAcctPopup();
+ return;
+ }
+
+ int limit = 100;
+ String s = src.substring( last_sharp + 1, end );
+ ArrayList< String > tag_list = TagSet.searchPrefix( s, limit );
+ log.d( "search for %s, result=%d", s, tag_list.size() );
+ if( tag_list.isEmpty() ){
+ closeAcctPopup();
+ }else{
+ if( popup == null || ! popup.isShowing() ){
+ popup = new PopupAutoCompleteAcct( activity, et, formRoot ,bMainScreen);
+ }
+ popup.setList( tag_list, last_sharp, end );
+ }
+ }
+ };
+
+
+
+
+
+}
diff --git a/app/src/main/res/layout/act_app_setting.xml b/app/src/main/res/layout/act_app_setting.xml
index bb16f0df..eea1ea5d 100644
--- a/app/src/main/res/layout/act_app_setting.xml
+++ b/app/src/main/res/layout/act_app_setting.xml
@@ -287,6 +287,8 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/act_main.xml b/app/src/main/res/layout/act_main.xml
index 0abd599f..c21d65db 100644
--- a/app/src/main/res/layout/act_main.xml
+++ b/app/src/main/res/layout/act_main.xml
@@ -16,6 +16,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
+ android:id="@+id/llFormRoot"
>
+
+ android:layout_height="match_parent"
+ />
+
+
+
+
+
+
+
+
-
-
@@ -133,7 +166,6 @@
-
@@ -148,6 +180,7 @@
android:layout_gravity="start"
android:fitsSystemWindows="true"
- app:menu="@menu/menu_navi_drawer"/>
+ app:menu="@menu/menu_navi_drawer"
+ />
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index f2ba8d49..ee156c46 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -388,6 +388,8 @@
Timeline font size (unit:sp. leave empty to default. app restart required)
Acct font size (unit:sp. leave empty to default. app restart required)
Don\'t add duplication check header
+ Show \"Quick Toot\" bar (app restart required.)
+ Quick Toot
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 1fcf1702..207015da 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -675,5 +675,7 @@
タイムラインのフォントサイズ(単位:sp。空欄でデフォルト。アプリ再起動が必要)
Acctのフォントサイズ(単位:sp。空欄でデフォルト。アプリ再起動が必要)
重複チェックヘッダを付与しない
+ 簡易投稿入力
+ 簡易投稿バーを表示(アプリ再起動が必要)
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 447a042e..12124ada 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -383,5 +383,7 @@
Timeline font size (unit:sp. leave empty to default. app restart required)
Acct font size (unit:sp. leave empty to default. app restart required)
Don\'t add duplication check header
+ Show \"Quick Toot\" bar (app restart required.)
+ Quick Toot