diff --git a/app/build.gradle b/app/build.gradle
index 1d45f11..ce56baa 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -14,8 +14,11 @@ if (project.hasProperty("project.configs")
//noinspection GroovyMissingReturnStatement
android {
compileSdkVersion 27
- buildToolsVersion "26.0.2"
+ buildToolsVersion "27.0.3"
+ signingConfigs {
+ release
+ }
defaultConfig {
applicationId "ch.dissem.apps.${appName.toLowerCase()}"
minSdkVersion 19
@@ -51,11 +54,11 @@ android {
//ext.jabitVersion = '2.0.4'
ext.jabitVersion = 'feature-refactoring-SNAPSHOT'
-ext.supportVersion = '27.0.2'
+ext.supportVersion = '27.1.1'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation "org.jetbrains.anko:anko:$anko_version"
@@ -65,49 +68,50 @@ dependencies {
implementation "com.android.support:support-v13:$supportVersion"
implementation "com.android.support:preference-v14:$supportVersion"
implementation "com.android.support:design:$supportVersion"
- implementation "com.android.support:multidex:1.0.2"
+ implementation "com.android.support:multidex:1.0.3"
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"
+ implementation "ch.dissem.jabit:jabit-cryptography-spongy:$jabitVersion"
+ testImplementation "ch.dissem.jabit:jabit-cryptography-bouncy:$jabitVersion"
implementation 'org.slf4j:slf4j-android:1.7.25'
implementation 'com.mikepenz:materialize:1.1.2@aar'
- implementation('com.mikepenz:materialdrawer:6.0.2@aar') {
+ implementation('com.mikepenz:materialdrawer:6.0.6@aar') {
transitive = true
}
- implementation('com.mikepenz:aboutlibraries:6.0.2@aar') {
+ implementation('com.mikepenz:aboutlibraries:6.0.6@aar') {
transitive = true
}
- implementation "com.mikepenz:iconics-core:3.0.0@aar"
- implementation "com.mikepenz:iconics-views:3.0.0@aar"
+ implementation "com.mikepenz:iconics-core:3.0.3@aar"
+ implementation "com.mikepenz:iconics-views:3.0.3@aar"
implementation 'com.mikepenz:google-material-typeface:3.0.1.2.original@aar'
implementation 'com.mikepenz:community-material-typeface:2.0.46.1@aar'
- implementation 'com.journeyapps:zxing-android-embedded:3.5.0@aar'
- implementation 'com.google.zxing:core:3.3.1'
+ implementation 'com.journeyapps:zxing-android-embedded:3.6.0@aar'
+ implementation 'com.google.zxing:core:3.3.2'
- implementation 'com.github.kobakei:MaterialFabSpeedDial:1.1.8'
- implementation 'com.github.amlcurran.showcaseview:library:5.4.3'
+ implementation 'com.github.kobakei:MaterialFabSpeedDial:1.2.0'
+ implementation 'com.github.deano2390:MaterialShowcaseView:1.2.0@aar'
implementation('com.github.h6ah4i:android-advancedrecyclerview:0.11.0@aar') {
transitive = true
}
implementation 'com.github.angads25:filepicker:1.1.1'
- implementation 'com.android.support.constraint:constraint-layout:1.0.2'
+ implementation 'com.android.support.constraint:constraint-layout:1.1.0'
testImplementation 'junit:junit:4.12'
- testImplementation 'org.mockito:mockito-core:2.13.0'
+ testImplementation 'org.mockito:mockito-core:2.15.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.6.1'
- testImplementation "org.robolectric:shadows-multidex:3.6.1"
+ testImplementation 'org.robolectric:robolectric:3.7.1'
+ testImplementation "org.robolectric:shadows-multidex:3.7.1"
- androidTestImplementation "com.android.support:multidex:1.0.2"
+ androidTestImplementation "com.android.support:multidex:1.0.3"
}
idea.module {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index cc7f881..a9d4f55 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,6 +6,7 @@
+
@@ -198,6 +199,10 @@
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE" />
+
+
()
activity,
R.layout.subscription_row,
R.id.name,
- LinkedList()) {
+ LinkedList()
+ ) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val result: View
val v: ViewHolder
@@ -72,7 +72,8 @@ class AddressListFragment : AbstractItemListFragment()
v.avatar.setImageDrawable(Identicon(item))
v.name.text = item.toString()
v.streamNumber.text = v.ctx.getString(R.string.stream_number, item.stream)
- v.subscribed.visibility = if (item.isSubscribed) View.VISIBLE else View.INVISIBLE
+ v.subscribed.visibility =
+ if (item.isSubscribed) View.VISIBLE else View.INVISIBLE
}
return result
}
@@ -105,11 +106,11 @@ class AddressListFragment : AbstractItemListFragment()
val menu = FabSpeedDialMenu(activity)
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)
+ activity.initFab(R.drawable.ic_action_add_contact, menu)
.addOnMenuItemClickListener { _, _, itemId ->
when (itemId) {
1 -> IntentIntegrator.forSupportFragment(this@AddressListFragment)
- .setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES)
+ .setDesiredBarcodeFormats(IntentIntegrator.QR_CODE)
.initiateScan()
2 -> {
val intent = Intent(getActivity(), CreateAddressActivity::class.java)
@@ -121,7 +122,11 @@ class AddressListFragment : AbstractItemListFragment()
}
}
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View =
inflater.inflate(R.layout.fragment_address_list, container, false)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
diff --git a/app/src/main/java/ch/dissem/apps/abit/ComposeMessageActivity.kt b/app/src/main/java/ch/dissem/apps/abit/ComposeMessageActivity.kt
index ea9828c..a900abf 100644
--- a/app/src/main/java/ch/dissem/apps/abit/ComposeMessageActivity.kt
+++ b/app/src/main/java/ch/dissem/apps/abit/ComposeMessageActivity.kt
@@ -98,7 +98,8 @@ class ComposeMessageActivity : AppCompatActivity() {
val prefix: String = if (subject.length >= 3 && subject.substring(0, 3).equals(
"RE:",
ignoreCase = true
- )) {
+ )
+ ) {
""
} else {
"RE: "
@@ -107,7 +108,7 @@ class ComposeMessageActivity : AppCompatActivity() {
}
replyIntent.putExtra(
EXTRA_CONTENT,
- "\n\n------------------------------------------------------\n" + item.text!!
+ "\n\n------------------------------------------------------\n${item.text ?: ""}"
)
return replyIntent
}
diff --git a/app/src/main/java/ch/dissem/apps/abit/ConversationDetailFragment.kt b/app/src/main/java/ch/dissem/apps/abit/ConversationDetailFragment.kt
new file mode 100644
index 0000000..f611ff1
--- /dev/null
+++ b/app/src/main/java/ch/dissem/apps/abit/ConversationDetailFragment.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2016 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.apps.abit
+
+import android.os.Bundle
+import android.support.v4.app.Fragment
+import android.support.v7.widget.LinearLayoutManager
+import android.view.*
+import ch.dissem.apps.abit.adapter.ConversationAdapter
+import ch.dissem.apps.abit.service.Singleton
+import ch.dissem.apps.abit.util.Drawables
+import ch.dissem.bitmessage.entity.Conversation
+import com.mikepenz.google_material_typeface_library.GoogleMaterial
+import kotlinx.android.synthetic.main.fragment_conversation_detail.*
+import java.util.*
+
+/**
+ * A fragment representing a single Message detail screen.
+ * This fragment is either contained in a [MainActivity]
+ * in two-pane mode (on tablets) or a [MessageDetailActivity]
+ * on handsets.
+ */
+class ConversationDetailFragment : Fragment() {
+
+ /**
+ * The content this fragment is presenting.
+ */
+ private var itemId: UUID? = null
+ private var item: Conversation? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ arguments?.let { arguments ->
+ if (arguments.containsKey(ARG_ITEM_ID)) {
+ // Load the dummy content specified by the fragment
+ // arguments. In a real-world scenario, use a Loader
+ // to load content from a content provider.
+ itemId = arguments.getSerializable(ARG_ITEM_ID) as UUID
+ }
+ }
+ setHasOptionsMenu(true)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View =
+ inflater.inflate(R.layout.fragment_conversation_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")
+
+ item = itemId?.let { Singleton.getConversationService(ctx).getConversation(it) }
+
+ // Show the dummy content as text in a TextView.
+ item?.let { item ->
+ subject.text = item.subject
+ avatar.setImageDrawable(MultiIdenticon(item.participants))
+ messages.adapter =
+ ConversationAdapter(ctx, this@ConversationDetailFragment, item, Singleton.currentLabel.value)
+ messages.layoutManager = LinearLayoutManager(activity)
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ inflater.inflate(R.menu.conversation, menu)
+ activity?.let { activity ->
+ Drawables.addIcon(activity, menu, R.id.delete, GoogleMaterial.Icon.gmd_delete)
+ 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 ?: throw IllegalStateException("No context available")
+ )
+ item?.let { item ->
+ when (menuItem.itemId) {
+ R.id.delete -> {
+ item.messages.forEach {
+ Singleton.labeler.delete(it)
+ messageRepo.remove(it)
+ }
+ MainActivity.apply { updateUnread() }
+ activity?.onBackPressed()
+ return true
+ }
+ R.id.archive -> {
+ item.messages.forEach {
+ Singleton.labeler.archive(it)
+ messageRepo.save(it)
+ }
+ MainActivity.apply { updateUnread() }
+ return true
+ }
+ else -> return false
+ }
+ }
+ return false
+ }
+
+ companion object {
+ /**
+ * The fragment argument representing the item ID that this fragment
+ * represents.
+ */
+ const val ARG_ITEM_ID = "item_id"
+ }
+}
diff --git a/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt b/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt
new file mode 100644
index 0000000..4e9439c
--- /dev/null
+++ b/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt
@@ -0,0 +1,339 @@
+/*
+ * Copyright 2016 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.apps.abit
+
+
+import android.content.Intent
+import android.os.Bundle
+import android.support.v4.app.Fragment
+import android.support.v4.content.ContextCompat
+import android.support.v7.widget.LinearLayoutManager
+import android.support.v7.widget.RecyclerView
+import android.support.v7.widget.RecyclerView.OnScrollListener
+import android.view.*
+import android.widget.Toast
+import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_BROADCAST
+import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_IDENTITY
+import ch.dissem.apps.abit.adapter.SwipeableConversationAdapter
+import ch.dissem.apps.abit.listener.ListSelectionListener
+import ch.dissem.apps.abit.repository.AndroidMessageRepository
+import ch.dissem.apps.abit.service.Singleton
+import ch.dissem.apps.abit.service.Singleton.currentLabel
+import ch.dissem.bitmessage.entity.Conversation
+import ch.dissem.bitmessage.entity.valueobject.Label
+import ch.dissem.bitmessage.utils.ConversationService
+import com.h6ah4i.android.widget.advrecyclerview.animator.SwipeDismissItemAnimator
+import com.h6ah4i.android.widget.advrecyclerview.decoration.SimpleListDividerDecorator
+import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager
+import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager
+import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils
+import io.github.kobakei.materialfabspeeddial.FabSpeedDialMenu
+import kotlinx.android.synthetic.main.fragment_message_list.*
+import org.jetbrains.anko.doAsync
+import org.jetbrains.anko.support.v4.onUiThread
+import org.jetbrains.anko.uiThread
+import java.util.*
+
+private const val PAGE_SIZE = 15
+
+/**
+ * A list fragment representing a list of Messages. This fragment
+ * also supports tablet devices by allowing list items to be given an
+ * 'activated' state upon selection. This helps indicate which item is
+ * currently being viewed in a [MessageDetailFragment].
+ *
+ *
+ * Activities containing this fragment MUST implement the [ListSelectionListener]
+ * interface.
+ */
+class ConversationListFragment : Fragment(), ListHolder