サイドメニューに「URLからカラムを開く」を追加

This commit is contained in:
tateisu 2021-11-16 22:05:08 +09:00
parent 629cb1df9b
commit 4c43e807b1
8 changed files with 227 additions and 10 deletions

View File

@ -1,12 +1,16 @@
package jp.juggler.subwaytooter.action
import android.app.AlertDialog
import android.net.Uri
import jp.juggler.subwaytooter.ActColumnList
import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.actmain.currentColumn
import jp.juggler.subwaytooter.actmain.handleOtherUri
import jp.juggler.subwaytooter.api.entity.TootApplication
import jp.juggler.subwaytooter.dialog.DlgOpenUrl
import jp.juggler.subwaytooter.table.MutedApp
import jp.juggler.util.dismissSafe
import jp.juggler.util.showToast
// カラム一覧を開く
@ -33,3 +37,15 @@ fun ActMain.appMute(
appState.onMuteUpdated()
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)
}
}
}

View File

@ -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()
url.findStatusIdFromUrl()?.let { statusInfo ->
@ -55,7 +55,7 @@ private fun ActMain.handleOtherUri(uri: Uri) {
statusInfo.host,
statusInfo.statusId
)
return
return true
}
TootAccount.reAccountUrl.matcher(url).takeIf { it.find() }?.let { m ->
@ -80,7 +80,7 @@ private fun ActMain.handleOtherUri(uri: Uri) {
userUrl = url,
)
}
return
return true
}
TootAccount.reAccountUrl2.matcher(url).takeIf { it.find() }?.let { m ->
@ -93,7 +93,7 @@ private fun ActMain.handleOtherUri(uri: Uri) {
acct = Acct.parse(user, host),
userUrl = url,
)
return
return true
}
// このアプリでは処理できないURLだった
@ -139,7 +139,7 @@ private fun ActMain.handleOtherUri(uri: Uri) {
// 指定した選択肢でチューザーを作成して開く
startActivity(chooser)
return
return true
} catch (ex: Throwable) {
log.trace(ex)
}
@ -149,6 +149,7 @@ private fun ActMain.handleOtherUri(uri: Uri) {
.setMessage(errorMessage)
.setPositiveButton(R.string.close, null)
.show()
return false
}
private fun ActMain.handleCustomSchemaUri(uri: Uri) {
@ -176,8 +177,8 @@ private fun ActMain.handleNotificationClick(uri: Uri, dataIdString: String) {
val columnList = appState.columnList
val column = columnList.firstOrNull {
it.type == ColumnType.NOTIFICATIONS &&
it.accessInfo == account &&
!it.systemNotificationNotRelated
it.accessInfo == account &&
!it.systemNotificationNotRelated
}?.also {
scrollToColumn(columnList.indexOf(it))
} ?: addColumn(
@ -257,7 +258,9 @@ private fun ActMain.handleOAuth2Callback(uri: Uri) {
val error = uri.getQueryParameter("error")
val errorDescription = uri.getQueryParameter("error_description")
if (error != null || errorDescription != null) {
return@runApiTask TootApiResult(errorDescription.notBlank() ?: error.notBlank() ?: "?")
return@runApiTask TootApiResult(
errorDescription.notBlank() ?: error.notBlank() ?: "?"
)
}
// subwaytooter://oauth(\d*)/
@ -364,7 +367,11 @@ fun ActMain.afterAccountVerify(
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) {
showToast(true, R.string.user_name_not_match)
return false

View File

@ -223,6 +223,10 @@ class SideMenuAdapter(
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) {
timeline(defaultInsertPosition, ColumnType.HOME)
},
@ -459,7 +463,7 @@ class SideMenuAdapter(
var tz = TimeZone.getDefault()
val tzId = PrefS.spTimeZone()
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)
var offset = tz.rawOffset.toLong()

View File

@ -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
}
}

View File

@ -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>

View File

@ -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>

View File

@ -1102,4 +1102,8 @@
<string name="show_media_description">添付メディアの説明文を表示する</string>
<string name="background_pattern">背景パターン</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>

View File

@ -1113,4 +1113,8 @@
<string name="show_media_description">Show media description</string>
<string name="background_pattern">Background pattern</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>