Merge branch 'feature/drafts' into develop

This commit is contained in:
Christian Basler 2018-02-20 18:01:54 +01:00
commit ec645d70ce
41 changed files with 614 additions and 397 deletions

View File

@ -62,7 +62,8 @@ dependencies {
implementation "com.android.support:appcompat-v7:$supportVersion" implementation "com.android.support:appcompat-v7:$supportVersion"
implementation "com.android.support:preference-v7:$supportVersion" implementation "com.android.support:preference-v7:$supportVersion"
implementation "com.android.support:cardview-v7:$supportVersion" implementation "com.android.support:cardview-v7:$supportVersion"
implementation "com.android.support:support-v4:$supportVersion" implementation "com.android.support:support-v13:$supportVersion"
implementation "com.android.support:preference-v14:$supportVersion"
implementation "com.android.support:design:$supportVersion" implementation "com.android.support:design:$supportVersion"
implementation "com.android.support:multidex:1.0.2" implementation "com.android.support:multidex:1.0.2"

View File

@ -1,32 +1,32 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ch.dissem.apps.abit" xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android" package="ch.dissem.apps.abit">
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/> <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/> <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/> <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.READ_CONTACTS"/> <uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS"/> <uses-permission android:name="android.permission.WRITE_CONTACTS" />
<application <application
android:name="android.support.multidex.MultiDexApplication"
android:allowBackup="false" android:allowBackup="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/AppTheme" android:supportsRtl="true"
android:name="android.support.multidex.MultiDexApplication"> android:theme="@style/AppTheme">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:label="@string/app_name"> android:label="@string/app_name">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
@ -36,7 +36,7 @@
tools:ignore="UnusedAttribute"> tools:ignore="UnusedAttribute">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity"/> android:value=".MainActivity" />
</activity> </activity>
<activity <activity
android:name=".AddressDetailActivity" android:name=".AddressDetailActivity"
@ -45,42 +45,42 @@
tools:ignore="UnusedAttribute"> tools:ignore="UnusedAttribute">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity"/> android:value=".MainActivity" />
</activity> </activity>
<activity <activity
android:name=".dialog.FullNodeDialogActivity" android:name=".dialog.FullNodeDialogActivity"
android:label="@string/full_node" android:label="@string/full_node"
android:theme="@style/Theme.AppCompat.Light.Dialog"/> android:theme="@style/Theme.AppCompat.Light.Dialog" />
<activity <activity
android:name=".ComposeMessageActivity" android:name=".ComposeMessageActivity"
android:label="@string/compose_message" android:label="@string/compose_message"
android:parentActivityName=".MainActivity"> android:parentActivityName=".MainActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity"/> android:value=".MainActivity" />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SENDTO"/> <action android:name="android.intent.action.SENDTO" />
<data android:scheme="bitmessage"/> <data android:scheme="bitmessage" />
<data android:scheme="bitmsg"/> <data android:scheme="bitmsg" />
<data android:scheme="bm"/> <data android:scheme="bm" />
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND"/> <action android:name="android.intent.action.SEND" />
<data android:mimeType="text/plain"/> <data android:mimeType="text/plain" />
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE"/> <action android:name="android.intent.action.SEND_MULTIPLE" />
<data android:mimeType="text/plain"/> <data android:mimeType="text/plain" />
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
@ -88,14 +88,14 @@
android:label="@string/title_activity_open_bitmessage_link" android:label="@string/title_activity_open_bitmessage_link"
android:theme="@style/Theme.AppCompat.Light.Dialog"> android:theme="@style/Theme.AppCompat.Light.Dialog">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.VIEW" />
<data android:scheme="bitmessage"/> <data android:scheme="bitmessage" />
<data android:scheme="bitmsg"/> <data android:scheme="bitmsg" />
<data android:scheme="bm"/> <data android:scheme="bm" />
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE"/> <category android:name="android.intent.category.BROWSABLE" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
@ -104,34 +104,34 @@
android:parentActivityName=".MainActivity"> android:parentActivityName=".MainActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity"/> android:value=".MainActivity" />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.VIEW" />
<data <data
android:host="*" android:host="*"
android:mimeType="*/*" android:mimeType="*/*"
android:pathPattern=".*\\.dat" android:pathPattern=".*\\.dat"
android:scheme="file"/> android:scheme="file" />
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE"/> <category android:name="android.intent.category.BROWSABLE" />
</intent-filter> </intent-filter>
</activity> </activity>
<service <service
android:name=".service.BitmessageService" android:name=".service.BitmessageService"
android:exported="false"/> android:exported="false" />
<service <service
android:name=".service.ProofOfWorkService" android:name=".service.ProofOfWorkService"
android:exported="false"/> android:exported="false" />
<!-- Synchronization --> <!-- Synchronization -->
<provider <provider
android:name=".synchronization.StubProvider" android:name=".synchronization.StubProvider"
android:authorities="ch.dissem.apps.abit.provider" android:authorities="ch.dissem.apps.abit.provider"
android:exported="false" android:exported="false"
android:syncable="true"/> android:syncable="true" />
<!-- Exports --> <!-- Exports -->
<provider <provider
@ -149,44 +149,54 @@
android:exported="true" android:exported="true"
tools:ignore="ExportedService"> tools:ignore="ExportedService">
<intent-filter> <intent-filter>
<action android:name="android.accounts.AccountAuthenticator"/> <action android:name="android.accounts.AccountAuthenticator" />
</intent-filter> </intent-filter>
<meta-data <meta-data
android:name="android.accounts.AccountAuthenticator" android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator"/> android:resource="@xml/authenticator" />
</service> </service>
<service <service
android:name=".synchronization.SyncService" android:name=".synchronization.SyncService"
android:exported="true" android:exported="true"
tools:ignore="ExportedService"> tools:ignore="ExportedService">
<intent-filter> <intent-filter>
<action android:name="android.content.SyncAdapter"/> <action android:name="android.content.SyncAdapter" />
</intent-filter> </intent-filter>
<meta-data <meta-data
android:name="android.content.SyncAdapter" android:name="android.content.SyncAdapter"
android:resource="@xml/syncadapter"/> android:resource="@xml/syncadapter" />
</service> </service>
<service <service
android:name=".service.BitmessageIntentService" android:name=".service.BitmessageIntentService"
android:exported="false"/> android:exported="false" />
<!-- Receive Wi-Fi connection state changes --> <!-- Receive Wi-Fi connection state changes -->
<receiver android:name=".listener.WifiReceiver" android:enabled="@bool/is_pre_api_21"> <receiver
android:name=".listener.WifiReceiver"
android:enabled="@bool/is_pre_api_21">
<intent-filter> <intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/> <!-- This is bad for battery life, but needed on older devices to check
if WiFi is available. Let's be honest, the whole app is bad for
battery life. -->
<action
android:name="android.net.conn.CONNECTIVITY_CHANGE"
tools:ignore="BatteryLife" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".service.StartServiceReceiver" android:enabled="@bool/is_post_api_21"> <receiver
android:name=".service.StartServiceReceiver"
android:enabled="@bool/is_post_api_21">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<service <service
android:name=".service.StartupNodeOnWifiService" android:name=".service.StartupNodeOnWifiService"
android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true"
android:exported="true"/> android:permission="android.permission.BIND_JOB_SERVICE" />
<activity <activity
android:name=".StatusActivity" android:name=".StatusActivity"
@ -194,7 +204,7 @@
android:parentActivityName=".MainActivity"> android:parentActivityName=".MainActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity"/> android:value=".MainActivity" />
</activity> </activity>
</application> </application>

View File

@ -204,7 +204,7 @@ class AddressDetailFragment : Fragment() {
* The fragment argument representing the item ID that this fragment * The fragment argument representing the item ID that this fragment
* represents. * represents.
*/ */
val ARG_ITEM = "item" const val ARG_ITEM = "item"
val EXPORT_POSTFIX = ".keys.dat" const val EXPORT_POSTFIX = ".keys.dat"
} }
} }

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,72 +57,107 @@ 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")
}.invoke()
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)
if (broadcast) { context?.let { ctx ->
recipient_input.visibility = View.GONE val identities = Singleton.getAddressRepository(ctx).getIdentities()
} else { sender_input.adapter = ContactAdapter(ctx, identities, true)
val adapter = ContactAdapter(context!!) val index = identities.indexOf(Singleton.getIdentity(ctx))
recipient_input.setAdapter(adapter) if (index >= 0) {
recipient_input.onItemClickListener = AdapterView.OnItemClickListener { _, _, pos, _ -> adapter.getItem(pos) } sender_input.setSelection(index)
recipient_input.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
recipient = adapter.getItem(position)
}
override fun onNothingSelected(parent: AdapterView<*>) = Unit // leave current selection
} }
recipient?.let { recipient_input.setText(it.toString()) }
}
subject_input.setText(subject)
body_input.setText(content)
when { if (broadcast) {
recipient == null -> recipient_input.requestFocus() recipient_input.visibility = View.GONE
subject.isEmpty() -> subject_input.requestFocus() } else {
else -> { val adapter = ContactAdapter(
body_input.requestFocus() ctx,
body_input.setSelection(0) Singleton.getAddressRepository(ctx).getContacts()
)
recipient_input.setAdapter(adapter)
recipient_input.onItemClickListener =
AdapterView.OnItemClickListener { _, _, pos, _ -> adapter.getItem(pos) }
recipient_input.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>,
view: View,
position: Int,
id: Long
) {
recipient = adapter.getItem(position)
}
override fun onNothingSelected(parent: AdapterView<*>) =
Unit // leave current selection
}
recipient?.let { recipient_input.setText(it.toString()) }
}
subject_input.setText(subject)
body_input.setText(content)
when {
recipient == null -> recipient_input.requestFocus()
subject.isEmpty() -> subject_input.requestFocus()
else -> {
body_input.requestFocus()
body_input.setSelection(0)
}
} }
} }
} }
@ -146,18 +186,17 @@ 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)
} else { } else {
val inputString = recipient_input.text.toString() val inputString = recipient_input.text.toString()
if (recipient == null || recipient?.toString() != inputString) { if (recipient == null || recipient?.toString() != inputString) {
@ -175,42 +214,69 @@ 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) .to(recipient)
.to(recipient)
} }
val sender = sender_input.selectedItem as? ch.dissem.bitmessage.entity.BitmessageAddress
sender?.let { builder.from(it) }
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

