サイドメニューに「URLからカラムを開く」を追加
This commit is contained in:
parent
629cb1df9b
commit
4c43e807b1
|
@ -1,12 +1,16 @@
|
||||||
package jp.juggler.subwaytooter.action
|
package jp.juggler.subwaytooter.action
|
||||||
|
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
|
import android.net.Uri
|
||||||
import jp.juggler.subwaytooter.ActColumnList
|
import jp.juggler.subwaytooter.ActColumnList
|
||||||
import jp.juggler.subwaytooter.ActMain
|
import jp.juggler.subwaytooter.ActMain
|
||||||
import jp.juggler.subwaytooter.R
|
import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.actmain.currentColumn
|
import jp.juggler.subwaytooter.actmain.currentColumn
|
||||||
|
import jp.juggler.subwaytooter.actmain.handleOtherUri
|
||||||
import jp.juggler.subwaytooter.api.entity.TootApplication
|
import jp.juggler.subwaytooter.api.entity.TootApplication
|
||||||
|
import jp.juggler.subwaytooter.dialog.DlgOpenUrl
|
||||||
import jp.juggler.subwaytooter.table.MutedApp
|
import jp.juggler.subwaytooter.table.MutedApp
|
||||||
|
import jp.juggler.util.dismissSafe
|
||||||
import jp.juggler.util.showToast
|
import jp.juggler.util.showToast
|
||||||
|
|
||||||
// カラム一覧を開く
|
// カラム一覧を開く
|
||||||
|
@ -33,3 +37,15 @@ fun ActMain.appMute(
|
||||||
appState.onMuteUpdated()
|
appState.onMuteUpdated()
|
||||||
showToast(false, R.string.app_was_muted)
|
showToast(false, R.string.app_was_muted)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ActMain.openColumnFromUrl() {
|
||||||
|
DlgOpenUrl.show(this) { dialog, url ->
|
||||||
|
try {
|
||||||
|
if (handleOtherUri(Uri.parse(url))) {
|
||||||
|
dialog.dismissSafe()
|
||||||
|
}
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
showToast(ex, R.string.url_parse_failed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ fun ActMain.handleIntentUri(uri: Uri) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ActMain.handleOtherUri(uri: Uri) {
|
fun ActMain.handleOtherUri(uri: Uri): Boolean {
|
||||||
val url = uri.toString()
|
val url = uri.toString()
|
||||||
|
|
||||||
url.findStatusIdFromUrl()?.let { statusInfo ->
|
url.findStatusIdFromUrl()?.let { statusInfo ->
|
||||||
|
@ -55,7 +55,7 @@ private fun ActMain.handleOtherUri(uri: Uri) {
|
||||||
statusInfo.host,
|
statusInfo.host,
|
||||||
statusInfo.statusId
|
statusInfo.statusId
|
||||||
)
|
)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
TootAccount.reAccountUrl.matcher(url).takeIf { it.find() }?.let { m ->
|
TootAccount.reAccountUrl.matcher(url).takeIf { it.find() }?.let { m ->
|
||||||
|
@ -80,7 +80,7 @@ private fun ActMain.handleOtherUri(uri: Uri) {
|
||||||
userUrl = url,
|
userUrl = url,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
TootAccount.reAccountUrl2.matcher(url).takeIf { it.find() }?.let { m ->
|
TootAccount.reAccountUrl2.matcher(url).takeIf { it.find() }?.let { m ->
|
||||||
|
@ -93,7 +93,7 @@ private fun ActMain.handleOtherUri(uri: Uri) {
|
||||||
acct = Acct.parse(user, host),
|
acct = Acct.parse(user, host),
|
||||||
userUrl = url,
|
userUrl = url,
|
||||||
)
|
)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// このアプリでは処理できないURLだった
|
// このアプリでは処理できないURLだった
|
||||||
|
@ -139,7 +139,7 @@ private fun ActMain.handleOtherUri(uri: Uri) {
|
||||||
|
|
||||||
// 指定した選択肢でチューザーを作成して開く
|
// 指定した選択肢でチューザーを作成して開く
|
||||||
startActivity(chooser)
|
startActivity(chooser)
|
||||||
return
|
return true
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.trace(ex)
|
log.trace(ex)
|
||||||
}
|
}
|
||||||
|
@ -149,6 +149,7 @@ private fun ActMain.handleOtherUri(uri: Uri) {
|
||||||
.setMessage(errorMessage)
|
.setMessage(errorMessage)
|
||||||
.setPositiveButton(R.string.close, null)
|
.setPositiveButton(R.string.close, null)
|
||||||
.show()
|
.show()
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ActMain.handleCustomSchemaUri(uri: Uri) {
|
private fun ActMain.handleCustomSchemaUri(uri: Uri) {
|
||||||
|
@ -176,8 +177,8 @@ private fun ActMain.handleNotificationClick(uri: Uri, dataIdString: String) {
|
||||||
val columnList = appState.columnList
|
val columnList = appState.columnList
|
||||||
val column = columnList.firstOrNull {
|
val column = columnList.firstOrNull {
|
||||||
it.type == ColumnType.NOTIFICATIONS &&
|
it.type == ColumnType.NOTIFICATIONS &&
|
||||||
it.accessInfo == account &&
|
it.accessInfo == account &&
|
||||||
!it.systemNotificationNotRelated
|
!it.systemNotificationNotRelated
|
||||||
}?.also {
|
}?.also {
|
||||||
scrollToColumn(columnList.indexOf(it))
|
scrollToColumn(columnList.indexOf(it))
|
||||||
} ?: addColumn(
|
} ?: addColumn(
|
||||||
|
@ -257,7 +258,9 @@ private fun ActMain.handleOAuth2Callback(uri: Uri) {
|
||||||
val error = uri.getQueryParameter("error")
|
val error = uri.getQueryParameter("error")
|
||||||
val errorDescription = uri.getQueryParameter("error_description")
|
val errorDescription = uri.getQueryParameter("error_description")
|
||||||
if (error != null || errorDescription != null) {
|
if (error != null || errorDescription != null) {
|
||||||
return@runApiTask TootApiResult(errorDescription.notBlank() ?: error.notBlank() ?: "?")
|
return@runApiTask TootApiResult(
|
||||||
|
errorDescription.notBlank() ?: error.notBlank() ?: "?"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// subwaytooter://oauth(\d*)/
|
// subwaytooter://oauth(\d*)/
|
||||||
|
@ -364,7 +367,11 @@ fun ActMain.afterAccountVerify(
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ActMain.afterAccessTokenUpdate(ta: TootAccount, sa: SavedAccount, tokenInfo: JsonObject?): Boolean {
|
private fun ActMain.afterAccessTokenUpdate(
|
||||||
|
ta: TootAccount,
|
||||||
|
sa: SavedAccount,
|
||||||
|
tokenInfo: JsonObject?
|
||||||
|
): Boolean {
|
||||||
if (sa.username != ta.username) {
|
if (sa.username != ta.username) {
|
||||||
showToast(true, R.string.user_name_not_match)
|
showToast(true, R.string.user_name_not_match)
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -223,6 +223,10 @@ class SideMenuAdapter(
|
||||||
closeColumnAll()
|
closeColumnAll()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Item(icon = R.drawable.ic_paste, title = R.string.open_column_from_url) {
|
||||||
|
openColumnFromUrl()
|
||||||
|
},
|
||||||
|
|
||||||
Item(icon = R.drawable.ic_home, title = R.string.home) {
|
Item(icon = R.drawable.ic_home, title = R.string.home) {
|
||||||
timeline(defaultInsertPosition, ColumnType.HOME)
|
timeline(defaultInsertPosition, ColumnType.HOME)
|
||||||
},
|
},
|
||||||
|
@ -459,7 +463,7 @@ class SideMenuAdapter(
|
||||||
var tz = TimeZone.getDefault()
|
var tz = TimeZone.getDefault()
|
||||||
val tzId = PrefS.spTimeZone()
|
val tzId = PrefS.spTimeZone()
|
||||||
if (tzId.isBlank()) {
|
if (tzId.isBlank()) {
|
||||||
return tz.displayName +"("+context.getString(R.string.device_timezone)+")"
|
return tz.displayName + "(" + context.getString(R.string.device_timezone) + ")"
|
||||||
}
|
}
|
||||||
tz = TimeZone.getTimeZone(tzId)
|
tz = TimeZone.getTimeZone(tzId)
|
||||||
var offset = tz.rawOffset.toLong()
|
var offset = tz.rawOffset.toLong()
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
package jp.juggler.subwaytooter.dialog
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import android.view.WindowManager
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.widget.EditText
|
||||||
|
import androidx.core.view.postDelayed
|
||||||
|
import jp.juggler.subwaytooter.R
|
||||||
|
import jp.juggler.subwaytooter.databinding.DlgOpenUrlBinding
|
||||||
|
import jp.juggler.util.LogCategory
|
||||||
|
import jp.juggler.util.isEnabledAlpha
|
||||||
|
import jp.juggler.util.showToast
|
||||||
|
import jp.juggler.util.systemService
|
||||||
|
|
||||||
|
object DlgOpenUrl {
|
||||||
|
private val log = LogCategory("DlgOpenUrl")
|
||||||
|
|
||||||
|
fun show(
|
||||||
|
activity: Activity,
|
||||||
|
onEmptyError: () -> Unit = { activity.showToast(false, R.string.url_empty) },
|
||||||
|
onOK: (Dialog, String) -> Unit
|
||||||
|
) {
|
||||||
|
|
||||||
|
val allowEmpty = false
|
||||||
|
|
||||||
|
val clipboard: ClipboardManager? = systemService(activity)
|
||||||
|
val viewBinding = DlgOpenUrlBinding.inflate(activity.layoutInflater)
|
||||||
|
|
||||||
|
viewBinding.etInput.setOnEditorActionListener { _, actionId, _ ->
|
||||||
|
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||||
|
viewBinding.btnOk.performClick()
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val dialog = Dialog(activity)
|
||||||
|
dialog.setContentView(viewBinding.root)
|
||||||
|
viewBinding.btnCancel.setOnClickListener { dialog.cancel() }
|
||||||
|
viewBinding.btnPaste.setOnClickListener { pasteTo(clipboard, viewBinding.etInput) }
|
||||||
|
viewBinding.btnOk.setOnClickListener {
|
||||||
|
val token = viewBinding.etInput.text.toString().trim { it <= ' ' }
|
||||||
|
if (token.isEmpty() && !allowEmpty) {
|
||||||
|
onEmptyError()
|
||||||
|
} else {
|
||||||
|
onOK(dialog, token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.window?.setLayout(
|
||||||
|
WindowManager.LayoutParams.MATCH_PARENT,
|
||||||
|
WindowManager.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
|
||||||
|
val clipboardListener = ClipboardManager.OnPrimaryClipChangedListener {
|
||||||
|
showPasteButton(clipboard, viewBinding)
|
||||||
|
}
|
||||||
|
clipboard?.addPrimaryClipChangedListener(clipboardListener)
|
||||||
|
dialog.setOnDismissListener {
|
||||||
|
clipboard?.removePrimaryClipChangedListener(clipboardListener)
|
||||||
|
}
|
||||||
|
viewBinding.root.postDelayed(100L) {
|
||||||
|
showPasteButton(clipboard, viewBinding)
|
||||||
|
pasteTo(clipboard, viewBinding.etInput)
|
||||||
|
}
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showPasteButton(clipboard: ClipboardManager?, viewBinding: DlgOpenUrlBinding) {
|
||||||
|
viewBinding.btnPaste.isEnabledAlpha = when {
|
||||||
|
clipboard == null -> false
|
||||||
|
!clipboard.hasPrimaryClip() -> false
|
||||||
|
clipboard.primaryClipDescription?.hasMimeType("text/plain") != true -> false
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pasteTo(clipboard: ClipboardManager?, et: EditText) {
|
||||||
|
val text = clipboard?.getUrlFromClipboard()
|
||||||
|
?: return
|
||||||
|
val ss = et.selectionStart
|
||||||
|
val se = et.selectionEnd
|
||||||
|
et.text.replace(ss, se, text)
|
||||||
|
et.setSelection(ss, ss + text.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ClipboardManager.getUrlFromClipboard(): String? {
|
||||||
|
try {
|
||||||
|
val item = primaryClip?.getItemAt(0)
|
||||||
|
item?.uri?.toString()?.let { return it }
|
||||||
|
item?.text?.toString()?.let { return it }
|
||||||
|
log.w("clip has nor uri or text.")
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
log.w(ex, "getUrlFromClipboard failed.")
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M19,2h-4.18C14.4,0.84 13.3,0 12,0c-1.3,0 -2.4,0.84 -2.82,2L5,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,4c0,-1.1 -0.9,-2 -2,-2zM12,2c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM19,20L5,20L5,4h2v3h10L17,4h2v16z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="12dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:labelFor="@+id/etInput"
|
||||||
|
android:text="@string/url_of_user_or_status"
|
||||||
|
tools:ignore="LabelFor" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btnPaste"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:contentDescription="@android:string/paste"
|
||||||
|
android:src="@drawable/ic_paste"
|
||||||
|
app:tint="?attr/colorImageButton" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:imeOptions="actionDone"
|
||||||
|
android:importantForAutofill="no"
|
||||||
|
android:inputType="text" />
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
style="?android:attr/buttonBarStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnCancel"
|
||||||
|
style="?android:attr/buttonBarButtonStyle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/cancel" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnOk"
|
||||||
|
style="?android:attr/buttonBarButtonStyle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/ok" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
|
@ -1102,4 +1102,8 @@
|
||||||
<string name="show_media_description">添付メディアの説明文を表示する</string>
|
<string name="show_media_description">添付メディアの説明文を表示する</string>
|
||||||
<string name="background_pattern">背景パターン</string>
|
<string name="background_pattern">背景パターン</string>
|
||||||
<string name="use_twemoji_emoji">Twemoji絵文字を使う</string>
|
<string name="use_twemoji_emoji">Twemoji絵文字を使う</string>
|
||||||
|
<string name="open_column_from_url">URLからカラムを開く</string>
|
||||||
|
<string name="url_empty">ユーザや投稿のURLを指定してください。</string>
|
||||||
|
<string name="url_parse_failed">URLの指定が変です</string>
|
||||||
|
<string name="url_of_user_or_status">ユーザや投稿のURL</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1113,4 +1113,8 @@
|
||||||
<string name="show_media_description">Show media description</string>
|
<string name="show_media_description">Show media description</string>
|
||||||
<string name="background_pattern">Background pattern</string>
|
<string name="background_pattern">Background pattern</string>
|
||||||
<string name="use_twemoji_emoji">Use Twemoji emoji</string>
|
<string name="use_twemoji_emoji">Use Twemoji emoji</string>
|
||||||
|
<string name="open_column_from_url">Open column from URL…</string>
|
||||||
|
<string name="url_empty">please input URL of user or status.</string>
|
||||||
|
<string name="url_of_user_or_status">URL of user or status</string>
|
||||||
|
<string name="url_parse_failed">parse error.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in New Issue