「アプリ設定/挙動/Webブラウザ」を追加

This commit is contained in:
tateisu 2020-12-19 15:46:42 +09:00
parent 70dd21bf41
commit 35deaf985f
9 changed files with 1043 additions and 947 deletions

View File

@ -20,6 +20,18 @@
<category android:name="android.intent.category.BROWSABLE" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
</intent>
<!-- Chrome Custom Tabs の存在確認 -->
<intent>
<action android:name="android.support.customtabs.action.CustomTabsService" />

View File

@ -2,6 +2,7 @@ package jp.juggler.subwaytooter
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.ResolveInfo
import android.graphics.Color
import android.graphics.Typeface
import android.net.Uri
@ -21,13 +22,14 @@ import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import com.jrummyapps.android.colorpicker.ColorPickerDialog
import com.jrummyapps.android.colorpicker.ColorPickerDialogListener
import jp.juggler.subwaytooter.util.CustomShare
import jp.juggler.subwaytooter.util.CustomShareTarget
import jp.juggler.subwaytooter.dialog.DlgAppPicker
import jp.juggler.subwaytooter.notification.PollingWorker
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.AsyncActivity
import jp.juggler.subwaytooter.util.CustomShare
import jp.juggler.subwaytooter.util.CustomShareTarget
import jp.juggler.subwaytooter.util.cn
import jp.juggler.util.*
import org.apache.commons.io.IOUtils
import java.io.File
@ -1127,7 +1129,7 @@ class ActAppSetting : AsyncActivity(), ColorPickerDialogListener, View.OnClickLi
return list.size
}
override fun getItem(position : Int) : Any? {
override fun getItem(position : Int) : Any {
return list[position]
}
@ -1164,16 +1166,17 @@ class ActAppSetting : AsyncActivity(), ColorPickerDialogListener, View.OnClickLi
}
}
fun openCustomShareChooser(target : CustomShareTarget) {
fun openCustomShareChooser(appSettingItem: AppSettingItem,target : CustomShareTarget) {
try {
val rv = DlgAppPicker(
this,
Intent().apply {
intent = Intent().apply {
action = Intent.ACTION_SEND
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, getString(R.string.content_sample))
}
) { setCustomShare(target, it) }
},
addCopyAction = true
) { setCustomShare(appSettingItem,target, it) }
.show()
if(! rv) showToast(true, "share target app is not installed.")
} catch(ex : Throwable) {
@ -1182,22 +1185,11 @@ class ActAppSetting : AsyncActivity(), ColorPickerDialogListener, View.OnClickLi
}
}
fun setCustomShare(target : CustomShareTarget?, value : String) {
target ?: return
val item = when(target) {
CustomShareTarget.Translate -> AppSettingItem.CUSTOM_TRANSLATE
CustomShareTarget.CustomShare1 -> AppSettingItem.CUSTOM_SHARE_1
CustomShareTarget.CustomShare2 -> AppSettingItem.CUSTOM_SHARE_2
CustomShareTarget.CustomShare3 -> AppSettingItem.CUSTOM_SHARE_3
}
?: error("setCustomShare $target has no setting item.")
val sp : StringPref = item.pref.cast() ?: error("$target: not StringPref")
fun setCustomShare(appSettingItem: AppSettingItem,target : CustomShareTarget, value : String) {
val sp : StringPref = appSettingItem.pref.cast() ?: error("$target: not StringPref")
pref.edit().put(sp, value).apply()
showCustomShareIcon(findItemViewHolder(item)?.textView1, target)
showCustomShareIcon(findItemViewHolder(appSettingItem)?.textView1, target)
}
fun showCustomShareIcon(tv : TextView?, target : CustomShareTarget) {
@ -1209,4 +1201,35 @@ class ActAppSetting : AsyncActivity(), ColorPickerDialogListener, View.OnClickLi
tv.compoundDrawablePadding = (resources.displayMetrics.density * 4f + 0.5f).toInt()
}
fun openWebBrowserChooser(appSettingItem: AppSettingItem, intent:Intent, filter: (ResolveInfo) -> Boolean ) {
try {
val rv = DlgAppPicker(
this,
intent=intent,
filter = filter,
addCopyAction = false
) { setWebBrowser( appSettingItem,it) }
.show()
if(! rv) showToast(true, "share target app is not installed.")
} catch(ex : Throwable) {
log.trace(ex)
showToast(ex, "openCustomShareChooser failed.")
}
}
fun setWebBrowser(appSettingItem: AppSettingItem, value : String) {
val sp : StringPref = appSettingItem.pref.cast() ?: error("${getString(appSettingItem.caption)}: not StringPref")
pref.edit().put(sp, value).apply()
showWebBrowser(findItemViewHolder(appSettingItem)?.textView1, value )
}
fun showWebBrowser(tv : TextView?,prefValue:String) {
tv ?: return
val cn =prefValue.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()
}
}

