keyword highlight
|
@ -181,6 +181,16 @@
|
|||
android:name=".ActMutedWord"
|
||||
android:label="@string/muted_word"
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".ActHighlightWordList"
|
||||
android:label="@string/highlight_word"
|
||||
/>
|
||||
<activity
|
||||
android:name=".ActHighlightWordEdit"
|
||||
android:label="@string/highlight_word"
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".ActColumnCustomize"
|
||||
android:label="@string/color_and_background"
|
||||
|
|
|
@ -48,6 +48,7 @@ import java.io.InputStream;
|
|||
|
||||
import jp.juggler.subwaytooter.api.TootApiClient;
|
||||
import jp.juggler.subwaytooter.api.TootApiResult;
|
||||
import jp.juggler.subwaytooter.api.TootParser;
|
||||
import jp.juggler.subwaytooter.api.TootTask;
|
||||
import jp.juggler.subwaytooter.api.TootTaskRunner;
|
||||
import jp.juggler.subwaytooter.api.entity.TootAccount;
|
||||
|
@ -680,7 +681,7 @@ public class ActAccountSetting extends AppCompatActivity
|
|||
@Override public TootApiResult background( @NonNull TootApiClient client ){
|
||||
TootApiResult result = client.request( "/api/v1/accounts/verify_credentials" );
|
||||
if( result != null && result.object != null ){
|
||||
data = TootAccount.parse( ActAccountSetting.this, account, result.object );
|
||||
data = new TootParser( ActAccountSetting.this, account ).account( result.object );
|
||||
if( data == null ) return new TootApiResult( "TootAccount parse failed." );
|
||||
}
|
||||
return result;
|
||||
|
@ -741,7 +742,7 @@ public class ActAccountSetting extends AppCompatActivity
|
|||
|
||||
TootApiResult result = client.request( "/api/v1/accounts/update_credentials", request_builder );
|
||||
if( result != null && result.object != null ){
|
||||
data = TootAccount.parse( ActAccountSetting.this, account, result.object );
|
||||
data = new TootParser( ActAccountSetting.this, account ).account( result.object );
|
||||
if( data == null ) return new TootApiResult( "TootAccount parse failed." );
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -0,0 +1,250 @@
|
|||
package jp.juggler.subwaytooter;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.jrummyapps.android.colorpicker.ColorPickerDialog;
|
||||
import com.jrummyapps.android.colorpicker.ColorPickerDialogListener;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import jp.juggler.subwaytooter.table.HighlightWord;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
|
||||
public class ActHighlightWordEdit extends AppCompatActivity
|
||||
implements View.OnClickListener, ColorPickerDialogListener, CompoundButton.OnCheckedChangeListener
|
||||
{
|
||||
static final LogCategory log = new LogCategory( "ActHighlightWordEdit" );
|
||||
|
||||
static final String EXTRA_ITEM = "item";
|
||||
|
||||
public static void open( @NonNull Activity activity, int request_code, @NonNull HighlightWord item ){
|
||||
try{
|
||||
Intent intent = new Intent( activity, ActHighlightWordEdit.class );
|
||||
intent.putExtra( EXTRA_ITEM, item.encodeJson().toString() );
|
||||
activity.startActivityForResult( intent, request_code );
|
||||
}catch( JSONException ex ){
|
||||
throw new RuntimeException( ex );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
HighlightWord item;
|
||||
|
||||
private void makeResult(){
|
||||
try{
|
||||
Intent data = new Intent();
|
||||
data.putExtra( EXTRA_ITEM, item.encodeJson().toString() );
|
||||
setResult( RESULT_OK, data );
|
||||
}catch( JSONException ex ){
|
||||
throw new RuntimeException( ex );
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void onBackPressed(){
|
||||
makeResult();
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override protected void onCreate( @Nullable Bundle savedInstanceState ){
|
||||
super.onCreate( savedInstanceState );
|
||||
App1.setActivityTheme( this, false );
|
||||
initUI();
|
||||
|
||||
try{
|
||||
if( savedInstanceState != null ){
|
||||
item = new HighlightWord( new JSONObject( savedInstanceState.getString( EXTRA_ITEM ) ) );
|
||||
}else{
|
||||
item = new HighlightWord( new JSONObject( getIntent().getStringExtra( EXTRA_ITEM ) ) );
|
||||
}
|
||||
}catch( Throwable ex ){
|
||||
throw new RuntimeException( "can't loading data", ex );
|
||||
}
|
||||
|
||||
showSampleText();
|
||||
|
||||
}
|
||||
|
||||
@Override protected void onDestroy(){
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
static final int REQUEST_CODE_NOTIFICATION_SOUND = 2;
|
||||
|
||||
@Override protected void onActivityResult( int requestCode, int resultCode, Intent data ){
|
||||
switch( requestCode ){
|
||||
default:
|
||||
super.onActivityResult( requestCode, resultCode, data );
|
||||
break;
|
||||
|
||||
case REQUEST_CODE_NOTIFICATION_SOUND:{
|
||||
if( resultCode == RESULT_OK ){
|
||||
// RINGTONE_PICKERからの選択されたデータを取得する
|
||||
Uri uri = (Uri) Utils.getExtraObject( data, RingtoneManager.EXTRA_RINGTONE_PICKED_URI );
|
||||
if( uri != null ){
|
||||
item.sound_uri = uri.toString();
|
||||
item.sound_type = HighlightWord.SOUND_TYPE_CUSTOM;
|
||||
swSound.setChecked( true );
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TextView tvName;
|
||||
private Switch swSound;
|
||||
|
||||
private void initUI(){
|
||||
setContentView( R.layout.act_highlight_edit );
|
||||
|
||||
tvName = findViewById( R.id.tvName );
|
||||
swSound = findViewById( R.id.swSound );
|
||||
swSound.setOnCheckedChangeListener( this );
|
||||
|
||||
findViewById( R.id.btnTextColorEdit ).setOnClickListener( this );
|
||||
findViewById( R.id.btnTextColorReset ).setOnClickListener( this );
|
||||
findViewById( R.id.btnBackgroundColorEdit ).setOnClickListener( this );
|
||||
findViewById( R.id.btnBackgroundColorReset ).setOnClickListener( this );
|
||||
findViewById( R.id.btnNotificationSoundEdit ).setOnClickListener( this );
|
||||
findViewById( R.id.btnNotificationSoundReset ).setOnClickListener( this );
|
||||
}
|
||||
|
||||
boolean bBusy = false;
|
||||
private void showSampleText(){
|
||||
bBusy = true;
|
||||
try{
|
||||
|
||||
swSound.setChecked( item.sound_type != HighlightWord.SOUND_TYPE_NONE );
|
||||
|
||||
tvName.setText( item.name );
|
||||
|
||||
int c = item.color_bg;
|
||||
if( c == 0 ){
|
||||
tvName.setBackgroundColor( 0 );
|
||||
}else{
|
||||
tvName.setBackgroundColor( c );
|
||||
}
|
||||
|
||||
c = item.color_fg;
|
||||
if( c == 0 ){
|
||||
tvName.setTextColor( Styler.getAttributeColor( this, android.R.attr.textColorPrimary ) );
|
||||
}else{
|
||||
tvName.setTextColor( c );
|
||||
}
|
||||
}finally{
|
||||
bBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void onClick( View v ){
|
||||
|
||||
switch( v.getId() ){
|
||||
|
||||
case R.id.btnTextColorEdit:
|
||||
openColorPicker( COLOR_DIALOG_ID_TEXT, item.color_fg );
|
||||
break;
|
||||
|
||||
case R.id.btnTextColorReset:
|
||||
item.color_fg = 0;
|
||||
showSampleText();
|
||||
break;
|
||||
|
||||
case R.id.btnBackgroundColorEdit:
|
||||
openColorPicker( COLOR_DIALOG_ID_BACKGROUND, item.color_bg );
|
||||
break;
|
||||
|
||||
case R.id.btnBackgroundColorReset:
|
||||
item.color_bg = 0;
|
||||
showSampleText();
|
||||
break;
|
||||
|
||||
case R.id.btnNotificationSoundEdit:
|
||||
openNotificationSoundPicker();
|
||||
break;
|
||||
|
||||
case R.id.btnNotificationSoundReset:
|
||||
item.sound_uri = null;
|
||||
item.sound_type = swSound.isChecked() ? HighlightWord.SOUND_TYPE_DEFAULT : HighlightWord.SOUND_TYPE_NONE;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override public void onCheckedChanged( CompoundButton buttonView, boolean isChecked ){
|
||||
if( bBusy ) return;
|
||||
|
||||
if( ! isChecked ){
|
||||
item.sound_type = HighlightWord.SOUND_TYPE_NONE;
|
||||
}else{
|
||||
item.sound_type = TextUtils.isEmpty( item.sound_uri ) ? HighlightWord.SOUND_TYPE_DEFAULT : HighlightWord.SOUND_TYPE_CUSTOM;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// using ColorPickerDialog
|
||||
|
||||
private static final int COLOR_DIALOG_ID_TEXT = 1;
|
||||
private static final int COLOR_DIALOG_ID_BACKGROUND = 2;
|
||||
|
||||
private void openColorPicker( int id, int initial_color ){
|
||||
ColorPickerDialog.Builder builder = ColorPickerDialog.newBuilder()
|
||||
.setDialogType( ColorPickerDialog.TYPE_CUSTOM )
|
||||
.setAllowPresets( true )
|
||||
.setShowAlphaSlider( id == COLOR_DIALOG_ID_BACKGROUND )
|
||||
.setDialogId( id );
|
||||
|
||||
if( initial_color != 0 ) builder.setColor( initial_color );
|
||||
|
||||
builder.show( this );
|
||||
|
||||
}
|
||||
|
||||
@Override public void onDialogDismissed( int dialogId ){
|
||||
}
|
||||
|
||||
@Override public void onColorSelected( int dialogId, int color ){
|
||||
switch( dialogId ){
|
||||
case COLOR_DIALOG_ID_TEXT:
|
||||
item.color_fg = 0xff000000 | color;
|
||||
break;
|
||||
case COLOR_DIALOG_ID_BACKGROUND:
|
||||
item.color_bg = color == 0 ? 0x01000000 : color;
|
||||
break;
|
||||
}
|
||||
showSampleText();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
private void openNotificationSoundPicker(){
|
||||
Intent intent = new Intent( RingtoneManager.ACTION_RINGTONE_PICKER );
|
||||
intent.putExtra( RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION );
|
||||
intent.putExtra( RingtoneManager.EXTRA_RINGTONE_TITLE, R.string.notification_sound );
|
||||
intent.putExtra( RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false );
|
||||
intent.putExtra( RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false );
|
||||
try{
|
||||
Uri uri = TextUtils.isEmpty( item.sound_uri ) ? null : Uri.parse( item.sound_uri );
|
||||
if( uri != null ){
|
||||
intent.putExtra( RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, uri );
|
||||
}
|
||||
}catch( Throwable ignored ){
|
||||
}
|
||||
|
||||
Intent chooser = Intent.createChooser( intent, getString( R.string.notification_sound ) );
|
||||
startActivityForResult( chooser, REQUEST_CODE_NOTIFICATION_SOUND );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,347 @@
|
|||
package jp.juggler.subwaytooter;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.media.Ringtone;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.text.TextUtils;
|
||||
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 org.json.JSONObject;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import jp.juggler.subwaytooter.dialog.DlgTextInput;
|
||||
import jp.juggler.subwaytooter.table.HighlightWord;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
|
||||
public class ActHighlightWordList extends AppCompatActivity implements View.OnClickListener {
|
||||
|
||||
private static final LogCategory log = new LogCategory( "ActHighlightWordList" );
|
||||
|
||||
DragListView listView;
|
||||
MyListAdapter listAdapter;
|
||||
|
||||
// @Override public void onBackPressed(){
|
||||
// setResult( RESULT_OK );
|
||||
// super.onBackPressed();
|
||||
// }
|
||||
|
||||
@Override
|
||||
protected void onCreate( @Nullable Bundle savedInstanceState ){
|
||||
super.onCreate( savedInstanceState );
|
||||
App1.setActivityTheme( this, false );
|
||||
initUI();
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override protected void onDestroy(){
|
||||
super.onDestroy();
|
||||
stopLastRingtone();
|
||||
}
|
||||
|
||||
private void initUI(){
|
||||
setContentView( R.layout.act_highlight_list );
|
||||
|
||||
Styler.fixHorizontalPadding2( findViewById( R.id.llContent ) );
|
||||
|
||||
// リストのアダプター
|
||||
listAdapter = new MyListAdapter();
|
||||
|
||||
// ハンドル部分をドラッグで並べ替えできるRecyclerView
|
||||
listView = 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_highlight_word ) );
|
||||
|
||||
listView.getRecyclerView().setVerticalScrollBarEnabled( true );
|
||||
|
||||
// リストを左右スワイプした
|
||||
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 );
|
||||
|
||||
// 左にスワイプした(右端にBGが見えた) なら要素を削除する
|
||||
if( swipedDirection == ListSwipeItem.SwipeDirection.LEFT ){
|
||||
Object o = item.getTag();
|
||||
if( o instanceof HighlightWord ){
|
||||
HighlightWord adapterItem = (HighlightWord) o;
|
||||
adapterItem.delete();
|
||||
listAdapter.removeItem( listAdapter.getPositionForItem( adapterItem ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
findViewById( R.id.btnAdd ).setOnClickListener( this );
|
||||
}
|
||||
|
||||
private void loadData(){
|
||||
|
||||
ArrayList< HighlightWord > tmp_list = new ArrayList<>();
|
||||
try{
|
||||
Cursor cursor = HighlightWord.createCursor();
|
||||
if( cursor != null ){
|
||||
try{
|
||||
while( cursor.moveToNext() ){
|
||||
HighlightWord item = new HighlightWord( cursor );
|
||||
tmp_list.add( item );
|
||||
}
|
||||
|
||||
}finally{
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}catch( Throwable ex ){
|
||||
log.trace( ex );
|
||||
}
|
||||
listAdapter.setItemList( tmp_list );
|
||||
}
|
||||
|
||||
@Override public void onClick( View v ){
|
||||
switch( v.getId() ){
|
||||
case R.id.btnAdd:
|
||||
create();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// リスト要素のViewHolder
|
||||
class MyViewHolder extends DragItemAdapter.ViewHolder implements View.OnClickListener {
|
||||
|
||||
final TextView tvName;
|
||||
final View btnSound;
|
||||
|
||||
MyViewHolder( final View viewRoot ){
|
||||
super( viewRoot
|
||||
, R.id.ivDragHandle // View ID。 ここを押すとドラッグ操作をすぐに開始する
|
||||
, false // 長押しでドラッグ開始するなら真
|
||||
);
|
||||
|
||||
tvName = viewRoot.findViewById( R.id.tvName );
|
||||
btnSound = viewRoot.findViewById( R.id.btnSound );
|
||||
|
||||
// リスト要素のビューが ListSwipeItem だった場合、Swipe操作を制御できる
|
||||
if( viewRoot instanceof ListSwipeItem ){
|
||||
ListSwipeItem lsi = (ListSwipeItem) viewRoot;
|
||||
lsi.setSwipeInStyle( ListSwipeItem.SwipeInStyle.SLIDE );
|
||||
lsi.setSupportedSwipeDirection( ListSwipeItem.SwipeDirection.LEFT );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void bind( HighlightWord item ){
|
||||
itemView.setTag( item ); // itemView は親クラスのメンバ変数
|
||||
tvName.setText( item.name );
|
||||
|
||||
int c = item.color_bg;
|
||||
if( c == 0 ){
|
||||
tvName.setBackgroundColor( 0 );
|
||||
}else{
|
||||
tvName.setBackgroundColor( c );
|
||||
}
|
||||
|
||||
c = item.color_fg;
|
||||
if( c == 0 ){
|
||||
tvName.setTextColor( Styler.getAttributeColor( ActHighlightWordList.this, android.R.attr.textColorPrimary ) );
|
||||
}else{
|
||||
tvName.setTextColor( c );
|
||||
}
|
||||
|
||||
btnSound.setVisibility( item.sound_type == HighlightWord.SOUND_TYPE_NONE ? View.GONE :View.VISIBLE);
|
||||
btnSound.setOnClickListener( this );
|
||||
btnSound.setTag( item );
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public boolean onItemLongClicked( View view ){
|
||||
// return false;
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void onItemClicked( View view ){
|
||||
Object o = view.getTag();
|
||||
if( o instanceof HighlightWord ){
|
||||
HighlightWord adapterItem = (HighlightWord) o;
|
||||
edit( adapterItem );
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void onClick( View v ){
|
||||
Object o = v.getTag();
|
||||
if( o instanceof HighlightWord ){
|
||||
sound( (HighlightWord) o );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// ドラッグ操作中のデータ
|
||||
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.btnSound ) ).setVisibility(
|
||||
( clickedView.findViewById( R.id.btnSound ) ).getVisibility()
|
||||
);
|
||||
dragView.findViewById( R.id.item_layout ).setBackgroundColor(
|
||||
Styler.getAttributeColor( ActHighlightWordList.this, R.attr.list_item_bg_pressed_dragged )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private class MyListAdapter extends DragItemAdapter< HighlightWord, MyViewHolder > {
|
||||
|
||||
MyListAdapter(){
|
||||
super();
|
||||
setHasStableIds( true );
|
||||
setItemList( new ArrayList< HighlightWord >() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public MyViewHolder onCreateViewHolder( ViewGroup parent, int viewType ){
|
||||
View view = getLayoutInflater().inflate( R.layout.lv_highlight_word, 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 getUniqueItemId( int position ){
|
||||
HighlightWord item = mItemList.get( position ); // mItemList は親クラスのメンバ変数
|
||||
return item.id;
|
||||
}
|
||||
}
|
||||
|
||||
private void create(){
|
||||
DlgTextInput.show( this, getString( R.string.new_item ), "", new DlgTextInput.Callback() {
|
||||
@Override public void onEmptyError(){
|
||||
Utils.showToast( ActHighlightWordList.this, true, R.string.word_empty );
|
||||
}
|
||||
|
||||
@Override public void onOK( Dialog dialog, String text ){
|
||||
HighlightWord item = HighlightWord.load( text );
|
||||
if( item == null ){
|
||||
item = new HighlightWord( text );
|
||||
item.save();
|
||||
loadData();
|
||||
}
|
||||
edit( item );
|
||||
try{
|
||||
dialog.dismiss();
|
||||
}catch( Throwable ignored ){
|
||||
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
private void edit( @NonNull HighlightWord item ){
|
||||
ActHighlightWordEdit.open( this, REQUEST_CODE_EDIT, item );
|
||||
}
|
||||
|
||||
private static final int REQUEST_CODE_EDIT = 1;
|
||||
|
||||
@Override protected void onActivityResult( int requestCode, int resultCode, Intent data ){
|
||||
if( requestCode == REQUEST_CODE_EDIT && resultCode == RESULT_OK && data != null ){
|
||||
try{
|
||||
HighlightWord item = new HighlightWord( new JSONObject( data.getStringExtra( ActHighlightWordEdit.EXTRA_ITEM ) ) );
|
||||
item.save();
|
||||
loadData();
|
||||
return;
|
||||
}catch( Throwable ex ){
|
||||
throw new RuntimeException( "can't loading data", ex );
|
||||
}
|
||||
}
|
||||
super.onActivityResult( requestCode, resultCode, data );
|
||||
}
|
||||
|
||||
WeakReference< Ringtone > last_ringtone;
|
||||
|
||||
private void stopLastRingtone(){
|
||||
Ringtone r = last_ringtone == null ? null : last_ringtone.get();
|
||||
if( r != null ){
|
||||
try{
|
||||
r.stop();
|
||||
}catch( Throwable ex ){
|
||||
log.trace( ex );
|
||||
}finally{
|
||||
last_ringtone = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sound( @NonNull HighlightWord item ){
|
||||
|
||||
stopLastRingtone();
|
||||
|
||||
if( item.sound_type == HighlightWord.SOUND_TYPE_NONE ) return;
|
||||
|
||||
if( item.sound_type == HighlightWord.SOUND_TYPE_CUSTOM
|
||||
&& ! TextUtils.isEmpty( item.sound_uri )
|
||||
){
|
||||
try{
|
||||
Ringtone ringtone = RingtoneManager.getRingtone( this, Uri.parse( item.sound_uri ) );
|
||||
if( ringtone != null ){
|
||||
last_ringtone = new WeakReference<>( ringtone );
|
||||
ringtone.play();
|
||||
return;
|
||||
}
|
||||
}catch( Throwable ex ){
|
||||
log.trace( ex );
|
||||
}
|
||||
}
|
||||
|
||||
Uri uri = RingtoneManager.getDefaultUri( RingtoneManager.TYPE_NOTIFICATION );
|
||||
try{
|
||||
Ringtone ringtone = RingtoneManager.getRingtone( this, uri );
|
||||
if( ringtone != null ){
|
||||
last_ringtone = new WeakReference<>( ringtone );
|
||||
ringtone.play();
|
||||
}
|
||||
}catch( Throwable ex ){
|
||||
log.trace( ex );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -748,7 +748,10 @@ public class ActMain extends AppCompatActivity
|
|||
|
||||
}else if( id == R.id.nav_muted_word ){
|
||||
startActivity( new Intent( this, ActMutedWord.class ) );
|
||||
|
||||
|
||||
}else if( id == R.id.nav_highlight_word ){
|
||||
startActivity( new Intent( this, ActHighlightWordList.class ) );
|
||||
|
||||
}else if( id == R.id.nav_app_about ){
|
||||
startActivityForResult( new Intent( this, ActAbout.class ), ActMain.REQUEST_APP_ABOUT );
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ public class ActMutedApp extends AppCompatActivity {
|
|||
}
|
||||
|
||||
private void initUI(){
|
||||
setContentView( R.layout.act_mute_app );
|
||||
setContentView( R.layout.act_word_list );
|
||||
|
||||
Styler.fixHorizontalPadding2( findViewById( R.id.llContent ) );
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ public class ActMutedWord extends AppCompatActivity {
|
|||
}
|
||||
|
||||
private void initUI(){
|
||||
setContentView( R.layout.act_mute_app );
|
||||
setContentView( R.layout.act_word_list );
|
||||
|
||||
Styler.fixHorizontalPadding2( findViewById( R.id.llContent ) );
|
||||
|
||||
|
|
|
@ -64,6 +64,7 @@ import jp.juggler.subwaytooter.api.entity.TootAttachment;
|
|||
import jp.juggler.subwaytooter.api.entity.TootMention;
|
||||
import jp.juggler.subwaytooter.api.entity.TootResults;
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus;
|
||||
import jp.juggler.subwaytooter.api.TootParser;
|
||||
import jp.juggler.subwaytooter.dialog.AccountPicker;
|
||||
import jp.juggler.subwaytooter.dialog.DlgDraftPicker;
|
||||
import jp.juggler.subwaytooter.dialog.DlgTextInput;
|
||||
|
@ -71,6 +72,7 @@ import jp.juggler.subwaytooter.table.AcctColor;
|
|||
import jp.juggler.subwaytooter.table.PostDraft;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.dialog.ActionsDialog;
|
||||
import jp.juggler.subwaytooter.util.CharacterGroup;
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions;
|
||||
import jp.juggler.subwaytooter.util.EmojiDecoder;
|
||||
import jp.juggler.subwaytooter.util.LinkClickContext;
|
||||
|
@ -409,7 +411,8 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
|
|||
sv = intent.getStringExtra( KEY_REPLY_STATUS );
|
||||
if( sv != null ){
|
||||
try{
|
||||
TootStatus reply_status = TootStatus.parse( ActPost.this, account, new JSONObject( sv ) );
|
||||
TootStatus reply_status = new TootParser( ActPost.this, account).status(new JSONObject( sv ));
|
||||
|
||||
|
||||
if( reply_status != null ){
|
||||
// CW をリプライ元に合わせる
|
||||
|
@ -874,7 +877,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
|
|||
|
||||
TootApiResult result = client.request( path );
|
||||
if( result != null && result.object != null ){
|
||||
TootResults tmp = TootResults.parse( ActPost.this, access_info, result.object );
|
||||
TootResults tmp = new TootParser( ActPost.this, access_info).results( result.object );
|
||||
if( tmp != null && tmp.statuses != null && ! tmp.statuses.isEmpty() ){
|
||||
target_status = tmp.statuses.get( 0 );
|
||||
}
|
||||
|
@ -1351,7 +1354,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
|
|||
Editable e = etContent.getEditableText();
|
||||
int len = e.length();
|
||||
char last_char = ( len <= 0 ? ' ' : e.charAt( len - 1 ) );
|
||||
if( ! EmojiDecoder.isWhitespaceBeforeEmoji( last_char ) ){
|
||||
if( ! CharacterGroup.isWhitespace( last_char ) ){
|
||||
e.append( " " ).append( pa.attachment.text_url );
|
||||
}else{
|
||||
e.append( pa.attachment.text_url );
|
||||
|
|
|
@ -20,7 +20,6 @@ import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory;
|
|||
import com.bumptech.glide.load.model.GlideUrl;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
@ -33,6 +32,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
import jp.juggler.subwaytooter.api.entity.TootAttachment;
|
||||
import jp.juggler.subwaytooter.table.AcctColor;
|
||||
import jp.juggler.subwaytooter.table.AcctSet;
|
||||
import jp.juggler.subwaytooter.table.HighlightWord;
|
||||
import jp.juggler.subwaytooter.table.MutedApp;
|
||||
import jp.juggler.subwaytooter.table.ClientInfo;
|
||||
import jp.juggler.subwaytooter.table.ContentWarning;
|
||||
|
@ -54,7 +54,6 @@ import okhttp3.CacheControl;
|
|||
import okhttp3.Call;
|
||||
import okhttp3.CipherSuite;
|
||||
import okhttp3.ConnectionSpec;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Response;
|
||||
import uk.co.chrisjenx.calligraphy.CalligraphyConfig;
|
||||
|
@ -77,7 +76,7 @@ public class App1 extends Application {
|
|||
public static final String FILE_PROVIDER_AUTHORITY = "jp.juggler.subwaytooter.FileProvider";
|
||||
|
||||
static final String DB_NAME = "app_db";
|
||||
static final int DB_VERSION = 20;
|
||||
static final int DB_VERSION = 21;
|
||||
|
||||
// 2017/4/25 v10 1=>2 SavedAccount に通知設定を追加
|
||||
// 2017/4/25 v10 1=>2 NotificationTracking テーブルを追加
|
||||
|
@ -97,6 +96,7 @@ public class App1 extends Application {
|
|||
// 2017/9/23 v161 17=>18 SavedAccountに項目追加
|
||||
// 2017/9/23 v161 18=>19 ClientInfoテーブルを置き換える
|
||||
// 2017/12/01 v175 19=>20 UserRelation に項目追加
|
||||
// 2018/1/03 v197 20=>21 HighlightWord テーブルを追加
|
||||
|
||||
private static DBOpenHelper db_open_helper;
|
||||
|
||||
|
@ -125,6 +125,7 @@ public class App1 extends Application {
|
|||
MutedWord.onDBCreate( db );
|
||||
PostDraft.onDBCreate( db );
|
||||
TagSet.onDBCreate( db );
|
||||
HighlightWord.onDBCreate( db );
|
||||
}
|
||||
|
||||
@Override public void onUpgrade( SQLiteDatabase db, int oldVersion, int newVersion ){
|
||||
|
@ -142,6 +143,7 @@ public class App1 extends Application {
|
|||
MutedWord.onDBUpgrade( db, oldVersion, newVersion );
|
||||
PostDraft.onDBUpgrade( db, oldVersion, newVersion );
|
||||
TagSet.onDBUpgrade( db, oldVersion, newVersion );
|
||||
HighlightWord.onDBUpgrade( db, oldVersion, newVersion );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -536,4 +538,11 @@ public class App1 extends Application {
|
|||
openCustomTab( activity, url );
|
||||
}
|
||||
}
|
||||
|
||||
public static void sound( @NonNull HighlightWord item ){
|
||||
if( app_state != null ){
|
||||
app_state.sound( item );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,6 +6,9 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.media.Ringtone;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
|
@ -24,6 +27,7 @@ import java.io.ByteArrayOutputStream;
|
|||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
|
@ -36,6 +40,7 @@ import java.util.regex.Pattern;
|
|||
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus;
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatusLike;
|
||||
import jp.juggler.subwaytooter.table.HighlightWord;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.MyClickableSpan;
|
||||
|
@ -484,4 +489,58 @@ public class AppState {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
private WeakReference< Ringtone > last_ringtone;
|
||||
|
||||
private void stopLastRingtone(){
|
||||
Ringtone r = last_ringtone == null ? null : last_ringtone.get();
|
||||
if( r != null ){
|
||||
try{
|
||||
r.stop();
|
||||
}catch( Throwable ex ){
|
||||
log.trace( ex );
|
||||
}finally{
|
||||
last_ringtone = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long last_sound;
|
||||
|
||||
void sound( @NonNull HighlightWord item ){
|
||||
// 短時間に何度もならないようにする
|
||||
long now = SystemClock.elapsedRealtime();
|
||||
if( now - last_sound < 500L ) return;
|
||||
last_sound = now;
|
||||
|
||||
stopLastRingtone();
|
||||
|
||||
if( item.sound_type == HighlightWord.SOUND_TYPE_NONE ) return;
|
||||
|
||||
if( item.sound_type == HighlightWord.SOUND_TYPE_CUSTOM
|
||||
&& ! TextUtils.isEmpty( item.sound_uri )
|
||||
){
|
||||
try{
|
||||
Ringtone ringtone = RingtoneManager.getRingtone( context, Uri.parse( item.sound_uri ) );
|
||||
if( ringtone != null ){
|
||||
last_ringtone = new WeakReference<>( ringtone );
|
||||
ringtone.play();
|
||||
return;
|
||||
}
|
||||
}catch( Throwable ex ){
|
||||
log.trace( ex );
|
||||
}
|
||||
}
|
||||
|
||||
Uri uri = RingtoneManager.getDefaultUri( RingtoneManager.TYPE_NOTIFICATION );
|
||||
try{
|
||||
Ringtone ringtone = RingtoneManager.getRingtone( context, uri );
|
||||
if( ringtone != null ){
|
||||
last_ringtone = new WeakReference<>( ringtone );
|
||||
ringtone.play();
|
||||
}
|
||||
}catch( Throwable ex ){
|
||||
log.trace( ex );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,8 @@ import jp.juggler.subwaytooter.api.entity.TootRelationShip;
|
|||
import jp.juggler.subwaytooter.api.entity.TootReport;
|
||||
import jp.juggler.subwaytooter.api.entity.TootResults;
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus;
|
||||
import jp.juggler.subwaytooter.api.TootParser;
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatusLike;
|
||||
import jp.juggler.subwaytooter.api.entity.TootTag;
|
||||
import jp.juggler.subwaytooter.api_msp.MSPClient;
|
||||
import jp.juggler.subwaytooter.api_msp.entity.MSPToot;
|
||||
|
@ -46,6 +48,7 @@ import jp.juggler.subwaytooter.api_tootsearch.TSClient;
|
|||
import jp.juggler.subwaytooter.api_tootsearch.entity.TSToot;
|
||||
import jp.juggler.subwaytooter.table.AcctColor;
|
||||
import jp.juggler.subwaytooter.table.AcctSet;
|
||||
import jp.juggler.subwaytooter.table.HighlightWord;
|
||||
import jp.juggler.subwaytooter.table.MutedApp;
|
||||
import jp.juggler.subwaytooter.table.MutedWord;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
|
@ -1171,6 +1174,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
private Pattern column_regex_filter;
|
||||
private HashSet< String > muted_app;
|
||||
private WordTrieTree muted_word;
|
||||
private WordTrieTree highlight_trie;
|
||||
|
||||
private void initFilter(){
|
||||
column_regex_filter = null;
|
||||
|
@ -1184,6 +1188,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
|
||||
muted_app = MutedApp.getNameSet();
|
||||
muted_word = MutedWord.getNameSet();
|
||||
highlight_trie = HighlightWord.getNameSet();
|
||||
}
|
||||
|
||||
private boolean isFiltered( @NonNull TootStatus status ){
|
||||
|
@ -1378,6 +1383,8 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
fireShowContent();
|
||||
|
||||
@SuppressLint("StaticFieldLeak") AsyncTask< Void, Void, TootApiResult > task = this.last_task = new AsyncTask< Void, Void, TootApiResult >() {
|
||||
TootParser parser = new TootParser( context, access_info).setHighlightTrie( highlight_trie );
|
||||
|
||||
|
||||
TootInstance instance_tmp;
|
||||
|
||||
|
@ -1398,7 +1405,10 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
TootApiResult result = client.request( path_base );
|
||||
if( result != null && result.array != null ){
|
||||
//
|
||||
TootStatus.List src = TootStatus.parseList( context, access_info, result.array, true );
|
||||
TootStatus.List src = new TootParser( context, access_info)
|
||||
.setPinned( true )
|
||||
.setHighlightTrie( highlight_trie )
|
||||
.statusList( result.array );
|
||||
|
||||
for( TootStatus status : src ){
|
||||
log.d( "pinned: %s %s", status.id, status.decoded_content );
|
||||
|
@ -1407,52 +1417,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
list_pinned = new ArrayList<>( src.size() );
|
||||
addWithFilter( list_pinned, src );
|
||||
|
||||
// 1.6rc では以下の理由により、40overの固定トゥートを取得することは困難である
|
||||
// - max_idを指定せずにAPIで取得すると適当な件数のリストが返ってくる。ソート順はpinした日時。max_idはリスト中の最後の要素のIDを返す
|
||||
// - max_idを指定してAPIで取得すると「ステータスIDがmax_idより小さい&pinされている」トゥートをpin日時順にソートしたものが返ってくる
|
||||
// - max_idはpin日時を考慮していないのだからページング用のパラメータとしては全く不適切である
|
||||
// - 取得できるステータスにはpinされた日時は含まれない
|
||||
// //
|
||||
// // pinステータスは独自にページ管理する
|
||||
// long time_start = SystemClock.elapsedRealtime();
|
||||
// String max_id = parseMaxId( result );
|
||||
// char delimiter = ( - 1 != path_base.indexOf( '?' ) ? '&' : '?' );
|
||||
// for( ; ; ){
|
||||
//
|
||||
// if( client.isCancelled() ){
|
||||
// log.d( "loading-statuses-pinned: cancelled." );
|
||||
// break;
|
||||
// }
|
||||
// if( max_id == null ){
|
||||
// log.d( "loading-statuses-pinned: max_id is null." );
|
||||
// break;
|
||||
// }
|
||||
// if( src.isEmpty() ){
|
||||
// log.d( "loading-statuses-pinned: previous response is empty." );
|
||||
// break;
|
||||
// }
|
||||
// if( SystemClock.elapsedRealtime() - time_start > LOOP_TIMEOUT ){
|
||||
// log.d( "loading-statuses-pinned: timeout." );
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// String path = path_base + delimiter + "max_id=" + max_id;
|
||||
// TootApiResult result2 = client.request( path );
|
||||
// if( result2 == null || result2.array == null ){
|
||||
// log.d( "loading-statuses-pinned: error or cancelled." );
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// src = TootStatus.parseList( context, access_info, result2.array ,true);
|
||||
// for(TootStatus status : src ){
|
||||
// log.d("pinned: %s %s",status.id, status.decoded_content);
|
||||
// }
|
||||
//
|
||||
// addWithFilter( list_pinned, src );
|
||||
//
|
||||
// // pinnedステータスは独自にページ管理する
|
||||
// max_id = parseMaxId( result2 );
|
||||
// }
|
||||
// pinned tootにはページングの概念はない
|
||||
}
|
||||
log.d( "getStatusesPinned: list size=%s", list_pinned == null ? - 1 : list_pinned.size() );
|
||||
}
|
||||
|
@ -1460,12 +1425,14 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
ArrayList< Object > list_tmp;
|
||||
|
||||
TootApiResult getStatuses( TootApiClient client, String path_base ){
|
||||
|
||||
|
||||
long time_start = SystemClock.elapsedRealtime();
|
||||
TootApiResult result = client.request( path_base );
|
||||
if( result != null && result.array != null ){
|
||||
saveRange( result, true, true );
|
||||
//
|
||||
TootStatus.List src = TootStatus.parseList( context, access_info, result.array );
|
||||
TootStatus.List src = parser.statusList( result.array );
|
||||
list_tmp = new ArrayList<>( src.size() );
|
||||
addWithFilter( list_tmp, src );
|
||||
//
|
||||
|
@ -1502,7 +1469,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
break;
|
||||
}
|
||||
|
||||
src = TootStatus.parseList( context, access_info, result2.array );
|
||||
src = parser.statusList( result2.array );
|
||||
|
||||
addWithFilter( list_tmp, src );
|
||||
|
||||
|
@ -1564,7 +1531,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
if( result != null && result.array != null ){
|
||||
saveRange( result, true, true );
|
||||
//
|
||||
TootNotification.List src = TootNotification.parseList( context, access_info, result.array );
|
||||
TootNotification.List src = parser.notificationList( result.array );
|
||||
list_tmp = new ArrayList<>( src.size() );
|
||||
addWithFilter( list_tmp, src );
|
||||
//
|
||||
|
@ -1605,7 +1572,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
break;
|
||||
}
|
||||
|
||||
src = TootNotification.parseList( context, access_info, result2.array );
|
||||
src = parser.notificationList( result2.array );
|
||||
|
||||
addWithFilter( list_tmp, src );
|
||||
|
||||
|
@ -1741,7 +1708,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
result = client.request(
|
||||
String.format( Locale.JAPAN, PATH_STATUSES, status_id ) );
|
||||
if( result == null || result.object == null ) return result;
|
||||
TootStatus target_status = TootStatus.parse( context, access_info, result.object );
|
||||
TootStatus target_status = parser.status( result.object );
|
||||
if( target_status == null ){
|
||||
return new TootApiResult( "TootStatus parse failed." );
|
||||
}
|
||||
|
@ -1753,7 +1720,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
if( result == null || result.object == null ) return result;
|
||||
|
||||
// 一つのリストにまとめる
|
||||
TootContext conversation_context = TootContext.parse( context, access_info, result.object );
|
||||
TootContext conversation_context = parser.context( result.object );
|
||||
if( conversation_context != null ){
|
||||
list_tmp = new ArrayList<>( 1 + conversation_context.ancestors.size() + conversation_context.descendants.size() );
|
||||
if( conversation_context.ancestors != null )
|
||||
|
@ -1792,7 +1759,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
result = client.request( path );
|
||||
if( result == null || result.object == null ) return result;
|
||||
|
||||
TootResults tmp = TootResults.parse( context, access_info, result.object );
|
||||
TootResults tmp = parser.results( result.object );
|
||||
if( tmp != null ){
|
||||
list_tmp = new ArrayList<>();
|
||||
list_tmp.addAll( tmp.hashtags );
|
||||
|
@ -1831,7 +1798,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
// max_id の更新
|
||||
max_id = MSPClient.getMaxId( result.array, max_id );
|
||||
// リストデータの用意
|
||||
MSPToot.List search_result = MSPToot.parseList( context, access_info, result.array );
|
||||
MSPToot.List search_result = MSPToot.parseList(parser, result.array );
|
||||
if( search_result != null ){
|
||||
list_tmp = new ArrayList<>();
|
||||
addWithFilter( list_tmp, search_result );
|
||||
|
@ -1867,7 +1834,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
// max_id の更新
|
||||
max_id = TSClient.getMaxId( result.object, max_id );
|
||||
// リストデータの用意
|
||||
TSToot.List search_result = TSToot.parseList( context, access_info, result.object );
|
||||
TSToot.List search_result = TSToot.parseList( parser, result.object );
|
||||
list_tmp = new ArrayList<>();
|
||||
addWithFilter( list_tmp, search_result );
|
||||
if( search_result.isEmpty() ){
|
||||
|
@ -2194,6 +2161,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
mRefreshLoadingError = null;
|
||||
|
||||
@SuppressLint("StaticFieldLeak") AsyncTask< Void, Void, TootApiResult > task = this.last_task = new AsyncTask< Void, Void, TootApiResult >() {
|
||||
TootParser parser = new TootParser( context, access_info).setHighlightTrie( highlight_trie );
|
||||
|
||||
TootApiResult getAccountList( TootApiClient client, String path_base ){
|
||||
long time_start = SystemClock.elapsedRealtime();
|
||||
|
@ -2417,7 +2385,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
if( result != null && result.array != null ){
|
||||
saveRange( result, bBottom, ! bBottom );
|
||||
list_tmp = new ArrayList<>();
|
||||
TootNotification.List src = TootNotification.parseList( context, access_info, result.array );
|
||||
TootNotification.List src = parser.notificationList( result.array );
|
||||
addWithFilter( list_tmp, src );
|
||||
|
||||
if( ! bBottom ){
|
||||
|
@ -2462,7 +2430,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
break;
|
||||
}
|
||||
|
||||
src = TootNotification.parseList( context, access_info, result2.array );
|
||||
src = parser.notificationList( result2.array );
|
||||
if( ! src.isEmpty() ){
|
||||
addWithFilter( list_tmp, src );
|
||||
PollingWorker.injectData( context, access_info.db_id, src );
|
||||
|
@ -2507,7 +2475,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
break;
|
||||
}
|
||||
|
||||
src = TootNotification.parseList( context, access_info, result2.array );
|
||||
src = parser.notificationList( result2.array );
|
||||
|
||||
addWithFilter( list_tmp, src );
|
||||
|
||||
|
@ -2524,6 +2492,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
ArrayList< Object > list_tmp;
|
||||
|
||||
TootApiResult getStatusList( TootApiClient client, String path_base ){
|
||||
|
||||
long time_start = SystemClock.elapsedRealtime();
|
||||
|
||||
char delimiter = ( - 1 != path_base.indexOf( '?' ) ? '&' : '?' );
|
||||
|
@ -2532,7 +2501,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
TootApiResult result = client.request( addRange( bBottom, path_base ) );
|
||||
if( result != null && result.array != null ){
|
||||
saveRange( result, bBottom, ! bBottom );
|
||||
TootStatus.List src = TootStatus.parseList( context, access_info, result.array );
|
||||
TootStatus.List src = parser.statusList( result.array );
|
||||
list_tmp = new ArrayList<>();
|
||||
|
||||
addWithFilter( list_tmp, src );
|
||||
|
@ -2576,7 +2545,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
break;
|
||||
}
|
||||
|
||||
src = TootStatus.parseList( context, access_info, result2.array );
|
||||
src = parser.statusList( result2.array );
|
||||
|
||||
addWithFilter( list_tmp, src );
|
||||
|
||||
|
@ -2632,7 +2601,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
break;
|
||||
}
|
||||
|
||||
src = TootStatus.parseList( context, access_info, result2.array );
|
||||
src = parser.statusList( result2.array );
|
||||
addWithFilter( list_tmp, src );
|
||||
}
|
||||
}
|
||||
|
@ -2773,7 +2742,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
// max_id の更新
|
||||
max_id = MSPClient.getMaxId( result.array, max_id );
|
||||
// リストデータの用意
|
||||
MSPToot.List search_result = MSPToot.parseList( context, access_info, result.array );
|
||||
MSPToot.List search_result = MSPToot.parseList( parser, result.array );
|
||||
if( search_result != null ){
|
||||
list_tmp = new ArrayList<>();
|
||||
addWithFilter( list_tmp, search_result );
|
||||
|
@ -2813,7 +2782,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
// max_id の更新
|
||||
max_id = TSClient.getMaxId( result.object, max_id );
|
||||
// リストデータの用意
|
||||
TSToot.List search_result = TSToot.parseList( context, access_info, result.object );
|
||||
TSToot.List search_result = TSToot.parseList( parser, result.object );
|
||||
list_tmp = new ArrayList<>();
|
||||
addWithFilter( list_tmp, search_result );
|
||||
}
|
||||
|
@ -2863,6 +2832,8 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 事前にスクロール位置を覚えておく
|
||||
ScrollPosition sp = null;
|
||||
ColumnViewHolder holder = getViewHolder();
|
||||
|
@ -2879,6 +2850,16 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
}
|
||||
}else{
|
||||
|
||||
for( Object o : list_new ){
|
||||
if( o instanceof TootStatusLike){
|
||||
TootStatusLike s = (TootStatusLike) o;
|
||||
if( s.highlight_sound != null ){
|
||||
App1.sound( s.highlight_sound );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int status_index = - 1;
|
||||
for( int i = 0, ie = list_new.size() ; i < ie ; ++ i ){
|
||||
Object o = list_new.get( i );
|
||||
|
@ -2949,6 +2930,9 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
final String since_id = gap.since_id;
|
||||
ArrayList< Object > list_tmp;
|
||||
|
||||
TootParser parser = new TootParser( context, access_info).setHighlightTrie( highlight_trie );
|
||||
|
||||
|
||||
TootApiResult getAccountList( TootApiClient client, String path_base ){
|
||||
long time_start = SystemClock.elapsedRealtime();
|
||||
char delimiter = ( - 1 != path_base.indexOf( '?' ) ? '&' : '?' );
|
||||
|
@ -3076,7 +3060,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
}
|
||||
|
||||
result = r2;
|
||||
TootNotification.List src = TootNotification.parseList( context, access_info, r2.array );
|
||||
TootNotification.List src = parser.notificationList( r2.array );
|
||||
|
||||
if( src.isEmpty() ){
|
||||
log.d( "gap-notification: empty." );
|
||||
|
@ -3132,7 +3116,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
// 成功した場合はそれを返したい
|
||||
result = r2;
|
||||
|
||||
TootStatus.List src = TootStatus.parseList( context, access_info, r2.array );
|
||||
TootStatus.List src = parser.statusList( r2.array );
|
||||
if( src.size() == 0 ){
|
||||
// 直前の取得でカラのデータが帰ってきたら終了
|
||||
log.d( "gap-statuses: empty." );
|
||||
|
@ -3324,6 +3308,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
task.executeOnExecutor( App1.task_executor );
|
||||
}
|
||||
|
||||
|
||||
private static final int heightSpec = View.MeasureSpec.makeMeasureSpec( 0, View.MeasureSpec.UNSPECIFIED );
|
||||
|
||||
private static int getListItemHeight( ListView listView, int idx ){
|
||||
|
@ -3465,6 +3450,16 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
}
|
||||
}
|
||||
|
||||
for( Object o : list_new ){
|
||||
if( o instanceof TootStatusLike){
|
||||
TootStatusLike s = (TootStatusLike) o;
|
||||
if( s.highlight_sound != null ){
|
||||
App1.sound( s.highlight_sound );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
list_data.addAll( 0, list_new );
|
||||
fireShowContent();
|
||||
int added = list_new.size();
|
||||
|
@ -3702,6 +3697,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
app_state.stream_reader.register(
|
||||
access_info
|
||||
, stream_path
|
||||
, highlight_trie
|
||||
, this
|
||||
);
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ import jp.juggler.subwaytooter.api.TootApiClient;
|
|||
import jp.juggler.subwaytooter.api.TootApiResult;
|
||||
import jp.juggler.subwaytooter.api.entity.TootNotification;
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus;
|
||||
import jp.juggler.subwaytooter.api.TootParser;
|
||||
import jp.juggler.subwaytooter.table.AcctColor;
|
||||
import jp.juggler.subwaytooter.table.MutedApp;
|
||||
import jp.juggler.subwaytooter.table.MutedWord;
|
||||
|
@ -1031,6 +1032,8 @@ public class PollingWorker {
|
|||
){
|
||||
nr = NotificationTracking.load( account.db_id );
|
||||
|
||||
TootParser parser = new TootParser( context,account );
|
||||
|
||||
// まずキャッシュされたデータを処理する
|
||||
if( nr.last_data != null ){
|
||||
try{
|
||||
|
@ -1038,7 +1041,7 @@ public class PollingWorker {
|
|||
for( int i = array.length() - 1 ; i >= 0 ; -- i ){
|
||||
if( job.isJobCancelled() ) return;
|
||||
JSONObject src = array.optJSONObject( i );
|
||||
update_sub( src, data_list, muted_app, muted_word );
|
||||
update_sub( src, data_list, muted_app, muted_word ,parser);
|
||||
}
|
||||
}catch( JSONException ex ){
|
||||
log.trace( ex );
|
||||
|
@ -1054,6 +1057,7 @@ public class PollingWorker {
|
|||
|
||||
client.setAccount( account );
|
||||
|
||||
|
||||
for( int nTry = 0 ; nTry < 4 ; ++ nTry ){
|
||||
if( job.isJobCancelled() ) return;
|
||||
|
||||
|
@ -1071,7 +1075,7 @@ public class PollingWorker {
|
|||
JSONArray array = result.array;
|
||||
for( int i = array.length() - 1 ; i >= 0 ; -- i ){
|
||||
JSONObject src = array.optJSONObject( i );
|
||||
update_sub( src, data_list, muted_app, muted_word );
|
||||
update_sub( src, data_list, muted_app, muted_word ,parser);
|
||||
}
|
||||
}catch( JSONException ex ){
|
||||
log.trace( ex );
|
||||
|
@ -1130,6 +1134,7 @@ public class PollingWorker {
|
|||
, @NonNull ArrayList< Data > data_list
|
||||
, @NonNull HashSet< String > muted_app
|
||||
, @NonNull WordTrieTree muted_word
|
||||
, @NonNull TootParser parser
|
||||
) throws JSONException{
|
||||
|
||||
if( nr.nid_read == 0 || nr.nid_show == 0 ){
|
||||
|
@ -1168,7 +1173,7 @@ public class PollingWorker {
|
|||
return;
|
||||
}
|
||||
|
||||
TootNotification notification = TootNotification.parse( context, account, src );
|
||||
TootNotification notification = parser.notification( src );
|
||||
if( notification == null ){
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -21,11 +21,11 @@ import jp.juggler.subwaytooter.api.TootApiClient;
|
|||
import jp.juggler.subwaytooter.api.TootApiResult;
|
||||
import jp.juggler.subwaytooter.api.TootTask;
|
||||
import jp.juggler.subwaytooter.api.TootTaskRunner;
|
||||
import jp.juggler.subwaytooter.api.entity.TootNotification;
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus;
|
||||
import jp.juggler.subwaytooter.api.TootParser;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
import jp.juggler.subwaytooter.util.WordTrieTree;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.WebSocket;
|
||||
|
@ -35,7 +35,7 @@ import okhttp3.WebSocketListener;
|
|||
static final LogCategory log = new LogCategory( "StreamReader" );
|
||||
|
||||
static final Pattern reNumber = Pattern.compile( "([-]?\\d+)" );
|
||||
static final Pattern reAuthorizeError = Pattern.compile( "authorize",Pattern.CASE_INSENSITIVE );
|
||||
static final Pattern reAuthorizeError = Pattern.compile( "authorize", Pattern.CASE_INSENSITIVE );
|
||||
|
||||
interface Callback {
|
||||
void onStreamingMessage( String event_type, Object o );
|
||||
|
@ -45,10 +45,16 @@ import okhttp3.WebSocketListener;
|
|||
final SavedAccount access_info;
|
||||
final String end_point;
|
||||
final LinkedList< Callback > callback_list = new LinkedList<>();
|
||||
final TootParser parser;
|
||||
|
||||
Reader( SavedAccount access_info, String end_point ){
|
||||
Reader( SavedAccount access_info, String end_point , WordTrieTree highlight_trie){
|
||||
this.access_info = access_info;
|
||||
this.end_point = end_point;
|
||||
this.parser = new TootParser( context, access_info ).setHighlightTrie( highlight_trie );
|
||||
}
|
||||
|
||||
synchronized void updateHighlight( WordTrieTree highlight_trie ){
|
||||
this.parser.setHighlightTrie( highlight_trie );
|
||||
}
|
||||
|
||||
synchronized void addCallback( @NonNull Callback stream_callback ){
|
||||
|
@ -121,7 +127,7 @@ import okhttp3.WebSocketListener;
|
|||
static final String PAYLOAD = "payload";
|
||||
|
||||
// ストリーミングAPIのペイロード部分をTootStatus,TootNotification,整数IDのどれかに解釈する
|
||||
private Object parsePayload( @NonNull String event, @NonNull JSONObject parent, @NonNull String parent_text ){
|
||||
synchronized private Object parsePayload( @NonNull String event, @NonNull JSONObject parent, @NonNull String parent_text ){
|
||||
try{
|
||||
if( parent.isNull( PAYLOAD ) ){
|
||||
return null;
|
||||
|
@ -135,11 +141,11 @@ import okhttp3.WebSocketListener;
|
|||
|
||||
case "update":
|
||||
// ここを通るケースはまだ確認できていない
|
||||
return TootStatus.parse( context, access_info, src );
|
||||
return parser.status( src );
|
||||
|
||||
case "notification":
|
||||
// ここを通るケースはまだ確認できていない
|
||||
return TootNotification.parse( context, access_info, src );
|
||||
return parser.notification( src );
|
||||
|
||||
default:
|
||||
// ここを通るケースはまだ確認できていない
|
||||
|
@ -161,11 +167,11 @@ import okhttp3.WebSocketListener;
|
|||
switch( event ){
|
||||
case "update":
|
||||
// 2017/8/24 18:37 mastodon.juggler.jpでここを通った
|
||||
return TootStatus.parse( context, access_info, src );
|
||||
return parser.status( src );
|
||||
|
||||
case "notification":
|
||||
// 2017/8/24 18:37 mastodon.juggler.jpでここを通った
|
||||
return TootNotification.parse( context, access_info, src );
|
||||
return parser.notification( src );
|
||||
|
||||
default:
|
||||
// ここを通るケースはまだ確認できていない
|
||||
|
@ -222,14 +228,13 @@ import okhttp3.WebSocketListener;
|
|||
public void onFailure( WebSocket webSocket, Throwable ex, Response response ){
|
||||
log.e( ex, "WebSocket onFailure. url=%s .", webSocket.request().url() );
|
||||
|
||||
|
||||
bListening.set( false );
|
||||
handler.removeCallbacks( proc_reconnect );
|
||||
|
||||
|
||||
if( ex instanceof ProtocolException ){
|
||||
String msg = ex.getMessage();
|
||||
if(msg != null && reAuthorizeError.matcher( msg).find() ){
|
||||
log.e("seems old instance that does not support streaming public timeline without access token. don't retry...");
|
||||
if( msg != null && reAuthorizeError.matcher( msg ).find() ){
|
||||
log.e( "seems old instance that does not support streaming public timeline without access token. don't retry..." );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -285,16 +290,17 @@ import okhttp3.WebSocketListener;
|
|||
this.handler = handler;
|
||||
}
|
||||
|
||||
private Reader prepareReader( @NonNull SavedAccount access_info, @NonNull String end_point ){
|
||||
private Reader prepareReader( @NonNull SavedAccount access_info, @NonNull String end_point ,WordTrieTree highlight_trie ){
|
||||
synchronized( reader_list ){
|
||||
for( Reader reader : reader_list ){
|
||||
if( reader.access_info.db_id == access_info.db_id
|
||||
&& reader.end_point.equals( end_point )
|
||||
){
|
||||
if( highlight_trie != null ) reader.updateHighlight( highlight_trie );
|
||||
return reader;
|
||||
}
|
||||
}
|
||||
Reader reader = new Reader( access_info, end_point );
|
||||
Reader reader = new Reader( access_info, end_point ,highlight_trie);
|
||||
reader_list.add( reader );
|
||||
return reader;
|
||||
}
|
||||
|
@ -332,9 +338,9 @@ import okhttp3.WebSocketListener;
|
|||
}
|
||||
|
||||
// onResume や ロード完了ののタイミングで登録される
|
||||
void register( @NonNull SavedAccount access_info, @NonNull String end_point, @NonNull Callback stream_callback ){
|
||||
void register( @NonNull SavedAccount access_info, @NonNull String end_point, @Nullable WordTrieTree highlight_trie , @NonNull Callback stream_callback ){
|
||||
|
||||
final Reader reader = prepareReader( access_info, end_point );
|
||||
final Reader reader = prepareReader( access_info, end_point ,highlight_trie);
|
||||
reader.addCallback( stream_callback );
|
||||
|
||||
if( ! reader.bListening.get() ){
|
||||
|
|
|
@ -21,6 +21,7 @@ import jp.juggler.subwaytooter.api.TootTaskRunner;
|
|||
import jp.juggler.subwaytooter.api.entity.TootResults;
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus;
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatusLike;
|
||||
import jp.juggler.subwaytooter.api.TootParser;
|
||||
import jp.juggler.subwaytooter.api_msp.entity.MSPToot;
|
||||
import jp.juggler.subwaytooter.api_tootsearch.entity.TSToot;
|
||||
import jp.juggler.subwaytooter.dialog.AccountPicker;
|
||||
|
@ -95,7 +96,7 @@ public class Action_Toot {
|
|||
return result;
|
||||
}
|
||||
target_status = null;
|
||||
TootResults tmp = TootResults.parse( activity, access_info, result.object );
|
||||
TootResults tmp = new TootParser( activity, access_info).results( result.object );
|
||||
if( tmp != null ){
|
||||
if( tmp.statuses != null && ! tmp.statuses.isEmpty() ){
|
||||
target_status = tmp.statuses.get( 0 );
|
||||
|
@ -125,7 +126,7 @@ public class Action_Toot {
|
|||
)
|
||||
, request_builder );
|
||||
if( result != null && result.object != null ){
|
||||
new_status = TootStatus.parse( activity, access_info, result.object );
|
||||
new_status = new TootParser( activity, access_info).status( result.object );
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -265,6 +266,8 @@ public class Action_Toot {
|
|||
new TootTaskRunner( activity, false ).run( access_info, new TootTask() {
|
||||
@Override public TootApiResult background( @NonNull TootApiClient client ){
|
||||
|
||||
TootParser parser = new TootParser( activity, access_info);
|
||||
|
||||
TootApiResult result;
|
||||
|
||||
TootStatusLike target_status;
|
||||
|
@ -278,7 +281,7 @@ public class Action_Toot {
|
|||
return result;
|
||||
}
|
||||
target_status = null;
|
||||
TootResults tmp = TootResults.parse( activity, access_info, result.object );
|
||||
TootResults tmp = parser.results( result.object );
|
||||
if( tmp != null ){
|
||||
if( tmp.statuses != null && ! tmp.statuses.isEmpty() ){
|
||||
target_status = tmp.statuses.get( 0 );
|
||||
|
@ -306,7 +309,7 @@ public class Action_Toot {
|
|||
|
||||
if( result != null && result.object != null ){
|
||||
|
||||
new_status = TootStatus.parse( activity, access_info, result.object );
|
||||
new_status = parser .status( result.object );
|
||||
|
||||
// reblogはreblogを表すStatusを返す
|
||||
// unreblogはreblogしたStatusを返す
|
||||
|
@ -612,7 +615,7 @@ public class Action_Toot {
|
|||
path = path + "&resolve=1";
|
||||
result = client.request( path );
|
||||
if( result != null && result.object != null ){
|
||||
TootResults tmp = TootResults.parse( activity, access_info, result.object );
|
||||
TootResults tmp = new TootParser( activity, access_info).results( result.object );
|
||||
if( tmp != null && tmp.statuses != null && ! tmp.statuses.isEmpty() ){
|
||||
TootStatus status = tmp.statuses.get( 0 );
|
||||
local_status_id = status.id;
|
||||
|
@ -672,7 +675,7 @@ public class Action_Toot {
|
|||
)
|
||||
, request_builder );
|
||||
if( result != null && result.object != null ){
|
||||
new_status = TootStatus.parse( activity, access_info, result.object );
|
||||
new_status = new TootParser( activity, access_info).status( result.object );
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -759,7 +762,7 @@ public class Action_Toot {
|
|||
|
||||
TootApiResult result = client.request( path );
|
||||
if( result != null && result.object != null ){
|
||||
TootResults tmp = TootResults.parse( activity, access_info, result.object );
|
||||
TootResults tmp = new TootParser( activity, access_info).results( result.object );
|
||||
if( tmp != null && tmp.statuses != null && ! tmp.statuses.isEmpty() ){
|
||||
local_status = tmp.statuses.get( 0 );
|
||||
log.d( "status id conversion %s => %s", remote_status_url, local_status.id );
|
||||
|
@ -808,7 +811,7 @@ public class Action_Toot {
|
|||
);
|
||||
|
||||
if( result != null && result.object != null ){
|
||||
local_status = TootStatus.parse( activity, access_info, result.object );
|
||||
local_status = new TootParser( activity, access_info).status( result.object );
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
@ -22,6 +22,7 @@ import jp.juggler.subwaytooter.api.entity.TootAccount;
|
|||
import jp.juggler.subwaytooter.api.entity.TootRelationShip;
|
||||
import jp.juggler.subwaytooter.api.entity.TootResults;
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus;
|
||||
import jp.juggler.subwaytooter.api.TootParser;
|
||||
import jp.juggler.subwaytooter.dialog.AccountPicker;
|
||||
import jp.juggler.subwaytooter.dialog.ReportForm;
|
||||
import jp.juggler.subwaytooter.table.AcctColor;
|
||||
|
@ -224,7 +225,7 @@ public class Action_User {
|
|||
|
||||
if( result != null && result.object != null ){
|
||||
|
||||
TootResults tmp = TootResults.parse( activity, access_info, result.object );
|
||||
TootResults tmp = new TootParser( activity, access_info ).results( result.object );
|
||||
if( tmp != null ){
|
||||
if( tmp.accounts != null && ! tmp.accounts.isEmpty() ){
|
||||
who_local = tmp.accounts.get( 0 );
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
package jp.juggler.subwaytooter.api;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import jp.juggler.subwaytooter.api.entity.TootAccount;
|
||||
import jp.juggler.subwaytooter.api.entity.TootContext;
|
||||
import jp.juggler.subwaytooter.api.entity.TootNotification;
|
||||
import jp.juggler.subwaytooter.api.entity.TootResults;
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.util.WordTrieTree;
|
||||
|
||||
public class TootParser {
|
||||
@NonNull public final Context context;
|
||||
@NonNull public final SavedAccount access_info;
|
||||
|
||||
public TootParser( @NonNull Context context, @NonNull SavedAccount access_info ){
|
||||
this.context = context;
|
||||
this.access_info = access_info;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// parser options
|
||||
|
||||
// プロフィールカラムからpinned TL を読んだ時だけ真
|
||||
public boolean isPinned;
|
||||
|
||||
public TootParser setPinned( boolean isPinned ){
|
||||
this.isPinned = isPinned;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nullable public WordTrieTree highlight_trie;
|
||||
public TootParser setHighlightTrie( @Nullable WordTrieTree highlight_trie ){
|
||||
this.highlight_trie = highlight_trie;
|
||||
return this;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// parser methods
|
||||
|
||||
@Nullable public TootAccount account( @Nullable JSONObject src ){
|
||||
return TootAccount.parse( context,access_info, src );
|
||||
}
|
||||
|
||||
@Nullable public TootStatus status( @Nullable JSONObject src ){
|
||||
return TootStatus.parse( this, src );
|
||||
}
|
||||
|
||||
@NonNull public TootStatus.List statusList( @Nullable JSONArray array ){
|
||||
return TootStatus.parseList( this, array );
|
||||
}
|
||||
|
||||
@Nullable public TootNotification notification( @Nullable JSONObject src ){
|
||||
return TootNotification.parse(this,src);
|
||||
}
|
||||
|
||||
@NonNull public TootNotification.List notificationList( @Nullable JSONArray src ){
|
||||
return TootNotification.parseList( this, src );
|
||||
}
|
||||
|
||||
@Nullable public TootResults results( @Nullable JSONObject src ){
|
||||
return TootResults.parse( this, src );
|
||||
}
|
||||
|
||||
@Nullable public TootContext context( @Nullable JSONObject src ){
|
||||
return TootContext.parse(this, src );
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,12 +1,11 @@
|
|||
package jp.juggler.subwaytooter.api.entity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.api.TootParser;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
|
||||
public class TootContext {
|
||||
|
@ -19,12 +18,12 @@ public class TootContext {
|
|||
public TootStatus.List descendants;
|
||||
|
||||
@Nullable
|
||||
public static TootContext parse( @NonNull Context context, @NonNull SavedAccount access_info, JSONObject src ){
|
||||
public static TootContext parse( @NonNull TootParser parser , JSONObject src ){
|
||||
if( src == null ) return null;
|
||||
try{
|
||||
TootContext dst = new TootContext();
|
||||
dst.ancestors = TootStatus.parseList( context, access_info, src.optJSONArray( "ancestors" ) );
|
||||
dst.descendants = TootStatus.parseList( context, access_info, src.optJSONArray( "descendants" ) );
|
||||
dst.ancestors = TootStatus.parseList( parser, src.optJSONArray( "ancestors" ) );
|
||||
dst.descendants = TootStatus.parseList( parser, src.optJSONArray( "descendants" ) );
|
||||
return dst;
|
||||
}catch( Throwable ex ){
|
||||
log.trace( ex );
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package jp.juggler.subwaytooter.api.entity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
|
@ -9,7 +8,7 @@ import org.json.JSONObject;
|
|||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.api.TootParser;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
|
||||
|
@ -41,16 +40,16 @@ public class TootNotification extends TootId {
|
|||
public JSONObject json;
|
||||
|
||||
@Nullable
|
||||
public static TootNotification parse( @NonNull Context context, @NonNull SavedAccount access_info, JSONObject src ){
|
||||
public static TootNotification parse( @NonNull TootParser parser, JSONObject src ){
|
||||
if( src == null ) return null;
|
||||
try{
|
||||
TootNotification dst = new TootNotification();
|
||||
dst.json = src;
|
||||
dst.id = Utils.optLongX(src, "id" );
|
||||
dst.id = Utils.optLongX( src, "id" );
|
||||
dst.type = Utils.optStringX( src, "type" );
|
||||
dst.created_at = Utils.optStringX( src, "created_at" );
|
||||
dst.account = TootAccount.parse( context, access_info, src.optJSONObject( "account" ) );
|
||||
dst.status = TootStatus.parse( context, access_info, src.optJSONObject( "status" ) );
|
||||
dst.account = TootAccount.parse( parser.context, parser.access_info, src.optJSONObject( "account" ) );
|
||||
dst.status = TootStatus.parse( parser, src.optJSONObject( "status" ) );
|
||||
|
||||
dst.time_created_at = TootStatus.parseTime( dst.created_at );
|
||||
|
||||
|
@ -73,7 +72,7 @@ public class TootNotification extends TootId {
|
|||
}
|
||||
|
||||
@NonNull
|
||||
public static List parseList( @NonNull Context context, @NonNull SavedAccount access_info, JSONArray array ){
|
||||
public static List parseList( @NonNull TootParser parser, JSONArray array ){
|
||||
List result = new List();
|
||||
if( array != null ){
|
||||
int array_size = array.length();
|
||||
|
@ -81,7 +80,7 @@ public class TootNotification extends TootId {
|
|||
for( int i = 0 ; i < array_size ; ++ i ){
|
||||
JSONObject src = array.optJSONObject( i );
|
||||
if( src == null ) continue;
|
||||
TootNotification item = parse( context, access_info, src );
|
||||
TootNotification item = parse( parser, src );
|
||||
if( item != null ) result.add( item );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package jp.juggler.subwaytooter.api.entity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
|
@ -8,7 +7,7 @@ import org.json.JSONObject;
|
|||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.api.TootParser;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
|
||||
|
@ -26,12 +25,12 @@ public class TootResults {
|
|||
public ArrayList< String > hashtags;
|
||||
|
||||
@Nullable
|
||||
public static TootResults parse( @NonNull Context context, @NonNull SavedAccount access_info, JSONObject src ){
|
||||
public static TootResults parse( @NonNull TootParser parser, JSONObject src ){
|
||||
try{
|
||||
if( src == null ) return null;
|
||||
TootResults dst = new TootResults();
|
||||
dst.accounts = TootAccount.parseList( context, access_info, src.optJSONArray( "accounts" ) );
|
||||
dst.statuses = TootStatus.parseList( context, access_info, src.optJSONArray( "statuses" ) );
|
||||
dst.accounts = TootAccount.parseList( parser.context, parser.access_info, src.optJSONArray( "accounts" ) );
|
||||
dst.statuses = TootStatus.parseList( parser, src.optJSONArray( "statuses" ) );
|
||||
dst.hashtags = Utils.parseStringArray( src.optJSONArray( "hashtags" ) );
|
||||
return dst;
|
||||
}catch( Throwable ex ){
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.util.regex.Pattern;
|
|||
import jp.juggler.subwaytooter.App1;
|
||||
import jp.juggler.subwaytooter.Pref;
|
||||
import jp.juggler.subwaytooter.R;
|
||||
import jp.juggler.subwaytooter.api.TootParser;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions;
|
||||
import jp.juggler.subwaytooter.util.HTMLDecoder;
|
||||
|
@ -88,28 +89,19 @@ public class TootStatus extends TootStatusLike {
|
|||
public NicoEnquete enquete;
|
||||
|
||||
@Nullable
|
||||
public static TootStatus parse( @NonNull Context context, @NonNull SavedAccount access_info, JSONObject src ){
|
||||
return parse( context,access_info,src,false);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static TootStatus parse( @NonNull Context context, @NonNull SavedAccount access_info, JSONObject src ,boolean bPinned){
|
||||
/*
|
||||
bPinned 引数がtrueになるのはプロフィールカラムからpinned TL を読んだ時だけである
|
||||
*/
|
||||
public static TootStatus parse( @NonNull TootParser parser, @Nullable JSONObject src ){
|
||||
|
||||
if( src == null ) return null;
|
||||
// log.d( "parse: %s", src.toString() );
|
||||
|
||||
try{
|
||||
TootStatus status = new TootStatus();
|
||||
status.json = src;
|
||||
|
||||
// 絵文字マップは割と最初の方で読み込んでおきたい
|
||||
status.custom_emojis = CustomEmoji.parseMap( src.optJSONArray( "emojis" ),access_info.host);
|
||||
status.custom_emojis = CustomEmoji.parseMap( src.optJSONArray( "emojis" ), parser.access_info.host );
|
||||
status.profile_emojis = NicoProfileEmoji.parseMap( src.optJSONArray( "profile_emojis" ) );
|
||||
|
||||
status.account = TootAccount.parse( context, access_info, src.optJSONObject( "account" ) );
|
||||
|
||||
status.account = TootAccount.parse( parser.context, parser.access_info, src.optJSONObject( "account" ) );
|
||||
|
||||
if( status.account == null ) return null;
|
||||
|
||||
|
@ -117,20 +109,21 @@ public class TootStatus extends TootStatusLike {
|
|||
status.uri = Utils.optStringX( src, "uri" );
|
||||
status.url = Utils.optStringX( src, "url" );
|
||||
|
||||
status.host_access = access_info.host;
|
||||
status.host_access = parser.access_info.host;
|
||||
status.host_original = status.account.getAcctHost();
|
||||
if( status.host_original == null ){
|
||||
status.host_original = access_info.host;
|
||||
status.host_original = parser.access_info.host;
|
||||
}
|
||||
|
||||
status.in_reply_to_id = Utils.optStringX( src, "in_reply_to_id" ); // null
|
||||
status.in_reply_to_account_id = Utils.optStringX( src, "in_reply_to_account_id" ); // null
|
||||
status.reblog = TootStatus.parse( context, access_info, src.optJSONObject( "reblog" ) ,false );
|
||||
/* Pinned TL を取得した時にreblogが登場することはないので、reblogをパースするときのbPinnedはfalseでよい */
|
||||
status.content = Utils.optStringX( src, "content" );
|
||||
|
||||
// Pinned TL を取得した時にreblogが登場することはないので、reblogについてpinned 状態を気にする必要はない
|
||||
status.reblog = TootStatus.parse( parser, src.optJSONObject( "reblog" ) );
|
||||
|
||||
status.created_at = Utils.optStringX( src, "created_at" ); // "2017-04-16T09:37:14.000Z"
|
||||
status.reblogs_count = Utils.optLongX(src, "reblogs_count" );
|
||||
status.favourites_count = Utils.optLongX(src, "favourites_count" );
|
||||
status.reblogs_count = Utils.optLongX( src, "reblogs_count" );
|
||||
status.favourites_count = Utils.optLongX( src, "favourites_count" );
|
||||
status.reblogged = src.optBoolean( "reblogged" );
|
||||
status.favourited = src.optBoolean( "favourited" );
|
||||
status.sensitive = src.optBoolean( "sensitive" ); // false
|
||||
|
@ -140,29 +133,21 @@ public class TootStatus extends TootStatusLike {
|
|||
status.tags = TootTag.parseList( src.optJSONArray( "tags" ) );
|
||||
status.application = TootApplication.parse( src.optJSONObject( "application" ) ); // null
|
||||
|
||||
status.pinned = bPinned || src.optBoolean( "pinned" );
|
||||
status.pinned = parser.isPinned || src.optBoolean( "pinned" );
|
||||
|
||||
status.setSpoilerText( context, Utils.optStringX( src, "spoiler_text" ) );
|
||||
status.setSpoilerText( parser, Utils.optStringX( src, "spoiler_text" ) );
|
||||
|
||||
status.muted = src.optBoolean( "muted" );
|
||||
status.language = Utils.optStringX( src, "language" );
|
||||
|
||||
|
||||
status.time_created_at = parseTime( status.created_at );
|
||||
status.decoded_content = new DecodeOptions()
|
||||
.setShort( true )
|
||||
.setDecodeEmoji( true)
|
||||
.setAttachment( status.media_attachments )
|
||||
.setCustomEmojiMap( status.custom_emojis )
|
||||
.setProfileEmojis( status.profile_emojis )
|
||||
.setLinkTag( status )
|
||||
.decodeHTML( context, access_info, status.content );
|
||||
|
||||
|
||||
status.setContent( parser, status.media_attachments, Utils.optStringX( src, "content" ) );
|
||||
|
||||
// status.decoded_tags = HTMLDecoder.decodeTags( account,status.tags );
|
||||
status.decoded_mentions = HTMLDecoder.decodeMentions( access_info, status.mentions ,status);
|
||||
|
||||
status.enquete = NicoEnquete.parse( context,access_info , status.media_attachments , Utils.optStringX( src, "enquete"),status.id,status.time_created_at,status );
|
||||
status.decoded_mentions = HTMLDecoder.decodeMentions( parser.access_info, status.mentions, status );
|
||||
|
||||
status.enquete = NicoEnquete.parse( parser.context, parser.access_info, status.media_attachments, Utils.optStringX( src, "enquete" ), status.id, status.time_created_at, status );
|
||||
|
||||
return status;
|
||||
}catch( Throwable ex ){
|
||||
|
@ -173,12 +158,7 @@ public class TootStatus extends TootStatusLike {
|
|||
}
|
||||
|
||||
@NonNull
|
||||
public static List parseList( @NonNull Context context, @NonNull SavedAccount access_info, JSONArray array ){
|
||||
return parseList( context,access_info,array,false );
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static List parseList( @NonNull Context context, @NonNull SavedAccount access_info, JSONArray array ,boolean bPinned){
|
||||
public static List parseList( @NonNull TootParser parser, JSONArray array ){
|
||||
List result = new List();
|
||||
if( array != null ){
|
||||
int array_size = array.length();
|
||||
|
@ -186,7 +166,7 @@ public class TootStatus extends TootStatusLike {
|
|||
for( int i = 0 ; i < array_size ; ++ i ){
|
||||
JSONObject src = array.optJSONObject( i );
|
||||
if( src == null ) continue;
|
||||
TootStatus item = parse( context, access_info, src ,bPinned );
|
||||
TootStatus item = parse( parser, src );
|
||||
if( item != null ) result.add( item );
|
||||
}
|
||||
}
|
||||
|
@ -296,11 +276,11 @@ public class TootStatus extends TootStatusLike {
|
|||
}
|
||||
|
||||
// word mute
|
||||
if( decoded_content != null && muted_word.containsWord( decoded_content.toString() ) ){
|
||||
if( muted_word.matchShort( decoded_content ) ){
|
||||
return true;
|
||||
}
|
||||
|
||||
if( decoded_spoiler_text != null && muted_word.containsWord( decoded_spoiler_text.toString() ) ){
|
||||
if( muted_word.matchShort( decoded_spoiler_text ) ){
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,10 +9,14 @@ import android.text.TextUtils;
|
|||
import org.json.JSONObject;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import jp.juggler.subwaytooter.api.TootParser;
|
||||
import jp.juggler.subwaytooter.api_msp.entity.MSPToot;
|
||||
import jp.juggler.subwaytooter.api_tootsearch.entity.TSToot;
|
||||
import jp.juggler.subwaytooter.table.HighlightWord;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
|
@ -20,7 +24,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
|
||||
public abstract class TootStatusLike extends TootId {
|
||||
|
||||
static final LogCategory log = new LogCategory("TootStatusLike");
|
||||
static final LogCategory log = new LogCategory( "TootStatusLike" );
|
||||
|
||||
//URL to the status page (can be remote)
|
||||
public String url;
|
||||
|
@ -95,8 +99,9 @@ public abstract class TootStatusLike extends TootId {
|
|||
|
||||
private static final Pattern reWhitespace = Pattern.compile( "[\\s\\t\\x0d\\x0a]+" );
|
||||
|
||||
@Nullable public HighlightWord highlight_sound;
|
||||
|
||||
public void setSpoilerText( Context context, String sv ){
|
||||
public void setSpoilerText( @NonNull TootParser parser, String sv ){
|
||||
if( TextUtils.isEmpty( sv ) ){
|
||||
this.spoiler_text = null;
|
||||
this.decoded_spoiler_text = null;
|
||||
|
@ -105,10 +110,38 @@ public abstract class TootStatusLike extends TootId {
|
|||
// remove white spaces
|
||||
sv = reWhitespace.matcher( this.spoiler_text ).replaceAll( " " );
|
||||
// decode emoji code
|
||||
this.decoded_spoiler_text = new DecodeOptions()
|
||||
|
||||
DecodeOptions options = new DecodeOptions()
|
||||
.setCustomEmojiMap( custom_emojis )
|
||||
.setProfileEmojis( this.profile_emojis )
|
||||
.decodeEmoji( context, sv );
|
||||
.setHighlightTrie(parser.highlight_trie)
|
||||
;
|
||||
|
||||
this.decoded_spoiler_text = options.decodeEmoji( parser.context, sv );
|
||||
|
||||
if( options.highlight_sound != null && this.highlight_sound == null ){
|
||||
this.highlight_sound = options.highlight_sound;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setContent( @NonNull TootParser parser, TootAttachment.List list_attachment, @Nullable String content ){
|
||||
this.content = content;
|
||||
|
||||
DecodeOptions options =new DecodeOptions()
|
||||
.setShort( true )
|
||||
.setDecodeEmoji( true )
|
||||
.setCustomEmojiMap( this.custom_emojis )
|
||||
.setProfileEmojis( this.profile_emojis )
|
||||
.setLinkTag( this )
|
||||
.setAttachment( list_attachment )
|
||||
.setHighlightTrie(parser.highlight_trie)
|
||||
;
|
||||
|
||||
this.decoded_content = options.decodeHTML( parser.context, parser.access_info, content );
|
||||
|
||||
if( options.highlight_sound != null && this.highlight_sound == null ){
|
||||
this.highlight_sound = options.highlight_sound;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,8 +153,8 @@ public abstract class TootStatusLike extends TootId {
|
|||
public Spannable decoded_spoiler_text;
|
||||
public int originalLineCount;
|
||||
}
|
||||
public AutoCW auto_cw;
|
||||
|
||||
public AutoCW auto_cw;
|
||||
|
||||
// OStatus
|
||||
static final Pattern reTootUriOS = Pattern.compile( "tag:([^,]*),[^:]*:objectId=(\\d+):objectType=Status", Pattern.CASE_INSENSITIVE );
|
||||
|
@ -132,7 +165,7 @@ public abstract class TootStatusLike extends TootId {
|
|||
|
||||
// 投稿元タンスでのステータスIDを調べる
|
||||
public long parseStatusId(){
|
||||
return TootStatusLike.parseStatusId(this);
|
||||
return TootStatusLike.parseStatusId( this );
|
||||
}
|
||||
|
||||
// 投稿元タンスでのステータスIDを調べる
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.json.JSONObject;
|
|||
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import jp.juggler.subwaytooter.api.TootParser;
|
||||
import jp.juggler.subwaytooter.api.entity.TootAccount;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions;
|
||||
|
@ -19,7 +20,7 @@ public class MSPAccount extends TootAccount {
|
|||
private static final LogCategory log = new LogCategory( "MSPAccount" );
|
||||
|
||||
@Nullable
|
||||
static TootAccount parseAccount( @NonNull Context context, @NonNull SavedAccount access_info, @Nullable JSONObject src ){
|
||||
static TootAccount parseAccount( @NonNull TootParser parser , @Nullable JSONObject src ){
|
||||
|
||||
if( src == null ) return null;
|
||||
|
||||
|
@ -29,7 +30,7 @@ public class MSPAccount extends TootAccount {
|
|||
dst.avatar = dst.avatar_static = Utils.optStringX( src, "avatar" );
|
||||
|
||||
String sv = Utils.optStringX( src, "display_name" );
|
||||
dst.setDisplayName( context, dst.username, sv );
|
||||
dst.setDisplayName( parser.context, dst.username, sv );
|
||||
|
||||
dst.id = Utils.optLongX( src, "id" );
|
||||
|
||||
|
@ -38,7 +39,7 @@ public class MSPAccount extends TootAccount {
|
|||
.setShort( true )
|
||||
.setDecodeEmoji( true )
|
||||
.setProfileEmojis( dst.profile_emojis )
|
||||
.decodeHTML( context, access_info, dst.note );
|
||||
.decodeHTML( parser.context, parser.access_info, dst.note );
|
||||
|
||||
if( TextUtils.isEmpty( dst.url ) ){
|
||||
log.e( "parseAccount: missing url" );
|
||||
|
|
|
@ -16,6 +16,7 @@ import java.util.TimeZone;
|
|||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import jp.juggler.subwaytooter.api.TootParser;
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatusLike;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions;
|
||||
|
@ -37,11 +38,11 @@ public class MSPToot extends TootStatusLike {
|
|||
// private long msp_id;
|
||||
|
||||
@Nullable
|
||||
private static MSPToot parse( @NonNull Context context, SavedAccount access_info, JSONObject src ){
|
||||
private static MSPToot parse( @NonNull TootParser parser, JSONObject src ){
|
||||
if( src == null ) return null;
|
||||
MSPToot dst = new MSPToot();
|
||||
|
||||
dst.account = MSPAccount.parseAccount( context, access_info, src.optJSONObject( "account" ) );
|
||||
dst.account = MSPAccount.parseAccount( parser, src.optJSONObject( "account" ) );
|
||||
if( dst.account == null ){
|
||||
log.e( "missing status account" );
|
||||
return null;
|
||||
|
@ -76,26 +77,18 @@ public class MSPToot extends TootStatusLike {
|
|||
// dst.msp_id = Utils.optLongX(src, "msp_id" );
|
||||
dst.sensitive = ( src.optInt( "sensitive", 0 ) != 0 );
|
||||
|
||||
dst.setSpoilerText( context, Utils.optStringX( src, "spoiler_text" ) );
|
||||
|
||||
dst.content = Utils.optStringX( src, "content" );
|
||||
dst.decoded_content = new DecodeOptions()
|
||||
.setShort( true )
|
||||
.setDecodeEmoji( true )
|
||||
.setCustomEmojiMap( dst.custom_emojis )
|
||||
.setProfileEmojis( dst.profile_emojis )
|
||||
.setLinkTag( dst )
|
||||
.decodeHTML( context, access_info, dst.content );
|
||||
dst.setSpoilerText( parser, Utils.optStringX( src, "spoiler_text" ) );
|
||||
dst.setContent( parser, null, Utils.optStringX( src, "content" ) );
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
public static List parseList( @NonNull Context context, SavedAccount access_info, JSONArray array ){
|
||||
public static List parseList( @NonNull TootParser parser, JSONArray array ){
|
||||
List list = new List();
|
||||
for( int i = 0, ie = array.length() ; i < ie ; ++ i ){
|
||||
JSONObject src = array.optJSONObject( i );
|
||||
if( src == null ) continue;
|
||||
MSPToot item = parse( context, access_info, src );
|
||||
MSPToot item = parse( parser, src );
|
||||
if( item == null ) continue;
|
||||
list.add( item );
|
||||
}
|
||||
|
@ -146,11 +139,11 @@ public class MSPToot extends TootStatusLike {
|
|||
// }
|
||||
//
|
||||
// word mute
|
||||
if( decoded_content != null && muted_word.containsWord( decoded_content.toString() ) ){
|
||||
if( muted_word.matchShort( decoded_content ) ){
|
||||
return true;
|
||||
}
|
||||
|
||||
if( decoded_spoiler_text != null && muted_word.containsWord( decoded_spoiler_text.toString() ) ){
|
||||
if( muted_word.matchShort( decoded_spoiler_text ) ){
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import java.util.ArrayList;
|
|||
import java.util.HashSet;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import jp.juggler.subwaytooter.api.TootParser;
|
||||
import jp.juggler.subwaytooter.api.entity.CustomEmoji;
|
||||
import jp.juggler.subwaytooter.api.entity.NicoProfileEmoji;
|
||||
import jp.juggler.subwaytooter.api.entity.TootAccount;
|
||||
|
@ -37,11 +38,11 @@ public class TSToot extends TootStatusLike {
|
|||
public String uri;
|
||||
|
||||
@Nullable
|
||||
private static TSToot parse( @NonNull Context context, SavedAccount access_info, JSONObject src ){
|
||||
private static TSToot parse( @NonNull TootParser parser, JSONObject src ){
|
||||
if( src == null ) return null;
|
||||
TSToot dst = new TSToot();
|
||||
|
||||
dst.account = parseAccount( context, access_info, src.optJSONObject( "account" ) );
|
||||
dst.account = parseAccount( parser, src.optJSONObject( "account" ) );
|
||||
if( dst.account == null ){
|
||||
log.e( "missing status account" );
|
||||
return null;
|
||||
|
@ -50,7 +51,7 @@ public class TSToot extends TootStatusLike {
|
|||
dst.json = src;
|
||||
|
||||
// 絵文字マップは割と最初の方で読み込んでおきたい
|
||||
dst.custom_emojis = CustomEmoji.parseMap( src.optJSONArray( "emojis" ), access_info.host );
|
||||
dst.custom_emojis = CustomEmoji.parseMap( src.optJSONArray( "emojis" ), parser.access_info.host );
|
||||
dst.profile_emojis = NicoProfileEmoji.parseMap( src.optJSONArray( "profile_emojis" ) );
|
||||
|
||||
dst.url = Utils.optStringX( src, "url" );
|
||||
|
@ -65,7 +66,7 @@ public class TSToot extends TootStatusLike {
|
|||
log.e( "missing status uri or url or host or id" );
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// uri から投稿元タンスでのIDを調べる
|
||||
dst.id = TootStatusLike.parseStatusId( dst );
|
||||
|
||||
|
@ -76,18 +77,8 @@ public class TSToot extends TootStatusLike {
|
|||
|
||||
dst.sensitive = src.optBoolean( "sensitive", false );
|
||||
|
||||
dst.setSpoilerText( context, Utils.optStringX( src, "spoiler_text" ) );
|
||||
|
||||
dst.content = Utils.optStringX( src, "content" );
|
||||
dst.decoded_content = new DecodeOptions()
|
||||
.setShort( true )
|
||||
.setDecodeEmoji( true )
|
||||
.setCustomEmojiMap( dst.custom_emojis )
|
||||
.setProfileEmojis( dst.profile_emojis )
|
||||
.setLinkTag( dst )
|
||||
.decodeHTML( context, access_info, dst.content );
|
||||
|
||||
|
||||
dst.setSpoilerText( parser, Utils.optStringX( src, "spoiler_text" ) );
|
||||
dst.setContent( parser, dst.media_attachments, Utils.optStringX( src, "content" ) );
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
@ -96,7 +87,7 @@ public class TSToot extends TootStatusLike {
|
|||
}
|
||||
|
||||
@NonNull
|
||||
public static TSToot.List parseList( @NonNull Context context, SavedAccount access_info, @NonNull JSONObject root ){
|
||||
public static TSToot.List parseList( @NonNull TootParser parser, @NonNull JSONObject root ){
|
||||
TSToot.List list = new TSToot.List();
|
||||
JSONArray array = TSClient.getHits( root );
|
||||
if( array != null ){
|
||||
|
@ -105,11 +96,11 @@ public class TSToot extends TootStatusLike {
|
|||
JSONObject src = array.optJSONObject( i );
|
||||
if( src == null ) continue;
|
||||
JSONObject src2 = src.optJSONObject( "_source" );
|
||||
TSToot item = parse( context, access_info, src2 );
|
||||
TSToot item = parse( parser, src2 );
|
||||
if( item == null ) continue;
|
||||
list.add( item );
|
||||
}catch(Throwable ex){
|
||||
log.trace(ex);
|
||||
}catch( Throwable ex ){
|
||||
log.trace( ex );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -119,11 +110,11 @@ public class TSToot extends TootStatusLike {
|
|||
public boolean checkMuted( @SuppressWarnings("UnusedParameters") @NonNull HashSet< String > muted_app, @NonNull WordTrieTree muted_word ){
|
||||
|
||||
// word mute
|
||||
if( decoded_content != null && muted_word.containsWord( decoded_content.toString() ) ){
|
||||
if( decoded_content != null && muted_word.matchShort( decoded_content.toString() ) ){
|
||||
return true;
|
||||
}
|
||||
|
||||
if( decoded_spoiler_text != null && muted_word.containsWord( decoded_spoiler_text.toString() ) ){
|
||||
if( decoded_spoiler_text != null && muted_word.matchShort( decoded_spoiler_text.toString() ) ){
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -143,9 +134,10 @@ public class TSToot extends TootStatusLike {
|
|||
}
|
||||
|
||||
@Nullable
|
||||
private static TootAccount parseAccount( @NonNull Context context, @NonNull SavedAccount access_info, @Nullable JSONObject src ){
|
||||
private static TootAccount parseAccount( @NonNull TootParser parser, @Nullable JSONObject src ){
|
||||
|
||||
TootAccount dst = parser.account( src );
|
||||
|
||||
TootAccount dst = TootAccount.parse( context, access_info, src );
|
||||
if( dst != null ){
|
||||
|
||||
// tootsearch のアカウントのIDはどのタンス上のものか分からない
|
||||
|
|
|
@ -35,6 +35,7 @@ import jp.juggler.subwaytooter.api.TootTaskRunner;
|
|||
import jp.juggler.subwaytooter.api.entity.TootAccount;
|
||||
import jp.juggler.subwaytooter.api.entity.TootList;
|
||||
import jp.juggler.subwaytooter.api.entity.TootResults;
|
||||
import jp.juggler.subwaytooter.api.TootParser;
|
||||
import jp.juggler.subwaytooter.table.AcctColor;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.util.NetworkEmojiInvalidator;
|
||||
|
@ -190,7 +191,7 @@ public class DlgListMember implements View.OnClickListener {
|
|||
return result;
|
||||
}
|
||||
|
||||
TootResults search_result = TootResults.parse( activity, list_owner, result.object );
|
||||
TootResults search_result = new TootParser(activity, list_owner).results( result.object );
|
||||
if( search_result != null ){
|
||||
for( TootAccount a : search_result.accounts ){
|
||||
if( target_user_full_acct.equalsIgnoreCase( list_owner.getFullAcct( a ) ) ){
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
package jp.juggler.subwaytooter.table;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import jp.juggler.subwaytooter.App1;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
import jp.juggler.subwaytooter.util.WordTrieTree;
|
||||
|
||||
public class HighlightWord {
|
||||
|
||||
private static final LogCategory log = new LogCategory( "HighlightWord" );
|
||||
|
||||
public static final int SOUND_TYPE_NONE = 0;
|
||||
public static final int SOUND_TYPE_DEFAULT = 1;
|
||||
public static final int SOUND_TYPE_CUSTOM = 2;
|
||||
|
||||
public static final String table = "highlight_word";
|
||||
public static final String COL_ID = "_id";
|
||||
public static final String COL_NAME = "name";
|
||||
private static final String COL_TIME_SAVE = "time_save";
|
||||
private static final String COL_COLOR_BG = "color_bg";
|
||||
private static final String COL_COLOR_FG = "color_fg";
|
||||
private static final String COL_SOUND_TYPE = "sound_type";
|
||||
private static final String COL_SOUND_URI = "sound_uri";
|
||||
|
||||
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"
|
||||
+ ",color_bg integer not null default 0"
|
||||
+ ",color_fg integer not null default 0"
|
||||
+ ",sound_type integer not null default 1"
|
||||
+ ",sound_uri text default 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 < 21 && newVersion >= 21 ){
|
||||
onDBCreate( db );
|
||||
}
|
||||
}
|
||||
|
||||
public long id = -1L;
|
||||
@NonNull public String name;
|
||||
public int color_bg;
|
||||
public int color_fg;
|
||||
public int sound_type;
|
||||
@Nullable public String sound_uri;
|
||||
|
||||
public JSONObject encodeJson() throws JSONException{
|
||||
JSONObject dst = new JSONObject( );
|
||||
dst.put(COL_ID,id);
|
||||
dst.put(COL_NAME,name);
|
||||
dst.put(COL_COLOR_BG,color_bg);
|
||||
dst.put(COL_COLOR_FG,color_fg);
|
||||
dst.put(COL_SOUND_TYPE,sound_type);
|
||||
if( sound_uri != null ) dst.put(COL_SOUND_URI,sound_uri);
|
||||
return dst;
|
||||
}
|
||||
|
||||
public HighlightWord(@NonNull JSONObject src ){
|
||||
this.id = Utils.optLongX( src,COL_ID );
|
||||
String sv = Utils.optStringX(src,COL_NAME);
|
||||
if( TextUtils.isEmpty( sv )) throw new RuntimeException( "HighlightWord: name is empty" );
|
||||
this.name = sv;
|
||||
this.color_bg = src.optInt( COL_COLOR_BG );
|
||||
this.color_fg = src.optInt( COL_COLOR_FG );
|
||||
this.sound_type = src.optInt( COL_SOUND_TYPE );
|
||||
this.sound_uri = Utils.optStringX( src,COL_SOUND_URI );
|
||||
}
|
||||
|
||||
|
||||
public HighlightWord(@NonNull String name){
|
||||
this.name = name;
|
||||
this.sound_type = SOUND_TYPE_DEFAULT;
|
||||
this.color_fg = 0xFFFF0000;
|
||||
}
|
||||
|
||||
public HighlightWord(@NonNull Cursor cursor){
|
||||
this.id = cursor.getLong( cursor.getColumnIndex( COL_ID ));
|
||||
this.name = cursor.getString( cursor.getColumnIndex( COL_NAME ));
|
||||
this.color_bg = cursor.getInt( cursor.getColumnIndex( COL_COLOR_BG ));
|
||||
this.color_fg = cursor.getInt( cursor.getColumnIndex( COL_COLOR_FG ));
|
||||
this.sound_type = cursor.getInt( cursor.getColumnIndex( COL_SOUND_TYPE ));
|
||||
int colIdx_sound_uri = cursor.getColumnIndex( COL_SOUND_URI );
|
||||
this.sound_uri = cursor.isNull( colIdx_sound_uri ) ? null : cursor.getString( colIdx_sound_uri);
|
||||
}
|
||||
|
||||
private static final String selection_name = COL_NAME+"=?";
|
||||
private static final String selection_id = COL_ID+"=?";
|
||||
|
||||
|
||||
@Nullable public static HighlightWord load(@NonNull String name){
|
||||
try{
|
||||
Cursor cursor = App1.getDB().query( table, null, selection_name, new String[]{ name }, null, null, null );
|
||||
if( cursor != null ){
|
||||
try{
|
||||
if( cursor.moveToNext() ){
|
||||
return new HighlightWord( cursor );
|
||||
}
|
||||
}finally{
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}catch( Throwable ex ){
|
||||
log.trace( ex );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void save(){
|
||||
|
||||
if( TextUtils.isEmpty( name )) throw new RuntimeException( "HighlightWord: name is empty" );
|
||||
|
||||
try{
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put( COL_NAME, name );
|
||||
cv.put( COL_TIME_SAVE, System.currentTimeMillis() );
|
||||
cv.put( COL_COLOR_BG, color_bg );
|
||||
cv.put( COL_COLOR_FG, color_fg );
|
||||
cv.put( COL_SOUND_TYPE, sound_type );
|
||||
if( TextUtils.isEmpty(sound_uri) ){
|
||||
cv.putNull( COL_SOUND_URI );
|
||||
|
||||
}else{
|
||||
cv.put( COL_SOUND_URI, sound_uri );
|
||||
}
|
||||
if( id == -1L ){
|
||||
App1.getDB().replace( table, null, cv );
|
||||
}else{
|
||||
App1.getDB().update( table, cv, selection_id, new String[]{ Long.toString( id ) } );
|
||||
}
|
||||
}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 void delete(){
|
||||
try{
|
||||
App1.getDB().delete( table, selection_id, new String[]{ Long.toString( id ) } );
|
||||
}catch( Throwable ex ){
|
||||
log.e( ex, "delete failed." );
|
||||
}
|
||||
}
|
||||
|
||||
private static final String[] columns_name = new String[]{ COL_NAME };
|
||||
|
||||
|
||||
@Nullable public static WordTrieTree getNameSet(){
|
||||
WordTrieTree dst = null;
|
||||
try{
|
||||
Cursor cursor = App1.getDB().query( table, columns_name, null, null, null, null, null );
|
||||
if( cursor != null ){
|
||||
try{
|
||||
int idx_name = cursor.getColumnIndex( COL_NAME );
|
||||
while( cursor.moveToNext() ){
|
||||
if( dst == null) dst = new WordTrieTree();
|
||||
String s = cursor.getString( idx_name );
|
||||
dst.add( s );
|
||||
}
|
||||
}finally{
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}catch( Throwable ex ){
|
||||
log.trace( ex );
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,339 @@
|
|||
package jp.juggler.subwaytooter.util;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.util.SparseArrayCompat;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class CharacterGroup {
|
||||
|
||||
// Tokenizerが終端に達したことを示す
|
||||
static final int END = - 1;
|
||||
|
||||
// 同じと見なす文字列の集合
|
||||
static class Group {
|
||||
|
||||
// 文字列の配列
|
||||
@NonNull final String[] list;
|
||||
|
||||
// 代表する文字のコード
|
||||
final int id;
|
||||
|
||||
Group( @NonNull String[] list ){
|
||||
this.list = list;
|
||||
this.id = findGroupId();
|
||||
}
|
||||
|
||||
private int findGroupId(){
|
||||
// グループのIDは、グループ中の文字(長さ1)のunicodeのどれか
|
||||
for( String s : list ){
|
||||
if( s.length() == 1 ){
|
||||
return s.charAt( 0 );
|
||||
}
|
||||
}
|
||||
throw new RuntimeException( "group has not id!!" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// テキスト中に出現した文字列からグループを探すためのマップ
|
||||
|
||||
// キー文字数1
|
||||
private final SparseArrayCompat< Group > map1 = new SparseArrayCompat<>();
|
||||
|
||||
// キー文字数2
|
||||
private final SparseArrayCompat< Group > map2 = new SparseArrayCompat<>();
|
||||
|
||||
// グループをmapに登録する
|
||||
private void addGroup( @NonNull String[] list ){
|
||||
|
||||
// グループを生成
|
||||
Group g = new Group( list );
|
||||
|
||||
// 含まれる各文字列をマップに登録する
|
||||
for( String s : list ){
|
||||
int len = s.length();
|
||||
int v1 = s.charAt( 0 );
|
||||
|
||||
SparseArrayCompat< Group > map;
|
||||
int key;
|
||||
if( len == 1 ){
|
||||
map = map1;
|
||||
key = v1;
|
||||
}else{
|
||||
map = map2;
|
||||
int v2 = s.charAt( 1 );
|
||||
key = v1 | ( v2 << 16 );
|
||||
}
|
||||
|
||||
Group old = map.get( key );
|
||||
if( old != null && old != g ){
|
||||
throw new RuntimeException( String.format( Locale.JAPAN, "group conflict: %s", s ) );
|
||||
}
|
||||
map.put( key, g );
|
||||
}
|
||||
}
|
||||
|
||||
// CharSequence の範囲 から 文字,グループ,終端 のどれかを列挙する
|
||||
class Tokenizer {
|
||||
public CharSequence text;
|
||||
public int end;
|
||||
|
||||
// next() を読むと以下の変数が更新される
|
||||
public int offset;
|
||||
public int c; // may END or group.id or UTF-16 character
|
||||
public Group group;
|
||||
|
||||
Tokenizer( @NonNull CharSequence text, int start, int end ){
|
||||
reset( text, start, end );
|
||||
}
|
||||
|
||||
public void reset( CharSequence text, int start, int end ){
|
||||
this.text = text;
|
||||
this.offset = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public void next(){
|
||||
|
||||
int pos = offset;
|
||||
|
||||
// 空白を読み飛ばす
|
||||
while( pos < end && isWhitespace( text.charAt( pos ) ) ) ++ pos;
|
||||
|
||||
// 終端までの文字数
|
||||
int remain = end - pos;
|
||||
if( remain <= 0 ){
|
||||
// 空白を読み飛ばしたら終端になった
|
||||
// 終端の場合、末尾の空白はoffsetに含めない
|
||||
this.group = null;
|
||||
this.c = END;
|
||||
return;
|
||||
}
|
||||
|
||||
int v1 = text.charAt( pos );
|
||||
int v2 = remain > 1 ? text.charAt( pos + 1 ) : 0;
|
||||
|
||||
// グループに登録された文字を長い順にチェック
|
||||
int check_len = remain > 2 ? 2 : remain;
|
||||
while( check_len > 0 ){
|
||||
Group g = check_len == 1 ? map1.get( v1 ) : map2.get( v1 | ( v2 << 16 ) );
|
||||
if( g != null ){
|
||||
this.group = g;
|
||||
this.c = g.id;
|
||||
this.offset = pos + check_len;
|
||||
return;
|
||||
}
|
||||
-- check_len;
|
||||
}
|
||||
|
||||
this.group = null;
|
||||
this.c = v1;
|
||||
this.offset = pos + 1;
|
||||
}
|
||||
}
|
||||
|
||||
Tokenizer tokenizer( CharSequence text, int start, int end ){
|
||||
return new Tokenizer( text, start, end );
|
||||
}
|
||||
|
||||
public static boolean isWhitespace( int cp ){
|
||||
switch( cp ){
|
||||
case 0x0009: // HORIZONTAL TABULATION
|
||||
case 0x000A: // LINE FEED
|
||||
case 0x000B: // VERTICAL TABULATION
|
||||
case 0x000C: // FORM FEED
|
||||
case 0x000D: // CARRIAGE RETURN
|
||||
case 0x001C: // FILE SEPARATOR
|
||||
case 0x001D: // GROUP SEPARATOR
|
||||
case 0x001E: // RECORD SEPARATOR
|
||||
case 0x001F: // UNIT SEPARATOR
|
||||
case 0x0020:
|
||||
case 0x0085: // next line (latin-1)
|
||||
case 0x00A0: //非区切りスペース
|
||||
case 0x1680:
|
||||
case 0x180E:
|
||||
case 0x2000:
|
||||
case 0x2001:
|
||||
case 0x2002:
|
||||
case 0x2003:
|
||||
case 0x2004:
|
||||
case 0x2005:
|
||||
case 0x2006:
|
||||
case 0x2007: //非区切りスペース
|
||||
case 0x2008:
|
||||
case 0x2009:
|
||||
case 0x200A:
|
||||
case 0x200B:
|
||||
case 0x200C:
|
||||
case 0x200D:
|
||||
case 0x2028: // line separator
|
||||
case 0x2029: // paragraph separator
|
||||
|
||||
case 0x202F: //非区切りスペース
|
||||
case 0x205F:
|
||||
case 0x2060:
|
||||
case 0x3000:
|
||||
case 0x3164:
|
||||
case 0xFEFF:
|
||||
return true;
|
||||
default:
|
||||
return Character.isWhitespace( cp );
|
||||
}
|
||||
}
|
||||
|
||||
// 文字コードから文字列を作る
|
||||
private static String c2s( char[] tmp, int c ){
|
||||
tmp[ 0 ] = (char) c;
|
||||
return new String( tmp, 0, 1 );
|
||||
}
|
||||
|
||||
CharacterGroup(){
|
||||
char[] tmp = new char[ 1 ];
|
||||
|
||||
// 数字
|
||||
for( int i = 0 ; i < 9 ; ++ i ){
|
||||
String[] list = new String[ 2 ];
|
||||
list[ 0 ] = c2s( tmp, '0' + i );
|
||||
list[ 1 ] = c2s( tmp, '0' + i );
|
||||
addGroup( list );
|
||||
}
|
||||
|
||||
// 英字
|
||||
for( int i = 0 ; i < 26 ; ++ i ){
|
||||
String[] list = new String[ 4 ];
|
||||
list[ 0 ] = c2s( tmp, 'a' + i );
|
||||
list[ 1 ] = c2s( tmp, 'A' + i );
|
||||
list[ 2 ] = c2s( tmp, 'a' + i );
|
||||
list[ 3 ] = c2s( tmp, 'A' + i );
|
||||
addGroup( list );
|
||||
}
|
||||
|
||||
// ハイフン
|
||||
addGroup( new String[]{
|
||||
c2s( tmp, 0x002D ), // ASCIIのハイフン
|
||||
c2s( tmp, 0x30FC ), // 全角カナの長音 Shift_JIS由来
|
||||
c2s( tmp, 0x2010 ),
|
||||
c2s( tmp, 0x2011 ),
|
||||
c2s( tmp, 0x2013 ),
|
||||
c2s( tmp, 0x2014 ),
|
||||
c2s( tmp, 0x2015 ), // 全角カナのダッシュ Shift_JIS由来
|
||||
c2s( tmp, 0x2212 ),
|
||||
c2s( tmp, 0xFF0d ), // 全角カナの長音 MS932由来
|
||||
c2s( tmp, 0xFF70 ), // 半角カナの長音 MS932由来
|
||||
} );
|
||||
|
||||
addGroup( new String[]{ "!", "!" } );
|
||||
addGroup( new String[]{ """, "\"" } );
|
||||
addGroup( new String[]{ "#", "#" } );
|
||||
addGroup( new String[]{ "$", "$" } );
|
||||
addGroup( new String[]{ "%", "%" } );
|
||||
addGroup( new String[]{ "&", "&" } );
|
||||
addGroup( new String[]{ "'", "'" } );
|
||||
addGroup( new String[]{ "(", "(" } );
|
||||
addGroup( new String[]{ ")", ")" } );
|
||||
addGroup( new String[]{ "*", "*" } );
|
||||
addGroup( new String[]{ "+", "+" } );
|
||||
addGroup( new String[]{ ",", ",", "、", "、" } );
|
||||
addGroup( new String[]{ ".", ".", "。", "。" } );
|
||||
addGroup( new String[]{ "/", "/" } );
|
||||
addGroup( new String[]{ ":", ":" } );
|
||||
addGroup( new String[]{ ";", ";" } );
|
||||
addGroup( new String[]{ "<", "<" } );
|
||||
addGroup( new String[]{ "=", "=" } );
|
||||
addGroup( new String[]{ ">", ">" } );
|
||||
addGroup( new String[]{ "?", "?" } );
|
||||
addGroup( new String[]{ "@", "@" } );
|
||||
addGroup( new String[]{ "[", "[" } );
|
||||
addGroup( new String[]{ "\", "\\", "¥" } );
|
||||
addGroup( new String[]{ "]", "]" } );
|
||||
addGroup( new String[]{ "^", "^" } );
|
||||
addGroup( new String[]{ "_", "_" } );
|
||||
addGroup( new String[]{ "`", "`" } );
|
||||
addGroup( new String[]{ "{", "{" } );
|
||||
addGroup( new String[]{ "|", "|", "¦" } );
|
||||
addGroup( new String[]{ "}", "}" } );
|
||||
|
||||
addGroup( new String[]{ "・", "・", "・" } );
|
||||
addGroup( new String[]{ "「", "「", "「" } );
|
||||
addGroup( new String[]{ "」", "」", "」" } );
|
||||
|
||||
// チルダ
|
||||
addGroup( new String[]{ "~", c2s(tmp,0x301C),c2s(tmp,0xFF5E) } );
|
||||
|
||||
// 半角カナの濁音,半濁音は2文字になる
|
||||
addGroup( new String[]{ "ガ", "が", "ガ" } );
|
||||
addGroup( new String[]{ "ギ", "ぎ", "ギ" } );
|
||||
addGroup( new String[]{ "グ", "ぐ", "グ" } );
|
||||
addGroup( new String[]{ "ゲ", "げ", "ゲ" } );
|
||||
addGroup( new String[]{ "ゴ", "ご", "ゴ" } );
|
||||
addGroup( new String[]{ "ザ", "ざ", "ザ" } );
|
||||
addGroup( new String[]{ "ジ", "じ", "ジ" } );
|
||||
addGroup( new String[]{ "ズ", "ず", "ズ" } );
|
||||
addGroup( new String[]{ "ゼ", "ぜ", "ゼ" } );
|
||||
addGroup( new String[]{ "ゾ", "ぞ", "ゾ" } );
|
||||
addGroup( new String[]{ "ダ", "だ", "ダ" } );
|
||||
addGroup( new String[]{ "ヂ", "ぢ", "ヂ" } );
|
||||
addGroup( new String[]{ "ヅ", "づ", "ヅ" } );
|
||||
addGroup( new String[]{ "デ", "で", "デ" } );
|
||||
addGroup( new String[]{ "ド", "ど", "ド" } );
|
||||
addGroup( new String[]{ "バ", "ば", "バ" } );
|
||||
addGroup( new String[]{ "ビ", "び", "ビ" } );
|
||||
addGroup( new String[]{ "ブ", "ぶ", "ブ" } );
|
||||
addGroup( new String[]{ "ベ", "べ", "ベ" } );
|
||||
addGroup( new String[]{ "ボ", "ぼ", "ボ" } );
|
||||
addGroup( new String[]{ "パ", "ぱ", "パ" } );
|
||||
addGroup( new String[]{ "ピ", "ぴ", "ピ" } );
|
||||
addGroup( new String[]{ "プ", "ぷ", "プ" } );
|
||||
addGroup( new String[]{ "ペ", "ぺ", "ペ" } );
|
||||
addGroup( new String[]{ "ポ", "ぽ", "ポ" } );
|
||||
addGroup( new String[]{ "ヴ", "う゛", "ヴ" } );
|
||||
|
||||
addGroup( new String[]{ "あ", "ア", "ア", "ぁ", "ァ", "ァ" } );
|
||||
addGroup( new String[]{ "い", "イ", "イ", "ぃ", "ィ", "ィ" } );
|
||||
addGroup( new String[]{ "う", "ウ", "ウ", "ぅ", "ゥ", "ゥ" } );
|
||||
addGroup( new String[]{ "え", "エ", "エ", "ぇ", "ェ", "ェ" } );
|
||||
addGroup( new String[]{ "お", "オ", "オ", "ぉ", "ォ", "ォ" } );
|
||||
addGroup( new String[]{ "か", "カ", "カ" } );
|
||||
addGroup( new String[]{ "き", "キ", "キ" } );
|
||||
addGroup( new String[]{ "く", "ク", "ク" } );
|
||||
addGroup( new String[]{ "け", "ケ", "ケ" } );
|
||||
addGroup( new String[]{ "こ", "コ", "コ" } );
|
||||
addGroup( new String[]{ "さ", "サ", "サ" } );
|
||||
addGroup( new String[]{ "し", "シ", "シ" } );
|
||||
addGroup( new String[]{ "す", "ス", "ス" } );
|
||||
addGroup( new String[]{ "せ", "セ", "セ" } );
|
||||
addGroup( new String[]{ "そ", "ソ", "ソ" } );
|
||||
addGroup( new String[]{ "た", "タ", "タ" } );
|
||||
addGroup( new String[]{ "ち", "チ", "チ" } );
|
||||
addGroup( new String[]{ "つ", "ツ", "ツ", "っ", "ッ", "ッ" } );
|
||||
addGroup( new String[]{ "て", "テ", "テ" } );
|
||||
addGroup( new String[]{ "と", "ト", "ト" } );
|
||||
addGroup( new String[]{ "な", "ナ", "ナ" } );
|
||||
addGroup( new String[]{ "に", "ニ", "ニ" } );
|
||||
addGroup( new String[]{ "ぬ", "ヌ", "ヌ" } );
|
||||
addGroup( new String[]{ "ね", "ネ", "ネ" } );
|
||||
addGroup( new String[]{ "の", "ノ", "ノ" } );
|
||||
addGroup( new String[]{ "は", "ハ", "ハ" } );
|
||||
addGroup( new String[]{ "ひ", "ヒ", "ヒ" } );
|
||||
addGroup( new String[]{ "ふ", "フ", "フ" } );
|
||||
addGroup( new String[]{ "へ", "ヘ", "ヘ" } );
|
||||
addGroup( new String[]{ "ほ", "ホ", "ホ" } );
|
||||
addGroup( new String[]{ "ま", "マ", "マ" } );
|
||||
addGroup( new String[]{ "み", "ミ", "ミ" } );
|
||||
addGroup( new String[]{ "む", "ム", "ム" } );
|
||||
addGroup( new String[]{ "め", "メ", "メ" } );
|
||||
addGroup( new String[]{ "も", "モ", "モ" } );
|
||||
addGroup( new String[]{ "や", "ヤ", "ヤ", "ゃ", "ャ", "ャ" } );
|
||||
addGroup( new String[]{ "ゆ", "ユ", "ユ", "ゅ", "ュ", "ュ" } );
|
||||
addGroup( new String[]{ "よ", "ヨ", "ヨ", "ょ", "ョ", "ョ" } );
|
||||
addGroup( new String[]{ "ら", "ラ", "ラ" } );
|
||||
addGroup( new String[]{ "り", "リ", "リ" } );
|
||||
addGroup( new String[]{ "る", "ル", "ル" } );
|
||||
addGroup( new String[]{ "れ", "レ", "レ" } );
|
||||
addGroup( new String[]{ "ろ", "ロ", "ロ" } );
|
||||
addGroup( new String[]{ "わ", "ワ", "ワ" } );
|
||||
addGroup( new String[]{ "を", "ヲ", "ヲ" } );
|
||||
addGroup( new String[]{ "ん", "ン", "ン" } );
|
||||
}
|
||||
}
|
|
@ -6,9 +6,13 @@ import android.support.annotation.Nullable;
|
|||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import jp.juggler.subwaytooter.api.TootParser;
|
||||
import jp.juggler.subwaytooter.api.entity.CustomEmoji;
|
||||
import jp.juggler.subwaytooter.api.entity.NicoProfileEmoji;
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment;
|
||||
import jp.juggler.subwaytooter.table.HighlightWord;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class DecodeOptions {
|
||||
|
@ -62,4 +66,13 @@ public class DecodeOptions {
|
|||
public Spannable decodeEmoji( @NonNull final Context context, @NonNull final String s ){
|
||||
return EmojiDecoder.decodeEmoji( context, s, this );
|
||||
}
|
||||
|
||||
// highlight first found
|
||||
@Nullable public HighlightWord highlight_sound;
|
||||
|
||||
@Nullable public WordTrieTree highlight_trie;
|
||||
public DecodeOptions setHighlightTrie( WordTrieTree highlight_trie ){
|
||||
this.highlight_trie = highlight_trie;
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ import jp.juggler.subwaytooter.App1;
|
|||
import jp.juggler.subwaytooter.R;
|
||||
import jp.juggler.subwaytooter.api.entity.CustomEmoji;
|
||||
import jp.juggler.subwaytooter.api.entity.NicoProfileEmoji;
|
||||
import jp.juggler.subwaytooter.table.HighlightWord;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class EmojiDecoder {
|
||||
|
@ -22,9 +23,38 @@ public class EmojiDecoder {
|
|||
private static class DecodeEnv {
|
||||
@NonNull final Context context;
|
||||
@NonNull final SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
@NonNull final DecodeOptions options;
|
||||
int normal_char_start = -1;
|
||||
|
||||
DecodeEnv( @NonNull Context context ){
|
||||
DecodeEnv( @NonNull Context context ,@NonNull final DecodeOptions options){
|
||||
this.context = context;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
|
||||
void closeNormalText(){
|
||||
if( normal_char_start != -1 ){
|
||||
int end = sb.length();
|
||||
applyHighlight(normal_char_start,end);
|
||||
normal_char_start = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private void applyHighlight( int start , int end ){
|
||||
if( options.highlight_trie != null ){
|
||||
ArrayList<WordTrieTree.Match > list = options.highlight_trie.matchList( sb,start,end );
|
||||
if( list != null ){
|
||||
for( WordTrieTree.Match range : list ){
|
||||
HighlightWord word = HighlightWord.load( range.word );
|
||||
if( word !=null ){
|
||||
sb.setSpan( new HighlightSpan( word.color_fg,word.color_bg ), range.start, range.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE );
|
||||
if( word.sound_type != HighlightWord.SOUND_TYPE_NONE ){
|
||||
options.highlight_sound = word;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addUnicodeString( String s ){
|
||||
|
@ -34,6 +64,7 @@ public class EmojiDecoder {
|
|||
int remain = end - i;
|
||||
String emoji = null;
|
||||
Integer image_id = null;
|
||||
|
||||
for( int j = EmojiMap201709.utf16_max_length ; j > 0 ; -- j ){
|
||||
if( j > remain ) continue;
|
||||
String check = s.substring( i, i + j );
|
||||
|
@ -55,6 +86,9 @@ public class EmojiDecoder {
|
|||
if( image_id == 0 ){
|
||||
// 絵文字バリエーション・シーケンス(EVS)のU+FE0E(VS-15)が直後にある場合
|
||||
// その文字を絵文字化しない
|
||||
if( normal_char_start == - 1 ){
|
||||
normal_char_start = sb.length();
|
||||
}
|
||||
sb.append( emoji );
|
||||
}else{
|
||||
addImageSpan( emoji, image_id );
|
||||
|
@ -63,6 +97,9 @@ public class EmojiDecoder {
|
|||
continue;
|
||||
}
|
||||
|
||||
if( normal_char_start == - 1 ){
|
||||
normal_char_start = sb.length();
|
||||
}
|
||||
int length = Character.charCount( s.codePointAt( i ) );
|
||||
if( length == 1 ){
|
||||
sb.append( s.charAt( i ) );
|
||||
|
@ -75,6 +112,7 @@ public class EmojiDecoder {
|
|||
}
|
||||
|
||||
void addImageSpan( String text, @DrawableRes int res_id ){
|
||||
closeNormalText();
|
||||
int start = sb.length();
|
||||
sb.append( text );
|
||||
int end = sb.length();
|
||||
|
@ -82,6 +120,7 @@ public class EmojiDecoder {
|
|||
}
|
||||
|
||||
void addNetworkEmojiSpan( String text, @NonNull String url ){
|
||||
closeNormalText();
|
||||
int start = sb.length();
|
||||
sb.append( text );
|
||||
int end = sb.length();
|
||||
|
@ -89,44 +128,7 @@ public class EmojiDecoder {
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean isWhitespaceBeforeEmoji( int cp ){
|
||||
switch( cp ){
|
||||
case 0x0009: // HORIZONTAL TABULATION
|
||||
case 0x000A: // LINE FEED
|
||||
case 0x000B: // VERTICAL TABULATION
|
||||
case 0x000C: // FORM FEED
|
||||
case 0x000D: // CARRIAGE RETURN
|
||||
case 0x001C: // FILE SEPARATOR
|
||||
case 0x001D: // GROUP SEPARATOR
|
||||
case 0x001E: // RECORD SEPARATOR
|
||||
case 0x001F: // UNIT SEPARATOR
|
||||
case 0x0020:
|
||||
case 0x00A0: //非区切りスペース
|
||||
case 0x1680:
|
||||
case 0x180E:
|
||||
case 0x2000:
|
||||
case 0x2001:
|
||||
case 0x2002:
|
||||
case 0x2003:
|
||||
case 0x2004:
|
||||
case 0x2005:
|
||||
case 0x2006:
|
||||
case 0x2007: //非区切りスペース
|
||||
case 0x2008:
|
||||
case 0x2009:
|
||||
case 0x200A:
|
||||
case 0x200B:
|
||||
case 0x202F: //非区切りスペース
|
||||
case 0x205F:
|
||||
case 0x2060:
|
||||
case 0x3000:
|
||||
case 0x3164:
|
||||
case 0xFEFF:
|
||||
return true;
|
||||
default:
|
||||
return Character.isWhitespace( cp );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean isShortCodeCharacter( int cp ){
|
||||
return ( 'A' <= cp && cp <= 'Z' )
|
||||
|
@ -165,7 +167,7 @@ public class EmojiDecoder {
|
|||
}else if( i + width < end && s.codePointAt( i + width ) == '@' ){
|
||||
// フレニコのプロフ絵文字 :@who: は手前の空白を要求しない
|
||||
break;
|
||||
}else if( i == 0 || isWhitespaceBeforeEmoji( s.codePointBefore( i ) ) ){
|
||||
}else if( i == 0 || CharacterGroup.isWhitespace( s.codePointBefore( i ) ) ){
|
||||
// ショートコードの手前は始端か改行か空白文字でないとならない
|
||||
// 空白文字の判定はサーバサイドのそれにあわせる
|
||||
break;
|
||||
|
@ -218,7 +220,7 @@ public class EmojiDecoder {
|
|||
, @NonNull final String s
|
||||
, @NonNull DecodeOptions options
|
||||
){
|
||||
final DecodeEnv decode_env = new DecodeEnv( context );
|
||||
final DecodeEnv decode_env = new DecodeEnv( context ,options);
|
||||
final CustomEmoji.Map custom_map = options.customEmojiMap;
|
||||
final NicoProfileEmoji.Map profile_emojis = options.profile_emojis;
|
||||
|
||||
|
@ -265,6 +267,8 @@ public class EmojiDecoder {
|
|||
}
|
||||
} );
|
||||
|
||||
decode_env.closeNormalText();
|
||||
|
||||
return decode_env.sb;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import jp.juggler.subwaytooter.R;
|
|||
import jp.juggler.subwaytooter.api.entity.TootAccount;
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment;
|
||||
import jp.juggler.subwaytooter.api.entity.TootMention;
|
||||
import jp.juggler.subwaytooter.table.HighlightWord;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
|
@ -202,6 +203,9 @@ public class HTMLDecoder {
|
|||
sb.append( sb_tmp.toString() );
|
||||
}
|
||||
end = sb.length();
|
||||
|
||||
|
||||
|
||||
}else if( sb_tmp != sb ){
|
||||
// style もscript も読み捨てる
|
||||
}
|
||||
|
@ -213,6 +217,22 @@ public class HTMLDecoder {
|
|||
String link_text = sb.subSequence( start, end ).toString();
|
||||
MyClickableSpan span = new MyClickableSpan( account, link_text, href, account.findAcctColor( href ), options.link_tag );
|
||||
sb.setSpan( span, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );
|
||||
|
||||
// リンクスパンを設定した後に色をつける
|
||||
if( options.highlight_trie != null ){
|
||||
ArrayList<WordTrieTree.Match > list = options.highlight_trie.matchList( sb,start,end );
|
||||
if( list != null ){
|
||||
for( WordTrieTree.Match range : list ){
|
||||
HighlightWord word = HighlightWord.load( range.word );
|
||||
if( word !=null ){
|
||||
sb.setSpan( new HighlightSpan( word.color_fg,word.color_bg ), range.start, range.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE );
|
||||
if( word.sound_type != HighlightWord.SOUND_TYPE_NONE ){
|
||||
options.highlight_sound = word;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package jp.juggler.subwaytooter.util;
|
||||
|
||||
import android.text.TextPaint;
|
||||
import android.text.style.CharacterStyle;
|
||||
|
||||
public class HighlightSpan extends CharacterStyle {
|
||||
|
||||
public final int color_fg;
|
||||
public final int color_bg;
|
||||
|
||||
HighlightSpan( int color_fg ,int color_bg ){
|
||||
super();
|
||||
this.color_fg = color_fg;
|
||||
this.color_bg = color_bg;
|
||||
}
|
||||
|
||||
@Override public void updateDrawState( TextPaint ds ){
|
||||
// super.updateDrawState( ds );
|
||||
|
||||
if( color_fg != 0 ){
|
||||
ds.setColor( color_fg );
|
||||
}
|
||||
if( color_bg != 0 ){
|
||||
ds.bgColor = color_bg;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -35,6 +35,7 @@ import jp.juggler.subwaytooter.api.entity.CustomEmoji;
|
|||
import jp.juggler.subwaytooter.api.entity.TootAccount;
|
||||
import jp.juggler.subwaytooter.api.entity.TootInstance;
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus;
|
||||
import jp.juggler.subwaytooter.api.TootParser;
|
||||
import jp.juggler.subwaytooter.dialog.DlgConfirm;
|
||||
import jp.juggler.subwaytooter.dialog.EmojiPicker;
|
||||
import jp.juggler.subwaytooter.table.AcctColor;
|
||||
|
@ -315,7 +316,7 @@ public class PostHelper implements CustomEmojiLister.Callback, EmojiPicker.Callb
|
|||
|
||||
TootApiResult result = client.request( "/api/v1/statuses", request_builder );
|
||||
if( result != null && result.object != null ){
|
||||
status = TootStatus.parse( activity, account, result.object );
|
||||
status = new TootParser( activity, account).status( result.object );
|
||||
if( status != null ){
|
||||
Spannable s = status.decoded_content;
|
||||
MyClickableSpan[] span_list = s.getSpans( 0, s.length(), MyClickableSpan.class );
|
||||
|
@ -599,7 +600,7 @@ public class PostHelper implements CustomEmojiLister.Callback, EmojiPicker.Callb
|
|||
}
|
||||
|
||||
// : の手前は始端か改行か空白でなければならない
|
||||
if( last_colon > 0 && ! EmojiDecoder.isWhitespaceBeforeEmoji( src.codePointBefore( last_colon ) ) ){
|
||||
if( last_colon > 0 && ! CharacterGroup.isWhitespace( src.codePointBefore( last_colon ) ) ){
|
||||
log.d( "checkEmoji: invalid character before shortcode." );
|
||||
closeAcctPopup();
|
||||
return;
|
||||
|
|
|
@ -173,11 +173,11 @@ public class Utils {
|
|||
}
|
||||
}
|
||||
|
||||
public static String optStringX( JSONObject src, String key ){
|
||||
@Nullable public static String optStringX( JSONObject src, String key ){
|
||||
return src.isNull( key ) ? null : src.optString( key );
|
||||
}
|
||||
|
||||
public static String optStringX( JSONArray src, int key ){
|
||||
@Nullable public static String optStringX( JSONArray src, int key ){
|
||||
return src.isNull( key ) ? null : src.optString( key );
|
||||
}
|
||||
|
||||
|
|
|
@ -1,76 +1,143 @@
|
|||
package jp.juggler.subwaytooter.util;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.util.SparseArrayCompat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class WordTrieTree {
|
||||
|
||||
static class Match {
|
||||
String word;
|
||||
int start;
|
||||
int end;
|
||||
}
|
||||
|
||||
// private static class Matcher {
|
||||
//
|
||||
// // ミュートの場合などは短いマッチでも構わない
|
||||
// final boolean allowShortMatch;
|
||||
//
|
||||
// // マッチ範囲の始端を覚えておく
|
||||
// int start;
|
||||
//
|
||||
// Matcher( boolean allowShortMatch ){
|
||||
// this.allowShortMatch = allowShortMatch;
|
||||
// }
|
||||
//
|
||||
// void setTokenizer( CharacterGroup grouper, CharSequence src, int start, int end ){
|
||||
// this.match = null;
|
||||
// this.start = start;
|
||||
// if( t == null ){
|
||||
//
|
||||
// }else{
|
||||
// t.reset( src, start, end );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
private static final CharacterGroup grouper = new CharacterGroup();
|
||||
|
||||
private static class Node {
|
||||
|
||||
final SparseArrayCompat< Node > child_nodes = new SparseArrayCompat<>();
|
||||
// 続くノード
|
||||
@NonNull final SparseArrayCompat< Node > child_nodes = new SparseArrayCompat<>();
|
||||
|
||||
boolean is_end;
|
||||
// このノードが終端なら、マッチした単語の元の表記がある
|
||||
@Nullable String match_word;
|
||||
|
||||
boolean match( String s, int offset, int remain ){
|
||||
|
||||
if( is_end ){
|
||||
// ワードの始端から終端までマッチした
|
||||
return true;
|
||||
}
|
||||
|
||||
if( remain <= 0 ){
|
||||
// テスト文字列の終端に達した
|
||||
return false;
|
||||
}
|
||||
|
||||
int c = s.charAt( offset );
|
||||
++ offset;
|
||||
-- remain;
|
||||
|
||||
Node n = child_nodes.get( c );
|
||||
return n != null && n.match( s, offset, remain );
|
||||
}
|
||||
|
||||
public void add( String s, int offset, int remain ){
|
||||
|
||||
if( is_end ){
|
||||
// NGワード用なので、既に終端を含むなら後続ノードの情報は不要
|
||||
return;
|
||||
}
|
||||
|
||||
if( remain <= 0 ){
|
||||
|
||||
// 終端マークを設定
|
||||
is_end = true;
|
||||
|
||||
// 後続ノードは不要になる
|
||||
child_nodes.clear();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int c = s.charAt( offset );
|
||||
++ offset;
|
||||
-- remain;
|
||||
|
||||
// 文字別に後続ノードを作成
|
||||
Node n = child_nodes.get( c );
|
||||
if( n == null ) child_nodes.put( c, n = new Node() );
|
||||
|
||||
n.add( s, offset, remain );
|
||||
}
|
||||
// Trieツリー的には終端単語と続くノードの両方が存在する場合がありうる。
|
||||
// たとえば ABC と ABCDEF を登録してからABCDEF を探索したら、単語 ABC と単語 DEF にマッチする。
|
||||
}
|
||||
|
||||
private final Node node_root = new Node();
|
||||
|
||||
public void add( @NonNull String s ){
|
||||
node_root.add( s, 0, s.length() );
|
||||
// 単語の追加
|
||||
public void add( @NonNull String s ){
|
||||
CharacterGroup.Tokenizer t = grouper.tokenizer( s, 0, s.length() );
|
||||
Node node = node_root;
|
||||
for( ; ; ){
|
||||
t.next();
|
||||
int id = t.c;
|
||||
if( id == CharacterGroup.END ){
|
||||
// より長いマッチ単語を覚えておく
|
||||
if( node.match_word == null || node.match_word.length() < t.text.length() ){
|
||||
node.match_word = t.text.toString();
|
||||
}
|
||||
return;
|
||||
}
|
||||
Node child = node.child_nodes.get( t.c );
|
||||
if( child == null ){
|
||||
node.child_nodes.put( id, child = new Node() );
|
||||
}
|
||||
node = child;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean containsWord( @NonNull String src ){
|
||||
for( int i = 0, ie = src.length() ; i < ie ; ++ i ){
|
||||
if( node_root.match( src, i, ie - i ) ) return true;
|
||||
// 前方一致でマッチング
|
||||
@Nullable
|
||||
private Match match( boolean allowShortMatch, @NonNull CharacterGroup.Tokenizer t ){
|
||||
|
||||
int start = t.offset;
|
||||
Match dst = null;
|
||||
|
||||
Node node = node_root;
|
||||
for( ; ; ){
|
||||
|
||||
// このノードは単語の終端でもある
|
||||
if( node.match_word != null ){
|
||||
dst = new Match();
|
||||
dst.word = node.match_word;
|
||||
dst.start = start;
|
||||
dst.end = t.offset;
|
||||
|
||||
// 最短マッチのみを調べるのなら、以降の処理は必要ない
|
||||
if( allowShortMatch ) break;
|
||||
}
|
||||
|
||||
t.next();
|
||||
int id = t.c;
|
||||
if( id == CharacterGroup.END ) break;
|
||||
Node child = node.child_nodes.get( id );
|
||||
if( child == null ) break;
|
||||
node = child;
|
||||
}
|
||||
return false;
|
||||
return dst;
|
||||
}
|
||||
|
||||
public boolean matchShort( @Nullable CharSequence src ){
|
||||
return null != src && null != matchShort( src, 0, src.length() );
|
||||
}
|
||||
|
||||
private Match matchShort( @NonNull CharSequence src, int start, int end ){
|
||||
CharacterGroup.Tokenizer t = grouper.tokenizer( src, start, end );
|
||||
for( int i = start ; i < end ; ++ i ){
|
||||
int c = src.charAt( i );
|
||||
if( CharacterGroup.isWhitespace( c ) ) continue;
|
||||
t.reset( src, i, end );
|
||||
Match item = match( true, t );
|
||||
if( item != null ) return item;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable ArrayList< Match > matchList( @NonNull CharSequence src, int start, int end ){
|
||||
ArrayList< Match > dst = null;
|
||||
|
||||
CharacterGroup.Tokenizer t = grouper.tokenizer( src, start, end );
|
||||
for( int i = start ; i < end ; ++ i ){
|
||||
int c = src.charAt( i );
|
||||
if( CharacterGroup.isWhitespace( c ) ) continue;
|
||||
t.reset( src, i, end );
|
||||
Match item = match( false, t );
|
||||
if( item != null ){
|
||||
if( dst == null ) dst = new ArrayList<>();
|
||||
dst.add( item );
|
||||
i = item.end - 1;
|
||||
}
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 668 B |
After Width: | Height: | Size: 604 B |
After Width: | Height: | Size: 442 B |
After Width: | Height: | Size: 396 B |
After Width: | Height: | Size: 846 B |
After Width: | Height: | Size: 756 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,128 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:fadeScrollbars="false"
|
||||
android:fillViewport="true"
|
||||
android:padding="12dp"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/highlight_word"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvName"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:padding="6dp"
|
||||
android:gravity="center"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/text_color"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnTextColorEdit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/edit"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnTextColorReset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reset"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/background_color"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnBackgroundColorEdit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/edit"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnBackgroundColorReset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reset"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/notification_sound"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Switch
|
||||
android:id="@+id/swSound"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:gravity="center"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnNotificationSoundEdit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/edit"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnNotificationSoundReset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reset"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/llContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
||||
android:orientation="vertical"
|
||||
|
||||
>
|
||||
|
||||
<!-- Need to wrap DragListView in another layout for wrap_content to work for some reason -->
|
||||
<com.woxthebox.draglistview.DragListView
|
||||
android:id="@+id/drag_list_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text="@string/highlight_desc"
|
||||
android:textSize="12sp"
|
||||
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnAdd"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/new_item"
|
||||
android:src="?attr/ic_add"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
|
@ -0,0 +1,78 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.woxthebox.draglistview.swipe.ListSwipeItem
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:leftViewId="@+id/item_left"
|
||||
app:rightViewId="@+id/item_right"
|
||||
app:swipeViewId="@+id/item_layout">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_left"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignBottom="@+id/item_layout"
|
||||
android:layout_alignTop="@+id/item_layout"
|
||||
android:background="#0088ff"
|
||||
android:gravity="center"
|
||||
android:text="@string/app_name"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="20sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_right"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignBottom="@+id/item_layout"
|
||||
android:layout_alignTop="@+id/item_layout"
|
||||
android:background="?attr/colorColumnListDeleteBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/delete"
|
||||
android:textColor="?attr/colorColumnListDeleteText"
|
||||
android:textSize="20sp"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@id/item_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/column_list_selector"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivDragHandle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="?attr/colorColumnListDragHandleBackground"
|
||||
android:contentDescription="@string/drag_handle"
|
||||
android:scaleType="center"
|
||||
android:src="?attr/ic_knob"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textSize="20sp"
|
||||
android:minHeight="48dp"
|
||||
android:gravity="center_vertical|start"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
/>
|
||||
<ImageButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/btnSound"
|
||||
android:src="?attr/ic_volume_up"
|
||||
android:contentDescription="@string/check_sound"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
</com.woxthebox.draglistview.swipe.ListSwipeItem>
|
|
@ -128,6 +128,10 @@
|
|||
android:id="@+id/nav_muted_word"
|
||||
android:icon="?attr/ic_setting"
|
||||
android:title="@string/muted_word"/>
|
||||
<item
|
||||
android:id="@+id/nav_highlight_word"
|
||||
android:icon="?attr/ic_setting"
|
||||
android:title="@string/highlight_word"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_app_about"
|
||||
|
|
|
@ -587,8 +587,14 @@
|
|||
<string name="dont_repeat_download_to_same_url">Can\'t repeat downloading same URL in a short time.</string>
|
||||
<string name="zooming_of">×%3$.1f\n%1$d×%2$d</string>
|
||||
<string name="media_attachment_still_uploading">Media uploading has not completed yet.</string>
|
||||
<string name="highlight_word">Highlight word</string>
|
||||
<string name="new_item">New item…</string>
|
||||
<string name="word_empty">Please input keyword.</string>
|
||||
<string name="already_exist">already exist.</string>
|
||||
<string name="highlight_desc">Swipe to delete. You may need reload column to check update.</string>
|
||||
<string name="check_sound">Check sound</string>
|
||||
|
||||
<!--<string name="abc_action_bar_home_description">Revenir à l\'accueil</string>-->
|
||||
<!--<string name="abc_action_bar_home_description">Revenir à l\'accueil</string>-->
|
||||
<!--<string name="abc_action_bar_home_description_format">%1$s, %2$s</string>-->
|
||||
<!--<string name="abc_action_bar_home_subtitle_description_format">%1$s, %2$s, %3$s</string>-->
|
||||
<!--<string name="abc_action_bar_up_description">Revenir en haut de la page</string>-->
|
||||
|
|
|
@ -874,5 +874,11 @@
|
|||
<string name="dont_repeat_download_to_same_url">同じURLを数秒以内に繰り返しダウンロードすることはできません</string>
|
||||
<string name="zooming_of">×%3$.1f\n%1$d×%2$d</string>
|
||||
<string name="media_attachment_still_uploading">添付メディアのアップロードが終わってません</string>
|
||||
<string name="highlight_word">Highlight word</string>
|
||||
<string name="new_item">New item…</string>
|
||||
<string name="word_empty">Please input keyword.</string>
|
||||
<string name="already_exist">既に存在します</string>
|
||||
<string name="highlight_desc">Swipe to delete. You may need reload column to check update.</string>
|
||||
<string name="check_sound">Check sound</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -133,5 +133,6 @@
|
|||
<attr name="ic_copy" format="reference" />
|
||||
<attr name="ic_left" format="reference" />
|
||||
<attr name="ic_right" format="reference" />
|
||||
<attr name="ic_volume_up" format="reference" />
|
||||
|
||||
</resources>
|
|
@ -577,5 +577,11 @@
|
|||
<string name="dont_repeat_download_to_same_url">Can\'t repeat downloading same URL in a few second.</string>
|
||||
<string name="zooming_of">×%3$.1f\n%1$d×%2$d</string>
|
||||
<string name="media_attachment_still_uploading">Media uploading has not completed yet.</string>
|
||||
<string name="highlight_word">Highlight word</string>
|
||||
<string name="new_item">New item…</string>
|
||||
<string name="word_empty">Please input keyword.</string>
|
||||
<string name="already_exist">already exist.</string>
|
||||
<string name="highlight_desc">Swipe to delete. You may need reload column to check update.</string>
|
||||
<string name="check_sound">Check sound</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -106,6 +106,8 @@
|
|||
<item name="ic_copy">@drawable/ic_copy</item>
|
||||
<item name="ic_left">@drawable/ic_left</item>
|
||||
<item name="ic_right">@drawable/ic_right</item>
|
||||
<item name="ic_volume_up">@drawable/ic_volume_up</item>
|
||||
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Light.NoActionBar" parent="AppTheme.Light">
|
||||
|
@ -214,6 +216,7 @@
|
|||
<item name="ic_copy">@drawable/ic_copy_dark</item>
|
||||
<item name="ic_left">@drawable/ic_left_dark</item>
|
||||
<item name="ic_right">@drawable/ic_right_dark</item>
|
||||
<item name="ic_volume_up">@drawable/ic_volume_up_dark</item>
|
||||
|
||||
</style>
|
||||
|
||||
|
|