fix user creation sequence. add resend confirm email dialog. (but api does not work)

This commit is contained in:
tateisu 2021-05-11 06:30:42 +09:00
parent 3f71b87125
commit 970264bd3a
9 changed files with 776 additions and 518 deletions

View File

@ -1826,8 +1826,8 @@ class ActMain : AsyncActivity(), View.OnClickListener,
var ta: TootAccount? = null
var sa: SavedAccount? = null
var host: Host? = null
var ti: TootInstance? = null
var apiHost: Host? = null
var apDomain: Host? = null
override suspend fun background(client: TootApiClient): TootApiResult? {
@ -1868,8 +1868,8 @@ class ActMain : AsyncActivity(), View.OnClickListener,
val (ti, r2) = TootInstance.get(client)
ti ?: return r2
this.ti = ti
this.host = instance
this.apiHost = instance
this.apDomain = ti.uri?.let{ Host.parse(it)}
val parser = TootParser(
this@ActMain,
@ -1938,7 +1938,8 @@ class ActMain : AsyncActivity(), View.OnClickListener,
val instance = client.apiHost
?: return TootApiResult("missing instance in callback url.")
this.host = instance
this.apiHost = instance
val parser = TootParser(
@ -1955,24 +1956,26 @@ class ActMain : AsyncActivity(), View.OnClickListener,
this.ta = parser.account(it.jsonObject)
if( ta != null){
val (ti, ri) = TootInstance.getEx(client, forceAccessToken = refToken.get())
this.ti = ti ?: return ri
ti ?: return ri
this.apDomain = ti.uri?.let{ it2-> Host.parse(it2)}
}
}
}
}
override suspend fun handleResult(result: TootApiResult?) {
val host = this.host
val apiHost = this.apiHost
val apDomain = this.apDomain
val ta = this.ta
var sa = this.sa
if (ta != null && host?.isValid == true && sa == null) {
val acct = Acct.parse(ta.username, host)
if (ta != null && apiHost?.isValid == true && sa == null) {
val acct = Acct.parse(ta.username, apDomain ?: apiHost )
// アカウント追加時に、アプリ内に既にあるアカウントと同じものを登録していたかもしれない
sa = SavedAccount.loadAccountByAcct(this@ActMain, acct.ascii)
}
afterAccountVerify(result, ta, sa, ti, host)
afterAccountVerify(result, ta, sa, apiHost, apDomain)
}
})
@ -1994,8 +1997,8 @@ class ActMain : AsyncActivity(), View.OnClickListener,
result: TootApiResult?,
ta: TootAccount?,
sa: SavedAccount?,
ti: TootInstance?,
host: Host?
apiHost: Host?,
apDomain: Host?
): Boolean {
result ?: return false
@ -2041,20 +2044,14 @@ class ActMain : AsyncActivity(), View.OnClickListener,
return true
}
host != null -> {
apiHost != null -> {
// アカウント追加時
val user = Acct.parse(ta.username, host)
val apDomain = ti?.uri
if (apDomain == null) {
showToast(false, "Can't get ActivityPub domain name.")
return false
}
val user = Acct.parse(ta.username, apDomain ?: apiHost)
val row_id = SavedAccount.insert(
acct = user.ascii,
host = host.ascii,
domain = apDomain,
host = apiHost.ascii,
domain = (apDomain ?: apiHost).ascii,
account = jsonObject,
token = token_info,
misskeyVersion = TootInstance.parseMisskeyVersion(token_info)
@ -2120,12 +2117,17 @@ class ActMain : AsyncActivity(), View.OnClickListener,
TootTaskRunner(this@ActMain).run(apiHost, object : TootTask {
var ta: TootAccount? = null
var ti: TootInstance? = null
var apDomain : Host? = null
override suspend fun background(client: TootApiClient): TootApiResult? {
val (ti,ri) = TootInstance.getEx(client,forceAccessToken = access_token)
this.ti = ti ?: return ri
ti ?: return ri
val apDomain = ti.uri?.let { Host.parse(it) }
?: return TootApiResult("missing uri in Instance Information")
this.apDomain = apDomain
val misskeyVersion = ti.misskeyVersion
@ -2134,8 +2136,8 @@ class ActMain : AsyncActivity(), View.OnClickListener,
this.ta = TootParser(
this@ActMain,
LinkHelper.create(
apiHost,
apDomainArg = ti.uri?.let { Host.parse(it) },
apiHostArg = apiHost,
apDomainArg = apDomain,
misskeyVersion = misskeyVersion
)
).account(result?.jsonObject)
@ -2144,7 +2146,7 @@ class ActMain : AsyncActivity(), View.OnClickListener,
}
override suspend fun handleResult(result: TootApiResult?) {
if (afterAccountVerify(result, ta, sa, ti, apiHost)) {
if (afterAccountVerify(result, ta, sa, apiHost, apDomain)) {
dialog_host?.dismissSafe()
dialog_token?.dismissSafe()
}

View File

@ -26,6 +26,7 @@ import com.google.android.flexbox.JustifyContent
import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayout
import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayoutDirection
import jp.juggler.emoji.UnicodeEmoji
import jp.juggler.subwaytooter.action.Action_Account
import jp.juggler.subwaytooter.action.Action_List
import jp.juggler.subwaytooter.action.Action_Notification
import jp.juggler.subwaytooter.api.TootApiClient
@ -87,7 +88,10 @@ class ColumnViewHolder(
private var status_adapter: ItemListAdapter? = null
private var page_idx: Int = 0
private lateinit var llLoading: View
private lateinit var btnConfirmMail: Button
private lateinit var tvLoading: TextView
lateinit var listView: RecyclerView
lateinit var refreshLayout: SwipyRefreshLayout
@ -297,7 +301,7 @@ class ColumnViewHolder(
@SuppressLint("ClickableViewAccessibility")
private fun initLoadingTextView() {
tvLoading.setOnTouchListener(ErrorFlickListener(activity))
llLoading.setOnTouchListener(ErrorFlickListener(activity))
}
val viewRoot: View = inflate(activity, parent)
@ -354,6 +358,7 @@ class ColumnViewHolder(
btnColumnClose.setOnClickListener(this)
btnColumnClose.setOnLongClickListener(this)
btnDeleteNotification.setOnClickListener(this)
btnConfirmMail.setOnClickListener(this)
btnColor.setOnClickListener(this)
btnLanguageFilter.setOnClickListener(this)
@ -782,7 +787,13 @@ class ColumnViewHolder(
)
}
val streamStatus = column.getStreamingStatus()
log.d("procShowColumnStatus: streamStatus=${streamStatus}, column=${column.access_info.acct}/${column.getColumnName(true)}")
log.d(
"procShowColumnStatus: streamStatus=${streamStatus}, column=${column.access_info.acct}/${
column.getColumnName(
true
)
}"
)
when (streamStatus) {
StreamStatus.Missing, StreamStatus.Closed -> {
@ -1181,6 +1192,11 @@ class ColumnViewHolder(
activity.app_state.saveColumnList()
showAnnouncements()
}
btnConfirmMail -> {
Action_Account.resendConfirmMail(activity, column.access_info)
}
}
}
@ -1198,12 +1214,13 @@ class ColumnViewHolder(
private fun showError(message: String) {
hideRefreshError()
tvLoading.visibility = View.VISIBLE
tvLoading.text = message
refreshLayout.isRefreshing = false
refreshLayout.visibility = View.GONE
llLoading.visibility = View.VISIBLE
tvLoading.text = message
btnConfirmMail.vg(column?.access_info?.isConfirmed == false)
}
private fun showColumnCloseButton() {
@ -1300,7 +1317,7 @@ class ColumnViewHolder(
return
}
tvLoading.visibility = View.GONE
llLoading.visibility = View.GONE
refreshLayout.visibility = View.VISIBLE
@ -2353,9 +2370,27 @@ class ColumnViewHolder(
}.lparams(matchParent, matchParent)
tvLoading = textView {
llLoading = verticalLayout {
lparams(matchParent, matchParent)
isBaselineAligned = false
gravity = Gravity.CENTER
}.lparams(matchParent, matchParent)
tvLoading = textView {
gravity = Gravity.CENTER
}.lparams(matchParent, wrapContent)
btnConfirmMail = button {
text = activity.getString(R.string.resend_confirm_mail)
background = ContextCompat.getDrawable(
activity,
R.drawable.btn_bg_transparent_round6dp
)
}.lparams(matchParent, wrapContent) {
topMargin = dip(8)
}
}
refreshLayout = swipyRefreshLayout {
lparams(matchParent, matchParent)
@ -2744,7 +2779,7 @@ class ColumnViewHolder(
if (sample == null) {
EmojiPicker(activity, column.access_info, closeOnSelected = true) { result ->
val emoji = result.emoji
val code = when(emoji){
val code = when (emoji) {
is UnicodeEmoji -> emoji.unifiedCode
is CustomEmoji -> emoji.shortcode
else -> error("unknown emoji type")

View File

@ -3,13 +3,11 @@ package jp.juggler.subwaytooter.action
import android.app.Dialog
import android.os.Build
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import jp.juggler.subwaytooter.*
import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.dialog.AccountPicker
import jp.juggler.subwaytooter.dialog.DlgCreateAccount
import jp.juggler.subwaytooter.dialog.DlgTextInput
import jp.juggler.subwaytooter.dialog.LoginForm
import jp.juggler.subwaytooter.dialog.*
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.table.UserRelation
import jp.juggler.subwaytooter.util.LinkHelper
@ -17,318 +15,357 @@ import jp.juggler.subwaytooter.util.openBrowser
import jp.juggler.util.*
object Action_Account {
@Suppress("unused")
private val log = LogCategory("Action_Account")
// アカウントの追加
fun add(activity : ActMain) {
LoginForm.showLoginForm(
activity,
null
) { dialog, instance, action ->
TootTaskRunner(activity).run(instance, object : TootTask {
override suspend fun background(client : TootApiClient) : TootApiResult? {
return when(action) {
LoginForm.Action.Existing ->
client.authentication1(Pref.spClientName(activity))
LoginForm.Action.Create ->
client.createUser1(Pref.spClientName(activity))
LoginForm.Action.Pseudo,
LoginForm.Action.Token -> {
val (ti, ri) = TootInstance.get(client)
if(ti != null) ri?.data = ti
ri
}
}
}
override suspend fun handleResult(result : TootApiResult?) {
result ?: return // cancelled.
val data = result.data
if(result.error == null && data != null) {
when(action) {
LoginForm.Action.Existing -> if(data is String) {
// ブラウザ用URLが生成された
activity.openBrowser(data.toUri())
dialog.dismissSafe()
return
}
LoginForm.Action.Create -> if(data is JsonObject) {
// インスタンスを確認できた
createAccount(
activity,
instance,
data,
dialog
)
return
}
LoginForm.Action.Pseudo -> if(data is TootInstance) {
addPseudoAccount(
activity,
instance,
instanceInfo = data
) { a ->
activity.showToast(false, R.string.server_confirmed)
val pos = activity.app_state.columnCount
activity.addColumn(pos, a, ColumnType.LOCAL)
dialog.dismissSafe()
}
}
LoginForm.Action.Token -> if(data is TootInstance) {
DlgTextInput.show(
activity,
activity.getString(R.string.access_token_or_api_token),
null,
callback = object : DlgTextInput.Callback {
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
override fun onOK(
dialog_token : Dialog,
text : String
) {
// dialog引数が二つあるのに注意
activity.checkAccessToken(
dialog,
dialog_token,
instance,
text,
null
)
}
override fun onEmptyError() {
activity.showToast(true, R.string.token_not_specified)
}
}
)
}
}
}
val errorText = result.error ?: "(no error information)"
if(errorText.contains("SSLHandshakeException")
&& (Build.VERSION.RELEASE.startsWith("7.0")
|| Build.VERSION.RELEASE.startsWith("7.1")
&& ! Build.VERSION.RELEASE.startsWith("7.1.")
)
) {
AlertDialog.Builder(activity)
.setMessage(errorText + "\n\n" + activity.getString(R.string.ssl_bug_7_0))
.setNeutralButton(R.string.close, null)
.show()
} else {
activity.showToast(true, "$errorText ${result.requestInfo}".trim())
}
}
})
}
}
private fun createAccount(
activity : ActMain,
instance : Host,
client_info : JsonObject,
dialog_host : Dialog
) {
DlgCreateAccount(
activity,
instance
) { dialog_create, username, email, password, agreement, reason ->
// dialog引数が二つあるのに注意
TootTaskRunner(activity).run(instance, object : TootTask {
var ta : TootAccount? = null
var ti : TootInstance? = null
override suspend fun background(client : TootApiClient) : TootApiResult? {
val r1 = client.createUser2Mastodon(
client_info,
username,
email,
password,
agreement,
reason
)
val ti = r1?.jsonObject ?: return r1
val misskeyVersion = TootInstance.parseMisskeyVersion(ti)
val parser = TootParser(
activity,
linkHelper = LinkHelper.create(instance, misskeyVersion = misskeyVersion)
)
this.ti = TootInstance(parser, ti)
val access_token = ti.string("access_token")
?: return TootApiResult("can't get user access token")
val r2 = client.getUserCredential(access_token, misskeyVersion = misskeyVersion)
this.ta = parser.account(r2?.jsonObject)
if(this.ta != null) return r2
val jsonObject = jsonObject {
put("id", EntityId.CONFIRMING.toString())
put("username", username)
put("acct", username)
put("acct", username)
put("url", "https://$instance/@$username")
}
this.ta = parser.account(jsonObject)
r1.data = jsonObject
r1.tokenInfo = ti
return r1
}
override suspend fun handleResult(result : TootApiResult?) {
val sa : SavedAccount? = null
if(activity.afterAccountVerify(result, ta, sa, ti, instance)) {
dialog_host.dismissSafe()
dialog_create.dismissSafe()
}
}
})
}.show()
}
// アカウント設定
fun setting(activity : ActMain) {
AccountPicker.pick(
activity,
bAllowPseudo = true,
bAuto = true,
message = activity.getString(R.string.account_picker_open_setting)
) { ai -> ActAccountSetting.open(activity, ai, ActMain.REQUEST_CODE_ACCOUNT_SETTING) }
}
// アカウントを選んでタイムラインカラムを追加
fun timeline(
activity : ActMain,
pos : Int,
type : ColumnType,
args : Array<out Any> = emptyArray()
) {
AccountPicker.pick(
activity,
bAllowPseudo = type.bAllowPseudo,
bAllowMisskey = type.bAllowMisskey,
bAllowMastodon = type.bAllowMastodon,
bAuto = true,
message = activity.getString(
R.string.account_picker_add_timeline_of,
type.name1(activity)
)
) { ai ->
when(type) {
ColumnType.PROFILE -> {
val id = ai.loginAccount?.id
if(id != null) activity.addColumn(pos, ai, type, id)
}
ColumnType.PROFILE_DIRECTORY ->
activity.addColumn(pos, ai, type, ai.apiHost)
else -> activity.addColumn(pos, ai, type, *args)
}
}
}
// 投稿画面を開く。初期テキストを指定する
fun openPost(
activity : ActMain,
initial_text : String? = activity.quickTootText
) {
activity.post_helper.closeAcctPopup()
val db_id = activity.currentPostTarget?.db_id ?: - 1L
if(db_id != - 1L) {
ActPost.open(activity, ActMain.REQUEST_CODE_POST, db_id, initial_text = initial_text)
} else {
AccountPicker.pick(
activity,
bAllowPseudo = false,
bAuto = true,
message = activity.getString(R.string.account_picker_toot)
) { ai ->
ActPost.open(
activity,
ActMain.REQUEST_CODE_POST,
ai.db_id,
initial_text = initial_text
)
}
}
}
fun endorse(
activity : ActMain,
access_info : SavedAccount,
who : TootAccount,
bSet : Boolean
) {
if(access_info.isMisskey) {
activity.showToast(false, "This feature is not provided on Misskey account.")
return
}
TootTaskRunner(activity).run(access_info, object : TootTask {
var relation : UserRelation? = null
override suspend fun background(client : TootApiClient) : TootApiResult? {
val result = client.request(
"/api/v1/accounts/${who.id}/" + when(bSet) {
true -> "pin"
false -> "unpin"
},
"".toFormRequestBody().toPost()
)
val jsonObject = result?.jsonObject
if(jsonObject != null) {
val tr = parseItem(
::TootRelationShip,
TootParser(client.context, access_info),
jsonObject
)
if(tr != null) {
this.relation = saveUserRelation(access_info, tr)
}
}
return result
}
override suspend fun handleResult(result : TootApiResult?) {
result ?: return
if(result.error != null) {
activity.showToast(true, result.error)
} else {
activity.showToast(
false, when(bSet) {
true -> R.string.endorse_succeeded
else -> R.string.remove_endorse_succeeded
}
)
}
}
})
}
@Suppress("unused")
private val log = LogCategory("Action_Account")
// アカウントの追加
fun add(activity: ActMain) {
LoginForm.showLoginForm(
activity,
null
) { dialog, instance, action ->
TootTaskRunner(activity).run(instance, object : TootTask {
override suspend fun background(client: TootApiClient): TootApiResult? {
return when (action) {
LoginForm.Action.Existing ->
client.authentication1(Pref.spClientName(activity))
LoginForm.Action.Create ->
client.createUser1(Pref.spClientName(activity))
LoginForm.Action.Pseudo,
LoginForm.Action.Token -> {
val (ti, ri) = TootInstance.get(client)
if (ti != null) ri?.data = ti
ri
}
}
}
override suspend fun handleResult(result: TootApiResult?) {
result ?: return // cancelled.
val data = result.data
if (result.error == null && data != null) {
when (action) {
LoginForm.Action.Existing -> if (data is String) {
// ブラウザ用URLが生成された
activity.openBrowser(data.toUri())
dialog.dismissSafe()
return
}
LoginForm.Action.Create -> if (data is JsonObject) {
// インスタンスを確認できた
createAccount(
activity,
instance,
data,
dialog
)
return
}
LoginForm.Action.Pseudo -> if (data is TootInstance) {
addPseudoAccount(
activity,
instance,
instanceInfo = data
) { a ->
activity.showToast(false, R.string.server_confirmed)
val pos = activity.app_state.columnCount
activity.addColumn(pos, a, ColumnType.LOCAL)
dialog.dismissSafe()
}
}
LoginForm.Action.Token -> if (data is TootInstance) {
DlgTextInput.show(
activity,
activity.getString(R.string.access_token_or_api_token),
null,
callback = object : DlgTextInput.Callback {
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
override fun onOK(
dialog_token: Dialog,
text: String
) {
// dialog引数が二つあるのに注意
activity.checkAccessToken(
dialog,
dialog_token,
instance,
text,
null
)
}
override fun onEmptyError() {
activity.showToast(true, R.string.token_not_specified)
}
}
)
}
}
}
val errorText = result.error ?: "(no error information)"
if (errorText.contains("SSLHandshakeException")
&& (Build.VERSION.RELEASE.startsWith("7.0")
|| Build.VERSION.RELEASE.startsWith("7.1")
&& !Build.VERSION.RELEASE.startsWith("7.1.")
)
) {
AlertDialog.Builder(activity)
.setMessage(errorText + "\n\n" + activity.getString(R.string.ssl_bug_7_0))
.setNeutralButton(R.string.close, null)
.show()
} else {
activity.showToast(true, "$errorText ${result.requestInfo}".trim())
}
}
})
}
}
private fun createAccount(
activity: ActMain,
apiHost: Host,
client_info: JsonObject,
dialog_host: Dialog
) {
DlgCreateAccount(
activity,
apiHost
) { dialog_create, username, email, password, agreement, reason ->
// dialog引数が二つあるのに注意
TootTaskRunner(activity).run(apiHost, object : TootTask {
var ta: TootAccount? = null
var apDomain: Host? = null
override suspend fun background(client: TootApiClient): TootApiResult? {
val r1 = client.createUser2Mastodon(
client_info,
username,
email,
password,
agreement,
reason
)
val tokenJson = r1?.jsonObject ?: return r1
val misskeyVersion = TootInstance.parseMisskeyVersion(tokenJson)
val parser = TootParser(
activity,
linkHelper = LinkHelper.create(apiHost, misskeyVersion = misskeyVersion)
)
// ここだけMastodon専用
val access_token = tokenJson.string("access_token")
?: return TootApiResult("can't get user access token")
client.apiHost = apiHost
val (ti, ri) = TootInstance.getEx(client, forceAccessToken = access_token)
ti ?: return ri
this.apDomain = ti.uri?.let { Host.parse(it) }
val r2 = client.getUserCredential(access_token, misskeyVersion = misskeyVersion)
this.ta = parser.account(r2?.jsonObject)
if (this.ta != null) return r2
val jsonObject = jsonObject {
put("id", EntityId.CONFIRMING.toString())
put("username", username)
put("acct", username)
put("url", "https://$apiHost/@$username")
}
this.ta = parser.account(jsonObject)
r1.data = jsonObject
r1.tokenInfo = tokenJson
return r1
}
override suspend fun handleResult(result: TootApiResult?) {
val sa: SavedAccount? = null
if (activity.afterAccountVerify(result, ta, sa, apiHost, apDomain)) {
dialog_host.dismissSafe()
dialog_create.dismissSafe()
}
}
})
}.show()
}
// アカウント設定
fun setting(activity: ActMain) {
AccountPicker.pick(
activity,
bAllowPseudo = true,
bAuto = true,
message = activity.getString(R.string.account_picker_open_setting)
) { ai -> ActAccountSetting.open(activity, ai, ActMain.REQUEST_CODE_ACCOUNT_SETTING) }
}
// アカウントを選んでタイムラインカラムを追加
fun timeline(
activity: ActMain,
pos: Int,
type: ColumnType,
args: Array<out Any> = emptyArray()
) {
AccountPicker.pick(
activity,
bAllowPseudo = type.bAllowPseudo,
bAllowMisskey = type.bAllowMisskey,
bAllowMastodon = type.bAllowMastodon,
bAuto = true,
message = activity.getString(
R.string.account_picker_add_timeline_of,
type.name1(activity)
)
) { ai ->
when (type) {
ColumnType.PROFILE -> {
val id = ai.loginAccount?.id
if (id != null) activity.addColumn(pos, ai, type, id)
}
ColumnType.PROFILE_DIRECTORY ->
activity.addColumn(pos, ai, type, ai.apiHost)
else -> activity.addColumn(pos, ai, type, *args)
}
}
}
// 投稿画面を開く。初期テキストを指定する
fun openPost(
activity: ActMain,
initial_text: String? = activity.quickTootText
) {
activity.post_helper.closeAcctPopup()
val db_id = activity.currentPostTarget?.db_id ?: -1L
if (db_id != -1L) {
ActPost.open(activity, ActMain.REQUEST_CODE_POST, db_id, initial_text = initial_text)
} else {
AccountPicker.pick(
activity,
bAllowPseudo = false,
bAuto = true,
message = activity.getString(R.string.account_picker_toot)
) { ai ->
ActPost.open(
activity,
ActMain.REQUEST_CODE_POST,
ai.db_id,
initial_text = initial_text
)
}
}
}
fun endorse(
activity: ActMain,
access_info: SavedAccount,
who: TootAccount,
bSet: Boolean
) {
if (access_info.isMisskey) {
activity.showToast(false, "This feature is not provided on Misskey account.")
return
}
TootTaskRunner(activity).run(access_info, object : TootTask {
var relation: UserRelation? = null
override suspend fun background(client: TootApiClient): TootApiResult? {
val result = client.request(
"/api/v1/accounts/${who.id}/" + when (bSet) {
true -> "pin"
false -> "unpin"
},
"".toFormRequestBody().toPost()
)
val jsonObject = result?.jsonObject
if (jsonObject != null) {
val tr = parseItem(
::TootRelationShip,
TootParser(client.context, access_info),
jsonObject
)
if (tr != null) {
this.relation = saveUserRelation(access_info, tr)
}
}
return result
}
override suspend fun handleResult(result: TootApiResult?) {
result ?: return
if (result.error != null) {
activity.showToast(true, result.error)
} else {
activity.showToast(
false, when (bSet) {
true -> R.string.endorse_succeeded
else -> R.string.remove_endorse_succeeded
}
)
}
}
})
}
private val mailRegex =
"""\A[a-z0-9_+&*-]+(?:\.[a-z0-9_+&*-]+)*@(?:[a-z0-9-]+\.)+[a-z]{2,12}\z""".toRegex(
RegexOption.IGNORE_CASE
)
fun resendConfirmMail(activity: AppCompatActivity, accessInfo: SavedAccount) {
DlgConfirmMail(
activity,
accessInfo
) { email ->
TootTaskRunner(activity).run(accessInfo, object : TootTask {
override suspend fun background(client: TootApiClient): TootApiResult? {
email?.let {
if (!mailRegex.matches(it))
return TootApiResult("email address is not valid.")
}
return client.request(
"/api/v1/emails/confirmations",
ArrayList<String>().apply {
if (email != null) add("email=${email.encodePercent()}")
}.joinToString("&").toFormRequestBody().toPost()
)
}
override suspend fun handleResult(result: TootApiResult?) {
result ?: return // cancelled.
when (val error = result.error) {
null ->
activity.showToast(true, R.string.resend_confirm_mail_requested)
else ->
activity.showToast(true, error)
}
}
})
}.show()
}
}

View File

@ -475,7 +475,7 @@ class TootInstance(parser: TootParser, src: JsonObject) {
TootParser(
client.context,
linkHelper = linkHelper ?: LinkHelper.create(
hostArg!!,
(hostArg ?: client.apiHost)!!,
misskeyVersion = parseMisskeyVersion(json)
)
),

View File

@ -0,0 +1,73 @@
package jp.juggler.subwaytooter.dialog
import android.annotation.SuppressLint
import android.app.Dialog
import android.view.View
import android.view.WindowManager
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.R
import jp.juggler.subwaytooter.table.SavedAccount
class DlgConfirmMail(
val activity: AppCompatActivity,
val accessInfo: SavedAccount,
val onClickOk: (email: String?) -> Unit
) : View.OnClickListener {
@SuppressLint("InflateParams")
private val viewRoot = activity.layoutInflater
.inflate(R.layout.dlg_confirm_mail, null, false)
private val cbUpdateMailAddress: CheckBox = viewRoot.findViewById(R.id.cbUpdateMailAddress)
private val etEmail: EditText = viewRoot.findViewById(R.id.etEmail)
private val dialog = Dialog(activity)
init {
viewRoot.findViewById<TextView>(R.id.tvUserName).text = accessInfo.acct.pretty
viewRoot.findViewById<TextView>(R.id.tvInstance).text =
if (accessInfo.apiHost != accessInfo.apDomain) {
"${accessInfo.apiHost.pretty} (${accessInfo.apDomain.pretty})"
} else {
accessInfo.apiHost.pretty
}
cbUpdateMailAddress.setOnCheckedChangeListener { _, isChecked ->
etEmail.isEnabled = isChecked
}
arrayOf(
R.id.btnCancel,
R.id.btnOk
).forEach {
viewRoot.findViewById<Button>(it)?.setOnClickListener(this)
}
}
fun show() {
dialog.setContentView(viewRoot)
dialog.window?.setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.WRAP_CONTENT
)
dialog.show()
}
override fun onClick(v: View?) {
when (v?.id) {
R.id.btnCancel ->
dialog.cancel()
R.id.btnOk ->
onClickOk(
if (cbUpdateMailAddress.isChecked) etEmail.text.toString().trim() else null
)
}
}
}

View File

@ -1,180 +1,174 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:ignore="UnusedAttribute"
>
tools:ignore="UnusedAttribute">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:text="@string/instance"
/>
<TextView
android:id="@+id/tvInstance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:text="@string/description"
/>
<TextView
android:id="@+id/tvDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:text="@string/user_name"
/>
<EditText
android:id="@+id/etUserName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:hint="@string/user_name_hint"
android:inputType="text"
android:importantForAutofill="no"
tools:ignore="UnusedAttribute"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:text="@string/email"
/>
<EditText
android:id="@+id/etEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:hint="@string/email_hint"
android:inputType="text"
android:importantForAutofill="no"
tools:ignore="TextFields"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:text="@string/password"
/>
<EditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:inputType="textPassword"
android:importantForAutofill="no"
android:hint="@string/password_hint"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:text="@string/reason_create_account"
android:id="@+id/tvReasonCaption"
android:labelFor="@+id/etReason"
/>
<EditText
android:id="@+id/etReason"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:inputType="text"
android:importantForAutofill="no"
/>
<Button
android:id="@+id/btnRules"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:text="@string/instance_rules"
android:textAllCaps="false"
/>
<Button
android:id="@+id/btnTerms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:text="@string/privacy_policy"
android:textAllCaps="false"
/>
<CheckBox
android:id="@+id/cbAgreement"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:text="@string/agree_terms"
/>
<LinearLayout
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
android:orientation="vertical"
tools:ignore="UnusedAttribute">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:text="@string/instance" />
<TextView
android:id="@+id/tvInstance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:text="@string/description" />
<TextView
android:id="@+id/tvDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:text="@string/user_name" />
<EditText
android:id="@+id/etUserName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:hint="@string/user_name_hint"
android:importantForAutofill="no"
android:inputType="text"
tools:ignore="UnusedAttribute" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:text="@string/email" />
<EditText
android:id="@+id/etEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:hint="@string/email_hint"
android:importantForAutofill="no"
android:inputType="text"
tools:ignore="TextFields" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:text="@string/password" />
<EditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:hint="@string/password_hint"
android:importantForAutofill="no"
android:inputType="textPassword" />
<TextView
android:id="@+id/tvReasonCaption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:labelFor="@+id/etReason"
android:text="@string/reason_create_account" />
<EditText
android:id="@+id/etReason"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:importantForAutofill="no"
android:inputType="text" />
<Button
android:id="@+id/btnCancel"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:id="@+id/btnRules"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/cancel"
/>
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:text="@string/instance_rules"
android:textAllCaps="false" />
<Button
android:id="@+id/btnOk"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:id="@+id/btnTerms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/ok"
/>
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:text="@string/privacy_policy"
android:textAllCaps="false" />
<CheckBox
android:id="@+id/cbAgreement"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:text="@string/agree_terms" />
<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>
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:ignore="UnusedAttribute">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:ignore="UnusedAttribute">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:text="@string/instance" />
<TextView
android:id="@+id/tvInstance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:text="@string/user_name" />
<TextView
android:id="@+id/tvUserName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:hint="@string/user_name_hint"
android:importantForAutofill="no"
tools:ignore="UnusedAttribute" />
<CheckBox
android:id="@+id/cbUpdateMailAddress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:text="@string/update_mail_address" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:text="@string/email" />
<EditText
android:id="@+id/etEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:enabled="false"
android:hint="@string/email_hint"
android:importantForAutofill="no"
android:inputType="text"
tools:ignore="TextFields" />
<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>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:text="@string/confirm_mail_description" />
</LinearLayout>
</ScrollView>

View File

@ -1073,5 +1073,9 @@
<string name="server_has_no_support_of_visibility">公開範囲\'%1$s\'はこのサーバで使えません</string>
<string name="in_app_unicode_emoji">アプリ内蔵のUnicode絵文字を使う(アプリ再起動が必要)</string>
<string name="emoji_category_composite_tones">複合トーン</string>
<string name="resend_confirm_mail">確認メールの再送…</string>
<string name="update_mail_address">登録メールアドレスを変更する</string>
<string name="resend_confirm_mail_requested">確認メールの再送を要求しました。</string>
<string name="confirm_mail_description">確認メールの再送を要求した後の手順:\n- あなたのメーラーで新着メールが届くのを確認する。\n- メール中の確認リンクを開く。\n- このダイアログを閉じてカラムをリロードする。</string>
</resources>

View File

@ -1087,4 +1087,8 @@
<string name="show_emoji_picker_other_category">Show emoji picker other category</string>
<string name="in_app_unicode_emoji">use in-app Unicode Emoji (app restart required)</string>
<string name="emoji_category_composite_tones">Composite Tones</string>
<string name="resend_confirm_mail">Resend confirm mail…</string>
<string name="update_mail_address">Update e-mail address</string>
<string name="resend_confirm_mail_requested">Resending confirm E-mail was requested.</string>
<string name="confirm_mail_description">After requesting resending confirm E-mail,\n- please check the mail on your mailer.\n- open confirm link in the mail.\n- close this dialog and reload column.</string>
</resources>