View File

@ -14,7 +14,7 @@ import jp.juggler.subwaytooter.util.openBrowser
import jp.juggler.util.*
import org.jetbrains.anko.backgroundDrawable
enum class SettingType(val id : Int) {
enum class SettingType(val id: Int) {
Path(0),
Divider(1),
Switch(2),
@ -31,177 +31,172 @@ enum class SettingType(val id : Int) {
}
class AppSettingItem(
val parent : AppSettingItem?,
val type : SettingType,
@StringRes val caption : Int,
val pref : BasePref<*>? = null
val parent: AppSettingItem?,
val type: SettingType,
@StringRes val caption: Int,
val pref: BasePref<*>? = null
) {
@StringRes
var desc : Int = 0
var desc: Int = 0
var descClickSet = false
var descClick : ActAppSetting.() -> Unit = {}
var descClick: ActAppSetting.() -> Unit = {}
set(value) {
field = value
descClickSet = true
}
var getError : ActAppSetting.(String) -> String? = { null }
var getError: ActAppSetting.(String) -> String? = { null }
// may be open exportAppData() or importAppData()
var action : ActAppSetting.() -> Unit = {}
var action: ActAppSetting.() -> Unit = {}
var changed : ActAppSetting.() -> Unit = {}
var changed: ActAppSetting.() -> Unit = {}
// used for EditText
var inputType = InputTypeEx.text
var sampleLayoutId : Int = 0
var sampleUpdate : (ActAppSetting, View) -> Unit = { _, _ -> }
var sampleLayoutId: Int = 0
var sampleUpdate: (ActAppSetting, View) -> Unit = { _, _ -> }
var spinnerArgs : IntArray? = null
var spinnerArgsProc : (ActAppSetting) -> List<String> = { _ -> emptyList() }
var spinnerInitializer : ActAppSetting.(Spinner) -> Unit = {}
var spinnerOnSelected : ActAppSetting.(Spinner, Int) -> Unit = { _, _ -> }
var spinnerArgs: IntArray? = null
var spinnerArgsProc: (ActAppSetting) -> List<String> = { _ -> emptyList() }
var spinnerInitializer: ActAppSetting.(Spinner) -> Unit = {}
var spinnerOnSelected: ActAppSetting.(Spinner, Int) -> Unit = { _, _ -> }
var enabled : Boolean = true
var enabled: Boolean = true
var onClickEdit : ActAppSetting.() -> Unit = {}
var onClickReset : ActAppSetting.() -> Unit = {}
var showTextView : ActAppSetting.(TextView) -> Unit = {}
var onClickEdit: ActAppSetting.() -> Unit = {}
var onClickReset: ActAppSetting.() -> Unit = {}
var showTextView: ActAppSetting.(TextView) -> Unit = {}
// for EditText
var hint : String? = null
var filter : (String) -> String = { it.trim() }
var captionFontSize : ActAppSetting.() -> Float? = { null }
var captionSpacing : ActAppSetting.() -> Float? = { null }
var hint: String? = null
var filter: (String) -> String = { it.trim() }
var captionFontSize: ActAppSetting.() -> Float? = { null }
var captionSpacing: ActAppSetting.() -> Float? = { null }
// cast before save
var toFloat : ActAppSetting.(String) -> Float = { 0f }
var fromFloat : ActAppSetting.(Float) -> String = { it.toString() }
var toFloat: ActAppSetting.(String) -> Float = { 0f }
var fromFloat: ActAppSetting.(Float) -> String = { it.toString() }
val items = ArrayList<AppSettingItem>()
fun section(
@StringRes caption : Int,
initializer : AppSettingItem.() -> Unit = {}
@StringRes caption: Int,
initializer: AppSettingItem.() -> Unit = {}
) {
items.add(AppSettingItem(this, SettingType.Section, caption).apply { initializer() })
}
fun group(
@StringRes caption : Int,
initializer : AppSettingItem.() -> Unit = {}
@StringRes caption: Int,
initializer: AppSettingItem.() -> Unit = {}
) {
items.add(AppSettingItem(this, SettingType.Group, caption).apply { initializer() })
}
fun item(
type : SettingType,
pref : BasePref<*>?,
@StringRes caption : Int,
initializer : AppSettingItem.() -> Unit = {}
) : AppSettingItem {
type: SettingType,
pref: BasePref<*>?,
@StringRes caption: Int,
initializer: AppSettingItem.() -> Unit = {}
): AppSettingItem {
val item = AppSettingItem(this, type, caption, pref).apply { initializer() }
items.add(item)
return item
}
fun spinner(
pref : IntPref,
@StringRes caption : Int,
vararg args : Int
pref: IntPref,
@StringRes caption: Int,
vararg args: Int
) = item(SettingType.Spinner, pref, caption) {
spinnerArgs = args
}
fun spinner(
pref : IntPref,
@StringRes caption : Int,
argsProc : (ActAppSetting) -> List<String>
pref: IntPref,
@StringRes caption: Int,
argsProc: (ActAppSetting) -> List<String>
) = item(SettingType.Spinner, pref, caption) {
spinnerArgsProc = argsProc
}
fun sw(
pref : BooleanPref,
@StringRes caption : Int,
initializer : AppSettingItem.() -> Unit = {}
pref: BooleanPref,
@StringRes caption: Int,
initializer: AppSettingItem.() -> Unit = {}
) = item(SettingType.Switch, pref, caption, initializer)
fun checkbox(
pref : BooleanPref,
@StringRes caption : Int,
initializer : AppSettingItem.() -> Unit = {}
pref: BooleanPref,
@StringRes caption: Int,
initializer: AppSettingItem.() -> Unit = {}
) = item(SettingType.CheckBox, pref, caption, initializer)
fun action(
@StringRes caption : Int,
initializer : AppSettingItem.() -> Unit = {}
@StringRes caption: Int,
initializer: AppSettingItem.() -> Unit = {}
) = item(SettingType.Action, null, caption, initializer)
fun colorOpaque(
pref : IntPref,
@StringRes caption : Int,
initializer : AppSettingItem.() -> Unit = {}
pref: IntPref,
@StringRes caption: Int,
initializer: AppSettingItem.() -> Unit = {}
) = item(SettingType.ColorOpaque, pref, caption, initializer)
fun colorAlpha(
pref : IntPref,
@StringRes caption : Int,
initializer : AppSettingItem.() -> Unit = {}
pref: IntPref,
@StringRes caption: Int,
initializer: AppSettingItem.() -> Unit = {}
) = item(SettingType.ColorAlpha, pref, caption, initializer)
fun text(
pref : StringPref,
@StringRes caption : Int,
inputType : Int,
initializer : AppSettingItem.() -> Unit = {}
pref: StringPref,
@StringRes caption: Int,
inputType: Int,
initializer: AppSettingItem.() -> Unit = {}
) = item(SettingType.EditText, pref, caption) {
this.inputType = inputType
this.initializer()
}
fun textX(
pref : BasePref<*>,
@StringRes caption : Int,
inputType : Int,
initializer : AppSettingItem.() -> Unit = {}
pref: BasePref<*>,
@StringRes caption: Int,
inputType: Int,
initializer: AppSettingItem.() -> Unit = {}
) = item(SettingType.EditText, pref, caption) {
this.inputType = inputType
this.initializer()
}
fun sample(
sampleLayoutId : Int = 0,
sampleUpdate : (ActAppSetting, View) -> Unit = { _, _ -> }
sampleLayoutId: Int = 0,
sampleUpdate: (ActAppSetting, View) -> Unit = { _, _ -> }
// ,initializer : AppSettingItem.() -> Unit = {}
) = item(SettingType.Sample, pref, caption) {
this.sampleLayoutId = sampleLayoutId
this.sampleUpdate = sampleUpdate
}
fun scan(block : (AppSettingItem) -> Unit) {
fun scan(block: (AppSettingItem) -> Unit) {
block(this)
for(item in items) item.scan(block)
for (item in items) item.scan(block)
}
companion object {
var SAMPLE_CCD_HEADER : AppSettingItem? = null
var SAMPLE_CCD_BODY : AppSettingItem? = null
var SAMPLE_FOOTER : AppSettingItem? = null
var SAMPLE_CCD_HEADER: AppSettingItem? = null
var SAMPLE_CCD_BODY: AppSettingItem? = null
var SAMPLE_FOOTER: AppSettingItem? = null
var CUSTOM_TRANSLATE : AppSettingItem? = null
var CUSTOM_SHARE_1 : AppSettingItem? = null
var CUSTOM_SHARE_2 : AppSettingItem? = null
var CUSTOM_SHARE_3 : AppSettingItem? = null
var TIMELINE_FONT: AppSettingItem? = null
var TIMELINE_FONT_BOLD: AppSettingItem? = null
var TIMELINE_FONT : AppSettingItem? = null
var TIMELINE_FONT_BOLD : AppSettingItem? = null
var FONT_SIZE_TIMELINE : AppSettingItem? = null
var FONT_SIZE_NOTIFICATION_TL : AppSettingItem? = null
var FONT_SIZE_TIMELINE: AppSettingItem? = null
var FONT_SIZE_NOTIFICATION_TL: AppSettingItem? = null
}
}
@ -259,6 +254,44 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
sw(Pref.bpDontScreenOff, R.string.dont_screen_off)
sw(Pref.bpDontUseCustomTabs, R.string.dont_use_custom_tabs)
sw(Pref.bpPriorChrome, R.string.prior_chrome_custom_tabs)
item(
SettingType.TextWithSelector,
Pref.spWebBrowser,
R.string.web_browser
) {
onClickEdit = {
openWebBrowserChooser(
this@item,
intent = Intent(Intent.ACTION_VIEW, "https://joinmastodon.org/".toUri()).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
addCategory(Intent.CATEGORY_BROWSABLE)
},
filter = {
when {
it.activityInfo.packageName == packageName -> false
!it.activityInfo.exported -> false
// Huaweiの謎Activityのせいでうまく働かないことがある
-1 != it.activityInfo.packageName.indexOf("com.huawei.android.internal") -> false
// 標準アプリが設定されていない場合、アプリを選択するためのActivityが出てくる場合がある
it.activityInfo.packageName == "android" -> false
it.activityInfo.javaClass.name.startsWith("com.android.internal") -> false
it.activityInfo.javaClass.name.startsWith("com.android.systemui") -> false
// たぶんChromeとかfirefoxとか
else -> true
}
}
)
}
onClickReset = { setWebBrowser(this@item, "") }
showTextView = {
showWebBrowser(it, this@item.pref.cast<StringPref>()!!.invoke(pref))
}
}
sw(Pref.bpAllowColumnDuplication, R.string.allow_column_duplication)
sw(Pref.bpForceGap, R.string.force_gap_when_refresh)
spinner(
@ -280,7 +313,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
filter = { it.replace(ActAppSetting.reLinefeed, " ").trim() }
getError = {
val m = App1.reNotAllowedInUserAgent.matcher(it)
when(m.find()) {
when (m.find()) {
true -> getString(R.string.user_agent_error, m.group())
else -> null
}
@ -291,45 +324,46 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
sw(Pref.bpCustomEmojiSeparatorZwsp, R.string.custom_emoji_separator_zwsp)
sw(Pref.bpShowTranslateButton, R.string.show_translate_button)
AppSettingItem.CUSTOM_TRANSLATE = item(
item(
SettingType.TextWithSelector,
Pref.spTranslateAppComponent,
R.string.translation_app
) {
val target = CustomShareTarget.Translate
onClickEdit = { openCustomShareChooser(target) }
onClickReset = { setCustomShare(target, "") }
onClickEdit = { openCustomShareChooser(this@item, target) }
onClickReset = { setCustomShare(this@item, target, "") }
showTextView = { showCustomShareIcon(it, target) }
}
AppSettingItem.CUSTOM_SHARE_1 = item(
item(
SettingType.TextWithSelector,
Pref.spCustomShare1,
R.string.custom_share_button_1
) {
val target = CustomShareTarget.CustomShare1
onClickEdit = { openCustomShareChooser(target) }
onClickReset = { setCustomShare(target, "") }
onClickEdit = { openCustomShareChooser(this@item, target) }
onClickReset = { setCustomShare(this@item, target, "") }
showTextView = { showCustomShareIcon(it, target) }
}
AppSettingItem.CUSTOM_SHARE_2 = item(
item(
SettingType.TextWithSelector,
Pref.spCustomShare2,
R.string.custom_share_button_2
) {
val target = CustomShareTarget.CustomShare2
onClickEdit = { openCustomShareChooser(target) }
onClickReset = { setCustomShare(target, "") }
onClickEdit = { openCustomShareChooser(this@item, target) }
onClickReset = { setCustomShare(this@item, target, "") }
showTextView = { showCustomShareIcon(it, target) }
}
AppSettingItem.CUSTOM_SHARE_3 = item(
item(
SettingType.TextWithSelector,
Pref.spCustomShare3,
R.string.custom_share_button_3
) {
val target = CustomShareTarget.CustomShare3
onClickEdit = { openCustomShareChooser(target) }
onClickReset = { setCustomShare(target, "") }
onClickEdit = { openCustomShareChooser(this@item, target) }
onClickReset = { setCustomShare(this@item, target, "") }
showTextView = { showCustomShareIcon(it, target) }
}
@ -351,7 +385,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
spinner(Pref.ipResizeImage, R.string.resize_image) { activity ->
ActPost.resizeConfigList.map {
when(it.type) {
when (it.type) {
ResizeType.None -> activity.getString(R.string.dont_resize)
ResizeType.LongSide -> activity.getString(R.string.long_side_pixel, it.size)
ResizeType.SquarePixel -> activity.getString(
@ -426,7 +460,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
Pref.lpTabletTootDefaultAccount,
R.string.toot_button_default_account
) {
val lp = pref.cast<LongPref>() !!
val lp = pref.cast<LongPref>()!!
spinnerInitializer = { spinner ->
val adapter = AccountAdapter()
spinner.adapter = adapter
@ -479,7 +513,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
Pref.spTimeZone,
R.string.timezone
) {
val sp : StringPref = pref.cast() !!
val sp: StringPref = pref.cast()!!
spinnerInitializer = { spinner ->
val adapter = TimeZoneAdapter()
spinner.adapter = adapter
@ -537,7 +571,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
try {
val intent = intentOpenDocument("*/*")
startActivityForResult(intent, ActAppSetting.REQUEST_CODE_TIMELINE_FONT)
} catch(ex : Throwable) {
} catch (ex: Throwable) {
showToast(ex, "could not open picker for font")
}
}
@ -559,7 +593,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
try {
val intent = intentOpenDocument("*/*")
startActivityForResult(intent, ActAppSetting.REQUEST_CODE_TIMELINE_FONT_BOLD)
} catch(ex : Throwable) {
} catch (ex: Throwable) {
showToast(ex, "could not open picker for font")
}
}
@ -577,7 +611,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
) {
val item = this
val fp : FloatPref = item.pref.cast() !!
val fp: FloatPref = item.pref.cast()!!
toFloat = { parseFontSize(it) }
fromFloat = { formatFontSize(it) }
@ -585,7 +619,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
captionFontSize = {
val fv = fp(pref)
when {
! fv.isFinite() -> Pref.default_timeline_font_size
!fv.isFinite() -> Pref.default_timeline_font_size
fv < 1f -> 1f
else -> fv
}
@ -600,7 +634,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
textX(Pref.fpAcctFontSize, R.string.acct_font_size, InputTypeEx.numberDecimal) {
val item = this
val fp : FloatPref = item.pref.cast() !!
val fp: FloatPref = item.pref.cast()!!
toFloat = { parseFontSize(it) }
fromFloat = { formatFontSize(it) }
@ -608,7 +642,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
captionFontSize = {
val fv = fp(pref)
when {
! fv.isFinite() -> Pref.default_acct_font_size
!fv.isFinite() -> Pref.default_acct_font_size
fv < 1f -> 1f
else -> fv
}
@ -623,7 +657,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
InputTypeEx.numberDecimal
) {
val item = this
val fp : FloatPref = item.pref.cast() !!
val fp: FloatPref = item.pref.cast()!!
toFloat = { parseFontSize(it) }
fromFloat = { formatFontSize(it) }
@ -631,7 +665,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
captionFontSize = {
val fv = fp(pref)
when {
! fv.isFinite() -> Pref.default_notification_tl_font_size
!fv.isFinite() -> Pref.default_notification_tl_font_size
fv < 1f -> 1f
else -> fv
}
@ -674,7 +708,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
text(Pref.spHeaderIconSize, R.string.header_icon_size, InputTypeEx.numberDecimal)
textX(Pref.fpHeaderTextSize, R.string.header_text_size, InputTypeEx.numberDecimal) {
val item = this
val fp : FloatPref = item.pref.cast() !!
val fp: FloatPref = item.pref.cast()!!
toFloat = { parseFontSize(it) }
fromFloat = { formatFontSize(it) }
@ -682,7 +716,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
captionFontSize = {
val fv = fp(pref)
when {
! fv.isFinite() -> Pref.default_header_font_size
!fv.isFinite() -> Pref.default_header_font_size
fv < 1f -> 1f
else -> fv
}
@ -701,7 +735,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
sw(Pref.bpOpenSticker, R.string.show_open_sticker) {
desc = R.string.powered_by_open_sticker
descClick = {openBrowser("https://github.com/cutls/OpenSticker") }
descClick = { openBrowser("https://github.com/cutls/OpenSticker") }
}
sw(Pref.bpLinksInContextMenu, R.string.show_links_in_context_menu)
@ -787,9 +821,9 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
AppSettingItem.SAMPLE_CCD_HEADER =
sample(R.layout.setting_sample_column_header) { activity, viewRoot ->
val llColumnHeader : View = viewRoot.findViewById(R.id.llColumnHeader)
val ivColumnHeader : ImageView = viewRoot.findViewById(R.id.ivColumnHeader)
val tvColumnName : TextView = viewRoot.findViewById(R.id.tvColumnName)
val llColumnHeader: View = viewRoot.findViewById(R.id.llColumnHeader)
val ivColumnHeader: ImageView = viewRoot.findViewById(R.id.ivColumnHeader)
val tvColumnName: TextView = viewRoot.findViewById(R.id.tvColumnName)
val color_column_header_bg = Pref.ipCcdHeaderBg(activity.pref)
val color_column_header_fg = Pref.ipCcdHeaderFg(activity.pref)
@ -820,9 +854,9 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
AppSettingItem.SAMPLE_CCD_BODY =
sample(R.layout.setting_sample_column_body) { activity, viewRoot ->
val flColumnBackground : View = viewRoot.findViewById(R.id.flColumnBackground)
val tvSampleAcct : TextView = viewRoot.findViewById(R.id.tvSampleAcct)
val tvSampleContent : TextView = viewRoot.findViewById(R.id.tvSampleContent)
val flColumnBackground: View = viewRoot.findViewById(R.id.flColumnBackground)
val tvSampleAcct: TextView = viewRoot.findViewById(R.id.tvSampleAcct)
val tvSampleContent: TextView = viewRoot.findViewById(R.id.tvSampleContent)
val color_column_bg = Pref.ipCcdContentBg(activity.pref)
val color_column_acct = Pref.ipCcdContentAcct(activity.pref)
@ -858,12 +892,12 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
AppSettingItem.SAMPLE_FOOTER =
sample(R.layout.setting_sample_footer) { activity, viewRoot ->
val pref = activity.pref
val ivFooterToot : AppCompatImageView = viewRoot.findViewById(R.id.ivFooterToot)
val ivFooterMenu : AppCompatImageView = viewRoot.findViewById(R.id.ivFooterMenu)
val llFooterBG : View = viewRoot.findViewById(R.id.llFooterBG)
val vFooterDivider1 : View = viewRoot.findViewById(R.id.vFooterDivider1)
val vFooterDivider2 : View = viewRoot.findViewById(R.id.vFooterDivider2)
val vIndicator : View = viewRoot.findViewById(R.id.vIndicator)
val ivFooterToot: AppCompatImageView = viewRoot.findViewById(R.id.ivFooterToot)
val ivFooterMenu: AppCompatImageView = viewRoot.findViewById(R.id.ivFooterMenu)
val llFooterBG: View = viewRoot.findViewById(R.id.llFooterBG)
val vFooterDivider1: View = viewRoot.findViewById(R.id.vFooterDivider1)
val vFooterDivider2: View = viewRoot.findViewById(R.id.vFooterDivider2)
val vIndicator: View = viewRoot.findViewById(R.id.vIndicator)
val footer_button_bg_color = Pref.ipFooterButtonBgColor(pref)
val footer_button_fg_color = Pref.ipFooterButtonFgColor(pref)
@ -960,7 +994,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
}
action(R.string.exit_reasons) {
action = {
if(Build.VERSION.SDK_INT >= 30) {
if (Build.VERSION.SDK_INT >= 30) {
startActivity(Intent(this, ActExitReasons::class.java))
} else {
showToast(false, "this feature requires Android 11")

View File

@ -591,6 +591,7 @@ object Pref {
val spCustomShare1 = StringPref("CustomShare1", "")
val spCustomShare2 = StringPref("CustomShare2", "")
val spCustomShare3 = StringPref("CustomShare3", "")
val spWebBrowser = StringPref("WebBrowser", "")
val spTimelineSpacing = StringPref("TimelineSpacing", "")

View File

@ -21,8 +21,9 @@ class DlgAppPicker(
val activity: Activity,
val intent: Intent,
val autoSelect: Boolean = false,
val addCopyAction:Boolean= false,
val filter: (ResolveInfo) -> Boolean = { true },
val callback: (String) -> Unit
val callback: (String) -> Unit,
) {
companion object {
@ -55,7 +56,7 @@ class DlgAppPicker(
}
// 自動選択オフの場合、末尾にクリップボード項目を追加する
if (!autoSelect) {
if (addCopyAction && !autoSelect) {
val (label, icon) = CustomShare.getInfo(activity, CustomShare.CN_CLIPBOARD.cn())
add(ListItem(icon, label.toString(), CustomShare.CN_CLIPBOARD))
}

View File

@ -8,6 +8,7 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.app.Activity
import android.content.SharedPreferences
import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.browser.customtabs.CustomTabsIntent
import jp.juggler.subwaytooter.ActMain
@ -40,15 +41,23 @@ private val log = LogCategory("AppOpener")
// returns true if activity is opened.
// returns false if fallback required
private fun Activity.startActivityExcludeMyApp(
pref:SharedPreferences,
intent: Intent,
startAnimationBundle: Bundle? = null
): Boolean {
try {
if( intent.component == null){
val cn = Pref.spWebBrowser(pref).cn()
if( cn?.exists(this) == true){
intent.component = cn
}
}
// このアプリのパッケージ名
val myName = packageName
val filter: (ResolveInfo) -> Boolean = {
when{
when {
it.activityInfo.packageName == myName -> false
!it.activityInfo.exported -> false
@ -57,7 +66,7 @@ private fun Activity.startActivityExcludeMyApp(
// 標準アプリが設定されていない場合、アプリを選択するためのActivityが出てくる場合がある
it.activityInfo.packageName == "android" -> false
it.activityInfo.javaClass.name.startsWith( "com.android.internal") -> false
it.activityInfo.javaClass.name.startsWith("com.android.internal") -> false
it.activityInfo.javaClass.name.startsWith("com.android.systemui") -> false
// たぶんChromeとかfirefoxとか
@ -89,7 +98,8 @@ private fun Activity.startActivityExcludeMyApp(
this,
intent,
autoSelect = true,
filter = filter
filter = filter,
addCopyAction = false
) {
try {
intent.component = it.cn()
@ -108,16 +118,20 @@ private fun Activity.startActivityExcludeMyApp(
}
}
fun Activity.openBrowser(uri: Uri?) {
fun Activity.openBrowser(uri: Uri? , pref:SharedPreferences = pref()) {
uri ?: return
val rv = startActivityExcludeMyApp(Intent(Intent.ACTION_VIEW, uri))
val rv = startActivityExcludeMyApp(
pref,
Intent(Intent.ACTION_VIEW, uri)
.apply { addCategory(Intent.CATEGORY_BROWSABLE) }
)
if (!rv) showToast(true, "there is no app that can open $uri")
}
fun Activity.openBrowser(url: String?) = openBrowser(url.mayUri())
fun Activity.openBrowser(url: String?, pref:SharedPreferences = pref()) = openBrowser(url.mayUri(),pref)
// Chrome Custom Tab を開く
fun Activity.openCustomTab(url: String?) {
fun Activity.openCustomTab(url: String?, pref:SharedPreferences = pref()) {
url ?: return
if (url.isEmpty()) {
@ -125,9 +139,8 @@ fun Activity.openCustomTab(url: String?) {
return
}
val pref = pref()
if (Pref.bpDontUseCustomTabs(pref)) {
openBrowser(url)
openBrowser(url,pref)
return
}
@ -144,6 +157,7 @@ fun Activity.openCustomTab(url: String?) {
.let {
log.w("startCustomTabIntent ComponentName=$cn")
startActivityExcludeMyApp(
pref,
it.intent.also { intent ->
if (cn != null) intent.component = cn
intent.data = url.toUri()

View File

@ -31,12 +31,12 @@ object CustomShare {
"com.google.android.apps.translate/com.google.android.apps.translate.TranslateActivity"
fun getCustomShareComponentName(
pref : SharedPreferences,
target : CustomShareTarget
) : ComponentName? {
val src : String
val defaultComponentName : String?
when(target) {
pref: SharedPreferences,
target: CustomShareTarget
): ComponentName? {
val src: String
val defaultComponentName: String?
when (target) {
CustomShareTarget.Translate -> {
src = Pref.spTranslateAppComponent(pref)
defaultComponentName = translate_app_component_default
@ -60,14 +60,14 @@ object CustomShare {
return src.cn() ?: defaultComponentName?.cn()
}
fun getInfo(context : Context, cn : ComponentName?) : Pair<CharSequence?, Drawable?> {
var label : CharSequence? = null
var icon : Drawable? = null
fun getInfo(context: Context, cn: ComponentName?): Pair<CharSequence?, Drawable?> {
var label: CharSequence? = null
var icon: Drawable? = null
try {
if(cn != null) {
if (cn != null) {
val cnStr = "${cn.packageName}/${cn.className}"
label = cnStr
if(cnStr == CN_CLIPBOARD) {
if (cnStr == CN_CLIPBOARD) {
label =
"${context.getString(R.string.copy_to_clipboard)}(${context.getString(R.string.app_name)})"
icon = ContextCompat.getDrawable(context, R.drawable.ic_copy)?.mutate()?.apply {
@ -77,44 +77,44 @@ object CustomShare {
} else {
val pm = context.packageManager
val ri = pm.resolveActivity(Intent().apply { component = cn }, 0)
if(ri != null) {
if (ri != null) {
try {
label = ri.loadLabel(pm)
} catch(ex : Throwable) {
} catch (ex: Throwable) {
log.e(ex, "loadLabel failed.")
}
try {
icon = ri.loadIcon(pm)
} catch(ex : Throwable) {
} catch (ex: Throwable) {
log.e(ex, "loadIcon failed.")
}
}
}
}
} catch(ex : Throwable) {
} catch (ex: Throwable) {
log.e(ex, "getInfo failed.")
}
return Pair(label, icon)
}
fun invoke(
context : Context,
text : String,
target : CustomShareTarget
context: Context,
text: String,
target: CustomShareTarget
) {
// convert "pkgName/className" string to ComponentName object.
val cn = getCustomShareComponentName(App1.pref, target)
if(cn == null) {
if (cn == null) {
context.showToast(true, R.string.custom_share_app_not_found)
return
}
val cnStr = "${cn.packageName}/${cn.className}"
if(cnStr == CN_CLIPBOARD) {
if (cnStr == CN_CLIPBOARD) {
try {
val cm : ClipboardManager = systemService(context) !!
val cm: ClipboardManager = systemService(context)!!
cm.setPrimaryClip(ClipData.newPlainText("", text))
context.showToast(false, R.string.copied_to_clipboard)
} catch(ex : Throwable) {
} catch (ex: Throwable) {
context.showToast(ex, "copy to clipboard failed.")
}
return
@ -126,10 +126,10 @@ object CustomShare {
intent.putExtra(Intent.EXTRA_TEXT, text)
intent.component = cn
context.startActivity(intent)
} catch(ex : ActivityNotFoundException) {
} catch (ex: ActivityNotFoundException) {
log.trace(ex)
context.showToast(true, R.string.custom_share_app_not_found)
} catch(ex : Throwable) {
} catch (ex: Throwable) {
log.trace(ex)
context.showToast(ex, "invoke() failed.")
}
@ -137,23 +137,23 @@ object CustomShare {
}
fun invoke(
context : Context,
access_info : SavedAccount,
status : TootStatus?,
target : CustomShareTarget
context: Context,
access_info: SavedAccount,
status: TootStatus?,
target: CustomShareTarget
) {
status ?: return
try {
// convert "pkgName/className" string to ComponentName object.
val cn = getCustomShareComponentName(App1.pref, target)
if(cn == null) {
if (cn == null) {
context.showToast(true, R.string.custom_share_app_not_found)
return
}
val sv = TootTextEncoder.encodeStatusForTranslate(context, access_info, status)
invoke(context, sv, target)
} catch(ex : Throwable) {
} catch (ex: Throwable) {
log.trace(ex)
context.showToast(ex, "invoke() failed.")
}
@ -161,9 +161,9 @@ object CustomShare {
private val cache = HashMap<CustomShareTarget, Pair<CharSequence?, Drawable?>>()
fun getCache(target : CustomShareTarget) = cache[target]
fun getCache(target: CustomShareTarget) = cache[target]
fun reloadCache(context : Context, pref : SharedPreferences) {
fun reloadCache(context: Context, pref: SharedPreferences) {
CustomShareTarget.values().forEach { target ->
val cn = getCustomShareComponentName(pref, target)
val pair = getInfo(context, cn)
@ -173,12 +173,21 @@ object CustomShare {
}
// convert "pkgName/className" string to ComponentName object.
fun String.cn() : ComponentName? {
fun String.cn(): ComponentName? {
try {
val idx = indexOf('/')
if(idx >= 1) return ComponentName(substring(0 until idx), substring(idx + 1))
} catch(ex : Throwable) {
if (idx >= 1) return ComponentName(substring(0 until idx), substring(idx + 1))
} catch (ex: Throwable) {
CustomShare.log.e(ex, "incorrect component name $this")
}
return null
}
fun ComponentName.exists(context: Context): Boolean {
return try {
context.packageManager.resolveActivity(Intent().apply { component = this@exists }, 0)
?.activityInfo?.exported ?: false
} catch (_: Throwable) {
false
}
}

View File

@ -1065,5 +1065,6 @@
<string name="username_not_need_atmark">ユーザ名に@や/ を含めることはできません</string>
<string name="speech">読み上げ</string>
<string name="reset_notification_tracking_status">既読状態をリセット</string>
<string name="web_browser">Webブラウザー</string>
</resources>

View File

@ -1076,4 +1076,5 @@
<string name="toot_search_notestock_of">notestock search \"%1$s\"</string>
<string name="update_push_subscription_not_force" translatable="false">Update push subscription(not force)</string>
<string name="reset_notification_tracking_status">Reset notification tracking status</string>
<string name="web_browser">Web browser</string>
</resources>