v0.4.1
This commit is contained in:
parent
23c5c9d572
commit
492ec81fb4
|
@ -9,8 +9,8 @@ android {
|
|||
applicationId "jp.juggler.subwaytooter"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 25
|
||||
versionCode 40
|
||||
versionName "0.4.0"
|
||||
versionCode 41
|
||||
versionName "0.4.1"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
|
|
|
@ -131,7 +131,10 @@
|
|||
android:name=".ActMutedApp"
|
||||
android:label="@string/muted_app"
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".ActMutedWord"
|
||||
android:label="@string/muted_word"
|
||||
/>
|
||||
<activity
|
||||
android:name=".ActColumnCustomize"
|
||||
android:label="@string/color_and_background"
|
||||
|
@ -142,7 +145,11 @@
|
|||
android:label="@string/nickname_and_color"
|
||||
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".ActText"
|
||||
android:label="@string/select_and_copy"
|
||||
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
|
||||
/>
|
||||
<meta-data
|
||||
android:name="android.max_aspect"
|
||||
android:value="ratio_float"/>
|
||||
|
|
|
@ -12,6 +12,7 @@ import android.os.Bundle;
|
|||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.customtabs.CustomTabsIntent;
|
||||
import android.support.v4.text.BidiFormatter;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
|
@ -454,6 +455,10 @@ public class ActMain extends AppCompatActivity
|
|||
|
||||
}else if( id == R.id.nav_muted_app ){
|
||||
startActivity( new Intent( this, ActMutedApp.class ) );
|
||||
|
||||
}else if( id == R.id.nav_muted_word ){
|
||||
startActivity( new Intent( this, ActMutedWord.class ) );
|
||||
|
||||
// Handle the camera action
|
||||
// }else if( id == R.id.nav_gallery ){
|
||||
//
|
||||
|
@ -1630,8 +1635,14 @@ public class ActMain extends AppCompatActivity
|
|||
);
|
||||
return;
|
||||
}else if( bFollow ){
|
||||
BidiFormatter bidiFormatter = BidiFormatter.getInstance();
|
||||
String msg = getString( R.string.confirm_follow_who_from
|
||||
, bidiFormatter.unicodeWrap(who.display_name )
|
||||
, AcctColor.getNickname( access_info.acct )
|
||||
);
|
||||
|
||||
DlgConfirm.open( this
|
||||
, getString( R.string.confirm_follow_who_from, who.display_name, AcctColor.getNickname( access_info.acct ) )
|
||||
, msg
|
||||
, new DlgConfirm.Callback() {
|
||||
@Override public boolean isConfirmEnabled(){
|
||||
return access_info.confirm_follow;
|
||||
|
@ -2212,47 +2223,6 @@ public class ActMain extends AppCompatActivity
|
|||
|
||||
////////////////////////////////////////////////
|
||||
|
||||
void sendStatus( SavedAccount access_info, TootStatus status ){
|
||||
try{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append( getString( R.string.send_header_url ) );
|
||||
sb.append( ": " );
|
||||
sb.append( status.url );
|
||||
sb.append( "\n" );
|
||||
sb.append( getString( R.string.send_header_date ) );
|
||||
sb.append( ": " );
|
||||
sb.append( TootStatus.formatTime( status.time_created_at ) );
|
||||
sb.append( "\n" );
|
||||
sb.append( getString( R.string.send_header_from_acct ) );
|
||||
sb.append( ": " );
|
||||
sb.append( access_info.getFullAcct( status.account ) );
|
||||
sb.append( "\n" );
|
||||
sb.append( getString( R.string.send_header_from_name ) );
|
||||
sb.append( ": " );
|
||||
sb.append( status.account.display_name );
|
||||
sb.append( "\n" );
|
||||
if( ! TextUtils.isEmpty( status.spoiler_text ) ){
|
||||
sb.append( getString( R.string.send_header_content_warning ) );
|
||||
sb.append( ": " );
|
||||
sb.append( HTMLDecoder.decodeHTMLForClipboard( access_info, status.spoiler_text ) );
|
||||
sb.append( "\n" );
|
||||
}
|
||||
sb.append( "\n" );
|
||||
sb.append( HTMLDecoder.decodeHTMLForClipboard( access_info, status.content ) );
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setAction( Intent.ACTION_SEND );
|
||||
intent.setType( "text/plain" );
|
||||
intent.putExtra( Intent.EXTRA_TEXT, sb.toString() );
|
||||
startActivity( intent );
|
||||
|
||||
}catch( Throwable ex ){
|
||||
log.e( ex, "sendStatus failed." );
|
||||
ex.printStackTrace();
|
||||
Utils.showToast( this, ex, "sendStatus failed." );
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////
|
||||
|
||||
final RelationChangedCallback follow_complete_callback = new RelationChangedCallback() {
|
||||
|
|
|
@ -156,6 +156,7 @@ public class ActMutedApp extends AppCompatActivity {
|
|||
}
|
||||
|
||||
void bind( MyItem item ){
|
||||
itemView.setTag( item ); // itemView は親クラスのメンバ変数
|
||||
tvName.setText( item.name );
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
package jp.juggler.subwaytooter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.woxthebox.draglistview.DragItem;
|
||||
import com.woxthebox.draglistview.DragItemAdapter;
|
||||
import com.woxthebox.draglistview.DragListView;
|
||||
import com.woxthebox.draglistview.swipe.ListSwipeHelper;
|
||||
import com.woxthebox.draglistview.swipe.ListSwipeItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import jp.juggler.subwaytooter.table.MutedWord;
|
||||
|
||||
public class ActMutedWord extends AppCompatActivity {
|
||||
|
||||
DragListView listView;
|
||||
MyListAdapter listAdapter;
|
||||
|
||||
@Override
|
||||
protected void onCreate( @Nullable Bundle savedInstanceState ){
|
||||
super.onCreate( savedInstanceState );
|
||||
App1.setActivityTheme(this,false);
|
||||
initUI();
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override public void onBackPressed(){
|
||||
setResult( RESULT_OK );
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
private void initUI(){
|
||||
setContentView( R.layout.act_mute_app );
|
||||
|
||||
// リストのアダプター
|
||||
listAdapter = new MyListAdapter();
|
||||
|
||||
// ハンドル部分をドラッグで並べ替えできるRecyclerView
|
||||
listView = (DragListView) findViewById( R.id.drag_list_view );
|
||||
listView.setLayoutManager( new LinearLayoutManager( this ) );
|
||||
listView.setAdapter( listAdapter, false );
|
||||
|
||||
listView.setCanDragHorizontally( true );
|
||||
listView.setDragEnabled( false );
|
||||
listView.setCustomDragItem( new MyDragItem( this, R.layout.lv_mute_app ) );
|
||||
|
||||
listView.getRecyclerView().setVerticalScrollBarEnabled( true );
|
||||
// listView.setDragListListener( new DragListView.DragListListenerAdapter() {
|
||||
// @Override
|
||||
// public void onItemDragStarted( int position ){
|
||||
// // 操作中はリフレッシュ禁止
|
||||
// // mRefreshLayout.setEnabled( false );
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void onItemDragEnded( int fromPosition, int toPosition ){
|
||||
// // 操作完了でリフレッシュ許可
|
||||
// // mRefreshLayout.setEnabled( USE_SWIPE_REFRESH );
|
||||
//
|
||||
//// if( fromPosition != toPosition ){
|
||||
//// // 並べ替えが発生した
|
||||
//// }
|
||||
// }
|
||||
// } );
|
||||
|
||||
// リストを左右スワイプした
|
||||
listView.setSwipeListener( new ListSwipeHelper.OnSwipeListenerAdapter() {
|
||||
|
||||
@Override
|
||||
public void onItemSwipeStarted( ListSwipeItem item ){
|
||||
// 操作中はリフレッシュ禁止
|
||||
// mRefreshLayout.setEnabled( false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemSwipeEnded( ListSwipeItem item, ListSwipeItem.SwipeDirection swipedDirection ){
|
||||
// 操作完了でリフレッシュ許可
|
||||
// mRefreshLayout.setEnabled( USE_SWIPE_REFRESH );
|
||||
|
||||
// 左にスワイプした(右端に青が見えた) なら要素を削除する
|
||||
if( swipedDirection == ListSwipeItem.SwipeDirection.LEFT ){
|
||||
Object o = item.getTag();
|
||||
if( o instanceof MyItem){
|
||||
MyItem adapterItem = ( MyItem ) o;
|
||||
MutedWord.delete( adapterItem.name );
|
||||
listAdapter.removeItem( listAdapter.getPositionForItem( adapterItem ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
private void loadData(){
|
||||
|
||||
ArrayList< MyItem > tmp_list = new ArrayList<>();
|
||||
try{
|
||||
Cursor cursor = MutedWord.createCursor();
|
||||
if( cursor != null ){
|
||||
try{
|
||||
int idx_name = cursor.getColumnIndex( MutedWord.COL_NAME );
|
||||
while( cursor.moveToNext() ){
|
||||
String name = cursor.getString( idx_name);
|
||||
MyItem item = new MyItem( name );
|
||||
tmp_list.add( item );
|
||||
}
|
||||
|
||||
}finally{
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}catch( Throwable ex ){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
listAdapter.setItemList( tmp_list );
|
||||
}
|
||||
|
||||
|
||||
// リスト要素のデータ
|
||||
static class MyItem {
|
||||
String name;
|
||||
MyItem(String name ){
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// リスト要素のViewHolder
|
||||
static class MyViewHolder extends DragItemAdapter.ViewHolder {
|
||||
|
||||
final TextView tvName;
|
||||
|
||||
MyViewHolder( final View viewRoot ){
|
||||
super( viewRoot
|
||||
, R.id.ivDragHandle // View ID。 ここを押すとドラッグ操作をすぐに開始する
|
||||
, false // 長押しでドラッグ開始するなら真
|
||||
);
|
||||
|
||||
tvName = (TextView) viewRoot.findViewById( R.id.tvName );
|
||||
|
||||
// リスト要素のビューが ListSwipeItem だった場合、Swipe操作を制御できる
|
||||
if( viewRoot instanceof ListSwipeItem ){
|
||||
ListSwipeItem lsi = (ListSwipeItem) viewRoot;
|
||||
lsi.setSwipeInStyle( ListSwipeItem.SwipeInStyle.SLIDE );
|
||||
lsi.setSupportedSwipeDirection( ListSwipeItem.SwipeDirection.LEFT );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void bind( MyItem item ){
|
||||
itemView.setTag( item ); // itemView は親クラスのメンバ変数
|
||||
tvName.setText( item.name );
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public boolean onItemLongClicked( View view ){
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// @Override
|
||||
// public void onItemClicked( View view ){
|
||||
// }
|
||||
}
|
||||
|
||||
// ドラッグ操作中のデータ
|
||||
private class MyDragItem extends DragItem {
|
||||
MyDragItem( Context context, int layoutId ){
|
||||
super( context, layoutId );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindDragView( View clickedView, View dragView ){
|
||||
((TextView)dragView.findViewById( R.id.tvName )).setText(
|
||||
((TextView)clickedView.findViewById( R.id.tvName )).getText()
|
||||
);
|
||||
|
||||
dragView.findViewById(R.id.item_layout).setBackgroundColor(
|
||||
Styler.getAttributeColor( ActMutedWord.this, R.attr.list_item_bg_pressed_dragged)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private class MyListAdapter extends DragItemAdapter< MyItem, MyViewHolder > {
|
||||
|
||||
MyListAdapter(){
|
||||
super();
|
||||
setHasStableIds( true );
|
||||
setItemList( new ArrayList< MyItem >() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public MyViewHolder onCreateViewHolder( ViewGroup parent, int viewType ){
|
||||
View view = getLayoutInflater().inflate( R.layout.lv_mute_app, parent, false );
|
||||
return new MyViewHolder( view );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder( MyViewHolder holder, int position ){
|
||||
super.onBindViewHolder( holder, position );
|
||||
holder.bind( getItemList().get( position ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId( int position ){
|
||||
MyItem item = mItemList.get( position ); // mItemList は親クラスのメンバ変数
|
||||
return item.name.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,6 +48,8 @@ import java.io.InputStream;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import jp.juggler.subwaytooter.api.TootApiClient;
|
||||
import jp.juggler.subwaytooter.api.TootApiResult;
|
||||
|
@ -158,7 +160,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
|
|||
break;
|
||||
|
||||
case R.id.btnPost:
|
||||
performPost( false );
|
||||
performPost( false,false );
|
||||
break;
|
||||
|
||||
case R.id.btnRemoveReply:
|
||||
|
@ -867,7 +869,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
|
|||
// 設定からリサイズ指定を読む
|
||||
int resize_to = list_resize_max[ pref.getInt( Pref.KEY_RESIZE_IMAGE, 4 ) ];
|
||||
|
||||
Bitmap bitmap = Utils.createResizedBitmap( log, this, uri, true,resize_to );
|
||||
Bitmap bitmap = Utils.createResizedBitmap( log, this, uri, true, resize_to );
|
||||
if( bitmap != null ){
|
||||
try{
|
||||
File cache_dir = getExternalCacheDir();
|
||||
|
@ -1251,7 +1253,18 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
|
|||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// post
|
||||
|
||||
private void performPost( boolean bConfirm ){
|
||||
// [: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 );
|
||||
|
@ -1269,7 +1282,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
|
|||
}
|
||||
}
|
||||
|
||||
if( ! bConfirm ){
|
||||
if( ! bConfirmAccount ){
|
||||
DlgConfirm.open( this
|
||||
, getString( R.string.confirm_post_from, AcctColor.getNickname( account.acct ) )
|
||||
, new DlgConfirm.Callback() {
|
||||
|
@ -1283,12 +1296,29 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
|
|||
}
|
||||
|
||||
@Override public void onOK(){
|
||||
performPost( true );
|
||||
performPost( bConfirmTag, true );
|
||||
}
|
||||
} );
|
||||
return;
|
||||
}
|
||||
|
||||
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 ){
|
||||
performPost( true, bConfirmAccount );
|
||||
}
|
||||
} )
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "status=" );
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
package jp.juggler.subwaytooter;
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus;
|
||||
import jp.juggler.subwaytooter.table.MutedWord;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.util.HTMLDecoder;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
|
||||
public class ActText extends AppCompatActivity implements View.OnClickListener {
|
||||
|
||||
static final LogCategory log = new LogCategory( "ActText" );
|
||||
|
||||
static String encodeStatus( Context context, SavedAccount access_info, TootStatus status ){
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append( context.getString( R.string.send_header_url ) );
|
||||
sb.append( ": " );
|
||||
sb.append( status.url );
|
||||
sb.append( "\n" );
|
||||
sb.append( context.getString( R.string.send_header_date ) );
|
||||
sb.append( ": " );
|
||||
sb.append( TootStatus.formatTime( status.time_created_at ) );
|
||||
sb.append( "\n" );
|
||||
sb.append( context.getString( R.string.send_header_from_acct ) );
|
||||
sb.append( ": " );
|
||||
sb.append( access_info.getFullAcct( status.account ) );
|
||||
sb.append( "\n" );
|
||||
sb.append( context.getString( R.string.send_header_from_name ) );
|
||||
sb.append( ": " );
|
||||
sb.append( status.account.display_name );
|
||||
sb.append( "\n" );
|
||||
if( ! TextUtils.isEmpty( status.spoiler_text ) ){
|
||||
sb.append( context.getString( R.string.send_header_content_warning ) );
|
||||
sb.append( ": " );
|
||||
sb.append( HTMLDecoder.decodeHTMLForClipboard( access_info, status.spoiler_text ) );
|
||||
sb.append( "\n" );
|
||||
}
|
||||
sb.append( "\n" );
|
||||
sb.append( HTMLDecoder.decodeHTMLForClipboard( access_info, status.content ) );
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
static final String EXTRA_TEXT = "text";
|
||||
|
||||
public static void open( ActMain activity, SavedAccount access_info, TootStatus status ){
|
||||
String sv = encodeStatus( activity, access_info, status );
|
||||
Intent intent = new Intent( activity, ActText.class );
|
||||
intent.putExtra( EXTRA_TEXT, sv );
|
||||
activity.startActivity( intent );
|
||||
}
|
||||
|
||||
@Override protected void onCreate( @Nullable Bundle savedInstanceState ){
|
||||
super.onCreate( savedInstanceState );
|
||||
initUI();
|
||||
|
||||
|
||||
if( savedInstanceState == null ){
|
||||
Intent intent = getIntent();
|
||||
String sv = intent.getStringExtra( EXTRA_TEXT );
|
||||
etText.setText(sv);
|
||||
etText.setSelection( 0,sv.length() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
EditText etText;
|
||||
|
||||
void initUI(){
|
||||
setContentView( R.layout.act_text );
|
||||
etText = (EditText) findViewById( R.id.etText );
|
||||
|
||||
findViewById( R.id.btnCopy ).setOnClickListener( this );
|
||||
findViewById( R.id.btnSearch ).setOnClickListener( this );
|
||||
findViewById( R.id.btnSend ).setOnClickListener( this );
|
||||
findViewById( R.id.btnMuteWord ).setOnClickListener( this );
|
||||
|
||||
}
|
||||
|
||||
@Override public void onClick( View v ){
|
||||
switch( v.getId() ){
|
||||
case R.id.btnCopy:
|
||||
copy();
|
||||
break;
|
||||
case R.id.btnSearch:
|
||||
search();
|
||||
break;
|
||||
case R.id.btnSend:
|
||||
send();
|
||||
|
||||
break;
|
||||
case R.id.btnMuteWord:
|
||||
muteWord();
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private String getSelection(){
|
||||
int s = etText.getSelectionStart();
|
||||
int e = etText.getSelectionEnd();
|
||||
String text = etText.getText().toString();
|
||||
if( s == e ){
|
||||
return text;
|
||||
}else{
|
||||
return text.substring( s, e );
|
||||
}
|
||||
}
|
||||
|
||||
private void copy(){
|
||||
try{
|
||||
// Gets a handle to the clipboard service.
|
||||
ClipboardManager clipboard = (ClipboardManager)
|
||||
getSystemService( Context.CLIPBOARD_SERVICE );
|
||||
// Creates a new text clip to put on the clipboard
|
||||
ClipData clip = ClipData.newPlainText( "text", getSelection() );
|
||||
// Set the clipboard's primary clip.
|
||||
clipboard.setPrimaryClip( clip );
|
||||
|
||||
Utils.showToast( this,false,R.string.copy_complete );
|
||||
}catch( Throwable ex ){
|
||||
ex.printStackTrace();
|
||||
Utils.showToast( this, ex, "copy failed." );
|
||||
}
|
||||
}
|
||||
|
||||
private void search(){
|
||||
try{
|
||||
Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
|
||||
intent.putExtra( SearchManager.QUERY, getSelection() );
|
||||
if( intent.resolveActivity(getPackageManager()) != null ) {
|
||||
startActivity(intent);
|
||||
}
|
||||
}catch( Throwable ex ){
|
||||
ex.printStackTrace();
|
||||
Utils.showToast( this, ex, "search failed." );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void send(){
|
||||
try{
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setAction( Intent.ACTION_SEND );
|
||||
intent.setType( "text/plain" );
|
||||
intent.putExtra( Intent.EXTRA_TEXT, getSelection() );
|
||||
startActivity( intent );
|
||||
|
||||
}catch( Throwable ex ){
|
||||
ex.printStackTrace();
|
||||
Utils.showToast( this, ex, "send failed." );
|
||||
}
|
||||
}
|
||||
|
||||
private void muteWord(){
|
||||
try{
|
||||
MutedWord.save( getSelection() );
|
||||
for( Column column : App1.getAppState( this ).column_list ){
|
||||
column.removeMuteApp();
|
||||
}
|
||||
Utils.showToast( this, false, R.string.word_was_muted );
|
||||
}catch( Throwable ex ){
|
||||
ex.printStackTrace();
|
||||
Utils.showToast( this, ex, "muteWord failed." );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -31,6 +31,7 @@ import jp.juggler.subwaytooter.api.entity.TootApplication;
|
|||
import jp.juggler.subwaytooter.api.entity.TootNotification;
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus;
|
||||
import jp.juggler.subwaytooter.table.MutedApp;
|
||||
import jp.juggler.subwaytooter.table.MutedWord;
|
||||
import jp.juggler.subwaytooter.table.NotificationTracking;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
|
@ -129,13 +130,15 @@ public class AlarmService extends IntentService {
|
|||
}else if( ACTION_NOTIFICATION_CLICK.equals( action ) ){
|
||||
log.d( "Notification clicked!" );
|
||||
long db_id = received_intent.getLongExtra( EXTRA_DB_ID, 0L );
|
||||
NotificationTracking.updateRead( db_id );
|
||||
notification_manager.cancel( Long.toString( db_id ), NOTIFICATION_ID );
|
||||
//
|
||||
// 画面を開く
|
||||
intent = new Intent( this, ActCallback.class );
|
||||
intent.setData( Uri.parse( "subwaytooter://notification_click?db_id=" + db_id ) );
|
||||
intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK );
|
||||
startActivity( intent );
|
||||
// 通知をキャンセル
|
||||
notification_manager.cancel( Long.toString( db_id ), NOTIFICATION_ID );
|
||||
// DB更新処理
|
||||
NotificationTracking.updateRead( db_id );
|
||||
return;
|
||||
|
||||
}
|
||||
|
@ -156,7 +159,8 @@ public class AlarmService extends IntentService {
|
|||
boolean bAlarmRequired = false;
|
||||
|
||||
HashSet< String > muted_app = MutedApp.getNameSet();
|
||||
|
||||
HashSet< String > muted_word = MutedWord.getNameSet();
|
||||
|
||||
for( SavedAccount account : account_list ){
|
||||
try{
|
||||
if( account.notification_mention
|
||||
|
@ -168,7 +172,7 @@ public class AlarmService extends IntentService {
|
|||
|
||||
ArrayList< Data > data_list = new ArrayList<>();
|
||||
|
||||
checkAccount( client, data_list, account, muted_app );
|
||||
checkAccount( client, data_list, account, muted_app ,muted_word);
|
||||
|
||||
showNotification( account.db_id, data_list );
|
||||
|
||||
|
@ -200,7 +204,7 @@ public class AlarmService extends IntentService {
|
|||
|
||||
private static final String PATH_NOTIFICATIONS = "/api/v1/notifications";
|
||||
|
||||
private void checkAccount( TootApiClient client, ArrayList< Data > data_list, SavedAccount account, HashSet< String > muted_app ){
|
||||
private void checkAccount( TootApiClient client, ArrayList< Data > data_list, SavedAccount account, HashSet< String > muted_app, HashSet< String > muted_word ){
|
||||
log.d( "checkAccount account_db_id=%s", account.db_id );
|
||||
|
||||
NotificationTracking nr = NotificationTracking.load( account.db_id );
|
||||
|
@ -213,7 +217,7 @@ public class AlarmService extends IntentService {
|
|||
JSONArray array = new JSONArray( nr.last_data );
|
||||
for( int i = array.length() - 1 ; i >= 0 ; -- i ){
|
||||
JSONObject src = array.optJSONObject( i );
|
||||
update_sub( src, nr, account, dst_array, data_list, duplicate_check, muted_app );
|
||||
update_sub( src, nr, account, dst_array, data_list, duplicate_check, muted_app ,muted_word);
|
||||
}
|
||||
}catch( JSONException ex ){
|
||||
ex.printStackTrace();
|
||||
|
@ -237,7 +241,7 @@ public class AlarmService extends IntentService {
|
|||
JSONArray array = result.array;
|
||||
for( int i = array.length() - 1 ; i >= 0 ; -- i ){
|
||||
JSONObject src = array.optJSONObject( i );
|
||||
update_sub( src, nr, account, dst_array, data_list, duplicate_check, muted_app );
|
||||
update_sub( src, nr, account, dst_array, data_list, duplicate_check, muted_app, muted_word );
|
||||
}
|
||||
}catch( JSONException ex ){
|
||||
ex.printStackTrace();
|
||||
|
@ -277,7 +281,10 @@ public class AlarmService extends IntentService {
|
|||
, ArrayList< Data > data_list
|
||||
, HashSet< Long > duplicate_check
|
||||
, HashSet< String > muted_app
|
||||
) throws JSONException{
|
||||
, HashSet< String > muted_word
|
||||
)
|
||||
throws JSONException
|
||||
{
|
||||
|
||||
long id = src.optLong( "id" );
|
||||
|
||||
|
@ -306,18 +313,11 @@ public class AlarmService extends IntentService {
|
|||
return;
|
||||
}
|
||||
|
||||
// app mute
|
||||
{
|
||||
TootStatus status = notification.status;
|
||||
if( status != null ){
|
||||
TootApplication application = status.application;
|
||||
if( application != null ){
|
||||
String name = application.name;
|
||||
if( name != null ){
|
||||
if( muted_app.contains( name ) ){
|
||||
return;
|
||||
}
|
||||
}
|
||||
if( status.checkMuted( muted_app,muted_word )){
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import jp.juggler.subwaytooter.table.ClientInfo;
|
|||
import jp.juggler.subwaytooter.table.ContentWarning;
|
||||
import jp.juggler.subwaytooter.table.LogData;
|
||||
import jp.juggler.subwaytooter.table.MediaShown;
|
||||
import jp.juggler.subwaytooter.table.MutedWord;
|
||||
import jp.juggler.subwaytooter.table.NotificationTracking;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.table.UserRelation;
|
||||
|
@ -50,7 +51,7 @@ public class App1 extends Application {
|
|||
static final LogCategory log = new LogCategory( "App1" );
|
||||
|
||||
static final String DB_NAME = "app_db";
|
||||
static final int DB_VERSION = 10;
|
||||
static final int DB_VERSION = 11;
|
||||
// 2017/4/25 v10 1=>2 SavedAccount に通知設定を追加
|
||||
// 2017/4/25 v10 1=>2 NotificationTracking テーブルを追加
|
||||
// 2017/4/29 v20 2=>5 MediaShown,ContentWarningのインデクスが間違っていたので貼り直す
|
||||
|
@ -59,6 +60,7 @@ public class App1 extends Application {
|
|||
// 2017/5/02 v32 7=>8 (この変更は取り消された)
|
||||
// 2017/5/02 v32 8=>9 AcctColor テーブルの追加
|
||||
// 2017/5/04 v33 9=>10 SavedAccountに項目追加
|
||||
// 2017/5/08 v41 10=>11 MutedWord テーブルの追加
|
||||
|
||||
static DBOpenHelper db_open_helper;
|
||||
|
||||
|
@ -101,6 +103,7 @@ public class App1 extends Application {
|
|||
UserRelation.onDBCreate( db );
|
||||
AcctSet.onDBCreate( db );
|
||||
AcctColor.onDBCreate( db );
|
||||
MutedWord.onDBCreate( db );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -116,6 +119,7 @@ public class App1 extends Application {
|
|||
UserRelation.onDBUpgrade( db, oldVersion, newVersion );
|
||||
AcctSet.onDBUpgrade( db, oldVersion, newVersion );
|
||||
AcctColor.onDBUpgrade( db, oldVersion, newVersion );
|
||||
MutedWord.onDBUpgrade( db, oldVersion, newVersion );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ import java.util.regex.Pattern;
|
|||
import jp.juggler.subwaytooter.api.TootApiClient;
|
||||
import jp.juggler.subwaytooter.api.TootApiResult;
|
||||
import jp.juggler.subwaytooter.api.entity.TootAccount;
|
||||
import jp.juggler.subwaytooter.api.entity.TootApplication;
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment;
|
||||
import jp.juggler.subwaytooter.api.entity.TootContext;
|
||||
import jp.juggler.subwaytooter.api.entity.TootGap;
|
||||
|
@ -34,6 +33,7 @@ import jp.juggler.subwaytooter.api.entity.TootStatus;
|
|||
import jp.juggler.subwaytooter.table.AcctColor;
|
||||
import jp.juggler.subwaytooter.table.AcctSet;
|
||||
import jp.juggler.subwaytooter.table.MutedApp;
|
||||
import jp.juggler.subwaytooter.table.MutedWord;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.table.UserRelation;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
|
@ -93,11 +93,11 @@ class Column {
|
|||
private static final String KEY_DONT_SHOW_REPLY = "dont_show_reply";
|
||||
private static final String KEY_REGEX_TEXT = "regex_text";
|
||||
|
||||
static final String KEY_HEADER_BACKGROUND_COLOR = "header_background_color";
|
||||
static final String KEY_HEADER_TEXT_COLOR = "header_text_color";
|
||||
static final String KEY_COLUMN_BACKGROUND_COLOR = "column_background_color";
|
||||
static final String KEY_COLUMN_BACKGROUND_IMAGE = "column_background_image";
|
||||
static final String KEY_COLUMN_BACKGROUND_IMAGE_ALPHA = "column_background_image_alpha";
|
||||
private static final String KEY_HEADER_BACKGROUND_COLOR = "header_background_color";
|
||||
private static final String KEY_HEADER_TEXT_COLOR = "header_text_color";
|
||||
private static final String KEY_COLUMN_BACKGROUND_COLOR = "column_background_color";
|
||||
private static final String KEY_COLUMN_BACKGROUND_IMAGE = "column_background_image";
|
||||
private static final String KEY_COLUMN_BACKGROUND_IMAGE_ALPHA = "column_background_image_alpha";
|
||||
|
||||
private static final String KEY_PROFILE_ID = "profile_id";
|
||||
private static final String KEY_PROFILE_TAB = "tab";
|
||||
|
@ -726,17 +726,14 @@ class Column {
|
|||
ArrayList< Object > tmp_list = new ArrayList<>( list_data.size() );
|
||||
|
||||
HashSet< String > muted_app = MutedApp.getNameSet();
|
||||
HashSet< String > muted_word = MutedWord.getNameSet();
|
||||
|
||||
for( Object o : list_data ){
|
||||
if( o instanceof TootStatus ){
|
||||
TootStatus item = (TootStatus) o;
|
||||
TootApplication application = item.application;
|
||||
if( application != null ){
|
||||
String name = application.name;
|
||||
if( name != null && muted_app.contains( name ) ){
|
||||
log.d( "removeMuteApp: mute app %s", name );
|
||||
continue;
|
||||
}
|
||||
if( item.checkMuted( muted_app,muted_word )){
|
||||
continue;
|
||||
|
||||
}
|
||||
}
|
||||
if( o instanceof TootNotification ){
|
||||
|
@ -744,9 +741,8 @@ class Column {
|
|||
TootStatus status = item.status;
|
||||
|
||||
if( status != null ){
|
||||
if( status.application != null ){
|
||||
String sv = status.application.name;
|
||||
if( sv != null && muted_app.contains( sv ) ) continue;
|
||||
if( status.checkMuted( muted_app,muted_word )){
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -772,7 +768,8 @@ class Column {
|
|||
}
|
||||
|
||||
HashSet< String > muted_app = MutedApp.getNameSet();
|
||||
|
||||
HashSet< String > muted_word = MutedWord.getNameSet();
|
||||
|
||||
for( TootStatus status : src ){
|
||||
if( with_attachment ){
|
||||
if( ! hasMedia( status ) && ! hasMedia( status.reblog ) ) continue;
|
||||
|
@ -799,12 +796,8 @@ class Column {
|
|||
}
|
||||
}
|
||||
|
||||
if( status.application != null ){
|
||||
String sv = status.application.name;
|
||||
if( sv != null && muted_app.contains( sv ) ){
|
||||
log.d( "addWithFilter: mute app %s", sv );
|
||||
continue;
|
||||
}
|
||||
if( status.checkMuted( muted_app,muted_word )){
|
||||
continue;
|
||||
}
|
||||
|
||||
dst.add( status );
|
||||
|
@ -815,19 +808,18 @@ class Column {
|
|||
private void addWithFilter( ArrayList< Object > dst, TootNotification.List src ){
|
||||
|
||||
HashSet< String > muted_app = MutedApp.getNameSet();
|
||||
HashSet< String > muted_word = MutedWord.getNameSet();
|
||||
|
||||
for( TootNotification item : src ){
|
||||
|
||||
TootStatus status = item.status;
|
||||
|
||||
if( status != null ){
|
||||
if( status.application != null ){
|
||||
String sv = status.application.name;
|
||||
if( sv != null && muted_app.contains( sv ) ){
|
||||
log.d( "addWithFilter: mute app %s", sv );
|
||||
continue;
|
||||
}
|
||||
if( status.checkMuted( muted_app,muted_word ) ){
|
||||
log.d( "addWithFilter: status muted.");
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dst.add( item );
|
||||
|
@ -2243,6 +2235,7 @@ class Column {
|
|||
fireShowContent();
|
||||
|
||||
if( holder != null ){
|
||||
//noinspection StatementWithEmptyBody
|
||||
if(restore_idx >= 0 ){
|
||||
setItemTop( restore_idx + added - 1, restore_y );
|
||||
}else{
|
||||
|
|
|
@ -61,7 +61,7 @@ class DlgContextMenu implements View.OnClickListener {
|
|||
|
||||
View llStatus = viewRoot.findViewById( R.id.llStatus );
|
||||
View btnStatusWebPage = viewRoot.findViewById( R.id.btnStatusWebPage );
|
||||
View btnStatusSendApp = viewRoot.findViewById( R.id.btnStatusSendApp );
|
||||
View btnText = viewRoot.findViewById( R.id.btnText );
|
||||
View btnFavouriteAnotherAccount = viewRoot.findViewById( R.id.btnFavouriteAnotherAccount );
|
||||
View btnBoostAnotherAccount = viewRoot.findViewById( R.id.btnBoostAnotherAccount );
|
||||
View btnDelete = viewRoot.findViewById( R.id.btnDelete );
|
||||
|
@ -98,7 +98,7 @@ class DlgContextMenu implements View.OnClickListener {
|
|||
boolean status_by_me = access_info.isMe( status.account );
|
||||
|
||||
btnStatusWebPage.setOnClickListener( this );
|
||||
btnStatusSendApp.setOnClickListener( this );
|
||||
btnText.setOnClickListener( this );
|
||||
|
||||
if( account_list_non_pseudo_same_instance.isEmpty() ){
|
||||
btnFavouriteAnotherAccount.setVisibility( View.GONE );
|
||||
|
@ -237,9 +237,9 @@ class DlgContextMenu implements View.OnClickListener {
|
|||
}
|
||||
break;
|
||||
|
||||
case R.id.btnStatusSendApp:
|
||||
case R.id.btnText:
|
||||
if( status != null ){
|
||||
activity.sendStatus( access_info, status );
|
||||
ActText.open( activity,access_info,status);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@ public class TootAccount {
|
|||
if( TextUtils.isEmpty( sv ) ){
|
||||
dst.display_name = dst.username;
|
||||
}else{
|
||||
dst.display_name = Emojione.decodeEmoji( HTMLDecoder.decodeEntity(sv ) );
|
||||
dst.display_name = filterDisplayName(sv);
|
||||
}
|
||||
|
||||
dst.locked = src.optBoolean( "locked" );
|
||||
|
@ -123,6 +123,7 @@ public class TootAccount {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public static TootAccount parse( LogCategory log, LinkClickContext account,JSONObject src ){
|
||||
return parse( log, account, src, new TootAccount() );
|
||||
}
|
||||
|
@ -142,4 +143,16 @@ public class TootAccount {
|
|||
return result;
|
||||
}
|
||||
|
||||
private static CharSequence filterDisplayName( String sv ){
|
||||
|
||||
// decode HTML entity
|
||||
sv = HTMLDecoder.decodeEntity(sv );
|
||||
|
||||
// sanitize LRO,RLO
|
||||
sv = Utils.sanitizeBDI( sv);
|
||||
|
||||
// decode emoji code
|
||||
return Emojione.decodeEmoji( sv ) ;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.text.ParseException;
|
|||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.regex.Matcher;
|
||||
|
@ -24,7 +25,6 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
|
||||
public class TootStatus extends TootId {
|
||||
|
||||
|
||||
public static class List extends ArrayList< TootStatus > {
|
||||
|
||||
public List(){
|
||||
|
@ -230,4 +230,38 @@ public class TootStatus extends TootId {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean checkMuted( HashSet< String > muted_app, HashSet< String > muted_word ){
|
||||
|
||||
// app mute
|
||||
if( application != null ){
|
||||
String name = application.name;
|
||||
if( name != null ){
|
||||
if( muted_app.contains( name ) ){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// word mute
|
||||
for( String word: muted_word ){
|
||||
if( decoded_content != null && decoded_content.toString().contains( word ) ){
|
||||
return true;
|
||||
}
|
||||
if( decoded_spoiler_text != null && decoded_spoiler_text.toString().contains( word ) ){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// reblog
|
||||
//noinspection RedundantIfStatement
|
||||
if( reblog != null && reblog.checkMuted( muted_app,muted_word ) ){
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.database.sqlite.SQLiteDatabase;
|
|||
|
||||
import jp.juggler.subwaytooter.App1;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
@ -142,7 +143,7 @@ public class AcctColor {
|
|||
|
||||
@NonNull public static String getNickname( @NonNull String acct ){
|
||||
AcctColor ac = load( acct );
|
||||
return ac != null && ! TextUtils.isEmpty( ac.nickname ) ? ac.nickname : acct;
|
||||
return ac != null && ! TextUtils.isEmpty( ac.nickname ) ? Utils.sanitizeBDI( ac.nickname ) : acct;
|
||||
}
|
||||
|
||||
public static boolean hasNickname( @Nullable AcctColor ac ){
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
package jp.juggler.subwaytooter.table;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
import jp.juggler.subwaytooter.App1;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
|
||||
public class MutedWord {
|
||||
|
||||
private static final LogCategory log = new LogCategory( "MutedWord" );
|
||||
|
||||
private static final String table = "word_mute";
|
||||
public static final String COL_NAME = "name";
|
||||
private static final String COL_TIME_SAVE = "time_save";
|
||||
|
||||
public static void onDBCreate( SQLiteDatabase db ){
|
||||
log.d("onDBCreate!");
|
||||
db.execSQL(
|
||||
"create table if not exists " + table
|
||||
+ "(_id INTEGER PRIMARY KEY"
|
||||
+ ",name text not null"
|
||||
+ ",time_save integer not null"
|
||||
+ ")"
|
||||
);
|
||||
db.execSQL(
|
||||
"create unique index if not exists " + table + "_name on " + table + "(name)"
|
||||
);
|
||||
}
|
||||
|
||||
public static void onDBUpgrade( SQLiteDatabase db, int oldVersion, int newVersion ){
|
||||
if(oldVersion < 11 && newVersion >= 11){
|
||||
onDBCreate( db );
|
||||
}
|
||||
}
|
||||
|
||||
public static void save( String word ){
|
||||
if( word == null ) return;
|
||||
try{
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put( COL_NAME, word );
|
||||
cv.put( COL_TIME_SAVE, now );
|
||||
App1.getDB().replace( table, null, cv );
|
||||
|
||||
}catch( Throwable ex ){
|
||||
log.e( ex, "save failed." );
|
||||
}
|
||||
}
|
||||
|
||||
public static Cursor createCursor(){
|
||||
return App1.getDB().query( table, null,null,null, null, null, COL_NAME+" asc" );
|
||||
}
|
||||
|
||||
public static void delete( String name ){
|
||||
try{
|
||||
App1.getDB().delete( table, COL_NAME+"=?",new String[]{ name });
|
||||
}catch( Throwable ex ){
|
||||
log.e( ex, "delete failed." );
|
||||
}
|
||||
}
|
||||
|
||||
public static HashSet<String> getNameSet(){
|
||||
HashSet<String> dst = new HashSet<>();
|
||||
try{
|
||||
Cursor cursor = App1.getDB().query( table, null,null,null, null, null, null);
|
||||
if( cursor != null ){
|
||||
try{
|
||||
int idx_name = cursor.getColumnIndex( COL_NAME );
|
||||
while(cursor.moveToNext()){
|
||||
String s = cursor.getString(idx_name);
|
||||
dst.add( s);
|
||||
}
|
||||
}finally{
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}catch(Throwable ex){
|
||||
ex.printStackTrace( );
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
// private static final String[] isMuted_projection = new String[]{COL_NAME};
|
||||
// private static final String isMuted_where = COL_NAME+"=?";
|
||||
// private static final ThreadLocal<String[]> isMuted_where_arg = new ThreadLocal<String[]>() {
|
||||
// @Override protected String[] initialValue() {
|
||||
// return new String[1];
|
||||
// }
|
||||
// };
|
||||
// public static boolean isMuted( String app_name ){
|
||||
// if( app_name == null ) return false;
|
||||
// try{
|
||||
// String[] where_arg = isMuted_where_arg.get();
|
||||
// where_arg[0] = app_name;
|
||||
// Cursor cursor = App1.getDB().query( table, isMuted_projection,isMuted_where , where_arg, null, null, null );
|
||||
// try{
|
||||
// if( cursor.moveToFirst() ){
|
||||
// return true;
|
||||
// }
|
||||
// }finally{
|
||||
// cursor.close();
|
||||
// }
|
||||
// }catch( Throwable ex ){
|
||||
// log.e( ex, "load failed." );
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
}
|
|
@ -74,8 +74,6 @@ public abstract class Emojione
|
|||
|
||||
public static CharSequence decodeEmoji( String s ){
|
||||
|
||||
|
||||
|
||||
DecodeEnv decode_env = new DecodeEnv();
|
||||
Matcher matcher = SHORTNAME_PATTERN.matcher(s);
|
||||
int last_end = 0;
|
||||
|
@ -101,6 +99,7 @@ public abstract class Emojione
|
|||
}
|
||||
// close span
|
||||
decode_env.closeSpan();
|
||||
|
||||
return decode_env.sb;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import java.security.MessageDigest;
|
|||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
|
@ -1040,4 +1041,51 @@ public class Utils {
|
|||
return null;
|
||||
}
|
||||
|
||||
|
||||
private static final char ALM = (char)0x061c; // Arabic letter mark (ALM)
|
||||
private static final char LRM = (char)0x200E; // Left-to-right mark (LRM)
|
||||
private static final char RLM = (char)0x200F; // Right-to-left mark (RLM)
|
||||
private static final char LRE = (char)0x202A; // Left-to-right embedding (LRE)
|
||||
private static final char RLE = (char)0x202B; // Right-to-left embedding (RLE)
|
||||
private static final char PDF = (char)0x202C; // Pop directional formatting (PDF)
|
||||
private static final char LRO = (char)0x202D; // Left-to-right override (LRO)
|
||||
private static final char RLO = (char)0x202E; // Right-to-left override (RLO)
|
||||
|
||||
private static final String CHARS_MUST_PDF = String.valueOf( LRE ) + RLE + LRO + RLO ;
|
||||
|
||||
private static final char LRI = (char)0x2066; // Left-to-right isolate (LRI)
|
||||
private static final char RLI = (char)0x2067; // Right-to-left isolate (RLI)
|
||||
private static final char FSI = (char)0x2068; // First strong isolate (FSI)
|
||||
private static final char PDI = (char)0x2069; // Pop directional isolate (PDI)
|
||||
|
||||
private static final String CHARS_MUST_PDI = String.valueOf( LRI ) + RLI + FSI ;
|
||||
|
||||
public static String sanitizeBDI(String src){
|
||||
|
||||
LinkedList< Character > stack = null;
|
||||
|
||||
for( int i = 0, ie = src.length() ; i < ie ; ++ i ){
|
||||
char c = src.charAt( i );
|
||||
|
||||
if( - 1 != CHARS_MUST_PDF.indexOf( c ) ){
|
||||
if( stack == null ) stack = new LinkedList<>();
|
||||
stack.add( PDF );
|
||||
|
||||
}else if( - 1 != CHARS_MUST_PDI.indexOf( c ) ){
|
||||
if( stack == null ) stack = new LinkedList<>();
|
||||
stack.add( PDI );
|
||||
}else if( stack != null && ! stack.isEmpty() && stack.getLast() == c ){
|
||||
stack.removeLast();
|
||||
}
|
||||
}
|
||||
|
||||
if( stack == null || stack.isEmpty() ) return src;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append( src );
|
||||
while( ! stack.isEmpty() ){
|
||||
sb.append( (char) stack.removeLast() );
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:fillViewport="true"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
<jp.juggler.subwaytooter.util.MyEditText
|
||||
android:id="@+id/etText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"
|
||||
/>
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fadeScrollbars="false"
|
||||
>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingBottom="8dp"
|
||||
>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnCopy"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/copy"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSearch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/search_web"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSend"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/send"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnMuteWord"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/mute_word"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
</HorizontalScrollView>
|
||||
</LinearLayout>
|
|
@ -60,7 +60,7 @@
|
|||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnStatusSendApp"
|
||||
android:id="@+id/btnText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
|
@ -70,7 +70,7 @@
|
|||
android:paddingEnd="8dp"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="4dp"
|
||||
android:text="@string/send_text"
|
||||
android:text="@string/select_and_copy"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
|
|
|
@ -273,7 +273,7 @@
|
|||
<LinearLayout
|
||||
android:id="@+id/llContents"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
|
|
|
@ -270,7 +270,7 @@
|
|||
<LinearLayout
|
||||
android:id="@+id/llContents"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
|
|
|
@ -97,6 +97,11 @@
|
|||
android:id="@+id/nav_muted_app"
|
||||
android:icon="?attr/ic_setting"
|
||||
android:title="@string/muted_app"/>
|
||||
<item
|
||||
android:id="@+id/nav_muted_word"
|
||||
android:icon="?attr/ic_setting"
|
||||
android:title="@string/muted_word"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_app_about"
|
||||
android:icon="?attr/ic_info"
|
||||
|
|
|
@ -286,5 +286,13 @@
|
|||
<string name="menu">Menu</string>
|
||||
<string name="open_in_pseudo_account">open in pseudo account %1$s</string>
|
||||
<string name="pick_image">pick image</string>
|
||||
<string name="hashtag_and_visibility_not_match">this message contains hashtags, but message visibility is not public. normally hashtags are searchable only in public messages. Are you sure?</string>
|
||||
<string name="copy">Copy</string>
|
||||
<string name="mute_word">mute word</string>
|
||||
<string name="muted_word">Muted words</string>
|
||||
<string name="search_web">Web search</string>
|
||||
<string name="select_and_copy">Select and copy</string>
|
||||
<string name="send">Send</string>
|
||||
<string name="word_was_muted">Word was muted.</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -282,4 +282,12 @@
|
|||
<string name="menu">メニュー</string>
|
||||
<string name="open_in_pseudo_account">疑似アカウント %1$s で開く</string>
|
||||
<string name="pick_image">画像の選択</string>
|
||||
<string name="hashtag_and_visibility_not_match">メッセージにハッシュタグが含まれていますが、公開範囲が公開ではありません。ハッシュタグは公開メッセージに含まれる場合のみ検索されることができます。続けてもよろしいですか?</string>
|
||||
<string name="copy">コピー</string>
|
||||
<string name="mute_word">単語をミュート</string>
|
||||
<string name="muted_word">ミュートした単語</string>
|
||||
<string name="search_web">Web検索</string>
|
||||
<string name="select_and_copy">選択してコピー…</string>
|
||||
<string name="send">共有</string>
|
||||
<string name="word_was_muted">単語をミュートしました</string>
|
||||
</resources>
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
|
||||
<color name="Light_colorPostFormBackground">#eee</color>
|
||||
|
||||
<color name="Light_colorColumnStripBackground">#FFFFFFFF</color>
|
||||
|
||||
<color name="Light_colorProfileBackgroundMask">#C0FFFFFF</color>
|
||||
|
||||
|
@ -44,6 +43,7 @@
|
|||
<color name="Light_colorColumnListDeleteBackground">#FF0000</color>
|
||||
<color name="Light_colorColumnListDeleteText">#fff</color>
|
||||
<color name="Light_colorRegexFilterError">#f00</color>
|
||||
<color name="Light_colorColumnStripBackground">#000000</color>
|
||||
|
||||
|
||||
<!-- Dark theme -->
|
||||
|
@ -78,7 +78,6 @@
|
|||
<color name="Dark_colorProfileBackgroundMask">#C0000000</color>
|
||||
<color name="Dark_colorBackground">#000</color>
|
||||
|
||||
<color name="Dark_colorColumnStripBackground">#000</color>
|
||||
|
||||
<color name="Dark_colorPrimaryDark">#222</color>
|
||||
|
||||
|
@ -91,5 +90,6 @@
|
|||
<color name="Dark_colorColumnListDeleteBackground">#FF0000</color>
|
||||
<color name="Dark_colorColumnListDeleteText">#fff</color>
|
||||
<color name="Dark_colorRegexFilterError">#f00</color>
|
||||
<color name="Dark_colorColumnStripBackground">#000</color>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -282,4 +282,13 @@
|
|||
<string name="image">image</string>
|
||||
<string name="column_header">column header</string>
|
||||
<string name="foreground_color">foreground color</string>
|
||||
<string name="hashtag_and_visibility_not_match">this message contains hashtags, but message visibility is not public. normally hashtags are searchable only in public messages. Are you sure?</string>
|
||||
<string name="copy">Copy</string>
|
||||
<string name="send">Send</string>
|
||||
<string name="mute_word">mute word</string>
|
||||
<string name="select_and_copy">Select and copy</string>
|
||||
<string name="search_web">Web search</string>
|
||||
<string name="muted_word">Muted words</string>
|
||||
<string name="word_was_muted">Word was muted.</string>
|
||||
<string name="copy_complete">Clipboard updated.</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue