サイドメニューに「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 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)
}
}
}

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() 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

View File

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

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="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>

View File

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