Implemented basic draft functionality

This commit is contained in:
Christian Basler 2018-02-13 07:24:24 +01:00
parent 72213a53a5
commit f481914a65
4 changed files with 201 additions and 103 deletions

View File

@ -53,6 +53,7 @@ class ComposeMessageActivity : AppCompatActivity() {
} }
companion object { companion object {
const val EXTRA_DRAFT = "ch.dissem.abit.Message.DRAFT"
const val EXTRA_IDENTITY = "ch.dissem.abit.Message.SENDER" const val EXTRA_IDENTITY = "ch.dissem.abit.Message.SENDER"
const val EXTRA_RECIPIENT = "ch.dissem.abit.Message.RECIPIENT" const val EXTRA_RECIPIENT = "ch.dissem.abit.Message.RECIPIENT"
const val EXTRA_SUBJECT = "ch.dissem.abit.Message.SUBJECT" const val EXTRA_SUBJECT = "ch.dissem.abit.Message.SUBJECT"

View File

@ -17,6 +17,7 @@
package ch.dissem.apps.abit package ch.dissem.apps.abit
import android.app.Activity.RESULT_OK import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
@ -25,6 +26,7 @@ import android.widget.AdapterView
import android.widget.Toast import android.widget.Toast
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_BROADCAST import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_BROADCAST
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_CONTENT import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_CONTENT
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_DRAFT
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_ENCODING import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_ENCODING
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_IDENTITY import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_IDENTITY
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_PARENT import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_PARENT
@ -38,6 +40,9 @@ import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.Plaintext import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST
import ch.dissem.bitmessage.entity.Plaintext.Type.MSG import ch.dissem.bitmessage.entity.Plaintext.Type.MSG
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
import ch.dissem.bitmessage.entity.valueobject.Label
import ch.dissem.bitmessage.entity.valueobject.extended.Message import ch.dissem.bitmessage.entity.valueobject.extended.Message
import kotlinx.android.synthetic.main.fragment_compose_message.* import kotlinx.android.synthetic.main.fragment_compose_message.*
@ -52,34 +57,49 @@ class ComposeMessageFragment : Fragment() {
private var broadcast: Boolean = false private var broadcast: Boolean = false
private var encoding: Plaintext.Encoding = Plaintext.Encoding.SIMPLE private var encoding: Plaintext.Encoding = Plaintext.Encoding.SIMPLE
private var parent: Plaintext? = null private val parents = mutableListOf<InventoryVector>()
private var draft: Plaintext? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
arguments?.let { arguments -> arguments?.apply {
var id = arguments.getSerializable(EXTRA_IDENTITY) as? BitmessageAddress val draft = getSerializable(EXTRA_DRAFT) as Plaintext?
if (context != null && (id == null || id.privateKey == null)) { if (draft != null) {
id = Singleton.getIdentity(context!!) this@ComposeMessageFragment.draft = draft
} identity = draft.from
if (id?.privateKey != null) { recipient = draft.to
identity = id subject = draft.subject ?: ""
content = draft.text ?: ""
encoding = draft.encoding ?: Plaintext.Encoding.SIMPLE
parents.addAll(draft.parents)
} else { } else {
throw IllegalStateException("No identity set for ComposeMessageFragment") var id = getSerializable(EXTRA_IDENTITY) as? BitmessageAddress
} if (context != null && (id == null || id.privateKey == null)) {
broadcast = arguments.getBoolean(EXTRA_BROADCAST, false) id = Singleton.getIdentity(context!!)
if (arguments.containsKey(EXTRA_RECIPIENT)) { }
recipient = arguments.getSerializable(EXTRA_RECIPIENT) as BitmessageAddress if (id?.privateKey != null) {
} identity = id
if (arguments.containsKey(EXTRA_SUBJECT)) { } else {
subject = arguments.getString(EXTRA_SUBJECT) throw IllegalStateException("No identity set for ComposeMessageFragment")
} }
if (arguments.containsKey(EXTRA_CONTENT)) { broadcast = getBoolean(EXTRA_BROADCAST, false)
content = arguments.getString(EXTRA_CONTENT) if (containsKey(EXTRA_RECIPIENT)) {
} recipient = getSerializable(EXTRA_RECIPIENT) as BitmessageAddress
encoding = arguments.getSerializable(EXTRA_ENCODING) as? Plaintext.Encoding ?: Plaintext.Encoding.SIMPLE }
if (containsKey(EXTRA_SUBJECT)) {
subject = getString(EXTRA_SUBJECT)
}
if (containsKey(EXTRA_CONTENT)) {
content = getString(EXTRA_CONTENT)
}
encoding = getSerializable(EXTRA_ENCODING) as? Plaintext.Encoding ?:
Plaintext.Encoding.SIMPLE
if (arguments.containsKey(EXTRA_PARENT)) { if (containsKey(EXTRA_PARENT)) {
parent = arguments.getSerializable(EXTRA_PARENT) as Plaintext val parent = getSerializable(EXTRA_PARENT) as Plaintext
parent.inventoryVector?.let { parents.add(it) }
}
} }
} ?: { } ?: {
throw IllegalStateException("No identity set for ComposeMessageFragment") throw IllegalStateException("No identity set for ComposeMessageFragment")
@ -87,9 +107,10 @@ class ComposeMessageFragment : Fragment() {
setHasOptionsMenu(true) setHasOptionsMenu(true)
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(
savedInstanceState: Bundle?): View = inflater: LayoutInflater, container: ViewGroup?,
inflater.inflate(R.layout.fragment_compose_message, container, false) savedInstanceState: Bundle?
): View = inflater.inflate(R.layout.fragment_compose_message, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -99,13 +120,20 @@ class ComposeMessageFragment : Fragment() {
} else { } else {
val adapter = ContactAdapter(context!!) val adapter = ContactAdapter(context!!)
recipient_input.setAdapter(adapter) recipient_input.setAdapter(adapter)
recipient_input.onItemClickListener = AdapterView.OnItemClickListener { _, _, pos, _ -> adapter.getItem(pos) } recipient_input.onItemClickListener =
AdapterView.OnItemClickListener { _, _, pos, _ -> adapter.getItem(pos) }
recipient_input.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { recipient_input.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) { override fun onItemSelected(
parent: AdapterView<*>,
view: View,
position: Int,
id: Long
) {
recipient = adapter.getItem(position) recipient = adapter.getItem(position)
} }
override fun onNothingSelected(parent: AdapterView<*>) = Unit // leave current selection override fun onNothingSelected(parent: AdapterView<*>) =
Unit // leave current selection
} }
recipient?.let { recipient_input.setText(it.toString()) } recipient?.let { recipient_input.setText(it.toString()) }
} }
@ -146,16 +174,15 @@ class ComposeMessageFragment : Fragment() {
} }
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) = if (requestCode == 0 && data != null && resultCode == RESULT_OK) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) =
encoding = data.getSerializableExtra(EXTRA_ENCODING) as Plaintext.Encoding if (requestCode == 0 && data != null && resultCode == RESULT_OK) {
} else { encoding = data.getSerializableExtra(EXTRA_ENCODING) as Plaintext.Encoding
super.onActivityResult(requestCode, resultCode, data) } else {
} super.onActivityResult(requestCode, resultCode, data)
}
private fun send() { private fun build(ctx: Context): Plaintext {
val builder: Plaintext.Builder val builder: Plaintext.Builder
val ctx = activity ?: throw IllegalStateException("Fragment is not attached to an activity")
val bmc = Singleton.getBitmessageContext(ctx)
if (broadcast) { if (broadcast) {
builder = Plaintext.Builder(BROADCAST).from(identity) builder = Plaintext.Builder(BROADCAST).from(identity)
} else { } else {
@ -175,42 +202,68 @@ class ComposeMessageFragment : Fragment() {
} }
} }
if (recipient == null) {
Toast.makeText(context, R.string.error_msg_recipient_missing, Toast.LENGTH_LONG).show()
return
}
builder = Plaintext.Builder(MSG) builder = Plaintext.Builder(MSG)
.from(identity) .from(identity)
.to(recipient) .to(recipient)
} }
if (!Preferences.requestAcknowledgements(ctx)) { if (!Preferences.requestAcknowledgements(ctx)) {
builder.preventAck() builder.preventAck()
} }
when (encoding) { when (encoding) {
Plaintext.Encoding.SIMPLE -> builder.message( Plaintext.Encoding.SIMPLE -> builder.message(
subject_input.text.toString(), subject_input.text.toString(),
body_input.text.toString() body_input.text.toString()
) )
Plaintext.Encoding.EXTENDED -> builder.message( Plaintext.Encoding.EXTENDED -> builder.message(
Message.Builder() ExtendedEncoding(
.subject(subject_input.text.toString()) Message(
.body(body_input.text.toString()) subject = subject_input.text.toString(),
.addParent(parent) body = body_input.text.toString(),
.build() parents = parents,
files = emptyList()
)
)
) )
else -> { else -> {
Toast.makeText( Toast.makeText(
ctx, ctx,
ctx.getString(R.string.error_unsupported_encoding, encoding), ctx.getString(R.string.error_unsupported_encoding, encoding),
Toast.LENGTH_LONG Toast.LENGTH_LONG
).show() ).show()
builder.message( builder.message(
subject_input.text.toString(), subject_input.text.toString(),
body_input.text.toString() body_input.text.toString()
) )
} }
} }
bmc.send(builder.build()) draft?.id?.let { builder.id(it) }
return builder.build()
}
override fun onPause() {
if (draft?.labels?.any { it.type == Label.Type.DRAFT } != false) {
context?.let { ctx ->
draft = build(ctx).also { msg ->
Singleton.labeler.markAsDraft(msg)
Singleton.getMessageRepository(ctx).save(msg)
}
Toast.makeText(ctx, "Message saved as draft", Toast.LENGTH_LONG).show()
} ?: throw IllegalStateException("Context is not available")
}
super.onPause()
}
private fun send() {
val ctx = activity ?: throw IllegalStateException("Fragment is not attached to an activity")
if (recipient == null) {
Toast.makeText(ctx, R.string.error_msg_recipient_missing, Toast.LENGTH_LONG)
.show()
return
}
build(ctx).let { message ->
draft = message
Singleton.getBitmessageContext(ctx).send(message)
}
ctx.finish() ctx.finish()
} }
} }

