予約投稿を行う。
This commit is contained in:
parent
4e83cb9fcc
commit
14530707c3
|
@ -40,6 +40,7 @@ import okhttp3.RequestBody
|
|||
import okio.BufferedSink
|
||||
import org.json.JSONObject
|
||||
import java.io.*
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class ActAccountSetting
|
||||
: AppCompatActivity(), View.OnClickListener, CompoundButton.OnCheckedChangeListener {
|
||||
|
@ -1157,7 +1158,8 @@ class ActAccountSetting
|
|||
private fun sendNote(bConfirmed : Boolean = false) {
|
||||
val sv = etNote.text.toString()
|
||||
if(! bConfirmed) {
|
||||
val length = sv.codePointCount(0, sv.length)
|
||||
|
||||
val length = countNoteText(sv)
|
||||
if(length > max_length_note) {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(
|
||||
|
@ -1178,6 +1180,18 @@ class ActAccountSetting
|
|||
updateCredential("note", EmojiDecoder.decodeShortCode(sv))
|
||||
}
|
||||
|
||||
// Mastodon 2.7 でnoteの文字数計算が変わる
|
||||
// https://github.com/tootsuite/mastodon/commit/45899cfa691b1e4f43da98c456ae8faa584eb437
|
||||
private val reLinkUrl = Pattern.compile("""(https?://[\w/:%#@${'$'}&?!()\[\]~.=+\-]+)""")
|
||||
private val reMention =Pattern.compile("""(?<=^|[^/\w\p{Pc}])@((\w+([\w.-]+\w+)?)(?:@[a-z0-9.\-]+[a-z0-9]+)?)""",Pattern.CASE_INSENSITIVE)
|
||||
private val strUrlReplacement = (0 until 23).map{ ' '}.joinToString()
|
||||
private fun countNoteText(s:String):Int{
|
||||
val s2 = s
|
||||
.replaceAll(reLinkUrl,strUrlReplacement)
|
||||
.replaceAll(reMention,"@\\2")
|
||||
return s2.codePointCount(0,s2.length)
|
||||
}
|
||||
|
||||
private fun sendLocked(willLocked : Boolean) {
|
||||
updateCredential("locked", willLocked)
|
||||
}
|
||||
|
|
|
@ -725,16 +725,22 @@ class ActMain : AppCompatActivity()
|
|||
|
||||
etQuickToot.hideKeyboard()
|
||||
|
||||
post_helper.post(
|
||||
account
|
||||
) { target_account, status ->
|
||||
etQuickToot.setText("")
|
||||
posted_acct = target_account.acct
|
||||
posted_status_id = status.id
|
||||
posted_reply_id = status.in_reply_to_id
|
||||
posted_redraft_id = null
|
||||
refreshAfterPost()
|
||||
}
|
||||
post_helper.post(account,callback=object:PostHelper.PostCompleteCallback{
|
||||
override fun onPostComplete(
|
||||
target_account : SavedAccount,
|
||||
status : TootStatus
|
||||
) {
|
||||
etQuickToot.setText("")
|
||||
posted_acct = target_account.acct
|
||||
posted_status_id = status.id
|
||||
posted_reply_id = status.in_reply_to_id
|
||||
posted_redraft_id = null
|
||||
refreshAfterPost()
|
||||
}
|
||||
|
||||
override fun onScheduledPostComplete(target_account : SavedAccount) { // TODO
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onPageScrolled(
|
||||
|
@ -1102,6 +1108,13 @@ class ActMain : AppCompatActivity()
|
|||
, bAllowPseudo = false
|
||||
, bAllowMisskey = false
|
||||
)
|
||||
R.id.nav_scheduled_statuses_list-> Action_Account.timeline(
|
||||
this
|
||||
, defaultInsertPosition
|
||||
, Column.TYPE_SCHEDULED_STATUS
|
||||
, bAllowPseudo = false
|
||||
, bAllowMisskey = false
|
||||
)
|
||||
R.id.nav_add_list -> Action_Account.timeline(
|
||||
this
|
||||
, defaultInsertPosition
|
||||
|
|
|
@ -30,6 +30,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.view.ViewTreeObserver
|
||||
import android.widget.*
|
||||
import jp.juggler.subwaytooter.R.string.status
|
||||
import jp.juggler.subwaytooter.api.*
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.dialog.*
|
||||
|
@ -57,7 +58,9 @@ import java.util.*
|
|||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callback {
|
||||
class ActPost : AppCompatActivity(),
|
||||
View.OnClickListener,
|
||||
PostAttachment.Callback {
|
||||
|
||||
companion object {
|
||||
internal val log = LogCategory("ActPost")
|
||||
|
@ -166,6 +169,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
|||
private const val STATE_MUSHROOM_END = "mushroom_end"
|
||||
private const val STATE_REDRAFT_STATUS_ID = "redraft_status_id"
|
||||
private const val STATE_URI_CAMERA_IMAGE = "uri_camera_image"
|
||||
private const val STATE_TIME_SCHEDULE = "time_schedule"
|
||||
|
||||
fun open(
|
||||
activity : Activity,
|
||||
|
@ -256,6 +260,10 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
|||
private lateinit var ivReply : MyNetworkImageView
|
||||
private lateinit var scrollView : ScrollView
|
||||
|
||||
private lateinit var tvSchedule : TextView
|
||||
private lateinit var ibSchedule : ImageButton
|
||||
private lateinit var ibScheduleReset : ImageButton
|
||||
|
||||
internal lateinit var pref : SharedPreferences
|
||||
internal lateinit var app_state : AppState
|
||||
private lateinit var post_helper : PostHelper
|
||||
|
@ -268,6 +276,8 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
|||
|
||||
private var redraft_status_id : EntityId? = null
|
||||
|
||||
private var timeSchedule = 0L
|
||||
|
||||
private val text_watcher : TextWatcher = object : TextWatcher {
|
||||
override fun beforeTextChanged(charSequence : CharSequence, i : Int, i1 : Int, i2 : Int) {
|
||||
|
||||
|
@ -308,7 +318,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
|||
private var mushroom_end : Int = 0
|
||||
|
||||
private val link_click_listener : MyClickableSpanClickCallback = { _, span ->
|
||||
App1.openBrowser(this@ActPost,span.url)
|
||||
App1.openBrowser(this@ActPost, span.url)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
@ -327,6 +337,8 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
|||
R.id.btnMore -> performMore()
|
||||
R.id.btnPlugin -> openMushroom()
|
||||
R.id.btnEmojiPicker -> post_helper.openEmojiPickerFromMore()
|
||||
R.id.ibSchedule -> performSchedule()
|
||||
R.id.ibScheduleReset -> resetSchedule()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -415,6 +427,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
|||
mushroom_start = savedInstanceState.getInt(STATE_MUSHROOM_START, 0)
|
||||
mushroom_end = savedInstanceState.getInt(STATE_MUSHROOM_END, 0)
|
||||
redraft_status_id = EntityId.from(savedInstanceState, STATE_REDRAFT_STATUS_ID)
|
||||
timeSchedule = savedInstanceState.getLong(STATE_TIME_SCHEDULE, 0L)
|
||||
|
||||
savedInstanceState.getString(STATE_URI_CAMERA_IMAGE).mayUri()?.let {
|
||||
uriCameraImage = it
|
||||
|
@ -724,6 +737,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
|||
showReplyTo()
|
||||
showEnquete()
|
||||
showQuotedRenote()
|
||||
showSchedule()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
@ -741,6 +755,9 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
|||
outState.putInt(STATE_MUSHROOM_START, mushroom_start)
|
||||
outState.putInt(STATE_MUSHROOM_END, mushroom_end)
|
||||
redraft_status_id?.putTo(outState, STATE_REDRAFT_STATUS_ID)
|
||||
|
||||
outState.putLong(STATE_TIME_SCHEDULE, timeSchedule)
|
||||
|
||||
if(uriCameraImage != null) {
|
||||
outState.putString(STATE_URI_CAMERA_IMAGE, uriCameraImage.toString())
|
||||
}
|
||||
|
@ -894,6 +911,13 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
|||
btnRemoveReply = findViewById(R.id.btnRemoveReply)
|
||||
ivReply = findViewById(R.id.ivReply)
|
||||
|
||||
tvSchedule = findViewById(R.id.tvSchedule)
|
||||
ibSchedule = findViewById(R.id.ibSchedule)
|
||||
ibScheduleReset= findViewById(R.id.ibScheduleReset)
|
||||
|
||||
ibSchedule.setOnClickListener(this)
|
||||
ibScheduleReset.setOnClickListener(this)
|
||||
|
||||
account_list = SavedAccount.loadAccountList(this@ActPost)
|
||||
SavedAccount.sort(account_list)
|
||||
|
||||
|
@ -969,18 +993,19 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
|||
private var lastInstanceTask : TootTaskRunner? = null
|
||||
|
||||
private fun getMaxCharCount() : Int {
|
||||
|
||||
|
||||
val account = account
|
||||
|
||||
when{
|
||||
account == null || account.isPseudo -> {}
|
||||
|
||||
else->{
|
||||
|
||||
when {
|
||||
account == null || account.isPseudo -> {
|
||||
}
|
||||
|
||||
else -> {
|
||||
val info = account.instance
|
||||
|
||||
|
||||
// 情報がないか古いなら再取得
|
||||
if(info == null || System.currentTimeMillis() - info.time_parse >= 300000L) {
|
||||
|
||||
|
||||
// 同時に実行するタスクは1つまで
|
||||
var lastTask = lastInstanceTask
|
||||
if(lastTask?.isActive != true) {
|
||||
|
@ -990,15 +1015,16 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
|||
var newInfo : TootInstance? = null
|
||||
|
||||
override fun background(client : TootApiClient) : TootApiResult? {
|
||||
val result = if( account.isMisskey){
|
||||
val result = if(account.isMisskey) {
|
||||
client.request(
|
||||
"/api/meta",
|
||||
account.putMisskeyApiToken().toPostRequestBuilder()
|
||||
)
|
||||
}else{
|
||||
} else {
|
||||
client.request("/api/v1/instance")
|
||||
}
|
||||
newInfo = TootParser(this@ActPost, account).instance(result?.jsonObject)
|
||||
newInfo =
|
||||
TootParser(this@ActPost, account).instance(result?.jsonObject)
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -1299,7 +1325,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
|||
override fun background(client : TootApiClient) : TootApiResult? {
|
||||
try {
|
||||
val result = client.request(
|
||||
"/api/v1/media/${attachment.id}" ,
|
||||
"/api/v1/media/${attachment.id}",
|
||||
JSONObject()
|
||||
.put("focus", "%.2f,%.2f".format(x, y))
|
||||
.toPutRequestBuilder()
|
||||
|
@ -1809,7 +1835,6 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
|||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
val result = client.request(
|
||||
"/api/v1/media",
|
||||
|
@ -2090,16 +2115,30 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
|||
|
||||
post_helper.useQuotedRenote = cbQuoteRenote.isChecked
|
||||
|
||||
post_helper.post(account) { target_account, status ->
|
||||
val data = Intent()
|
||||
data.putExtra(EXTRA_POSTED_ACCT, target_account.acct)
|
||||
status.id.putTo(data, EXTRA_POSTED_STATUS_ID)
|
||||
redraft_status_id?.putTo(data, EXTRA_POSTED_REDRAFT_ID)
|
||||
status.in_reply_to_id?.putTo(data, EXTRA_POSTED_REPLY_ID)
|
||||
setResult(RESULT_OK, data)
|
||||
isPostComplete = true
|
||||
this@ActPost.finish()
|
||||
}
|
||||
post_helper.scheduledAt = timeSchedule
|
||||
|
||||
post_helper.post(account,callback=object:PostHelper.PostCompleteCallback{
|
||||
override fun onPostComplete(
|
||||
target_account : SavedAccount,
|
||||
status : TootStatus
|
||||
) {
|
||||
val data = Intent()
|
||||
data.putExtra(EXTRA_POSTED_ACCT, target_account.acct)
|
||||
status.id.putTo(data, EXTRA_POSTED_STATUS_ID)
|
||||
redraft_status_id?.putTo(data, EXTRA_POSTED_REDRAFT_ID)
|
||||
status.in_reply_to_id?.putTo(data, EXTRA_POSTED_REPLY_ID)
|
||||
setResult(RESULT_OK, data)
|
||||
isPostComplete = true
|
||||
this@ActPost.finish()
|
||||
}
|
||||
|
||||
override fun onScheduledPostComplete(target_account : SavedAccount) {
|
||||
showToast(this@ActPost,false,getString(R.string.scheduled_status_sent))
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
isPostComplete = true
|
||||
this@ActPost.finish()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun showQuotedRenote() {
|
||||
|
@ -2549,4 +2588,23 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
|||
|
||||
true
|
||||
}
|
||||
|
||||
private fun showSchedule() {
|
||||
tvSchedule.text = when(timeSchedule) {
|
||||
0L -> getString(R.string.unspecified)
|
||||
else -> TootStatus.formatTime(this, timeSchedule, Pref.bpRelativeTimestamp(pref))
|
||||
}
|
||||
}
|
||||
|
||||
private fun performSchedule() {
|
||||
DlgDateTime(this).open(timeSchedule) { t ->
|
||||
timeSchedule = t
|
||||
showSchedule()
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetSchedule(){
|
||||
timeSchedule = 0L
|
||||
showSchedule()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -218,6 +218,7 @@ class Column(
|
|||
internal const val TYPE_LOCAL_AROUND = 29
|
||||
internal const val TYPE_FEDERATED_AROUND = 30
|
||||
internal const val TYPE_ACCOUNT_AROUND = 31
|
||||
internal const val TYPE_SCHEDULED_STATUS = 33
|
||||
|
||||
internal const val TAB_STATUS = 0
|
||||
internal const val TAB_FOLLOWING = 1
|
||||
|
@ -279,6 +280,7 @@ class Column(
|
|||
TYPE_LIST_TL -> context.getString(R.string.list_timeline)
|
||||
TYPE_DIRECT_MESSAGES -> context.getString(R.string.direct_messages)
|
||||
TYPE_TREND_TAG -> context.getString(R.string.trend_tag)
|
||||
TYPE_SCHEDULED_STATUS ->context.getString(R.string.scheduled_status)
|
||||
else -> "?"
|
||||
}
|
||||
}
|
||||
|
@ -317,6 +319,7 @@ class Column(
|
|||
TYPE_LIST_TL -> R.attr.ic_list_tl
|
||||
TYPE_DIRECT_MESSAGES -> R.attr.ic_mail
|
||||
TYPE_TREND_TAG -> R.attr.ic_hashtag
|
||||
TYPE_SCHEDULED_STATUS -> R.attr.ic_timer
|
||||
else -> R.attr.ic_info
|
||||
}
|
||||
}
|
||||
|
@ -803,8 +806,8 @@ class Column(
|
|||
|
||||
when(column_type) {
|
||||
|
||||
TYPE_CONVERSATION, TYPE_BOOSTED_BY, TYPE_FAVOURITED_BY, TYPE_LOCAL_AROUND, TYPE_FEDERATED_AROUND, TYPE_ACCOUNT_AROUND -> status_id =
|
||||
when(isMisskey) {
|
||||
TYPE_CONVERSATION, TYPE_BOOSTED_BY, TYPE_FAVOURITED_BY, TYPE_LOCAL_AROUND, TYPE_FEDERATED_AROUND, TYPE_ACCOUNT_AROUND ->
|
||||
status_id = when(isMisskey) {
|
||||
true -> EntityId.mayNull(src.parseString(KEY_STATUS_ID))
|
||||
else -> EntityId.mayNull(src.parseLong(KEY_STATUS_ID))
|
||||
}
|
||||
|
@ -2582,6 +2585,18 @@ class Column(
|
|||
|
||||
}
|
||||
|
||||
private fun getScheduledStatuses(client:TootApiClient):TootApiResult?{
|
||||
val result = client.request("/api/v1/scheduled_statuses")
|
||||
val src = parser.statusList(result?.jsonArray)
|
||||
list_tmp = addWithFilterStatus(list_tmp,src)
|
||||
|
||||
// TODO: paging?
|
||||
idOld = null
|
||||
idRecent = null
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
override fun doInBackground(vararg unused : Void) : TootApiResult? {
|
||||
ctStarted.set(true)
|
||||
|
||||
|
@ -3268,6 +3283,8 @@ class Column(
|
|||
return result
|
||||
}
|
||||
|
||||
TYPE_SCHEDULED_STATUS -> return getScheduledStatuses(client)
|
||||
|
||||
else -> return getStatuses(client, makeHomeTlUrl())
|
||||
}
|
||||
} finally {
|
||||
|
@ -6199,6 +6216,7 @@ class Column(
|
|||
TYPE_CONVERSATION,
|
||||
TYPE_LIST_LIST,
|
||||
TYPE_TREND_TAG,
|
||||
TYPE_SCHEDULED_STATUS,
|
||||
TYPE_FOLLOW_SUGGESTION -> true
|
||||
|
||||
TYPE_LIST_MEMBER,
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
package jp.juggler.subwaytooter.dialog
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.Button
|
||||
import android.widget.DatePicker
|
||||
import android.widget.TimePicker
|
||||
import jp.juggler.subwaytooter.R
|
||||
import java.util.*
|
||||
import android.provider.Settings.System.TIME_12_24
|
||||
|
||||
class DlgDateTime(
|
||||
val activity : Activity
|
||||
) : DatePicker.OnDateChangedListener, View.OnClickListener {
|
||||
|
||||
private lateinit var datePicker : DatePicker
|
||||
private lateinit var timePicker : TimePicker
|
||||
private lateinit var btnCancel : Button
|
||||
private lateinit var btnOk : Button
|
||||
private lateinit var dialog : Dialog
|
||||
|
||||
private lateinit var callback : (Long) -> Unit
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
fun open(initialValue : Long, callback : (Long) -> Unit) {
|
||||
this.callback = callback
|
||||
|
||||
val view = activity.layoutInflater.inflate(R.layout.dlg_date_time, null, false)
|
||||
|
||||
datePicker = view.findViewById(R.id.datePicker)
|
||||
timePicker = view.findViewById(R.id.timePicker)
|
||||
btnCancel = view.findViewById(R.id.btnCancel)
|
||||
btnOk = view.findViewById(R.id.btnOk)
|
||||
|
||||
val c = GregorianCalendar.getInstance(TimeZone.getDefault())
|
||||
c.timeInMillis = when(initialValue) {
|
||||
0L -> System.currentTimeMillis() + 10 * 60000L
|
||||
else -> initialValue
|
||||
}
|
||||
datePicker.firstDayOfWeek = Calendar.MONDAY
|
||||
datePicker.init(
|
||||
c.get(Calendar.YEAR),
|
||||
c.get(Calendar.MONTH),
|
||||
c.get(Calendar.DAY_OF_MONTH),
|
||||
this
|
||||
)
|
||||
|
||||
if(Build.VERSION.SDK_INT >= 23) {
|
||||
timePicker.hour = c.get(Calendar.HOUR_OF_DAY)
|
||||
timePicker.minute = c.get(Calendar.MINUTE)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
timePicker.currentHour = c.get(Calendar.HOUR_OF_DAY)
|
||||
@Suppress("DEPRECATION")
|
||||
timePicker.currentMinute = c.get(Calendar.MINUTE)
|
||||
}
|
||||
|
||||
timePicker.setIs24HourView(
|
||||
when(Settings.System.getString(activity.contentResolver, Settings.System.TIME_12_24)) {
|
||||
"12" -> false
|
||||
else -> true
|
||||
}
|
||||
)
|
||||
|
||||
btnCancel.setOnClickListener(this)
|
||||
btnOk.setOnClickListener(this)
|
||||
|
||||
dialog = Dialog(activity)
|
||||
dialog.setContentView(view)
|
||||
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 -> {
|
||||
dialog.dismiss()
|
||||
callback(getTime())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDateChanged(
|
||||
view : DatePicker,
|
||||
year : Int,
|
||||
monthOfYear : Int,
|
||||
dayOfMonth : Int
|
||||
) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
private fun getTime() : Long {
|
||||
val y = datePicker.year
|
||||
val m = datePicker.month
|
||||
val d = datePicker.dayOfMonth
|
||||
|
||||
val h : Int
|
||||
val j : Int
|
||||
if(Build.VERSION.SDK_INT >= 23) {
|
||||
h = timePicker.hour
|
||||
j = timePicker.minute
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
h = timePicker.currentHour
|
||||
@Suppress("DEPRECATION")
|
||||
j = timePicker.currentMinute
|
||||
}
|
||||
|
||||
val c = GregorianCalendar.getInstance(TimeZone.getDefault())
|
||||
c.set(y, m, d, h, j)
|
||||
c.set(Calendar.SECOND, 0)
|
||||
c.set(Calendar.MILLISECOND, 0)
|
||||
return c.timeInMillis
|
||||
}
|
||||
|
||||
}
|
|
@ -202,7 +202,7 @@ object EmojiDecoder {
|
|||
|
||||
private interface ShortCodeSplitterCallback {
|
||||
fun onString(part : String) // shortcode以外の文字列
|
||||
fun onShortCode(part : String, name : String) // part : ":shortcode:", name : "shortcode"
|
||||
fun onShortCode(prevCodePoint:Int,part : String, name : String) // part : ":shortcode:", name : "shortcode"
|
||||
}
|
||||
|
||||
private fun splitShortCode(
|
||||
|
@ -256,9 +256,16 @@ object EmojiDecoder {
|
|||
continue
|
||||
}
|
||||
|
||||
val prevCodePoint = if(start >0){
|
||||
s.codePointBefore(start)
|
||||
}else{
|
||||
0x20
|
||||
}
|
||||
|
||||
callback.onShortCode(
|
||||
s.substring(start, posEndColon + 1) // ":shortcode:"
|
||||
, s.substring(start + 1, posEndColon) // "shortcode"
|
||||
prevCodePoint,
|
||||
s.substring(start, posEndColon + 1), // ":shortcode:"
|
||||
s.substring(start + 1, posEndColon) // "shortcode"
|
||||
)
|
||||
|
||||
i = posEndColon + 1 // コロンの次の位置
|
||||
|
@ -280,7 +287,7 @@ object EmojiDecoder {
|
|||
builder.addUnicodeString(part)
|
||||
}
|
||||
|
||||
override fun onShortCode(part : String, name : String) {
|
||||
override fun onShortCode(prevCodePoint:Int,part : String, name : String) {
|
||||
|
||||
// フレニコのプロフ絵文字
|
||||
if(emojiMapProfile != null && name.length >= 2 && name[0] == '@') {
|
||||
|
@ -346,7 +353,7 @@ object EmojiDecoder {
|
|||
sb.append(part)
|
||||
}
|
||||
|
||||
override fun onShortCode(part : String, name : String) {
|
||||
override fun onShortCode(prevCodePoint:Int,part : String, name : String) {
|
||||
|
||||
// カスタム絵文字にマッチするなら変換しない
|
||||
val emojiCustom = emojiMapCustom?.get(name)
|
||||
|
|
|
@ -56,19 +56,25 @@ class PostHelper(
|
|||
|
||||
}
|
||||
|
||||
interface PostCompleteCallback {
|
||||
fun onPostComplete(target_account : SavedAccount, status : TootStatus)
|
||||
fun onScheduledPostComplete(target_account : SavedAccount)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 投稿機能
|
||||
|
||||
var content : String? = null
|
||||
var spoiler_text : String? = null
|
||||
var visibility : TootVisibility = TootVisibility.Public
|
||||
var bNSFW : Boolean = false
|
||||
var bNSFW = false
|
||||
var in_reply_to_id : EntityId? = null
|
||||
var attachment_list : ArrayList<PostAttachment>? = null
|
||||
var enquete_items : ArrayList<String>? = null
|
||||
var emojiMapCustom : HashMap<String, CustomEmoji>? = null
|
||||
var redraft_status_id : EntityId? = null
|
||||
var useQuotedRenote : Boolean = false
|
||||
var useQuotedRenote = false
|
||||
var scheduledAt = 0L
|
||||
|
||||
private var last_post_tapped : Long = 0L
|
||||
|
||||
|
@ -88,6 +94,7 @@ class PostHelper(
|
|||
val attachment_list = this.attachment_list
|
||||
val enquete_items = this.enquete_items
|
||||
val visibility = this.visibility
|
||||
val scheduledAt = this.scheduledAt
|
||||
|
||||
val hasAttachment = attachment_list?.isNotEmpty() ?: false
|
||||
|
||||
|
@ -224,6 +231,8 @@ class PostHelper(
|
|||
|
||||
val parser = TootParser(activity, account)
|
||||
|
||||
var scheduledStatusSucceeded = false
|
||||
|
||||
fun getInstanceInformation(client : TootApiClient) : TootApiResult? {
|
||||
val result = if(account.isMisskey) {
|
||||
val params = JSONObject().apply {
|
||||
|
@ -408,6 +417,10 @@ class PostHelper(
|
|||
}
|
||||
}
|
||||
|
||||
if(scheduledAt != 0L) {
|
||||
return TootApiResult("misskey has no scheduled status API")
|
||||
}
|
||||
|
||||
} else {
|
||||
json.put(
|
||||
"status",
|
||||
|
@ -456,6 +469,25 @@ class PostHelper(
|
|||
json.put("enquete_items", array)
|
||||
}
|
||||
|
||||
if(scheduledAt != 0L) {
|
||||
if(! instance.versionGE(TootInstance.VERSION_2_7_0_rc1)) {
|
||||
return TootApiResult("Mastodon pre-2.7.0 has no scheduled status API")
|
||||
}
|
||||
// UTCの日時を渡す
|
||||
val c = GregorianCalendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||
c.timeInMillis = scheduledAt
|
||||
val sv = String.format(
|
||||
"%d-%02d-%02d %02d:%02d:%02d",
|
||||
c.get(Calendar.YEAR),
|
||||
c.get(Calendar.MONTH) + 1,
|
||||
c.get(Calendar.DAY_OF_MONTH),
|
||||
c.get(Calendar.HOUR_OF_DAY),
|
||||
c.get(Calendar.MINUTE),
|
||||
c.get(Calendar.SECOND)
|
||||
)
|
||||
json.put("scheduled_at", sv)
|
||||
}
|
||||
|
||||
}
|
||||
} catch(ex : JSONException) {
|
||||
log.trace(ex)
|
||||
|
@ -478,6 +510,14 @@ class PostHelper(
|
|||
client.request("/api/v1/statuses", request_builder)
|
||||
}
|
||||
|
||||
val jsonObject = result?.jsonObject
|
||||
|
||||
if(scheduledAt != 0L && jsonObject != null) {
|
||||
// {"id":"3","scheduled_at":"2019-01-06T07:08:00.000Z","media_attachments":[]}
|
||||
scheduledStatusSucceeded = true
|
||||
return result
|
||||
}
|
||||
|
||||
val status = parser.status(
|
||||
if(isMisskey) {
|
||||
result?.jsonObject?.optJSONObject("createdNote") ?: result?.jsonObject
|
||||
|
@ -513,16 +553,23 @@ class PostHelper(
|
|||
}
|
||||
|
||||
override fun handleResult(result : TootApiResult?) {
|
||||
result ?: return // cancelled.
|
||||
|
||||
result ?: return
|
||||
val status = this.status
|
||||
if(status != null) {
|
||||
// 連投してIdempotency が同じだった場合もエラーにはならず、ここを通る
|
||||
callback(account, status)
|
||||
} else {
|
||||
showToast(activity, true, result.error)
|
||||
when {
|
||||
status != null -> {
|
||||
// 連投してIdempotency が同じだった場合もエラーにはならず、ここを通る
|
||||
callback.onPostComplete(account, status)
|
||||
return
|
||||
}
|
||||
|
||||
scheduledStatusSucceeded -> {
|
||||
callback.onScheduledPostComplete(account)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
else -> showToast(activity, true, result.error)
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
)
|
||||
|
@ -711,13 +758,13 @@ class PostHelper(
|
|||
val dst = ArrayList<CharSequence>()
|
||||
|
||||
if(instance?.isNotEmpty() == true) {
|
||||
|
||||
|
||||
val custom_list = App1.custom_emoji_lister.getListWithAliases(
|
||||
instance,
|
||||
isMisskey,
|
||||
onEmojiListLoad
|
||||
)
|
||||
|
||||
|
||||
if(custom_list != null) {
|
||||
|
||||
for(item in custom_list) {
|
||||
|
|
|
@ -2,8 +2,6 @@ package jp.juggler.subwaytooter.util
|
|||
|
||||
import android.content.DialogInterface
|
||||
import jp.juggler.subwaytooter.api.TootApiResult
|
||||
import jp.juggler.subwaytooter.api.entity.TootAccount
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
@ -18,6 +16,5 @@ typealias SavedAccountCallback = (ai : SavedAccount) -> Unit
|
|||
|
||||
typealias DialogInterfaceCallback = (dialog: DialogInterface) -> Unit
|
||||
|
||||
typealias PostCompleteCallback = (target_account : SavedAccount, status : TootStatus) -> Unit
|
||||
|
||||
typealias ProgressResponseBodyCallback = (bytesRead : Long, bytesTotal : Long)->Unit
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M15,1L9,1v2h6L15,1zM11,14h2L13,8h-2v6zM19.03,7.39l1.42,-1.42c-0.43,-0.51 -0.9,-0.99 -1.41,-1.41l-1.42,1.42C16.07,4.74 14.12,4 12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9 9,-4.03 9,-9c0,-2.12 -0.74,-4.07 -1.97,-5.61zM12,20c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
|
||||
</vector>
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M15,1L9,1v2h6L15,1zM11,14h2L13,8h-2v6zM19.03,7.39l1.42,-1.42c-0.43,-0.51 -0.9,-0.99 -1.41,-1.41l-1.42,1.42C16.07,4.74 14.12,4 12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9 9,-4.03 9,-9c0,-2.12 -0.74,-4.07 -1.97,-5.61zM12,20c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
|
||||
</vector>
|
|
@ -225,7 +225,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorPostFormBackground"
|
||||
android:layout_marginBottom="32dp"
|
||||
|
||||
>
|
||||
|
||||
<jp.juggler.subwaytooter.view.MyEditText
|
||||
|
@ -240,6 +240,44 @@
|
|||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:text="@string/scheduled_status"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:id="@+id/tvSchedule"
|
||||
android:gravity="center"
|
||||
android:layout_gravity="center_vertical"
|
||||
/>
|
||||
<ImageButton
|
||||
android:id="@+id/ibSchedule"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:contentDescription="@string/edit"
|
||||
android:src="?attr/ic_edit"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
/>
|
||||
<ImageButton
|
||||
android:id="@+id/ibScheduleReset"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:contentDescription="@string/reset"
|
||||
android:src="?attr/btn_close"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<CheckBox
|
||||
|
@ -247,6 +285,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/make_enquete"
|
||||
android:layout_marginTop="32dp"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
<DatePicker
|
||||
android:id="@+id/datePicker"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:datePickerMode="spinner"
|
||||
android:calendarViewShown="false"
|
||||
android:spinnersShown="true"
|
||||
android:layout_gravity="center_horizontal"
|
||||
/>
|
||||
|
||||
<TimePicker
|
||||
android:id="@+id/timePicker"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:timePickerMode="spinner"
|
||||
android:layout_gravity="center_horizontal"
|
||||
/>
|
||||
|
||||
<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>
|
|
@ -117,6 +117,10 @@
|
|||
android:icon="?attr/ic_domain_block"
|
||||
android:title="@string/blocked_domains"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_scheduled_statuses_list"
|
||||
android:icon="?attr/ic_timer"
|
||||
android:title="@string/scheduled_status"/>
|
||||
<!--<item-->
|
||||
<!--android:id="@+id/nav_add_reports"-->
|
||||
<!--android:icon="?attr/btn_report"-->
|
||||
|
|
|
@ -833,5 +833,8 @@
|
|||
<string name="agree_terms">サービスの規約に同意します</string>
|
||||
<string name="pseudo_account_cant_get_follow_list">疑似アカウントではフォローリストを読めません</string>
|
||||
<string name="reaction_remove_confirm">あなたのリアクション \"%1$s\"を削除しますか?</string>
|
||||
<string name="scheduled_status">予約投稿</string>
|
||||
<string name="unspecified">指定なし</string>
|
||||
<string name="scheduled_status_sent">予約投稿を送信しました。</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -157,5 +157,6 @@
|
|||
<attr name="ic_local_lock_open" format="reference" />
|
||||
<attr name="ic_local_ltl" format="reference" />
|
||||
<attr name="ic_music" format="reference" />
|
||||
<attr name="ic_timer" format="reference" />
|
||||
|
||||
</resources>
|
|
@ -853,5 +853,8 @@
|
|||
<string name="username_not_need_atmark">The user name must not contains \"@\" or \"/\".</string>
|
||||
<string name="pseudo_account_cant_get_follow_list">Can\'t read follow list from pseudo account.</string>
|
||||
<string name="reaction_remove_confirm">Remove your reaction \"%1$s\"?</string>
|
||||
<string name="scheduled_status">Scheduled post</string>
|
||||
<string name="unspecified">Unspecified</string>
|
||||
<string name="scheduled_status_sent">Scheduled status was sent.</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -117,6 +117,7 @@
|
|||
<item name="ic_local_lock_open">@drawable/ic_local_lock_open</item>
|
||||
<item name="ic_local_ltl">@drawable/ic_local_ltl</item>
|
||||
<item name="ic_music">@drawable/ic_music</item>
|
||||
<item name="ic_timer">@drawable/ic_timer</item>
|
||||
|
||||
</style>
|
||||
|
||||
|
@ -244,6 +245,7 @@
|
|||
<item name="ic_local_lock_open">@drawable/ic_local_lock_open_dark</item>
|
||||
<item name="ic_local_ltl">@drawable/ic_local_ltl_dark</item>
|
||||
<item name="ic_music">@drawable/ic_music_dark</item>
|
||||
<item name="ic_timer">@drawable/ic_timer_dark</item>
|
||||
|
||||
</style>
|
||||
|
||||
|
|
Loading…
Reference in New Issue