開こうとしたURLがタグっぽい時に何かする処理を改善した

This commit is contained in:
tateisu 2017-12-31 22:14:28 +09:00
parent 425f0ad4ed
commit 067cc32ee5
11 changed files with 151 additions and 102 deletions

View File

@ -10,8 +10,8 @@ android {
applicationId "jp.juggler.subwaytooter"
minSdkVersion 21
targetSdkVersion 26
versionCode 195
versionName "1.9.5"
versionCode 196
versionName "1.9.6"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

View File

@ -88,6 +88,7 @@ import jp.juggler.subwaytooter.table.MutedApp;
import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.table.UserRelation;
import jp.juggler.subwaytooter.dialog.ActionsDialog;
import jp.juggler.subwaytooter.util.ChromeTabOpener;
import jp.juggler.subwaytooter.util.LinkClickContext;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.MyClickableSpan;
@ -1266,7 +1267,7 @@ public class ActMain extends AppCompatActivity
final String host = m.group( 1 );
final long status_id = Long.parseLong( m.group( 3 ), 10 );
// ステータスをアプリ内で開く
openStatusOtherInstance( getDefaultInsertPosition(), null, uri.toString(), status_id, host, status_id );
openStatusOtherInstance( getDefaultInsertPosition(), uri.toString(), status_id, host, status_id );
}catch( Throwable ex ){
Utils.showToast( this, ex, "can't parse status id." );
}
@ -1769,41 +1770,72 @@ public class ActMain extends AppCompatActivity
}
static final Pattern reUrlHashTag = Pattern.compile( "\\Ahttps://([^/]+)/tags/([^?#]+)(?:\\z|[?#])" );
static final Pattern reUrlHashTag = Pattern.compile( "\\Ahttps://([^/]+)/tags/([^?#\\s\\-+.,:;/]+)(?:\\z|[?#])" );
static final Pattern reUserPage = Pattern.compile( "\\Ahttps://([^/]+)/@([A-Za-z0-9_]+)(?:\\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( @NonNull final ChromeTabOpener opener ){
try{
log.d( "openChromeTab url=%s", url );
log.d( "openChromeTab url=%s", opener.url );
if( ! noIntercept && access_info != null ){
if( opener.bAllowIntercept && opener.access_info != null ){
// ハッシュタグをアプリ内で開く
Matcher m = reUrlHashTag.matcher( url );
// ハッシュタグはいきなり開くのではなくメニューがある
Matcher m = reUrlHashTag.matcher( opener.url );
if( m.find() ){
// https://mastodon.juggler.jp/tags/%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%82%BF%E3%82%B0
String host = m.group( 1 );
String tag_without_sharp = Uri.decode( m.group( 2 ) );
if( access_info.isNA() || ! host.equalsIgnoreCase( access_info.host ) ){
openHashTagOtherInstance( pos, access_info, url, host, tag_without_sharp );
}else{
openHashTag( pos, access_info, tag_without_sharp );
final String host = m.group( 1 );
final String tag_without_sharp = Uri.decode( m.group( 2 ) );
final String tag_with_sharp = "#" + tag_without_sharp;
ActionsDialog d = new ActionsDialog()
.addAction( getString( R.string.open_hashtag_column ), new Runnable() {
@Override public void run(){
openHashTagOtherInstance( opener.pos, opener.url, host, tag_without_sharp );
}
} )
.addAction( getString( R.string.open_in_browser ), new Runnable() {
@Override public void run(){
App1.openCustomTab( ActMain.this, opener.url );
}
} )
.addAction( getString( R.string.quote_hashtag_of, tag_with_sharp ), new Runnable() {
@Override public void run(){
openPost( tag_with_sharp + " " );
}
} );
if( opener.tag_list != null && opener.tag_list.size() > 1 ){
StringBuilder sb = new StringBuilder();
for( String s : opener.tag_list ){
if( sb.length() > 0 ) sb.append( ' ' );
sb.append( s );
}
final String tag_all = sb.toString();
d.addAction( getString( R.string.quote_all_hashtag_of, tag_all ), new Runnable() {
@Override public void run(){
openPost( tag_all + " " );
}
} );
}
d.show( ActMain.this, tag_with_sharp );
return;
}
// ステータスページをアプリから開く
m = reStatusPage.matcher( url );
m = reStatusPage.matcher( opener.url );
if( m.find() ){
try{
// https://mastodon.juggler.jp/@SubwayTooter/(status_id)
final String host = m.group( 1 );
final long status_id = Long.parseLong( m.group( 3 ), 10 );
if( access_info.isNA() || ! host.equalsIgnoreCase( access_info.host ) ){
openStatusOtherInstance( pos, access_info, url, status_id, host, status_id );
if( opener.access_info.isNA() || ! host.equalsIgnoreCase( opener.access_info.host ) ){
openStatusOtherInstance( opener.pos, opener.url, status_id, host, status_id );
}else{
openStatusLocal( pos, access_info, status_id );
openStatusLocal( opener.pos, opener.access_info, status_id );
}
}catch( Throwable ex ){
Utils.showToast( this, ex, "can't parse status id." );
@ -1812,23 +1844,22 @@ public class ActMain extends AppCompatActivity
}
// ユーザページをアプリ内で開く
m = reUserPage.matcher( url );
m = reUserPage.matcher( opener.url );
if( m.find() ){
// https://mastodon.juggler.jp/@SubwayTooter
final String host = m.group( 1 );
final String user = Uri.decode( m.group( 2 ) );
openProfileByHostUser( pos, access_info, url, host, user );
openProfileByHostUser( opener.pos, opener.access_info, opener.url, host, user );
return;
}
}
App1.openCustomTab( this,url);
App1.openCustomTab( this, opener.url );
}catch( Throwable ex ){
// log.trace( ex );
log.e( ex, "openChromeTab failed. url=%s", url );
log.e( ex, "openChromeTab failed. url=%s", opener.url );
}
}
@ -1841,18 +1872,16 @@ public class ActMain extends AppCompatActivity
// 他インスタンスのハッシュタグの表示
private void openHashTagOtherInstance(
int pos
, @NonNull SavedAccount access_info
, @NonNull String url
, @NonNull String host
, @NonNull String tag_without_sharp
){
openHashTagOtherInstance_sub( pos, access_info, url, host, tag_without_sharp );
openHashTagOtherInstance_sub( pos, url, host, tag_without_sharp );
}
// 他インスタンスのハッシュタグの表示
private void openHashTagOtherInstance_sub(
final int pos
, @NonNull final SavedAccount access_info
, @NonNull final String url
, @NonNull final String host
, @NonNull final String tag_without_sharp
@ -1883,7 +1912,7 @@ public class ActMain extends AppCompatActivity
// ブラウザで表示する
dialog.addAction( getString( R.string.open_web_on_host, host ), new Runnable() {
@Override public void run(){
openChromeTab( pos, access_info, url, true );
App1.openCustomTab( ActMain.this, url );
}
} );
@ -1958,63 +1987,32 @@ public class ActMain extends AppCompatActivity
}
}
final int pos = nextPosition( column );
@Nullable SavedAccount access_info = column == null ? null : column.access_info;
// ハッシュタグはいきなり開くのではなくメニューがある
Matcher m = reUrlHashTag.matcher( span.url );
if( m.find() ){
// https://mastodon.juggler.jp/tags/%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%82%BF%E3%82%B0
final String host = m.group( 1 );
final String tag_with_sharp = span.text.startsWith( "#" ) ? span.text : "#" + Uri.decode( m.group( 2 ) );
final String tag_without_sharp = tag_with_sharp.substring( 1 );
ActionsDialog d = new ActionsDialog()
.addAction( getString( R.string.open_hashtag_column ), new Runnable() {
@Override public void run(){
openHashTagOtherInstance( pos, (SavedAccount) span.lcc, span.url, host, tag_without_sharp );
}
} )
.addAction( getString( R.string.quote_hashtag_of, tag_with_sharp ), new Runnable() {
@Override public void run(){
openPost( tag_with_sharp + " " );
}
} );
final ArrayList< String > tag_list = new ArrayList<>();
try{
//noinspection ConstantConditions
CharSequence cs = ( (TextView) view_orig ).getText();
if( cs instanceof Spannable ){
Spannable content = (Spannable) cs;
for( MyClickableSpan s : content.getSpans( 0, content.length(), MyClickableSpan.class ) ){
m = reUrlHashTag.matcher( s.url );
if( m.find() ){
String s_tag = s.text.startsWith( "#" ) ? s.text : "#" + Uri.decode( m.group( 2 ) );
tag_list.add( s_tag );
}
final ArrayList< String > tag_list = new ArrayList<>();
try{
//noinspection ConstantConditions
CharSequence cs = ( (TextView) view_orig ).getText();
if( cs instanceof Spannable ){
Spannable content = (Spannable) cs;
for( MyClickableSpan s : content.getSpans( 0, content.length(), MyClickableSpan.class ) ){
Matcher m = reUrlHashTag.matcher( s.url );
if( m.find() ){
String s_tag = s.text.startsWith( "#" ) ? s.text : "#" + Uri.decode( m.group( 2 ) );
tag_list.add( s_tag );
}
}
}catch( Throwable ex ){
log.trace( ex );
}
if( tag_list.size() > 1 ){
StringBuilder sb = new StringBuilder();
for( String s : tag_list ){
if( sb.length() > 0 ) sb.append( ' ' );
sb.append( s );
}
final String tag_all = sb.toString();
d.addAction( getString( R.string.quote_all_hashtag_of, tag_all ), new Runnable() {
@Override public void run(){
openPost( tag_all + " " );
}
} );
}
d.show( ActMain.this, tag_with_sharp );
return;
}catch( Throwable ex ){
log.trace( ex );
}
openChromeTab( pos, (SavedAccount) span.lcc, span.url, false );
new ChromeTabOpener( ActMain.this, pos, span.url )
.accessInfo( access_info )
.tagList( tag_list )
.open();
}
};
@ -2120,7 +2118,7 @@ public class ActMain extends AppCompatActivity
Utils.showToast( ActMain.this, true, result.error );
// 仕方ないのでchrome tab で開く
openChromeTab( pos, access_info, who_url, true );
App1.openCustomTab( ActMain.this, who_url );
}
}
@ -2175,7 +2173,7 @@ public class ActMain extends AppCompatActivity
return;
}
// ダメならchromeで開く
openChromeTab( pos, access_info, url, true );
App1.openCustomTab( ActMain.this, url );
}
} );
}else{
@ -2192,7 +2190,7 @@ public class ActMain extends AppCompatActivity
if( ! SavedAccount.hasRealAccount( log ) ){
// 疑似アカウントではユーザ情報APIを呼べないし検索APIも使えない
// chrome tab で開くしかない
openChromeTab( pos, access_info, url, true );
App1.openCustomTab( ActMain.this, url );
}else{
// アカウントを選択して開く
AccountPicker.pick( this, false, false
@ -2559,18 +2557,18 @@ public class ActMain extends AppCompatActivity
public void openStatus( int pos, @NonNull SavedAccount access_info, @NonNull TootStatusLike status ){
if( access_info.isNA() || ! access_info.host.equalsIgnoreCase( status.host_access ) ){
openStatusOtherInstance( pos, access_info, status );
openStatusOtherInstance( pos, status );
}else{
openStatusLocal( pos, access_info, status.id );
}
}
public void openStatusOtherInstance( int pos, @NonNull SavedAccount access_info, @Nullable TootStatusLike status ){
public void openStatusOtherInstance( int pos, @Nullable TootStatusLike status ){
// アカウント情報がないと出来ないことがある
if( status == null || status.account == null ) return;
if( status instanceof MSPToot ){
openStatusOtherInstance( pos, access_info, status.url
openStatusOtherInstance( pos, status.url
, status.id
, null, - 1L
);
@ -2580,7 +2578,7 @@ public class ActMain extends AppCompatActivity
// uri から投稿元タンスでのステータスIDを調べる
long status_id_original = TootStatusLike.parseStatusId( status );
openStatusOtherInstance( pos, access_info, status.url
openStatusOtherInstance( pos, status.url
, status_id_original
, null, - 1L
);
@ -2588,7 +2586,7 @@ public class ActMain extends AppCompatActivity
}else if( status instanceof TootStatus ){
if( status.host_original.equals( status.host_access ) ){
// TLアカウントのホストとトゥートのアカウントのホストが同じ場合
openStatusOtherInstance( pos, access_info, status.url
openStatusOtherInstance( pos, status.url
, status.id
, null, - 1L
);
@ -2597,7 +2595,7 @@ public class ActMain extends AppCompatActivity
// uri から投稿元タンスでのステータスIDを調べる
long status_id_original = TootStatusLike.parseStatusId( status );
openStatusOtherInstance( pos, access_info, status.url
openStatusOtherInstance( pos, status.url
, status_id_original
, status.host_access, status.id
);
@ -2607,7 +2605,6 @@ public class ActMain extends AppCompatActivity
void openStatusOtherInstance(
final int pos
, @Nullable final SavedAccount access_info
, @NonNull final String url
, final long status_id_original
, final String host_access, final long status_id_access
@ -2619,7 +2616,7 @@ public class ActMain extends AppCompatActivity
// 選択肢ブラウザで表示する
dialog.addAction( getString( R.string.open_web_on_host, host_original ), new Runnable() {
@Override public void run(){
openChromeTab( pos, access_info, url, true );
App1.openCustomTab( ActMain.this, url );
}
} );

View File

@ -525,7 +525,8 @@ public class App1 extends Application {
CustomTabsIntent customTabsIntent = builder.build();
customTabsIntent.launchUrl( activity, Uri.parse( url ) );
}catch( Throwable ex ){
log.e( ex, "openCustomTab: failed." );
log.trace( ex );
Utils.showToast(activity,false,"can't open browser app");
}
}

View File

@ -332,7 +332,7 @@ class DlgContextMenu implements View.OnClickListener, View.OnLongClickListener {
case R.id.btnStatusWebPage:
if( status != null ){
activity.openChromeTab( pos, access_info, status.url, true );
App1.openCustomTab( activity, status.url );
}
break;
@ -356,7 +356,7 @@ class DlgContextMenu implements View.OnClickListener, View.OnLongClickListener {
case R.id.btnConversationAnotherAccount:
if( status != null ){
activity.openStatusOtherInstance( pos, access_info, status );
activity.openStatusOtherInstance( pos, status );
}
break;
@ -478,7 +478,7 @@ class DlgContextMenu implements View.OnClickListener, View.OnLongClickListener {
case R.id.btnAccountWebPage:
if( who != null ){
activity.openChromeTab( pos, access_info, who.url, true );
App1.openCustomTab( activity,who.url );
}
break;
@ -559,7 +559,8 @@ class DlgContextMenu implements View.OnClickListener, View.OnLongClickListener {
case R.id.btnAvatarImage:
if( who != null ){
String url = ! TextUtils.isEmpty( who.avatar ) ? who.avatar : who.avatar_static;
if( url != null ) activity.openChromeTab( pos, access_info, url, true );
if( url != null ) App1.openCustomTab( activity,url );
// FIXME: 設定によっては内蔵メディアビューアで開けないか
}
break;

View File

@ -148,7 +148,7 @@ class HeaderViewHolderInstance extends HeaderViewHolderBase implements View.OnCl
case R.id.btnInstance:
if( instance != null && instance.uri != null ){
activity.openChromeTab( activity.nextPosition( column ), column.access_info, "https://" + instance.uri + "/about", true );
App1.openCustomTab( activity, "https://" + instance.uri + "/about");
}
break;

View File

@ -244,7 +244,7 @@ class HeaderViewHolderProfile extends HeaderViewHolderBase implements View.OnCli
case R.id.tvRemoteProfileWarning:
if( who != null ){
// 強制的にブラウザで開く
activity.openChromeTab( activity.nextPosition( column ), access_info, who.url, true );
App1.openCustomTab( activity, who.url );
}
break;

View File

@ -785,8 +785,11 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
break;
case R.id.ivCardThumbnail:
if( status != null && status.card != null ){
activity.openChromeTab( pos, access_info, status.card.url, false );
if( status != null
&& status.card != null
&& !TextUtils.isEmpty( status.card.url )
){
App1.openCustomTab( activity, status.card.url);
}
break;
@ -911,7 +914,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
private void clickMedia( int i ){
try{
if( status instanceof MSPToot ){
activity.openStatusOtherInstance( activity.nextPosition( column ), access_info, status );
activity.openStatusOtherInstance( activity.nextPosition( column ), status );
return;
}

View File

@ -205,7 +205,7 @@ class StatusButtons implements View.OnClickListener, View.OnLongClickListener {
switch( v.getId() ){
case R.id.btnConversation:
activity.openStatusOtherInstance( activity.nextPosition( column ), access_info, status );
activity.openStatusOtherInstance( activity.nextPosition( column ), status );
break;
case R.id.btnBoost:

View File

@ -0,0 +1,46 @@
package jp.juggler.subwaytooter.util;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.ArrayList;
import jp.juggler.subwaytooter.ActMain;
import jp.juggler.subwaytooter.table.SavedAccount;
public class ChromeTabOpener {
@NonNull public final ActMain activity;
@NonNull public final String url;
public final int pos;
public ChromeTabOpener( @NonNull ActMain activity, int pos, @NonNull String url ){
this.activity = activity;
this.pos = pos;
this.url = url;
}
public void open(){
activity.openChromeTab( this );
}
@Nullable public SavedAccount access_info;
public ChromeTabOpener accessInfo( @Nullable SavedAccount access_info ){
this.access_info = access_info;
return this;
}
public boolean bAllowIntercept = true;
// public ChromeTabOpener allowIntercept( boolean v){
// this.bAllowIntercept = v;
// return this;
// }
@Nullable public ArrayList< String > tag_list;
public ChromeTabOpener tagList( ArrayList< String > tag_list ){
this.tag_list = tag_list;
return this;
}
}

View File

@ -82,7 +82,7 @@ public class PostHelper implements CustomEmojiLister.Callback, EmojiPicker.Callb
, Pattern.CASE_INSENSITIVE
);
private static final Pattern reCharsNotTag = Pattern.compile( "[\\s\\-+.,:;/]" );
private static final Pattern reCharsNotTag = Pattern.compile( "[\\s\\-+.,:;/]" );
private static final Pattern reCharsNotEmoji = Pattern.compile( "[^0-9A-Za-z_-]" );
public String content;

View File

@ -577,4 +577,5 @@
<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>
</resources>