diff --git a/app/build.gradle b/app/build.gradle
index 74973757..26ec1eac 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -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"
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 486885d4..9ce22b2f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -131,7 +131,10 @@
android:name=".ActMutedApp"
android:label="@string/muted_app"
/>
-
+
-
+
diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMain.java b/app/src/main/java/jp/juggler/subwaytooter/ActMain.java
index 73fb4649..21a7a185 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/ActMain.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/ActMain.java
@@ -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() {
diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMutedApp.java b/app/src/main/java/jp/juggler/subwaytooter/ActMutedApp.java
index 6bc4327f..0853baf1 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/ActMutedApp.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/ActMutedApp.java
@@ -156,6 +156,7 @@ public class ActMutedApp extends AppCompatActivity {
}
void bind( MyItem item ){
+ itemView.setTag( item ); // itemView は親クラスのメンバ変数
tvName.setText( item.name );
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMutedWord.java b/app/src/main/java/jp/juggler/subwaytooter/ActMutedWord.java
new file mode 100644
index 00000000..498b2bd2
--- /dev/null
+++ b/app/src/main/java/jp/juggler/subwaytooter/ActMutedWord.java
@@ -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();
+ }
+ }
+}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPost.java b/app/src/main/java/jp/juggler/subwaytooter/ActPost.java
index 96d5ad5f..681a9f7c 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/ActPost.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/ActPost.java
@@ -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=" );
diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActText.java b/app/src/main/java/jp/juggler/subwaytooter/ActText.java
new file mode 100644
index 00000000..8bd12122
--- /dev/null
+++ b/app/src/main/java/jp/juggler/subwaytooter/ActText.java
@@ -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." );
+ }
+ }
+
+}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/AlarmService.java b/app/src/main/java/jp/juggler/subwaytooter/AlarmService.java
index 663fd0c0..012c19bc 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/AlarmService.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/AlarmService.java
@@ -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;
}
}
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/App1.java b/app/src/main/java/jp/juggler/subwaytooter/App1.java
index 8890443d..34966b12 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/App1.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/App1.java
@@ -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 );
}
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/Column.java b/app/src/main/java/jp/juggler/subwaytooter/Column.java
index 7925bc46..3b5955a2 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/Column.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/Column.java
@@ -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{
diff --git a/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.java b/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.java
index c4da3e44..a88e6458 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.java
@@ -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;
diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.java b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.java
index fb202e38..d502ca9d 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.java
@@ -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 ) ;
+ }
+
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.java b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.java
index 3ac93f5c..94677694 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.java
@@ -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;
+
+ }
+
+
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/table/AcctColor.java b/app/src/main/java/jp/juggler/subwaytooter/table/AcctColor.java
index 2bb5e264..fe848c6b 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/table/AcctColor.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/table/AcctColor.java
@@ -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 ){
diff --git a/app/src/main/java/jp/juggler/subwaytooter/table/MutedWord.java b/app/src/main/java/jp/juggler/subwaytooter/table/MutedWord.java
new file mode 100644
index 00000000..e637486c
--- /dev/null
+++ b/app/src/main/java/jp/juggler/subwaytooter/table/MutedWord.java
@@ -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 getNameSet(){
+ HashSet 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 isMuted_where_arg = new ThreadLocal() {
+// @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;
+// }
+
+}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/Emojione.java b/app/src/main/java/jp/juggler/subwaytooter/util/Emojione.java
index 38ddffae..b7ed4cd4 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/util/Emojione.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/util/Emojione.java
@@ -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;
}
}
diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/Utils.java b/app/src/main/java/jp/juggler/subwaytooter/util/Utils.java
index 98396661..2f34a66f 100644
--- a/app/src/main/java/jp/juggler/subwaytooter/util/Utils.java
+++ b/app/src/main/java/jp/juggler/subwaytooter/util/Utils.java
@@ -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();
+ }
}
diff --git a/app/src/main/res/layout/act_text.xml b/app/src/main/res/layout/act_text.xml
new file mode 100644
index 00000000..1ad6da62
--- /dev/null
+++ b/app/src/main/res/layout/act_text.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dlg_context_menu.xml b/app/src/main/res/layout/dlg_context_menu.xml
index 7c84ae08..1f8d94b8 100644
--- a/app/src/main/res/layout/dlg_context_menu.xml
+++ b/app/src/main/res/layout/dlg_context_menu.xml
@@ -60,7 +60,7 @@
/>
diff --git a/app/src/main/res/layout/lv_status.xml b/app/src/main/res/layout/lv_status.xml
index fb1da0c3..d2ac4cf3 100644
--- a/app/src/main/res/layout/lv_status.xml
+++ b/app/src/main/res/layout/lv_status.xml
@@ -273,7 +273,7 @@
diff --git a/app/src/main/res/layout/lv_status_simple.xml b/app/src/main/res/layout/lv_status_simple.xml
index 51398c81..08e70bad 100644
--- a/app/src/main/res/layout/lv_status_simple.xml
+++ b/app/src/main/res/layout/lv_status_simple.xml
@@ -270,7 +270,7 @@
diff --git a/app/src/main/res/menu/men_navi_drawer.xml b/app/src/main/res/menu/men_navi_drawer.xml
index 56eee3c3..20504623 100644
--- a/app/src/main/res/menu/men_navi_drawer.xml
+++ b/app/src/main/res/menu/men_navi_drawer.xml
@@ -97,6 +97,11 @@
android:id="@+id/nav_muted_app"
android:icon="?attr/ic_setting"
android:title="@string/muted_app"/>
+
+
- Menu
open in pseudo account %1$s
pick image
+ this message contains hashtags, but message visibility is not public. normally hashtags are searchable only in public messages. Are you sure?
+ Copy
+ mute word
+ Muted words
+ Web search
+ Select and copy
+ Send
+ Word was muted.
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index d2e7c251..5e259e66 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -282,4 +282,12 @@
メニュー
疑似アカウント %1$s で開く
画像の選択
+ メッセージにハッシュタグが含まれていますが、公開範囲が公開ではありません。ハッシュタグは公開メッセージに含まれる場合のみ検索されることができます。続けてもよろしいですか?
+ コピー
+ 単語をミュート
+ ミュートした単語
+ Web検索
+ 選択してコピー…
+ 共有
+ 単語をミュートしました
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index ba8b9ab7..7af8359d 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -28,7 +28,6 @@
#eee
- #FFFFFFFF
#C0FFFFFF
@@ -44,6 +43,7 @@
#FF0000
#fff
#f00
+ #000000
@@ -78,7 +78,6 @@
#C0000000
#000
- #000
#222
@@ -91,5 +90,6 @@
#FF0000
#fff
#f00
+ #000
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3331a8e9..9f68c05e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -282,4 +282,13 @@
image
column header
foreground color
+ this message contains hashtags, but message visibility is not public. normally hashtags are searchable only in public messages. Are you sure?
+ Copy
+ Send
+ mute word
+ Select and copy
+ Web search
+ Muted words
+ Word was muted.
+ Clipboard updated.