From 4ba7cd321aa1379578aac4ebcd767bea501a4fb2 Mon Sep 17 00:00:00 2001 From: tateisu Date: Sun, 23 Jul 2017 18:49:51 +0900 Subject: [PATCH] =?UTF-8?q?v1.0.2=20-=20=E3=82=A2=E3=83=97=E3=83=AA?= =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E3=81=AB=E3=80=8C=E6=8A=95=E7=A8=BF/?= =?UTF-8?q?=E7=B0=A1=E6=98=93=E6=8A=95=E7=A8=BF=E3=83=90=E3=83=BC=E3=82=92?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E3=80=8D=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 4 +- .../juggler/subwaytooter/ActAppSetting.java | 8 +- .../java/jp/juggler/subwaytooter/ActMain.java | 128 +++++- .../java/jp/juggler/subwaytooter/ActPost.java | 375 ++------------- .../juggler/subwaytooter/AppDataExporter.java | 1 + .../java/jp/juggler/subwaytooter/Pref.java | 2 + .../{ => util}/PopupAutoCompleteAcct.java | 67 ++- .../juggler/subwaytooter/util/PostHelper.java | 433 ++++++++++++++++++ app/src/main/res/layout/act_app_setting.xml | 18 + app/src/main/res/layout/act_main.xml | 55 ++- app/src/main/res/values-fr/strings.xml | 2 + app/src/main/res/values-ja/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 13 files changed, 699 insertions(+), 398 deletions(-) rename app/src/main/java/jp/juggler/subwaytooter/{ => util}/PopupAutoCompleteAcct.java (68%) create mode 100644 app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.java 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