Switch to API level 27 and updated libraries

This commit is contained in:
Christian Basler 2017-10-31 07:50:57 +01:00
parent f58a22dadb
commit 072f732924
68 changed files with 678 additions and 759 deletions

View File

@ -8,18 +8,18 @@ ext {
} }
if (project.hasProperty("project.configs") if (project.hasProperty("project.configs")
&& new File(project.property("project.configs") + appName + ".gradle").exists()) { && new File(project.property("project.configs") + appName + ".gradle").exists()) {
apply from: project.property("project.configs") + appName + ".gradle"; apply from: project.property("project.configs") + appName + ".gradle"
} }
//noinspection GroovyMissingReturnStatement //noinspection GroovyMissingReturnStatement
android { android {
compileSdkVersion 25 compileSdkVersion 27
buildToolsVersion "25.0.2" buildToolsVersion "26.0.2"
defaultConfig { defaultConfig {
applicationId "ch.dissem.apps." + appName.toLowerCase() applicationId "ch.dissem.apps." + appName.toLowerCase()
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 25 targetSdkVersion 27
versionCode 17 versionCode 17
versionName "1.0-beta17" versionName "1.0-beta17"
multiDexEnabled true multiDexEnabled true
@ -28,6 +28,9 @@ android {
sourceCompatibility JavaVersion.VERSION_1_7 sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7
} }
lintOptions {
abortOnError false
}
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled false
@ -36,69 +39,74 @@ android {
signingConfig signingConfigs.release signingConfig signingConfigs.release
} }
} }
testOptions {
unitTests {
includeAndroidResources = true
}
}
} }
//ext.jabitVersion = '2.0.4' //ext.jabitVersion = '2.0.4'
ext.jabitVersion = 'development-SNAPSHOT' ext.jabitVersion = 'development-SNAPSHOT'
ext.supportVersion = '25.3.1' ext.supportVersion = '27.0.0'
dependencies { dependencies {
compile fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
compile "org.jetbrains.anko:anko:$anko_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation "org.jetbrains.anko:anko:$anko_version"
compile "com.android.support:appcompat-v7:$supportVersion" implementation "com.android.support:appcompat-v7:$supportVersion"
compile "com.android.support:preference-v7:$supportVersion" implementation "com.android.support:preference-v7:$supportVersion"
compile "com.android.support:support-v4:$supportVersion" implementation "com.android.support:cardview-v7:$supportVersion"
compile "com.android.support:design:$supportVersion" implementation "com.android.support:support-v4:$supportVersion"
compile "com.android.support:multidex:1.0.1" implementation "com.android.support:design:$supportVersion"
implementation "com.android.support:multidex:1.0.2"
compile "ch.dissem.jabit:jabit-core:$jabitVersion" implementation "ch.dissem.jabit:jabit-core:$jabitVersion"
compile "ch.dissem.jabit:jabit-networking:$jabitVersion" implementation "ch.dissem.jabit:jabit-networking:$jabitVersion"
compile "ch.dissem.jabit:jabit-cryptography-spongy:$jabitVersion" implementation "ch.dissem.jabit:jabit-cryptography-spongy:$jabitVersion"
compile "ch.dissem.jabit:jabit-extensions:$jabitVersion" implementation "ch.dissem.jabit:jabit-extensions:$jabitVersion"
compile "ch.dissem.jabit:jabit-wif:$jabitVersion" implementation "ch.dissem.jabit:jabit-wif:$jabitVersion"
compile "ch.dissem.jabit:jabit-exports:$jabitVersion" implementation "ch.dissem.jabit:jabit-exports:$jabitVersion"
compile 'org.slf4j:slf4j-android:1.7.25' implementation 'org.slf4j:slf4j-android:1.7.25'
compile 'com.mikepenz:materialize:1.0.1@aar' implementation 'com.mikepenz:materialize:1.1.0@aar'
compile('com.mikepenz:materialdrawer:5.9.0@aar') { implementation('com.mikepenz:materialdrawer:5.9.5@aar') {
transitive = true transitive = true
} }
compile('com.mikepenz:aboutlibraries:5.9.5@aar') { implementation('com.mikepenz:aboutlibraries:5.9.7@aar') {
transitive = true transitive = true
} }
compile "com.mikepenz:iconics-core:2.8.3@aar" implementation "com.mikepenz:iconics-core:2.9.3@aar"
compile 'com.mikepenz:google-material-typeface:3.0.1.0.original@aar' implementation "com.mikepenz:iconics-views:2.9.3@aar"
compile 'com.mikepenz:community-material-typeface:1.9.32.1@aar' implementation 'com.mikepenz:google-material-typeface:3.0.1.1.original@aar'
implementation 'com.mikepenz:community-material-typeface:1.9.32.2@aar'
compile 'com.journeyapps:zxing-android-embedded:3.5.0@aar' implementation 'com.journeyapps:zxing-android-embedded:3.5.0@aar'
compile 'com.google.zxing:core:3.3.0' implementation 'com.google.zxing:core:3.3.1'
compile 'com.github.kobakei:MaterialFabSpeedDial:1.1.5' implementation 'com.github.kobakei:MaterialFabSpeedDial:1.1.7'
compile 'com.github.amlcurran.showcaseview:library:5.4.3' implementation 'com.github.amlcurran.showcaseview:library:5.4.3'
compile('com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.10.4@aar') { // FIXME: switch back to com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.10.+ a.s.a.p.
implementation('com.github.h6ah4i:android-advancedrecyclerview:develop-SNAPSHOT@aar') {
transitive = true transitive = true
} }
compile 'com.github.angads25:filepicker:1.1.0' implementation 'com.github.angads25:filepicker:1.1.1'
compile 'com.android.support.constraint:constraint-layout:1.0.2' implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:2.8.9' testImplementation 'org.mockito:mockito-core:2.11.0'
testCompile 'org.hamcrest:hamcrest-library:1.3' testImplementation 'org.hamcrest:hamcrest-library:1.3'
testCompile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" testImplementation 'com.nhaarman:mockito-kotlin-kt1.1:1.5.0'
testCompile 'com.nhaarman:mockito-kotlin-kt1.1:1.5.0' testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
testCompile 'org.robolectric:robolectric:3.4.2' testImplementation 'org.robolectric:robolectric:3.5'
testCompile "org.robolectric:shadows-multidex:3.4-rc2" testImplementation "org.robolectric:shadows-multidex:3.5"
androidTestImplementation "com.android.support:multidex:1.0.2"
} }
idea.module { idea.module {
downloadJavadoc = true downloadJavadoc = true
downloadSources = true downloadSources = true
} }
android {
lintOptions {
abortOnError false
}
}

View File

@ -40,7 +40,7 @@ abstract class AbstractItemListFragment<L, T> : ListFragment(), ListHolder<L> {
private var activatedPosition = ListView.INVALID_POSITION private var activatedPosition = ListView.INVALID_POSITION
private var activateOnItemClick: Boolean = false private var activateOnItemClick: Boolean = false
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
// Restore the previously serialized activated item position. // Restore the previously serialized activated item position.
@ -92,9 +92,9 @@ abstract class AbstractItemListFragment<L, T> : ListFragment(), ListHolder<L> {
} }
} }
override fun onSaveInstanceState(outState: Bundle?) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
if (outState != null && activatedPosition != ListView.INVALID_POSITION) { if (activatedPosition != ListView.INVALID_POSITION) {
// Serialize and persist the activated item position. // Serialize and persist the activated item position.
outState.putInt(STATE_ACTIVATED_POSITION, activatedPosition) outState.putInt(STATE_ACTIVATED_POSITION, activatedPosition)
} }
@ -136,9 +136,7 @@ abstract class AbstractItemListFragment<L, T> : ListFragment(), ListHolder<L> {
* nothing. Used only when this fragment is not attached to an activity. * nothing. Used only when this fragment is not attached to an activity.
*/ */
internal object DummyCallback : ListSelectionListener<Any> { internal object DummyCallback : ListSelectionListener<Any> {
override fun onItemSelected(item: Any) { override fun onItemSelected(item: Any) = Unit // NO OP
// NO OP
}
} }
companion object { companion object {

View File

@ -49,8 +49,10 @@ class AddressDetailFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (arguments.containsKey(ARG_ITEM)) { arguments?.let { arguments ->
item = arguments.getSerializable(ARG_ITEM) as BitmessageAddress if (arguments.containsKey(ARG_ITEM)) {
item = arguments.getSerializable(ARG_ITEM) as BitmessageAddress
}
} }
setHasOptionsMenu(true) setHasOptionsMenu(true)
} }
@ -58,7 +60,7 @@ class AddressDetailFragment : Fragment() {
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.address, menu) inflater.inflate(R.menu.address, menu)
val ctx = activity val ctx = activity!!
Drawables.addIcon(ctx, menu, R.id.write_message, GoogleMaterial.Icon.gmd_mail) Drawables.addIcon(ctx, menu, R.id.write_message, GoogleMaterial.Icon.gmd_mail)
Drawables.addIcon(ctx, menu, R.id.share, GoogleMaterial.Icon.gmd_share) Drawables.addIcon(ctx, menu, R.id.share, GoogleMaterial.Icon.gmd_share)
Drawables.addIcon(ctx, menu, R.id.delete, GoogleMaterial.Icon.gmd_delete) Drawables.addIcon(ctx, menu, R.id.delete, GoogleMaterial.Icon.gmd_delete)
@ -69,7 +71,7 @@ class AddressDetailFragment : Fragment() {
override fun onOptionsItemSelected(menuItem: MenuItem): Boolean { override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
val item = item ?: return false val item = item ?: return false
val ctx = activity val ctx = activity ?: return false
when (menuItem.itemId) { when (menuItem.itemId) {
R.id.write_message -> { R.id.write_message -> {
val identity = Singleton.getIdentity(ctx) val identity = Singleton.getIdentity(ctx)
@ -89,35 +91,35 @@ class AddressDetailFragment : Fragment() {
else else
R.string.delete_contact_warning R.string.delete_contact_warning
AlertDialog.Builder(ctx) AlertDialog.Builder(ctx)
.setMessage(warning) .setMessage(warning)
.setPositiveButton(android.R.string.yes) { _, _ -> .setPositiveButton(android.R.string.yes) { _, _ ->
Singleton.getAddressRepository(ctx).remove(item) Singleton.getAddressRepository(ctx).remove(item)
val mainActivity = MainActivity.getInstance() val mainActivity = MainActivity.getInstance()
if (item.privateKey != null && mainActivity != null) { if (item.privateKey != null && mainActivity != null) {
mainActivity.removeIdentityEntry(item) mainActivity.removeIdentityEntry(item)
}
this.item = null
ctx.onBackPressed()
} }
.setNegativeButton(android.R.string.no, null) this.item = null
.show() ctx.onBackPressed()
}
.setNegativeButton(android.R.string.no, null)
.show()
return true return true
} }
R.id.export -> { R.id.export -> {
AlertDialog.Builder(ctx) AlertDialog.Builder(ctx)
.setMessage(R.string.confirm_export) .setMessage(R.string.confirm_export)
.setPositiveButton(android.R.string.yes) { _, _ -> .setPositiveButton(android.R.string.yes) { _, _ ->
val shareIntent = Intent(Intent.ACTION_SEND) val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.type = "text/plain" shareIntent.type = "text/plain"
shareIntent.putExtra(Intent.EXTRA_TITLE, item.toString() + EXPORT_POSTFIX) shareIntent.putExtra(Intent.EXTRA_TITLE, item.toString() + EXPORT_POSTFIX)
val exporter = WifExporter(Singleton val exporter = WifExporter(Singleton
.getBitmessageContext(ctx)) .getBitmessageContext(ctx))
exporter.addIdentity(item) exporter.addIdentity(item)
shareIntent.putExtra(Intent.EXTRA_TEXT, exporter.toString()) shareIntent.putExtra(Intent.EXTRA_TEXT, exporter.toString())
startActivity(Intent.createChooser(shareIntent, null)) startActivity(Intent.createChooser(shareIntent, null))
} }
.setNegativeButton(android.R.string.no, null) .setNegativeButton(android.R.string.no, null)
.show() .show()
return true return true
} }
R.id.share -> { R.id.share -> {
@ -132,30 +134,27 @@ class AddressDetailFragment : Fragment() {
} }
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_address_detail, container, false) = inflater.inflate(R.layout.fragment_address_detail, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
// Show the dummy content as text in a TextView. // Show the dummy content as text in a TextView.
item?.let { item -> item?.let { item ->
val activity = activity activity?.let { activity ->
when { when {
item.isChan -> activity.setTitle(R.string.title_chan_detail) item.isChan -> activity.setTitle(R.string.title_chan_detail)
item.privateKey != null -> activity.setTitle(R.string.title_identity_detail) item.privateKey != null -> activity.setTitle(R.string.title_identity_detail)
item.isSubscribed -> activity.setTitle(R.string.title_subscription_detail) item.isSubscribed -> activity.setTitle(R.string.title_subscription_detail)
else -> activity.setTitle(R.string.title_contact_detail) else -> activity.setTitle(R.string.title_contact_detail)
}
} }
avatar.setImageDrawable(Identicon(item)) avatar.setImageDrawable(Identicon(item))
name.setText(item.toString()) name.setText(item.toString())
name.addTextChangedListener(object : TextWatcher { name.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit // Nothing to do
// Nothing to do
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) = Unit // Nothing to do
// Nothing to do
}
override fun afterTextChanged(s: Editable) { override fun afterTextChanged(s: Editable) {
item.alias = s.toString() item.alias = s.toString()
@ -185,7 +184,7 @@ class AddressDetailFragment : Fragment() {
override fun onPause() { override fun onPause() {
item?.let { item -> item?.let { item ->
Singleton.getAddressRepository(context).save(item) Singleton.getAddressRepository(context!!).save(item)
if (item.privateKey != null) { if (item.privateKey != null) {
MainActivity.getInstance()?.updateIdentityEntry(item) MainActivity.getInstance()?.updateIdentityEntry(item)
} }

View File

@ -45,10 +45,10 @@ class AddressListFragment : AbstractItemListFragment<Void, BitmessageAddress>()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
adapter = object : ArrayAdapter<BitmessageAddress>( adapter = object : ArrayAdapter<BitmessageAddress>(
activity, activity,
R.layout.subscription_row, R.layout.subscription_row,
R.id.name, R.id.name,
LinkedList()) { LinkedList()) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val result: View val result: View
val v: ViewHolder val v: ViewHolder
@ -56,11 +56,11 @@ class AddressListFragment : AbstractItemListFragment<Void, BitmessageAddress>()
val inflater = LayoutInflater.from(context) val inflater = LayoutInflater.from(context)
val view = inflater.inflate(R.layout.subscription_row, parent, false) val view = inflater.inflate(R.layout.subscription_row, parent, false)
v = ViewHolder( v = ViewHolder(
ctx = context, ctx = context,
avatar = view.findViewById(R.id.avatar) as ImageView, avatar = view.findViewById(R.id.avatar),
name = view.findViewById(R.id.name) as TextView, name = view.findViewById(R.id.name),
streamNumber = view.findViewById(R.id.stream_number) as TextView, streamNumber = view.findViewById(R.id.stream_number),
subscribed = view.findViewById(R.id.subscribed) subscribed = view.findViewById(R.id.subscribed)
) )
view.tag = v view.tag = v
result = view result = view
@ -89,12 +89,14 @@ class AddressListFragment : AbstractItemListFragment<Void, BitmessageAddress>()
fun updateList() { fun updateList() {
adapter.clear() adapter.clear()
val addressRepo = Singleton.getAddressRepository(context) context?.let { context ->
doAsync { val addressRepo = Singleton.getAddressRepository(context)
addressRepo.getContactIds() doAsync {
addressRepo.getContactIds()
.map { addressRepo.getAddress(it) } .map { addressRepo.getAddress(it) }
.forEach { address -> uiThread { adapter.add(address) } } .forEach { address -> uiThread { adapter.add(address) } }
}
} }
} }
@ -104,23 +106,23 @@ class AddressListFragment : AbstractItemListFragment<Void, BitmessageAddress>()
menu.add(R.string.scan_qr_code).setIcon(R.drawable.ic_action_qr_code) menu.add(R.string.scan_qr_code).setIcon(R.drawable.ic_action_qr_code)
menu.add(R.string.create_contact).setIcon(R.drawable.ic_action_create_contact) menu.add(R.string.create_contact).setIcon(R.drawable.ic_action_create_contact)
FabUtils.initFab(activity, R.drawable.ic_action_add_contact, menu) FabUtils.initFab(activity, R.drawable.ic_action_add_contact, menu)
.addOnMenuItemClickListener { _, _, itemId -> .addOnMenuItemClickListener { _, _, itemId ->
when (itemId) { when (itemId) {
1 -> IntentIntegrator.forSupportFragment(this@AddressListFragment) 1 -> IntentIntegrator.forSupportFragment(this@AddressListFragment)
.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES) .setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES)
.initiateScan() .initiateScan()
2 -> { 2 -> {
val intent = Intent(getActivity(), CreateAddressActivity::class.java) val intent = Intent(getActivity(), CreateAddressActivity::class.java)
startActivity(intent) startActivity(intent)
} }
else -> { else -> {
}
} }
} }
}
} }
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_address_list, container, false) inflater.inflate(R.layout.fragment_address_list, container, false)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (data != null && data.hasExtra("SCAN_RESULT")) { if (data != null && data.hasExtra("SCAN_RESULT")) {
@ -131,15 +133,13 @@ class AddressListFragment : AbstractItemListFragment<Void, BitmessageAddress>()
} }
} }
override fun updateList(label: Void) { override fun updateList(label: Void) = updateList()
updateList()
}
private data class ViewHolder( private data class ViewHolder(
val ctx: Context, val ctx: Context,
val avatar: ImageView, val avatar: ImageView,
val name: TextView, val name: TextView,
val streamNumber: TextView, val streamNumber: TextView,
val subscribed: View val subscribed: View
) )
} }

View File

@ -47,9 +47,9 @@ class ComposeMessageActivity : AppCompatActivity() {
val fragment = ComposeMessageFragment() val fragment = ComposeMessageFragment()
fragment.arguments = intent.extras fragment.arguments = intent.extras
supportFragmentManager supportFragmentManager
.beginTransaction() .beginTransaction()
.replace(R.id.content, fragment) .replace(R.id.content, fragment)
.commit() .commit()
} }
companion object { companion object {
@ -61,18 +61,19 @@ class ComposeMessageActivity : AppCompatActivity() {
const val EXTRA_ENCODING = "ch.dissem.abit.Message.ENCODING" const val EXTRA_ENCODING = "ch.dissem.abit.Message.ENCODING"
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.activity, item)) fragment.startActivity(getReplyIntent(
} 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))
}
private fun getReplyIntent(ctx: Context, item: Plaintext): Intent { private fun getReplyIntent(ctx: Context, item: Plaintext): Intent {
val replyIntent = Intent(ctx, ComposeMessageActivity::class.java) val replyIntent = Intent(ctx, ComposeMessageActivity::class.java)
val receivingIdentity = item.to val receivingIdentity = item.to
if (receivingIdentity?.isChan ?: false) { if (receivingIdentity?.isChan == true) {
// reply to chan, not to the sender of the message // reply to chan, not to the sender of the message
replyIntent.putExtra(EXTRA_RECIPIENT, receivingIdentity) replyIntent.putExtra(EXTRA_RECIPIENT, receivingIdentity)
// I hate when people send as chan, so it won't be the default behaviour. // I hate when people send as chan, so it won't be the default behaviour.
@ -96,7 +97,7 @@ class ComposeMessageActivity : AppCompatActivity() {
replyIntent.putExtra(EXTRA_SUBJECT, prefix + subject) replyIntent.putExtra(EXTRA_SUBJECT, prefix + subject)
} }
replyIntent.putExtra(EXTRA_CONTENT, replyIntent.putExtra(EXTRA_CONTENT,
"\n\n------------------------------------------------------\n" + item.text!!) "\n\n------------------------------------------------------\n" + item.text!!)
return replyIntent return replyIntent
} }
} }

View File

@ -59,7 +59,7 @@ class ComposeMessageFragment : Fragment() {
arguments?.let { arguments -> arguments?.let { arguments ->
var id = arguments.getSerializable(EXTRA_IDENTITY) as? BitmessageAddress var id = arguments.getSerializable(EXTRA_IDENTITY) as? BitmessageAddress
if (context != null && (id == null || id.privateKey == null)) { if (context != null && (id == null || id.privateKey == null)) {
id = Singleton.getIdentity(context) id = Singleton.getIdentity(context!!)
} }
if (id?.privateKey != null) { if (id?.privateKey != null) {
identity = id identity = id
@ -97,7 +97,7 @@ class ComposeMessageFragment : Fragment() {
if (broadcast) { if (broadcast) {
recipient_input.visibility = View.GONE recipient_input.visibility = View.GONE
} else { } else {
val adapter = ContactAdapter(context) val adapter = ContactAdapter(context!!)
recipient_input.setAdapter(adapter) recipient_input.setAdapter(adapter)
recipient_input.onItemClickListener = AdapterView.OnItemClickListener { _, _, pos, _ -> adapter.getItem(pos) } recipient_input.onItemClickListener = AdapterView.OnItemClickListener { _, _, pos, _ -> adapter.getItem(pos) }
recipient_input.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { recipient_input.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
@ -105,9 +105,7 @@ class ComposeMessageFragment : Fragment() {
recipient = adapter.getItem(position) recipient = adapter.getItem(position)
} }
override fun onNothingSelected(parent: AdapterView<*>) { override fun onNothingSelected(parent: AdapterView<*>) = Unit // leave current selection
// leave current selection
}
} }
recipient?.let { recipient_input.setText(it.toString()) } recipient?.let { recipient_input.setText(it.toString()) }
} }
@ -148,17 +146,16 @@ class ComposeMessageFragment : Fragment() {
} }
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) = if (requestCode == 0 && data != null && resultCode == RESULT_OK) {
if (requestCode == 0 && data != null && resultCode == RESULT_OK) { encoding = data.getSerializableExtra(EXTRA_ENCODING) as Plaintext.Encoding
encoding = data.getSerializableExtra(EXTRA_ENCODING) as Plaintext.Encoding } else {
} else { super.onActivityResult(requestCode, resultCode, data)
super.onActivityResult(requestCode, resultCode, data)
}
} }
private fun send() { private fun send() {
val builder: Plaintext.Builder val builder: Plaintext.Builder
val bmc = Singleton.getBitmessageContext(context) val ctx = activity ?: throw IllegalStateException("Fragment is not attached to an activity")
val bmc = Singleton.getBitmessageContext(ctx)
if (broadcast) { if (broadcast) {
builder = Plaintext.Builder(BROADCAST).from(identity) builder = Plaintext.Builder(BROADCAST).from(identity)
} else { } else {
@ -167,7 +164,7 @@ class ComposeMessageFragment : Fragment() {
try { try {
recipient = BitmessageAddress(inputString) recipient = BitmessageAddress(inputString)
} catch (e: Exception) { } catch (e: Exception) {
val contacts = Singleton.getAddressRepository(context).getContacts() val contacts = Singleton.getAddressRepository(ctx).getContacts()
for (contact in contacts) { for (contact in contacts) {
if (inputString.equals(contact.alias, ignoreCase = true)) { if (inputString.equals(contact.alias, ignoreCase = true)) {
recipient = contact recipient = contact
@ -186,7 +183,7 @@ class ComposeMessageFragment : Fragment() {
.from(identity) .from(identity)
.to(recipient) .to(recipient)
} }
if (!Preferences.requestAcknowledgements(context)) { if (!Preferences.requestAcknowledgements(ctx)) {
builder.preventAck() builder.preventAck()
} }
when (encoding) { when (encoding) {
@ -203,8 +200,8 @@ class ComposeMessageFragment : Fragment() {
) )
else -> { else -> {
Toast.makeText( Toast.makeText(
context, ctx,
context.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(
@ -214,7 +211,7 @@ class ComposeMessageFragment : Fragment() {
} }
} }
bmc.send(builder.build()) bmc.send(builder.build())
activity.finish() ctx.finish()
} }
} }

View File

@ -46,9 +46,9 @@ class CreateAddressActivity : AppCompatActivity() {
else else
setContentView(R.layout.activity_create_bitmessage_address) setContentView(R.layout.activity_create_bitmessage_address)
val address = findViewById(R.id.address) as TextView val address = findViewById<TextView>(R.id.address)
val label = findViewById(R.id.label) as EditText val label = findViewById<EditText>(R.id.label)
val subscribe = findViewById(R.id.subscribe) as Switch val subscribe = findViewById<Switch>(R.id.subscribe)
if (uri != null) { if (uri != null) {
val addressText = getAddress(uri) val addressText = getAddress(uri)
@ -70,12 +70,12 @@ class CreateAddressActivity : AppCompatActivity() {
address.text = addressText address.text = addressText
} }
val cancel = findViewById(R.id.cancel) as Button val cancel = findViewById<Button>(R.id.cancel)
cancel.setOnClickListener { cancel.setOnClickListener {
setResult(Activity.RESULT_CANCELED) setResult(Activity.RESULT_CANCELED)
finish() finish()
} }
findViewById(R.id.do_import).setOnClickListener { onOK(address, label, subscribe) } findViewById<Button>(R.id.do_import).setOnClickListener { onOK(address, label, subscribe) }
} }

View File

@ -24,6 +24,7 @@ import android.support.v7.widget.RecyclerView
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.Button
import com.h6ah4i.android.widget.advrecyclerview.decoration.SimpleListDividerDecorator import com.h6ah4i.android.widget.advrecyclerview.decoration.SimpleListDividerDecorator
@ -38,7 +39,7 @@ 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: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
inflater.inflate(R.layout.fragment_import_select_identities, container, false) inflater.inflate(R.layout.fragment_import_select_identities, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -52,14 +53,14 @@ class ImportIdentitiesFragment : Fragment() {
val layoutManager = LinearLayoutManager(activity, val layoutManager = LinearLayoutManager(activity,
LinearLayoutManager.VERTICAL, LinearLayoutManager.VERTICAL,
false) false)
val recyclerView = view.findViewById(R.id.recycler_view) as RecyclerView 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(SimpleListDividerDecorator(
ContextCompat.getDrawable(activity, R.drawable.list_divider_h), true)) ContextCompat.getDrawable(activity, R.drawable.list_divider_h), true))
view.findViewById(R.id.finish).setOnClickListener { view.findViewById<Button>(R.id.finish).setOnClickListener {
importer.importAll(adapter.selected) importer.importAll(adapter.selected)
val mainActivity = MainActivity.getInstance() val mainActivity = MainActivity.getInstance()
if (mainActivity != null) { if (mainActivity != null) {

View File

@ -39,7 +39,7 @@ class InputWifFragment : 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_import_input, container, false) inflater.inflate(R.layout.fragment_import_input, container, false)
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
@ -58,9 +58,8 @@ class InputWifFragment : Fragment() {
} }
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) =
inflater.inflate(R.menu.import_input_data, menu) inflater.inflate(R.menu.import_input_data, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
val properties = DialogProperties() val properties = DialogProperties()

View File

@ -99,7 +99,7 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
private lateinit var nodeSwitch: SwitchDrawerItem private lateinit var nodeSwitch: SwitchDrawerItem
val floatingActionButton: FabSpeedDial? val floatingActionButton: FabSpeedDial?
get() = findViewById(R.id.fab) as FabSpeedDial? get() = findViewById(R.id.fab)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -109,7 +109,7 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
fab.hide() fab.hide()
val toolbar = findViewById(R.id.toolbar) as Toolbar val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
val listFragment = MessageListFragment() val listFragment = MessageListFragment()
@ -118,7 +118,7 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
.replace(R.id.item_list, listFragment) .replace(R.id.item_list, listFragment)
.commit() .commit()
if (findViewById(R.id.message_detail_container) != null) { if (findViewById<View>(R.id.message_detail_container) != null) {
// The detail container view will be present only in the // The detail container view will be present only in the
// large-screen layouts (res/values-large and // large-screen layouts (res/values-large and
// res/values-sw600dp). If this view is present, then the // res/values-sw600dp). If this view is present, then the
@ -358,7 +358,7 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
} }
override fun onRestoreInstanceState(savedInstanceState: Bundle) { override fun onRestoreInstanceState(savedInstanceState: Bundle) {
selectedLabel = savedInstanceState.getSerializable("selectedLabel") as Label? selectedLabel = savedInstanceState.getSerializable("selectedLabel") as? Label
selectedLabel?.let { selectedLabel -> selectedLabel?.let { selectedLabel ->
drawer.getDrawerItem(selectedLabel)?.let { selectedItem -> drawer.getDrawerItem(selectedLabel)?.let { selectedItem ->

View File

@ -25,32 +25,23 @@ import android.support.v7.widget.GridLayoutManager
import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.text.util.Linkify import android.text.util.Linkify
import android.view.LayoutInflater import android.text.util.Linkify.WEB_URLS
import android.view.Menu import android.view.*
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.mikepenz.iconics.view.IconicsImageView
import java.util.ArrayList
import ch.dissem.apps.abit.service.Singleton import ch.dissem.apps.abit.service.Singleton
import ch.dissem.apps.abit.util.Assets import ch.dissem.apps.abit.util.Assets
import ch.dissem.apps.abit.util.Drawables
import ch.dissem.apps.abit.util.Labels
import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.entity.valueobject.Label
import android.text.util.Linkify.WEB_URLS
import ch.dissem.apps.abit.util.Constants.BITMESSAGE_ADDRESS_PATTERN import ch.dissem.apps.abit.util.Constants.BITMESSAGE_ADDRESS_PATTERN
import ch.dissem.apps.abit.util.Constants.BITMESSAGE_URL_SCHEMA import ch.dissem.apps.abit.util.Constants.BITMESSAGE_URL_SCHEMA
import ch.dissem.apps.abit.util.Drawables
import ch.dissem.apps.abit.util.Labels
import ch.dissem.apps.abit.util.Strings.prepareMessageExtract import ch.dissem.apps.abit.util.Strings.prepareMessageExtract
import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.entity.valueobject.Label
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.mikepenz.iconics.view.IconicsImageView
import kotlinx.android.synthetic.main.fragment_message_detail.* import kotlinx.android.synthetic.main.fragment_message_detail.*
import java.util.*
/** /**
* A fragment representing a single Message detail screen. * A fragment representing a single Message detail screen.
@ -68,21 +59,25 @@ class MessageDetailFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (arguments.containsKey(ARG_ITEM)) { arguments?.let { arguments ->
// Load the dummy content specified by the fragment if (arguments.containsKey(ARG_ITEM)) {
// arguments. In a real-world scenario, use a Loader // Load the dummy content specified by the fragment
// to load content from a content provider. // arguments. In a real-world scenario, use a Loader
item = arguments.getSerializable(ARG_ITEM) as Plaintext // to load content from a content provider.
item = arguments.getSerializable(ARG_ITEM) as Plaintext
}
} }
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?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val ctx = activity ?: throw IllegalStateException("Fragment is not attached to an activity")
// Show the dummy content as text in a TextView. // Show the dummy content as text in a TextView.
item?.let { item -> item?.let { item ->
subject.text = item.subject subject.text = item.subject
@ -97,7 +92,7 @@ class MessageDetailFragment : Fragment() {
recipient.setText(R.string.broadcast) recipient.setText(R.string.broadcast)
} }
}.invoke() }.invoke()
val labelAdapter = LabelAdapter(activity, item.labels) val labelAdapter = LabelAdapter(ctx, item.labels)
labels.adapter = labelAdapter labels.adapter = labelAdapter
labels.layoutManager = GridLayoutManager(activity, 2) labels.layoutManager = GridLayoutManager(activity, 2)
@ -105,18 +100,16 @@ class MessageDetailFragment : Fragment() {
Linkify.addLinks(text, WEB_URLS) Linkify.addLinks(text, WEB_URLS)
Linkify.addLinks(text, BITMESSAGE_ADDRESS_PATTERN, BITMESSAGE_URL_SCHEMA, null, Linkify.addLinks(text, BITMESSAGE_ADDRESS_PATTERN, BITMESSAGE_URL_SCHEMA, null,
Linkify.TransformFilter { match, _ -> match.group() } Linkify.TransformFilter { match, _ -> match.group() }
) )
text.linksClickable = true text.linksClickable = true
text.setTextIsSelectable(true) text.setTextIsSelectable(true)
val removed = item.labels.removeAll { it.type==Label.Type.UNREAD } val removed = item.labels.removeAll { it.type == Label.Type.UNREAD }
val messageRepo = Singleton.getMessageRepository(context) val messageRepo = Singleton.getMessageRepository(ctx)
if (removed) { if (removed) {
if (activity is MainActivity) { (activity as? MainActivity)?.updateUnread()
(activity as MainActivity).updateUnread()
}
messageRepo.save(item) messageRepo.save(item)
} }
val parents = ArrayList<Plaintext>(item.parents.size) val parents = ArrayList<Plaintext>(item.parents.size)
@ -126,32 +119,35 @@ class MessageDetailFragment : Fragment() {
parents.add(parent) parents.add(parent)
} }
} }
showRelatedMessages(view, R.id.parents, parents) showRelatedMessages(ctx, view, R.id.parents, parents)
showRelatedMessages(view, R.id.responses, messageRepo.findResponses(item)) showRelatedMessages(ctx, view, R.id.responses, messageRepo.findResponses(item))
} }
} }
private fun showRelatedMessages(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(id) as RecyclerView val recyclerView = rootView.findViewById<RecyclerView>(id)
val adapter = RelatedMessageAdapter(activity, messages) val adapter = RelatedMessageAdapter(ctx, messages)
recyclerView.adapter = adapter recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(activity) recyclerView.layoutManager = LinearLayoutManager(activity)
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.message, menu) inflater.inflate(R.menu.message, menu)
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(activity, menu, R.id.mark_unread, GoogleMaterial.Icon
.gmd_markunread) .gmd_markunread)
Drawables.addIcon(activity, menu, R.id.archive, GoogleMaterial.Icon.gmd_archive) Drawables.addIcon(activity, menu, R.id.archive, GoogleMaterial.Icon.gmd_archive)
}
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
} }
override fun onOptionsItemSelected(menuItem: MenuItem): Boolean { override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
val messageRepo = Singleton.getMessageRepository(context) val messageRepo = Singleton.getMessageRepository(
context ?: throw IllegalStateException("No context available")
)
item?.let { item -> item?.let { item ->
when (menuItem.itemId) { when (menuItem.itemId) {
R.id.reply -> { R.id.reply -> {
@ -167,7 +163,7 @@ class MessageDetailFragment : Fragment() {
messageRepo.save(item) messageRepo.save(item)
} }
(activity as? MainActivity)?.updateUnread() (activity as? MainActivity)?.updateUnread()
activity.onBackPressed() activity?.onBackPressed()
return true return true
} }
R.id.mark_unread -> { R.id.mark_unread -> {
@ -220,10 +216,10 @@ class MessageDetailFragment : Fragment() {
override fun getItemCount() = messages.size override fun getItemCount() = messages.size
internal inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { internal inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
internal val avatar = itemView.findViewById(R.id.avatar) as ImageView internal val avatar = itemView.findViewById<ImageView>(R.id.avatar)
internal val status = itemView.findViewById(R.id.status) as ImageView internal val status = itemView.findViewById<ImageView>(R.id.status)
internal val sender = itemView.findViewById(R.id.sender) as TextView internal val sender = itemView.findViewById<TextView>(R.id.sender)
internal val extract = itemView.findViewById(R.id.text) as TextView internal val extract = itemView.findViewById<TextView>(R.id.text)
internal var item: Plaintext? = null internal var item: Plaintext? = null
init { init {
@ -260,16 +256,16 @@ class MessageDetailFragment : Fragment() {
// Get the data model based on position // Get the data model based on position
val label = labels[position] val label = labels[position]
viewHolder.icon.setColor(Labels.getColor(label)) viewHolder.icon.icon?.color(Labels.getColor(label))
viewHolder.icon.setIcon(Labels.getIcon(label)) viewHolder.icon.icon?.icon(Labels.getIcon(label))
viewHolder.label.text = Labels.getText(label, ctx) viewHolder.label.text = Labels.getText(label, ctx)
} }
override fun getItemCount() = labels.size override fun getItemCount() = labels.size
internal class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { internal class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var icon = itemView.findViewById(R.id.icon) as IconicsImageView var icon = itemView.findViewById<IconicsImageView>(R.id.icon)!!
var label = itemView.findViewById(R.id.label) as TextView var label = itemView.findViewById<TextView>(R.id.label)!!
} }
} }
@ -280,6 +276,6 @@ class MessageDetailFragment : Fragment() {
*/ */
val ARG_ITEM = "item" val ARG_ITEM = "item"
fun isInTrash(item: Plaintext?) = item?.labels?.any { it.type == Label.Type.TRASH } ?: false fun isInTrash(item: Plaintext?) = item?.labels?.any { it.type == Label.Type.TRASH } == true
} }
} }

View File

@ -165,6 +165,8 @@ class MessageListFragment : Fragment(), ListHolder<Label> {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val context = context ?: throw IllegalStateException("No context available")
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
// touch guard manager (this class is required to suppress scrolling while swipe-dismiss // touch guard manager (this class is required to suppress scrolling while swipe-dismiss
@ -190,12 +192,10 @@ class MessageListFragment : Fragment(), ListHolder<Label> {
Singleton.labeler.delete(item) Singleton.labeler.delete(item)
messageRepo.save(item) messageRepo.save(item)
} }
recyclerViewOnScrollListener.onScrolled(null, 0, 0)
} }
override fun onItemArchived(item: Plaintext) { override fun onItemArchived(item: Plaintext) {
Singleton.labeler.archive(item) Singleton.labeler.archive(item)
recyclerViewOnScrollListener.onScrolled(null, 0, 0)
} }
override fun onItemViewClicked(v: View?) { override fun onItemViewClicked(v: View?) {
@ -239,28 +239,27 @@ class MessageListFragment : Fragment(), ListHolder<Label> {
this.swipeableMessageAdapter = adapter this.swipeableMessageAdapter = adapter
Singleton.labeler.listener = { message, added, removed -> Singleton.labeler.listener = { message, added, removed ->
when { swipeableMessageAdapter?.let { swipeableMessageAdapter ->
currentLabel?.type == Label.Type.TRASH && added.all { it.type == Label.Type.TRASH } && removed.any { it.type == Label.Type.TRASH } -> { when {
// work-around for messages that are deleted from trash currentLabel?.type == Label.Type.TRASH && added.all { it.type == Label.Type.TRASH } && removed.any { it.type == Label.Type.TRASH } -> {
swipeableMessageAdapter?.remove(message) // work-around for messages that are deleted from trash
recyclerViewOnScrollListener.onScrolled(null, 0, 0) swipeableMessageAdapter.remove(message)
} }
currentLabel?.type == Label.Type.UNREAD && added.all { it.type == Label.Type.TRASH } -> { currentLabel?.type == Label.Type.UNREAD && added.all { it.type == Label.Type.TRASH } -> {
// work-around for messages that are deleted from unread, which already have the unread label removed // work-around for messages that are deleted from unread, which already have the unread label removed
swipeableMessageAdapter?.remove(message) swipeableMessageAdapter.remove(message)
recyclerViewOnScrollListener.onScrolled(null, 0, 0) }
} added.contains(currentLabel) -> {
added.contains(currentLabel) -> { // in most cases, top should be the correct position, but time will show if
// in most cases, top should be the correct position, but time will show if // the message should be properly sorted in
// the message should be properly sorted in swipeableMessageAdapter.addFirst(message)
swipeableMessageAdapter?.addFirst(message) }
} removed.contains(currentLabel) -> {
removed.contains(currentLabel) -> { swipeableMessageAdapter.remove(message)
swipeableMessageAdapter?.remove(message) }
recyclerViewOnScrollListener.onScrolled(null, 0, 0) removed.any { it.type == Label.Type.UNREAD } || added.any { it.type == Label.Type.UNREAD } -> {
} swipeableMessageAdapter.update(message)
removed.any { it.type == Label.Type.UNREAD } || added.any { it.type == Label.Type.UNREAD } -> { }
swipeableMessageAdapter?.update(message)
} }
} }
} }
@ -272,7 +271,7 @@ class MessageListFragment : Fragment(), ListHolder<Label> {
menu.add(R.string.personal_message).setIcon(R.drawable.ic_action_personal) menu.add(R.string.personal_message).setIcon(R.drawable.ic_action_personal)
FabUtils.initFab(context, R.drawable.ic_action_compose_message, menu) FabUtils.initFab(context, R.drawable.ic_action_compose_message, menu)
.addOnMenuItemClickListener { _, _, itemId -> .addOnMenuItemClickListener { _, _, itemId ->
val identity = Singleton.getIdentity(activity) val identity = Singleton.getIdentity(context)
if (identity == null) { if (identity == null) {
Toast.makeText(activity, R.string.no_identity_warning, Toast.makeText(activity, R.string.no_identity_warning,
Toast.LENGTH_LONG).show() Toast.LENGTH_LONG).show()
@ -347,12 +346,10 @@ class MessageListFragment : Fragment(), ListHolder<Label> {
this.activateOnItemClick = activateOnItemClick this.activateOnItemClick = activateOnItemClick
} }
override fun showPreviousList(): Boolean { override fun showPreviousList() = if (backStack.isEmpty()) {
return if (backStack.isEmpty()) { false
false } else {
} else { doUpdateList(backStack.pop())
doUpdateList(backStack.pop()) true
true
}
} }
} }

View File

@ -59,26 +59,26 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
addPreferencesFromResource(R.xml.preferences) addPreferencesFromResource(R.xml.preferences)
findPreference("about")?.onPreferenceClickListener = Preference.OnPreferenceClickListener { findPreference("about")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
val libsBuilder = LibsBuilder() (activity as? MainActivity)?.let { activity ->
val libsBuilder = LibsBuilder()
.withActivityTitle(activity.getString(R.string.about)) .withActivityTitle(activity.getString(R.string.about))
.withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR) .withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
.withAboutIconShown(true) .withAboutIconShown(true)
.withAboutVersionShown(true) .withAboutVersionShown(true)
.withAboutDescription(getString(R.string.about_app)) .withAboutDescription(getString(R.string.about_app))
val activity = activity as MainActivity if (activity.hasDetailPane) {
if (activity.hasDetailPane) { activity.setDetailView(libsBuilder.supportFragment())
activity.setDetailView(libsBuilder.supportFragment()) } else {
} else { libsBuilder.start(activity)
libsBuilder.start(getActivity()) }
} }
return@OnPreferenceClickListener true return@OnPreferenceClickListener true
} }
val cleanup = findPreference("cleanup") val cleanup = findPreference("cleanup")
cleanup?.onPreferenceClickListener = Preference.OnPreferenceClickListener { cleanup?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
val ctx = activity.applicationContext val ctx = activity?.applicationContext ?: throw IllegalStateException("Context not available")
cleanup.isEnabled = false cleanup.isEnabled = false
Toast.makeText(ctx, R.string.cleanup_notification_start, Toast Toast.makeText(ctx, R.string.cleanup_notification_start, Toast.LENGTH_SHORT).show()
.LENGTH_SHORT).show()
doAsync { doAsync {
val bmc = Singleton.getBitmessageContext(ctx) val bmc = Singleton.getBitmessageContext(ctx)
@ -88,9 +88,9 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
uiThread { uiThread {
Toast.makeText( Toast.makeText(
ctx, ctx,
R.string.cleanup_notification_end, R.string.cleanup_notification_end,
Toast.LENGTH_LONG Toast.LENGTH_LONG
).show() ).show()
cleanup.isEnabled = true cleanup.isEnabled = true
} }
@ -99,36 +99,38 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
} }
findPreference("export")?.onPreferenceClickListener = Preference.OnPreferenceClickListener { findPreference("export")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
val ctx = context ?: throw IllegalStateException("No context available")
val dialog = indeterminateProgressDialog(R.string.export_data_summary, R.string.export_data) val dialog = indeterminateProgressDialog(R.string.export_data_summary, R.string.export_data)
doAsync { doAsync {
val exportDirectory = Preferences.getExportDirectory(context) val exportDirectory = Preferences.getExportDirectory(ctx)
exportDirectory.mkdirs() exportDirectory.mkdirs()
val temp = File(exportDirectory, "export-${UnixTime.now}.zip") val temp = File(exportDirectory, "export-${UnixTime.now}.zip")
ZipOutputStream(FileOutputStream(temp)).use { zip -> ZipOutputStream(FileOutputStream(temp)).use { zip ->
zip.putNextEntry(ZipEntry("contacts.json")) zip.putNextEntry(ZipEntry("contacts.json"))
val addressRepo = Singleton.getAddressRepository(context) val addressRepo = Singleton.getAddressRepository(ctx)
val exportContacts = ContactExport.exportContacts(addressRepo.getContacts()) val exportContacts = ContactExport.exportContacts(addressRepo.getContacts())
zip.write( zip.write(
exportContacts.toJsonString(true).toByteArray() exportContacts.toJsonString(true).toByteArray()
) )
zip.closeEntry() zip.closeEntry()
val messageRepo = Singleton.getMessageRepository(context) val messageRepo = Singleton.getMessageRepository(ctx)
zip.putNextEntry(ZipEntry("labels.json")) zip.putNextEntry(ZipEntry("labels.json"))
val exportLabels = MessageExport.exportLabels(messageRepo.getLabels()) val exportLabels = MessageExport.exportLabels(messageRepo.getLabels())
zip.write( zip.write(
exportLabels.toJsonString(true).toByteArray() exportLabels.toJsonString(true).toByteArray()
) )
zip.closeEntry() zip.closeEntry()
zip.putNextEntry(ZipEntry("messages.json")) zip.putNextEntry(ZipEntry("messages.json"))
val exportMessages = MessageExport.exportMessages(messageRepo.getAllMessages()) val exportMessages = MessageExport.exportMessages(messageRepo.getAllMessages())
zip.write( zip.write(
exportMessages.toJsonString(true).toByteArray() exportMessages.toJsonString(true).toByteArray()
) )
zip.closeEntry() zip.closeEntry()
} }
val contentUri = getUriForFile(context, "ch.dissem.apps.abit.fileprovider", temp) val contentUri = getUriForFile(ctx, "ch.dissem.apps.abit.fileprovider", temp)
val intent = Intent(android.content.Intent.ACTION_SEND) val intent = Intent(android.content.Intent.ACTION_SEND)
intent.type = "application/zip" intent.type = "application/zip"
intent.putExtra(Intent.EXTRA_SUBJECT, "abit-export.zip") intent.putExtra(Intent.EXTRA_SUBJECT, "abit-export.zip")
@ -161,8 +163,8 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
} }
} }
private fun processEntry(zipFile: Uri, entry: String, processor: (JsonArray<*>) -> Unit) { private fun processEntry(ctx: Context, zipFile: Uri, entry: String, processor: (JsonArray<*>) -> Unit) =
ZipInputStream(context.contentResolver.openInputStream(zipFile)).use { zip -> ZipInputStream(ctx.contentResolver.openInputStream(zipFile)).use { zip ->
var nextEntry = zip.nextEntry var nextEntry = zip.nextEntry
while (nextEntry != null) { while (nextEntry != null) {
if (nextEntry.name == entry) { if (nextEntry.name == entry) {
@ -171,38 +173,38 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
nextEntry = zip.nextEntry nextEntry = zip.nextEntry
} }
} }
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val ctx = context ?: throw IllegalStateException("No context available")
when (requestCode) { when (requestCode) {
WRITE_EXPORT_REQUEST_CODE -> Preferences.cleanupExportDirectory(context) WRITE_EXPORT_REQUEST_CODE -> Preferences.cleanupExportDirectory(ctx)
READ_IMPORT_REQUEST_CODE -> { READ_IMPORT_REQUEST_CODE -> {
if (resultCode == Activity.RESULT_OK && data?.data != null) { if (resultCode == Activity.RESULT_OK && data?.data != null) {
val dialog = indeterminateProgressDialog(R.string.import_data_summary, R.string.import_data) val dialog = indeterminateProgressDialog(R.string.import_data_summary, R.string.import_data)
doAsync { doAsync {
val ctx = Singleton.getBitmessageContext(context) val bmc = Singleton.getBitmessageContext(ctx)
val labels = mutableMapOf<String, Label>() val labels = mutableMapOf<String, Label>()
val zipFile = data.data val zipFile = data.data
processEntry(zipFile, "contacts.json") { json -> processEntry(ctx, zipFile, "contacts.json") { json ->
ContactExport.importContacts(json).forEach { contact -> ContactExport.importContacts(json).forEach { contact ->
ctx.addresses.save(contact) bmc.addresses.save(contact)
} }
} }
ctx.messages.getLabels().forEach { label -> bmc.messages.getLabels().forEach { label ->
labels[label.toString()] = label labels[label.toString()] = label
} }
processEntry(zipFile, "labels.json") { json -> processEntry(ctx, zipFile, "labels.json") { json ->
MessageExport.importLabels(json).forEach { label -> MessageExport.importLabels(json).forEach { label ->
if (!labels.contains(label.toString())) { if (!labels.contains(label.toString())) {
ctx.messages.save(label) bmc.messages.save(label)
labels[label.toString()] = label labels[label.toString()] = label
} }
} }
} }
processEntry(zipFile, "messages.json") { json -> processEntry(ctx, zipFile, "messages.json") { json ->
MessageExport.importMessages(json, labels).forEach { message -> MessageExport.importMessages(json, labels).forEach { message ->
ctx.messages.save(message) bmc.messages.save(message)
} }
} }
uiThread { uiThread {
@ -218,7 +220,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
super.onAttach(ctx) super.onAttach(ctx)
(ctx as? MainActivity)?.floatingActionButton?.hide() (ctx as? MainActivity)?.floatingActionButton?.hide()
PreferenceManager.getDefaultSharedPreferences(ctx) PreferenceManager.getDefaultSharedPreferences(ctx)
.registerOnSharedPreferenceChangeListener(this) .registerOnSharedPreferenceChangeListener(this)
(ctx as? MainActivity)?.updateTitle(getString(R.string.settings)) (ctx as? MainActivity)?.updateTitle(getString(R.string.settings))
} }
@ -227,19 +229,21 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
when (key) { when (key) {
PREFERENCE_TRUSTED_NODE -> { PREFERENCE_TRUSTED_NODE -> {
val node = sharedPreferences.getString(PREFERENCE_TRUSTED_NODE, null) val node = sharedPreferences.getString(PREFERENCE_TRUSTED_NODE, null)
val ctx = context ?: throw IllegalStateException("No context available")
if (node != null) { if (node != null) {
SyncAdapter.startSync(activity) SyncAdapter.startSync(ctx)
} else { } else {
SyncAdapter.stopSync(activity) SyncAdapter.stopSync(ctx)
} }
} }
PREFERENCE_SERVER_POW -> { PREFERENCE_SERVER_POW -> {
val node = sharedPreferences.getString(PREFERENCE_TRUSTED_NODE, null) val node = sharedPreferences.getString(PREFERENCE_TRUSTED_NODE, null)
if (node != null) { if (node != null) {
val ctx = context ?: throw IllegalStateException("No context available")
if (sharedPreferences.getBoolean(PREFERENCE_SERVER_POW, false)) { if (sharedPreferences.getBoolean(PREFERENCE_SERVER_POW, false)) {
SyncAdapter.startPowSync(activity) SyncAdapter.startPowSync(ctx)
} else { } else {
SyncAdapter.stopPowSync(activity) SyncAdapter.stopPowSync(ctx)
} }
} }
} }

View File

@ -18,7 +18,6 @@ package ch.dissem.apps.abit
import android.os.Bundle import android.os.Bundle
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import ch.dissem.apps.abit.service.Singleton import ch.dissem.apps.abit.service.Singleton
import com.mikepenz.materialize.MaterializeBuilder import com.mikepenz.materialize.MaterializeBuilder
import kotlinx.android.synthetic.main.activity_status.* import kotlinx.android.synthetic.main.activity_status.*

View File

@ -28,19 +28,21 @@ import ch.dissem.apps.abit.service.Singleton
class StatusFragment : Fragment() { class StatusFragment : Fragment() {
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_status, container, false) inflater.inflate(R.layout.fragment_status, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val bmc = Singleton.getBitmessageContext(context) val bmc = Singleton.getBitmessageContext(
context ?: throw IllegalStateException("No context available")
)
val status = StringBuilder() val status = StringBuilder()
for (address in bmc.addresses.getIdentities()) { for (address in bmc.addresses.getIdentities()) {
status.append(address.address).append('\n') status.append(address.address).append('\n')
} }
status.append('\n') status.append('\n')
status.append(bmc.status()) status.append(bmc.status())
(view.findViewById(R.id.content) as TextView).text = status view.findViewById<TextView>(R.id.content).text = status
} }
} }

View File

@ -51,8 +51,8 @@ class AddressSelectorAdapter(identities: List<BitmessageAddress>) : RecyclerView
class ViewHolder internal constructor(v: View) : RecyclerView.ViewHolder(v) { class ViewHolder internal constructor(v: View) : RecyclerView.ViewHolder(v) {
var data: Selectable<BitmessageAddress>? = null var data: Selectable<BitmessageAddress>? = null
val checkbox = v.findViewById(R.id.checkbox) as CheckBox val checkbox = v.findViewById<CheckBox>(R.id.checkbox)!!
val address = v.findViewById(R.id.address) as TextView val address = v.findViewById<TextView>(R.id.address)!!
init { init {
checkbox.setOnCheckedChangeListener { _, isChecked -> checkbox.setOnCheckedChangeListener { _, isChecked ->
@ -63,12 +63,8 @@ class AddressSelectorAdapter(identities: List<BitmessageAddress>) : RecyclerView
val selected: List<BitmessageAddress> val selected: List<BitmessageAddress>
get() { get() {
val result = LinkedList<BitmessageAddress>() return data
for (selectable in data) { .filter { it.selected }
if (selectable.selected) { .mapTo(LinkedList()) { it.data }
result.add(selectable.data)
}
}
return result
} }
} }

View File

@ -37,15 +37,9 @@ import ch.dissem.bitmessage.entity.BitmessageAddress
* 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) : BaseAdapter(), Filterable {
private val inflater: LayoutInflater private val inflater = LayoutInflater.from(ctx)
private val originalData: List<BitmessageAddress> private val originalData = Singleton.getAddressRepository(ctx).getContacts()
private var data: List<BitmessageAddress> = emptyList() private var data: List<BitmessageAddress> = originalData
init {
inflater = LayoutInflater.from(ctx)
originalData = Singleton.getAddressRepository(ctx).getContacts()
data = originalData
}
override fun getCount() = data.size override fun getCount() = data.size
@ -69,11 +63,16 @@ class ContactAdapter(ctx: Context) : BaseAdapter(), Filterable {
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(R.id.avatar) as ImageView val avatar = view.findViewById<ImageView>(R.id.avatar)!!
val name = view.findViewById(R.id.name) as TextView val name = view.findViewById<TextView>(R.id.name)!!
val address = view.findViewById(R.id.address) as TextView val address = view.findViewById<TextView>(R.id.address)!!
init {
view.tag = this
}
} }
private inner class ContactFilter : Filter() { private inner class ContactFilter : Filter() {
override fun performFiltering(prefix: CharSequence?): Filter.FilterResults { override fun performFiltering(prefix: CharSequence?): Filter.FilterResults {
val results = Filter.FilterResults() val results = Filter.FilterResults()
@ -116,7 +115,7 @@ class ContactAdapter(ctx: Context) : BaseAdapter(), Filterable {
return results return results
} }
override fun publishResults(constraint: CharSequence, results: Filter.FilterResults) { override fun publishResults(constraint: CharSequence?, results: Filter.FilterResults) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
data = results.values as List<BitmessageAddress> data = results.values as List<BitmessageAddress>
if (results.count > 0) { if (results.count > 0) {

View File

@ -19,6 +19,6 @@ package ch.dissem.apps.abit.adapter
/** /**
* @author Christian Basler * @author Christian Basler
*/ */
class Selectable<T>(val data: T) { class Selectable<out T>(val data: T) {
var selected = false var selected = false
} }

View File

@ -72,12 +72,12 @@ class SwipeableMessageAdapter : RecyclerView.Adapter<SwipeableMessageAdapter.Vie
} }
class ViewHolder(v: View) : AbstractSwipeableItemViewHolder(v) { class ViewHolder(v: View) : AbstractSwipeableItemViewHolder(v) {
val container = v.findViewById(R.id.container) as FrameLayout val container = v.findViewById<FrameLayout>(R.id.container)!!
val avatar = v.findViewById(R.id.avatar) as ImageView val avatar = v.findViewById<ImageView>(R.id.avatar)!!
val status = v.findViewById(R.id.status) as ImageView val status = v.findViewById<ImageView>(R.id.status)!!
val sender = v.findViewById(R.id.sender) as TextView val sender = v.findViewById<TextView>(R.id.sender)!!
val subject = v.findViewById(R.id.subject) as TextView val subject = v.findViewById<TextView>(R.id.subject)!!
val extract = v.findViewById(R.id.text) as TextView val extract = v.findViewById<TextView>(R.id.text)!!
override fun getSwipeableContainerView() = container override fun getSwipeableContainerView() = container
} }
@ -110,19 +110,16 @@ class SwipeableMessageAdapter : RecyclerView.Adapter<SwipeableMessageAdapter.Vie
fun remove(item: Plaintext) { fun remove(item: Plaintext) {
val index = data.indexOf(item) val index = data.indexOf(item)
data.removeIf { it.id == item.id } data.removeAll { it.id == item.id }
notifyItemRemoved(index) notifyItemRemoved(index)
} }
fun update(item: Plaintext) { fun update(item: Plaintext) {
data.replaceAll { val index = data.indexOfFirst { it.id == item.id }
if (it.id == item.id) { if (index >= 0) {
item data[index] = item
} else { notifyItemChanged(index)
it
}
} }
notifyItemChanged(data.indexOf(item))
} }
fun clear(newLabel: Label?) { fun clear(newLabel: Label?) {
@ -137,7 +134,7 @@ class SwipeableMessageAdapter : RecyclerView.Adapter<SwipeableMessageAdapter.Vie
private fun onSwipeableViewContainerClick(v: View) { private fun onSwipeableViewContainerClick(v: View) {
eventListener?.onItemViewClicked( eventListener?.onItemViewClicked(
RecyclerViewAdapterUtils.getParentViewHolderItemView(v)) RecyclerViewAdapterUtils.getParentViewHolderItemView(v))
} }
fun getItem(position: Int) = data[position] fun getItem(position: Int) = data[position]
@ -156,10 +153,10 @@ class SwipeableMessageAdapter : RecyclerView.Adapter<SwipeableMessageAdapter.Vie
holder.apply { holder.apply {
if (activateOnItemClick) { if (activateOnItemClick) {
container.setBackgroundResource( container.setBackgroundResource(
if (position == selectedPosition) if (position == selectedPosition)
R.drawable.bg_item_selected_state R.drawable.bg_item_selected_state
else else
R.drawable.bg_item_normal_state R.drawable.bg_item_normal_state
) )
} }
@ -188,36 +185,35 @@ class SwipeableMessageAdapter : RecyclerView.Adapter<SwipeableMessageAdapter.Vie
override fun getItemCount() = data.size override fun getItemCount() = data.size
override fun onGetSwipeReactionType(holder: ViewHolder, position: Int, x: Int, y: Int): Int { override fun onGetSwipeReactionType(holder: ViewHolder, position: Int, x: Int, y: Int): Int =
return if (label === LABEL_ARCHIVE || label?.type == Label.Type.TRASH) { if (label === LABEL_ARCHIVE || label?.type == Label.Type.TRASH) {
REACTION_CAN_SWIPE_LEFT or REACTION_CAN_NOT_SWIPE_RIGHT_WITH_RUBBER_BAND_EFFECT REACTION_CAN_SWIPE_LEFT or REACTION_CAN_NOT_SWIPE_RIGHT_WITH_RUBBER_BAND_EFFECT
} else { } else {
REACTION_CAN_SWIPE_BOTH_H REACTION_CAN_SWIPE_BOTH_H
} }
}
@SuppressLint("SwitchIntDef") @SuppressLint("SwitchIntDef")
override fun onSetSwipeBackground(holder: ViewHolder, position: Int, type: Int) { override fun onSetSwipeBackground(holder: ViewHolder, position: Int, type: Int) =
var bgRes = 0 holder.itemView.setBackgroundResource(when (type) {
when (type) { DRAWABLE_SWIPE_NEUTRAL_BACKGROUND -> R.drawable.bg_swipe_item_neutral
DRAWABLE_SWIPE_NEUTRAL_BACKGROUND -> bgRes = R.drawable.bg_swipe_item_neutral DRAWABLE_SWIPE_LEFT_BACKGROUND -> R.drawable.bg_swipe_item_left
DRAWABLE_SWIPE_LEFT_BACKGROUND -> bgRes = R.drawable.bg_swipe_item_left
DRAWABLE_SWIPE_RIGHT_BACKGROUND -> if (label === LABEL_ARCHIVE || label?.type == Label.Type.TRASH) { DRAWABLE_SWIPE_RIGHT_BACKGROUND -> if (label === LABEL_ARCHIVE || label?.type == Label.Type.TRASH) {
bgRes = R.drawable.bg_swipe_item_neutral R.drawable.bg_swipe_item_neutral
} else { } else {
bgRes = R.drawable.bg_swipe_item_right R.drawable.bg_swipe_item_right
} }
} else -> R.drawable.bg_swipe_item_neutral
holder.itemView.setBackgroundResource(bgRes) })
}
@SuppressLint("SwitchIntDef") @SuppressLint("SwitchIntDef")
override fun onSwipeItem(holder: ViewHolder, position: Int, result: Int) = override fun onSwipeItem(holder: ViewHolder, position: Int, result: Int) =
when (result) { when (result) {
RESULT_SWIPED_RIGHT -> SwipeRightResultAction(this, position) RESULT_SWIPED_RIGHT -> SwipeRightResultAction(this, position)
RESULT_SWIPED_LEFT -> SwipeLeftResultAction(this, position) RESULT_SWIPED_LEFT -> SwipeLeftResultAction(this, position)
else -> null else -> null
} }
override fun onSwipeItemStarted(holder: ViewHolder?, position: Int) = Unit
fun setSelectedPosition(selectedPosition: Int) { fun setSelectedPosition(selectedPosition: Int) {
val oldPosition = this.selectedPosition val oldPosition = this.selectedPosition

View File

@ -17,11 +17,7 @@
package ch.dissem.apps.abit.adapter package ch.dissem.apps.abit.adapter
import android.content.Context import android.content.Context
import android.content.SharedPreferences
import android.preference.PreferenceManager import android.preference.PreferenceManager
import java.util.Arrays
import ch.dissem.bitmessage.InternalContext import ch.dissem.bitmessage.InternalContext
import ch.dissem.bitmessage.ports.ProofOfWorkEngine import ch.dissem.bitmessage.ports.ProofOfWorkEngine
@ -46,9 +42,7 @@ class SwitchingProofOfWorkEngine(
} }
} }
override fun setContext(context: InternalContext) { override fun setContext(context: InternalContext) = listOf(option, fallback)
listOf(option, fallback) .filterIsInstance<InternalContext.ContextHolder>()
.filterIsInstance<InternalContext.ContextHolder>() .forEach { it.setContext(context) }
.forEach { it.setContext(context) }
}
} }

View File

@ -42,6 +42,7 @@ import org.jetbrains.anko.uiThread
class AddIdentityDialogFragment : AppCompatDialogFragment() { class AddIdentityDialogFragment : AppCompatDialogFragment() {
private lateinit var bmc: BitmessageContext private lateinit var bmc: BitmessageContext
private var parent: ViewGroup? = null
override fun onAttach(context: Context?) { override fun onAttach(context: Context?) {
super.onAttach(context) super.onAttach(context)
@ -50,13 +51,15 @@ class AddIdentityDialogFragment : AppCompatDialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
dialog.setTitle(R.string.add_identity) dialog.setTitle(R.string.add_identity)
parent = container
return inflater.inflate(R.layout.dialog_add_identity, container, false) return inflater.inflate(R.layout.dialog_add_identity, container, false)
} }
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
ok.setOnClickListener(View.OnClickListener { ok.setOnClickListener(View.OnClickListener {
val ctx = activity.baseContext val ctx = activity?.baseContext ?: throw IllegalStateException("No context available")
when (radioGroup.checkedRadioButtonId) { when (radioGroup.checkedRadioButtonId) {
R.id.create_identity -> { R.id.create_identity -> {
Toast.makeText(ctx, Toast.makeText(ctx,
@ -84,14 +87,14 @@ class AddIdentityDialogFragment : AppCompatDialogFragment() {
} }
private fun addChanDialog() { private fun addChanDialog() {
val activity = activity val activity = activity ?: throw IllegalStateException("No activity available")
val ctx = activity.baseContext val ctx = activity.baseContext ?: throw IllegalStateException("No context available")
val dialogView = activity.layoutInflater.inflate(R.layout.dialog_input_passphrase, null) val dialogView = activity.layoutInflater.inflate(R.layout.dialog_input_passphrase, parent)
AlertDialog.Builder(activity) AlertDialog.Builder(activity)
.setTitle(R.string.add_chan) .setTitle(R.string.add_chan)
.setView(dialogView) .setView(dialogView)
.setPositiveButton(R.string.ok) { _, _ -> .setPositiveButton(R.string.ok) { _, _ ->
val passphrase = dialogView.findViewById(R.id.passphrase) as TextView val passphrase = dialogView.findViewById<TextView>(R.id.passphrase)
Toast.makeText(ctx, R.string.toast_long_running_operation, Toast.makeText(ctx, R.string.toast_long_running_operation,
Toast.LENGTH_SHORT).show() Toast.LENGTH_SHORT).show()
val pass = passphrase.text.toString() val pass = passphrase.text.toString()

View File

@ -22,7 +22,6 @@ import android.support.v7.app.AppCompatDialogFragment
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.TextView
import android.widget.Toast import android.widget.Toast
import ch.dissem.apps.abit.MainActivity import ch.dissem.apps.abit.MainActivity
import ch.dissem.apps.abit.R import ch.dissem.apps.abit.R
@ -53,7 +52,7 @@ class DeterministicIdentityDialogFragment : AppCompatDialogFragment() {
super.onViewCreated(dialogView, savedInstanceState) super.onViewCreated(dialogView, savedInstanceState)
ok.setOnClickListener { ok.setOnClickListener {
dismiss() dismiss()
val context = activity.baseContext val context = activity?.baseContext ?: throw IllegalStateException("No context available")
val passphraseText = passphrase.text.toString() val passphraseText = passphrase.text.toString()
Toast.makeText(context, R.string.toast_long_running_operation, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.toast_long_running_operation, Toast.LENGTH_SHORT).show()

View File

@ -39,12 +39,12 @@ class SelectEncodingDialogFragment : AppCompatDialogFragment() {
private lateinit var encoding: Plaintext.Encoding private lateinit var encoding: Plaintext.Encoding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
encoding = (arguments.getSerializable(EXTRA_ENCODING) as? Plaintext.Encoding) ?: SIMPLE encoding = (arguments?.getSerializable(EXTRA_ENCODING) as? Plaintext.Encoding) ?: SIMPLE
dialog.setTitle(R.string.select_encoding_title) dialog.setTitle(R.string.select_encoding_title)
return inflater.inflate(R.layout.dialog_select_message_encoding, container, false) return inflater.inflate(R.layout.dialog_select_message_encoding, container, false)
} }
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
when (encoding) { when (encoding) {
SIMPLE -> radioGroup.check(R.id.simple) SIMPLE -> radioGroup.check(R.id.simple)
@ -62,7 +62,7 @@ class SelectEncodingDialogFragment : AppCompatDialogFragment() {
} }
val result = Intent() val result = Intent()
result.putExtra(EXTRA_ENCODING, encoding) result.putExtra(EXTRA_ENCODING, encoding)
targetFragment.onActivityResult(targetRequestCode, RESULT_OK, result) targetFragment?.onActivityResult(targetRequestCode, RESULT_OK, result)
dismiss() dismiss()
}) })
dismiss.setOnClickListener { dismiss() } dismiss.setOnClickListener { dismiss() }

View File

@ -3,19 +3,16 @@ package ch.dissem.apps.abit.drawer
import android.app.Dialog import android.app.Dialog
import android.content.Context import android.content.Context
import android.graphics.Point import android.graphics.Point
import android.view.Display
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.Window import android.view.Window
import android.view.WindowManager import android.view.WindowManager
import android.widget.ImageView import android.widget.ImageView
import android.widget.RelativeLayout import android.widget.RelativeLayout
import com.mikepenz.materialdrawer.AccountHeader
import com.mikepenz.materialdrawer.model.interfaces.IProfile
import ch.dissem.apps.abit.service.Singleton import ch.dissem.apps.abit.service.Singleton
import ch.dissem.apps.abit.util.Drawables import ch.dissem.apps.abit.util.Drawables
import com.mikepenz.materialdrawer.AccountHeader
import com.mikepenz.materialdrawer.model.interfaces.IProfile
class ProfileImageListener(private val ctx: Context) : AccountHeader.OnAccountHeaderProfileImageListener { class ProfileImageListener(private val ctx: Context) : AccountHeader.OnAccountHeaderProfileImageListener {

View File

@ -49,7 +49,5 @@ class ProfileSelectionListener(
return false return false
} }
private fun addIdentityDialog() { private fun addIdentityDialog() = AddIdentityDialogFragment().show(fragmentManager, "dialog")
AddIdentityDialogFragment().show(fragmentManager, "dialog")
}
} }

View File

@ -21,7 +21,7 @@ package ch.dissem.apps.abit.listener
* implement. This mechanism allows activities to be notified of item * implement. This mechanism allows activities to be notified of item
* selections. * selections.
*/ */
interface ListSelectionListener<T> { interface ListSelectionListener<in T> {
/** /**
* Callback for when an item has been selected. * Callback for when an item has been selected.
*/ */

View File

@ -19,13 +19,14 @@ package ch.dissem.apps.abit.notification
import android.app.Notification import android.app.Notification
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import org.jetbrains.anko.notificationManager
/** /**
* Some base class to create and handle notifications. * Some base class to create and handle notifications.
*/ */
abstract class AbstractNotification(ctx: Context) { abstract class AbstractNotification(ctx: Context) {
protected val ctx = ctx.applicationContext protected val ctx = ctx.applicationContext!!
protected val manager = ctx.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager private val manager = ctx.notificationManager
var notification: Notification? = null var notification: Notification? = null
protected set protected set
protected var showing = false protected var showing = false

View File

@ -18,7 +18,7 @@ package ch.dissem.apps.abit.notification
import android.content.Context import android.content.Context
import android.support.annotation.StringRes import android.support.annotation.StringRes
import android.support.v7.app.NotificationCompat import android.support.v4.app.NotificationCompat
import ch.dissem.apps.abit.R import ch.dissem.apps.abit.R
@ -30,7 +30,7 @@ import ch.dissem.apps.abit.R
*/ */
class ErrorNotification(ctx: Context) : AbstractNotification(ctx) { class ErrorNotification(ctx: Context) : AbstractNotification(ctx) {
private val builder = NotificationCompat.Builder(ctx) private val builder = NotificationCompat.Builder(ctx, "abit.error")
.setContentTitle(ctx.getString(R.string.app_name)) .setContentTitle(ctx.getString(R.string.app_name))
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)

View File

@ -21,7 +21,7 @@ import android.app.PendingIntent
import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.support.v7.app.NotificationCompat import android.support.v4.app.NotificationCompat
import ch.dissem.apps.abit.MainActivity import ch.dissem.apps.abit.MainActivity
import ch.dissem.apps.abit.R import ch.dissem.apps.abit.R
import ch.dissem.apps.abit.service.BitmessageIntentService import ch.dissem.apps.abit.service.BitmessageIntentService
@ -34,7 +34,7 @@ import kotlin.concurrent.fixedRateTimer
*/ */
class NetworkNotification(ctx: Context) : AbstractNotification(ctx) { class NetworkNotification(ctx: Context) : AbstractNotification(ctx) {
private val builder = NotificationCompat.Builder(ctx) private val builder = NotificationCompat.Builder(ctx, "abit.network")
private var timer: Timer? = null private var timer: Timer? = null
init { init {

View File

@ -20,7 +20,7 @@ import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Typeface import android.graphics.Typeface
import android.support.v7.app.NotificationCompat import android.support.v4.app.NotificationCompat
import android.support.v4.app.NotificationCompat.BigTextStyle import android.support.v4.app.NotificationCompat.BigTextStyle
import android.support.v4.app.NotificationCompat.InboxStyle import android.support.v4.app.NotificationCompat.InboxStyle
import android.text.Spannable import android.text.Spannable
@ -43,7 +43,7 @@ import ch.dissem.apps.abit.util.Drawables.toBitmap
class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) { class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) {
fun singleNotification(plaintext: Plaintext): NewMessageNotification { fun singleNotification(plaintext: Plaintext): NewMessageNotification {
val builder = NotificationCompat.Builder(ctx) val builder = NotificationCompat.Builder(ctx, CHANNEL_ID)
val bigText = SpannableString(plaintext.subject + "\n" + plaintext.text) val bigText = SpannableString(plaintext.subject + "\n" + plaintext.text)
plaintext.subject?.let { subject -> plaintext.subject?.let { subject ->
bigText.setSpan(SPAN_EMPHASIS, 0, subject.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) bigText.setSpan(SPAN_EMPHASIS, 0, subject.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
@ -83,7 +83,7 @@ class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) {
* * {}` block * * {}` block
*/ */
fun multiNotification(unacknowledged: Collection<Plaintext>, numberOfUnacknowledgedMessages: Int): NewMessageNotification { fun multiNotification(unacknowledged: Collection<Plaintext>, numberOfUnacknowledgedMessages: Int): NewMessageNotification {
val builder = NotificationCompat.Builder(ctx) val builder = NotificationCompat.Builder(ctx, CHANNEL_ID)
builder.setSmallIcon(R.drawable.ic_notification_new_message) builder.setSmallIcon(R.drawable.ic_notification_new_message)
.setContentTitle(ctx.getString(R.string.n_new_messages, numberOfUnacknowledgedMessages)) .setContentTitle(ctx.getString(R.string.n_new_messages, numberOfUnacknowledgedMessages))
.setContentText(ctx.getString(R.string.app_name)) .setContentText(ctx.getString(R.string.app_name))
@ -113,5 +113,6 @@ class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) {
companion object { companion object {
private val NEW_MESSAGE_NOTIFICATION_ID = 1 private val NEW_MESSAGE_NOTIFICATION_ID = 1
private val SPAN_EMPHASIS = StyleSpan(Typeface.BOLD) private val SPAN_EMPHASIS = StyleSpan(Typeface.BOLD)
private val CHANNEL_ID = "abit.message"
} }
} }

View File

@ -19,7 +19,7 @@ package ch.dissem.apps.abit.notification
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.support.v7.app.NotificationCompat import android.support.v4.app.NotificationCompat
import ch.dissem.apps.abit.MainActivity import ch.dissem.apps.abit.MainActivity
import ch.dissem.apps.abit.R import ch.dissem.apps.abit.R
@ -33,7 +33,7 @@ import kotlin.concurrent.fixedRateTimer
*/ */
class ProofOfWorkNotification(ctx: Context) : AbstractNotification(ctx) { class ProofOfWorkNotification(ctx: Context) : AbstractNotification(ctx) {
private val builder = NotificationCompat.Builder(ctx) private val builder = NotificationCompat.Builder(ctx, "abit.pow")
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setUsesChronometer(true) .setUsesChronometer(true)
.setOngoing(true) .setOngoing(true)

View File

@ -17,26 +17,18 @@
package ch.dissem.apps.abit.pow package ch.dissem.apps.abit.pow
import android.content.Context import android.content.Context
import android.widget.Toast
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.ThreadFactory
import ch.dissem.apps.abit.service.Singleton import ch.dissem.apps.abit.service.Singleton
import ch.dissem.apps.abit.synchronization.SyncAdapter import ch.dissem.apps.abit.synchronization.SyncAdapter
import ch.dissem.apps.abit.util.Preferences import ch.dissem.apps.abit.util.Preferences
import ch.dissem.bitmessage.InternalContext import ch.dissem.bitmessage.InternalContext
import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.extensions.CryptoCustomMessage import ch.dissem.bitmessage.extensions.CryptoCustomMessage
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATE import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATE
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
import ch.dissem.bitmessage.utils.Singleton.cryptography import ch.dissem.bitmessage.utils.Singleton.cryptography
import org.slf4j.LoggerFactory
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
/** /**
* @author Christian Basler * @author Christian Basler
@ -54,7 +46,7 @@ class ServerPowEngine(private val ctx: Context) : ProofOfWorkEngine, InternalCon
} }
} }
override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) { override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) =
pool.execute { pool.execute {
val identity = Singleton.getIdentity(ctx) ?: throw RuntimeException("No Identity for calculating POW") val identity = Singleton.getIdentity(ctx) ?: throw RuntimeException("No Identity for calculating POW")
@ -80,7 +72,6 @@ class ServerPowEngine(private val ctx: Context) : ProofOfWorkEngine, InternalCon
LOG.error(e.message, e) LOG.error(e.message, e)
} }
} }
}
override fun setContext(context: InternalContext) { override fun setContext(context: InternalContext) {
this.context = context this.context = context

View File

@ -21,7 +21,6 @@ import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.payload.V3Pubkey import ch.dissem.bitmessage.entity.payload.V3Pubkey
import ch.dissem.bitmessage.entity.payload.V4Pubkey import ch.dissem.bitmessage.entity.payload.V4Pubkey
import ch.dissem.bitmessage.entity.valueobject.PrivateKey import ch.dissem.bitmessage.entity.valueobject.PrivateKey
import ch.dissem.bitmessage.exception.ApplicationException
import ch.dissem.bitmessage.factory.Factory import ch.dissem.bitmessage.factory.Factory
import ch.dissem.bitmessage.ports.AddressRepository import ch.dissem.bitmessage.ports.AddressRepository
import ch.dissem.bitmessage.utils.Encode import ch.dissem.bitmessage.utils.Encode
@ -149,12 +148,10 @@ class AndroidAddressRepository(private val sql: SqlHelper) : AddressRepository {
return result return result
} }
override fun save(address: BitmessageAddress) { override fun save(address: BitmessageAddress) = if (exists(address)) {
if (exists(address)) { update(address)
update(address) } else {
} else { insert(address)
insert(address)
}
} }
private fun exists(address: BitmessageAddress): Boolean { private fun exists(address: BitmessageAddress): Boolean {
@ -219,15 +216,6 @@ class AndroidAddressRepository(private val sql: SqlHelper) : AddressRepository {
db.delete(TABLE_NAME, "address = ?", arrayOf(address.address)) db.delete(TABLE_NAME, "address = ?", arrayOf(address.address))
} }
fun getById(id: String): BitmessageAddress {
val result = find("address = '$id'")
return if (result.isNotEmpty()) {
result[0]
} else {
throw ApplicationException("Address with id $id not found.")
}
}
override fun getAddress(address: String) = find("address = '$address'").firstOrNull() override fun getAddress(address: String) = find("address = '$address'").firstOrNull()
companion object { companion object {

View File

@ -18,7 +18,6 @@ package ch.dissem.apps.abit.repository
import android.content.ContentValues import android.content.ContentValues
import android.database.sqlite.SQLiteConstraintException import android.database.sqlite.SQLiteConstraintException
import ch.dissem.apps.abit.repository.SqlHelper.Companion.join
import ch.dissem.bitmessage.entity.ObjectMessage import ch.dissem.bitmessage.entity.ObjectMessage
import ch.dissem.bitmessage.entity.payload.ObjectType import ch.dissem.bitmessage.entity.payload.ObjectType
import ch.dissem.bitmessage.entity.valueobject.InventoryVector import ch.dissem.bitmessage.entity.valueobject.InventoryVector

View File

@ -48,12 +48,6 @@ class AndroidMessageRepository(private val sql: SqlHelper, private val context:
super.findMessages(label, offset, limit) super.findMessages(label, offset, limit)
} }
fun findMessageIds(label: Label) = if (label === LABEL_ARCHIVE) {
findIds("id NOT IN (SELECT message_id FROM Message_Label)")
} else {
findIds("id IN (SELECT message_id FROM Message_Label WHERE label_id=${label.id})")
}
public override fun findLabels(where: String): List<Label> { public override fun findLabels(where: String): List<Label> {
val result = LinkedList<Label>() val result = LinkedList<Label>()
@ -203,27 +197,6 @@ class AndroidMessageRepository(private val sql: SqlHelper, private val context:
db.update(PARENTS_TABLE_NAME, values, where, null) db.update(PARENTS_TABLE_NAME, values, where, null)
} }
private fun findIds(where: String): List<Long> {
val result = LinkedList<Long>()
// Define a projection that specifies which columns from the database
// you will actually use after this query.
val projection = arrayOf(COLUMN_ID)
val db = sql.readableDatabase
db.query(
TABLE_NAME, projection,
where, null, null, null,
"$COLUMN_RECEIVED DESC, $COLUMN_SENT DESC"
).use { c ->
while (c.moveToNext()) {
val id = c.getLong(0)
result.add(id)
}
}
return result
}
override fun find(where: String, offset: Int, limit: Int): List<Plaintext> { override fun find(where: String, offset: Int, limit: Int): List<Plaintext> {
val result = LinkedList<Plaintext>() val result = LinkedList<Plaintext>()

View File

@ -54,10 +54,10 @@ class AndroidNodeRegistry(private val sql: SqlHelper) : NodeRegistry {
statement.bindLong(1, node.stream) statement.bindLong(1, node.stream)
statement.bindBlob(2, node.IPv6) statement.bindBlob(2, node.IPv6)
statement.bindLong(3, node.port.toLong()) statement.bindLong(3, node.port.toLong())
try { return try {
return statement.simpleQueryForLong() statement.simpleQueryForLong()
} catch (e: SQLiteDoneException) { } catch (e: SQLiteDoneException) {
return null null
} }
} }

View File

@ -117,9 +117,8 @@ class AndroidProofOfWorkRepository(private val sql: SqlHelper) : ProofOfWorkRepo
} }
override fun putObject(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) { override fun putObject(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) =
putObject(ProofOfWorkRepository.Item(objectMessage, nonceTrialsPerByte, extraBytes)) putObject(ProofOfWorkRepository.Item(objectMessage, nonceTrialsPerByte, extraBytes))
}
override fun removeObject(initialHash: ByteArray) { override fun removeObject(initialHash: ByteArray) {
val db = sql.writableDatabase val db = sql.writableDatabase

View File

@ -29,57 +29,50 @@ import java.util.*
*/ */
class SqlHelper(private val ctx: Context) : SQLiteOpenHelper(ctx, DATABASE_NAME, null, DATABASE_VERSION) { class SqlHelper(private val ctx: Context) : SQLiteOpenHelper(ctx, DATABASE_NAME, null, DATABASE_VERSION) {
override fun onCreate(db: SQLiteDatabase) { override fun onCreate(db: SQLiteDatabase) = onUpgrade(db, 0, DATABASE_VERSION)
onUpgrade(db, 0, DATABASE_VERSION)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) = mapOf(
mapOf( 0 to {
0 to { executeMigration(db, "V1.0__Create_table_inventory")
executeMigration(db, "V1.0__Create_table_inventory") executeMigration(db, "V1.1__Create_table_address")
executeMigration(db, "V1.1__Create_table_address") executeMigration(db, "V1.2__Create_table_message")
executeMigration(db, "V1.2__Create_table_message") },
}, 1 to {
1 to { // executeMigration(db, "V2.0__Update_table_message");
// executeMigration(db, "V2.0__Update_table_message"); executeMigration(db, "V2.1__Create_table_POW")
executeMigration(db, "V2.1__Create_table_POW") },
}, 2 to {
2 to { executeMigration(db, "V3.0__Update_table_address")
executeMigration(db, "V3.0__Update_table_address") },
}, 3 to {
3 to { executeMigration(db, "V3.1__Update_table_POW")
executeMigration(db, "V3.1__Update_table_POW") executeMigration(db, "V3.2__Update_table_message")
executeMigration(db, "V3.2__Update_table_message") },
}, 4 to {
4 to { executeMigration(db, "V3.3__Create_table_node")
executeMigration(db, "V3.3__Create_table_node") },
}, 5 to {
5 to { executeMigration(db, "V3.4__Add_label_outbox")
executeMigration(db, "V3.4__Add_label_outbox") },
}, 6 to {
6 to { executeMigration(db, "V4.0__Create_table_message_parent")
executeMigration(db, "V4.0__Create_table_message_parent") },
}, 7 to {
7 to { setMissingConversationIds(db)
setMissingConversationIds(db) }
} ).filterKeys { it in oldVersion until newVersion }.forEach { (_, v) -> v.invoke() }
).filterKeys { it in oldVersion..(newVersion - 1) }.forEach { (_, v) -> v.invoke() }
}
/** /**
* Set UUIDs for all messages that have no conversation ID * Set UUIDs for all messages that have no conversation ID
*/ */
private fun setMissingConversationIds(db: SQLiteDatabase) { private fun setMissingConversationIds(db: SQLiteDatabase) = db.query(
db.query( "Message", arrayOf("id"),
"Message", arrayOf("id"), "conversation IS NULL", null, null, null, null
"conversation IS NULL", null, null, null, null ).use { c ->
).use { c -> while (c.moveToNext()) {
while (c.moveToNext()) { val id = c.getLong(0)
val id = c.getLong(0) setMissingConversationId(id, db)
setMissingConversationId(id, db)
}
} }
} }
private fun setMissingConversationId(id: Long, db: SQLiteDatabase) { private fun setMissingConversationId(id: Long, db: SQLiteDatabase) {
@ -98,7 +91,5 @@ class SqlHelper(private val ctx: Context) : SQLiteOpenHelper(ctx, DATABASE_NAME,
// 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 val DATABASE_VERSION = 7
val DATABASE_NAME = "jabit.db" val DATABASE_NAME = "jabit.db"
internal fun join(vararg types: Enum<*>): String = types.joinToString(separator = "', '", prefix = "'", postfix = "'", transform = { it.name })
} }
} }

View File

@ -94,7 +94,7 @@ class BitmessageService : Service() {
@Volatile private var running = false @Volatile private var running = false
val isRunning: Boolean val isRunning: Boolean
get() = running && Singleton.bitmessageContext?.isRunning() ?: false get() = running && Singleton.bitmessageContext?.isRunning() == true
val status: Property val status: Property
get() = Singleton.bitmessageContext?.status() ?: Property("bitmessage context") get() = Singleton.bitmessageContext?.status() ?: Property("bitmessage context")

View File

@ -38,35 +38,45 @@ class ProofOfWorkService : Service() {
notification = ProofOfWorkNotification(this) notification = ProofOfWorkNotification(this)
} }
override fun onBind(intent: Intent): IBinder? { override fun onBind(intent: Intent) = PowBinder(this)
return PowBinder(this)
}
class PowBinder internal constructor(private val service: ProofOfWorkService) : Binder() { class PowBinder internal constructor(private val service: ProofOfWorkService) : Binder() {
private val notification: ProofOfWorkNotification private val notification = service.notification
init { fun process(item: PowItem) = synchronized(queue) {
this.notification = service.notification service.startService(Intent(service, ProofOfWorkService::class.java))
} service.startForeground(ONGOING_NOTIFICATION_ID,
notification.notification)
fun process(item: PowItem) { if (!calculating) {
synchronized(queue) { calculating = true
service.startService(Intent(service, ProofOfWorkService::class.java)) service.calculateNonce(item)
service.startForeground(ONGOING_NOTIFICATION_ID, } else {
notification.notification) queue.add(item)
if (!calculating) { notification.update(queue.size).show()
calculating = true
service.calculateNonce(item)
} else {
queue.add(item)
notification.update(queue.size).show()
}
} }
} }
} }
data class PowItem(val initialHash: ByteArray, val targetValue: ByteArray, val callback: ProofOfWorkEngine.Callback) data class PowItem(val initialHash: ByteArray, val targetValue: ByteArray, val callback: ProofOfWorkEngine.Callback) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as PowItem
if (!Arrays.equals(initialHash, other.initialHash)) return false
if (!Arrays.equals(targetValue, other.targetValue)) return false
return true
}
override fun hashCode(): Int {
var result = Arrays.hashCode(initialHash)
result = 31 * result + Arrays.hashCode(targetValue)
return result
}
}
private fun calculateNonce(item: PowItem) { private fun calculateNonce(item: PowItem) {
notification.start(item) notification.start(item)

View File

@ -38,12 +38,10 @@ class ServicePowEngine(private val ctx: Context) : ProofOfWorkEngine {
private var service: PowBinder? = null private var service: PowBinder? = null
private val connection = object : ServiceConnection { private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) { override fun onServiceConnected(name: ComponentName, service: IBinder) = synchronized(lock) {
synchronized(lock) { this@ServicePowEngine.service = service as PowBinder
this@ServicePowEngine.service = service as PowBinder while (!queue.isEmpty()) {
while (!queue.isEmpty()) { service.process(queue.poll())
service.process(queue.poll())
}
} }
} }

View File

@ -50,8 +50,8 @@ object Singleton {
private var powRepo: AndroidProofOfWorkRepository? = null private var powRepo: AndroidProofOfWorkRepository? = null
private var creatingIdentity: Boolean = false private var creatingIdentity: Boolean = false
fun getBitmessageContext(context: Context): BitmessageContext { fun getBitmessageContext(context: Context): BitmessageContext =
return init({ bitmessageContext }, { bitmessageContext = it }) { init({ bitmessageContext }, { bitmessageContext = it }) {
val ctx = context.applicationContext val ctx = context.applicationContext
val sqlHelper = SqlHelper(ctx) val sqlHelper = SqlHelper(ctx)
val powRepo = AndroidProofOfWorkRepository(sqlHelper) val powRepo = AndroidProofOfWorkRepository(sqlHelper)
@ -75,7 +75,6 @@ object Singleton {
.doNotSendPubkeyOnIdentityCreation() .doNotSendPubkeyOnIdentityCreation()
.build() .build()
} }
}
fun getMessageListener(ctx: Context) = init({ messageListener }, { messageListener = it }) { MessageListener(ctx) } fun getMessageListener(ctx: Context) = init({ messageListener }, { messageListener = it }) { MessageListener(ctx) }
@ -85,8 +84,8 @@ object Singleton {
fun getProofOfWorkRepository(ctx: Context) = powRepo ?: getBitmessageContext(ctx).internals.proofOfWorkRepository fun getProofOfWorkRepository(ctx: Context) = powRepo ?: getBitmessageContext(ctx).internals.proofOfWorkRepository
fun getIdentity(ctx: Context): BitmessageAddress? { fun getIdentity(ctx: Context): BitmessageAddress? =
return init<BitmessageAddress?>(ctx, { identity }, { identity = it }) { bmc -> init<BitmessageAddress?>(ctx, { identity }, { identity = it }) { bmc ->
val identities = bmc.addresses.getIdentities() val identities = bmc.addresses.getIdentities()
if (identities.isNotEmpty()) { if (identities.isNotEmpty()) {
identities[0] identities[0]
@ -112,7 +111,6 @@ object Singleton {
null null
} }
} }
}
fun setIdentity(identity: BitmessageAddress) { fun setIdentity(identity: BitmessageAddress) {
if (identity.privateKey == null) if (identity.privateKey == null)
@ -122,8 +120,8 @@ object Singleton {
fun getConversationService(ctx: Context) = init(ctx, { conversationService }, { conversationService = it }) { ConversationService(it.messages) } fun getConversationService(ctx: Context) = init(ctx, { conversationService }, { conversationService = it }) { ConversationService(it.messages) }
private inline fun <T> init(crossinline getter: () -> T?, crossinline setter: (T) -> Unit, crossinline creator: () -> T): T { private inline fun <T> init(crossinline getter: () -> T?, crossinline setter: (T) -> Unit, crossinline creator: () -> T): T =
return getter() ?: { getter() ?: {
synchronized(Singleton) { synchronized(Singleton) {
getter() ?: { getter() ?: {
val v = creator() val v = creator()
@ -132,10 +130,9 @@ object Singleton {
}.invoke() }.invoke()
} }
}.invoke() }.invoke()
}
private inline fun <T> init(ctx: Context, crossinline getter: () -> T?, crossinline setter: (T) -> Unit, crossinline creator: (BitmessageContext) -> T): T { private inline fun <T> init(ctx: Context, crossinline getter: () -> T?, crossinline setter: (T) -> Unit, crossinline creator: (BitmessageContext) -> T): T =
return getter() ?: { getter() ?: {
val bmc = getBitmessageContext(ctx) val bmc = getBitmessageContext(ctx)
synchronized(Singleton) { synchronized(Singleton) {
getter() ?: { getter() ?: {
@ -145,5 +142,4 @@ object Singleton {
}.invoke() }.invoke()
} }
}.invoke() }.invoke()
}
} }

View File

@ -7,12 +7,14 @@ import ch.dissem.apps.abit.util.NetworkUtils
import ch.dissem.apps.abit.util.Preferences import ch.dissem.apps.abit.util.Preferences
/** /**
* Created by chrigu on 18.08.17. * Starts the Bitmessage "full node" service if conditions allow it
*/ */
class StartServiceReceiver : BroadcastReceiver() { class StartServiceReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) { override fun onReceive(context: Context, intent: Intent?) {
if (Preferences.isFullNodeActive(context)) { if (intent?.action == "android.intent.action.BOOT_COMPLETED") {
NetworkUtils.enableNode(context, false) if (Preferences.isFullNodeActive(context)) {
NetworkUtils.enableNode(context, false)
}
} }
} }
} }

View File

@ -24,13 +24,11 @@ class StartupNodeOnWifiService : JobService() {
return true return true
} }
override fun onStopJob(params: JobParameters?): Boolean { override fun onStopJob(params: JobParameters?) = if (Preferences.isWifiOnly(this)) {
if (Preferences.isWifiOnly(this)) { // Don't actually stop the service, otherwise it will be stopped after 1 or 10 minutes
// Don't actually stop the service, otherwise it will be stopped after 1 or 10 minutes // depending on Android version.
// depending on Android version. Preferences.isFullNodeActive(this)
return Preferences.isFullNodeActive(this) } else {
} else { false
return false
}
} }
} }

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
/** /**
* A bound Service that instantiates the authenticator * A bound Service that instantiates the authenticator

View File

@ -30,14 +30,9 @@ class SyncService : Service() {
/** /**
* Instantiate the sync adapter object. * Instantiate the sync adapter object.
*/ */
override fun onCreate() { override fun onCreate() = synchronized(syncAdapterLock) {
// Create the sync adapter as a singleton. if (syncAdapter == null) {
// Set the sync adapter as syncable syncAdapter = SyncAdapter(this, true)
// Disallow parallel syncs
synchronized(syncAdapterLock) {
if (syncAdapter == null) {
syncAdapter = SyncAdapter(this, true)
}
} }
} }
@ -45,13 +40,7 @@ class SyncService : Service() {
* Return an object that allows the system to invoke * Return an object that allows the system to invoke
* the sync adapter. * the sync adapter.
*/ */
override fun onBind(intent: Intent): IBinder? { override fun onBind(intent: Intent) = syncAdapter?.syncAdapterBinder
// Get the object that allows external processes
// to call onPerformSync(). The object is created
// in the base class code when the SyncAdapter
// constructors call super()
return syncAdapter?.syncAdapterBinder
}
companion object { companion object {
// Storage for an instance of the sync adapter // Storage for an instance of the sync adapter

View File

@ -47,28 +47,24 @@ object Assets {
} }
@DrawableRes @DrawableRes
fun getStatusDrawable(status: Plaintext.Status): Int { fun getStatusDrawable(status: Plaintext.Status) = when (status) {
when (status) { Plaintext.Status.RECEIVED -> 0
Plaintext.Status.RECEIVED -> return 0 Plaintext.Status.DRAFT -> R.drawable.draft
Plaintext.Status.DRAFT -> return R.drawable.draft Plaintext.Status.PUBKEY_REQUESTED -> R.drawable.public_key
Plaintext.Status.PUBKEY_REQUESTED -> return R.drawable.public_key Plaintext.Status.DOING_PROOF_OF_WORK -> R.drawable.ic_notification_proof_of_work
Plaintext.Status.DOING_PROOF_OF_WORK -> return R.drawable.ic_notification_proof_of_work Plaintext.Status.SENT -> R.drawable.sent
Plaintext.Status.SENT -> return R.drawable.sent Plaintext.Status.SENT_ACKNOWLEDGED -> R.drawable.sent_acknowledged
Plaintext.Status.SENT_ACKNOWLEDGED -> return R.drawable.sent_acknowledged else -> 0
else -> return 0
}
} }
@StringRes @StringRes
fun getStatusString(status: Plaintext.Status): Int { fun getStatusString(status: Plaintext.Status) = when (status) {
when (status) { Plaintext.Status.RECEIVED -> R.string.status_received
Plaintext.Status.RECEIVED -> return R.string.status_received Plaintext.Status.DRAFT -> R.string.status_draft
Plaintext.Status.DRAFT -> return R.string.status_draft Plaintext.Status.PUBKEY_REQUESTED -> R.string.status_public_key
Plaintext.Status.PUBKEY_REQUESTED -> return R.string.status_public_key Plaintext.Status.DOING_PROOF_OF_WORK -> R.string.proof_of_work_title
Plaintext.Status.DOING_PROOF_OF_WORK -> return R.string.proof_of_work_title Plaintext.Status.SENT -> R.string.status_sent
Plaintext.Status.SENT -> return R.string.status_sent Plaintext.Status.SENT_ACKNOWLEDGED -> R.string.status_sent_acknowledged
Plaintext.Status.SENT_ACKNOWLEDGED -> return R.string.status_sent_acknowledged else -> 0
else -> return 0
}
} }
} }

View File

@ -33,5 +33,5 @@ object Constants {
const val BITMESSAGE_URL_SCHEMA = "bitmessage:" const val BITMESSAGE_URL_SCHEMA = "bitmessage:"
val BITMESSAGE_ADDRESS_PATTERN = Pattern.compile("\\bBM-[a-zA-Z0-9]+\\b") val BITMESSAGE_ADDRESS_PATTERN = Pattern.compile("\\bBM-[a-zA-Z0-9]+\\b")!!
} }

View File

@ -39,9 +39,7 @@ object Labels {
} }
@ColorInt @ColorInt
fun getColor(label: Label): Int { fun getColor(label: Label) = if (label.type == null) {
return if (label.type == null) { label.color
label.color } else 0xFF000000.toInt()
} else 0xFF000000.toInt()
}
} }

View File

@ -12,8 +12,8 @@ import java.math.BigInteger
object PowStats { object PowStats {
private val TWO_POW_64: BigInteger = BigInteger.valueOf(2).pow(64) private val TWO_POW_64: BigInteger = BigInteger.valueOf(2).pow(64)
var averagePowUnitTime = 0L private var averagePowUnitTime = 0L
var powCount = 0L private var powCount = 0L
fun getExpectedPowTimeInMilliseconds(ctx: Context, target: ByteArray): Long { fun getExpectedPowTimeInMilliseconds(ctx: Context, target: ByteArray): Long {
if (averagePowUnitTime == 0L) { if (averagePowUnitTime == 0L) {

View File

@ -19,7 +19,6 @@ package ch.dissem.apps.abit.util
import android.content.Context import android.content.Context
import android.preference.PreferenceManager import android.preference.PreferenceManager
import ch.dissem.apps.abit.R import ch.dissem.apps.abit.R
import ch.dissem.apps.abit.listener.WifiReceiver
import ch.dissem.apps.abit.notification.ErrorNotification import ch.dissem.apps.abit.notification.ErrorNotification
import ch.dissem.apps.abit.util.Constants.PREFERENCE_FULL_NODE import ch.dissem.apps.abit.util.Constants.PREFERENCE_FULL_NODE
import ch.dissem.apps.abit.util.Constants.PREFERENCE_REQUEST_ACK import ch.dissem.apps.abit.util.Constants.PREFERENCE_REQUEST_ACK
@ -27,7 +26,6 @@ import ch.dissem.apps.abit.util.Constants.PREFERENCE_SYNC_TIMEOUT
import ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE import ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE
import ch.dissem.apps.abit.util.Constants.PREFERENCE_WIFI_ONLY import ch.dissem.apps.abit.util.Constants.PREFERENCE_WIFI_ONLY
import org.jetbrains.anko.connectivityManager import org.jetbrains.anko.connectivityManager
import org.jetbrains.anko.networkStatsManager
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException

View File

@ -24,17 +24,15 @@ import java.util.regex.Pattern
object Strings { object Strings {
private val WHITESPACES = Pattern.compile("\\s+") private val WHITESPACES = Pattern.compile("\\s+")
private fun trim(string: CharSequence?, length: Int) = if (string == null) { private fun trim(string: CharSequence?, length: Int) = when {
"" string == null -> ""
} else if (string.length <= length) { string.length <= length -> string
string else -> string.subSequence(0, length)
} else {
string.subSequence(0, length)
} }
/** /**
* Trim the string to 200 characters and normalizes all whitespaces by replacing any sequence * Trim the string to 200 characters and normalizes all whitespaces by replacing any sequence
* of whitespace characters with a single space character. * of whitespace characters with a single space character.
*/ */
fun prepareMessageExtract(string: CharSequence?) = WHITESPACES.matcher(trim(string, 200)).replaceAll(" ") fun prepareMessageExtract(string: CharSequence?): String = WHITESPACES.matcher(trim(string, 200)).replaceAll(" ")
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="315"
android:endColor="#ff6502"
android:centerColor="#ff4f02"
android:startColor="#ff3902"
android:type="linear" />
</shape>

View File

@ -13,8 +13,8 @@
android:layout_height="16dp" android:layout_height="16dp"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
app:iiv_color="@android:color/black" app:ico_color="@android:color/black"
app:iiv_icon="cmd-label" /> app:ico_icon="cmd-label" />
<TextView <TextView
android:id="@+id/label" android:id="@+id/label"

View File

@ -70,7 +70,7 @@
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
app:iiv_color="@android:color/black" app:ico_color="@android:color/black"
app:iiv_icon="cmd-rss"/> app:ico_icon="cmd-rss"/>
</RelativeLayout> </RelativeLayout>

View File

@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/send" android:id="@+id/send"
app:showAsAction="always" app:showAsAction="ifRoom"
android:icon="@drawable/ic_action_send" android:icon="@drawable/ic_action_send"
android:title="@string/send"/> android:title="@string/send"/>
<item <item

View File

@ -20,7 +20,7 @@
<string name="add_deterministic_address">Deterministic identity</string> <string name="add_deterministic_address">Deterministic identity</string>
<string name="add_deterministic_address_description">Create or recreate a deterministic identity</string> <string name="add_deterministic_address_description">Create or recreate a deterministic identity</string>
<string name="add_chan">Add Chan</string> <string name="add_chan">Add Chan</string>
<string name="add_chan_description">Create or join a isChan</string> <string name="add_chan_description">Create or join a Chan</string>
<string name="title_activity_open_bitmessage_link">Import Contact</string> <string name="title_activity_open_bitmessage_link">Import Contact</string>
<string name="connection_info_1">Stream #%1$d: one connection</string> <string name="connection_info_1">Stream #%1$d: one connection</string>

View File

@ -32,14 +32,14 @@ import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
@Config(constants = BuildConfig::class, sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP), packageName = "ch.dissem.apps.abit") @Config(sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP), packageName = "ch.dissem.apps.abit")
class AndroidAddressRepositoryTest : TestBase() { class AndroidAddressRepositoryTest : TestBase() {
private val CONTACT_A = "BM-2cW7cD5cDQJDNkE7ibmyTxfvGAmnPqa9Vt" private val contactA = "BM-2cW7cD5cDQJDNkE7ibmyTxfvGAmnPqa9Vt"
private val CONTACT_B = "BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj" private val contactB = "BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj"
private val CONTACT_C = "BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke" private val contactC = "BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke"
private lateinit var IDENTITY_A: String private lateinit var identityA: String
private lateinit var IDENTITY_B: String private lateinit var identityB: String
private lateinit var repo: AndroidAddressRepository private lateinit var repo: AndroidAddressRepository
@ -50,21 +50,23 @@ class AndroidAddressRepositoryTest : TestBase() {
repo = AndroidAddressRepository(sqlHelper) repo = AndroidAddressRepository(sqlHelper)
repo.save(BitmessageAddress(CONTACT_A)) repo.save(BitmessageAddress(contactA))
repo.save(BitmessageAddress(CONTACT_B)) repo.save(BitmessageAddress(contactB))
repo.save(BitmessageAddress(CONTACT_C)) repo.save(BitmessageAddress(contactC))
val identityA = BitmessageAddress(PrivateKey(false, 1, 1000, 1000, DOES_ACK)) BitmessageAddress(PrivateKey(false, 1, 1000, 1000, DOES_ACK)).let {
repo.save(identityA) repo.save(it)
IDENTITY_A = identityA.address identityA = it.address
val identityB = BitmessageAddress(PrivateKey(false, 1, 1000, 1000)) }
repo.save(identityB) BitmessageAddress(PrivateKey(false, 1, 1000, 1000)).let {
IDENTITY_B = identityB.address repo.save(it)
identityB = it.address
}
} }
@Test @Test
fun `ensure contact can be found`() { fun `ensure contact can be found`() {
val address = BitmessageAddress(CONTACT_A) val address = BitmessageAddress(contactA)
assertEquals(4, address.version) assertEquals(4, address.version)
assertEquals(address, repo.findContact(address.tag!!)) assertEquals(address, repo.findContact(address.tag!!))
assertNull(repo.findIdentity(address.tag!!)) assertNull(repo.findIdentity(address.tag!!))
@ -72,7 +74,7 @@ class AndroidAddressRepositoryTest : TestBase() {
@Test @Test
fun `ensure identity can be found`() { fun `ensure identity can be found`() {
val identity = BitmessageAddress(IDENTITY_A) val identity = BitmessageAddress(identityA)
assertEquals(4, identity.version) assertEquals(4, identity.version)
assertNull(repo.findContact(identity.tag!!)) assertNull(repo.findContact(identity.tag!!))
@ -131,7 +133,7 @@ class AndroidAddressRepositoryTest : TestBase() {
@Test @Test
fun `ensure existing address is updated`() { fun `ensure existing address is updated`() {
var address = repo.getAddress(CONTACT_A) var address = repo.getAddress(contactA)
address!!.alias = "Test-Alias" address!!.alias = "Test-Alias"
repo.save(address) repo.save(address)
address = repo.getAddress(address.address) address = repo.getAddress(address.address)
@ -140,10 +142,10 @@ class AndroidAddressRepositoryTest : TestBase() {
@Test @Test
fun `ensure existing keys are not deleted`() { fun `ensure existing keys are not deleted`() {
val address = BitmessageAddress(IDENTITY_A) val address = BitmessageAddress(identityA)
address.alias = "Test" address.alias = "Test"
repo.save(address) repo.save(address)
val identityA = repo.getAddress(IDENTITY_A) val identityA = repo.getAddress(identityA)
assertNotNull(identityA!!.pubkey) assertNotNull(identityA!!.pubkey)
assertNotNull(identityA.privateKey) assertNotNull(identityA.privateKey)
assertEquals("Test", identityA.alias) assertEquals("Test", identityA.alias)
@ -169,14 +171,14 @@ class AndroidAddressRepositoryTest : TestBase() {
@Test @Test
fun `ensure address is removed`() { fun `ensure address is removed`() {
val address = repo.getAddress(IDENTITY_A) val address = repo.getAddress(identityA)
repo.remove(address!!) repo.remove(address!!)
assertNull(repo.getAddress(IDENTITY_A)) assertNull(repo.getAddress(identityA))
} }
@Test @Test
fun `ensure address can be retrieved`() { fun `ensure address can be retrieved`() {
val address = repo.getAddress(IDENTITY_A) val address = repo.getAddress(identityA)
assertNotNull(address) assertNotNull(address)
assertNotNull(address!!.privateKey) assertNotNull(address!!.privateKey)
} }

View File

@ -40,7 +40,7 @@ import org.robolectric.annotation.Config
import java.util.* import java.util.*
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
@Config(constants = BuildConfig::class, sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP), packageName = "ch.dissem.apps.abit") @Config(sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP), packageName = "ch.dissem.apps.abit")
class AndroidInventoryTest : TestBase() { class AndroidInventoryTest : TestBase() {
private lateinit var inventory: Inventory private lateinit var inventory: Inventory
@ -134,14 +134,12 @@ class AndroidInventoryTest : TestBase() {
assertNull(inventory.getObject(inventoryVectorIgnore)) assertNull(inventory.getObject(inventoryVectorIgnore))
} }
private fun getObjectMessage(stream: Long, TTL: Long, payload: ObjectPayload): ObjectMessage { private fun getObjectMessage(stream: Long, TTL: Long, payload: ObjectPayload) = ObjectMessage(
return 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

@ -48,7 +48,7 @@ import org.robolectric.annotation.Config
import java.util.* import java.util.*
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
@Config(constants = BuildConfig::class, sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP), packageName = "ch.dissem.apps.abit") @Config(sdk = intArrayOf(Build.VERSION_CODES.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
@ -320,26 +320,24 @@ class AndroidMessageRepositoryTest : TestBase() {
return repo.getMessage(root.id!!) return repo.getMessage(root.id!!)
} }
private fun hasMessage(subject: String?, body: String?): Matcher<Plaintext> { private fun hasMessage(subject: String?, body: String?) = object : BaseMatcher<Plaintext>() {
return object : BaseMatcher<Plaintext>() { override fun describeTo(description: Description) {
override fun describeTo(description: Description) { description.appendText("Subject: ").appendText(subject)
description.appendText("Subject: ").appendText(subject) description.appendText(", ")
description.appendText(", ") description.appendText("Body: ").appendText(body)
description.appendText("Body: ").appendText(body) }
}
override fun matches(item: Any): Boolean { override fun matches(item: Any): Boolean {
if (item is Plaintext) { if (item is Plaintext) {
if (subject != null && subject != item.subject) { if (subject != null && subject != item.subject) {
return false
}
if (body != null && body != item.text) {
return false
}
return true
} else {
return false return false
} }
if (body != null && body != item.text) {
return false
}
return true
} else {
return false
} }
} }
} }

View File

@ -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(constants = BuildConfig::class, sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP), packageName = "ch.dissem.apps.abit") @Config(sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP), packageName = "ch.dissem.apps.abit")
class AndroidNodeRegistryTest : TestBase() { class AndroidNodeRegistryTest : TestBase() {
private lateinit var registry: NodeRegistry private lateinit var registry: NodeRegistry
@ -51,17 +51,16 @@ class AndroidNodeRegistryTest : TestBase() {
registry = AndroidNodeRegistry(sqlHelper) registry = AndroidNodeRegistry(sqlHelper)
registry.offerAddresses(Arrays.asList( registry.offerAddresses(Arrays.asList(
createAddress(1, 8444, 1, now), createAddress(lastByte = 1),
createAddress(2, 8444, 1, now), createAddress(lastByte = 2),
createAddress(3, 8444, 1, now), createAddress(lastByte = 3),
createAddress(4, 8444, 2, now) createAddress(lastByte = 4, stream = 2)
)) ))
} }
@Test @Test
fun `ensure getKnownNodes() without streams yields empty`() { fun `ensure getKnownNodes() without streams yields empty`() =
assertThat(registry.getKnownAddresses(10), empty<NetworkAddress>()) assertThat(registry.getKnownAddresses(10), empty<NetworkAddress>())
}
@Test @Test
fun `ensure predefined node is returned when database is empty`() { fun `ensure predefined node is returned when database is empty`() {
@ -86,26 +85,25 @@ class AndroidNodeRegistryTest : TestBase() {
@Test @Test
fun `ensure offered addresses are added`() { fun `ensure offered addresses are added`() {
registry.offerAddresses(Arrays.asList( registry.offerAddresses(Arrays.asList(
createAddress(1, 8444, 1, now), createAddress(lastByte = 1),
createAddress(10, 8444, 1, now), createAddress(lastByte = 10),
createAddress(11, 8444, 1, now) createAddress(lastByte = 11)
)) ))
var knownAddresses = registry.getKnownAddresses(1000, 1) var knownAddresses = registry.getKnownAddresses(1000, 1)
assertEquals(5, knownAddresses.size.toLong()) assertEquals(5, knownAddresses.size.toLong())
registry.offerAddresses(listOf(createAddress(1, 8445, 1, now))) registry.offerAddresses(listOf(createAddress(lastByte = 1, port = 8445)))
knownAddresses = registry.getKnownAddresses(1000, 1) knownAddresses = registry.getKnownAddresses(1000, 1)
assertEquals(6, knownAddresses.size.toLong()) assertEquals(6, knownAddresses.size.toLong())
} }
private fun createAddress(lastByte: Int, port: Int, stream: Long, time: Long): NetworkAddress { private fun createAddress(lastByte: Int, port: Int = 8444, stream: Long = 1, time: Long = now) =
return NetworkAddress.Builder() NetworkAddress.Builder()
.ipv6(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, lastByte) .ipv6(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, lastByte)
.port(port) .port(port)
.stream(stream) .stream(stream)
.time(time) .time(time)
.build() .build()
}
} }

View File

@ -17,7 +17,6 @@
package ch.dissem.bitmessage.repository package ch.dissem.bitmessage.repository
import android.os.Build.VERSION_CODES.LOLLIPOP import android.os.Build.VERSION_CODES.LOLLIPOP
import ch.dissem.apps.abit.BuildConfig
import ch.dissem.apps.abit.repository.AndroidAddressRepository import ch.dissem.apps.abit.repository.AndroidAddressRepository
import ch.dissem.apps.abit.repository.AndroidMessageRepository import ch.dissem.apps.abit.repository.AndroidMessageRepository
import ch.dissem.apps.abit.repository.AndroidProofOfWorkRepository import ch.dissem.apps.abit.repository.AndroidProofOfWorkRepository
@ -50,14 +49,14 @@ import kotlin.properties.Delegates
* @author Christian Basler * @author Christian Basler
*/ */
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
@Config(constants = BuildConfig::class, sdk = intArrayOf(LOLLIPOP), packageName = "ch.dissem.apps.abit") @Config(sdk = intArrayOf(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
private lateinit var messageRepo: MessageRepository private lateinit var messageRepo: MessageRepository
private var initialHash1: ByteArray by Delegates.notNull<ByteArray>() private var initialHash1: ByteArray by Delegates.notNull()
private var initialHash2: ByteArray by Delegates.notNull<ByteArray>() private var initialHash2: ByteArray by Delegates.notNull()
@Before @Before
fun setUp() { fun setUp() {
@ -68,15 +67,19 @@ class AndroidProofOfWorkRepositoryTest : TestBase() {
messageRepo = AndroidMessageRepository(sqlHelper, RuntimeEnvironment.application) messageRepo = AndroidMessageRepository(sqlHelper, RuntimeEnvironment.application)
repo = AndroidProofOfWorkRepository(sqlHelper) repo = AndroidProofOfWorkRepository(sqlHelper)
mockedInternalContext( mockedInternalContext(
addressRepository = addressRepo, addressRepository = addressRepo,
messageRepository = messageRepo, messageRepository = messageRepo,
proofOfWorkRepository = repo, proofOfWorkRepository = repo,
cryptography = cryptography() cryptography = cryptography()
) )
repo.putObject(ObjectMessage.Builder() repo.putObject(
.payload(GetPubkey(BitmessageAddress("BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn"))).build(), objectMessage = ObjectMessage.Builder()
1000, 1000) .payload(GetPubkey(BitmessageAddress("BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn")))
.build(),
nonceTrialsPerByte = 1000,
extraBytes = 1000
)
initialHash1 = repo.getItems()[0] initialHash1 = repo.getItems()[0]
val sender = loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") val sender = loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")
@ -84,28 +87,34 @@ class AndroidProofOfWorkRepositoryTest : TestBase() {
addressRepo.save(sender) addressRepo.save(sender)
addressRepo.save(recipient) addressRepo.save(recipient)
val plaintext = Plaintext.Builder(Plaintext.Type.MSG) val plaintext = Plaintext.Builder(Plaintext.Type.MSG)
.ackData(cryptography().randomBytes(32)) .ackData(cryptography().randomBytes(32))
.from(sender) .from(sender)
.to(recipient) .to(recipient)
.message("Subject", "Message") .message("Subject", "Message")
.status(Plaintext.Status.DOING_PROOF_OF_WORK) .status(Plaintext.Status.DOING_PROOF_OF_WORK)
.build() .build()
messageRepo.save(plaintext) messageRepo.save(plaintext)
initialHash2 = cryptography().getInitialHash(plaintext.ackMessage!!) plaintext.ackMessage!!.let { ackMessage ->
repo.putObject(ProofOfWorkRepository.Item( initialHash2 = cryptography().getInitialHash(ackMessage)
plaintext.ackMessage!!, repo.putObject(ProofOfWorkRepository.Item(
1000, 1000, objectMessage = ackMessage,
UnixTime.now + 10 * UnixTime.MINUTE, nonceTrialsPerByte = 1000, extraBytes = 1000,
plaintext expirationTime = UnixTime.now + 10 * UnixTime.MINUTE,
)) message = plaintext
))
}
} }
@Test @Test
fun `ensure object is stored`() { fun `ensure object is stored`() {
val sizeBefore = repo.getItems().size val sizeBefore = repo.getItems().size
repo.putObject(ObjectMessage.Builder() repo.putObject(
.payload(GetPubkey(BitmessageAddress("BM-2D9U2hv3YBMHM1zERP32anKfVKohyPN9x2"))).build(), objectMessage = ObjectMessage.Builder()
1000, 1000) .payload(GetPubkey(BitmessageAddress("BM-2D9U2hv3YBMHM1zERP32anKfVKohyPN9x2")))
.build(),
nonceTrialsPerByte = 1000,
extraBytes = 1000
)
assertThat(repo.getItems().size, `is`(sizeBefore + 1)) assertThat(repo.getItems().size, `is`(sizeBefore + 1))
} }
@ -117,19 +126,22 @@ class AndroidProofOfWorkRepositoryTest : TestBase() {
addressRepo.save(sender) addressRepo.save(sender)
addressRepo.save(recipient) addressRepo.save(recipient)
val plaintext = Plaintext.Builder(Plaintext.Type.MSG) val plaintext = Plaintext.Builder(Plaintext.Type.MSG)
.ackData(cryptography().randomBytes(32)) .ackData(cryptography().randomBytes(32))
.from(sender) .from(sender)
.to(recipient) .to(recipient)
.message("Subject", "Message") .message("Subject", "Message")
.status(Plaintext.Status.DOING_PROOF_OF_WORK) .status(Plaintext.Status.DOING_PROOF_OF_WORK)
.build() .build()
messageRepo.save(plaintext) messageRepo.save(plaintext)
repo.putObject(ProofOfWorkRepository.Item( plaintext.ackMessage!!.let { ackMessage ->
plaintext.ackMessage!!, repo.putObject(ProofOfWorkRepository.Item(
1000, 1000, objectMessage = ackMessage,
UnixTime.now + 10 * UnixTime.MINUTE, nonceTrialsPerByte = 1000,
plaintext extraBytes = 1000,
)) expirationTime = UnixTime.now + 10 * UnixTime.MINUTE,
message = plaintext
))
}
assertThat(repo.getItems().size, `is`(sizeBefore + 1)) assertThat(repo.getItems().size, `is`(sizeBefore + 1))
} }
@ -156,7 +168,7 @@ class AndroidProofOfWorkRepositoryTest : TestBase() {
} }
@Test(expected = RuntimeException::class) @Test(expected = RuntimeException::class)
fun `ensure retrieving nonexisting item causes exception`() { fun `ensure retrieving non-existing item causes exception`() {
repo.getItem(ByteArray(0)) repo.getItem(ByteArray(0))
} }
@ -168,7 +180,7 @@ class AndroidProofOfWorkRepositoryTest : TestBase() {
} }
@Test @Test
fun `ensure deletion of nonexisting item is handled silently`() { fun `ensure deletion of non-existing item is handled silently`() {
repo.removeObject(ByteArray(0)) repo.removeObject(ByteArray(0))
} }
} }

View File

@ -60,25 +60,23 @@ open class TestBase {
port: Int = 0, port: Int = 0,
connectionTTL: Long = 0, connectionTTL: Long = 0,
connectionLimit: Int = 0 connectionLimit: Int = 0
): InternalContext { ) = spy(InternalContext(
return spy(InternalContext( cryptography,
cryptography, inventory,
inventory, nodeRegistry,
nodeRegistry, networkHandler,
networkHandler, addressRepository,
addressRepository, messageRepository,
messageRepository, proofOfWorkRepository,
proofOfWorkRepository, proofOfWorkEngine,
proofOfWorkEngine, customCommandHandler,
customCommandHandler, listener,
listener, labeler,
labeler, "/Jabit:TEST/",
"/Jabit:TEST/", port,
port, connectionTTL,
connectionTTL, connectionLimit
connectionLimit ))
))
}
fun randomInventoryVector(): InventoryVector { fun randomInventoryVector(): InventoryVector {
val bytes = ByteArray(32) val bytes = ByteArray(32)
@ -86,16 +84,16 @@ open class TestBase {
return InventoryVector(bytes) return InventoryVector(bytes)
} }
fun getResource(resourceName: String) = private fun getResource(resourceName: String) =
TestBase::class.java.classLoader.getResourceAsStream(resourceName) TestBase::class.java.classLoader.getResourceAsStream(resourceName)
fun loadObjectMessage(version: Int, resourceName: String): ObjectMessage { private fun loadObjectMessage(version: Int, resourceName: String): ObjectMessage {
val data = getBytes(resourceName) val data = getBytes(resourceName)
val `in` = ByteArrayInputStream(data) val `in` = ByteArrayInputStream(data)
return Factory.getObjectMessage(version, `in`, data.size) ?: throw NoSuchElementException("error loading object message") return Factory.getObjectMessage(version, `in`, data.size) ?: throw NoSuchElementException("error loading object message")
} }
fun getBytes(resourceName: String): ByteArray { private fun getBytes(resourceName: String): ByteArray {
val `in` = getResource(resourceName) val `in` = getResource(resourceName)
val out = ByteArrayOutputStream() val out = ByteArrayOutputStream()
val buffer = ByteArray(1024) val buffer = ByteArray(1024)

View File

@ -1,19 +1,14 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
configurations.all {
// check for updates every build
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
}
buildscript { buildscript {
ext.kotlin_version = '1.1.4-3' ext.kotlin_version = '1.1.51'
ext.anko_version = '0.10.1' ext.anko_version = '0.10.2'
repositories { repositories {
jcenter() jcenter()
google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.3.3' classpath 'com.android.tools.build:gradle:3.0.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.github.ben-manes:gradle-versions-plugin:0.15.0' classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
@ -28,5 +23,6 @@ allprojects {
mavenCentral() mavenCentral()
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
maven { url "https://jitpack.io" } maven { url "https://jitpack.io" }
google()
} }
} }

Binary file not shown.

View File

@ -1,6 +1,6 @@
#Wed Jul 05 00:16:56 CEST 2017 #Mon Oct 23 08:19:50 CEST 2017
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-all.zip

6
gradlew vendored
View File

@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD="maximum"
warn ( ) { warn () {
echo "$*" echo "$*"
} }
die ( ) { die () {
echo echo
echo "$*" echo "$*"
echo echo
@ -155,7 +155,7 @@ if $cygwin ; then
fi fi
# Escape application args # Escape application args
save ( ) { save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " " echo " "
} }