@ -92,7 +92,7 @@ class Identicon(input: BitmessageAddress) : Drawable() {
override fun getOpacity() = PixelFormat.TRANSPARENT override fun getOpacity() = PixelFormat.TRANSPARENT
companion object { companion object {
private val SIZE = 9 private const val SIZE = 9
private val CENTER_COLUMN = 5 private const val CENTER_COLUMN = 5
} }
} }

View File

@ -72,6 +72,6 @@ class ImportIdentitiesFragment : Fragment() {
} }
companion object { companion object {
val WIF_DATA = "wif_data" const val WIF_DATA = "wif_data"
} }
} }

View File

@ -19,7 +19,7 @@ package ch.dissem.apps.abit
/** /**
* @author Christian Basler * @author Christian Basler
*/ */
interface ListHolder<L> { interface ListHolder<in L> {
fun updateList(label: L) fun updateList(label: L)
fun setActivateOnItemClick(activateOnItemClick: Boolean) fun setActivateOnItemClick(activateOnItemClick: Boolean)

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,19 +486,29 @@ 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)
} }
} }
fun setDetailView(fragment: Fragment) { fun setDetailView(fragment: Fragment) {
if (hasDetailPane) { if (hasDetailPane) {
supportFragmentManager.beginTransaction() supportFragmentManager
.beginTransaction()
.replace(R.id.message_detail_container, fragment) .replace(R.id.message_detail_container, fragment)
.commit() .commit()
} }
@ -474,15 +519,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

@ -70,7 +70,11 @@ class MessageDetailFragment : Fragment() {
setHasOptionsMenu(true) setHasOptionsMenu(true)
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View =
inflater.inflate(R.layout.fragment_message_detail, container, false) inflater.inflate(R.layout.fragment_message_detail, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -84,6 +88,13 @@ class MessageDetailFragment : Fragment() {
status.setImageResource(Assets.getStatusDrawable(item.status)) status.setImageResource(Assets.getStatusDrawable(item.status))
status.contentDescription = getString(Assets.getStatusString(item.status)) status.contentDescription = getString(Assets.getStatusString(item.status))
avatar.setImageDrawable(Identicon(item.from)) avatar.setImageDrawable(Identicon(item.from))
val senderClickListener: (View) -> Unit = {
MainActivity.apply {
onItemSelected(item.from)
}
}
avatar.setOnClickListener(senderClickListener)
sender.setOnClickListener(senderClickListener)
sender.text = item.from.toString() sender.text = item.from.toString()
item.to?.let { to -> item.to?.let { to ->
recipient.text = to.toString() recipient.text = to.toString()
@ -124,7 +135,11 @@ class MessageDetailFragment : Fragment() {
} }
} }
private fun showRelatedMessages(ctx: Context, rootView: View, @IdRes id: Int, messages: List<Plaintext>) { private fun showRelatedMessages(
ctx: Context,
rootView: View, @IdRes id: Int,
messages: List<Plaintext>
) {
val recyclerView = rootView.findViewById<RecyclerView>(id) val recyclerView = rootView.findViewById<RecyclerView>(id)
val adapter = RelatedMessageAdapter(ctx, messages) val adapter = RelatedMessageAdapter(ctx, messages)
recyclerView.adapter = adapter recyclerView.adapter = adapter
@ -136,8 +151,10 @@ class MessageDetailFragment : Fragment() {
activity?.let { activity -> activity?.let { activity ->
Drawables.addIcon(activity, menu, R.id.reply, GoogleMaterial.Icon.gmd_reply) Drawables.addIcon(activity, menu, R.id.reply, GoogleMaterial.Icon.gmd_reply)
Drawables.addIcon(activity, menu, R.id.delete, GoogleMaterial.Icon.gmd_delete) Drawables.addIcon(activity, menu, R.id.delete, GoogleMaterial.Icon.gmd_delete)
Drawables.addIcon(activity, menu, R.id.mark_unread, GoogleMaterial.Icon Drawables.addIcon(
.gmd_markunread) activity, menu, R.id.mark_unread, GoogleMaterial.Icon
.gmd_markunread
)
Drawables.addIcon(activity, menu, R.id.archive, GoogleMaterial.Icon.gmd_archive) Drawables.addIcon(activity, menu, R.id.archive, GoogleMaterial.Icon.gmd_archive)
} }
@ -187,9 +204,15 @@ class MessageDetailFragment : Fragment() {
return false return false
} }
private class RelatedMessageAdapter internal constructor(private val ctx: Context, private val messages: List<Plaintext>) : RecyclerView.Adapter<RelatedMessageAdapter.ViewHolder>() { private class RelatedMessageAdapter internal constructor(
private val ctx: Context,
private val messages: List<Plaintext>
) : RecyclerView.Adapter<RelatedMessageAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RelatedMessageAdapter.ViewHolder { override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): RelatedMessageAdapter.ViewHolder {
val context = parent.context val context = parent.context
val inflater = LayoutInflater.from(context) val inflater = LayoutInflater.from(context)
@ -236,7 +259,8 @@ class MessageDetailFragment : Fragment() {
} }
} }
private class LabelAdapter internal constructor(private val ctx: Context, labels: Set<Label>) : RecyclerView.Adapter<LabelAdapter.ViewHolder>() { private class LabelAdapter internal constructor(private val ctx: Context, labels: Set<Label>) :
RecyclerView.Adapter<LabelAdapter.ViewHolder>() {
private val labels = labels.toMutableList() private val labels = labels.toMutableList()
@ -274,7 +298,7 @@ class MessageDetailFragment : Fragment() {
* The fragment argument representing the item ID that this fragment * The fragment argument representing the item ID that this fragment
* represents. * represents.
*/ */
val ARG_ITEM = "item" const val ARG_ITEM = "item"
fun isInTrash(item: Plaintext?) = item?.labels?.any { it.type == Label.Type.TRASH } == true fun isInTrash(item: Plaintext?) = item?.labels?.any { it.type == Label.Type.TRASH } == true
} }

View File

@ -20,25 +20,22 @@ import android.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.BaseAdapter import android.widget.*
import android.widget.Filter
import android.widget.Filterable
import android.widget.ImageView
import android.widget.TextView
import java.util.ArrayList
import ch.dissem.apps.abit.Identicon import ch.dissem.apps.abit.Identicon
import ch.dissem.apps.abit.R import ch.dissem.apps.abit.R
import ch.dissem.apps.abit.service.Singleton
import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.BitmessageAddress
import java.util.*
/** /**
* An adapter for contacts. Can be filtered by alias or address. * An adapter for contacts. Can be filtered by alias or address.
*/ */
class ContactAdapter(ctx: Context) : BaseAdapter(), Filterable { class ContactAdapter(
ctx: Context,
private val originalData: List<BitmessageAddress>,
private val slim: Boolean = false
) :
BaseAdapter(), Filterable {
private val inflater = LayoutInflater.from(ctx) private val inflater = LayoutInflater.from(ctx)
private val originalData = Singleton.getAddressRepository(ctx).getContacts()
private var data: List<BitmessageAddress> = originalData private var data: List<BitmessageAddress> = originalData
override fun getCount() = data.size override fun getCount() = data.size
@ -49,23 +46,33 @@ class ContactAdapter(ctx: Context) : BaseAdapter(), Filterable {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val viewHolder = if (convertView == null) { val viewHolder = if (convertView == null) {
ViewHolder(inflater.inflate(R.layout.contact_row, parent, false)) ViewHolder(
inflater.inflate(
if (slim) {
R.layout.contact_row_slim
} else {
R.layout.contact_row
},
parent, false
)
)
} else { } else {
convertView.tag as ViewHolder convertView.tag as ViewHolder
} }
val item = getItem(position) val item = getItem(position)
viewHolder.avatar.setImageDrawable(Identicon(item)) viewHolder.avatar.setImageDrawable(Identicon(item))
viewHolder.name.text = item.toString() viewHolder.name.text = item.toString()
viewHolder.address.text = item.address viewHolder.address?.text = item.address
return viewHolder.view return viewHolder.view
} }
override fun getFilter(): Filter = ContactFilter() override fun getFilter(): Filter = ContactFilter()
private inner class ViewHolder(val view: View) { private inner class ViewHolder(val view: View) {
val avatar = view.findViewById<ImageView>(R.id.avatar)!! val avatar: ImageView = view.findViewById(R.id.avatar)
val name = view.findViewById<TextView>(R.id.name)!! val name: TextView = view.findViewById(R.id.name)
val address = view.findViewById<TextView>(R.id.address)!! val address: TextView? = view.findViewById(R.id.address)
init { init {
view.tag = this view.tag = this
@ -83,27 +90,29 @@ class ContactAdapter(ctx: Context) : BaseAdapter(), Filterable {
val newValues = ArrayList<BitmessageAddress>() val newValues = ArrayList<BitmessageAddress>()
originalData originalData
.forEach { value -> .forEach { value ->
value.alias?.toLowerCase()?.let { alias -> value.alias?.toLowerCase()?.let { alias ->
if (alias.startsWith(prefixString)) { if (alias.startsWith(prefixString)) {
newValues.add(value) newValues.add(value)
} else { } else {
val words = alias.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() val words =
alias.split(" ".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()
for (word in words) { for (word in words) {
if (word.startsWith(prefixString)) { if (word.startsWith(prefixString)) {
newValues.add(value) newValues.add(value)
break break
}
} }
} }
} ?: { }
val address = value.address.toLowerCase() } ?: {
if (address.contains(prefixString)) { val address = value.address.toLowerCase()
newValues.add(value) if (address.contains(prefixString)) {
} newValues.add(value)
}.invoke() }
} }.invoke()
}
results.values = newValues results.values = newValues
results.count = newValues.size results.count = newValues.size

View File

@ -55,6 +55,6 @@ class ErrorNotification(ctx: Context) : AbstractNotification(ctx) {
override val notificationId = ERROR_NOTIFICATION_ID override val notificationId = ERROR_NOTIFICATION_ID
companion object { companion object {
val ERROR_NOTIFICATION_ID = 4 const val ERROR_NOTIFICATION_ID = 4
} }
} }

View File

@ -128,7 +128,7 @@ class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) {
override val notificationId = NEW_MESSAGE_NOTIFICATION_ID override val notificationId = NEW_MESSAGE_NOTIFICATION_ID
companion object { companion object {
private val NEW_MESSAGE_NOTIFICATION_ID = 1 private const val NEW_MESSAGE_NOTIFICATION_ID = 1
private val SPAN_EMPHASIS = StyleSpan(Typeface.BOLD) private val SPAN_EMPHASIS = StyleSpan(Typeface.BOLD)
} }
} }

View File

@ -54,7 +54,7 @@ class AndroidInventory(private val sql: SqlHelper) : Inventory {
private fun getCache(stream: Long): MutableMap<InventoryVector, Long> { private fun getCache(stream: Long): MutableMap<InventoryVector, Long> {
fun addToCache(stream: Long): MutableMap<InventoryVector, Long> { fun addToCache(stream: Long): MutableMap<InventoryVector, Long> {
val result: MutableMap<InventoryVector, Long> = ConcurrentHashMap() val result: MutableMap<InventoryVector, Long> = ConcurrentHashMap()
cache.put(stream, result) cache[stream] = result
val projection = arrayOf(COLUMN_HASH, COLUMN_EXPIRES) val projection = arrayOf(COLUMN_HASH, COLUMN_EXPIRES)
@ -149,7 +149,7 @@ class AndroidInventory(private val sql: SqlHelper) : Inventory {
sql.writableDatabase.insertOrThrow(TABLE_NAME, null, values) sql.writableDatabase.insertOrThrow(TABLE_NAME, null, values)
getCache(objectMessage.stream).put(iv, objectMessage.expiresTime) getCache(objectMessage.stream)[iv] = objectMessage.expiresTime
} catch (e: SQLiteConstraintException) { } catch (e: SQLiteConstraintException) {
LOG.trace(e.message, e) LOG.trace(e.message, e)
} }

View File

@ -125,13 +125,13 @@ class AndroidProofOfWorkRepository(private val sql: SqlHelper) : ProofOfWorkRepo
companion object { companion object {
private val LOG = LoggerFactory.getLogger(AndroidProofOfWorkRepository::class.java) private val LOG = LoggerFactory.getLogger(AndroidProofOfWorkRepository::class.java)
private val TABLE_NAME = "POW" private const val TABLE_NAME = "POW"
private val COLUMN_INITIAL_HASH = "initial_hash" private const val COLUMN_INITIAL_HASH = "initial_hash"
private val COLUMN_DATA = "data" private const val COLUMN_DATA = "data"
private val COLUMN_VERSION = "version" private const val COLUMN_VERSION = "version"
private val COLUMN_NONCE_TRIALS_PER_BYTE = "nonce_trials_per_byte" private const val COLUMN_NONCE_TRIALS_PER_BYTE = "nonce_trials_per_byte"
private val COLUMN_EXTRA_BYTES = "extra_bytes" private const val COLUMN_EXTRA_BYTES = "extra_bytes"
private val COLUMN_EXPIRATION_TIME = "expiration_time" private const val COLUMN_EXPIRATION_TIME = "expiration_time"
private val COLUMN_MESSAGE_ID = "message_id" private const val COLUMN_MESSAGE_ID = "message_id"
} }
} }

View File

@ -90,7 +90,7 @@ class SqlHelper(private val ctx: Context) : SQLiteOpenHelper(ctx, DATABASE_NAME,
companion object { companion object {
// If you change the database schema, you must increment the database version. // If you change the database schema, you must increment the database version.
private val DATABASE_VERSION = 7 private const val DATABASE_VERSION = 7
val DATABASE_NAME = "jabit.db" const val DATABASE_NAME = "jabit.db"
} }
} }

View File

@ -25,11 +25,9 @@ import android.net.ConnectivityManager
import android.os.Handler import android.os.Handler
import ch.dissem.apps.abit.notification.NetworkNotification import ch.dissem.apps.abit.notification.NetworkNotification
import ch.dissem.apps.abit.notification.NetworkNotification.Companion.NETWORK_NOTIFICATION_ID import ch.dissem.apps.abit.notification.NetworkNotification.Companion.NETWORK_NOTIFICATION_ID
import ch.dissem.apps.abit.util.NetworkUtils
import ch.dissem.apps.abit.util.Preferences import ch.dissem.apps.abit.util.Preferences
import ch.dissem.bitmessage.BitmessageContext import ch.dissem.bitmessage.BitmessageContext
import ch.dissem.bitmessage.utils.Property import ch.dissem.bitmessage.utils.Property
import org.jetbrains.anko.connectivityManager
/** /**
* Define a Service that returns an IBinder for the * Define a Service that returns an IBinder for the

View File

@ -19,7 +19,6 @@ package ch.dissem.apps.abit.service
import android.app.Service import android.app.Service
import android.content.Intent import android.content.Intent
import android.os.Binder import android.os.Binder
import android.os.IBinder
import android.support.v4.content.ContextCompat import android.support.v4.content.ContextCompat
import ch.dissem.apps.abit.notification.ProofOfWorkNotification import ch.dissem.apps.abit.notification.ProofOfWorkNotification
import ch.dissem.apps.abit.notification.ProofOfWorkNotification.Companion.ONGOING_NOTIFICATION_ID import ch.dissem.apps.abit.notification.ProofOfWorkNotification.Companion.ONGOING_NOTIFICATION_ID

View File

@ -2,7 +2,6 @@ package ch.dissem.apps.abit.service
import android.app.job.JobParameters import android.app.job.JobParameters
import android.app.job.JobService import android.app.job.JobService
import android.content.Intent
import android.os.Build import android.os.Build
import android.support.annotation.RequiresApi import android.support.annotation.RequiresApi
import ch.dissem.apps.abit.util.NetworkUtils import ch.dissem.apps.abit.util.NetworkUtils

View File

@ -18,7 +18,6 @@ package ch.dissem.apps.abit.synchronization
import android.app.Service import android.app.Service
import android.content.Intent import android.content.Intent
import android.os.IBinder
/** /**
* Define a Service that returns an IBinder for the * Define a Service that returns an IBinder for the

View File

@ -44,7 +44,7 @@ import java.io.ByteArrayOutputStream
object Drawables { object Drawables {
private val LOG = LoggerFactory.getLogger(Drawables::class.java) private val LOG = LoggerFactory.getLogger(Drawables::class.java)
private val QR_CODE_SIZE = 350 private const val QR_CODE_SIZE = 350
fun addIcon(ctx: Context, menu: Menu, menuItem: Int, icon: IIcon): MenuItem { fun addIcon(ctx: Context, menu: Menu, menuItem: Int, icon: IIcon): MenuItem {
val item = menu.findItem(menuItem) val item = menu.findItem(menuItem)

View File

@ -25,7 +25,7 @@ class Observable<T>(value: T) {
* To prevent memory leaks, the observer must be removed if it isn't used anymore. * To prevent memory leaks, the observer must be removed if it isn't used anymore.
*/ */
fun addObserver(key: Any, observer: (T) -> Unit) { fun addObserver(key: Any, observer: (T) -> Unit) {
observers.put(key, observer) observers[key] = observer
} }
/** /**

View File

@ -44,7 +44,7 @@ public final class PRNGFixes {
private static final int VERSION_CODE_JELLY_BEAN = 16; private static final int VERSION_CODE_JELLY_BEAN = 16;
private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18; private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL =
getBuildFingerprintAndDeviceSerial(); getBuildFingerprintAndDeviceSerial();
/** /**
* Hidden constructor to prevent instantiation. * Hidden constructor to prevent instantiation.
@ -70,7 +70,7 @@ public final class PRNGFixes {
*/ */
private static void applyOpenSSLFix() throws SecurityException { private static void applyOpenSSLFix() throws SecurityException {
if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN) if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
|| (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) { || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
// No need to apply the fix // No need to apply the fix
return; return;
} }
@ -78,18 +78,18 @@ public final class PRNGFixes {
try { try {
// Mix in the device- and invocation-specific seed. // Mix in the device- and invocation-specific seed.
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_seed", byte[].class) .getMethod("RAND_seed", byte[].class)
.invoke(null, (Object) generateSeed()); .invoke(null, (Object) generateSeed());
// Mix output of Linux PRNG into OpenSSL's PRNG // Mix output of Linux PRNG into OpenSSL's PRNG
int bytesRead = (Integer) Class.forName( int bytesRead = (Integer) Class.forName(
"org.apache.harmony.xnet.provider.jsse.NativeCrypto") "org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_load_file", String.class, long.class) .getMethod("RAND_load_file", String.class, long.class)
.invoke(null, "/dev/urandom", 1024); .invoke(null, "/dev/urandom", 1024);
if (bytesRead != 1024) { if (bytesRead != 1024) {
throw new IOException( throw new IOException(
"Unexpected number of bytes read from Linux PRNG: " "Unexpected number of bytes read from Linux PRNG: "
+ bytesRead); + bytesRead);
} }
} catch (Exception e) { } catch (Exception e) {
throw new SecurityException("Failed to seed OpenSSL PRNG", e); throw new SecurityException("Failed to seed OpenSSL PRNG", e);
@ -104,7 +104,7 @@ public final class PRNGFixes {
* @throws SecurityException if the fix is needed but could not be applied. * @throws SecurityException if the fix is needed but could not be applied.
*/ */
private static void installLinuxPRNGSecureRandom() private static void installLinuxPRNGSecureRandom()
throws SecurityException { throws SecurityException {
if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) { if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
// No need to apply the fix // No need to apply the fix
return; return;
@ -113,11 +113,11 @@ public final class PRNGFixes {
// Install a Linux PRNG-based SecureRandom implementation as the // Install a Linux PRNG-based SecureRandom implementation as the
// default, if not yet installed. // default, if not yet installed.
Provider[] secureRandomProviders = Provider[] secureRandomProviders =
Security.getProviders("SecureRandom.SHA1PRNG"); Security.getProviders("SecureRandom.SHA1PRNG");
if ((secureRandomProviders == null) if ((secureRandomProviders == null)
|| (secureRandomProviders.length < 1) || (secureRandomProviders.length < 1)
|| (!LinuxPRNGSecureRandomProvider.class.equals( || (!LinuxPRNGSecureRandomProvider.class.equals(
secureRandomProviders[0].getClass()))) { secureRandomProviders[0].getClass()))) {
Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1); Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
} }
@ -126,10 +126,10 @@ public final class PRNGFixes {
// by the Linux PRNG-based SecureRandom implementation. // by the Linux PRNG-based SecureRandom implementation.
SecureRandom rng1 = new SecureRandom(); SecureRandom rng1 = new SecureRandom();
if (!LinuxPRNGSecureRandomProvider.class.equals( if (!LinuxPRNGSecureRandomProvider.class.equals(
rng1.getProvider().getClass())) { rng1.getProvider().getClass())) {
throw new SecurityException( throw new SecurityException(
"new SecureRandom() backed by wrong Provider: " "new SecureRandom() backed by wrong Provider: "
+ rng1.getProvider().getClass()); + rng1.getProvider().getClass());
} }
SecureRandom rng2; SecureRandom rng2;
@ -139,10 +139,10 @@ public final class PRNGFixes {
throw new SecurityException("SHA1PRNG not available", e); throw new SecurityException("SHA1PRNG not available", e);
} }
if (!LinuxPRNGSecureRandomProvider.class.equals( if (!LinuxPRNGSecureRandomProvider.class.equals(
rng2.getProvider().getClass())) { rng2.getProvider().getClass())) {
throw new SecurityException( throw new SecurityException(
"SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong"
+ " Provider: " + rng2.getProvider().getClass()); + " Provider: " + rng2.getProvider().getClass());
} }
} }
@ -152,11 +152,11 @@ public final class PRNGFixes {
*/ */
private static class LinuxPRNGSecureRandomProvider extends Provider { private static class LinuxPRNGSecureRandomProvider extends Provider {
public LinuxPRNGSecureRandomProvider() { LinuxPRNGSecureRandomProvider() {
super("LinuxPRNG", super("LinuxPRNG",
1.0, 1.0,
"A Linux-specific random number provider that uses" "A Linux-specific random number provider that uses"
+ " /dev/urandom"); + " /dev/urandom");
// Although /dev/urandom is not a SHA-1 PRNG, some apps // Although /dev/urandom is not a SHA-1 PRNG, some apps
// explicitly request a SHA1PRNG SecureRandom and we thus need to // explicitly request a SHA1PRNG SecureRandom and we thus need to
// prevent them from getting the default implementation whose output // prevent them from getting the default implementation whose output
@ -225,7 +225,7 @@ public final class PRNGFixes {
// On a small fraction of devices /dev/urandom is not writable. // On a small fraction of devices /dev/urandom is not writable.
// Log and ignore. // Log and ignore.
Log.w(PRNGFixes.class.getSimpleName(), Log.w(PRNGFixes.class.getSimpleName(),
"Failed to mix seed into " + URANDOM_FILE); "Failed to mix seed into " + URANDOM_FILE);
} finally { } finally {
mSeeded = true; mSeeded = true;
} }
@ -249,7 +249,7 @@ public final class PRNGFixes {
} }
} catch (IOException e) { } catch (IOException e) {
throw new SecurityException( throw new SecurityException(
"Failed to read from " + URANDOM_FILE, e); "Failed to read from " + URANDOM_FILE, e);
} }
} }
@ -269,10 +269,10 @@ public final class PRNGFixes {
// output being pulled into this process prematurely. // output being pulled into this process prematurely.
try { try {
sUrandomIn = new DataInputStream( sUrandomIn = new DataInputStream(
new FileInputStream(URANDOM_FILE)); new FileInputStream(URANDOM_FILE));
} catch (IOException e) { } catch (IOException e) {
throw new SecurityException("Failed to open " throw new SecurityException("Failed to open "
+ URANDOM_FILE + " for reading", e); + URANDOM_FILE + " for reading", e);
} }
} }
return sUrandomIn; return sUrandomIn;
@ -297,7 +297,7 @@ public final class PRNGFixes {
try { try {
ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
DataOutputStream seedBufferOut = DataOutputStream seedBufferOut =
new DataOutputStream(seedBuffer); new DataOutputStream(seedBuffer);
seedBufferOut.writeLong(System.currentTimeMillis()); seedBufferOut.writeLong(System.currentTimeMillis());
seedBufferOut.writeLong(System.nanoTime()); seedBufferOut.writeLong(System.nanoTime());
seedBufferOut.writeInt(Process.myPid()); seedBufferOut.writeInt(Process.myPid());
@ -341,4 +341,4 @@ public final class PRNGFixes {
throw new RuntimeException("UTF-8 encoding not supported"); throw new RuntimeException("UTF-8 encoding not supported");
} }
} }
} }

View File

@ -37,11 +37,11 @@
android:layout_alignTop="@+id/avatar" android:layout_alignTop="@+id/avatar"
android:layout_toEndOf="@+id/avatar" android:layout_toEndOf="@+id/avatar"
android:ellipsize="end" android:ellipsize="end"
android:lines="1"
android:paddingBottom="0dp" android:paddingBottom="0dp"
android:paddingLeft="8dp" android:paddingLeft="8dp"
android:paddingRight="8dp" android:paddingRight="8dp"
android:paddingTop="0dp" android:paddingTop="0dp"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
android:textStyle="bold" android:textStyle="bold"
tools:text="Name" /> tools:text="Name" />
@ -53,9 +53,9 @@
android:layout_alignBottom="@+id/avatar" android:layout_alignBottom="@+id/avatar"
android:layout_toEndOf="@+id/avatar" android:layout_toEndOf="@+id/avatar"
android:ellipsize="marquee" android:ellipsize="marquee"
android:lines="1"
android:paddingLeft="8dp" android:paddingLeft="8dp"
android:paddingRight="8dp" android:paddingRight="8dp"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
tools:text="BM-2cW0000000000000000000000000000000" /> tools:text="BM-2cW0000000000000000000000000000000" />

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2015 Christian Basler
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/avatar"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_margin="4dp"
android:src="@color/colorAccent"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@+id/avatar"
android:ellipsize="end"
android:lines="1"
android:paddingBottom="0dp"
android:paddingEnd="4dp"
android:paddingStart="4dp"
android:paddingTop="0dp"
android:textAppearance="?android:attr/textAppearanceMedium"
tools:text="Name" />
</RelativeLayout>

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!--
<!--
~ Copyright 2016 Christian Basler ~ Copyright 2016 Christian Basler
~ ~
~ Licensed under the Apache License, Version 2.0 (the "License"); ~ Licensed under the Apache License, Version 2.0 (the "License");
@ -15,8 +14,7 @@
~ limitations under the License. ~ limitations under the License.
--> -->
<android.support.constraint.ConstraintLayout <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -34,13 +32,13 @@
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:layout_constraintLeft_creator="1" tools:layout_constraintLeft_creator="1"
tools:layout_constraintTop_creator="1"/> tools:layout_constraintTop_creator="1" />
<android.support.design.widget.TextInputLayout <android.support.design.widget.TextInputLayout
android:id="@+id/label_wrapper" android:id="@+id/label_wrapper"
android:layout_marginTop="24dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/description"> app:layout_constraintTop_toBottomOf="@id/description">
@ -48,7 +46,7 @@
android:id="@+id/label" android:id="@+id/label"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/label"/> android:hint="@string/label" />
</android.support.design.widget.TextInputLayout> </android.support.design.widget.TextInputLayout>
@ -64,7 +62,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/passphrase" android:hint="@string/passphrase"
android:inputType="textMultiLine"/> android:inputType="textMultiLine" />
</android.support.design.widget.TextInputLayout> </android.support.design.widget.TextInputLayout>
@ -82,7 +80,8 @@
android:ems="10" android:ems="10"
android:hint="@string/number_of_identities" android:hint="@string/number_of_identities"
android:inputType="number" android:inputType="number"
android:text="1"/> android:text="1"
tools:ignore="HardcodedText" />
</android.support.design.widget.TextInputLayout> </android.support.design.widget.TextInputLayout>
@ -92,7 +91,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/shorter" android:text="@string/shorter"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/number_of_identities_wrapper"/> app:layout_constraintTop_toBottomOf="@id/number_of_identities_wrapper" />
<Button <Button
android:id="@+id/ok" android:id="@+id/ok"
@ -103,7 +102,7 @@
android:text="@string/ok" android:text="@string/ok"
android:textColor="@color/colorAccent" android:textColor="@color/colorAccent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/shorter"/> app:layout_constraintTop_toBottomOf="@id/shorter" />
<Button <Button
android:id="@+id/dismiss" android:id="@+id/dismiss"
@ -113,6 +112,6 @@
android:text="@string/cancel" android:text="@string/cancel"
android:textColor="@color/colorAccent" android:textColor="@color/colorAccent"
app:layout_constraintBottom_toBottomOf="@id/ok" app:layout_constraintBottom_toBottomOf="@id/ok"
app:layout_constraintRight_toLeftOf="@id/ok"/> app:layout_constraintRight_toLeftOf="@id/ok" />
</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>

View File

@ -1,46 +1,68 @@
<?xml version="1.0" encoding="utf-8"?> <?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"
android:layout_width="match_parent" xmlns:tools="http://schemas.android.com/tools"
android:layout_height="match_parent" android:layout_width="match_parent"
android:orientation="vertical"> android:layout_height="match_parent"
android:fillViewport="true">
<android.support.design.widget.TextInputLayout <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="4dp">
<AutoCompleteTextView
android:id="@+id/recipient_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/to"
android:inputType="textNoSuggestions"
android:maxLines="1"/>
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/subject_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/subject"
android:inputType="textEmailSubject"
android:textAppearance="?android:attr/textAppearanceLarge"/>
</android.support.design.widget.TextInputLayout>
<EditText
android:id="@+id/body_input"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:orientation="vertical">
android:gravity="start|top"
android:hint="@string/compose_body_hint"
android:inputType="textMultiLine|textCapSentences"
android:scrollbars="vertical"/>
</LinearLayout> <TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:labelFor="@id/sender_input"
android:padding="4dp"
android:text="@string/from"
android:textColor="#9b9b9b"
android:textSize="12sp" />
<Spinner
android:id="@+id/sender_input"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="4dp">
<AutoCompleteTextView
android:id="@+id/recipient_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/to"
android:inputType="textNoSuggestions"
android:maxLines="1" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/subject_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/subject"
android:inputType="textEmailSubject"
android:textAppearance="?android:attr/textAppearanceLarge" />
</android.support.design.widget.TextInputLayout>
<EditText
android:id="@+id/body_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start|top"
android:hint="@string/compose_body_hint"
android:inputType="textMultiLine|textCapSentences"
android:scrollbars="none"
tools:ignore="InefficientWeight" />
</LinearLayout>
</ScrollView>

View File

@ -26,6 +26,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@drawable/bg_item_normal_state" android:background="@drawable/bg_item_normal_state"
android:clickable="true" android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground" android:foreground="?attr/selectableItemBackground"
tools:ignore="UselessParent"> tools:ignore="UselessParent">

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!--
<!--
~ Copyright 2016 Christian Basler ~ Copyright 2016 Christian Basler
~ ~
~ Licensed under the Apache License, Version 2.0 (the "License"); ~ Licensed under the Apache License, Version 2.0 (the "License");
@ -16,9 +15,9 @@
--> -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="64dp"> android:layout_height="64dp">
<CheckBox <CheckBox
android:id="@+id/checkbox" android:id="@+id/checkbox"
@ -31,21 +30,22 @@
android:paddingEnd="8dp" android:paddingEnd="8dp"
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingTop="8dp" android:paddingTop="8dp"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
tools:text="Name"/> tools:text="Name" />
<TextView <TextView
android:id="@+id/address" android:id="@+id/address"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
android:layout_alignParentStart="true"
android:ellipsize="marquee" android:ellipsize="marquee"
android:lines="1"
android:paddingBottom="8dp" android:paddingBottom="8dp"
android:paddingEnd="8dp" android:paddingEnd="8dp"
android:paddingStart="48dp" android:paddingStart="48dp"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
tools:text="BM-2cW0000000000000000000000000000000"/> tools:text="BM-2cW0000000000000000000000000000000" />
</RelativeLayout> </RelativeLayout>

View File

@ -127,4 +127,5 @@ Als Alternative kann in den Einstellungen ein vertrauenswürdiger Knoten konfigu
<string name="import_data">Import</string> <string name="import_data">Import</string>
<string name="import_data_summary">Nachrichten und Kontakte importieren (aber keine Identitäten)</string> <string name="import_data_summary">Nachrichten und Kontakte importieren (aber keine Identitäten)</string>
<string name="select_encoding">Kodierung auswählen</string> <string name="select_encoding">Kodierung auswählen</string>
<string name="from">Von</string>
</resources> </resources>

View File

@ -126,4 +126,5 @@ As an alternative you could configure a trusted node in the settings, but as of
<string name="request_acknowledgements_summary">Acknowledges allow making sure a message was received, but require additional time to send</string> <string name="request_acknowledgements_summary">Acknowledges allow making sure a message was received, but require additional time to send</string>
<string name="got_it">Got it</string> <string name="got_it">Got it</string>
<string name="select_encoding">Select encoding</string> <string name="select_encoding">Select encoding</string>
<string name="from">From</string>
</resources> </resources>

View File

@ -5,7 +5,7 @@
<item name="android:activatedBackgroundIndicator">@drawable/bg_item_activated</item> <item name="android:activatedBackgroundIndicator">@drawable/bg_item_activated</item>
<item name="android:textColor">@color/colorPrimaryText</item> <item name="android:textColor">@color/colorPrimaryText</item>
<item name="android:textColorSecondary">@color/colorSecondaryText</item> <item name="android:textColorSecondary">@color/colorSecondaryText</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay</item> <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
</style> </style>
<style name="CustomShowcaseTheme" parent="ShowcaseView"> <style name="CustomShowcaseTheme" parent="ShowcaseView">

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android"> <paths>
<files-path name="exports" path="exports/"/> <files-path name="exports" path="exports/"/>
</paths> </paths>

View File

@ -1,64 +1,58 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <android.support.v7.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<SwitchPreferenceCompat <android.support.v7.preference.SwitchPreferenceCompat
android:defaultValue="true" android:defaultValue="true"
android:key="wifi_only" android:key="wifi_only"
android:summary="@string/wifi_only_summary" android:summary="@string/wifi_only_summary"
android:title="@string/wifi_only"/> android:title="@string/wifi_only" />
<SwitchPreferenceCompat <android.support.v7.preference.SwitchPreferenceCompat
android:defaultValue="true" android:defaultValue="true"
android:key="request_acknowledgements" android:key="request_acknowledgements"
android:summary="@string/request_acknowledgements_summary" android:summary="@string/request_acknowledgements_summary"
android:title="@string/request_acknowledgements"/> android:title="@string/request_acknowledgements" />
<EditTextPreference <android.support.v7.preference.EditTextPreference
android:inputType="textUri" android:inputType="textUri"
android:key="trusted_node" android:key="trusted_node"
android:summary="@string/trusted_node_summary" android:summary="@string/trusted_node_summary"
android:title="@string/trusted_node"/> android:title="@string/trusted_node" />
<EditTextPreference <android.support.v7.preference.EditTextPreference
android:defaultValue="120" android:defaultValue="120"
android:inputType="number" android:inputType="number"
android:key="sync_timeout" android:key="sync_timeout"
android:summary="@string/sync_timeout_summary" android:summary="@string/sync_timeout_summary"
android:title="@string/sync_timeout"/> android:title="@string/sync_timeout" />
<SwitchPreferenceCompat <android.support.v7.preference.SwitchPreferenceCompat
android:defaultValue="false" android:defaultValue="false"
android:dependency="trusted_node" android:dependency="trusted_node"
android:key="server_pow" android:key="server_pow"
android:summary="@string/server_pow_summary" android:summary="@string/server_pow_summary"
android:title="@string/server_pow" android:title="@string/server_pow" />
/> <android.support.v7.preference.Preference
<Preference
android:key="about" android:key="about"
android:summary="@string/about_summary" android:summary="@string/about_summary"
android:title="@string/about" android:title="@string/about" />
/> <android.support.v7.preference.Preference
<Preference
android:key="help_out" android:key="help_out"
android:summary="@string/help_out_summary" android:summary="@string/help_out_summary"
android:title="@string/help_out"> android:title="@string/help_out">
<intent <intent
android:action="android.intent.action.VIEW" android:action="android.intent.action.VIEW"
android:data="@string/help_out_link"/> android:data="@string/help_out_link" />
</Preference> </android.support.v7.preference.Preference>
<Preference <android.support.v7.preference.Preference
android:key="cleanup" android:key="cleanup"
android:title="@string/cleanup"
android:summary="@string/cleanup_summary" android:summary="@string/cleanup_summary"
/> android:title="@string/cleanup" />
<Preference <android.support.v7.preference.Preference
android:key="export" android:key="export"
android:title="@string/export_data"
android:summary="@string/export_data_summary" android:summary="@string/export_data_summary"
/> android:title="@string/export_data" />
<Preference <android.support.v7.preference.Preference
android:key="import" android:key="import"
android:title="@string/import_data"
android:summary="@string/import_data_summary" android:summary="@string/import_data_summary"
/> android:title="@string/import_data" />
<Preference <android.support.v7.preference.Preference
android:key="status" android:key="status"
android:summary="@string/status_summary" android:summary="@string/status_summary"
android:title="@string/status" android:title="@string/status" />
/> </android.support.v7.preference.PreferenceScreen>
</PreferenceScreen>

View File

@ -17,7 +17,7 @@
package ch.dissem.bitmessage.repository package ch.dissem.bitmessage.repository
import android.os.Build import android.os.Build
import ch.dissem.apps.abit.BuildConfig import android.os.Build.VERSION_CODES.LOLLIPOP
import ch.dissem.apps.abit.repository.AndroidAddressRepository import ch.dissem.apps.abit.repository.AndroidAddressRepository
import ch.dissem.apps.abit.repository.SqlHelper import ch.dissem.apps.abit.repository.SqlHelper
import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.BitmessageAddress
@ -32,7 +32,7 @@ import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
@Config(sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP), packageName = "ch.dissem.apps.abit") @Config(sdk = [LOLLIPOP], packageName = "ch.dissem.apps.abit")
class AndroidAddressRepositoryTest : TestBase() { class AndroidAddressRepositoryTest : TestBase() {
private val contactA = "BM-2cW7cD5cDQJDNkE7ibmyTxfvGAmnPqa9Vt" private val contactA = "BM-2cW7cD5cDQJDNkE7ibmyTxfvGAmnPqa9Vt"
private val contactB = "BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj" private val contactB = "BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj"

View File

@ -17,7 +17,7 @@
package ch.dissem.bitmessage.repository package ch.dissem.bitmessage.repository
import android.os.Build import android.os.Build
import ch.dissem.apps.abit.BuildConfig import android.os.Build.VERSION_CODES.LOLLIPOP
import ch.dissem.apps.abit.repository.AndroidInventory import ch.dissem.apps.abit.repository.AndroidInventory
import ch.dissem.apps.abit.repository.SqlHelper import ch.dissem.apps.abit.repository.SqlHelper
import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.BitmessageAddress
@ -40,7 +40,7 @@ import org.robolectric.annotation.Config
import java.util.* import java.util.*
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
@Config(sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP), packageName = "ch.dissem.apps.abit") @Config(sdk = [LOLLIPOP], packageName = "ch.dissem.apps.abit")
class AndroidInventoryTest : TestBase() { class AndroidInventoryTest : TestBase() {
private lateinit var inventory: Inventory private lateinit var inventory: Inventory
@ -135,11 +135,12 @@ class AndroidInventoryTest : TestBase() {
} }
private fun getObjectMessage(stream: Long, TTL: Long, payload: ObjectPayload) = ObjectMessage( private fun getObjectMessage(stream: Long, TTL: Long, payload: ObjectPayload) = ObjectMessage(
nonce = ByteArray(8), nonce = ByteArray(8),
expiresTime = now + TTL, expiresTime = now + TTL,
stream = stream, stream = stream,
payload = payload payload = payload
) )
private val getPubkey: GetPubkey = GetPubkey(BitmessageAddress("BM-2cW7cD5cDQJDNkE7ibmyTxfvGAmnPqa9Vt")) private val getPubkey: GetPubkey =
GetPubkey(BitmessageAddress("BM-2cW7cD5cDQJDNkE7ibmyTxfvGAmnPqa9Vt"))
} }

View File

@ -17,6 +17,7 @@
package ch.dissem.bitmessage.repository package ch.dissem.bitmessage.repository
import android.os.Build import android.os.Build
import android.os.Build.VERSION_CODES.LOLLIPOP
import ch.dissem.apps.abit.repository.AndroidLabelRepository import ch.dissem.apps.abit.repository.AndroidLabelRepository
import ch.dissem.apps.abit.repository.SqlHelper import ch.dissem.apps.abit.repository.SqlHelper
import ch.dissem.bitmessage.entity.valueobject.Label import ch.dissem.bitmessage.entity.valueobject.Label
@ -30,7 +31,7 @@ import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
@Config(sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP), packageName = "ch.dissem.apps.abit") @Config(sdk = [LOLLIPOP], packageName = "ch.dissem.apps.abit")
class AndroidLabelRepositoryTest : TestBase() { class AndroidLabelRepositoryTest : TestBase() {
private lateinit var repo: LabelRepository private lateinit var repo: LabelRepository

View File

@ -16,7 +16,7 @@
package ch.dissem.bitmessage.repository package ch.dissem.bitmessage.repository
import android.os.Build import android.os.Build.VERSION_CODES.LOLLIPOP
import ch.dissem.apps.abit.repository.AndroidAddressRepository import ch.dissem.apps.abit.repository.AndroidAddressRepository
import ch.dissem.apps.abit.repository.AndroidLabelRepository import ch.dissem.apps.abit.repository.AndroidLabelRepository
import ch.dissem.apps.abit.repository.AndroidMessageRepository import ch.dissem.apps.abit.repository.AndroidMessageRepository
@ -31,7 +31,6 @@ import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding
import ch.dissem.bitmessage.entity.valueobject.Label import ch.dissem.bitmessage.entity.valueobject.Label
import ch.dissem.bitmessage.entity.valueobject.PrivateKey import ch.dissem.bitmessage.entity.valueobject.PrivateKey
import ch.dissem.bitmessage.entity.valueobject.extended.Message import ch.dissem.bitmessage.entity.valueobject.extended.Message
import ch.dissem.bitmessage.ports.LabelRepository
import ch.dissem.bitmessage.ports.MessageRepository import ch.dissem.bitmessage.ports.MessageRepository
import ch.dissem.bitmessage.utils.UnixTime import ch.dissem.bitmessage.utils.UnixTime
import org.hamcrest.BaseMatcher import org.hamcrest.BaseMatcher
@ -48,7 +47,7 @@ import org.robolectric.annotation.Config
import java.util.* import java.util.*
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
@Config(sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP), packageName = "ch.dissem.apps.abit") @Config(sdk = [LOLLIPOP], packageName = "ch.dissem.apps.abit")
class AndroidMessageRepositoryTest : TestBase() { class AndroidMessageRepositoryTest : TestBase() {
private lateinit var contactA: BitmessageAddress private lateinit var contactA: BitmessageAddress
private lateinit var contactB: BitmessageAddress private lateinit var contactB: BitmessageAddress

View File

@ -17,7 +17,7 @@
package ch.dissem.bitmessage.repository package ch.dissem.bitmessage.repository
import android.os.Build import android.os.Build
import ch.dissem.apps.abit.BuildConfig import android.os.Build.VERSION_CODES.LOLLIPOP
import ch.dissem.apps.abit.repository.AndroidNodeRegistry import ch.dissem.apps.abit.repository.AndroidNodeRegistry
import ch.dissem.apps.abit.repository.SqlHelper import ch.dissem.apps.abit.repository.SqlHelper
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress import ch.dissem.bitmessage.entity.valueobject.NetworkAddress
@ -39,7 +39,7 @@ import java.util.*
* as the initial nodes' IP addresses are determined by DNS lookup. * as the initial nodes' IP addresses are determined by DNS lookup.
*/ */
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
@Config(sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP), packageName = "ch.dissem.apps.abit") @Config(sdk = [LOLLIPOP], packageName = "ch.dissem.apps.abit")
class AndroidNodeRegistryTest : TestBase() { class AndroidNodeRegistryTest : TestBase() {
private lateinit var registry: NodeRegistry private lateinit var registry: NodeRegistry

View File

@ -46,7 +46,7 @@ import kotlin.properties.Delegates
* @author Christian Basler * @author Christian Basler
*/ */
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
@Config(sdk = intArrayOf(LOLLIPOP), packageName = "ch.dissem.apps.abit") @Config(sdk = [LOLLIPOP], packageName = "ch.dissem.apps.abit")
class AndroidProofOfWorkRepositoryTest : TestBase() { class AndroidProofOfWorkRepositoryTest : TestBase() {
private lateinit var repo: ProofOfWorkRepository private lateinit var repo: ProofOfWorkRepository
private lateinit var addressRepo: AddressRepository private lateinit var addressRepo: AddressRepository

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