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")
&& 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
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
compileSdkVersion 27
buildToolsVersion "26.0.2"
defaultConfig {
applicationId "ch.dissem.apps." + appName.toLowerCase()
minSdkVersion 19
targetSdkVersion 25
targetSdkVersion 27
versionCode 17
versionName "1.0-beta17"
multiDexEnabled true
@ -28,6 +28,9 @@ android {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
lintOptions {
abortOnError false
}
buildTypes {
release {
minifyEnabled false
@ -36,69 +39,74 @@ android {
signingConfig signingConfigs.release
}
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
//ext.jabitVersion = '2.0.4'
ext.jabitVersion = 'development-SNAPSHOT'
ext.supportVersion = '25.3.1'
ext.supportVersion = '27.0.0'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
compile "org.jetbrains.anko:anko:$anko_version"
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation "org.jetbrains.anko:anko:$anko_version"
compile "com.android.support:appcompat-v7:$supportVersion"
compile "com.android.support:preference-v7:$supportVersion"
compile "com.android.support:support-v4:$supportVersion"
compile "com.android.support:design:$supportVersion"
compile "com.android.support:multidex:1.0.1"
implementation "com.android.support:appcompat-v7:$supportVersion"
implementation "com.android.support:preference-v7:$supportVersion"
implementation "com.android.support:cardview-v7:$supportVersion"
implementation "com.android.support:support-v4:$supportVersion"
implementation "com.android.support:design:$supportVersion"
implementation "com.android.support:multidex:1.0.2"
compile "ch.dissem.jabit:jabit-core:$jabitVersion"
compile "ch.dissem.jabit:jabit-networking:$jabitVersion"
compile "ch.dissem.jabit:jabit-cryptography-spongy:$jabitVersion"
compile "ch.dissem.jabit:jabit-extensions:$jabitVersion"
compile "ch.dissem.jabit:jabit-wif:$jabitVersion"
compile "ch.dissem.jabit:jabit-exports:$jabitVersion"
implementation "ch.dissem.jabit:jabit-core:$jabitVersion"
implementation "ch.dissem.jabit:jabit-networking:$jabitVersion"
implementation "ch.dissem.jabit:jabit-cryptography-spongy:$jabitVersion"
implementation "ch.dissem.jabit:jabit-extensions:$jabitVersion"
implementation "ch.dissem.jabit:jabit-wif:$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'
compile('com.mikepenz:materialdrawer:5.9.0@aar') {
implementation 'com.mikepenz:materialize:1.1.0@aar'
implementation('com.mikepenz:materialdrawer:5.9.5@aar') {
transitive = true
}
compile('com.mikepenz:aboutlibraries:5.9.5@aar') {
implementation('com.mikepenz:aboutlibraries:5.9.7@aar') {
transitive = true
}
compile "com.mikepenz:iconics-core:2.8.3@aar"
compile 'com.mikepenz:google-material-typeface:3.0.1.0.original@aar'
compile 'com.mikepenz:community-material-typeface:1.9.32.1@aar'
implementation "com.mikepenz:iconics-core:2.9.3@aar"
implementation "com.mikepenz:iconics-views:2.9.3@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'
compile 'com.google.zxing:core:3.3.0'
implementation 'com.journeyapps:zxing-android-embedded:3.5.0@aar'
implementation 'com.google.zxing:core:3.3.1'
compile 'com.github.kobakei:MaterialFabSpeedDial:1.1.5'
compile 'com.github.amlcurran.showcaseview:library:5.4.3'
compile('com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.10.4@aar') {
implementation 'com.github.kobakei:MaterialFabSpeedDial:1.1.7'
implementation 'com.github.amlcurran.showcaseview:library:5.4.3'
// 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
}
compile 'com.github.angads25:filepicker:1.1.0'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.github.angads25:filepicker:1.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:2.8.9'
testCompile 'org.hamcrest:hamcrest-library:1.3'
testCompile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
testCompile 'com.nhaarman:mockito-kotlin-kt1.1:1.5.0'
testCompile 'org.robolectric:robolectric:3.4.2'
testCompile "org.robolectric:shadows-multidex:3.4-rc2"
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.11.0'
testImplementation 'org.hamcrest:hamcrest-library:1.3'
testImplementation 'com.nhaarman:mockito-kotlin-kt1.1:1.5.0'
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
testImplementation 'org.robolectric:robolectric:3.5'
testImplementation "org.robolectric:shadows-multidex:3.5"
androidTestImplementation "com.android.support:multidex:1.0.2"
}
idea.module {
downloadJavadoc = 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 activateOnItemClick: Boolean = false
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 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)
if (outState != null && activatedPosition != ListView.INVALID_POSITION) {
if (activatedPosition != ListView.INVALID_POSITION) {
// Serialize and persist the activated item position.
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.
*/
internal object DummyCallback : ListSelectionListener<Any> {
override fun onItemSelected(item: Any) {
// NO OP
}
override fun onItemSelected(item: Any) = Unit // NO OP
}
companion object {

View File

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

View File

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

View File

@ -47,9 +47,9 @@ class ComposeMessageActivity : AppCompatActivity() {
val fragment = ComposeMessageFragment()
fragment.arguments = intent.extras
supportFragmentManager
.beginTransaction()
.replace(R.id.content, fragment)
.commit()
.beginTransaction()
.replace(R.id.content, fragment)
.commit()
}
companion object {
@ -61,18 +61,19 @@ class ComposeMessageActivity : AppCompatActivity() {
const val EXTRA_ENCODING = "ch.dissem.abit.Message.ENCODING"
const val EXTRA_PARENT = "ch.dissem.abit.Message.PARENT"
fun launchReplyTo(fragment: Fragment, item: Plaintext) {
fragment.startActivity(getReplyIntent(fragment.activity, item))
}
fun launchReplyTo(fragment: Fragment, item: Plaintext) =
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))
}
private fun getReplyIntent(ctx: Context, item: Plaintext): Intent {
val replyIntent = Intent(ctx, ComposeMessageActivity::class.java)
val receivingIdentity = item.to
if (receivingIdentity?.isChan ?: false) {
if (receivingIdentity?.isChan == true) {
// reply to chan, not to the sender of the message
replyIntent.putExtra(EXTRA_RECIPIENT, receivingIdentity)
// 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_CONTENT,
"\n\n------------------------------------------------------\n" + item.text!!)
"\n\n------------------------------------------------------\n" + item.text!!)
return replyIntent
}
}

View File

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

View File

@ -46,9 +46,9 @@ class CreateAddressActivity : AppCompatActivity() {
else
setContentView(R.layout.activity_create_bitmessage_address)
val address = findViewById(R.id.address) as TextView
val label = findViewById(R.id.label) as EditText
val subscribe = findViewById(R.id.subscribe) as Switch
val address = findViewById<TextView>(R.id.address)
val label = findViewById<EditText>(R.id.label)
val subscribe = findViewById<Switch>(R.id.subscribe)
if (uri != null) {
val addressText = getAddress(uri)
@ -70,12 +70,12 @@ class CreateAddressActivity : AppCompatActivity() {
address.text = addressText
}
val cancel = findViewById(R.id.cancel) as Button
val cancel = findViewById<Button>(R.id.cancel)
cancel.setOnClickListener {
setResult(Activity.RESULT_CANCELED)
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.View
import android.view.ViewGroup
import android.widget.Button
import com.h6ah4i.android.widget.advrecyclerview.decoration.SimpleListDividerDecorator
@ -38,7 +39,7 @@ class ImportIdentitiesFragment : Fragment() {
private lateinit var adapter: AddressSelectorAdapter
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)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -52,14 +53,14 @@ class ImportIdentitiesFragment : Fragment() {
val layoutManager = LinearLayoutManager(activity,
LinearLayoutManager.VERTICAL,
false)
val recyclerView = view.findViewById(R.id.recycler_view) as RecyclerView
val recyclerView = view.findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.layoutManager = layoutManager
recyclerView.adapter = adapter
recyclerView.addItemDecoration(SimpleListDividerDecorator(
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)
val mainActivity = MainActivity.getInstance()
if (mainActivity != null) {

View File

@ -39,7 +39,7 @@ class InputWifFragment : Fragment() {
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)
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)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val properties = DialogProperties()

View File

@ -99,7 +99,7 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
private lateinit var nodeSwitch: SwitchDrawerItem
val floatingActionButton: FabSpeedDial?
get() = findViewById(R.id.fab) as FabSpeedDial?
get() = findViewById(R.id.fab)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -109,7 +109,7 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
setContentView(R.layout.activity_main)
fab.hide()
val toolbar = findViewById(R.id.toolbar) as Toolbar
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
val listFragment = MessageListFragment()
@ -118,7 +118,7 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
.replace(R.id.item_list, listFragment)
.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
// large-screen layouts (res/values-large and
// res/values-sw600dp). If this view is present, then the
@ -358,7 +358,7 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
selectedLabel = savedInstanceState.getSerializable("selectedLabel") as Label?
selectedLabel = savedInstanceState.getSerializable("selectedLabel") as? Label
selectedLabel?.let { selectedLabel ->
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.RecyclerView
import android.text.util.Linkify
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.text.util.Linkify.WEB_URLS
import android.view.*
import android.widget.ImageView
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.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_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.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 java.util.*
/**
* A fragment representing a single Message detail screen.
@ -68,21 +59,25 @@ class MessageDetailFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (arguments.containsKey(ARG_ITEM)) {
// Load the dummy content specified by the fragment
// arguments. In a real-world scenario, use a Loader
// to load content from a content provider.
item = arguments.getSerializable(ARG_ITEM) as Plaintext
arguments?.let { arguments ->
if (arguments.containsKey(ARG_ITEM)) {
// Load the dummy content specified by the fragment
// arguments. In a real-world scenario, use a Loader
// to load content from a content provider.
item = arguments.getSerializable(ARG_ITEM) as Plaintext
}
}
setHasOptionsMenu(true)
}
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?) {
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.
item?.let { item ->
subject.text = item.subject
@ -97,7 +92,7 @@ class MessageDetailFragment : Fragment() {
recipient.setText(R.string.broadcast)
}
}.invoke()
val labelAdapter = LabelAdapter(activity, item.labels)
val labelAdapter = LabelAdapter(ctx, item.labels)
labels.adapter = labelAdapter
labels.layoutManager = GridLayoutManager(activity, 2)
@ -105,18 +100,16 @@ class MessageDetailFragment : Fragment() {
Linkify.addLinks(text, WEB_URLS)
Linkify.addLinks(text, BITMESSAGE_ADDRESS_PATTERN, BITMESSAGE_URL_SCHEMA, null,
Linkify.TransformFilter { match, _ -> match.group() }
Linkify.TransformFilter { match, _ -> match.group() }
)
text.linksClickable = true
text.setTextIsSelectable(true)
val removed = item.labels.removeAll { it.type==Label.Type.UNREAD }
val messageRepo = Singleton.getMessageRepository(context)
val removed = item.labels.removeAll { it.type == Label.Type.UNREAD }
val messageRepo = Singleton.getMessageRepository(ctx)
if (removed) {
if (activity is MainActivity) {
(activity as MainActivity).updateUnread()
}
(activity as? MainActivity)?.updateUnread()
messageRepo.save(item)
}
val parents = ArrayList<Plaintext>(item.parents.size)
@ -126,32 +119,35 @@ class MessageDetailFragment : Fragment() {
parents.add(parent)
}
}
showRelatedMessages(view, R.id.parents, parents)
showRelatedMessages(view, R.id.responses, messageRepo.findResponses(item))
showRelatedMessages(ctx, view, R.id.parents, parents)
showRelatedMessages(ctx, view, R.id.responses, messageRepo.findResponses(item))
}
}
private fun showRelatedMessages(rootView: View, @IdRes id: Int, messages: List<Plaintext>) {
val recyclerView = rootView.findViewById(id) as RecyclerView
val adapter = RelatedMessageAdapter(activity, messages)
private fun showRelatedMessages(ctx: Context, rootView: View, @IdRes id: Int, messages: List<Plaintext>) {
val recyclerView = rootView.findViewById<RecyclerView>(id)
val adapter = RelatedMessageAdapter(ctx, messages)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(activity)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.message, menu)
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.mark_unread, GoogleMaterial.Icon
activity?.let { activity ->
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.mark_unread, GoogleMaterial.Icon
.gmd_markunread)
Drawables.addIcon(activity, menu, R.id.archive, GoogleMaterial.Icon.gmd_archive)
Drawables.addIcon(activity, menu, R.id.archive, GoogleMaterial.Icon.gmd_archive)
}
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
val messageRepo = Singleton.getMessageRepository(context)
val messageRepo = Singleton.getMessageRepository(
context ?: throw IllegalStateException("No context available")
)
item?.let { item ->
when (menuItem.itemId) {
R.id.reply -> {
@ -167,7 +163,7 @@ class MessageDetailFragment : Fragment() {
messageRepo.save(item)
}
(activity as? MainActivity)?.updateUnread()
activity.onBackPressed()
activity?.onBackPressed()
return true
}
R.id.mark_unread -> {
@ -220,10 +216,10 @@ class MessageDetailFragment : Fragment() {
override fun getItemCount() = messages.size
internal inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
internal val avatar = itemView.findViewById(R.id.avatar) as ImageView
internal val status = itemView.findViewById(R.id.status) as ImageView
internal val sender = itemView.findViewById(R.id.sender) as TextView
internal val extract = itemView.findViewById(R.id.text) as TextView
internal val avatar = itemView.findViewById<ImageView>(R.id.avatar)
internal val status = itemView.findViewById<ImageView>(R.id.status)
internal val sender = itemView.findViewById<TextView>(R.id.sender)
internal val extract = itemView.findViewById<TextView>(R.id.text)
internal var item: Plaintext? = null
init {
@ -260,16 +256,16 @@ class MessageDetailFragment : Fragment() {
// Get the data model based on position
val label = labels[position]
viewHolder.icon.setColor(Labels.getColor(label))
viewHolder.icon.setIcon(Labels.getIcon(label))
viewHolder.icon.icon?.color(Labels.getColor(label))
viewHolder.icon.icon?.icon(Labels.getIcon(label))
viewHolder.label.text = Labels.getText(label, ctx)
}
override fun getItemCount() = labels.size
internal class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var icon = itemView.findViewById(R.id.icon) as IconicsImageView
var label = itemView.findViewById(R.id.label) as TextView
var icon = itemView.findViewById<IconicsImageView>(R.id.icon)!!
var label = itemView.findViewById<TextView>(R.id.label)!!
}
}
@ -280,6 +276,6 @@ class MessageDetailFragment : Fragment() {
*/
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?) {
super.onViewCreated(view, savedInstanceState)
val context = context ?: throw IllegalStateException("No context available")
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
// 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)
messageRepo.save(item)
}
recyclerViewOnScrollListener.onScrolled(null, 0, 0)
}
override fun onItemArchived(item: Plaintext) {
Singleton.labeler.archive(item)
recyclerViewOnScrollListener.onScrolled(null, 0, 0)
}
override fun onItemViewClicked(v: View?) {
@ -239,28 +239,27 @@ class MessageListFragment : Fragment(), ListHolder<Label> {
this.swipeableMessageAdapter = adapter
Singleton.labeler.listener = { message, added, removed ->
when {
currentLabel?.type == Label.Type.TRASH && added.all { it.type == Label.Type.TRASH } && removed.any { it.type == Label.Type.TRASH } -> {
// work-around for messages that are deleted from trash
swipeableMessageAdapter?.remove(message)
recyclerViewOnScrollListener.onScrolled(null, 0, 0)
}
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
swipeableMessageAdapter?.remove(message)
recyclerViewOnScrollListener.onScrolled(null, 0, 0)
}
added.contains(currentLabel) -> {
// in most cases, top should be the correct position, but time will show if
// the message should be properly sorted in
swipeableMessageAdapter?.addFirst(message)
}
removed.contains(currentLabel) -> {
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)
swipeableMessageAdapter?.let { swipeableMessageAdapter ->
when {
currentLabel?.type == Label.Type.TRASH && added.all { it.type == Label.Type.TRASH } && removed.any { it.type == Label.Type.TRASH } -> {
// work-around for messages that are deleted from trash
swipeableMessageAdapter.remove(message)
}
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
swipeableMessageAdapter.remove(message)
}
added.contains(currentLabel) -> {
// in most cases, top should be the correct position, but time will show if
// the message should be properly sorted in
swipeableMessageAdapter.addFirst(message)
}
removed.contains(currentLabel) -> {
swipeableMessageAdapter.remove(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)
FabUtils.initFab(context, R.drawable.ic_action_compose_message, menu)
.addOnMenuItemClickListener { _, _, itemId ->
val identity = Singleton.getIdentity(activity)
val identity = Singleton.getIdentity(context)
if (identity == null) {
Toast.makeText(activity, R.string.no_identity_warning,
Toast.LENGTH_LONG).show()
@ -347,12 +346,10 @@ class MessageListFragment : Fragment(), ListHolder<Label> {
this.activateOnItemClick = activateOnItemClick
}
override fun showPreviousList(): Boolean {
return if (backStack.isEmpty()) {
false
} else {
doUpdateList(backStack.pop())
true
}
override fun showPreviousList() = if (backStack.isEmpty()) {
false
} else {
doUpdateList(backStack.pop())
true
}
}

View File

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

View File

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

View File

@ -28,19 +28,21 @@ import ch.dissem.apps.abit.service.Singleton
class StatusFragment : Fragment() {
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?) {
super.onViewCreated(view, savedInstanceState)
val bmc = Singleton.getBitmessageContext(context)
val bmc = Singleton.getBitmessageContext(
context ?: throw IllegalStateException("No context available")
)
val status = StringBuilder()
for (address in bmc.addresses.getIdentities()) {
status.append(address.address).append('\n')
}
status.append('\n')
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) {
var data: Selectable<BitmessageAddress>? = null
val checkbox = v.findViewById(R.id.checkbox) as CheckBox
val address = v.findViewById(R.id.address) as TextView
val checkbox = v.findViewById<CheckBox>(R.id.checkbox)!!
val address = v.findViewById<TextView>(R.id.address)!!
init {
checkbox.setOnCheckedChangeListener { _, isChecked ->
@ -63,12 +63,8 @@ class AddressSelectorAdapter(identities: List<BitmessageAddress>) : RecyclerView
val selected: List<BitmessageAddress>
get() {
val result = LinkedList<BitmessageAddress>()
for (selectable in data) {
if (selectable.selected) {
result.add(selectable.data)
}
}
return result
return data
.filter { it.selected }
.mapTo(LinkedList()) { it.data }
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,7 +22,6 @@ import android.support.v7.app.AppCompatDialogFragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import ch.dissem.apps.abit.MainActivity
import ch.dissem.apps.abit.R
@ -53,7 +52,7 @@ class DeterministicIdentityDialogFragment : AppCompatDialogFragment() {
super.onViewCreated(dialogView, savedInstanceState)
ok.setOnClickListener {
dismiss()
val context = activity.baseContext
val context = activity?.baseContext ?: throw IllegalStateException("No context available")
val passphraseText = passphrase.text.toString()
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
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)
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)
when (encoding) {
SIMPLE -> radioGroup.check(R.id.simple)
@ -62,7 +62,7 @@ class SelectEncodingDialogFragment : AppCompatDialogFragment() {
}
val result = Intent()
result.putExtra(EXTRA_ENCODING, encoding)
targetFragment.onActivityResult(targetRequestCode, RESULT_OK, result)
targetFragment?.onActivityResult(targetRequestCode, RESULT_OK, result)
dismiss()
})
dismiss.setOnClickListener { dismiss() }

View File

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

View File

@ -49,7 +49,5 @@ class ProfileSelectionListener(
return false
}
private fun addIdentityDialog() {
AddIdentityDialogFragment().show(fragmentManager, "dialog")
}
private fun addIdentityDialog() = 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
* selections.
*/
interface ListSelectionListener<T> {
interface ListSelectionListener<in T> {
/**
* 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.NotificationManager
import android.content.Context
import org.jetbrains.anko.notificationManager
/**
* Some base class to create and handle notifications.
*/
abstract class AbstractNotification(ctx: Context) {
protected val ctx = ctx.applicationContext
protected val manager = ctx.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
protected val ctx = ctx.applicationContext!!
private val manager = ctx.notificationManager
var notification: Notification? = null
protected set
protected var showing = false

View File

@ -18,7 +18,7 @@ package ch.dissem.apps.abit.notification
import android.content.Context
import android.support.annotation.StringRes
import android.support.v7.app.NotificationCompat
import android.support.v4.app.NotificationCompat
import ch.dissem.apps.abit.R
@ -30,7 +30,7 @@ import ch.dissem.apps.abit.R
*/
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))
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)

View File

@ -21,7 +21,7 @@ import android.app.PendingIntent
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.content.Context
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.R
import ch.dissem.apps.abit.service.BitmessageIntentService
@ -34,7 +34,7 @@ import kotlin.concurrent.fixedRateTimer
*/
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
init {

View File

@ -20,7 +20,7 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
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.InboxStyle
import android.text.Spannable
@ -43,7 +43,7 @@ import ch.dissem.apps.abit.util.Drawables.toBitmap
class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) {
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)
plaintext.subject?.let { subject ->
bigText.setSpan(SPAN_EMPHASIS, 0, subject.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
@ -83,7 +83,7 @@ class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) {
* * {}` block
*/
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)
.setContentTitle(ctx.getString(R.string.n_new_messages, numberOfUnacknowledgedMessages))
.setContentText(ctx.getString(R.string.app_name))
@ -113,5 +113,6 @@ class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) {
companion object {
private val NEW_MESSAGE_NOTIFICATION_ID = 1
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.content.Context
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.R
@ -33,7 +33,7 @@ import kotlin.concurrent.fixedRateTimer
*/
class ProofOfWorkNotification(ctx: Context) : AbstractNotification(ctx) {
private val builder = NotificationCompat.Builder(ctx)
private val builder = NotificationCompat.Builder(ctx, "abit.pow")
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setUsesChronometer(true)
.setOngoing(true)

View File

@ -17,26 +17,18 @@
package ch.dissem.apps.abit.pow
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.synchronization.SyncAdapter
import ch.dissem.apps.abit.util.Preferences
import ch.dissem.bitmessage.InternalContext
import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.extensions.CryptoCustomMessage
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.ports.ProofOfWorkEngine
import ch.dissem.bitmessage.utils.Singleton.cryptography
import org.slf4j.LoggerFactory
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
/**
* @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 {
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)
}
}
}
override fun setContext(context: InternalContext) {
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.V4Pubkey
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
import ch.dissem.bitmessage.exception.ApplicationException
import ch.dissem.bitmessage.factory.Factory
import ch.dissem.bitmessage.ports.AddressRepository
import ch.dissem.bitmessage.utils.Encode
@ -149,12 +148,10 @@ class AndroidAddressRepository(private val sql: SqlHelper) : AddressRepository {
return result
}
override fun save(address: BitmessageAddress) {
if (exists(address)) {
update(address)
} else {
insert(address)
}
override fun save(address: BitmessageAddress) = if (exists(address)) {
update(address)
} else {
insert(address)
}
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))
}
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()
companion object {

View File

@ -18,7 +18,6 @@ package ch.dissem.apps.abit.repository
import android.content.ContentValues
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.payload.ObjectType
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)
}
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> {
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)
}
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> {
val result = LinkedList<Plaintext>()

View File

@ -54,10 +54,10 @@ class AndroidNodeRegistry(private val sql: SqlHelper) : NodeRegistry {
statement.bindLong(1, node.stream)
statement.bindBlob(2, node.IPv6)
statement.bindLong(3, node.port.toLong())
try {
return statement.simpleQueryForLong()
return try {
statement.simpleQueryForLong()
} 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))
}
override fun removeObject(initialHash: ByteArray) {
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) {
override fun onCreate(db: SQLiteDatabase) {
onUpgrade(db, 0, DATABASE_VERSION)
}
override fun onCreate(db: SQLiteDatabase) = onUpgrade(db, 0, DATABASE_VERSION)
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
mapOf(
0 to {
executeMigration(db, "V1.0__Create_table_inventory")
executeMigration(db, "V1.1__Create_table_address")
executeMigration(db, "V1.2__Create_table_message")
},
1 to {
// executeMigration(db, "V2.0__Update_table_message");
executeMigration(db, "V2.1__Create_table_POW")
},
2 to {
executeMigration(db, "V3.0__Update_table_address")
},
3 to {
executeMigration(db, "V3.1__Update_table_POW")
executeMigration(db, "V3.2__Update_table_message")
},
4 to {
executeMigration(db, "V3.3__Create_table_node")
},
5 to {
executeMigration(db, "V3.4__Add_label_outbox")
},
6 to {
executeMigration(db, "V4.0__Create_table_message_parent")
},
7 to {
setMissingConversationIds(db)
}
).filterKeys { it in oldVersion..(newVersion - 1) }.forEach { (_, v) -> v.invoke() }
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) = mapOf(
0 to {
executeMigration(db, "V1.0__Create_table_inventory")
executeMigration(db, "V1.1__Create_table_address")
executeMigration(db, "V1.2__Create_table_message")
},
1 to {
// executeMigration(db, "V2.0__Update_table_message");
executeMigration(db, "V2.1__Create_table_POW")
},
2 to {
executeMigration(db, "V3.0__Update_table_address")
},
3 to {
executeMigration(db, "V3.1__Update_table_POW")
executeMigration(db, "V3.2__Update_table_message")
},
4 to {
executeMigration(db, "V3.3__Create_table_node")
},
5 to {
executeMigration(db, "V3.4__Add_label_outbox")
},
6 to {
executeMigration(db, "V4.0__Create_table_message_parent")
},
7 to {
setMissingConversationIds(db)
}
).filterKeys { it in oldVersion until newVersion }.forEach { (_, v) -> v.invoke() }
/**
* Set UUIDs for all messages that have no conversation ID
*/
private fun setMissingConversationIds(db: SQLiteDatabase) {
db.query(
"Message", arrayOf("id"),
"conversation IS NULL", null, null, null, null
).use { c ->
while (c.moveToNext()) {
val id = c.getLong(0)
setMissingConversationId(id, db)
}
private fun setMissingConversationIds(db: SQLiteDatabase) = db.query(
"Message", arrayOf("id"),
"conversation IS NULL", null, null, null, null
).use { c ->
while (c.moveToNext()) {
val id = c.getLong(0)
setMissingConversationId(id, db)
}
}
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.
private val DATABASE_VERSION = 7
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
val isRunning: Boolean
get() = running && Singleton.bitmessageContext?.isRunning() ?: false
get() = running && Singleton.bitmessageContext?.isRunning() == true
val status: Property
get() = Singleton.bitmessageContext?.status() ?: Property("bitmessage context")

View File

@ -38,35 +38,45 @@ class ProofOfWorkService : Service() {
notification = ProofOfWorkNotification(this)
}
override fun onBind(intent: Intent): IBinder? {
return PowBinder(this)
}
override fun onBind(intent: Intent) = PowBinder(this)
class PowBinder internal constructor(private val service: ProofOfWorkService) : Binder() {
private val notification: ProofOfWorkNotification
private val notification = service.notification
init {
this.notification = service.notification
}
fun process(item: PowItem) {
synchronized(queue) {
service.startService(Intent(service, ProofOfWorkService::class.java))
service.startForeground(ONGOING_NOTIFICATION_ID,
notification.notification)
if (!calculating) {
calculating = true
service.calculateNonce(item)
} else {
queue.add(item)
notification.update(queue.size).show()
}
fun process(item: PowItem) = synchronized(queue) {
service.startService(Intent(service, ProofOfWorkService::class.java))
service.startForeground(ONGOING_NOTIFICATION_ID,
notification.notification)
if (!calculating) {
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) {
notification.start(item)

View File

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

View File

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

View File

@ -7,12 +7,14 @@ import ch.dissem.apps.abit.util.NetworkUtils
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() {
override fun onReceive(context: Context, intent: Intent?) {
if (Preferences.isFullNodeActive(context)) {
NetworkUtils.enableNode(context, false)
if (intent?.action == "android.intent.action.BOOT_COMPLETED") {
if (Preferences.isFullNodeActive(context)) {
NetworkUtils.enableNode(context, false)
}
}
}
}

View File

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

View File

@ -18,7 +18,6 @@ package ch.dissem.apps.abit.synchronization
import android.app.Service
import android.content.Intent
import android.os.IBinder
/**
* A bound Service that instantiates the authenticator

View File

@ -30,14 +30,9 @@ class SyncService : Service() {
/**
* Instantiate the sync adapter object.
*/
override fun onCreate() {
// Create the sync adapter as a singleton.
// Set the sync adapter as syncable
// Disallow parallel syncs
synchronized(syncAdapterLock) {
if (syncAdapter == null) {
syncAdapter = SyncAdapter(this, true)
}
override fun onCreate() = 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
* the sync adapter.
*/
override fun onBind(intent: Intent): IBinder? {
// 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
}
override fun onBind(intent: Intent) = syncAdapter?.syncAdapterBinder
companion object {
// Storage for an instance of the sync adapter

View File

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

View File

@ -33,5 +33,5 @@ object Constants {
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
fun getColor(label: Label): Int {
return if (label.type == null) {
label.color
} else 0xFF000000.toInt()
}
fun getColor(label: Label) = if (label.type == null) {
label.color
} else 0xFF000000.toInt()
}

View File

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

View File

@ -19,7 +19,6 @@ package ch.dissem.apps.abit.util
import android.content.Context
import android.preference.PreferenceManager
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.util.Constants.PREFERENCE_FULL_NODE
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_WIFI_ONLY
import org.jetbrains.anko.connectivityManager
import org.jetbrains.anko.networkStatsManager
import org.slf4j.LoggerFactory
import java.io.File
import java.io.IOException

View File

@ -24,17 +24,15 @@ import java.util.regex.Pattern
object Strings {
private val WHITESPACES = Pattern.compile("\\s+")
private fun trim(string: CharSequence?, length: Int) = if (string == null) {
""
} else if (string.length <= length) {
string
} else {
string.subSequence(0, length)
private fun trim(string: CharSequence?, length: Int) = when {
string == null -> ""
string.length <= length -> string
else -> string.subSequence(0, length)
}
/**
* Trim the string to 200 characters and normalizes all whitespaces by replacing any sequence
* 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_alignParentStart="true"
android:layout_alignParentTop="true"
app:iiv_color="@android:color/black"
app:iiv_icon="cmd-label" />
app:ico_color="@android:color/black"
app:ico_icon="cmd-label" />
<TextView
android:id="@+id/label"

View File

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

View File

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

View File

@ -20,7 +20,7 @@
<string name="add_deterministic_address">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_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="connection_info_1">Stream #%1$d: one connection</string>

View File

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

View File

@ -40,7 +40,7 @@ import org.robolectric.annotation.Config
import java.util.*
@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() {
private lateinit var inventory: Inventory
@ -134,14 +134,12 @@ class AndroidInventoryTest : TestBase() {
assertNull(inventory.getObject(inventoryVectorIgnore))
}
private fun getObjectMessage(stream: Long, TTL: Long, payload: ObjectPayload): ObjectMessage {
return ObjectMessage(
nonce = ByteArray(8),
expiresTime = now + TTL,
stream = stream,
payload = payload
)
}
private fun getObjectMessage(stream: Long, TTL: Long, payload: ObjectPayload) = ObjectMessage(
nonce = ByteArray(8),
expiresTime = now + TTL,
stream = stream,
payload = payload
)
private val getPubkey: GetPubkey = GetPubkey(BitmessageAddress("BM-2cW7cD5cDQJDNkE7ibmyTxfvGAmnPqa9Vt"))
}

View File

@ -48,7 +48,7 @@ import org.robolectric.annotation.Config
import java.util.*
@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() {
private lateinit var contactA: BitmessageAddress
private lateinit var contactB: BitmessageAddress
@ -320,26 +320,24 @@ class AndroidMessageRepositoryTest : TestBase() {
return repo.getMessage(root.id!!)
}
private fun hasMessage(subject: String?, body: String?): Matcher<Plaintext> {
return object : BaseMatcher<Plaintext>() {
override fun describeTo(description: Description) {
description.appendText("Subject: ").appendText(subject)
description.appendText(", ")
description.appendText("Body: ").appendText(body)
}
private fun hasMessage(subject: String?, body: String?) = object : BaseMatcher<Plaintext>() {
override fun describeTo(description: Description) {
description.appendText("Subject: ").appendText(subject)
description.appendText(", ")
description.appendText("Body: ").appendText(body)
}
override fun matches(item: Any): Boolean {
if (item is Plaintext) {
if (subject != null && subject != item.subject) {
return false
}
if (body != null && body != item.text) {
return false
}
return true
} else {
override fun matches(item: Any): Boolean {
if (item is Plaintext) {
if (subject != null && subject != item.subject) {
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.
*/
@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() {
private lateinit var registry: NodeRegistry
@ -51,17 +51,16 @@ class AndroidNodeRegistryTest : TestBase() {
registry = AndroidNodeRegistry(sqlHelper)
registry.offerAddresses(Arrays.asList(
createAddress(1, 8444, 1, now),
createAddress(2, 8444, 1, now),
createAddress(3, 8444, 1, now),
createAddress(4, 8444, 2, now)
createAddress(lastByte = 1),
createAddress(lastByte = 2),
createAddress(lastByte = 3),
createAddress(lastByte = 4, stream = 2)
))
}
@Test
fun `ensure getKnownNodes() without streams yields empty`() {
fun `ensure getKnownNodes() without streams yields empty`() =
assertThat(registry.getKnownAddresses(10), empty<NetworkAddress>())
}
@Test
fun `ensure predefined node is returned when database is empty`() {
@ -86,26 +85,25 @@ class AndroidNodeRegistryTest : TestBase() {
@Test
fun `ensure offered addresses are added`() {
registry.offerAddresses(Arrays.asList(
createAddress(1, 8444, 1, now),
createAddress(10, 8444, 1, now),
createAddress(11, 8444, 1, now)
createAddress(lastByte = 1),
createAddress(lastByte = 10),
createAddress(lastByte = 11)
))
var knownAddresses = registry.getKnownAddresses(1000, 1)
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)
assertEquals(6, knownAddresses.size.toLong())
}
private fun createAddress(lastByte: Int, port: Int, stream: Long, time: Long): NetworkAddress {
return NetworkAddress.Builder()
.ipv6(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, lastByte)
.port(port)
.stream(stream)
.time(time)
.build()
}
private fun createAddress(lastByte: Int, port: Int = 8444, stream: Long = 1, time: Long = now) =
NetworkAddress.Builder()
.ipv6(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, lastByte)
.port(port)
.stream(stream)
.time(time)
.build()
}

View File

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

View File

@ -60,25 +60,23 @@ open class TestBase {
port: Int = 0,
connectionTTL: Long = 0,
connectionLimit: Int = 0
): InternalContext {
return spy(InternalContext(
cryptography,
inventory,
nodeRegistry,
networkHandler,
addressRepository,
messageRepository,
proofOfWorkRepository,
proofOfWorkEngine,
customCommandHandler,
listener,
labeler,
"/Jabit:TEST/",
port,
connectionTTL,
connectionLimit
))
}
) = spy(InternalContext(
cryptography,
inventory,
nodeRegistry,
networkHandler,
addressRepository,
messageRepository,
proofOfWorkRepository,
proofOfWorkEngine,
customCommandHandler,
listener,
labeler,
"/Jabit:TEST/",
port,
connectionTTL,
connectionLimit
))
fun randomInventoryVector(): InventoryVector {
val bytes = ByteArray(32)
@ -86,16 +84,16 @@ open class TestBase {
return InventoryVector(bytes)
}
fun getResource(resourceName: String) =
private fun getResource(resourceName: String) =
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 `in` = ByteArrayInputStream(data)
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 out = ByteArrayOutputStream()
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 {
ext.kotlin_version = '1.1.4-3'
ext.anko_version = '0.10.1'
ext.kotlin_version = '1.1.51'
ext.anko_version = '0.10.2'
repositories {
jcenter()
google()
}
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 '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
// in the individual module build.gradle files
@ -28,5 +23,6 @@ allprojects {
mavenCentral()
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
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
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
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.
MAX_FD="maximum"
warn ( ) {
warn () {
echo "$*"
}
die ( ) {
die () {
echo
echo "$*"
echo
@ -155,7 +155,7 @@ if $cygwin ; then
fi
# Escape application args
save ( ) {
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}