diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.kt b/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.kt index 01dd7297..26dde978 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.kt @@ -102,11 +102,11 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli initUi() removeDefaultPref() - + load(null, null) } - private fun initUi(){ + private fun initUi() { setContentView(R.layout.act_app_setting) App1.initEdgeToEdge(this) @@ -147,11 +147,11 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli } } - private fun removeDefaultPref(){ + private fun removeDefaultPref() { val e = pref.edit() var changed = false - appSettingRoot.scan{ - if( it.pref?.removeDefault(pref,e) ==true ) changed = true + appSettingRoot.scan { + if(it.pref?.removeDefault(pref, e) == true) changed = true } if(changed) e.apply() } @@ -174,16 +174,18 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli } override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) { - if(resultCode == RESULT_OK && data != null ){ + if(resultCode == RESULT_OK && data != null) { when(requestCode) { REQUEST_CODE_APP_DATA_IMPORT -> { data.handleGetContentResult(contentResolver).firstOrNull()?.uri?.let { importAppData2(false, it) } } + REQUEST_CODE_TIMELINE_FONT -> { handleFontResult(AppSettingItem.TIMELINE_FONT, data, "TimelineFont") } + REQUEST_CODE_TIMELINE_FONT_BOLD -> { handleFontResult(AppSettingItem.TIMELINE_FONT_BOLD, data, "TimelineFontBold") } @@ -339,7 +341,8 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli // true if the item at the specified position is not a separator. // (A separator is a non-selectable, non-clickable item). - override fun areAllItemsEnabled() : Boolean =false + override fun areAllItemsEnabled() : Boolean = false + override fun isEnabled(position : Int) : Boolean = list[position] is AppSettingItem override fun getView(position : Int, convertView : View?, parent : ViewGroup?) : View = @@ -1201,7 +1204,14 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli fun openCustomShareChooser(target : CustomShareTarget) { try { - DlgAppPicker(this){ setCustomShare(target, it) }.show() + DlgAppPicker( + this, + Intent().apply { + action = Intent.ACTION_SEND + type = "text/plain" + putExtra(Intent.EXTRA_TEXT, getString(R.string.content_sample)) + } + ) { setCustomShare(target, it) }.show() } catch(ex : Throwable) { log.trace(ex) showToast(this, ex, "openCustomShareChooser failed.") @@ -1229,7 +1239,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli fun showCustomShareIcon(tv : TextView?, target : CustomShareTarget) { tv ?: return val cn = CustomShare.getCustomShareComponentName(pref, target) - val (label, icon) =CustomShare.getInfo(this, cn) + val (label, icon) = CustomShare.getInfo(this, cn) tv.text = label ?: getString(R.string.not_selected) tv.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null) tv.compoundDrawablePadding = (resources.displayMetrics.density * 4f + 0.5f).toInt() diff --git a/app/src/main/java/jp/juggler/subwaytooter/App1.kt b/app/src/main/java/jp/juggler/subwaytooter/App1.kt index 84554b06..61b04aa4 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/App1.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/App1.kt @@ -7,6 +7,7 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.SharedPreferences +import android.content.pm.PackageManager import android.content.res.ColorStateList import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteOpenHelper @@ -14,6 +15,7 @@ import android.graphics.Color import android.graphics.PorterDuff import android.net.Uri import android.os.Build +import android.os.Bundle import android.util.Log import android.view.View import android.view.WindowManager @@ -29,8 +31,10 @@ import com.bumptech.glide.load.engine.executor.GlideExecutor import com.bumptech.glide.load.engine.executor.GlideExecutor.newDiskCacheExecutor import com.bumptech.glide.load.engine.executor.GlideExecutor.newSourceExecutor import com.bumptech.glide.load.model.GlideUrl +import jp.juggler.subwaytooter.action.cn import jp.juggler.subwaytooter.api.TootApiClient import jp.juggler.subwaytooter.api.entity.TootAttachment +import jp.juggler.subwaytooter.dialog.DlgAppPicker import jp.juggler.subwaytooter.table.* import jp.juggler.subwaytooter.util.CustomEmojiCache import jp.juggler.subwaytooter.util.CustomEmojiLister @@ -59,7 +63,7 @@ class App1 : Application() { override fun onCreate() { log.d("onCreate") super.onCreate() - prepare(applicationContext,"App1.onCreate") + prepare(applicationContext, "App1.onCreate") } override fun onTerminate() { @@ -218,7 +222,7 @@ class App1 : Application() { // return maxSize * 1024; // } - val reNotAllowedInUserAgent ="[^\\x21-\\x7e]+".asciiPattern() + val reNotAllowedInUserAgent = "[^\\x21-\\x7e]+".asciiPattern() val userAgentDefault = "SubwayTooter/${BuildConfig.VERSION_NAME} Android/${Build.VERSION.RELEASE}" @@ -296,7 +300,7 @@ class App1 : Application() { @SuppressLint("StaticFieldLeak") lateinit var custom_emoji_lister : CustomEmojiLister - fun prepare(app_context : Context,caller:String) : AppState { + fun prepare(app_context : Context, caller : String) : AppState { var state = appStateX if(state != null) return state @@ -407,21 +411,21 @@ class App1 : Application() { state = AppState(app_context, pref) appStateX = state - + // getAppState()を使える状態にしてからカラム一覧をロードする log.d("load column list...") state.loadColumnList() log.d("prepare() complete! caller=$caller") - + return state } @SuppressLint("StaticFieldLeak") private var appStateX : AppState? = null - fun getAppState(context : Context,caller:String="getAppState") : AppState { - return prepare(context.applicationContext,caller) + fun getAppState(context : Context, caller : String = "getAppState") : AppState { + return prepare(context.applicationContext, caller) } fun sound(item : HighlightWord) { @@ -508,21 +512,21 @@ class App1 : Application() { fun setActivityTheme( activity : Activity, noActionBar : Boolean = false, - forceDark :Boolean = false + forceDark : Boolean = false ) { - prepare(activity.applicationContext,"setActivityTheme") + prepare(activity.applicationContext, "setActivityTheme") val theme_idx = Pref.ipUiTheme(pref) activity.setTheme( - if( forceDark || theme_idx ==1){ + if(forceDark || theme_idx == 1) { if(noActionBar) R.style.AppTheme_Dark_NoActionBar else R.style.AppTheme_Dark - }else{ + } else { if(noActionBar) R.style.AppTheme_Light_NoActionBar else R.style.AppTheme_Light } ) - setStatusBarColor(activity,forceDark=forceDark) + setStatusBarColor(activity, forceDark = forceDark) } internal val CACHE_CONTROL = CacheControl.Builder() @@ -592,59 +596,114 @@ class App1 : Application() { } - fun openBrowser(context : Context, uri : Uri?) { + private fun startActivityExcludeMyApp( + activity : AppCompatActivity, + intent : Intent, + startAnimationBundle : Bundle? = null + ) { try { - uri ?: return - val intent = Intent(Intent.ACTION_VIEW, uri) - context.startActivity(intent) + val pm = activity.packageManager!! + val flags = PackageManager.MATCH_DEFAULT_ONLY + val ri = pm.resolveActivity(intent, flags) + if(ri != null && ri.activityInfo.packageName != activity.packageName) { + // ST以外が選択された + activity.startActivity(intent, startAnimationBundle) + return + } + DlgAppPicker( + activity, + intent, + autoSelect = true, + filter = {it.activityInfo.packageName != activity.packageName } + ) { + try { + intent.component = it.cn() + activity.startActivity(intent, startAnimationBundle) + } catch(ex : Throwable) { + log.trace(ex) + showToast(activity, ex, "can't open. ${intent.data}") + } + }.show() + } catch(ex : Throwable) { - log.trace(ex, "openBrowser") - showToast(context, true, "missing web browser") + log.trace(ex) + showToast(activity, ex, "can't open. ${intent.data}") } } - fun openBrowser(context : Context, url : String?) = - openBrowser(context, url.mayUri()) + fun openBrowser(activity : AppCompatActivity, uri : Uri?) { + if(uri != null) startActivityExcludeMyApp(activity, Intent(Intent.ACTION_VIEW, uri)) + } + + fun openBrowser(activity : AppCompatActivity, url : String?) = + openBrowser(activity, url.mayUri()) + + // ubway Tooterの「アプリ設定/挙動/リンクを開く際にCustom Tabsを使わない」をONにして + // 投稿のコンテキストメニューの「トゥートへのアクション/Webページを開く」「ユーザへのアクション/Webページを開く」を使うと + // 投げたインテントをST自身が受け取って「次のアカウントから開く」ダイアログが出て + // 「Webページを開く」をまた押すと無限ループしてダイアログの影が徐々に濃くなりそのうち壊れる + // これを避けるには、投稿やトゥートを開く際に bpDontUseCustomTabs がオンならST以外のアプリを列挙したアプリ選択ダイアログを出すしかない + fun openCustomTabOrBrowser(activity : AppCompatActivity, url : String) { + if(! Pref.bpDontUseCustomTabs(pref)) { + openCustomTab(activity, url) + } else { + openBrowser(activity, url) + } + } // Chrome Custom Tab を開く - fun openCustomTab(activity : Activity, url : String) { + fun openCustomTab(activity : AppCompatActivity, url : String) { + if(Pref.bpDontUseCustomTabs(pref)) { + openCustomTabOrBrowser(activity, url) + return + } + try { - if(Pref.bpDontUseCustomTabs(pref)) { - openBrowser(activity, url) - } else { - - if(url.startsWith("http") && Pref.bpPriorChrome(pref)) { - try { - // 初回はChrome指定で試す - val customTabsIntent = CustomTabsIntent.Builder() - .setToolbarColor( - getAttributeColor( - activity, - R.attr.colorPrimary - ) + if(url.startsWith("http") && Pref.bpPriorChrome(pref)) { + try { + // 初回はChrome指定で試す + val customTabsIntent = CustomTabsIntent.Builder() + .setToolbarColor( + getAttributeColor( + activity, + R.attr.colorPrimary ) - .setShowTitle(true) - .build() - customTabsIntent.intent.component = ComponentName( - "com.android.chrome", - "com.google.android.apps.chrome.Main" ) - customTabsIntent.launchUrl(activity, url.toUri()) - return - } catch(ex2 : Throwable) { - log.e(ex2, "openChromeTab: missing chrome. retry to other application.") - } + .setShowTitle(true) + .build() + startActivityExcludeMyApp( + activity, + customTabsIntent.intent.also { + it.component = ComponentName( + "com.android.chrome", + "com.google.android.apps.chrome.Main" + ) + it.data = url.toUri() + }, + customTabsIntent.startAnimationBundle + ) + return + } catch(ex2 : Throwable) { + log.e(ex2, "openChromeTab: missing chrome. retry to other application.") } - // Chromeがないようなのでcomponent指定なしでリトライ - CustomTabsIntent.Builder() - .setToolbarColor(getAttributeColor(activity, R.attr.colorPrimary)) - .setShowTitle(true) - .build() - .launchUrl(activity, url.toUri()) - } + + // Chromeがないようなのでcomponent指定なしでリトライ + val customTabsIntent = CustomTabsIntent.Builder() + .setToolbarColor(getAttributeColor(activity, R.attr.colorPrimary)) + .setShowTitle(true) + .build() + + startActivityExcludeMyApp( + activity, + customTabsIntent.intent.also { + it.data = url.toUri() + }, + customTabsIntent.startAnimationBundle + ) + } catch(ex : Throwable) { log.trace(ex) val scheme = url.mayUri()?.scheme ?: url @@ -653,7 +712,7 @@ class App1 : Application() { } - fun openCustomTab(activity : Activity, ta : TootAttachment) { + fun openCustomTab(activity : AppCompatActivity, ta : TootAttachment) { val url = ta.getLargeUrl(pref) ?: return openCustomTab(activity, url) } @@ -706,14 +765,14 @@ class App1 : Application() { ) } - fun setStatusBarColor(activity : Activity,forceDark:Boolean=false ) { + fun setStatusBarColor(activity : Activity, forceDark : Boolean = false) { activity.window?.apply { // 古い端末ではナビゲーションバーのアイコン色を設定できないため // メディアビューア画面ではステータスバーやナビゲーションバーの色を設定しない… - if( forceDark && Build.VERSION.SDK_INT < 26 ) return - + if(forceDark && Build.VERSION.SDK_INT < 26) return + clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) @@ -721,13 +780,13 @@ class App1 : Application() { var c = when { forceDark -> Color.BLACK else -> Pref.ipStatusBarColor(pref).notZero() - ?: getAttributeColor(activity,R.attr.colorPrimaryDark) + ?: getAttributeColor(activity, R.attr.colorPrimaryDark) } statusBarColor = c or Color.BLACK if(Build.VERSION.SDK_INT >= 23) { decorView.systemUiVisibility = - if( rgbToLab(c).first >= 50f) { + if(rgbToLab(c).first >= 50f) { //Dark Text to show up on your light status bar decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR } else { @@ -758,7 +817,6 @@ class App1 : Application() { } } - fun setSwitchColor1( activity : AppCompatActivity, pref : SharedPreferences, diff --git a/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.kt b/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.kt index 7be8af1f..ec1fdf5c 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.kt @@ -736,7 +736,7 @@ internal class DlgContextMenu( Action_User.mention(activity, access_info, who) R.id.btnAccountWebPage -> who.url?.let { url -> - App1.openCustomTab(activity, url) + App1.openCustomTabOrBrowser(activity, url) } R.id.btnFollowRequestOK -> @@ -973,7 +973,7 @@ internal class DlgContextMenu( when(v.id) { R.id.btnStatusWebPage -> status?.url?.let { url -> - App1.openCustomTab(activity, url) + App1.openCustomTabOrBrowser(activity, url) } R.id.btnText -> if(status != null) { diff --git a/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgAppPicker.kt b/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgAppPicker.kt index e6ca6af9..72592e71 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgAppPicker.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgAppPicker.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.app.AlertDialog import android.content.Intent import android.content.pm.PackageManager +import android.content.pm.ResolveInfo import android.graphics.drawable.Drawable import android.os.Build import android.view.View @@ -18,12 +19,16 @@ import jp.juggler.util.* class DlgAppPicker( val activity : AppCompatActivity, + val intent : Intent, + val autoSelect : Boolean = false, + val filter : (ResolveInfo) -> Boolean = { true }, val callback : (String) -> Unit ) { - companion object{ - fun Char.isAlpha() = ('A' <= this && this <= 'Z')||('a' <= this && this <= 'z') + + companion object { + fun Char.isAlpha() = ('A' <= this && this <= 'Z') || ('a' <= this && this <= 'z') } - + class ListItem( val icon : Drawable?, val text : String, @@ -34,11 +39,7 @@ class DlgAppPicker( val pm = activity.packageManager val listResolveInfo = pm.queryIntentActivities( - Intent().apply { - action = Intent.ACTION_SEND - type = "text/plain" - putExtra(Intent.EXTRA_TEXT, activity.getString(R.string.content_sample)) - }, + intent, if(Build.VERSION.SDK_INT >= 23) { PackageManager.MATCH_ALL } else { @@ -47,6 +48,7 @@ class DlgAppPicker( ) for(it in listResolveInfo) { + if(! filter(it)) continue val cn = "${it.activityInfo.packageName}/${it.activityInfo.name}" add( ListItem( @@ -57,43 +59,52 @@ class DlgAppPicker( ) } - val (label, icon) = CustomShare.getInfo(activity, CustomShare.CN_CLIPBOARD.cn()) - add(ListItem(icon, label.toString(), CustomShare.CN_CLIPBOARD)) - sortWith(Comparator { a, b-> + if(! autoSelect) { + val (label, icon) = CustomShare.getInfo(activity, CustomShare.CN_CLIPBOARD.cn()) + add(ListItem(icon, label.toString(), CustomShare.CN_CLIPBOARD)) + } + sortWith(Comparator { a, b -> val a1 = a.text.firstOrNull() ?: '\u0000' val b1 = b.text.firstOrNull() ?: '\u0000' when { - !a1.isAlpha() && b1.isAlpha() -> -1 - a1.isAlpha() && !b1.isAlpha() -> 1 - else -> a.text.compareTo(b.text,ignoreCase = true) + ! a1.isAlpha() && b1.isAlpha() -> - 1 + a1.isAlpha() && ! b1.isAlpha() -> 1 + else -> a.text.compareTo(b.text, ignoreCase = true) } }) } - val dialog : AlertDialog + val dialog : AlertDialog? init { - @SuppressLint("InflateParams") - val listView : ListView = - activity.layoutInflater.inflate(R.layout.dlg_app_picker, null, false).cast() !! - val adapter = MyAdapter() - listView.adapter = adapter - listView.onItemClickListener = adapter - - - dialog = AlertDialog.Builder(activity) - .setView(listView) - .setNegativeButton(R.string.cancel, null) - .create() + if(autoSelect && list.size == 1) { + callback(list.first().componentName) + dialog = null + } else { + @SuppressLint("InflateParams") + val listView : ListView = + activity.layoutInflater.inflate(R.layout.dlg_app_picker, null, false).cast() !! + val adapter = MyAdapter() + listView.adapter = adapter + listView.onItemClickListener = adapter + + + dialog = AlertDialog.Builder(activity) + .setView(listView) + .setNegativeButton(R.string.cancel, null) + .create() + } } @SuppressLint("InflateParams") fun show() { - dialog.window?.setLayout( - WindowManager.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.WRAP_CONTENT - ) - dialog.show() + dialog?.run { + window?.setLayout( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.WRAP_CONTENT + ) + this.show() + } } private inner class MyAdapter : BaseAdapter(), AdapterView.OnItemClickListener { @@ -118,7 +129,7 @@ class DlgAppPicker( } override fun onItemClick(parent : AdapterView<*>?, view : View?, idx : Int, id : Long) { - dialog.dismissSafe() + dialog?.dismissSafe() callback(list[idx].componentName) } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgCreateAccount.kt b/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgCreateAccount.kt index c87951eb..cf10e4c2 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgCreateAccount.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgCreateAccount.kt @@ -9,6 +9,7 @@ import android.widget.Button import android.widget.CheckBox import android.widget.EditText import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity import jp.juggler.subwaytooter.App1 import jp.juggler.subwaytooter.R import jp.juggler.subwaytooter.api.entity.Host @@ -18,7 +19,7 @@ import jp.juggler.subwaytooter.util.LinkHelper import jp.juggler.util.* class DlgCreateAccount( - val activity : Activity, + val activity : AppCompatActivity, val instance : Host, val onClickOk : ( dialog : Dialog,