parent
aac510aca5
commit
4ba7cd321a
|
@ -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"
|
||||
}
|
||||
|
||||
|
|
|
@ -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() )
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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文も更新すること
|
||||
}
|
||||
|
|
|
@ -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 );
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -287,6 +287,8 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
|
@ -304,6 +306,22 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/show_quick_toot_bar"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Switch
|
||||
android:id="@+id/swQuickTootBar"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:gravity="center"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/llFormRoot"
|
||||
>
|
||||
|
||||
<FrameLayout
|
||||
|
@ -46,10 +47,12 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
/>
|
||||
|
||||
<jp.juggler.subwaytooter.view.MyRecyclerView
|
||||
android:id="@+id/rvPager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
android:layout_height="match_parent"
|
||||
/>
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
|
@ -63,28 +66,28 @@
|
|||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@drawable/btn_bg_ddd"
|
||||
app:srcCompat="?attr/ic_hamburger"
|
||||
android:contentDescription="@string/menu"
|
||||
app:srcCompat="?attr/ic_hamburger"
|
||||
/>
|
||||
|
||||
<View
|
||||
android:id="@+id/vFooterDivider1"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/colorImageButton"
|
||||
android:id="@+id/vFooterDivider1"
|
||||
/>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/svColumnStrip"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="?attr/colorColumnStripBackground"
|
||||
android:cacheColorHint="#00000000"
|
||||
android:fadingEdge="horizontal"
|
||||
android:fadingEdgeLength="20dp"
|
||||
android:fillViewport="true"
|
||||
android:scrollbars="none"
|
||||
android:background="?attr/colorColumnStripBackground"
|
||||
android:id="@+id/svColumnStrip"
|
||||
>
|
||||
|
||||
<jp.juggler.subwaytooter.view.ColumnStripLinearLayout
|
||||
|
@ -96,23 +99,53 @@
|
|||
</HorizontalScrollView>
|
||||
|
||||
<View
|
||||
android:id="@+id/vFooterDivider2"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/colorImageButton"
|
||||
android:id="@+id/vFooterDivider2"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnToot"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@drawable/btn_bg_ddd"
|
||||
app:srcCompat="?attr/ic_edit"
|
||||
android:contentDescription="@string/toot"
|
||||
app:srcCompat="?attr/ic_edit"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llQuickTootBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
|
||||
<jp.juggler.subwaytooter.view.MyEditText
|
||||
android:id="@+id/etQuickToot"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_weight="1"
|
||||
android:inputType="text"
|
||||
android:maxLines="1"
|
||||
android:hint="@string/quick_toot_hint"
|
||||
android:imeOptions="actionSend"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnQuickToot"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@drawable/btn_bg_ddd"
|
||||
android:contentDescription="@string/post"
|
||||
app:srcCompat="?attr/btn_post"
|
||||
/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<!--<android.support.design.widget.FloatingActionButton-->
|
||||
<!--android:id="@+id/"-->
|
||||
<!--android:layout_width="wrap_content"-->
|
||||
|
@ -133,7 +166,6 @@
|
|||
<!--app:srcCompat="?attr/ic_menu"-->
|
||||
<!--/>-->
|
||||
|
||||
|
||||
<!--<android.support.design.widget.CoordinatorLayout-->
|
||||
<!--android:layout_width="match_parent"-->
|
||||
<!--android:layout_height="match_parent"-->
|
||||
|
@ -148,6 +180,7 @@
|
|||
android:layout_gravity="start"
|
||||
android:fitsSystemWindows="true"
|
||||
|
||||
app:menu="@menu/menu_navi_drawer"/>
|
||||
app:menu="@menu/menu_navi_drawer"
|
||||
/>
|
||||
<!-- app:headerLayout="@layout/nav_header_act_main" -->
|
||||
</android.support.v4.widget.DrawerLayout>
|
||||
|
|
|
@ -388,6 +388,8 @@
|
|||
<string name="timeline_font_size">Timeline font size (unit:sp. leave empty to default. app restart required)</string>
|
||||
<string name="acct_font_size">Acct font size (unit:sp. leave empty to default. app restart required)</string>
|
||||
<string name="dont_add_duplication_check_header">Don\'t add duplication check header</string>
|
||||
<string name="show_quick_toot_bar">Show \"Quick Toot\" bar (app restart required.)</string>
|
||||
<string name="quick_toot_hint">Quick Toot</string>
|
||||
|
||||
<!--<string name="abc_action_bar_home_description">Revenir à l\'accueil</string>-->
|
||||
<!--<string name="abc_action_bar_home_description_format">%1$s, %2$s</string>-->
|
||||
|
|
|
@ -675,5 +675,7 @@
|
|||
<string name="timeline_font_size">タイムラインのフォントサイズ(単位:sp。空欄でデフォルト。アプリ再起動が必要)</string>
|
||||
<string name="acct_font_size">Acctのフォントサイズ(単位:sp。空欄でデフォルト。アプリ再起動が必要)</string>
|
||||
<string name="dont_add_duplication_check_header">重複チェックヘッダを付与しない</string>
|
||||
<string name="quick_toot_hint">簡易投稿入力</string>
|
||||
<string name="show_quick_toot_bar">簡易投稿バーを表示(アプリ再起動が必要)</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -383,5 +383,7 @@
|
|||
<string name="timeline_font_size">Timeline font size (unit:sp. leave empty to default. app restart required)</string>
|
||||
<string name="acct_font_size">Acct font size (unit:sp. leave empty to default. app restart required)</string>
|
||||
<string name="dont_add_duplication_check_header">Don\'t add duplication check header</string>
|
||||
<string name="show_quick_toot_bar">Show \"Quick Toot\" bar (app restart required.)</string>
|
||||
<string name="quick_toot_hint">Quick Toot</string>
|
||||
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue