リファクタ

This commit is contained in:
tateisu 2017-12-21 05:55:51 +09:00
parent 63aa33c02d
commit 276d4fd4c5
22 changed files with 237 additions and 270 deletions

View File

@ -16,6 +16,7 @@
<w>firebase</w> <w>firebase</w>
<w>foregrounder</w> <w>foregrounder</w>
<w>gifv</w> <w>gifv</w>
<w>github</w>
<w>hashtag</w> <w>hashtag</w>
<w>hashtags</w> <w>hashtags</w>
<w>hohoemi</w> <w>hohoemi</w>

View File

@ -57,8 +57,6 @@ dependencies {
exclude group: 'com.android.support', module: 'support-annotations' exclude group: 'com.android.support', module: 'support-annotations'
}) })
// SDKのソースの27が提供されたタイミングで依存関係をまとめて上げる
//
compile 'com.android.support:support-v4:26.1.0' compile 'com.android.support:support-v4:26.1.0'
compile 'com.android.support:appcompat-v7:26.1.0' compile 'com.android.support:appcompat-v7:26.1.0'
@ -69,7 +67,7 @@ dependencies {
compile 'com.google.firebase:firebase-messaging:11.6.2' compile 'com.google.firebase:firebase-messaging:11.6.2'
testCompile 'junit:junit:4.12' testCompile 'junit:junit:4.12'
compile 'commons-io:commons-io:2.6' compile 'commons-io:commons-io:2.4' // 2.6 closeQuietly try-with-resourceを使えと煩い
compile 'uk.co.chrisjenx:calligraphy:2.3.0' compile 'uk.co.chrisjenx:calligraphy:2.3.0'
compile 'com.github.woxthebox:draglistview:1.5.1' compile 'com.github.woxthebox:draglistview:1.5.1'
compile 'com.github.omadahealth:swipy:1.2.3@aar' compile 'com.github.omadahealth:swipy:1.2.3@aar'
@ -78,6 +76,9 @@ dependencies {
compile project(':exif') compile project(':exif')
compile 'com.squareup.okhttp3:okhttp:3.9.1' compile 'com.squareup.okhttp3:okhttp:3.9.1'
// com.github.bumptech.glide 4.x 27
// SDKのソースの27が提供されたら上げる
compile 'com.github.bumptech.glide:glide:3.8.0' compile 'com.github.bumptech.glide:glide:3.8.0'
compile 'com.github.bumptech.glide:okhttp3-integration:1.5.0' compile 'com.github.bumptech.glide:okhttp3-integration:1.5.0'

View File

@ -319,6 +319,7 @@ public class ActColumnList extends AppCompatActivity {
private class MyListAdapter extends DragItemAdapter< MyItem, MyViewHolder > { private class MyListAdapter extends DragItemAdapter< MyItem, MyViewHolder > {
MyListAdapter(){ MyListAdapter(){
super(); super();
setHasStableIds( true ); setHasStableIds( true );
@ -337,10 +338,10 @@ public class ActColumnList extends AppCompatActivity {
holder.bind( getItemList().get( position ) ); holder.bind( getItemList().get( position ) );
} }
@Override @Override public long getUniqueItemId( int position ){
public long getItemId( int position ){
MyItem item = mItemList.get( position ); // mItemList は親クラスのメンバ変数 MyItem item = mItemList.get( position ); // mItemList は親クラスのメンバ変数
return item.id; return item.id;
} }
} }
} }

View File

@ -7,7 +7,6 @@ import android.content.ComponentName;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.graphics.Typeface; import android.graphics.Typeface;
@ -16,7 +15,6 @@ import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Parcelable;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.customtabs.CustomTabsIntent; import android.support.customtabs.CustomTabsIntent;
@ -1304,25 +1302,25 @@ public class ActMain extends AppCompatActivity
// Android 5.xまでは MATCH_DEFAULT_ONLY でマッチするすべてのアプリを取得できる // Android 5.xまでは MATCH_DEFAULT_ONLY でマッチするすべてのアプリを取得できる
query_flag = PackageManager.MATCH_DEFAULT_ONLY; query_flag = PackageManager.MATCH_DEFAULT_ONLY;
} }
// queryIntentActivities に渡すURLは実在しないホストのものにする // queryIntentActivities に渡すURLは実在しないホストのものにする
Intent intent = new Intent( Intent.ACTION_VIEW ,Uri.parse( "https://dummy.subwaytooter.club/" )); Intent intent = new Intent( Intent.ACTION_VIEW, Uri.parse( "https://dummy.subwaytooter.club/" ) );
intent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK ); intent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK );
List< ResolveInfo > resolveInfoList = getPackageManager().queryIntentActivities( intent, query_flag ); List< ResolveInfo > resolveInfoList = getPackageManager().queryIntentActivities( intent, query_flag );
if( resolveInfoList.isEmpty() ){ if( resolveInfoList.isEmpty() ){
throw new RuntimeException( "resolveInfoList is empty." ); throw new RuntimeException( "resolveInfoList is empty." );
} }
// このアプリ以外の選択肢を集める // このアプリ以外の選択肢を集める
String my_name = getPackageName(); String my_name = getPackageName();
ArrayList< Intent > choice_list = new ArrayList<>(); ArrayList< Intent > choice_list = new ArrayList<>();
for( ResolveInfo ri : resolveInfoList ){ for( ResolveInfo ri : resolveInfoList ){
// 選択肢からこのアプリを除外 // 選択肢からこのアプリを除外
if( my_name.equals( ri.activityInfo.packageName ) ) continue; if( my_name.equals( ri.activityInfo.packageName ) ) continue;
// 選択肢のIntentは目的のUriで作成する // 選択肢のIntentは目的のUriで作成する
Intent choice = new Intent( Intent.ACTION_VIEW ,uri); Intent choice = new Intent( Intent.ACTION_VIEW, uri );
intent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK ); intent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK );
choice.setPackage( ri.activityInfo.packageName ); choice.setPackage( ri.activityInfo.packageName );
choice.setClassName( ri.activityInfo.packageName, ri.activityInfo.name ); choice.setClassName( ri.activityInfo.packageName, ri.activityInfo.name );
@ -1774,8 +1772,8 @@ public class ActMain extends AppCompatActivity
} }
static final Pattern reUrlHashTag = Pattern.compile( "\\Ahttps://([^/]+)/tags/([^?#]+)(?:\\z|[?#])" ); static final Pattern reUrlHashTag = Pattern.compile( "\\Ahttps://([^/]+)/tags/([^?#]+)(?:\\z|[?#])" );
static final Pattern reUserPage = Pattern.compile( "\\Ahttps://([^/]+)/@([^?#/@]+)(?:\\z|[?#])" ); static final Pattern reUserPage = Pattern.compile( "\\Ahttps://([^/]+)/@([A-Za-z0-9_]+)(?:\\z|[?#])" );
static final Pattern reStatusPage = Pattern.compile( "\\Ahttps://([^/]+)/@([^?#/@]+)/(\\d+)(?:\\z|[?#])" ); static final Pattern reStatusPage = Pattern.compile( "\\Ahttps://([^/]+)/@([A-Za-z0-9_]+)/(\\d+)(?:\\z|[?#])" );
public void openChromeTab( final int pos, @Nullable final SavedAccount access_info, final String url, boolean noIntercept ){ public void openChromeTab( final int pos, @Nullable final SavedAccount access_info, final String url, boolean noIntercept ){
try{ try{
@ -2589,15 +2587,6 @@ public class ActMain extends AppCompatActivity
} }
} }
// OStatus
static final Pattern reTootUriOS = Pattern.compile( "tag:([^,]*),[^:]*:objectId=(\\d+):objectType=Status", Pattern.CASE_INSENSITIVE );
// ActivityPub 1
static final Pattern reTootUriAP1 = Pattern.compile( "https?://([^/]+)/users/[^/]+/statuses/(\\d+)" );
// ActivityPub 2
static final Pattern reTootUriAP2 = Pattern.compile( "https?://([^/]+)/@[^/]+/(\\d+)" );
// static final Pattern reUriActivityPubToot = Pattern.compile( "tag:([^,]*),[^:]*:objectId=(\\d+):objectType=Status", Pattern.CASE_INSENSITIVE );
public void openStatusOtherInstance( int pos, @NonNull SavedAccount access_info, @Nullable TootStatusLike status ){ public void openStatusOtherInstance( int pos, @NonNull SavedAccount access_info, @Nullable TootStatusLike status ){
// アカウント情報がないと出来ないことがある // アカウント情報がないと出来ないことがある
if( status == null || status.account == null ) return; if( status == null || status.account == null ) return;
@ -2609,13 +2598,16 @@ public class ActMain extends AppCompatActivity
); );
}else if( status instanceof TSToot ){ }else if( status instanceof TSToot ){
// Tootsearch ではステータスのアクセス元ホストは分からない // Tootsearch ではステータスのアクセス元ホストは分からない
// ステータスの投稿元ホストでのIDも分からない
// uri から投稿元タンスでのステータスIDを調べる
long status_id_original = TootStatusLike.parseStatusId( status );
openStatusOtherInstance( pos, access_info, status.url openStatusOtherInstance( pos, access_info, status.url
, - 1L , status_id_original
, null, - 1L , null, - 1L
); );
}else if( status instanceof TootStatus ){ }else if( status instanceof TootStatus ){
TootStatus ts = (TootStatus) status;
if( status.host_original.equals( status.host_access ) ){ if( status.host_original.equals( status.host_access ) ){
// TLアカウントのホストとトゥートのアカウントのホストが同じ場合 // TLアカウントのホストとトゥートのアカウントのホストが同じ場合
openStatusOtherInstance( pos, access_info, status.url openStatusOtherInstance( pos, access_info, status.url
@ -2624,28 +2616,8 @@ public class ActMain extends AppCompatActivity
); );
}else{ }else{
// TLアカウントのホストとトゥートのアカウントのホストが異なる場合 // TLアカウントのホストとトゥートのアカウントのホストが異なる場合
// uri から投稿元タンスでのステータスIDを調べる
long status_id_original = - 1L; long status_id_original = TootStatusLike.parseStatusId( status );
try{
// UriにステータスIDが含まれている場合がある
Matcher m = reTootUriOS.matcher( ts.uri );
if( m.find() ){
status_id_original = Long.parseLong( m.group( 2 ), 10 );
}else{
m = reTootUriAP1.matcher( ts.uri );
if( m.find() ){
status_id_original = Long.parseLong( m.group( 2 ), 10 );
}else{
m = reTootUriAP2.matcher( ts.uri );
if( m.find() ){
status_id_original = Long.parseLong( m.group( 2 ), 10 );
}
}
}
}catch( Throwable ex ){
log.e( ex, "openStatusOtherInstance: cant parse tag: %s", ts.uri );
}
openStatusOtherInstance( pos, access_info, status.url openStatusOtherInstance( pos, access_info, status.url
, status_id_original , status_id_original
@ -2673,12 +2645,20 @@ public class ActMain extends AppCompatActivity
} }
} ); } );
// トゥートの投稿元タンスにあるアカウント
ArrayList< SavedAccount > local_account_list = new ArrayList<>(); ArrayList< SavedAccount > local_account_list = new ArrayList<>();
// TLを読んだタンスにあるアカウント
ArrayList< SavedAccount > access_account_list = new ArrayList<>(); ArrayList< SavedAccount > access_account_list = new ArrayList<>();
// その他のタンスにあるアカウント
ArrayList< SavedAccount > other_account_list = new ArrayList<>(); ArrayList< SavedAccount > other_account_list = new ArrayList<>();
for( SavedAccount a : SavedAccount.loadAccountList( ActMain.this, log ) ){ for( SavedAccount a : SavedAccount.loadAccountList( ActMain.this, log ) ){
// 疑似アカウントは後でまとめて処理する // 疑似アカウントは後でまとめて処理する
if( a.isPseudo() ) continue; if( a.isPseudo() ) continue;
if( status_id_original >= 0L && host_original.equalsIgnoreCase( a.host ) ){ if( status_id_original >= 0L && host_original.equalsIgnoreCase( a.host ) ){
// アクセス情報ステータスID でアクセスできるなら // アクセス情報ステータスID でアクセスできるなら
// 同タンスのアカウントならステータスIDの変換なしに表示できる // 同タンスのアカウントならステータスIDの変換なしに表示できる
@ -2800,8 +2780,7 @@ public class ActMain extends AppCompatActivity
return result; return result;
} }
@Override @Override protected void handleResult( TootApiResult result ){
protected void handleResult( TootApiResult result ){
if( result == null ){ if( result == null ){
// cancelled. // cancelled.

View File

@ -111,10 +111,12 @@ public class ActMutedApp extends AppCompatActivity {
Cursor cursor = MutedApp.createCursor(); Cursor cursor = MutedApp.createCursor();
if( cursor != null ){ if( cursor != null ){
try{ try{
int idx_id = cursor.getColumnIndex( MutedApp.COL_ID );
int idx_name = cursor.getColumnIndex( MutedApp.COL_NAME ); int idx_name = cursor.getColumnIndex( MutedApp.COL_NAME );
while( cursor.moveToNext() ){ while( cursor.moveToNext() ){
long id = cursor.getLong( idx_id );
String name = cursor.getString( idx_name ); String name = cursor.getString( idx_name );
MyItem item = new MyItem( name ); MyItem item = new MyItem( id, name );
tmp_list.add( item ); tmp_list.add( item );
} }
@ -130,9 +132,11 @@ public class ActMutedApp extends AppCompatActivity {
// リスト要素のデータ // リスト要素のデータ
static class MyItem { static class MyItem {
final long id;
final String name; final String name;
MyItem( String name ){ MyItem( long id,String name ){
this.id = id;
this.name = name; this.name = name;
} }
} }
@ -212,10 +216,9 @@ public class ActMutedApp extends AppCompatActivity {
holder.bind( getItemList().get( position ) ); holder.bind( getItemList().get( position ) );
} }
@Override @Override public long getUniqueItemId( int position ){
public long getItemId( int position ){
MyItem item = mItemList.get( position ); // mItemList は親クラスのメンバ変数 MyItem item = mItemList.get( position ); // mItemList は親クラスのメンバ変数
return item.name.hashCode(); return item.id;
} }
} }
} }

View File

@ -111,10 +111,12 @@ public class ActMutedWord extends AppCompatActivity {
Cursor cursor = MutedWord.createCursor(); Cursor cursor = MutedWord.createCursor();
if( cursor != null ){ if( cursor != null ){
try{ try{
int idx_id = cursor.getColumnIndex( MutedWord.COL_ID );
int idx_name = cursor.getColumnIndex( MutedWord.COL_NAME ); int idx_name = cursor.getColumnIndex( MutedWord.COL_NAME );
while( cursor.moveToNext() ){ while( cursor.moveToNext() ){
long id = cursor.getLong( idx_id );
String name = cursor.getString( idx_name ); String name = cursor.getString( idx_name );
MyItem item = new MyItem( name ); MyItem item = new MyItem( id, name );
tmp_list.add( item ); tmp_list.add( item );
} }
@ -130,9 +132,11 @@ public class ActMutedWord extends AppCompatActivity {
// リスト要素のデータ // リスト要素のデータ
static class MyItem { static class MyItem {
final long id;
final String name; final String name;
MyItem( String name ){ MyItem( long id,String name ){
this.id = id;
this.name = name; this.name = name;
} }
} }
@ -213,9 +217,9 @@ public class ActMutedWord extends AppCompatActivity {
} }
@Override @Override
public long getItemId( int position ){ public long getUniqueItemId( int position ){
MyItem item = mItemList.get( position ); // mItemList は親クラスのメンバ変数 MyItem item = mItemList.get( position ); // mItemList は親クラスのメンバ変数
return item.name.hashCode(); return item.id;
} }
} }
} }

View File

@ -42,7 +42,7 @@ import jp.juggler.subwaytooter.api.entity.TootStatus;
import jp.juggler.subwaytooter.api.entity.TootTag; import jp.juggler.subwaytooter.api.entity.TootTag;
import jp.juggler.subwaytooter.api_msp.MSPClient; import jp.juggler.subwaytooter.api_msp.MSPClient;
import jp.juggler.subwaytooter.api_msp.entity.MSPToot; import jp.juggler.subwaytooter.api_msp.entity.MSPToot;
import jp.juggler.subwaytooter.api_tootsearch.TootsearchClient; import jp.juggler.subwaytooter.api_tootsearch.TSClient;
import jp.juggler.subwaytooter.api_tootsearch.entity.TSToot; import jp.juggler.subwaytooter.api_tootsearch.entity.TSToot;
import jp.juggler.subwaytooter.table.AcctColor; import jp.juggler.subwaytooter.table.AcctColor;
import jp.juggler.subwaytooter.table.AcctSet; import jp.juggler.subwaytooter.table.AcctSet;
@ -1851,7 +1851,7 @@ import jp.juggler.subwaytooter.util.Utils;
list_tmp = new ArrayList<>(); list_tmp = new ArrayList<>();
result = new TootApiResult(); result = new TootApiResult();
}else{ }else{
result = TootsearchClient.search( context, search_query, max_id, new TootsearchClient.Callback() { result = TSClient.search( context, search_query, max_id, new TSClient.Callback() {
@Override public boolean isApiCancelled(){ @Override public boolean isApiCancelled(){
return isCancelled() || is_dispose.get(); return isCancelled() || is_dispose.get();
} }
@ -1869,7 +1869,7 @@ import jp.juggler.subwaytooter.util.Utils;
if( result != null ){ if( result != null ){
if( result.object != null ){ if( result.object != null ){
// max_id の更新 // max_id の更新
max_id = TootsearchClient.getMaxId( result.object, 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( context, access_info, result.object );
list_tmp = new ArrayList<>(); list_tmp = new ArrayList<>();
@ -2801,7 +2801,7 @@ import jp.juggler.subwaytooter.util.Utils;
list_tmp = new ArrayList<>(); list_tmp = new ArrayList<>();
result = new TootApiResult( context.getString( R.string.end_of_list ) ); result = new TootApiResult( context.getString( R.string.end_of_list ) );
}else{ }else{
result = TootsearchClient.search( context, search_query, max_id, new TootsearchClient.Callback() { result = TSClient.search( context, search_query, max_id, new TSClient.Callback() {
@Override public boolean isApiCancelled(){ @Override public boolean isApiCancelled(){
return isCancelled() || is_dispose.get(); return isCancelled() || is_dispose.get();
} }
@ -2818,13 +2818,11 @@ import jp.juggler.subwaytooter.util.Utils;
} ); } );
if( result != null && result.object != null ){ if( result != null && result.object != null ){
// max_id の更新 // max_id の更新
max_id = TootsearchClient.getMaxId( result.object, 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( context, access_info, result.object );
if( search_result != null ){ list_tmp = new ArrayList<>();
list_tmp = new ArrayList<>(); addWithFilter( list_tmp, search_result );
addWithFilter( list_tmp, search_result );
}
} }
} }
return result; return result;

View File

@ -828,7 +828,8 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
case R.id.btnSearchTag: case R.id.btnSearchTag:
if( search_tag != null ){ if( search_tag != null ){
activity.openHashTag( activity.nextPosition( column ), access_info, search_tag.substring( 1 ) ); // search_tag #を含まない
activity.openHashTag( activity.nextPosition( column ), access_info, search_tag );
}else if( gap != null ){ }else if( gap != null ){
column.startGap( gap ); column.startGap( gap );
}else if( domain_block != null ){ }else if( domain_block != null ){

View File

@ -13,7 +13,6 @@ import android.widget.PopupWindow;
import jp.juggler.subwaytooter.api.entity.TootNotification; import jp.juggler.subwaytooter.api.entity.TootNotification;
import jp.juggler.subwaytooter.api.entity.TootStatus; import jp.juggler.subwaytooter.api.entity.TootStatus;
import jp.juggler.subwaytooter.api.entity.TootStatusLike; import jp.juggler.subwaytooter.api.entity.TootStatusLike;
import jp.juggler.subwaytooter.api_msp.entity.MSPToot;
import jp.juggler.subwaytooter.table.SavedAccount; import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.table.UserRelation; import jp.juggler.subwaytooter.table.UserRelation;
import jp.juggler.subwaytooter.util.LogCategory; import jp.juggler.subwaytooter.util.LogCategory;

View File

@ -21,6 +21,8 @@ import jp.juggler.subwaytooter.util.Utils;
public class TootAccount { public class TootAccount {
private static final LogCategory log = new LogCategory( "TootAccount" ); private static final LogCategory log = new LogCategory( "TootAccount" );
public static final Pattern reAccountUrl = Pattern.compile( "\\Ahttps://([A-Za-z0-9.-]+)/@([A-Za-z0-9_]+)(?:\\z|[?#])" );
public static class List extends ArrayList< TootAccount > { public static class List extends ArrayList< TootAccount > {
} }
@ -28,7 +30,7 @@ public class TootAccount {
// The ID of the account // The ID of the account
public long id; public long id;
// The username of the account // The username of the account /[A-Za-z0-9_]{1,30}/
public String username; public String username;
// Equals username for local users, includes @domain for remote ones // Equals username for local users, includes @domain for remote ones
@ -108,13 +110,12 @@ public class TootAccount {
this.username = username; this.username = username;
} }
public static TootAccount parse( Context context, LinkClickContext account, JSONObject src ){ public static TootAccount parse( @NonNull Context context, @NonNull LinkClickContext account, @Nullable JSONObject src ){
return parse( context, account, src, new TootAccount() ); return parse( context, account, src, new TootAccount() );
} }
@Nullable @Nullable
public static TootAccount parse( Context context, LinkClickContext account, JSONObject src, TootAccount dst ){ public static TootAccount parse( @NonNull Context context, @NonNull LinkClickContext account, @Nullable JSONObject src, @NonNull TootAccount dst ){
if( src == null ) return null; if( src == null ) return null;
try{ try{
dst.id = Utils.optLongX( src, "id", - 1L ); dst.id = Utils.optLongX( src, "id", - 1L );
@ -155,7 +156,7 @@ public class TootAccount {
JSONObject o = src.optJSONObject( "moved" ); JSONObject o = src.optJSONObject( "moved" );
if( o != null ){ if( o != null ){
dst.moved = TootAccount.parse( context, account, o); dst.moved = TootAccount.parse( context, account, o );
} }
return dst; return dst;
@ -178,8 +179,6 @@ public class TootAccount {
return dst; return dst;
} }
@NonNull @NonNull
public static List parseList( Context context, LinkClickContext account, JSONArray array ){ public static List parseList( Context context, LinkClickContext account, JSONArray array ){
List result = new List(); List result = new List();
@ -197,10 +196,9 @@ public class TootAccount {
} }
private static final Pattern reWhitespace = Pattern.compile( "[\\s\\t\\x0d\\x0a]+" ); private static final Pattern reWhitespace = Pattern.compile( "[\\s\\t\\x0d\\x0a]+" );
public Spannable decodeDisplayName( Context context ){ public Spannable decodeDisplayName( Context context ){
// remove white spaces // remove white spaces
String sv = reWhitespace.matcher( display_name ).replaceAll( " " ); String sv = reWhitespace.matcher( display_name ).replaceAll( " " );
@ -214,7 +212,7 @@ public class TootAccount {
}else{ }else{
this.display_name = Utils.sanitizeBDI( sv ); this.display_name = Utils.sanitizeBDI( sv );
} }
this.decoded_display_name = decodeDisplayName(context); this.decoded_display_name = decodeDisplayName( context );
} }

View File

@ -1,6 +1,7 @@
package jp.juggler.subwaytooter.api.entity; package jp.juggler.subwaytooter.api.entity;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.Spannable; import android.text.Spannable;
import android.text.TextUtils; import android.text.TextUtils;
@ -8,14 +9,19 @@ import android.text.TextUtils;
import org.json.JSONObject; import org.json.JSONObject;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import jp.juggler.subwaytooter.api_tootsearch.entity.TSToot;
import jp.juggler.subwaytooter.table.SavedAccount; import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.util.DecodeOptions; import jp.juggler.subwaytooter.util.DecodeOptions;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.Utils; import jp.juggler.subwaytooter.util.Utils;
public abstract class TootStatusLike extends TootId { public abstract class TootStatusLike extends TootId {
static final LogCategory log = new LogCategory("TootStatusLike");
//URL to the status page (can be remote) //URL to the status page (can be remote)
public String url; public String url;
@ -115,4 +121,54 @@ public abstract class TootStatusLike extends TootId {
public int originalLineCount; 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 );
// ActivityPub 1
static final Pattern reTootUriAP1 = Pattern.compile( "https?://([^/]+)/users/[A-Za-z0-9_]+/statuses/(\\d+)" );
// ActivityPub 2
static final Pattern reTootUriAP2 = Pattern.compile( "https?://([^/]+)/@[A-Za-z0-9_]+/(\\d+)" );
public static long parseStatusId( @NonNull TootStatusLike status ){
String uri;
if( status instanceof TootStatus ){
uri = ( (TootStatus) status ).uri;
}else if( status instanceof TSToot ){
uri = ( (TSToot) status ).uri;
}else{
log.d( "parseStatusId: unsupported status type: %s", status.getClass().getSimpleName() );
return - 1L;
}
try{
Matcher m;
// https://friends.nico/users/(who)/statuses/(status_id)
m = reTootUriAP1.matcher( uri );
if( m.find() ){
return Long.parseLong( m.group( 2 ), 10 );
}
// tag:mstdn.osaka,2017-12-19:objectId=5672321:objectType=Status
m = reTootUriOS.matcher( uri );
if( m.find() ){
return Long.parseLong( m.group( 2 ), 10 );
}
//
m = reTootUriAP2.matcher( uri );
if( m.find() ){
return Long.parseLong( m.group( 2 ), 10 );
}
log.d( "parseStatusId: unsupported status uri: %s", uri );
}catch( Throwable ex ){
log.e( ex, "parseStatusId: cant parse tag: %s", uri );
}
return - 1L;
}
} }

View File

@ -8,7 +8,6 @@ import android.text.TextUtils;
import org.json.JSONObject; import org.json.JSONObject;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jp.juggler.subwaytooter.api.entity.TootAccount; import jp.juggler.subwaytooter.api.entity.TootAccount;
import jp.juggler.subwaytooter.table.SavedAccount; import jp.juggler.subwaytooter.table.SavedAccount;
@ -19,10 +18,8 @@ import jp.juggler.subwaytooter.util.Utils;
public class MSPAccount extends TootAccount { public class MSPAccount extends TootAccount {
private static final LogCategory log = new LogCategory( "MSPAccount" ); private static final LogCategory log = new LogCategory( "MSPAccount" );
private static final Pattern reAccountUrl = Pattern.compile( "\\Ahttps://([^/#?]+)/@([^/#?]+)\\z" );
@Nullable @Nullable
static TootAccount parseAccount( @NonNull Context context, SavedAccount access_info, JSONObject src ){ static TootAccount parseAccount( @NonNull Context context, @NonNull SavedAccount access_info, @Nullable JSONObject src ){
if( src == null ) return null; if( src == null ) return null;

View File

@ -1,19 +1,15 @@
package jp.juggler.subwaytooter.api_tootsearch; package jp.juggler.subwaytooter.api_tootsearch;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.text.TextUtils;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import jp.juggler.subwaytooter.App1; import jp.juggler.subwaytooter.App1;
import jp.juggler.subwaytooter.Pref;
import jp.juggler.subwaytooter.R; import jp.juggler.subwaytooter.R;
import jp.juggler.subwaytooter.api.TootApiResult; import jp.juggler.subwaytooter.api.TootApiResult;
import jp.juggler.subwaytooter.api_msp.MSPApiResult;
import jp.juggler.subwaytooter.util.LogCategory; import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.Utils; import jp.juggler.subwaytooter.util.Utils;
import okhttp3.Call; import okhttp3.Call;
@ -21,60 +17,54 @@ import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
public class TootsearchClient { public class TSClient {
private static final LogCategory log = new LogCategory( "MSPClient" ); private static final LogCategory log = new LogCategory( "TSClient" );
private static final String url_token = "http://mastodonsearch.jp/api/v1.0.1/utoken";
private static final String url_search = "http://mastodonsearch.jp/api/v1.0.1/cross";
private static final String api_key = "e53de7f66130208f62d1808672bf6320523dcd0873dc69bc";
private static final OkHttpClient ok_http_client = App1.ok_http_client; private static final OkHttpClient ok_http_client = App1.ok_http_client;
public interface Callback { public interface Callback {
boolean isApiCancelled(); boolean isApiCancelled();
void publishApiProgress( String s ); void publishApiProgress( String s );
} }
public static TootApiResult search( public static TootApiResult search(
@NonNull Context context @NonNull Context context
,@NonNull String query , @NonNull String query
,@NonNull String max_id // 空文字列もしくはfromに指定するパラメータ , @NonNull String max_id // 空文字列もしくはfromに指定するパラメータ
, @NonNull Callback callback , @NonNull Callback callback
){ ){
SharedPreferences pref = Pref.pref( context ); String url = "https://tootsearch.chotto.moe/api/v1/search"
+ "?sort=" + Uri.encode( "created_at:desc" )
+ "&from=" + max_id
+ "&q=" + Uri.encode( query );
Response response; Response response;
callback.publishApiProgress( "waiting search result..." );
StringBuilder sb = new StringBuilder();
sb.append("https://tootsearch.chotto.moe/api/v1/search?sort=created_at%3Adesc");
sb.append( "&from=").append(max_id );
sb.append( "&q=").append( Uri.encode( query ) );
String url = sb.toString();
try{ try{
Request request = new Request.Builder() Request request = new Request.Builder()
.url( url ) .url( url )
.build(); .build();
callback.publishApiProgress( "waiting search result..." );
Call call = ok_http_client.newCall( request ); Call call = ok_http_client.newCall( request );
response = call.execute(); response = call.execute();
}catch( Throwable ex ){ }catch( Throwable ex ){
log.trace( ex ); log.trace( ex );
return new TootApiResult( Utils.formatError( ex, context.getResources(), R.string.network_error ) ); return new TootApiResult( Utils.formatError( ex, context.getResources(), R.string.network_error ) );
} }
if( callback.isApiCancelled() ) return null; if( callback.isApiCancelled() ) return null;
if( ! response.isSuccessful() ){ if( ! response.isSuccessful() ){
log.d("response failed."); log.d( "response failed." );
return new TootApiResult( Utils.formatResponse( response, url ) ); return new TootApiResult( Utils.formatResponse( response, url ) );
} }
try{ try{
//noinspection ConstantConditions //noinspection ConstantConditions
String json = response.body().string(); String json = response.body().string();
JSONObject object = new JSONObject( json ); JSONObject object = new JSONObject( json );
return new TootApiResult( response,null,json,object ); return new TootApiResult( response, null, json, object );
}catch( Throwable ex ){ }catch( Throwable ex ){
log.trace( ex ); log.trace( ex );
return new TootApiResult( Utils.formatError( ex, "API data error" ) ); return new TootApiResult( Utils.formatError( ex, "API data error" ) );
@ -91,9 +81,9 @@ public class TootsearchClient {
// returns the number for "from" parameter of next page. // returns the number for "from" parameter of next page.
// returns "" if no more next page. // returns "" if no more next page.
public static String getMaxId( @NonNull JSONObject root,String old ){ public static String getMaxId( @NonNull JSONObject root, String old ){
int old_from = Utils.parse_int( old, 0 ); int old_from = Utils.parse_int( old, 0 );
JSONArray hits2 = getHits( root); JSONArray hits2 = getHits( root );
if( hits2 != null ){ if( hits2 != null ){
int size = hits2.length(); int size = hits2.length();
return size == 0 ? "" : Integer.toString( old_from + hits2.length() ); return size == 0 ? "" : Integer.toString( old_from + hits2.length() );

View File

@ -1,98 +0,0 @@
package jp.juggler.subwaytooter.api_tootsearch.entity;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import org.json.JSONObject;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jp.juggler.subwaytooter.api.entity.NicoProfileEmoji;
import jp.juggler.subwaytooter.api.entity.TootAccount;
import jp.juggler.subwaytooter.api.entity.TootStatus;
import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.util.DecodeOptions;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.Utils;
public class TSAccount extends TootAccount {
private static final LogCategory log = new LogCategory( "TSAccount" );
private static final Pattern reAccountUrl = Pattern.compile( "\\Ahttps://([^/#?]+)/@([^/#?]+)\\z" );
@Nullable
static TootAccount parseAccount( @NonNull Context context, SavedAccount access_info, JSONObject src ){
if( src == null ) return null;
TSAccount dst = new TSAccount();
dst.url = Utils.optStringX( src, "url" );
if( TextUtils.isEmpty( dst.url ) ){
log.e( "parseAccount: missing url" );
return null;
}
// tootsearch のアカウントのIDはどのタンス上のものか分からない
////// dst.id = Utils.optLongX( src, "id" );
dst.id = -1L;
dst.username = Utils.optStringX( src, "username" );
dst.id = Utils.optLongX( src, "id", - 1L );
dst.username = Utils.optStringX( src, "username" );
dst.acct = Utils.optStringX( src, "acct" );
if( dst.acct == null ){
dst.acct = "?@?";
}else if( -1 == dst.acct.indexOf( '@' ) ){
Matcher m = reAccountUrl.matcher( dst.url );
if( ! m.find() ){
log.e( "parseAccount: not account url: %s", dst.url );
return null;
}else{
dst.acct = dst.username + "@" + m.group( 1 );
}
}
// 絵文字データは先に読んでおく
dst.profile_emojis = NicoProfileEmoji.parseMap( src.optJSONArray( "profile_emojis" ) );
String sv = Utils.optStringX( src, "display_name" );
dst.setDisplayName( context, dst.username, sv );
dst.locked = src.optBoolean( "locked" );
dst.created_at = Utils.optStringX( src, "created_at" );
dst.followers_count = Utils.optLongX( src, "followers_count" );
dst.following_count = Utils.optLongX( src, "following_count" );
dst.statuses_count = Utils.optLongX( src, "statuses_count" );
dst.note = Utils.optStringX( src, "note" );
dst.decoded_note = new DecodeOptions()
.setShort( true )
.setDecodeEmoji( true )
.setProfileEmojis( dst.profile_emojis )
.decodeHTML( context, access_info, dst.note );
dst.avatar = Utils.optStringX( src, "avatar" ); // "https:\/\/mastodon.juggler.jp\/system\/accounts\/avatars\/000\/000\/148\/original\/0a468974fac5a448.PNG?1492081886",
dst.avatar_static = Utils.optStringX( src, "avatar_static" ); // "https:\/\/mastodon.juggler.jp\/system\/accounts\/avatars\/000\/000\/148\/original\/0a468974fac5a448.PNG?1492081886",
dst.header = Utils.optStringX( src, "header" ); // "https:\/\/mastodon.juggler.jp\/headers\/original\/missing.png"
dst.header_static = Utils.optStringX( src, "header_static" ); // "https:\/\/mastodon.juggler.jp\/headers\/original\/missing.png"}
// ,"nico_url":null
dst.time_created_at = TootStatus.parseTime( dst.created_at );
dst.source = parseSource( src.optJSONObject( "source" ) );
// JSONObject o = src.optJSONObject( "moved" );
// if( o != null ){
// dst.moved = TootAccount.parse( context, account, o);
// }
return dst;
}
}

View File

@ -10,13 +10,15 @@ import org.json.JSONObject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.regex.Matcher;
import jp.juggler.subwaytooter.api.entity.CustomEmoji; import jp.juggler.subwaytooter.api.entity.CustomEmoji;
import jp.juggler.subwaytooter.api.entity.NicoProfileEmoji; import jp.juggler.subwaytooter.api.entity.NicoProfileEmoji;
import jp.juggler.subwaytooter.api.entity.TootAccount;
import jp.juggler.subwaytooter.api.entity.TootAttachment; import jp.juggler.subwaytooter.api.entity.TootAttachment;
import jp.juggler.subwaytooter.api.entity.TootStatus; import jp.juggler.subwaytooter.api.entity.TootStatus;
import jp.juggler.subwaytooter.api.entity.TootStatusLike; import jp.juggler.subwaytooter.api.entity.TootStatusLike;
import jp.juggler.subwaytooter.api_tootsearch.TootsearchClient; import jp.juggler.subwaytooter.api_tootsearch.TSClient;
import jp.juggler.subwaytooter.table.SavedAccount; import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.util.DecodeOptions; import jp.juggler.subwaytooter.util.DecodeOptions;
import jp.juggler.subwaytooter.util.LogCategory; import jp.juggler.subwaytooter.util.LogCategory;
@ -39,7 +41,7 @@ public class TSToot extends TootStatusLike {
if( src == null ) return null; if( src == null ) return null;
TSToot dst = new TSToot(); TSToot dst = new TSToot();
dst.account = TSAccount.parseAccount( context, access_info, src.optJSONObject( "account" ) ); dst.account = parseAccount( context, access_info, src.optJSONObject( "account" ) );
if( dst.account == null ){ if( dst.account == null ){
log.e( "missing status account" ); log.e( "missing status account" );
return null; return null;
@ -47,16 +49,15 @@ public class TSToot extends TootStatusLike {
dst.json = src; dst.json = src;
// 絵文字マップは割と最初の方で読み込んでおきたい // 絵文字マップは割と最初の方で読み込んでおきたい
dst.custom_emojis = CustomEmoji.parseMap( src.optJSONArray( "emojis" ),access_info.host); dst.custom_emojis = CustomEmoji.parseMap( src.optJSONArray( "emojis" ), access_info.host );
dst.profile_emojis = NicoProfileEmoji.parseMap( src.optJSONArray( "profile_emojis" ) ); dst.profile_emojis = NicoProfileEmoji.parseMap( src.optJSONArray( "profile_emojis" ) );
dst.url = Utils.optStringX( src, "url" ); dst.url = Utils.optStringX( src, "url" );
dst.uri = Utils.optStringX( src, "uri" ); dst.uri = Utils.optStringX( src, "uri" );
dst.host_original = dst.account.getAcctHost(); dst.host_original = dst.account.getAcctHost();
dst.host_access = "?"; dst.host_access = "?";
dst.id = -1L; // Utils.optLongX( src, "id", - 1L ); dst.id = - 1L; // Utils.optLongX( src, "id", - 1L );
if( TextUtils.isEmpty( dst.url ) || TextUtils.isEmpty( dst.host_original ) ){ if( TextUtils.isEmpty( dst.url ) || TextUtils.isEmpty( dst.host_original ) ){
log.e( "missing status url or host or id" ); log.e( "missing status url or host or id" );
@ -68,7 +69,7 @@ public class TSToot extends TootStatusLike {
dst.media_attachments = TootAttachment.parseList( src.optJSONArray( "media_attachments" ) ); dst.media_attachments = TootAttachment.parseList( src.optJSONArray( "media_attachments" ) );
dst.sensitive = src.optBoolean( "sensitive" ,false ); dst.sensitive = src.optBoolean( "sensitive", false );
dst.setSpoilerText( context, Utils.optStringX( src, "spoiler_text" ) ); dst.setSpoilerText( context, Utils.optStringX( src, "spoiler_text" ) );
@ -87,9 +88,10 @@ public class TSToot extends TootStatusLike {
public static class List extends ArrayList< TSToot > { public static class List extends ArrayList< TSToot > {
} }
@NonNull public static TSToot.List parseList( @NonNull Context context, SavedAccount access_info, @NonNull JSONObject root ){ @NonNull
public static TSToot.List parseList( @NonNull Context context, SavedAccount access_info, @NonNull JSONObject root ){
TSToot.List list = new TSToot.List(); TSToot.List list = new TSToot.List();
JSONArray array = TootsearchClient.getHits( root ); JSONArray array = TSClient.getHits( root );
if( array != null ){ if( array != null ){
for( int i = 0, ie = array.length() ; i < ie ; ++ i ){ for( int i = 0, ie = array.length() ; i < ie ; ++ i ){
JSONObject src = array.optJSONObject( i ); JSONObject src = array.optJSONObject( i );
@ -128,4 +130,34 @@ public class TSToot extends TootStatusLike {
@Override public boolean canPin( SavedAccount access_info ){ @Override public boolean canPin( SavedAccount access_info ){
return false; return false;
} }
@Nullable
private static TootAccount parseAccount( @NonNull Context context, @NonNull SavedAccount access_info, @Nullable JSONObject src ){
TootAccount dst = TootAccount.parse( context, access_info, src );
if( dst != null ){
// tootsearch のアカウントのIDはどのタンス上のものか分からない
dst.id = - 1L;
// この後の処理でURLを使うのでURLがないならパースエラーとする
if( TextUtils.isEmpty( dst.url ) ){
log.e( "parseAccount: missing url" );
return null;
}
if( - 1 == dst.acct.indexOf( '@' ) ){
Matcher m = TootAccount.reAccountUrl.matcher( dst.url );
if( ! m.find() ){
log.e( "parseAccount: not account url: %s", dst.url );
return null;
}else{
dst.acct = dst.username + "@" + m.group( 1 );
}
}
}
return dst;
}
} }

View File

@ -58,7 +58,7 @@ public class AcctColor {
onDBCreate( db ); onDBCreate( db );
return; return;
} }
if( oldVersion < 17 && newVersion >= 17 ){ if( oldVersion < 17 && newVersion >= 17 ){
try{ try{
db.execSQL( "alter table " + table + " add column " + COL_NOTIFICATION_SOUND + " text default ''" ); db.execSQL( "alter table " + table + " add column " + COL_NOTIFICATION_SOUND + " text default ''" );
@ -72,9 +72,9 @@ public class AcctColor {
public int color_fg; public int color_fg;
public int color_bg; public int color_bg;
public String nickname; public String nickname;
public String notification_sound ; public String notification_sound;
public AcctColor( @NonNull String acct, String nickname, int color_fg, int color_bg ,String notification_sound){ public AcctColor( @NonNull String acct, String nickname, int color_fg, int color_bg, String notification_sound ){
this.acct = acct; this.acct = acct;
this.nickname = nickname; this.nickname = nickname;
this.color_fg = color_fg; this.color_fg = color_fg;
@ -186,23 +186,24 @@ public class AcctColor {
} }
public static void clearMemoryCache(){ public static void clearMemoryCache(){
mMemoryCache.evictAll (); mMemoryCache.evictAll();
} }
private static final char CHAR_REPLACE = 0x328A; private static final char CHAR_REPLACE = 0x328A;
@NonNull public static CharSequence getStringWithNickname( @NonNull Context context, int string_id , @NonNull String acct ){ @NonNull
public static CharSequence getStringWithNickname( @NonNull Context context, int string_id, @NonNull String acct ){
AcctColor ac = load( acct ); AcctColor ac = load( acct );
String name = ! TextUtils.isEmpty( ac.nickname ) ? Utils.sanitizeBDI( ac.nickname ) : acct ; String name = ! TextUtils.isEmpty( ac.nickname ) ? Utils.sanitizeBDI( ac.nickname ) : acct;
SpannableStringBuilder sb = new SpannableStringBuilder( context.getString( string_id,new String(new char[]{CHAR_REPLACE})) ); SpannableStringBuilder sb = new SpannableStringBuilder( context.getString( string_id, new String( new char[]{ CHAR_REPLACE } ) ) );
for(int i=sb.length()-1;i>=0;--i){ for( int i = sb.length() - 1 ; i >= 0 ; -- i ){
char c = sb.charAt( i ); char c = sb.charAt( i );
if( c != CHAR_REPLACE) continue; if( c != CHAR_REPLACE ) continue;
sb.replace( i,i+1,name ); sb.replace( i, i + 1, name );
if( ac.color_fg != 0){ if( ac.color_fg != 0 ){
sb.setSpan( new ForegroundColorSpan( ac.color_fg ), i, i + name.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ); sb.setSpan( new ForegroundColorSpan( ac.color_fg ), i, i + name.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );
} }
if( ac.color_bg != 0){ if( ac.color_bg != 0 ){
sb.setSpan( new BackgroundColorSpan( ac.color_bg ), i, i + name.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ); sb.setSpan( new BackgroundColorSpan( ac.color_bg ), i, i + name.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );
} }
} }

View File

@ -14,6 +14,7 @@ public class MutedApp {
private static final LogCategory log = new LogCategory( "MutedApp" ); private static final LogCategory log = new LogCategory( "MutedApp" );
public static final String table = "app_mute"; public static final String table = "app_mute";
public static final String COL_ID = "_id";
public static final String COL_NAME = "name"; public static final String COL_NAME = "name";
private static final String COL_TIME_SAVE = "time_save"; private static final String COL_TIME_SAVE = "time_save";

View File

@ -13,6 +13,7 @@ public class MutedWord {
private static final LogCategory log = new LogCategory( "MutedWord" ); private static final LogCategory log = new LogCategory( "MutedWord" );
public static final String table = "word_mute"; public static final String table = "word_mute";
public static final String COL_ID = "_id";
public static final String COL_NAME = "name"; public static final String COL_NAME = "name";
private static final String COL_TIME_SAVE = "time_save"; private static final String COL_TIME_SAVE = "time_save";

View File

@ -72,10 +72,10 @@ public class SavedAccount extends TootAccount implements LinkClickContext {
public JSONObject token_info; public JSONObject token_info;
public String visibility; public String visibility;
public boolean confirm_boost; public boolean confirm_boost;
public boolean dont_hide_nsfw; public boolean dont_hide_nsfw;
public boolean dont_show_timeout; public boolean dont_show_timeout;
public boolean notification_mention; public boolean notification_mention;
public boolean notification_boost; public boolean notification_boost;
public boolean notification_favourite; public boolean notification_favourite;
@ -87,15 +87,13 @@ public class SavedAccount extends TootAccount implements LinkClickContext {
public boolean confirm_unfollow; public boolean confirm_unfollow;
public boolean confirm_post; public boolean confirm_post;
public String notification_tag; public String notification_tag;
public String register_key; public String register_key;
public long register_time; public long register_time;
private final AtomicReference< TootInstance > refInstance = new AtomicReference<>( null );
private final AtomicReference<TootInstance> refInstance = new AtomicReference<>( null );
private static final long INSTANCE_INFORMATION_EXPIRE = 60000L * 5; private static final long INSTANCE_INFORMATION_EXPIRE = 60000L * 5;
// DBには保存しない // DBには保存しない
public @Nullable TootInstance getInstance(){ public @Nullable TootInstance getInstance(){
TootInstance instance = refInstance.get(); TootInstance instance = refInstance.get();
@ -104,10 +102,10 @@ public class SavedAccount extends TootAccount implements LinkClickContext {
} }
return instance; return instance;
} }
public void setInstance(@NonNull TootInstance instance){
refInstance.set(instance);
}
public void setInstance( @NonNull TootInstance instance ){
refInstance.set( instance );
}
// アプリデータのインポート時に呼ばれる // アプリデータのインポート時に呼ばれる
public static void onDBDelete( SQLiteDatabase db ){ public static void onDBDelete( SQLiteDatabase db ){
@ -154,7 +152,7 @@ public class SavedAccount extends TootAccount implements LinkClickContext {
// 以下はDBスキーマ18で更新 // 以下はDBスキーマ18で更新
+ "," + COL_DONT_SHOW_TIMEOUT + " integer default 0" + "," + COL_DONT_SHOW_TIMEOUT + " integer default 0"
+ ")" + ")"
); );
db.execSQL( "create index if not exists " + table + "_user on " + table + "(u)" ); db.execSQL( "create index if not exists " + table + "_user on " + table + "(u)" );
@ -268,7 +266,8 @@ public class SavedAccount extends TootAccount implements LinkClickContext {
return acct.equals( "?@?" ); return acct.equals( "?@?" );
} }
private static @Nullable SavedAccount parse( Context context, Cursor cursor ) throws JSONException{ private static @Nullable
SavedAccount parse( Context context, Cursor cursor ) throws JSONException{
JSONObject src = new JSONObject( cursor.getString( cursor.getColumnIndex( COL_ACCOUNT ) ) ); JSONObject src = new JSONObject( cursor.getString( cursor.getColumnIndex( COL_ACCOUNT ) ) );
SavedAccount dst = new SavedAccount(); SavedAccount dst = new SavedAccount();
dst = (SavedAccount) parse( context, dst, src, dst ); dst = (SavedAccount) parse( context, dst, src, dst );
@ -283,7 +282,7 @@ public class SavedAccount extends TootAccount implements LinkClickContext {
dst.confirm_boost = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_CONFIRM_BOOST ) ) ); dst.confirm_boost = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_CONFIRM_BOOST ) ) );
dst.dont_hide_nsfw = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_DONT_HIDE_NSFW ) ) ); dst.dont_hide_nsfw = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_DONT_HIDE_NSFW ) ) );
dst.dont_show_timeout = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_DONT_SHOW_TIMEOUT ) ) ); dst.dont_show_timeout = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_DONT_SHOW_TIMEOUT ) ) );
dst.notification_mention = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_NOTIFICATION_MENTION ) ) ); dst.notification_mention = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_NOTIFICATION_MENTION ) ) );
dst.notification_boost = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_NOTIFICATION_BOOST ) ) ); dst.notification_boost = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_NOTIFICATION_BOOST ) ) );
dst.notification_favourite = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_NOTIFICATION_FAVOURITE ) ) ); dst.notification_favourite = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_NOTIFICATION_FAVOURITE ) ) );
@ -340,9 +339,9 @@ public class SavedAccount extends TootAccount implements LinkClickContext {
public void updateTokenInfo( @Nullable JSONObject token_info ){ public void updateTokenInfo( @Nullable JSONObject token_info ){
if( db_id == INVALID_ID ) if( db_id == INVALID_ID )
throw new RuntimeException( "SavedAccount.updateTokenInfo missing db_id" ); throw new RuntimeException( "SavedAccount.updateTokenInfo missing db_id" );
if( token_info == null ) token_info = new JSONObject( ); if( token_info == null ) token_info = new JSONObject();
this.token_info = token_info; this.token_info = token_info;
ContentValues cv = new ContentValues(); ContentValues cv = new ContentValues();
cv.put( COL_TOKEN, token_info.toString() ); cv.put( COL_TOKEN, token_info.toString() );
@ -504,7 +503,7 @@ public class SavedAccount extends TootAccount implements LinkClickContext {
public static boolean hasRealAccount( @NonNull LogCategory log ){ public static boolean hasRealAccount( @NonNull LogCategory log ){
try{ try{
Cursor cursor = App1.getDB().query( table, null, COL_USER + " NOT LIKE '?@%'", null , null, null, null, "1"); Cursor cursor = App1.getDB().query( table, null, COL_USER + " NOT LIKE '?@%'", null, null, null, null, "1" );
try{ try{
if( cursor.moveToNext() ){ if( cursor.moveToNext() ){
return true; return true;
@ -519,7 +518,6 @@ public class SavedAccount extends TootAccount implements LinkClickContext {
return false; return false;
} }
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public @NonNull String getAccountHost( @Nullable String acct ){ public @NonNull String getAccountHost( @Nullable String acct ){
if( acct != null ){ if( acct != null ){
@ -607,14 +605,6 @@ public class SavedAccount extends TootAccount implements LinkClickContext {
return "?".equals( username ); return "?".equals( username );
} }
private static final Pattern reAcctUrl = Pattern.compile( "\\Ahttps://([A-Za-z0-9.-]+)/@([A-Za-z0-9_]+)\\z" );
@Override public AcctColor findAcctColor( String url ){
Matcher m = reAcctUrl.matcher( url );
if( m.find() ) return AcctColor.load( m.group( 2 ) + "@" + m.group( 1 ) );
return null;
}
public static long getCount(){ public static long getCount(){
try{ try{
Cursor cursor = App1.getDB().query( table, new String[]{ "count(*)" }, null, null, null, null, null ); Cursor cursor = App1.getDB().query( table, new String[]{ "count(*)" }, null, null, null, null, null );
@ -683,17 +673,17 @@ public class SavedAccount extends TootAccount implements LinkClickContext {
return true; return true;
} }
private static final Comparator< SavedAccount > account_comparator = new Comparator< SavedAccount >() { private static final Comparator< SavedAccount > account_comparator = new Comparator< SavedAccount >() {
@Override public int compare( SavedAccount a, SavedAccount b ){ @Override public int compare( SavedAccount a, SavedAccount b ){
int i; int i;
// NA > !NA // NA > !NA
i = (a.isNA()? 1:0 ) - (b.isNA()? 1:0); i = ( a.isNA() ? 1 : 0 ) - ( b.isNA() ? 1 : 0 );
if(i!=0) return i; if( i != 0 ) return i;
// pseudo > real // pseudo > real
i = (a.isPseudo()? 1:0 ) - (b.isPseudo()? 1:0); i = ( a.isPseudo() ? 1 : 0 ) - ( b.isPseudo() ? 1 : 0 );
if(i!=0) return i; if( i != 0 ) return i;
String sa = AcctColor.getNickname( a.acct ); String sa = AcctColor.getNickname( a.acct );
String sb = AcctColor.getNickname( b.acct ); String sb = AcctColor.getNickname( b.acct );
@ -709,4 +699,14 @@ public class SavedAccount extends TootAccount implements LinkClickContext {
String host = who.getAcctHost(); String host = who.getAcctHost();
return host != null ? host : this.host; return host != null ? host : this.host;
} }
// implements LinkClickContext
@Override @Nullable public AcctColor findAcctColor( @Nullable String url ){
if( url != null ){
Matcher m = TootAccount.reAccountUrl.matcher( url );
if( m.find() ) return AcctColor.load( m.group( 2 ) + "@" + m.group( 1 ) );
}
return null;
}
} }

View File

@ -18,6 +18,7 @@ import java.util.regex.Pattern;
import jp.juggler.subwaytooter.App1; import jp.juggler.subwaytooter.App1;
import jp.juggler.subwaytooter.Pref; import jp.juggler.subwaytooter.Pref;
import jp.juggler.subwaytooter.R; 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.TootAttachment;
import jp.juggler.subwaytooter.api.entity.TootMention; import jp.juggler.subwaytooter.api.entity.TootMention;
import jp.juggler.subwaytooter.table.SavedAccount; import jp.juggler.subwaytooter.table.SavedAccount;
@ -99,12 +100,11 @@ public class HTMLDecoder {
private static final boolean DEBUG_HTML_PARSER = false; private static final boolean DEBUG_HTML_PARSER = false;
static final Pattern reUserPage = Pattern.compile( "\\Ahttps://([^/]+)/@([^?#/]+)(?:\\z|\\?)" );
static HashSet< String > block_tag; static HashSet< String > block_tag;
private static void prepareTagInformation(){ private static void prepareTagInformation(){
synchronized( reUserPage ){ synchronized( log ){
if( block_tag == null ){ if( block_tag == null ){
block_tag = new HashSet<>(); block_tag = new HashSet<>();
block_tag.add( "div" ); block_tag.add( "div" );
@ -273,7 +273,7 @@ public class HTMLDecoder {
if( ! display_url.startsWith( "http" ) ){ if( ! display_url.startsWith( "http" ) ){
if( display_url.startsWith( "@" ) && href != null && App1.pref.getBoolean( Pref.KEY_MENTION_FULL_ACCT, false ) ){ if( display_url.startsWith( "@" ) && href != null && App1.pref.getBoolean( Pref.KEY_MENTION_FULL_ACCT, false ) ){
// メンションをfull acct にする // メンションをfull acct にする
Matcher m = reUserPage.matcher( href ); Matcher m = TootAccount.reAccountUrl.matcher( href );
if( m.find() ){ if( m.find() ){
return "@" + m.group( 2 ) + "@" + m.group( 1 ); return "@" + m.group( 2 ) + "@" + m.group( 1 );
} }

View File

@ -1,8 +1,10 @@
package jp.juggler.subwaytooter.util; package jp.juggler.subwaytooter.util;
import android.support.annotation.Nullable;
import jp.juggler.subwaytooter.table.AcctColor; import jp.juggler.subwaytooter.table.AcctColor;
public interface LinkClickContext { public interface LinkClickContext {
AcctColor findAcctColor( String url ); @Nullable AcctColor findAcctColor( @Nullable String url );
} }

View File

@ -29,7 +29,7 @@ public class MyClickableSpan extends ClickableSpan {
@NonNull LinkClickContext lcc @NonNull LinkClickContext lcc
, @NonNull String text , @NonNull String text
, @NonNull String url , @NonNull String url
, AcctColor ac , @Nullable AcctColor ac
, @Nullable Object tag , @Nullable Object tag
){ ){
this.lcc = lcc; this.lcc = lcc;