View File

@ -146,12 +146,15 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
SyncAdapter.stopSync(this) SyncAdapter.stopSync(this)
} }
if (drawer.isDrawerOpen) { if (drawer.isDrawerOpen) {
val lps = RelativeLayout.LayoutParams(ViewGroup val lps = RelativeLayout.LayoutParams(
.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) ViewGroup
lps.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM) .LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
lps.addRule(RelativeLayout.ALIGN_PARENT_LEFT) ).apply {
val margin = ((resources.displayMetrics.density * 12) as Number).toInt() addRule(RelativeLayout.ALIGN_PARENT_BOTTOM)
lps.setMargins(margin, margin, margin, margin) addRule(RelativeLayout.ALIGN_PARENT_LEFT)
val margin = ((resources.displayMetrics.density * 12) as Number).toInt()
setMargins(margin, margin, margin, margin)
}
ShowcaseView.Builder(this) ShowcaseView.Builder(this)
.withMaterialShowcase() .withMaterialShowcase()
@ -192,19 +195,23 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
private fun createDrawer(toolbar: Toolbar) { private fun createDrawer(toolbar: Toolbar) {
val profiles = ArrayList<IProfile<*>>() val profiles = ArrayList<IProfile<*>>()
profiles.add(ProfileSettingDrawerItem() profiles.add(
.withName(getString(R.string.add_identity)) ProfileSettingDrawerItem()
.withDescription(getString(R.string.add_identity_summary)) .withName(getString(R.string.add_identity))
.withIcon(IconicsDrawable(this, GoogleMaterial.Icon.gmd_add) .withDescription(getString(R.string.add_identity_summary))
.actionBar() .withIcon(
.paddingDp(5) IconicsDrawable(this, GoogleMaterial.Icon.gmd_add)
.colorRes(R.color.icons)) .actionBar()
.withIdentifier(ADD_IDENTITY.toLong()) .paddingDp(5)
.colorRes(R.color.icons)
)
.withIdentifier(ADD_IDENTITY.toLong())
) )
profiles.add(ProfileSettingDrawerItem() profiles.add(
.withName(getString(R.string.manage_identity)) ProfileSettingDrawerItem()
.withIcon(GoogleMaterial.Icon.gmd_settings) .withName(getString(R.string.manage_identity))
.withIdentifier(MANAGE_IDENTITY.toLong()) .withIcon(GoogleMaterial.Icon.gmd_settings)
.withIdentifier(MANAGE_IDENTITY.toLong())
) )
// Create the AccountHeader // Create the AccountHeader
accountHeader = AccountHeaderBuilder() accountHeader = AccountHeaderBuilder()
@ -212,26 +219,36 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
.withHeaderBackground(R.drawable.header) .withHeaderBackground(R.drawable.header)
.withProfiles(profiles) .withProfiles(profiles)
.withOnAccountHeaderProfileImageListener(ProfileImageListener(this)) .withOnAccountHeaderProfileImageListener(ProfileImageListener(this))
.withOnAccountHeaderListener(ProfileSelectionListener(this@MainActivity, supportFragmentManager)) .withOnAccountHeaderListener(
ProfileSelectionListener(
this@MainActivity,
supportFragmentManager
)
)
.build() .build()
if (profiles.size > 2) { // There's always the add and manage identity items if (profiles.size > 2) { // There's always the add and manage identity items
accountHeader.setActiveProfile(profiles[0], true) accountHeader.setActiveProfile(profiles[0], true)
} }
val drawerItems = ArrayList<IDrawerItem<*, *>>() val drawerItems = ArrayList<IDrawerItem<*, *>>()
drawerItems.add(PrimaryDrawerItem() drawerItems.add(
.withIdentifier(LABEL_ARCHIVE.id as Long) PrimaryDrawerItem()
.withName(R.string.archive) .withIdentifier(LABEL_ARCHIVE.id as Long)
.withTag(LABEL_ARCHIVE) .withName(R.string.archive)
.withIcon(CommunityMaterial.Icon.cmd_archive) .withTag(LABEL_ARCHIVE)
.withIcon(CommunityMaterial.Icon.cmd_archive)
) )
drawerItems.add(DividerDrawerItem()) drawerItems.add(DividerDrawerItem())
drawerItems.add(PrimaryDrawerItem() drawerItems.add(
.withName(R.string.contacts_and_subscriptions) PrimaryDrawerItem()
.withIcon(GoogleMaterial.Icon.gmd_contacts)) .withName(R.string.contacts_and_subscriptions)
drawerItems.add(PrimaryDrawerItem() .withIcon(GoogleMaterial.Icon.gmd_contacts)
.withName(R.string.settings) )
.withIcon(GoogleMaterial.Icon.gmd_settings)) drawerItems.add(
PrimaryDrawerItem()
.withName(R.string.settings)
.withIcon(GoogleMaterial.Icon.gmd_settings)
)
nodeSwitch = SwitchDrawerItem() nodeSwitch = SwitchDrawerItem()
.withIdentifier(ID_NODE_SWITCH) .withIdentifier(ID_NODE_SWITCH)
@ -369,7 +386,8 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
// we know that there are 2 setting elements. // we know that there are 2 setting elements.
// Set the new profile above them ;) // Set the new profile above them ;)
accountHeader.addProfile( accountHeader.addProfile(
newProfile, accountHeader.profiles.size - 2) newProfile, accountHeader.profiles.size - 2
)
} else { } else {
accountHeader.addProfiles(newProfile) accountHeader.addProfiles(newProfile)
} }
@ -435,14 +453,31 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
// In two-pane mode, show the detail view in this activity by // In two-pane mode, show the detail view in this activity by
// adding or replacing the detail fragment using a // adding or replacing the detail fragment using a
// fragment transaction. // fragment transaction.
val arguments = Bundle()
arguments.putSerializable(MessageDetailFragment.ARG_ITEM, item)
val fragment = when (item) { val fragment = when (item) {
is Plaintext -> MessageDetailFragment() is Plaintext -> {
is BitmessageAddress -> AddressDetailFragment() if (item.labels.any { it.type == Label.Type.DRAFT }) {
ComposeMessageFragment().apply {
arguments = Bundle().apply {
putSerializable(ComposeMessageActivity.EXTRA_DRAFT, item)
}
}
} else {
MessageDetailFragment().apply {
arguments = Bundle().apply {
putSerializable(MessageDetailFragment.ARG_ITEM, item)
}
}
}
}
is BitmessageAddress -> {
AddressDetailFragment().apply {
arguments = Bundle().apply {
putSerializable(AddressDetailFragment.ARG_ITEM, item)
}
}
}
else -> throw IllegalArgumentException("Plaintext or BitmessageAddress expected, but was ${item::class.simpleName}") else -> throw IllegalArgumentException("Plaintext or BitmessageAddress expected, but was ${item::class.simpleName}")
} }
fragment.arguments = arguments
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.replace(R.id.message_detail_container, fragment) .replace(R.id.message_detail_container, fragment)
.commit() .commit()
@ -451,12 +486,21 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
// for the selected item ID. // for the selected item ID.
val detailIntent = when (item) { val detailIntent = when (item) {
is Plaintext -> { is Plaintext -> {
Intent(this, MessageDetailActivity::class.java) if (item.labels.any { it.type == Label.Type.DRAFT }) {
Intent(this, ComposeMessageActivity::class.java).apply {
putExtra(ComposeMessageActivity.EXTRA_DRAFT, item)
}
} else {
Intent(this, MessageDetailActivity::class.java).apply {
putExtra(MessageDetailFragment.ARG_ITEM, item)
}
}
}
is BitmessageAddress -> Intent(this, AddressDetailActivity::class.java).apply {
putExtra(AddressDetailFragment.ARG_ITEM, item)
} }
is BitmessageAddress -> Intent(this, AddressDetailActivity::class.java)
else -> throw IllegalArgumentException("Plaintext or BitmessageAddress expected, but was ${item::class.simpleName}") else -> throw IllegalArgumentException("Plaintext or BitmessageAddress expected, but was ${item::class.simpleName}")
} }
detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item)
startActivity(detailIntent) startActivity(detailIntent)
} }
} }
@ -474,15 +518,15 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
} }
companion object { companion object {
val EXTRA_SHOW_MESSAGE = "ch.dissem.abit.ShowMessage" const val EXTRA_SHOW_MESSAGE = "ch.dissem.abit.ShowMessage"
val EXTRA_SHOW_LABEL = "ch.dissem.abit.ShowLabel" const val EXTRA_SHOW_LABEL = "ch.dissem.abit.ShowLabel"
val EXTRA_REPLY_TO_MESSAGE = "ch.dissem.abit.ReplyToMessage" const val EXTRA_REPLY_TO_MESSAGE = "ch.dissem.abit.ReplyToMessage"
val ACTION_SHOW_INBOX = "ch.dissem.abit.ShowInbox" const val ACTION_SHOW_INBOX = "ch.dissem.abit.ShowInbox"
val ADD_IDENTITY = 1 const val ADD_IDENTITY = 1
val MANAGE_IDENTITY = 2 const val MANAGE_IDENTITY = 2
private val ID_NODE_SWITCH: Long = 1 private const val ID_NODE_SWITCH: Long = 1
private var instance: WeakReference<MainActivity>? = null private var instance: WeakReference<MainActivity>? = null

View File

@ -1,5 +1,5 @@
buildscript { buildscript {
ext.kotlin_version = '1.2.20' ext.kotlin_version = '1.2.21'
ext.anko_version = '0.10.4' ext.anko_version = '0.10.4'
repositories { repositories {
jcenter() jcenter()