Merge branch 'release/1.0-rc1'

This commit is contained in:
Christian Basler 2018-02-24 08:50:13 +01:00
commit 2bddd0f256
46 changed files with 975 additions and 480 deletions

View File

@ -20,8 +20,8 @@ android {
applicationId "ch.dissem.apps.${appName.toLowerCase()}" applicationId "ch.dissem.apps.${appName.toLowerCase()}"
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 27 targetSdkVersion 27
versionCode 20 versionCode 22
versionName "1.0-beta20" versionName "1.0-rc1"
multiDexEnabled true multiDexEnabled true
} }
compileOptions { compileOptions {
@ -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

@ -43,16 +43,19 @@ class ComposeMessageActivity : AppCompatActivity() {
setHomeButtonEnabled(false) setHomeButtonEnabled(false)
} }
// Display the fragment as the main content. if (supportFragmentManager.findFragmentById(R.id.content) == null) {
val fragment = ComposeMessageFragment() // Display the fragment as the main content.
fragment.arguments = intent.extras val fragment = ComposeMessageFragment()
supportFragmentManager fragment.arguments = intent.extras
.beginTransaction() supportFragmentManager
.replace(R.id.content, fragment) .beginTransaction()
.commit() .replace(R.id.content, fragment)
.commit()
}
} }
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"
@ -62,10 +65,13 @@ class ComposeMessageActivity : AppCompatActivity() {
const val EXTRA_PARENT = "ch.dissem.abit.Message.PARENT" const val EXTRA_PARENT = "ch.dissem.abit.Message.PARENT"
fun launchReplyTo(fragment: Fragment, item: Plaintext) = fun launchReplyTo(fragment: Fragment, item: Plaintext) =
fragment.startActivity(getReplyIntent( fragment.startActivity(
ctx = fragment.activity ?: throw IllegalStateException("Fragment not attached to an activity"), getReplyIntent(
item = item ctx = fragment.activity
)) ?: throw IllegalStateException("Fragment not attached to an activity"),
item = item
)
)
fun launchReplyTo(activity: Activity, item: Plaintext) = fun launchReplyTo(activity: Activity, item: Plaintext) =
activity.startActivity(getReplyIntent(activity, item)) activity.startActivity(getReplyIntent(activity, item))
@ -89,15 +95,20 @@ class ComposeMessageActivity : AppCompatActivity() {
} }
replyIntent.putExtra(EXTRA_PARENT, item) replyIntent.putExtra(EXTRA_PARENT, item)
item.subject?.let { subject -> item.subject?.let { subject ->
val prefix: String = if (subject.length >= 3 && subject.substring(0, 3).equals("RE:", ignoreCase = true)) { val prefix: String = if (subject.length >= 3 && subject.substring(0, 3).equals(
"RE:",
ignoreCase = true
)) {
"" ""
} else { } else {
"RE: " "RE: "
} }
replyIntent.putExtra(EXTRA_SUBJECT, prefix + subject) replyIntent.putExtra(EXTRA_SUBJECT, prefix + subject)
} }
replyIntent.putExtra(EXTRA_CONTENT, replyIntent.putExtra(
"\n\n------------------------------------------------------\n" + item.text!!) EXTRA_CONTENT,
"\n\n------------------------------------------------------\n" + item.text!!
)
return replyIntent return replyIntent
} }
} }

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,108 @@ 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 -> retainInstance = true
var id = arguments.getSerializable(EXTRA_IDENTITY) as? BitmessageAddress arguments?.apply {
if (context != null && (id == null || id.privateKey == null)) { val draft = getSerializable(EXTRA_DRAFT) as Plaintext?
id = Singleton.getIdentity(context!!) if (draft != null) {
} this@ComposeMessageFragment.draft = draft
if (id?.privateKey != null) { identity = draft.from
identity = id recipient = draft.to
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 +187,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 +215,77 @@ 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()
}
override fun onDestroyView() {
identity = sender_input.selectedItem as BitmessageAddress
// recipient is set when one is selected
subject = subject_input.text?.toString() ?: ""
content = body_input.text?.toString() ?: ""
super.onDestroyView()
}
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

@ -25,12 +25,12 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import com.h6ah4i.android.widget.advrecyclerview.decoration.SimpleListDividerDecorator
import ch.dissem.apps.abit.adapter.AddressSelectorAdapter import ch.dissem.apps.abit.adapter.AddressSelectorAdapter
import ch.dissem.apps.abit.service.Singleton import ch.dissem.apps.abit.service.Singleton
import ch.dissem.bitmessage.wif.WifImporter import ch.dissem.bitmessage.wif.WifImporter
import com.h6ah4i.android.widget.advrecyclerview.decoration.SimpleListDividerDecorator
import org.ini4j.InvalidFileFormatException
import org.jetbrains.anko.longToast
/** /**
* @author Christian Basler * @author Christian Basler
@ -39,8 +39,12 @@ class ImportIdentitiesFragment : Fragment() {
private lateinit var adapter: AddressSelectorAdapter private lateinit var adapter: AddressSelectorAdapter
private lateinit var importer: WifImporter private lateinit var importer: WifImporter
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = override fun onCreateView(
inflater.inflate(R.layout.fragment_import_select_identities, container, false) inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View =
inflater.inflate(R.layout.fragment_import_select_identities, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -48,17 +52,29 @@ class ImportIdentitiesFragment : Fragment() {
val wifData = arguments.getString(WIF_DATA) val wifData = arguments.getString(WIF_DATA)
val bmc = Singleton.getBitmessageContext(activity) val bmc = Singleton.getBitmessageContext(activity)
importer = WifImporter(bmc, wifData) try {
importer = WifImporter(bmc, wifData)
} catch (e: InvalidFileFormatException) {
longToast(R.string.invalid_wif_file)
activity.finish()
return
}
adapter = AddressSelectorAdapter(importer.getIdentities()) adapter = AddressSelectorAdapter(importer.getIdentities())
val layoutManager = LinearLayoutManager(activity, val layoutManager = LinearLayoutManager(
LinearLayoutManager.VERTICAL, activity,
false) LinearLayoutManager.VERTICAL,
false
)
val recyclerView = view.findViewById<RecyclerView>(R.id.recycler_view) val recyclerView = view.findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.layoutManager = layoutManager recyclerView.layoutManager = layoutManager
recyclerView.adapter = adapter recyclerView.adapter = adapter
recyclerView.addItemDecoration(SimpleListDividerDecorator( recyclerView.addItemDecoration(
ContextCompat.getDrawable(activity, R.drawable.list_divider_h), true)) SimpleListDividerDecorator(
ContextCompat.getDrawable(activity, R.drawable.list_divider_h), true
)
)
view.findViewById<Button>(R.id.finish).setOnClickListener { view.findViewById<Button>(R.id.finish).setOnClickListener {
importer.importAll(adapter.selected) importer.importAll(adapter.selected)
@ -72,6 +88,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,36 @@ 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()
}
if (newValues.isEmpty()) {
try {
newValues.add(BitmessageAddress(prefix.toString()))
} catch (_: Exception) {
}
}
results.values = newValues results.values = newValues
results.count = newValues.size results.count = newValues.size
@ -125,4 +141,6 @@ class ContactAdapter(ctx: Context) : BaseAdapter(), Filterable {
} }
} }
} }
fun indexOf(element: BitmessageAddress) = originalData.indexOf(element)
} }

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

@ -68,15 +68,15 @@ class AndroidAddressRepository(private val sql: SqlHelper) : AddressRepository {
* Returns the contacts in the following order: * Returns the contacts in the following order:
* *
* * Subscribed addresses come first * * Subscribed addresses come first
* * Addresses with Aliases (alphabetically) * * Addresses with aliases (alphabetically)
* * Addresses (alphabetically) * * Addresses without aliases are omitted
* *
* *
* @return the ordered list of ids (address strings) * @return the ordered list of ids (address strings)
*/ */
fun getContactIds(): List<String> = findIds( fun getContactIds(): List<String> = findIds(
"private_key IS NULL OR chan = '1'", "($COLUMN_PRIVATE_KEY IS NULL OR $COLUMN_CHAN = '1') AND $COLUMN_ALIAS IS NOT NULL",
"$COLUMN_SUBSCRIBED DESC, $COLUMN_ALIAS IS NULL, $COLUMN_ALIAS, $COLUMN_ADDRESS" "$COLUMN_SUBSCRIBED DESC, $COLUMN_ALIAS, $COLUMN_ADDRESS"
) )
private fun findIds(where: String, orderBy: String): List<String> { private fun findIds(where: String, orderBy: String): List<String> {

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

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

@ -0,0 +1,135 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:tools="http://schemas.android.com/tools"><string name="app_name">Abit</string>
<string name="about_app">عميل Bitmessage أندرويد</string>
<string name="title_message_detail">رسالة</string>
<string name="title_subscription_detail">اشتراك</string>
<string name="title_chan_detail">قناة</string>
<string name="title_identity_detail">هوية</string>
<string name="title_contact_detail">جهة إتصال</string>
<string name="bitmessage_full_node">عقدة Bitmessage</string>
<string name="settings">إعدادات</string>
<string name="wifi_only">Wi-Fi فقط</string>
<string name="wifi_only_summary">منع الإتصال من خلال بيانات الهاتف</string>
<string name="to">إلى</string>
<string name="subject">الموضوع</string>
<string name="manage_identity">إدارة الهويات</string>
<string name="add_identity">إضافة هوية</string>
<string name="add_identity_summary">إنشاء هوية جديدة</string>
<string name="create_identity_description">إنشاء هوية عشوائية جديدة</string>
<string name="import_identity_description">استيراد هوية موجودة من PyBitmessage او من تصدير</string>
<string name="add_deterministic_address">هوية حتمية</string>
<string name="add_deterministic_address_description">إنشاء أو إعادة إنشاء هوية حتمية</string>
<string name="add_chan">إضافة قناة</string>
<string name="add_chan_description">إنشاء أو الإنضمام إلى قناة</string>
<string name="title_activity_open_bitmessage_link">إستيراد جهة اتصال</string>
<string name="subscribe">إشتراك</string>
<string name="do_import">استيراد</string>
<string name="cancel">إلغاء</string>
<string name="n_new_messages" tools:ignore="PluralsCandidate">رسائل جديدة</string>
<string name="reply">رد</string>
<string name="delete">حذف</string>
<string name="empty_trash">أفرغ المهملات</string>
<string name="trusted_node">عقدة موثوقة</string>
<string name="trusted_node_summary">استخدام العقدة في التزامن</string>
<string name="write_message">كتابة رسالة</string>
<string name="full_node">عقدة كاملة</string>
<string name="send">إرسال</string>
<string name="connection_info_disconnected">غير متصل</string>
<string name="connection_info_pending">يتصل…</string>
<string name="proof_of_work_title">إثبات العمل</string>
<string name="proof_of_work_text_0">جاري إثبات العمل لإرسال الرسالة</string>
<string name="inbox">صندوق الوارد</string>
<string name="draft">مسودات</string>
<string name="sent">المرسل</string>
<string name="unread">غير مقروء</string>
<string name="trash">مُهملات</string>
<string name="label">تسمية</string>
<string name="connection_info_1">بث #%1$d : توصيل واحد</string>
<string name="connection_info_n" tools:ignore="PluralsCandidate">"بث #%1$d: %2$d توصيلات"</string>
<string name="broadcast">إذاعة</string>
<string name="mark_unread">حدد كمقروء</string>
<string name="archive">أرشيف</string>
<string name="stream_number">بث #%d</string>
<string name="sync_timeout">انتهاء مهلة التزامن</string>
<string name="sync_timeout_summary">مهلة الإتصال بالثواني</string>
<string name="proof_of_work_text_n" tools:ignore="PluralsCandidate">جاري إثبات العمل لإرسال الرسالة (%1$d في قائمة الانتظار)</string>
<string name="error_invalid_sync_port">إعدادات منفذ التزامن غير صالحة</string>
<string name="compose_body_hint">كتابة رسالة</string>
<string name="contacts_and_subscriptions">جهات اتصال</string>
<string name="subscribed">تم الاشتراك</string>
<string name="server_pow">خادم إثبات العمل</string>
<string name="server_pow_summary">عقدة موثوقة تقوم بإثبات العمل</string>
<string name="full_node_warning">تشغيل عقدة Bitmessage كاملة يستهلك الكثير من البيانات، مما قد يكون مكلفًا لبيانات الهاتف، هل أنت متأكد أنك تريد تشغيل عقدة كاملة؟</string>
<string name="about">عن Abit</string>
<string name="about_summary">التبعيات مفتوحة المصدر.</string>
<string name="title_activity_status">تصحيح الأخطاء</string>
<string name="status">تصحيح الأخطاء</string>
<string name="status_summary">معلومات تقنية</string>
<string name="alias_default_identity">أنا</string>
<string name="pubkey_available">مفتاح التشفير المعلن متاح</string>
<string name="pubkey_not_available">مفتاح التشفير المعلن غير متاح بعد</string>
<string name="alt_qr_code">شفرة QR</string>
<string name="add_identity_warning">حيازة عدة هويات يحتاج إلى المزيد من الموارد، إذا كنت متأكدًأ أنك تريد إضافة هويات، أختر تمامًا ما تريد عمله:</string>
<string name="share">نشر</string>
<string name="delete_identity_warning">هل أنت متأكد من أنك تريد حذف هذه الهوية؟ لن تستطيع استقبال أي رسائل مرسلة لهذا العنوان ولا يمكنك التراجع عن هذه العملية.</string>
<string name="delete_contact_warning">هل أنت متأكد أنك تريد حذف جهة الاتصال هذه؟</string>
<string name="scan_qr_code">مسح شفرة QR</string>
<string name="create_contact">إنشاء جهة إتصال</string>
<string name="full_node_description">لا يمكنك إرسال أو استقبال رسائل إلا إذا قمت بتشغيل عقدة كاملة. رجاء العلم أن ذلك يستخدم الكثير من الموارد وبيانات الإنترنت. يمكنك تعيين عقدة موثوقة في الإعدادات، ويمكنك إطلاق عقدتك الخاصة.</string>
<string name="address">عنوان Bitmessage</string>
<string name="error_illegal_address">قد يكون هناك خطأ بالكتابة</string>
<string name="export">تصدير</string>
<string name="confirm_export">هل أنت متأكد أنك تريد تصدير هويتك؟ سيحتوي التصدير على مفاتيح خاصة غير مشفرة.</string>
<string name="compose_message">إنشاء</string>
<string name="passphrase">جملة المرور</string>
<string name="help_out">ادعم التطوير</string>
<string name="help_out_summary">هل تريد المساعدة؟ ألق نظرة هنا</string>
<string name="help_out_link">https://dissem.github.io/Abit/helping-out</string>
<string name="toast_long_running_operation">قد يستغرق بضع دقائق</string>
<string name="toast_identity_created">تم إنشاء الهوية</string>
<string name="toast_identities_created">تم إنشاء الهويات</string>
<string name="toast_chan_created">تم إنشاء القناة</string>
<string name="deterministic_address_warning">تأكد من تذكر هذه الإعدادات جيدًا عندما تعيد إنشاء الهوية الختمية.</string>
<string name="number_of_identities">عدد الهويات المنشئة</string>
<string name="shorter">ابحث عن عنوان أقصر</string>
<string name="wif_string">محتويات keys.dat</string>
<string name="next">استمرار</string>
<string name="title_import_identity">استيراد هوية</string>
<string name="open_file">فتح ملف</string>
<string name="error_loading_data">خطأ في تحميل البيانات</string>
<string name="select_file_title">اختيار ملف</string>
<string name="select_identities_to_import">اختر الهويات التي تريد استيرادها:</string>
<string name="import_input_description">يمكنك لصق محتويات تصدير او ملف keys.dat</string>
<string name="full_node_stop">إيقاف العقدة</string>
<string name="full_node_restart">اعادة تشغيل العقدة</string>
<string name="use_mobile_network">استخدم شبكة بيانات الهاتف</string>
<string name="personal_message">رسالة</string>
<string name="no_identity_warning">أعد المحاولة عند وجود هوية متاحة.</string>
<string name="status_public_key">تم طلب المفتاح المعلن</string>
<string name="status_sent_acknowledged">تم تسليم الرسالة</string>
<string name="status_draft">مسودة</string>
<string name="status_sent">تم الارسال</string>
<string name="status_received">تم الاستقبال</string>
<string name="error_unsupported_encoding">ترميز غير مدعوم، استخدم الترميز البسيط.</string>
<string name="select_encoding_warning">الترميز الموسع هو نظام رسالة جديد غير منتشر الدعم بعد، لكن يحتوي مميزات مختلفة. للبقاء على النظام الأكثر انتشارًا، اختر الترميز البسيط.</string>
<string name="select_encoding_title">اختر ترميز الرسالة</string>
<string name="cleanup">تطهير</string>
<string name="cleanup_summary">حذف المدخلات المنتهية</string>
<string name="cleanup_notification_start">تم بدأ التنظيف</string>
<string name="cleanup_notification_end">تم الانتهاء من التنظيف</string>
<string name="wait_for_wifi">انتظر Wi-Fi</string>
<string name="error_msg_recipient_missing">حدد المرسل إليه</string>
<string name="export_data">تصدير</string>
<string name="export_data_summary">تصدير جميع الرسائل وجهات الاتصال (الهويات غير مضمنة)</string>
<string name="import_data">استيراد</string>
<string name="import_data_summary">استيراد الرسائل وجهات الاتصال (الهويات غير مضمنة)</string>
<string name="request_acknowledgements">اطلب التبليغ بوصول الرسائل</string>
<string name="request_acknowledgements_summary">التبليغ يسمح بالتأكد من وصول الرسالة، لكن يحتاج المزيد من الوقت</string>
<string name="got_it">فهمت</string>
<string name="select_encoding">اختيار الترميز</string>
<string name="from">من</string>
<string name="invalid_wif_file">لم يتم معالجة الملف</string>
<string name="outbox">صندوق المرسل</string>
<string name="broadcasts">الإذاعات</string>
</resources>

View File

@ -1,25 +0,0 @@
<?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.
-->
<resources>
<string name="inbox">Posteingang</string>
<string name="draft">Entwürfe</string>
<string name="sent">Gesendet</string>
<string name="unread">Ungelesen</string>
<string name="trash">Papierkorb</string>
<string name="broadcasts">Broadcasts</string>
</resources>

View File

@ -104,7 +104,6 @@ Als Alternative kann in den Einstellungen ein vertrauenswürdiger Knoten konfigu
<string name="title_chan_detail">Chan</string> <string name="title_chan_detail">Chan</string>
<string name="title_contact_detail">Kontakt</string> <string name="title_contact_detail">Kontakt</string>
<string name="title_identity_detail">Identität</string> <string name="title_identity_detail">Identität</string>
<string name="outbox">Postausgang</string>
<string name="status_draft">Entwurf</string> <string name="status_draft">Entwurf</string>
<string name="status_public_key">öffentlicher Schlüssel angefordert</string> <string name="status_public_key">öffentlicher Schlüssel angefordert</string>
<string name="status_received">empfangen</string> <string name="status_received">empfangen</string>
@ -127,4 +126,13 @@ 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>
<string name="invalid_wif_file">Die Datei kann nicht verarbeitet werden</string>
<string name="inbox">Posteingang</string>
<string name="outbox">Postausgang</string>
<string name="draft">Entwürfe</string>
<string name="sent">Gesendet</string>
<string name="unread">Ungelesen</string>
<string name="trash">Papierkorb</string>
<string name="broadcasts">Broadcasts</string>
</resources> </resources>

View File

@ -0,0 +1,135 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:tools="http://schemas.android.com/tools"><string name="app_name">Abit</string>
<string name="about_app">Un client Bitmessage pour Android</string>
<string name="title_message_detail">Message</string>
<string name="title_subscription_detail">Abonnement</string>
<string name="title_chan_detail">Canal</string>
<string name="title_identity_detail">Identité</string>
<string name="title_contact_detail">Contact</string>
<string name="bitmessage_full_node">Nœud de bitmessage</string>
<string name="settings">Paramètres</string>
<string name="wifi_only">Seulement Wi-Fi</string>
<string name="wifi_only_summary">Ne vous connectez pas au réseau mobile</string>
<string name="to">A</string>
<string name="subject">Objet</string>
<string name="manage_identity">Gérer l\'identité</string>
<string name="add_identity">Ajouter une identité</string>
<string name="add_identity_summary">Créer une nouvelle identité</string>
<string name="create_identity_description">Créer une nouvelle identité aléatoire</string>
<string name="import_identity_description">Importation d\'une identité existante de PyBitmessage ou d\'une exportation</string>
<string name="add_deterministic_address">Identité déterministe</string>
<string name="add_deterministic_address_description">Créer ou recréer une identité déterministe</string>
<string name="add_chan">Ajouter un canal</string>
<string name="add_chan_description">Créer ou rejoindre un canal</string>
<string name="title_activity_open_bitmessage_link">Importation de contact</string>
<string name="connection_info_1">"Flux nº%1$d: une connexion"</string>
<string name="connection_info_n" tools:ignore="PluralsCandidate">"Flux nº%1$d: %2$d connexions"</string>
<string name="label">Étiquette</string>
<string name="subscribe">Inscrivez-vous</string>
<string name="do_import">Importation</string>
<string name="cancel">Annuler</string>
<string name="broadcast">Diffusion</string>
<string name="n_new_messages" tools:ignore="PluralsCandidate">%d messages nouveaux</string>
<string name="reply">Répondre</string>
<string name="delete">Supprimer</string>
<string name="mark_unread">Marquer non lu</string>
<string name="archive">Archive</string>
<string name="stream_number">"Flux nº%d"</string>
<string name="trusted_node">Nœud de confiance</string>
<string name="trusted_node_summary">Cette adresse est utilisée pour la synchronisation</string>
<string name="sync_timeout">Limitation du temps de synchronisation</string>
<string name="write_message">Écrire un message</string>
<string name="full_node">Nœud actif</string>
<string name="send">Transmission</string>
<string name="connection_info_disconnected">Déconnecté</string>
<string name="connection_info_pending">La connexion est établie…</string>
<string name="proof_of_work_title">Preuve de travail</string>
<string name="proof_of_work_text_0">Faire du travail pour envoyer la message</string>
<string name="proof_of_work_text_n" tools:ignore="PluralsCandidate">Faire du travail pour envoyer la message (%1$d en file d\'attente)</string>
<string name="error_invalid_sync_port">Port non valide dans les paramètres de synchronisation : %s</string>
<string name="compose_body_hint">Écrire un message</string>
<string name="contacts_and_subscriptions">Contacts</string>
<string name="subscribed">Souscrit</string>
<string name="full_node_warning">L\'exécution d\'un nœud Bitmessage actif consomme beaucoup de trafic, ce qui peut coûter cher sur un réseau mobile. Êtes-vous sûr de vouloir démarrer un nœud actif ?</string>
<string name="about">À propos d\'Abit</string>
<string name="about_summary">Les dépendances Open Source.</string>
<string name="title_activity_status">Déboguage</string>
<string name="status">Déboguage</string>
<string name="status_summary">Informations techniques</string>
<string name="alias_default_identity">Moi</string>
<string name="pubkey_available">Clé publique disponible</string>
<string name="pubkey_not_available">Clé publique pas encore disponible</string>
<string name="alt_qr_code">Code QR</string>
<string name="add_identity_warning">Avoir plus d\'identités exigera plus de ressources. Si vous êtes sûr de vouloir ajouter une identité, veuillez choisir la procédure :</string>
<string name="delete_identity_warning">Êtes-vous sûr de vouloir effacer cette identité ? Les messages envoyés à cette adresse ne peuvent plus être reçus et il n\'est pas possible d\'annuler cette action.</string>
<string name="delete_contact_warning">Êtes-vous sûr de vouloir supprimer ce contact?</string>
<string name="scan_qr_code">Scanner code QR</string>
<string name="create_contact">Créer un contact</string>
<string name="full_node_description">Vous ne pouvez pas recevoir ou envoyer de messages sans lancer un nœud actif. Mais sachez que cela utilise beaucoup de ressources et de trafic internet. Comme alternative, vous pouvez configurer un nœud de confiance dans les paramètres, mais à partir de maintenant vous devrez déployer votre propre nœud.</string>
<string name="address">Adresse Bitmessage</string>
<string name="error_illegal_address">C\'est peut-être une faute de frappe</string>
<string name="export">Exportation</string>
<string name="confirm_export">Voulez-vous vraiment exporter votre identité ? L\'exportation contiendra les clés privées non chiffrées.</string>
<string name="passphrase">Phrase de passe</string>
<string name="help_out">Soutenir le développement</string>
<string name="help_out_summary">Apprenez comment soutenir le développement d\'Abit</string>
<string name="toast_long_running_operation">Cela peut prendre quelques minutes</string>
<string name="toast_identity_created">Identité créée</string>
<string name="toast_identities_created">Les identités ont été créées</string>
<string name="toast_chan_created">Canal créé</string>
<string name="deterministic_address_warning">N\'oubliez pas ces paramètres et assurez-vous qu\'ils sont corrects lorsque vous recréez une adresse déterministe.</string>
<string name="number_of_identities">Nombre d\'identités à créer</string>
<string name="shorter">Recherche d\'adresses plus courtes</string>
<string name="wif_string">WIF / contenu de keys.dat</string>
<string name="next">Continuer</string>
<string name="title_import_identity">Importation d\'identité</string>
<string name="open_file">Ouvrir un fichier</string>
<string name="error_loading_data">Erreur lors du chargement des données</string>
<string name="select_file_title">Sélectionnez un fichier</string>
<string name="select_identities_to_import">Veuillez sélectionner les identités à importer :</string>
<string name="import_input_description">Vous pouvez simplement insérer le contenu d\'une exportation ou d\'un fichier \'keys.dat\'</string>
<string name="full_node_stop">arrêter le nœud</string>
<string name="full_node_restart">redémarrer le nœud</string>
<string name="use_mobile_network">Utiliser le réseau mobile</string>
<string name="personal_message">Message</string>
<string name="empty_trash">Vider les ordures</string>
<string name="sync_timeout_summary">Délai d\'expiration en secondes</string>
<string name="server_pow">POW sur le serveur</string>
<string name="server_pow_summary">Le nœud de confiance fait la preuve de travail</string>
<string name="share">Partager</string>
<string name="compose_message">Composer</string>
<string name="no_identity_warning">Veuillez réessayer dès qu\'une identité est disponible.</string>
<string name="status_public_key">clé publique demandée</string>
<string name="status_sent_acknowledged">Réception confirmée</string>
<string name="status_draft">brouillon</string>
<string name="status_sent">envoyé</string>
<string name="status_received">reçu</string>
<string name="error_unsupported_encoding">Encodage non supporté \'%s\', \'SIMPLE\' est utilisé.</string>
<string name="select_encoding_warning">\'EXTENDED\' est un nouveau format de message qui ne fonctionne pas encore partout, mais qui a des avantages intéressants. Sélectionnez \'SIMPLE\' pour être sûr.</string>
<string name="select_encoding_title">Sélectionner le codage des messages</string>
<string name="cleanup">Nettoyage</string>
<string name="cleanup_summary">Supprimer les entrées d\'inventaire obsolètes</string>
<string name="cleanup_notification_start">Démarrage du nettoyage</string>
<string name="cleanup_notification_end">Nettoyage terminé</string>
<string name="wait_for_wifi">Attendez la connexion Wi-Fi</string>
<string name="error_msg_recipient_missing">Veuillez indiquer un destinataire</string>
<string name="export_data">Exportation</string>
<string name="export_data_summary">Exporter tous les messages et contacts (mais pas les identités)</string>
<string name="import_data">Importation</string>
<string name="import_data_summary">Importer des messages et des contacts (mais pas des identités)</string>
<string name="request_acknowledgements">Demande d\'accusés de réception</string>
<string name="request_acknowledgements_summary">Les accusés de réception permettent de s\'assurer qu\'un message a bien été reçu, mais il faut plus de temps pour l\'envoyer</string>
<string name="got_it">J\'ai compris</string>
<string name="select_encoding">Choix du codage</string>
<string name="help_out_link">https://dissem.github.io/Abit/aider</string>
<string name="from">De</string>
<string name="invalid_wif_file">Le fichier ne peut pas être traité</string>
<string name="inbox">Boîte de réception</string>
<string name="outbox">Boîte d\'envoi</string>
<string name="draft">Brouillons</string>
<string name="sent">Envoyé</string>
<string name="unread">Non lues</string>
<string name="trash">Corbeille</string>
<string name="broadcasts">Diffusions</string>
</resources>

View File

@ -1,25 +0,0 @@
<?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.
-->
<resources>
<string name="inbox">Inbox</string>
<string name="draft">Drafts</string>
<string name="sent">Sent</string>
<string name="unread">Unread</string>
<string name="trash">Trash</string>
<string name="broadcasts">Broadcasts</string>
</resources>

View File

@ -108,7 +108,6 @@ As an alternative you could configure a trusted node in the settings, but as of
<string name="status_draft">draft</string> <string name="status_draft">draft</string>
<string name="status_sent">sent</string> <string name="status_sent">sent</string>
<string name="status_received">received</string> <string name="status_received">received</string>
<string name="outbox">Outbox</string>
<string name="error_unsupported_encoding">Unsupported encoding %s, using SIMPLE instead.</string> <string name="error_unsupported_encoding">Unsupported encoding %s, using SIMPLE instead.</string>
<string name="select_encoding_warning">EXTENDED is a new message format that\'s not widely supported yet but has some interesting features. To stay on the save side, select SIMPLE.</string> <string name="select_encoding_warning">EXTENDED is a new message format that\'s not widely supported yet but has some interesting features. To stay on the save side, select SIMPLE.</string>
<string name="select_encoding_title">Select Message Encoding</string> <string name="select_encoding_title">Select Message Encoding</string>
@ -126,4 +125,13 @@ 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>
<string name="invalid_wif_file">The file could not be processed</string>
<string name="inbox">Inbox</string>
<string name="outbox">Outbox</string>
<string name="draft">Drafts</string>
<string name="sent">Sent</string>
<string name="unread">Unread</string>
<string name="trash">Trash</string>
<string name="broadcasts">Broadcasts</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()