Compare commits
10 Commits
develop
...
feature/se
Author | SHA1 | Date | |
---|---|---|---|
6d7b77fd4b | |||
6b8066d473 | |||
0405d9e04f | |||
e67a4ea71b | |||
a9602368fb | |||
9f2508c1a5 | |||
6128fd32f9 | |||
ccfdff7fd8 | |||
3767d976c8 | |||
87bc01701c |
9
.drone.yml
Normal file
9
.drone.yml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
kind: pipeline
|
||||||
|
name: default
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: test
|
||||||
|
image: androidsdk/android-28
|
||||||
|
commands:
|
||||||
|
- ./gradlew assemble
|
||||||
|
- ./gradlew check
|
@ -13,8 +13,8 @@ if (project.hasProperty("project.configs")
|
|||||||
|
|
||||||
//noinspection GroovyMissingReturnStatement
|
//noinspection GroovyMissingReturnStatement
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 27
|
compileSdkVersion 28
|
||||||
buildToolsVersion "27.0.3"
|
buildToolsVersion "28.0.3"
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
release
|
release
|
||||||
@ -22,7 +22,7 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "ch.dissem.apps.${appName.toLowerCase()}"
|
applicationId "ch.dissem.apps.${appName.toLowerCase()}"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 27
|
targetSdkVersion 28
|
||||||
versionCode 23
|
versionCode 23
|
||||||
versionName "1.0-rc1"
|
versionName "1.0-rc1"
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
@ -62,13 +62,16 @@ dependencies {
|
|||||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
implementation "org.jetbrains.anko:anko:$anko_version"
|
implementation "org.jetbrains.anko:anko:$anko_version"
|
||||||
|
|
||||||
implementation "com.android.support:appcompat-v7:$supportVersion"
|
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||||
implementation "com.android.support:preference-v7:$supportVersion"
|
implementation 'androidx.preference:preference:1.0.0'
|
||||||
implementation "com.android.support:cardview-v7:$supportVersion"
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation "com.android.support:support-v13:$supportVersion"
|
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
|
||||||
implementation "com.android.support:preference-v14:$supportVersion"
|
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
|
||||||
implementation "com.android.support:design:$supportVersion"
|
implementation 'com.google.android.material:material:1.0.0'
|
||||||
implementation "com.android.support:multidex:1.0.3"
|
implementation 'androidx.multidex:multidex:2.0.0'
|
||||||
|
implementation 'androidx.core:core-ktx:1.0.0'
|
||||||
|
implementation 'androidx.sqlite:sqlite-ktx:2.0.0-rc01'
|
||||||
|
implementation 'androidx.fragment:fragment-ktx:1.0.0'
|
||||||
|
|
||||||
implementation "ch.dissem.jabit:jabit-core:$jabitVersion"
|
implementation "ch.dissem.jabit:jabit-core:$jabitVersion"
|
||||||
implementation "ch.dissem.jabit:jabit-networking:$jabitVersion"
|
implementation "ch.dissem.jabit:jabit-networking:$jabitVersion"
|
||||||
@ -80,38 +83,39 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'org.slf4j:slf4j-android:1.7.25'
|
implementation 'org.slf4j:slf4j-android:1.7.25'
|
||||||
|
|
||||||
implementation 'com.mikepenz:materialize:1.1.2@aar'
|
implementation 'com.mikepenz:materialize:1.2.0-rc01@aar'
|
||||||
implementation('com.mikepenz:materialdrawer:6.0.6@aar') {
|
implementation('com.mikepenz:materialdrawer:6.1.0-rc01.2@aar') {
|
||||||
transitive = true
|
transitive = true
|
||||||
}
|
}
|
||||||
implementation('com.mikepenz:aboutlibraries:6.0.6@aar') {
|
implementation('com.mikepenz:aboutlibraries:6.2.0-rc01@aar') {
|
||||||
transitive = true
|
transitive = true
|
||||||
}
|
}
|
||||||
implementation "com.mikepenz:iconics-core:3.0.3@aar"
|
implementation "com.mikepenz:iconics-core:3.1.0-rc01@aar"
|
||||||
implementation "com.mikepenz:iconics-views:3.0.3@aar"
|
implementation "com.mikepenz:iconics-views:3.1.0-rc01@aar"
|
||||||
implementation 'com.mikepenz:google-material-typeface:3.0.1.2.original@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.mikepenz:community-material-typeface:2.0.46.1@aar'
|
||||||
|
|
||||||
implementation 'com.journeyapps:zxing-android-embedded:3.6.0@aar'
|
implementation 'com.journeyapps:zxing-android-embedded:3.6.0@aar'
|
||||||
implementation 'com.google.zxing:core:3.3.2'
|
implementation 'com.google.zxing:core:3.3.3'
|
||||||
|
|
||||||
implementation 'com.github.kobakei:MaterialFabSpeedDial:1.2.0'
|
implementation 'com.github.kobakei:MaterialFabSpeedDial:1.2.0'
|
||||||
implementation 'com.github.deano2390:MaterialShowcaseView:1.2.0@aar'
|
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.github.angads25:filepicker:1.1.1'
|
||||||
implementation 'com.android.support.constraint:constraint-layout:1.1.1'
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
|
||||||
|
|
||||||
|
implementation "io.reactivex.rxjava2:rxjava:2.2.2"
|
||||||
|
implementation "io.reactivex.rxjava2:rxkotlin:2.3.0"
|
||||||
|
implementation "io.reactivex.rxjava2:rxandroid:2.1.0"
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
testImplementation 'org.mockito:mockito-core:2.15.0'
|
testImplementation 'org.mockito:mockito-core:2.19.0'
|
||||||
testImplementation 'org.hamcrest:hamcrest-library:1.3'
|
testImplementation 'org.hamcrest:hamcrest-library:1.3'
|
||||||
testImplementation 'com.nhaarman:mockito-kotlin-kt1.1:1.5.0'
|
testImplementation 'com.nhaarman:mockito-kotlin-kt1.1:1.6.0'
|
||||||
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
testImplementation 'org.robolectric:robolectric:3.7.1'
|
testImplementation 'org.robolectric:robolectric:3.7.1'
|
||||||
testImplementation "org.robolectric:shadows-multidex:3.7.1"
|
testImplementation "org.robolectric:shadows-multidex:3.7.1"
|
||||||
|
|
||||||
androidTestImplementation "com.android.support:multidex:1.0.3"
|
androidTestImplementation "androidx.multidex:multidex:2.0.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
idea.module {
|
idea.module {
|
||||||
|
@ -11,11 +11,10 @@
|
|||||||
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
|
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
|
||||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
||||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="android.support.multidex.MultiDexApplication"
|
android:name="androidx.multidex.MultiDexApplication"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
@ -120,23 +119,13 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<service
|
|
||||||
android:name=".service.BitmessageService"
|
|
||||||
android:exported="false" />
|
|
||||||
<service
|
<service
|
||||||
android:name=".service.ProofOfWorkService"
|
android:name=".service.ProofOfWorkService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<!-- Synchronization -->
|
|
||||||
<provider
|
|
||||||
android:name=".synchronization.StubProvider"
|
|
||||||
android:authorities="ch.dissem.apps.abit.provider"
|
|
||||||
android:exported="false"
|
|
||||||
android:syncable="true" />
|
|
||||||
|
|
||||||
<!-- Exports -->
|
<!-- Exports -->
|
||||||
<provider
|
<provider
|
||||||
android:name="android.support.v4.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="ch.dissem.apps.abit.fileprovider"
|
android:authorities="ch.dissem.apps.abit.fileprovider"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:grantUriPermissions="true">
|
android:grantUriPermissions="true">
|
||||||
@ -145,30 +134,6 @@
|
|||||||
android:resource="@xml/file_paths" />
|
android:resource="@xml/file_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<service
|
|
||||||
android:name=".synchronization.AuthenticatorService"
|
|
||||||
android:exported="true"
|
|
||||||
tools:ignore="ExportedService">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.accounts.AccountAuthenticator" />
|
|
||||||
</intent-filter>
|
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="android.accounts.AccountAuthenticator"
|
|
||||||
android:resource="@xml/authenticator" />
|
|
||||||
</service>
|
|
||||||
<service
|
|
||||||
android:name=".synchronization.SyncService"
|
|
||||||
android:exported="true"
|
|
||||||
tools:ignore="ExportedService">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.content.SyncAdapter" />
|
|
||||||
</intent-filter>
|
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="android.content.SyncAdapter"
|
|
||||||
android:resource="@xml/syncadapter" />
|
|
||||||
</service>
|
|
||||||
<service
|
<service
|
||||||
android:name=".service.BitmessageIntentService"
|
android:name=".service.BitmessageIntentService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
@ -182,7 +147,12 @@
|
|||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".service.StartupNodeOnWifiService"
|
android:name=".service.NodeStartupService"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".service.CleanupService"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ package ch.dissem.apps.abit
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.ListFragment
|
import androidx.fragment.app.ListFragment
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ListView
|
import android.widget.ListView
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ package ch.dissem.apps.abit
|
|||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.view.*
|
import android.view.*
|
||||||
|
@ -28,7 +28,6 @@ import android.widget.ImageView
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import ch.dissem.apps.abit.service.Singleton
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
import com.google.zxing.integration.android.IntentIntegrator
|
|
||||||
import io.github.kobakei.materialfabspeeddial.FabSpeedDialMenu
|
import io.github.kobakei.materialfabspeeddial.FabSpeedDialMenu
|
||||||
import org.jetbrains.anko.doAsync
|
import org.jetbrains.anko.doAsync
|
||||||
import org.jetbrains.anko.uiThread
|
import org.jetbrains.anko.uiThread
|
||||||
@ -44,7 +43,7 @@ class AddressListFragment : AbstractItemListFragment<Void, BitmessageAddress>()
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
adapter = object : ArrayAdapter<BitmessageAddress>(
|
adapter = object : ArrayAdapter<BitmessageAddress>(
|
||||||
activity,
|
activity!!,
|
||||||
R.layout.subscription_row,
|
R.layout.subscription_row,
|
||||||
R.id.name,
|
R.id.name,
|
||||||
LinkedList()
|
LinkedList()
|
||||||
@ -85,10 +84,10 @@ class AddressListFragment : AbstractItemListFragment<Void, BitmessageAddress>()
|
|||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
initFab(activity as MainActivity)
|
initFab(activity as MainActivity)
|
||||||
updateList()
|
reloadList()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateList() {
|
override fun reloadList() {
|
||||||
adapter.clear()
|
adapter.clear()
|
||||||
context?.let { context ->
|
context?.let { context ->
|
||||||
val addressRepo = Singleton.getAddressRepository(context)
|
val addressRepo = Singleton.getAddressRepository(context)
|
||||||
@ -109,9 +108,10 @@ class AddressListFragment : AbstractItemListFragment<Void, BitmessageAddress>()
|
|||||||
activity.initFab(R.drawable.ic_action_add_contact, menu)
|
activity.initFab(R.drawable.ic_action_add_contact, menu)
|
||||||
.addOnMenuItemClickListener { _, _, itemId ->
|
.addOnMenuItemClickListener { _, _, itemId ->
|
||||||
when (itemId) {
|
when (itemId) {
|
||||||
1 -> IntentIntegrator.forSupportFragment(this@AddressListFragment)
|
// FIXME
|
||||||
.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE)
|
// 1 -> IntentIntegrator.forSupportFragment(this@AddressListFragment)
|
||||||
.initiateScan()
|
// .setDesiredBarcodeFormats(IntentIntegrator.QR_CODE)
|
||||||
|
// .initiateScan()
|
||||||
2 -> {
|
2 -> {
|
||||||
val intent = Intent(getActivity(), CreateAddressActivity::class.java)
|
val intent = Intent(getActivity(), CreateAddressActivity::class.java)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
@ -138,7 +138,7 @@ class AddressListFragment : AbstractItemListFragment<Void, BitmessageAddress>()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateList(label: Void) = updateList()
|
override fun updateList(label: Void) = reloadList()
|
||||||
|
|
||||||
private data class ViewHolder(
|
private data class ViewHolder(
|
||||||
val ctx: Context,
|
val ctx: Context,
|
||||||
|
@ -20,8 +20,8 @@ import android.app.Activity
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import android.support.v7.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import ch.dissem.apps.abit.service.Singleton
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
import ch.dissem.bitmessage.entity.Plaintext
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
import ch.dissem.bitmessage.entity.Plaintext.Encoding.EXTENDED
|
import ch.dissem.bitmessage.entity.Plaintext.Encoding.EXTENDED
|
||||||
|
@ -20,7 +20,7 @@ import android.app.Activity.RESULT_OK
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
@ -35,7 +35,7 @@ import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_SUBJECT
|
|||||||
import ch.dissem.apps.abit.adapter.ContactAdapter
|
import ch.dissem.apps.abit.adapter.ContactAdapter
|
||||||
import ch.dissem.apps.abit.dialog.SelectEncodingDialogFragment
|
import ch.dissem.apps.abit.dialog.SelectEncodingDialogFragment
|
||||||
import ch.dissem.apps.abit.service.Singleton
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
import ch.dissem.apps.abit.util.Preferences
|
import ch.dissem.apps.abit.util.preferences
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
import ch.dissem.bitmessage.entity.Plaintext
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST
|
import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST
|
||||||
@ -76,7 +76,7 @@ class ComposeMessageFragment : Fragment() {
|
|||||||
parents.addAll(draft.parents)
|
parents.addAll(draft.parents)
|
||||||
} else {
|
} else {
|
||||||
var id = getSerializable(EXTRA_IDENTITY) as? BitmessageAddress
|
var id = getSerializable(EXTRA_IDENTITY) as? BitmessageAddress
|
||||||
if (context != null && (id == null || id.privateKey == null)) {
|
if (context != null && id?.privateKey == null) {
|
||||||
id = Singleton.getIdentity(context!!)
|
id = Singleton.getIdentity(context!!)
|
||||||
}
|
}
|
||||||
if (id?.privateKey != null) {
|
if (id?.privateKey != null) {
|
||||||
@ -89,13 +89,12 @@ class ComposeMessageFragment : Fragment() {
|
|||||||
recipient = getSerializable(EXTRA_RECIPIENT) as BitmessageAddress
|
recipient = getSerializable(EXTRA_RECIPIENT) as BitmessageAddress
|
||||||
}
|
}
|
||||||
if (containsKey(EXTRA_SUBJECT)) {
|
if (containsKey(EXTRA_SUBJECT)) {
|
||||||
subject = getString(EXTRA_SUBJECT)
|
subject = getString(EXTRA_SUBJECT) ?: throw IllegalStateException("EXTRA_SUBJECT expected")
|
||||||
}
|
}
|
||||||
if (containsKey(EXTRA_CONTENT)) {
|
if (containsKey(EXTRA_CONTENT)) {
|
||||||
content = getString(EXTRA_CONTENT)
|
content = getString(EXTRA_CONTENT) ?: throw IllegalStateException("EXTRA_CONTENT expected")
|
||||||
}
|
}
|
||||||
encoding = getSerializable(EXTRA_ENCODING) as? Plaintext.Encoding ?:
|
encoding = getSerializable(EXTRA_ENCODING) as? Plaintext.Encoding ?: Plaintext.Encoding.SIMPLE
|
||||||
Plaintext.Encoding.SIMPLE
|
|
||||||
|
|
||||||
if (containsKey(EXTRA_PARENT)) {
|
if (containsKey(EXTRA_PARENT)) {
|
||||||
val parent = getSerializable(EXTRA_PARENT) as Plaintext
|
val parent = getSerializable(EXTRA_PARENT) as Plaintext
|
||||||
@ -221,7 +220,7 @@ class ComposeMessageFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
val sender = sender_input.selectedItem as? ch.dissem.bitmessage.entity.BitmessageAddress
|
val sender = sender_input.selectedItem as? ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
sender?.let { builder.from(it) }
|
sender?.let { builder.from(it) }
|
||||||
if (!Preferences.requestAcknowledgements(ctx)) {
|
if (!ctx.preferences.requestAcknowledgements) {
|
||||||
builder.preventAck()
|
builder.preventAck()
|
||||||
}
|
}
|
||||||
when (encoding) {
|
when (encoding) {
|
||||||
|
@ -17,8 +17,8 @@
|
|||||||
package ch.dissem.apps.abit
|
package ch.dissem.apps.abit
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import android.support.v7.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import ch.dissem.apps.abit.adapter.ConversationAdapter
|
import ch.dissem.apps.abit.adapter.ConversationAdapter
|
||||||
import ch.dissem.apps.abit.service.Singleton
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
@ -74,8 +74,7 @@ class ConversationDetailFragment : Fragment() {
|
|||||||
item?.let { item ->
|
item?.let { item ->
|
||||||
subject.text = item.subject
|
subject.text = item.subject
|
||||||
avatar.setImageDrawable(MultiIdenticon(item.participants))
|
avatar.setImageDrawable(MultiIdenticon(item.participants))
|
||||||
messages.adapter =
|
messages.adapter = ConversationAdapter(ctx, this@ConversationDetailFragment, item, Singleton.currentLabel)
|
||||||
ConversationAdapter(ctx, this@ConversationDetailFragment, item, Singleton.currentLabel.value)
|
|
||||||
messages.layoutManager = LinearLayoutManager(activity)
|
messages.layoutManager = LinearLayoutManager(activity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,33 +19,29 @@ package ch.dissem.apps.abit
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.Fragment
|
|
||||||
import android.support.v4.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.view.*
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
|
||||||
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_BROADCAST
|
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_BROADCAST
|
||||||
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_IDENTITY
|
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_IDENTITY
|
||||||
|
import ch.dissem.apps.abit.adapter.EventListener
|
||||||
|
import ch.dissem.apps.abit.adapter.SwipeToDeleteCallback
|
||||||
import ch.dissem.apps.abit.adapter.SwipeableConversationAdapter
|
import ch.dissem.apps.abit.adapter.SwipeableConversationAdapter
|
||||||
import ch.dissem.apps.abit.listener.ListSelectionListener
|
import ch.dissem.apps.abit.listener.ListSelectionListener
|
||||||
import ch.dissem.apps.abit.repository.AndroidMessageRepository
|
import ch.dissem.apps.abit.repository.AndroidMessageRepository
|
||||||
import ch.dissem.apps.abit.service.Singleton
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
import ch.dissem.apps.abit.service.Singleton.currentLabel
|
import ch.dissem.apps.abit.service.Singleton.currentLabel
|
||||||
import ch.dissem.bitmessage.entity.Conversation
|
import ch.dissem.apps.abit.util.preferences
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label
|
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||||
import ch.dissem.bitmessage.utils.ConversationService
|
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 io.github.kobakei.materialfabspeeddial.FabSpeedDialMenu
|
||||||
|
import io.reactivex.disposables.Disposable
|
||||||
import kotlinx.android.synthetic.main.fragment_message_list.*
|
import kotlinx.android.synthetic.main.fragment_message_list.*
|
||||||
import org.jetbrains.anko.doAsync
|
import org.jetbrains.anko.*
|
||||||
import org.jetbrains.anko.support.v4.onUiThread
|
|
||||||
import org.jetbrains.anko.uiThread
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
private const val PAGE_SIZE = 15
|
private const val PAGE_SIZE = 15
|
||||||
@ -67,12 +63,9 @@ class ConversationListFragment : Fragment(), ListHolder<Label> {
|
|||||||
|
|
||||||
private var layoutManager: LinearLayoutManager? = null
|
private var layoutManager: LinearLayoutManager? = null
|
||||||
private var swipeableConversationAdapter: SwipeableConversationAdapter? = null
|
private var swipeableConversationAdapter: SwipeableConversationAdapter? = null
|
||||||
private var wrappedAdapter: RecyclerView.Adapter<*>? = null
|
|
||||||
private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null
|
|
||||||
private var recyclerViewTouchActionGuardManager: RecyclerViewTouchActionGuardManager? = null
|
|
||||||
|
|
||||||
private val recyclerViewOnScrollListener = object : OnScrollListener() {
|
private val recyclerViewOnScrollListener = object : OnScrollListener() {
|
||||||
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
layoutManager?.let { layoutManager ->
|
layoutManager?.let { layoutManager ->
|
||||||
val visibleItemCount = layoutManager.childCount
|
val visibleItemCount = layoutManager.childCount
|
||||||
val totalItemCount = layoutManager.itemCount
|
val totalItemCount = layoutManager.itemCount
|
||||||
@ -90,10 +83,18 @@ class ConversationListFragment : Fragment(), ListHolder<Label> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var emptyTrashMenuItem: MenuItem? = null
|
private var emptyTrashMenuItem: MenuItem? = null
|
||||||
|
private var deleteAllMenuItem: MenuItem? = null
|
||||||
private lateinit var messageRepo: AndroidMessageRepository
|
private lateinit var messageRepo: AndroidMessageRepository
|
||||||
private lateinit var conversationService: ConversationService
|
private lateinit var conversationService: ConversationService
|
||||||
private var activateOnItemClick: Boolean = false
|
private var activateOnItemClick: Boolean = false
|
||||||
|
|
||||||
|
private var subscription: Disposable? = null
|
||||||
|
|
||||||
|
override fun setActivateOnItemClick(activateOnItemClick: Boolean) {
|
||||||
|
swipeableConversationAdapter?.activateOnItemClick = activateOnItemClick
|
||||||
|
this.activateOnItemClick = activateOnItemClick
|
||||||
|
}
|
||||||
|
|
||||||
private val backStack = Stack<Label>()
|
private val backStack = Stack<Label>()
|
||||||
|
|
||||||
fun loadMoreItems() {
|
fun loadMoreItems() {
|
||||||
@ -103,11 +104,12 @@ class ConversationListFragment : Fragment(), ListHolder<Label> {
|
|||||||
val conversationIds = messageRepo.findConversations(
|
val conversationIds = messageRepo.findConversations(
|
||||||
currentLabel.value,
|
currentLabel.value,
|
||||||
messageAdapter.itemCount,
|
messageAdapter.itemCount,
|
||||||
PAGE_SIZE
|
PAGE_SIZE,
|
||||||
|
context?.preferences?.separateIdentities == true
|
||||||
)
|
)
|
||||||
conversationIds.forEach { conversationId ->
|
conversationIds.forEach { conversationId ->
|
||||||
val conversation = conversationService.getConversation(conversationId)
|
val conversation = conversationService.getConversation(conversationId)
|
||||||
onUiThread {
|
uiThread {
|
||||||
messageAdapter.add(conversation)
|
messageAdapter.add(conversation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -130,15 +132,17 @@ class ConversationListFragment : Fragment(), ListHolder<Label> {
|
|||||||
messageRepo = Singleton.getMessageRepository(activity)
|
messageRepo = Singleton.getMessageRepository(activity)
|
||||||
conversationService = Singleton.getConversationService(activity)
|
conversationService = Singleton.getConversationService(activity)
|
||||||
|
|
||||||
currentLabel.addObserver(this) { new -> doUpdateList(new) }
|
subscription = currentLabel.subscribe { new -> doUpdateList(new) }
|
||||||
doUpdateList(currentLabel.value)
|
doUpdateList(currentLabel.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
currentLabel.removeObserver(this)
|
subscription?.dispose()
|
||||||
super.onPause()
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun reloadList() = doUpdateList(currentLabel.value)
|
||||||
|
|
||||||
private fun doUpdateList(label: Label?) {
|
private fun doUpdateList(label: Label?) {
|
||||||
val mainActivity = activity as? MainActivity
|
val mainActivity = activity as? MainActivity
|
||||||
swipeableConversationAdapter?.clear(label)
|
swipeableConversationAdapter?.clear(label)
|
||||||
@ -148,7 +152,10 @@ class ConversationListFragment : Fragment(), ListHolder<Label> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
emptyTrashMenuItem?.isVisible = label.type == Label.Type.TRASH
|
emptyTrashMenuItem?.isVisible = label.type == Label.Type.TRASH
|
||||||
mainActivity?.apply {
|
// I'm not yet sure if it's a good idea in conversation views, so it's off for now
|
||||||
|
deleteAllMenuItem?.isVisible = false
|
||||||
|
|
||||||
|
MainActivity.apply {
|
||||||
if ("archive" == label.toString()) {
|
if ("archive" == label.toString()) {
|
||||||
updateTitle(getString(R.string.archive))
|
updateTitle(getString(R.string.archive))
|
||||||
} else {
|
} else {
|
||||||
@ -171,75 +178,57 @@ class ConversationListFragment : Fragment(), ListHolder<Label> {
|
|||||||
|
|
||||||
val context = context ?: throw IllegalStateException("No context available")
|
val context = context ?: throw IllegalStateException("No context available")
|
||||||
|
|
||||||
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
val listener = object : EventListener {
|
||||||
|
override fun onItemDeleted(position: Int) {
|
||||||
// touch guard manager (this class is required to suppress scrolling while swipe-dismiss
|
swipeableConversationAdapter?.getItem(position)?.let { item ->
|
||||||
// animation is running)
|
item.messages.forEach {
|
||||||
val touchActionGuardManager = RecyclerViewTouchActionGuardManager().apply {
|
Singleton.labeler.delete(it)
|
||||||
setInterceptVerticalScrollingWhileAnimationRunning(true)
|
messageRepo.save(it)
|
||||||
isEnabled = true
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// swipe manager
|
|
||||||
val swipeManager = RecyclerViewSwipeManager()
|
|
||||||
|
|
||||||
//swipeableConversationAdapter
|
|
||||||
val adapter = SwipeableConversationAdapter(context).apply {
|
|
||||||
setActivateOnItemClick(activateOnItemClick)
|
|
||||||
}
|
|
||||||
adapter.eventListener = object : SwipeableConversationAdapter.EventListener {
|
|
||||||
override fun onItemDeleted(item: Conversation) {
|
|
||||||
item.messages.forEach {
|
|
||||||
Singleton.labeler.delete(it)
|
|
||||||
messageRepo.save(it)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
swipeableConversationAdapter?.removeAt(position)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemArchived(item: Conversation) {
|
override fun onItemArchived(position: Int) {
|
||||||
item.messages.forEach { Singleton.labeler.archive(it) }
|
swipeableConversationAdapter?.getItem(position)?.let { item ->
|
||||||
|
item.messages.forEach {
|
||||||
|
Singleton.labeler.archive(it)
|
||||||
|
messageRepo.save(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
swipeableConversationAdapter?.removeAt(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemViewClicked(v: View?) {
|
override fun onItemSelected(position: Int) {
|
||||||
val position = recycler_view.getChildAdapterPosition(v)
|
swipeableConversationAdapter?.selectedPosition = position
|
||||||
adapter.setSelectedPosition(position)
|
|
||||||
if (position != RecyclerView.NO_POSITION) {
|
if (position != RecyclerView.NO_POSITION) {
|
||||||
MainActivity.apply { onItemSelected(adapter.getItem(position)) }
|
swipeableConversationAdapter?.getItem(position)?.let { item ->
|
||||||
|
MainActivity.apply { onItemSelected(item) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
|
||||||
// wrap for swiping
|
|
||||||
wrappedAdapter = swipeManager.createWrappedAdapter(adapter)
|
|
||||||
|
|
||||||
val animator = SwipeDismissItemAnimator()
|
|
||||||
|
|
||||||
// Change animations are enabled by default since support-v7-recyclerview v22.
|
|
||||||
// Disable the change animation in order to make turning back animation of swiped item
|
|
||||||
// works properly.
|
|
||||||
animator.supportsChangeAnimations = false
|
|
||||||
|
|
||||||
recycler_view.layoutManager = layoutManager
|
recycler_view.layoutManager = layoutManager
|
||||||
recycler_view.adapter = wrappedAdapter // requires *wrapped* swipeableConversationAdapter
|
swipeableConversationAdapter = SwipeableConversationAdapter(context).apply {
|
||||||
recycler_view.itemAnimator = animator
|
activateOnItemClick = this@ConversationListFragment.activateOnItemClick
|
||||||
|
eventListener = listener
|
||||||
|
}
|
||||||
|
recycler_view.adapter = swipeableConversationAdapter
|
||||||
recycler_view.addOnScrollListener(recyclerViewOnScrollListener)
|
recycler_view.addOnScrollListener(recyclerViewOnScrollListener)
|
||||||
|
|
||||||
recycler_view.addItemDecoration(
|
val dirs = when (currentLabel.value?.type) {
|
||||||
SimpleListDividerDecorator(
|
Label.Type.TRASH -> ItemTouchHelper.LEFT
|
||||||
ContextCompat.getDrawable(context, R.drawable.list_divider_h), true
|
else -> ItemTouchHelper.LEFT + ItemTouchHelper.RIGHT
|
||||||
)
|
}
|
||||||
)
|
|
||||||
|
|
||||||
// NOTE:
|
val swipeHandler = SwipeToDeleteCallback(context, dirs, listener)
|
||||||
// The initialization order is very important! This order determines the priority of
|
|
||||||
// touch event handling.
|
|
||||||
//
|
|
||||||
// priority: TouchActionGuard > Swipe > DragAndDrop
|
|
||||||
touchActionGuardManager.attachRecyclerView(recycler_view)
|
|
||||||
swipeManager.attachRecyclerView(recycler_view)
|
|
||||||
|
|
||||||
recyclerViewTouchActionGuardManager = touchActionGuardManager
|
val itemTouchHelper = ItemTouchHelper(swipeHandler)
|
||||||
recyclerViewSwipeManager = swipeManager
|
itemTouchHelper.attachToRecyclerView(recycler_view)
|
||||||
swipeableConversationAdapter = adapter
|
|
||||||
|
|
||||||
// FIXME Singleton.updateMessageListAdapterInListener(adapter)
|
// FIXME Singleton.updateMessageListAdapterInListener(adapter)
|
||||||
}
|
}
|
||||||
@ -277,18 +266,6 @@ class ConversationListFragment : Fragment(), ListHolder<Label> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
recyclerViewSwipeManager?.release()
|
|
||||||
recyclerViewSwipeManager = null
|
|
||||||
|
|
||||||
recyclerViewTouchActionGuardManager?.release()
|
|
||||||
recyclerViewTouchActionGuardManager = null
|
|
||||||
|
|
||||||
recycler_view.itemAnimator = null
|
|
||||||
recycler_view.adapter = null
|
|
||||||
|
|
||||||
wrappedAdapter?.let { WrapperAdapterUtils.releaseAll(it) }
|
|
||||||
wrappedAdapter = null
|
|
||||||
|
|
||||||
swipeableConversationAdapter = null
|
swipeableConversationAdapter = null
|
||||||
layoutManager = null
|
layoutManager = null
|
||||||
|
|
||||||
@ -298,6 +275,7 @@ class ConversationListFragment : Fragment(), ListHolder<Label> {
|
|||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
inflater.inflate(R.menu.message_list, menu)
|
inflater.inflate(R.menu.message_list, menu)
|
||||||
emptyTrashMenuItem = menu.findItem(R.id.empty_trash)
|
emptyTrashMenuItem = menu.findItem(R.id.empty_trash)
|
||||||
|
deleteAllMenuItem = menu.findItem(R.id.delete_all)
|
||||||
super.onCreateOptionsMenu(menu, inflater)
|
super.onCreateOptionsMenu(menu, inflater)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,12 +285,22 @@ class ConversationListFragment : Fragment(), ListHolder<Label> {
|
|||||||
currentLabel.value?.let { label ->
|
currentLabel.value?.let { label ->
|
||||||
if (label.type != Label.Type.TRASH) return true
|
if (label.type != Label.Type.TRASH) return true
|
||||||
|
|
||||||
doAsync {
|
deleteAllMessages(label)
|
||||||
for (message in messageRepo.findMessages(label)) {
|
}
|
||||||
messageRepo.remove(message)
|
return true
|
||||||
}
|
}
|
||||||
|
R.id.delete_all -> {
|
||||||
uiThread { doUpdateList(label) }
|
currentLabel.value?.let { label ->
|
||||||
|
context?.apply {
|
||||||
|
alert(
|
||||||
|
R.string.delete_all_messages_in_list,
|
||||||
|
R.string.delete_all_messages_in_list_ask
|
||||||
|
) {
|
||||||
|
positiveButton(R.string.delete) {
|
||||||
|
deleteAllMessages(label)
|
||||||
|
}
|
||||||
|
cancelButton { }
|
||||||
|
}.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -321,19 +309,24 @@ class ConversationListFragment : Fragment(), ListHolder<Label> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateList(label: Label) {
|
private fun deleteAllMessages(label: Label) {
|
||||||
currentLabel.value = label
|
doAsync {
|
||||||
|
for (message in messageRepo.findMessages(label, 0, 0, context?.preferences?.separateIdentities == true)) {
|
||||||
|
messageRepo.remove(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
uiThread { doUpdateList(label) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setActivateOnItemClick(activateOnItemClick: Boolean) {
|
override fun updateList(label: Label) {
|
||||||
swipeableConversationAdapter?.setActivateOnItemClick(activateOnItemClick)
|
currentLabel.onNext(label)
|
||||||
this.activateOnItemClick = activateOnItemClick
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showPreviousList() = if (backStack.isEmpty()) {
|
override fun showPreviousList() = if (backStack.isEmpty()) {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
currentLabel.value = backStack.pop()
|
currentLabel.onNext(backStack.pop())
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ package ch.dissem.apps.abit
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v7.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import android.util.Base64.URL_SAFE
|
import android.util.Base64.URL_SAFE
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
|
@ -2,8 +2,8 @@ package ch.dissem.apps.abit
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.NavUtils
|
import androidx.core.app.NavUtils
|
||||||
import android.support.v7.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import com.mikepenz.materialize.MaterializeBuilder
|
import com.mikepenz.materialize.MaterializeBuilder
|
||||||
import kotlinx.android.synthetic.main.scrolling_toolbar_layout.*
|
import kotlinx.android.synthetic.main.scrolling_toolbar_layout.*
|
||||||
|
@ -18,7 +18,7 @@ package ch.dissem.apps.abit
|
|||||||
|
|
||||||
import android.graphics.*
|
import android.graphics.*
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.support.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import android.text.TextPaint
|
import android.text.TextPaint
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
import org.jetbrains.anko.collections.forEachWithIndex
|
import org.jetbrains.anko.collections.forEachWithIndex
|
||||||
@ -68,8 +68,8 @@ class Identicon(input: BitmessageAddress) : Drawable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
override fun draw(canvas: Canvas) {
|
||||||
val width = canvas.width.toFloat()
|
val width = bounds.width().toFloat()
|
||||||
val height = canvas.height.toFloat()
|
val height = bounds.height().toFloat()
|
||||||
draw(canvas, 0f, 0f, width, height)
|
draw(canvas, 0f, 0f, width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,11 +124,11 @@ class MultiIdenticon(input: List<BitmessageAddress>, @ColorInt private val backg
|
|||||||
color = backgroundColor
|
color = backgroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
private val identicons = input.sortedBy { it.isChan }.map { Identicon(it) }.take(4)
|
private val identicons = input.asSequence().sortedBy { it.isChan }.map { Identicon(it) }.take(4).toList()
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
override fun draw(canvas: Canvas) {
|
||||||
val width = canvas.width.toFloat()
|
val width = bounds.width().toFloat()
|
||||||
val height = canvas.height.toFloat()
|
val height = bounds.height().toFloat()
|
||||||
|
|
||||||
when (identicons.size) {
|
when (identicons.size) {
|
||||||
0 -> canvas.drawCircle(width / 2, height / 2, width / 2, paint)
|
0 -> canvas.drawCircle(width / 2, height / 2, width / 2, paint)
|
||||||
|
@ -16,19 +16,18 @@
|
|||||||
|
|
||||||
package ch.dissem.apps.abit
|
package ch.dissem.apps.abit
|
||||||
|
|
||||||
import android.app.Fragment
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.content.ContextCompat
|
|
||||||
import android.support.v7.widget.LinearLayoutManager
|
|
||||||
import android.support.v7.widget.RecyclerView
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import ch.dissem.apps.abit.adapter.AddressSelectorAdapter
|
import ch.dissem.apps.abit.adapter.AddressSelectorAdapter
|
||||||
import ch.dissem.apps.abit.service.Singleton
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
import ch.dissem.bitmessage.wif.WifImporter
|
import ch.dissem.bitmessage.wif.WifImporter
|
||||||
import com.h6ah4i.android.widget.advrecyclerview.decoration.SimpleListDividerDecorator
|
|
||||||
import org.ini4j.InvalidFileFormatException
|
import org.ini4j.InvalidFileFormatException
|
||||||
import org.jetbrains.anko.longToast
|
import org.jetbrains.anko.longToast
|
||||||
|
|
||||||
@ -47,34 +46,31 @@ class ImportIdentitiesFragment : Fragment() {
|
|||||||
inflater.inflate(R.layout.fragment_import_select_identities, container, false)
|
inflater.inflate(R.layout.fragment_import_select_identities, container, false)
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
val ctx = activity ?: throw IllegalStateException("No activity available")
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
val wifData = arguments.getString(WIF_DATA)
|
val wifData = arguments?.getString(WIF_DATA) ?: throw IllegalStateException("No WIF data")
|
||||||
val bmc = Singleton.getBitmessageContext(activity)
|
val bmc = Singleton.getBitmessageContext(ctx)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
importer = WifImporter(bmc, wifData)
|
importer = WifImporter(bmc, wifData)
|
||||||
} catch (e: InvalidFileFormatException) {
|
} catch (e: InvalidFileFormatException) {
|
||||||
longToast(R.string.invalid_wif_file)
|
ctx.longToast(R.string.invalid_wif_file)
|
||||||
activity.finish()
|
ctx.finish()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter = AddressSelectorAdapter(importer.getIdentities())
|
adapter = AddressSelectorAdapter(importer.getIdentities())
|
||||||
val layoutManager = LinearLayoutManager(
|
val layoutManager = LinearLayoutManager(
|
||||||
activity,
|
activity,
|
||||||
LinearLayoutManager.VERTICAL,
|
RecyclerView.VERTICAL,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
val recyclerView = view.findViewById<RecyclerView>(R.id.recycler_view)
|
val recyclerView = view.findViewById<RecyclerView>(R.id.recycler_view)
|
||||||
recyclerView.layoutManager = layoutManager
|
recyclerView.layoutManager = layoutManager
|
||||||
recyclerView.adapter = adapter
|
recyclerView.adapter = adapter
|
||||||
|
|
||||||
recyclerView.addItemDecoration(
|
recyclerView.addItemDecoration(DividerItemDecoration(ctx, DividerItemDecoration.HORIZONTAL))
|
||||||
SimpleListDividerDecorator(
|
|
||||||
ContextCompat.getDrawable(activity, R.drawable.list_divider_h), true
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
view.findViewById<Button>(R.id.finish).setOnClickListener {
|
view.findViewById<Button>(R.id.finish).setOnClickListener {
|
||||||
importer.importAll(adapter.selected)
|
importer.importAll(adapter.selected)
|
||||||
@ -83,7 +79,7 @@ class ImportIdentitiesFragment : Fragment() {
|
|||||||
addIdentityEntry(selected)
|
addIdentityEntry(selected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
activity.finish()
|
ctx.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package ch.dissem.apps.abit
|
package ch.dissem.apps.abit
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.fragment.app.transaction
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Christian Basler
|
* @author Christian Basler
|
||||||
@ -29,19 +30,20 @@ class ImportIdentityActivity : DetailActivity() {
|
|||||||
val wifData: String? = savedInstanceState?.getString(ImportIdentitiesFragment.WIF_DATA)
|
val wifData: String? = savedInstanceState?.getString(ImportIdentitiesFragment.WIF_DATA)
|
||||||
|
|
||||||
if (wifData == null) {
|
if (wifData == null) {
|
||||||
fragmentManager.beginTransaction()
|
supportFragmentManager.transaction {
|
||||||
.replace(R.id.content, InputWifFragment())
|
replace(R.id.content, InputWifFragment())
|
||||||
.commit()
|
}
|
||||||
} else {
|
} else {
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
bundle.putString(ImportIdentitiesFragment.WIF_DATA, wifData)
|
bundle.putString(ImportIdentitiesFragment.WIF_DATA, wifData)
|
||||||
|
|
||||||
val fragment = ImportIdentitiesFragment()
|
val fragment = ImportIdentitiesFragment().apply {
|
||||||
fragment.arguments = bundle
|
arguments = bundle
|
||||||
|
}
|
||||||
|
|
||||||
fragmentManager.beginTransaction()
|
supportFragmentManager.transaction {
|
||||||
.replace(R.id.content, fragment)
|
replace(R.id.content, fragment)
|
||||||
.commit()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,10 +16,11 @@
|
|||||||
|
|
||||||
package ch.dissem.apps.abit
|
package ch.dissem.apps.abit
|
||||||
|
|
||||||
import android.app.Fragment
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.transaction
|
||||||
import com.github.angads25.filepicker.model.DialogConfigs
|
import com.github.angads25.filepicker.model.DialogConfigs
|
||||||
import com.github.angads25.filepicker.model.DialogProperties
|
import com.github.angads25.filepicker.model.DialogProperties
|
||||||
import com.github.angads25.filepicker.view.FilePickerDialog
|
import com.github.angads25.filepicker.view.FilePickerDialog
|
||||||
@ -40,9 +41,9 @@ class InputWifFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
|
||||||
inflater.inflate(R.layout.fragment_import_input, container, false)
|
inflater.inflate(R.layout.fragment_import_input, container, false)
|
||||||
|
|
||||||
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
next.setOnClickListener {
|
next.setOnClickListener {
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
@ -52,9 +53,9 @@ class InputWifFragment : Fragment() {
|
|||||||
arguments = bundle
|
arguments = bundle
|
||||||
}
|
}
|
||||||
|
|
||||||
fragmentManager.beginTransaction()
|
fragmentManager?.transaction {
|
||||||
.replace(R.id.content, fragment)
|
replace(R.id.content, fragment)
|
||||||
.commit()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,9 +88,9 @@ class InputWifFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
activity,
|
activity,
|
||||||
R.string.error_loading_data,
|
R.string.error_loading_data,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,8 @@ package ch.dissem.apps.abit
|
|||||||
* @author Christian Basler
|
* @author Christian Basler
|
||||||
*/
|
*/
|
||||||
interface ListHolder<in L> {
|
interface ListHolder<in L> {
|
||||||
|
fun reloadList()
|
||||||
|
|
||||||
fun updateList(label: L)
|
fun updateList(label: L)
|
||||||
|
|
||||||
fun setActivateOnItemClick(activateOnItemClick: Boolean)
|
fun setActivateOnItemClick(activateOnItemClick: Boolean)
|
||||||
|
@ -20,22 +20,23 @@ import android.content.Intent
|
|||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.annotation.DrawableRes
|
|
||||||
import android.support.v4.app.Fragment
|
|
||||||
import android.support.v7.app.AppCompatActivity
|
|
||||||
import android.support.v7.widget.Toolbar
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.transaction
|
||||||
import ch.dissem.apps.abit.drawer.ProfileImageListener
|
import ch.dissem.apps.abit.drawer.ProfileImageListener
|
||||||
import ch.dissem.apps.abit.drawer.ProfileSelectionListener
|
import ch.dissem.apps.abit.drawer.ProfileSelectionListener
|
||||||
import ch.dissem.apps.abit.listener.ListSelectionListener
|
import ch.dissem.apps.abit.listener.ListSelectionListener
|
||||||
import ch.dissem.apps.abit.repository.AndroidLabelRepository.Companion.LABEL_ARCHIVE
|
import ch.dissem.apps.abit.repository.AndroidLabelRepository.Companion.LABEL_ARCHIVE
|
||||||
|
import ch.dissem.apps.abit.repository.AndroidMessageRepository
|
||||||
import ch.dissem.apps.abit.service.Singleton
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
import ch.dissem.apps.abit.service.Singleton.currentLabel
|
import ch.dissem.apps.abit.service.Singleton.currentLabel
|
||||||
import ch.dissem.apps.abit.synchronization.SyncAdapter
|
|
||||||
import ch.dissem.apps.abit.util.NetworkUtils
|
|
||||||
import ch.dissem.apps.abit.util.Preferences
|
|
||||||
import ch.dissem.apps.abit.util.getColor
|
import ch.dissem.apps.abit.util.getColor
|
||||||
import ch.dissem.apps.abit.util.getIcon
|
import ch.dissem.apps.abit.util.getIcon
|
||||||
|
import ch.dissem.apps.abit.util.network
|
||||||
|
import ch.dissem.apps.abit.util.preferences
|
||||||
import ch.dissem.bitmessage.BitmessageContext
|
import ch.dissem.bitmessage.BitmessageContext
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
import ch.dissem.bitmessage.entity.Conversation
|
import ch.dissem.bitmessage.entity.Conversation
|
||||||
@ -54,6 +55,7 @@ import com.mikepenz.materialdrawer.model.interfaces.IProfile
|
|||||||
import com.mikepenz.materialdrawer.model.interfaces.Nameable
|
import com.mikepenz.materialdrawer.model.interfaces.Nameable
|
||||||
import io.github.kobakei.materialfabspeeddial.FabSpeedDial
|
import io.github.kobakei.materialfabspeeddial.FabSpeedDial
|
||||||
import io.github.kobakei.materialfabspeeddial.FabSpeedDialMenu
|
import io.github.kobakei.materialfabspeeddial.FabSpeedDialMenu
|
||||||
|
import io.reactivex.disposables.Disposable
|
||||||
import kotlinx.android.synthetic.main.activity_main.*
|
import kotlinx.android.synthetic.main.activity_main.*
|
||||||
import org.jetbrains.anko.doAsync
|
import org.jetbrains.anko.doAsync
|
||||||
import org.jetbrains.anko.uiThread
|
import org.jetbrains.anko.uiThread
|
||||||
@ -95,7 +97,10 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
|
|||||||
var hasDetailPane: Boolean = false
|
var hasDetailPane: Boolean = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
private var subscription: Disposable? = null
|
||||||
|
|
||||||
private lateinit var bmc: BitmessageContext
|
private lateinit var bmc: BitmessageContext
|
||||||
|
private lateinit var messageRepo: AndroidMessageRepository
|
||||||
private lateinit var accountHeader: AccountHeader
|
private lateinit var accountHeader: AccountHeader
|
||||||
|
|
||||||
private lateinit var drawer: Drawer
|
private lateinit var drawer: Drawer
|
||||||
@ -108,6 +113,7 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
instance = WeakReference(this)
|
instance = WeakReference(this)
|
||||||
bmc = Singleton.getBitmessageContext(this)
|
bmc = Singleton.getBitmessageContext(this)
|
||||||
|
messageRepo = Singleton.getMessageRepository(this)
|
||||||
|
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
fab.hide()
|
fab.hide()
|
||||||
@ -145,11 +151,6 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
|
|||||||
ComposeMessageActivity.launchReplyTo(this, item)
|
ComposeMessageActivity.launchReplyTo(this, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Preferences.useTrustedNode(this)) {
|
|
||||||
SyncAdapter.startSync(this)
|
|
||||||
} else {
|
|
||||||
SyncAdapter.stopSync(this)
|
|
||||||
}
|
|
||||||
if (drawer.isDrawerOpen) {
|
if (drawer.isDrawerOpen) {
|
||||||
MaterialShowcaseView.Builder(this)
|
MaterialShowcaseView.Builder(this)
|
||||||
.setMaskColour(R.color.colorPrimary)
|
.setMaskColour(R.color.colorPrimary)
|
||||||
@ -179,8 +180,6 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
|
|||||||
.setDelay(1000)
|
.setDelay(1000)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
SyncAdapter.startSync(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <F> changeList(listFragment: F) where F : Fragment, F : ListHolder<*> {
|
private fun <F> changeList(listFragment: F) where F : Fragment, F : ListHolder<*> {
|
||||||
@ -259,14 +258,13 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
|
|||||||
|
|
||||||
nodeSwitch = SwitchDrawerItem()
|
nodeSwitch = SwitchDrawerItem()
|
||||||
.withIdentifier(ID_NODE_SWITCH)
|
.withIdentifier(ID_NODE_SWITCH)
|
||||||
.withName(R.string.full_node)
|
.withName(R.string.online)
|
||||||
.withIcon(CommunityMaterial.Icon.cmd_cloud_outline)
|
.withIcon(CommunityMaterial.Icon.cmd_cloud_outline)
|
||||||
.withChecked(Preferences.isFullNodeActive(this))
|
.withChecked(preferences.online)
|
||||||
.withOnCheckedChangeListener { _, _, isChecked ->
|
.withOnCheckedChangeListener { _, _, isChecked ->
|
||||||
|
preferences.online = isChecked
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
NetworkUtils.enableNode(this@MainActivity)
|
network.enableNode(true)
|
||||||
} else {
|
|
||||||
NetworkUtils.disableNode(this@MainActivity)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,16 +301,16 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
|
|||||||
|
|
||||||
uiThread {
|
uiThread {
|
||||||
if (intent.hasExtra(EXTRA_SHOW_LABEL)) {
|
if (intent.hasExtra(EXTRA_SHOW_LABEL)) {
|
||||||
currentLabel.value = intent.getSerializableExtra(EXTRA_SHOW_LABEL) as Label
|
currentLabel.onNext(intent.getSerializableExtra(EXTRA_SHOW_LABEL) as Label)
|
||||||
} else if (currentLabel.value == null) {
|
} else if (currentLabel.value == null) {
|
||||||
currentLabel.value = labels[0]
|
currentLabel.onNext(labels[0])
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (label in labels) {
|
for (label in labels) {
|
||||||
addLabelEntry(label)
|
addLabelEntry(label)
|
||||||
}
|
}
|
||||||
currentLabel.value?.let {
|
currentLabel.value?.let { label ->
|
||||||
drawer.setSelection(it.id as Long)
|
drawer.setSelection(label.id as Long)
|
||||||
}
|
}
|
||||||
updateUnread()
|
updateUnread()
|
||||||
}
|
}
|
||||||
@ -331,7 +329,7 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
|
|||||||
val itemList = supportFragmentManager.findFragmentById(R.id.item_list)
|
val itemList = supportFragmentManager.findFragmentById(R.id.item_list)
|
||||||
val tag = item.tag
|
val tag = item.tag
|
||||||
if (tag is Label) {
|
if (tag is Label) {
|
||||||
currentLabel.value = tag
|
currentLabel.onNext(tag)
|
||||||
if (tag.type == Label.Type.INBOX || tag == LABEL_ARCHIVE) {
|
if (tag.type == Label.Type.INBOX || tag == LABEL_ARCHIVE) {
|
||||||
if (itemList !is ConversationListFragment) {
|
if (itemList !is ConversationListFragment) {
|
||||||
changeList(ConversationListFragment())
|
changeList(ConversationListFragment())
|
||||||
@ -346,18 +344,17 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
|
|||||||
when (item.name.textRes) {
|
when (item.name.textRes) {
|
||||||
R.string.contacts_and_subscriptions -> {
|
R.string.contacts_and_subscriptions -> {
|
||||||
if (itemList is AddressListFragment) {
|
if (itemList is AddressListFragment) {
|
||||||
itemList.updateList()
|
itemList.reloadList()
|
||||||
} else {
|
} else {
|
||||||
changeList(AddressListFragment())
|
changeList(AddressListFragment())
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
R.string.settings -> {
|
R.string.settings -> {
|
||||||
supportFragmentManager
|
supportFragmentManager?.transaction {
|
||||||
.beginTransaction()
|
replace(R.id.item_list, SettingsFragment())
|
||||||
.replace(R.id.item_list, SettingsFragment())
|
addToBackStack(null)
|
||||||
.addToBackStack(null)
|
}
|
||||||
.commit()
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
R.string.full_node -> return true
|
R.string.full_node -> return true
|
||||||
@ -369,14 +366,12 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
network.enableNode(false)
|
||||||
updateUnread()
|
updateUnread()
|
||||||
if (Preferences.isFullNodeActive(this) && Preferences.isConnectionAllowed(this@MainActivity)) {
|
|
||||||
NetworkUtils.enableNode(this, false)
|
|
||||||
}
|
|
||||||
Singleton.getMessageListener(this).resetNotification()
|
Singleton.getMessageListener(this).resetNotification()
|
||||||
currentLabel.addObserver(this) { label ->
|
subscription = currentLabel.subscribe { label ->
|
||||||
if (label != null && label.id is Long) {
|
if (label.id is Long) {
|
||||||
drawer.setSelection(label.id as Long)
|
drawer.setSelection(label.id as Long, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
active = true
|
active = true
|
||||||
@ -384,7 +379,7 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
currentLabel.removeObserver(this)
|
subscription?.dispose()
|
||||||
super.onPause()
|
super.onPause()
|
||||||
active = false
|
active = false
|
||||||
}
|
}
|
||||||
@ -446,7 +441,7 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
|
|||||||
if (item.tag is Label) {
|
if (item.tag is Label) {
|
||||||
val label = item.tag as Label
|
val label = item.tag as Label
|
||||||
if (label !== LABEL_ARCHIVE) {
|
if (label !== LABEL_ARCHIVE) {
|
||||||
val unread = bmc.messages.countUnread(label)
|
val unread = messageRepo.countUnread(label, preferences.separateIdentities)
|
||||||
if (unread > 0) {
|
if (unread > 0) {
|
||||||
(item as PrimaryDrawerItem).withBadge(unread.toString())
|
(item as PrimaryDrawerItem).withBadge(unread.toString())
|
||||||
} else {
|
} else {
|
||||||
@ -548,9 +543,8 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
|
|||||||
|
|
||||||
fun initFab(@DrawableRes drawableRes: Int, menu: FabSpeedDialMenu): FabSpeedDial {
|
fun initFab(@DrawableRes drawableRes: Int, menu: FabSpeedDialMenu): FabSpeedDial {
|
||||||
val fab = floatingActionButton ?: throw IllegalStateException("Fab must not be null")
|
val fab = floatingActionButton ?: throw IllegalStateException("Fab must not be null")
|
||||||
|
fab.hide()
|
||||||
fab.removeAllOnMenuItemClickListeners()
|
fab.removeAllOnMenuItemClickListeners()
|
||||||
fab.show()
|
|
||||||
fab.closeMenu()
|
|
||||||
val mainFab = fab.mainFab
|
val mainFab = fab.mainFab
|
||||||
mainFab.setImageResource(drawableRes)
|
mainFab.setImageResource(drawableRes)
|
||||||
fab.setMenu(menu)
|
fab.setMenu(menu)
|
||||||
@ -562,6 +556,8 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
|
|||||||
mainFab.setImageResource(drawableRes)
|
mainFab.setImageResource(drawableRes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fab.show()
|
||||||
|
fab.closeMenu()
|
||||||
return fab
|
return fab
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,15 +574,6 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
|
|||||||
|
|
||||||
private var instance: WeakReference<MainActivity>? = null
|
private var instance: WeakReference<MainActivity>? = null
|
||||||
|
|
||||||
fun updateNodeSwitch() {
|
|
||||||
apply {
|
|
||||||
runOnUiThread {
|
|
||||||
nodeSwitch.withChecked(Preferences.isFullNodeActive(this))
|
|
||||||
drawer.updateStickyFooterItem(nodeSwitch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the given code in the main activity context, if it currently exists. Otherwise,
|
* Runs the given code in the main activity context, if it currently exists. Otherwise,
|
||||||
* it's ignored.
|
* it's ignored.
|
||||||
|
@ -2,7 +2,7 @@ package ch.dissem.apps.abit
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.NavUtils
|
import androidx.core.app.NavUtils
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import ch.dissem.bitmessage.entity.Plaintext
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
|
||||||
|
@ -19,11 +19,11 @@ package ch.dissem.apps.abit
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.annotation.IdRes
|
import androidx.annotation.IdRes
|
||||||
import android.support.v4.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import android.support.v7.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import android.support.v7.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import android.support.v7.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import android.text.util.Linkify
|
import android.text.util.Linkify
|
||||||
import android.text.util.Linkify.WEB_URLS
|
import android.text.util.Linkify.WEB_URLS
|
||||||
import android.view.*
|
import android.view.*
|
||||||
|
@ -19,32 +19,28 @@ package ch.dissem.apps.abit
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import android.support.v4.content.ContextCompat
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import android.support.v7.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import android.support.v7.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import android.support.v7.widget.RecyclerView.OnScrollListener
|
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_BROADCAST
|
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_BROADCAST
|
||||||
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_IDENTITY
|
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_IDENTITY
|
||||||
|
import ch.dissem.apps.abit.adapter.EventListener
|
||||||
|
import ch.dissem.apps.abit.adapter.SwipeToDeleteCallback
|
||||||
import ch.dissem.apps.abit.adapter.SwipeableMessageAdapter
|
import ch.dissem.apps.abit.adapter.SwipeableMessageAdapter
|
||||||
import ch.dissem.apps.abit.listener.ListSelectionListener
|
import ch.dissem.apps.abit.listener.ListSelectionListener
|
||||||
import ch.dissem.apps.abit.repository.AndroidMessageRepository
|
import ch.dissem.apps.abit.repository.AndroidMessageRepository
|
||||||
import ch.dissem.apps.abit.service.Singleton
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
import ch.dissem.apps.abit.service.Singleton.currentLabel
|
import ch.dissem.apps.abit.service.Singleton.currentLabel
|
||||||
import ch.dissem.bitmessage.entity.Plaintext
|
import ch.dissem.apps.abit.util.preferences
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label
|
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||||
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 io.github.kobakei.materialfabspeeddial.FabSpeedDialMenu
|
||||||
|
import io.reactivex.disposables.Disposable
|
||||||
import kotlinx.android.synthetic.main.fragment_message_list.*
|
import kotlinx.android.synthetic.main.fragment_message_list.*
|
||||||
import org.jetbrains.anko.doAsync
|
import org.jetbrains.anko.*
|
||||||
import org.jetbrains.anko.support.v4.onUiThread
|
|
||||||
import org.jetbrains.anko.uiThread
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
private const val PAGE_SIZE = 15
|
private const val PAGE_SIZE = 15
|
||||||
@ -64,21 +60,20 @@ class MessageListFragment : Fragment(), ListHolder<Label> {
|
|||||||
private var isLoading = false
|
private var isLoading = false
|
||||||
private var isLastPage = false
|
private var isLastPage = false
|
||||||
|
|
||||||
|
private var subscription: Disposable? = null
|
||||||
|
|
||||||
private var layoutManager: LinearLayoutManager? = null
|
private var layoutManager: LinearLayoutManager? = null
|
||||||
private var swipeableMessageAdapter: SwipeableMessageAdapter? = null
|
private var swipeableMessageAdapter: SwipeableMessageAdapter? = null
|
||||||
private var wrappedAdapter: RecyclerView.Adapter<*>? = null
|
|
||||||
private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null
|
|
||||||
private var recyclerViewTouchActionGuardManager: RecyclerViewTouchActionGuardManager? = null
|
|
||||||
|
|
||||||
private val recyclerViewOnScrollListener = object : OnScrollListener() {
|
private val recyclerViewOnScrollListener = object : OnScrollListener() {
|
||||||
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
layoutManager?.let { layoutManager ->
|
layoutManager?.let { layoutManager ->
|
||||||
val visibleItemCount = layoutManager.childCount
|
val visibleItemCount = layoutManager.childCount
|
||||||
val totalItemCount = layoutManager.itemCount
|
val totalItemCount = layoutManager.itemCount
|
||||||
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
|
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
|
||||||
|
|
||||||
if (!isLoading && !isLastPage) {
|
if (!isLoading && !isLastPage) {
|
||||||
if (visibleItemCount + firstVisibleItemPosition >= totalItemCount - 5
|
if (visibleItemCount + firstVisibleItemPosition >= totalItemCount - PAGE_SIZE
|
||||||
&& firstVisibleItemPosition >= 0
|
&& firstVisibleItemPosition >= 0
|
||||||
) {
|
) {
|
||||||
loadMoreItems()
|
loadMoreItems()
|
||||||
@ -89,21 +84,29 @@ class MessageListFragment : Fragment(), ListHolder<Label> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var emptyTrashMenuItem: MenuItem? = null
|
private var emptyTrashMenuItem: MenuItem? = null
|
||||||
|
private var deleteAllMenuItem: MenuItem? = null
|
||||||
private lateinit var messageRepo: AndroidMessageRepository
|
private lateinit var messageRepo: AndroidMessageRepository
|
||||||
private var activateOnItemClick: Boolean = false
|
private var activateOnItemClick: Boolean = false
|
||||||
|
|
||||||
|
override fun setActivateOnItemClick(activateOnItemClick: Boolean) {
|
||||||
|
swipeableMessageAdapter?.activateOnItemClick = activateOnItemClick
|
||||||
|
this.activateOnItemClick = activateOnItemClick
|
||||||
|
}
|
||||||
|
|
||||||
private val backStack = Stack<Label>()
|
private val backStack = Stack<Label>()
|
||||||
|
|
||||||
fun loadMoreItems() {
|
fun loadMoreItems() {
|
||||||
isLoading = true
|
isLoading = true
|
||||||
swipeableMessageAdapter?.let { messageAdapter ->
|
swipeableMessageAdapter?.let { messageAdapter ->
|
||||||
doAsync {
|
doAsync {
|
||||||
|
val label = currentLabel.value
|
||||||
val messages = messageRepo.findMessages(
|
val messages = messageRepo.findMessages(
|
||||||
currentLabel.value,
|
label,
|
||||||
messageAdapter.itemCount,
|
messageAdapter.itemCount,
|
||||||
PAGE_SIZE
|
PAGE_SIZE,
|
||||||
|
context?.preferences?.separateIdentities == true && label?.type != Label.Type.BROADCAST
|
||||||
)
|
)
|
||||||
onUiThread {
|
uiThread {
|
||||||
messageAdapter.addAll(messages)
|
messageAdapter.addAll(messages)
|
||||||
isLoading = false
|
isLoading = false
|
||||||
isLastPage = messages.size < PAGE_SIZE
|
isLastPage = messages.size < PAGE_SIZE
|
||||||
@ -124,33 +127,40 @@ class MessageListFragment : Fragment(), ListHolder<Label> {
|
|||||||
initFab(activity)
|
initFab(activity)
|
||||||
messageRepo = Singleton.getMessageRepository(activity)
|
messageRepo = Singleton.getMessageRepository(activity)
|
||||||
|
|
||||||
currentLabel.addObserver(this) { new -> doUpdateList(new) }
|
subscription = currentLabel.subscribe { new -> doUpdateList(new) }
|
||||||
doUpdateList(currentLabel.value)
|
doUpdateList(currentLabel.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
currentLabel.removeObserver(this)
|
subscription?.dispose()
|
||||||
super.onPause()
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doUpdateList(label: Label?) {
|
override fun reloadList() = doUpdateList(currentLabel.value)
|
||||||
val mainActivity = activity as? MainActivity
|
|
||||||
swipeableMessageAdapter?.clear(label)
|
|
||||||
if (label == null) {
|
|
||||||
mainActivity?.updateTitle(getString(R.string.app_name))
|
|
||||||
swipeableMessageAdapter?.notifyDataSetChanged()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
emptyTrashMenuItem?.isVisible = label.type == Label.Type.TRASH
|
|
||||||
mainActivity?.apply {
|
|
||||||
if ("archive" == label.toString()) {
|
|
||||||
updateTitle(getString(R.string.archive))
|
|
||||||
} else {
|
|
||||||
updateTitle(label.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadMoreItems()
|
private fun doUpdateList(label: Label?) {
|
||||||
|
// If the menu item isn't available yet, we should wait - the method will be called again once it's
|
||||||
|
// initialized.
|
||||||
|
emptyTrashMenuItem?.let { menuItem ->
|
||||||
|
val mainActivity = activity as? MainActivity
|
||||||
|
swipeableMessageAdapter?.clear(label)
|
||||||
|
if (label == null) {
|
||||||
|
mainActivity?.updateTitle(getString(R.string.app_name))
|
||||||
|
swipeableMessageAdapter?.notifyDataSetChanged()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
menuItem.isVisible = label.type == Label.Type.TRASH
|
||||||
|
MainActivity.apply {
|
||||||
|
if ("archive" == label.toString()) {
|
||||||
|
updateTitle(getString(R.string.archive))
|
||||||
|
} else {
|
||||||
|
updateTitle(label.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadMoreItems()
|
||||||
|
}
|
||||||
|
deleteAllMenuItem?.isVisible = label?.type != Label.Type.TRASH
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
@ -165,81 +175,37 @@ class MessageListFragment : Fragment(), ListHolder<Label> {
|
|||||||
|
|
||||||
val context = context ?: throw IllegalStateException("No context available")
|
val context = context ?: throw IllegalStateException("No context available")
|
||||||
|
|
||||||
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
|
||||||
|
|
||||||
// touch guard manager (this class is required to suppress scrolling while swipe-dismiss
|
|
||||||
// animation is running)
|
|
||||||
val touchActionGuardManager = RecyclerViewTouchActionGuardManager().apply {
|
|
||||||
setInterceptVerticalScrollingWhileAnimationRunning(true)
|
|
||||||
isEnabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// swipe manager
|
|
||||||
val swipeManager = RecyclerViewSwipeManager()
|
|
||||||
|
|
||||||
//swipeableMessageAdapter
|
|
||||||
val adapter = SwipeableMessageAdapter().apply {
|
|
||||||
setActivateOnItemClick(activateOnItemClick)
|
|
||||||
}
|
|
||||||
adapter.eventListener = object : SwipeableMessageAdapter.EventListener {
|
|
||||||
override fun onItemDeleted(item: Plaintext) {
|
|
||||||
if (MessageDetailFragment.isInTrash(item)) {
|
|
||||||
Singleton.labeler.delete(item)
|
|
||||||
messageRepo.remove(item)
|
|
||||||
} else {
|
|
||||||
Singleton.labeler.delete(item)
|
|
||||||
messageRepo.save(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemArchived(item: Plaintext) {
|
|
||||||
Singleton.labeler.archive(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemViewClicked(v: View?) {
|
|
||||||
val position = recycler_view.getChildAdapterPosition(v)
|
|
||||||
adapter.setSelectedPosition(position)
|
|
||||||
if (position != RecyclerView.NO_POSITION) {
|
|
||||||
val item = adapter.getItem(position)
|
|
||||||
MainActivity.apply { onItemSelected(item) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// wrap for swiping
|
|
||||||
wrappedAdapter = swipeManager.createWrappedAdapter(adapter)
|
|
||||||
|
|
||||||
val animator = SwipeDismissItemAnimator()
|
|
||||||
|
|
||||||
// Change animations are enabled by default since support-v7-recyclerview v22.
|
|
||||||
// Disable the change animation in order to make turning back animation of swiped item
|
|
||||||
// works properly.
|
|
||||||
animator.supportsChangeAnimations = false
|
|
||||||
|
|
||||||
recycler_view.layoutManager = layoutManager
|
recycler_view.layoutManager = layoutManager
|
||||||
recycler_view.adapter = wrappedAdapter // requires *wrapped* swipeableMessageAdapter
|
swipeableMessageAdapter = SwipeableMessageAdapter(context).apply {
|
||||||
recycler_view.itemAnimator = animator
|
activateOnItemClick = this@MessageListFragment.activateOnItemClick
|
||||||
|
}
|
||||||
|
recycler_view.adapter = swipeableMessageAdapter // requires *wrapped* swipeableMessageAdapter
|
||||||
recycler_view.addOnScrollListener(recyclerViewOnScrollListener)
|
recycler_view.addOnScrollListener(recyclerViewOnScrollListener)
|
||||||
|
|
||||||
recycler_view.addItemDecoration(
|
val dirs = when (currentLabel.value?.type) {
|
||||||
SimpleListDividerDecorator(
|
Label.Type.TRASH -> ItemTouchHelper.LEFT
|
||||||
ContextCompat.getDrawable(context, R.drawable.list_divider_h), true
|
else -> ItemTouchHelper.LEFT + ItemTouchHelper.RIGHT
|
||||||
)
|
}
|
||||||
)
|
|
||||||
|
|
||||||
// NOTE:
|
val swipeHandler = SwipeToDeleteCallback(context, dirs, object : EventListener {
|
||||||
// The initialization order is very important! This order determines the priority of
|
override fun onItemDeleted(position: Int) {
|
||||||
// touch event handling.
|
context.toast("Deleted")
|
||||||
//
|
}
|
||||||
// priority: TouchActionGuard > Swipe > DragAndDrop
|
|
||||||
touchActionGuardManager.attachRecyclerView(recycler_view)
|
|
||||||
swipeManager.attachRecyclerView(recycler_view)
|
|
||||||
|
|
||||||
recyclerViewTouchActionGuardManager = touchActionGuardManager
|
override fun onItemArchived(position: Int) {
|
||||||
recyclerViewSwipeManager = swipeManager
|
context.toast("Archived")
|
||||||
swipeableMessageAdapter = adapter
|
}
|
||||||
|
|
||||||
Singleton.updateMessageListAdapterInListener(adapter)
|
override fun onItemSelected(position: Int) {
|
||||||
|
context.toast("Selected")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
val itemTouchHelper = ItemTouchHelper(swipeHandler)
|
||||||
|
itemTouchHelper.attachToRecyclerView(recycler_view)
|
||||||
|
|
||||||
|
// FIXME Singleton.updateMessageListAdapterInListener(adapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initFab(context: MainActivity) {
|
private fun initFab(context: MainActivity) {
|
||||||
@ -275,18 +241,6 @@ class MessageListFragment : Fragment(), ListHolder<Label> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
recyclerViewSwipeManager?.release()
|
|
||||||
recyclerViewSwipeManager = null
|
|
||||||
|
|
||||||
recyclerViewTouchActionGuardManager?.release()
|
|
||||||
recyclerViewTouchActionGuardManager = null
|
|
||||||
|
|
||||||
recycler_view.itemAnimator = null
|
|
||||||
recycler_view.adapter = null
|
|
||||||
|
|
||||||
wrappedAdapter?.let { WrapperAdapterUtils.releaseAll(it) }
|
|
||||||
wrappedAdapter = null
|
|
||||||
|
|
||||||
swipeableMessageAdapter = null
|
swipeableMessageAdapter = null
|
||||||
layoutManager = null
|
layoutManager = null
|
||||||
|
|
||||||
@ -296,6 +250,8 @@ class MessageListFragment : Fragment(), ListHolder<Label> {
|
|||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
inflater.inflate(R.menu.message_list, menu)
|
inflater.inflate(R.menu.message_list, menu)
|
||||||
emptyTrashMenuItem = menu.findItem(R.id.empty_trash)
|
emptyTrashMenuItem = menu.findItem(R.id.empty_trash)
|
||||||
|
deleteAllMenuItem = menu.findItem(R.id.delete_all)
|
||||||
|
// currentLabel.value?.let { doUpdateList(it) }
|
||||||
super.onCreateOptionsMenu(menu, inflater)
|
super.onCreateOptionsMenu(menu, inflater)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,12 +261,22 @@ class MessageListFragment : Fragment(), ListHolder<Label> {
|
|||||||
currentLabel.value?.let { label ->
|
currentLabel.value?.let { label ->
|
||||||
if (label.type != Label.Type.TRASH) return true
|
if (label.type != Label.Type.TRASH) return true
|
||||||
|
|
||||||
doAsync {
|
deleteAllMessages(label)
|
||||||
for (message in messageRepo.findMessages(label)) {
|
}
|
||||||
messageRepo.remove(message)
|
return true
|
||||||
}
|
}
|
||||||
|
R.id.delete_all -> {
|
||||||
uiThread { doUpdateList(label) }
|
currentLabel.value?.let { label ->
|
||||||
|
context?.apply {
|
||||||
|
alert(
|
||||||
|
R.string.delete_all_messages_in_list,
|
||||||
|
R.string.delete_all_messages_in_list_ask
|
||||||
|
) {
|
||||||
|
positiveButton(R.string.delete) {
|
||||||
|
deleteAllMessages(label)
|
||||||
|
}
|
||||||
|
cancelButton { }
|
||||||
|
}.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -319,19 +285,24 @@ class MessageListFragment : Fragment(), ListHolder<Label> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateList(label: Label) {
|
private fun deleteAllMessages(label: Label) {
|
||||||
currentLabel.value = label
|
doAsync {
|
||||||
|
for (message in messageRepo.findMessages(label, 0, 0, context?.preferences?.separateIdentities == true)) {
|
||||||
|
messageRepo.remove(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
uiThread { doUpdateList(label) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setActivateOnItemClick(activateOnItemClick: Boolean) {
|
override fun updateList(label: Label) {
|
||||||
swipeableMessageAdapter?.setActivateOnItemClick(activateOnItemClick)
|
currentLabel.onNext(label)
|
||||||
this.activateOnItemClick = activateOnItemClick
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showPreviousList() = if (backStack.isEmpty()) {
|
override fun showPreviousList() = if (backStack.isEmpty()) {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
currentLabel.value = backStack.pop()
|
currentLabel.onNext(backStack.pop())
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,42 +17,36 @@
|
|||||||
package ch.dissem.apps.abit
|
package ch.dissem.apps.abit
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.*
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.ServiceConnection
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.support.v4.app.Fragment
|
|
||||||
import android.support.v4.content.ContextCompat
|
|
||||||
import android.support.v4.content.FileProvider.getUriForFile
|
|
||||||
import android.support.v7.preference.Preference
|
|
||||||
import android.support.v7.preference.Preference.OnPreferenceChangeListener
|
|
||||||
import android.support.v7.preference.PreferenceFragmentCompat
|
|
||||||
import android.support.v7.preference.PreferenceScreen
|
|
||||||
import android.support.v7.preference.SwitchPreferenceCompat
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.core.content.FileProvider.getUriForFile
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import ch.dissem.apps.abit.service.BatchProcessorService
|
import ch.dissem.apps.abit.service.BatchProcessorService
|
||||||
import ch.dissem.apps.abit.service.SimpleJob
|
import ch.dissem.apps.abit.service.SimpleJob
|
||||||
import ch.dissem.apps.abit.service.Singleton
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
import ch.dissem.apps.abit.synchronization.SyncAdapter
|
|
||||||
import ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW
|
|
||||||
import ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE
|
|
||||||
import ch.dissem.apps.abit.util.Exports
|
import ch.dissem.apps.abit.util.Exports
|
||||||
import ch.dissem.apps.abit.util.NetworkUtils
|
import ch.dissem.apps.abit.util.network
|
||||||
import ch.dissem.apps.abit.util.Preferences
|
import ch.dissem.apps.abit.util.preferences
|
||||||
import ch.dissem.bitmessage.entity.Plaintext
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
import com.mikepenz.aboutlibraries.Libs
|
import com.mikepenz.aboutlibraries.Libs
|
||||||
import com.mikepenz.aboutlibraries.LibsBuilder
|
import com.mikepenz.aboutlibraries.LibsBuilder
|
||||||
import org.jetbrains.anko.doAsync
|
import org.jetbrains.anko.doAsync
|
||||||
import org.jetbrains.anko.support.v4.indeterminateProgressDialog
|
import org.jetbrains.anko.indeterminateProgressDialog
|
||||||
import org.jetbrains.anko.support.v4.startActivity
|
import org.jetbrains.anko.startActivity
|
||||||
import org.jetbrains.anko.uiThread
|
import org.jetbrains.anko.uiThread
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Christian Basler
|
* @author Christian Basler
|
||||||
*/
|
*/
|
||||||
class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener,
|
class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
|
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
setPreferencesFromResource(R.xml.preferences, rootKey)
|
setPreferencesFromResource(R.xml.preferences, rootKey)
|
||||||
@ -103,7 +97,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
|
|||||||
val bmc = Singleton.getBitmessageContext(ctx)
|
val bmc = Singleton.getBitmessageContext(ctx)
|
||||||
bmc.internals.nodeRegistry.clear()
|
bmc.internals.nodeRegistry.clear()
|
||||||
bmc.cleanup()
|
bmc.cleanup()
|
||||||
Preferences.cleanupExportDirectory(ctx)
|
ctx.preferences.cleanupExportDirectory()
|
||||||
|
|
||||||
uiThread {
|
uiThread {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
@ -118,11 +112,11 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun exportClickListener() = Preference.OnPreferenceClickListener {
|
private fun exportClickListener() = Preference.OnPreferenceClickListener {
|
||||||
val ctx = context ?: throw IllegalStateException("No context available")
|
val ctx = activity ?: throw IllegalStateException("No context available")
|
||||||
|
|
||||||
indeterminateProgressDialog(R.string.export_data_summary, R.string.export_data).apply {
|
ctx.indeterminateProgressDialog(R.string.export_data_summary, R.string.export_data).apply {
|
||||||
doAsync {
|
doAsync {
|
||||||
val exportDirectory = Preferences.getExportDirectory(ctx)
|
val exportDirectory = ctx.preferences.exportDirectory
|
||||||
exportDirectory.mkdirs()
|
exportDirectory.mkdirs()
|
||||||
val file = Exports.exportData(exportDirectory, ctx)
|
val file = Exports.exportData(exportDirectory, ctx)
|
||||||
val contentUri = getUriForFile(ctx, "ch.dissem.apps.abit.fileprovider", file)
|
val contentUri = getUriForFile(ctx, "ch.dissem.apps.abit.fileprovider", file)
|
||||||
@ -153,20 +147,20 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
|
|||||||
if (activity.hasDetailPane) {
|
if (activity.hasDetailPane) {
|
||||||
activity.setDetailView(StatusFragment())
|
activity.setDetailView(StatusFragment())
|
||||||
} else {
|
} else {
|
||||||
startActivity<StatusActivity>()
|
activity.startActivity<StatusActivity>()
|
||||||
}
|
}
|
||||||
return@OnPreferenceClickListener true
|
return@OnPreferenceClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
val ctx = context ?: throw IllegalStateException("No context available")
|
val ctx = activity ?: throw IllegalStateException("No context available")
|
||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
WRITE_EXPORT_REQUEST_CODE -> Preferences.cleanupExportDirectory(ctx)
|
WRITE_EXPORT_REQUEST_CODE -> ctx.preferences.cleanupExportDirectory()
|
||||||
READ_IMPORT_REQUEST_CODE -> {
|
READ_IMPORT_REQUEST_CODE -> {
|
||||||
if (resultCode == Activity.RESULT_OK && data?.data != null) {
|
if (resultCode == Activity.RESULT_OK && data?.data != null) {
|
||||||
indeterminateProgressDialog(R.string.import_data_summary, R.string.import_data).apply {
|
ctx.indeterminateProgressDialog(R.string.import_data_summary, R.string.import_data).apply {
|
||||||
doAsync {
|
doAsync {
|
||||||
Exports.importData(data.data, ctx)
|
Exports.importData(data.data!!, ctx)
|
||||||
uiThread {
|
uiThread {
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
@ -187,24 +181,6 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
|
||||||
when (key) {
|
|
||||||
PREFERENCE_SERVER_POW -> toggleSyncServerPOW(sharedPreferences)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun toggleSyncServerPOW(sharedPreferences: SharedPreferences) {
|
|
||||||
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(ctx)
|
|
||||||
} else {
|
|
||||||
SyncAdapter.stopPowSync(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val connection = object : ServiceConnection {
|
private val connection = object : ServiceConnection {
|
||||||
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||||
if (service is BatchProcessorService.BatchBinder) {
|
if (service is BatchProcessorService.BatchBinder) {
|
||||||
@ -243,44 +219,41 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun emulateConversationChangeListener(conversationInit: Preference?) =
|
private fun emulateConversationChangeListener(conversationInit: Preference?) =
|
||||||
OnPreferenceChangeListener { _, newValue ->
|
Preference.OnPreferenceChangeListener { _, newValue ->
|
||||||
conversationInit?.isEnabled = newValue as Boolean
|
conversationInit?.isEnabled = newValue as Boolean
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun connectivityChangeListener() =
|
private fun connectivityChangeListener() =
|
||||||
OnPreferenceChangeListener { _, _ ->
|
Preference.OnPreferenceChangeListener { _, _ ->
|
||||||
context?.let { ctx ->
|
activity?.network?.scheduleNodeStart()
|
||||||
if (Preferences.isFullNodeActive(ctx)) {
|
|
||||||
NetworkUtils.scheduleNodeStart(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
// The why-is-it-so-damn-hard-to-group-preferences section
|
// The why-is-it-so-damn-hard-to-group-preferences section
|
||||||
override fun getCallbackFragment(): Fragment = this
|
// FIXME: maybe this is once again necessary, maybe not. Test!
|
||||||
|
// override fun getCallbackFragment(): Fragment = this
|
||||||
override fun onPreferenceStartScreen(
|
//
|
||||||
preferenceFragmentCompat: PreferenceFragmentCompat,
|
// override fun onPreferenceStartScreen(
|
||||||
preferenceScreen: PreferenceScreen
|
// preferenceFragmentCompat: PreferenceFragment,
|
||||||
): Boolean {
|
// preferenceScreen: PreferenceScreen
|
||||||
fragmentManager?.beginTransaction()?.let { ft ->
|
// ): Boolean {
|
||||||
val fragment = SettingsFragment()
|
// fragmentManager?.beginTransaction()?.let { ft ->
|
||||||
fragment.arguments = Bundle().apply {
|
// val fragment = SettingsFragment()
|
||||||
putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, preferenceScreen.key)
|
// fragment.arguments = Bundle().apply {
|
||||||
}
|
// putString(PreferenceFragment.ARG_PREFERENCE_ROOT, preferenceScreen.key)
|
||||||
ft.add(R.id.item_list, fragment, preferenceScreen.key)
|
// }
|
||||||
ft.addToBackStack(preferenceScreen.key)
|
// ft.add(R.id.item_list, fragment, preferenceScreen.key)
|
||||||
ft.commit()
|
// ft.addToBackStack(preferenceScreen.key)
|
||||||
}
|
// ft.commit()
|
||||||
return true
|
// }
|
||||||
}
|
// return true
|
||||||
|
// }
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
//
|
||||||
super.onViewCreated(view, savedInstanceState)
|
// override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
context?.let { ctx -> view.setBackgroundColor(ContextCompat.getColor(ctx, R.color.contentBackground)) }
|
// super.onViewCreated(view, savedInstanceState)
|
||||||
}
|
// context?.let { ctx -> view.setBackgroundColor(ContextCompat.getColor(ctx, R.color.contentBackground)) }
|
||||||
|
// }
|
||||||
// End of the why-is-it-so-damn-hard-to-group-preferences section
|
// End of the why-is-it-so-damn-hard-to-group-preferences section
|
||||||
// Afterthought: here it looks so simple: https://developer.android.com/guide/topics/ui/settings.html
|
// Afterthought: here it looks so simple: https://developer.android.com/guide/topics/ui/settings.html
|
||||||
// Remind me, why do we need to use PreferenceFragmentCompat?
|
// Remind me, why do we need to use PreferenceFragmentCompat?
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
package ch.dissem.apps.abit
|
package ch.dissem.apps.abit
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v7.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import ch.dissem.apps.abit.service.Singleton
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
import com.mikepenz.materialize.MaterializeBuilder
|
import com.mikepenz.materialize.MaterializeBuilder
|
||||||
import kotlinx.android.synthetic.main.activity_status.*
|
import kotlinx.android.synthetic.main.activity_status.*
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
package ch.dissem.apps.abit
|
package ch.dissem.apps.abit
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
package ch.dissem.apps.abit.adapter
|
package ch.dissem.apps.abit.adapter
|
||||||
|
|
||||||
import android.support.v7.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@ -31,7 +31,7 @@ import java.util.*
|
|||||||
*/
|
*/
|
||||||
class AddressSelectorAdapter(identities: List<BitmessageAddress>) : RecyclerView.Adapter<AddressSelectorAdapter.ViewHolder>() {
|
class AddressSelectorAdapter(identities: List<BitmessageAddress>) : RecyclerView.Adapter<AddressSelectorAdapter.ViewHolder>() {
|
||||||
|
|
||||||
private val data = identities.map { Selectable(it) }.toMutableList()
|
private val data = identities.asSequence().map { Selectable(it) }.toMutableList()
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val inflater = LayoutInflater.from(parent.context)
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
@ -63,7 +63,7 @@ class AddressSelectorAdapter(identities: List<BitmessageAddress>) : RecyclerView
|
|||||||
|
|
||||||
val selected: List<BitmessageAddress>
|
val selected: List<BitmessageAddress>
|
||||||
get() {
|
get() {
|
||||||
return data
|
return data.asSequence()
|
||||||
.filter { it.selected }
|
.filter { it.selected }
|
||||||
.mapTo(LinkedList()) { it.data }
|
.mapTo(LinkedList()) { it.data }
|
||||||
}
|
}
|
||||||
|
@ -142,5 +142,4 @@ class ContactAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun indexOf(element: BitmessageAddress) = originalData.indexOf(element)
|
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
package ch.dissem.apps.abit.adapter
|
package ch.dissem.apps.abit.adapter
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.support.v4.app.Fragment
|
|
||||||
import android.support.v7.widget.GridLayoutManager
|
|
||||||
import android.support.v7.widget.PopupMenu
|
|
||||||
import android.support.v7.widget.RecyclerView
|
|
||||||
import android.text.util.Linkify
|
import android.text.util.Linkify
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.widget.PopupMenu
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import ch.dissem.apps.abit.*
|
import ch.dissem.apps.abit.*
|
||||||
import ch.dissem.apps.abit.service.Singleton
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
import ch.dissem.apps.abit.util.Constants
|
import ch.dissem.apps.abit.util.Constants
|
||||||
@ -19,18 +19,19 @@ import ch.dissem.bitmessage.entity.Conversation
|
|||||||
import ch.dissem.bitmessage.entity.Plaintext
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label
|
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||||
import ch.dissem.bitmessage.ports.MessageRepository
|
import ch.dissem.bitmessage.ports.MessageRepository
|
||||||
|
import io.reactivex.subjects.BehaviorSubject
|
||||||
|
|
||||||
|
|
||||||
class ConversationAdapter internal constructor(
|
class ConversationAdapter internal constructor(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
private val parent: Fragment,
|
private val parent: Fragment,
|
||||||
conversation: Conversation,
|
conversation: Conversation,
|
||||||
private val label: Label?
|
label: BehaviorSubject<Label>
|
||||||
) : RecyclerView.Adapter<ConversationAdapter.ViewHolder>() {
|
) : RecyclerView.Adapter<ConversationAdapter.ViewHolder>() {
|
||||||
|
|
||||||
private val messageRepo = Singleton.getMessageRepository(ctx)
|
private val messageRepo = Singleton.getMessageRepository(ctx)
|
||||||
|
|
||||||
private var filteredMessages = conversation.messages.filter { label == null || it.labels.any { it == label } }
|
private var filteredMessages = label.value?.let { l -> conversation.messages.filter { m -> m.labels.any { it == l } } } ?: emptyList()
|
||||||
|
|
||||||
override fun onCreateViewHolder(
|
override fun onCreateViewHolder(
|
||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
@ -98,51 +99,54 @@ class ConversationAdapter internal constructor(
|
|||||||
val sender = itemView.findViewById<TextView>(R.id.sender)!!
|
val sender = itemView.findViewById<TextView>(R.id.sender)!!
|
||||||
val recipient = itemView.findViewById<TextView>(R.id.recipient)!!
|
val recipient = itemView.findViewById<TextView>(R.id.recipient)!!
|
||||||
val status = itemView.findViewById<ImageView>(R.id.status)!!
|
val status = itemView.findViewById<ImageView>(R.id.status)!!
|
||||||
val menu = itemView.findViewById<ImageView>(R.id.menu)!!.also { view ->
|
val menu = itemView.findViewById<ImageView>(R.id.menu)!!.apply {
|
||||||
view.setOnClickListener {
|
setOnClickListener { view ->
|
||||||
val popup = PopupMenu(itemView.context, view)
|
PopupMenu(itemView.context, view).apply {
|
||||||
popup.menuInflater.inflate(R.menu.message, popup.menu)
|
|
||||||
popup.setOnMenuItemClickListener {
|
menuInflater.inflate(R.menu.message, menu)
|
||||||
item?.let { item ->
|
setOnMenuItemClickListener { menuItem ->
|
||||||
when (it.itemId) {
|
item?.let { item ->
|
||||||
R.id.reply -> {
|
when (menuItem.itemId) {
|
||||||
ComposeMessageActivity.launchReplyTo(parent, item)
|
R.id.reply -> {
|
||||||
true
|
ComposeMessageActivity.launchReplyTo(parent, item)
|
||||||
}
|
true
|
||||||
R.id.delete -> {
|
}
|
||||||
if (MessageDetailFragment.isInTrash(item)) {
|
R.id.delete -> {
|
||||||
Singleton.labeler.delete(item)
|
if (MessageDetailFragment.isInTrash(item)) {
|
||||||
messageRepo.remove(item)
|
Singleton.labeler.delete(item)
|
||||||
} else {
|
messageRepo.remove(item)
|
||||||
Singleton.labeler.delete(item)
|
} else {
|
||||||
|
Singleton.labeler.delete(item)
|
||||||
|
messageRepo.save(item)
|
||||||
|
}
|
||||||
|
filteredMessages.indexOf(item).let { i ->
|
||||||
|
filteredMessages -= item
|
||||||
|
notifyItemRemoved(i)
|
||||||
|
}
|
||||||
|
MainActivity.apply {
|
||||||
|
updateUnread()
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.mark_unread -> {
|
||||||
|
Singleton.labeler.markAsUnread(item)
|
||||||
messageRepo.save(item)
|
messageRepo.save(item)
|
||||||
|
MainActivity.apply { updateUnread() }
|
||||||
|
true
|
||||||
}
|
}
|
||||||
filteredMessages.indexOf(item).let { i ->
|
R.id.archive -> {
|
||||||
filteredMessages -= item
|
Singleton.labeler.archive(item)
|
||||||
notifyItemRemoved(i)
|
messageRepo.save(item)
|
||||||
|
MainActivity.apply { updateUnread() }
|
||||||
|
true
|
||||||
}
|
}
|
||||||
MainActivity.apply {
|
else -> false
|
||||||
updateUnread()
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
R.id.mark_unread -> {
|
} ?: false
|
||||||
Singleton.labeler.markAsUnread(item)
|
}
|
||||||
messageRepo.save(item)
|
show()
|
||||||
MainActivity.apply { updateUnread() }
|
|
||||||
true
|
|
||||||
}
|
|
||||||
R.id.archive -> {
|
|
||||||
Singleton.labeler.archive(item)
|
|
||||||
messageRepo.save(item)
|
|
||||||
MainActivity.apply { updateUnread() }
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
} ?: false
|
|
||||||
}
|
}
|
||||||
popup.show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val text = itemView.findViewById<TextView>(R.id.text)!!.apply {
|
val text = itemView.findViewById<TextView>(R.id.text)!!.apply {
|
||||||
|
@ -2,8 +2,8 @@ package ch.dissem.apps.abit.adapter
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.support.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import android.support.v7.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
@ -0,0 +1,312 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Haruki Hasegawa
|
||||||
|
* 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.adapter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.*
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import ch.dissem.apps.abit.Identicon
|
||||||
|
import ch.dissem.apps.abit.MultiIdenticon
|
||||||
|
import ch.dissem.apps.abit.R
|
||||||
|
import ch.dissem.apps.abit.util.Strings.prepareMessageExtract
|
||||||
|
import ch.dissem.apps.abit.util.getDrawable
|
||||||
|
import ch.dissem.apps.abit.util.getString
|
||||||
|
import ch.dissem.bitmessage.entity.Conversation
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapted from the basic swipeable example by Haruki Hasegawa. See
|
||||||
|
*
|
||||||
|
* @author Christian Basler
|
||||||
|
* @see [https://github.com/h6ah4i/android-advancedrecyclerview](https://github.com/h6ah4i/android-advancedrecyclerview)
|
||||||
|
*/
|
||||||
|
abstract class SwipeableAdapter<T, H>(val ctx: Context) :
|
||||||
|
RecyclerView.Adapter<H>() where H : SwipeableAdapter.AbstractViewHolder {
|
||||||
|
|
||||||
|
protected val data = LinkedList<T>()
|
||||||
|
var eventListener: EventListener? = null
|
||||||
|
|
||||||
|
protected var label: Label? = null
|
||||||
|
var selectedPosition = -1
|
||||||
|
set(value) {
|
||||||
|
val oldPosition = field
|
||||||
|
field = value
|
||||||
|
notifyItemChanged(oldPosition)
|
||||||
|
notifyItemChanged(value)
|
||||||
|
}
|
||||||
|
var activateOnItemClick: Boolean = false
|
||||||
|
|
||||||
|
protected val labelUnknown: String = ctx.getString(R.string.unknown)
|
||||||
|
|
||||||
|
open class AbstractViewHolder(v: View, adapter: SwipeableAdapter<*, *>) : RecyclerView.ViewHolder(v) {
|
||||||
|
|
||||||
|
val container = v.findViewById<FrameLayout>(R.id.container)!!
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemView.setOnClickListener { adapter.eventListener?.onItemSelected(adapterPosition) }
|
||||||
|
container.setOnClickListener { adapter.eventListener?.onItemSelected(adapterPosition) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
// SwipeableItemAdapter requires stable ID, and also
|
||||||
|
// have to implement the getItemId() method appropriately.
|
||||||
|
setHasStableIds(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: H, position: Int) {
|
||||||
|
val item = data[position]
|
||||||
|
|
||||||
|
holder.apply {
|
||||||
|
if (activateOnItemClick) {
|
||||||
|
container.setBackgroundResource(
|
||||||
|
if (position == selectedPosition)
|
||||||
|
R.drawable.bg_item_selected_state
|
||||||
|
else
|
||||||
|
R.drawable.bg_item_normal_state
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setData(holder, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun setData(holder: H, item: T)
|
||||||
|
|
||||||
|
fun add(item: T) {
|
||||||
|
val index = data.size
|
||||||
|
data.add(item)
|
||||||
|
notifyItemInserted(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addFirst(item: T) {
|
||||||
|
data.addFirst(item)
|
||||||
|
notifyItemInserted(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addAll(items: Collection<T>) {
|
||||||
|
val index = data.size
|
||||||
|
data.addAll(items)
|
||||||
|
notifyItemRangeInserted(index, items.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun remove(item: T) {
|
||||||
|
val itemId = getItemId(item)
|
||||||
|
val index = data.indexOfFirst { getItemId(it) == itemId }
|
||||||
|
if (index >= 0) {
|
||||||
|
removeAt(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeAt(index: Int) {
|
||||||
|
data.removeAt(index)
|
||||||
|
notifyItemRemoved(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemId(position: Int): Long {
|
||||||
|
return getItemId(data[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun getItemId(item: T): Long
|
||||||
|
|
||||||
|
abstract fun update(item: T)
|
||||||
|
|
||||||
|
fun clear(newLabel: Label?) {
|
||||||
|
label = newLabel
|
||||||
|
data.clear()
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getItem(position: Int) = data[position]
|
||||||
|
|
||||||
|
override fun getItemCount() = data.size
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class SwipeableConversationAdapter(ctx: Context) : SwipeableAdapter<Conversation, SwipeableConversationAdapter.ViewHolder>(ctx) {
|
||||||
|
override fun getItemId(item: Conversation) = item.id.leastSignificantBits
|
||||||
|
|
||||||
|
override fun update(item: Conversation) {
|
||||||
|
val index = data.indexOfFirst { it.id == item.id }
|
||||||
|
if (index >= 0) {
|
||||||
|
data[index] = item
|
||||||
|
notifyItemChanged(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
|
val v = inflater.inflate(R.layout.conversation_row, parent, false)
|
||||||
|
return ViewHolder(v, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setData(holder: ViewHolder, item: Conversation) {
|
||||||
|
holder.apply {
|
||||||
|
avatar.setImageDrawable(MultiIdenticon(item.participants))
|
||||||
|
|
||||||
|
sender.text = item.participants.sortedBy {
|
||||||
|
(it.alias?.let { 0 } ?: 1) + if (it.isChan) 2 else 0
|
||||||
|
}.map { it.alias ?: labelUnknown }.distinct().joinToString()
|
||||||
|
subject.text = prepareMessageExtract(item.subject)
|
||||||
|
extract.text = prepareMessageExtract(item.extract)
|
||||||
|
item.messages.count { it.labels.contains(label) }.let { size ->
|
||||||
|
if (size <= 1) {
|
||||||
|
count.text = ""
|
||||||
|
} else {
|
||||||
|
count.text = size.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.hasUnread()) {
|
||||||
|
sender.typeface = Typeface.DEFAULT_BOLD
|
||||||
|
subject.typeface = Typeface.DEFAULT_BOLD
|
||||||
|
} else {
|
||||||
|
sender.typeface = Typeface.DEFAULT
|
||||||
|
subject.typeface = Typeface.DEFAULT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewHolder(v: View, adapter: SwipeableConversationAdapter) : AbstractViewHolder(v, adapter) {
|
||||||
|
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)!!
|
||||||
|
val count = v.findViewById<TextView>(R.id.count)!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SwipeableMessageAdapter(ctx: Context) : SwipeableAdapter<Plaintext, SwipeableMessageAdapter.ViewHolder>(ctx) {
|
||||||
|
override fun getItemId(item: Plaintext) = item.id as Long
|
||||||
|
|
||||||
|
override fun update(item: Plaintext) {
|
||||||
|
val index = data.indexOfFirst { it.id == item.id }
|
||||||
|
if (index >= 0) {
|
||||||
|
data[index] = item
|
||||||
|
notifyItemChanged(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
|
val v = inflater.inflate(R.layout.message_row, parent, false)
|
||||||
|
return ViewHolder(v, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setData(holder: ViewHolder, item: Plaintext) {
|
||||||
|
holder.apply {
|
||||||
|
avatar.setImageDrawable(Identicon(item.from))
|
||||||
|
status.setImageResource(item.status.getDrawable())
|
||||||
|
status.contentDescription = holder.status.context.getString(item.status.getString())
|
||||||
|
|
||||||
|
sender.text = item.from.toString()
|
||||||
|
subject.text = prepareMessageExtract(item.subject)
|
||||||
|
extract.text = prepareMessageExtract(item.text)
|
||||||
|
if (item.isUnread()) {
|
||||||
|
sender.typeface = Typeface.DEFAULT_BOLD
|
||||||
|
subject.typeface = Typeface.DEFAULT_BOLD
|
||||||
|
} else {
|
||||||
|
sender.typeface = Typeface.DEFAULT
|
||||||
|
subject.typeface = Typeface.DEFAULT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewHolder(v: View, adapter: SwipeableMessageAdapter) : AbstractViewHolder(v, adapter) {
|
||||||
|
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)!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SwipeToDeleteCallback(ctx: Context, swipeDirs: Int, private val eventListener: EventListener) : ItemTouchHelper.SimpleCallback(0, swipeDirs) {
|
||||||
|
|
||||||
|
private val backgroundLeft = ContextCompat.getDrawable(ctx, R.drawable.bg_swipe_item_left)!!
|
||||||
|
private val backgroundRight = ContextCompat.getDrawable(ctx, R.drawable.bg_swipe_item_right)!!
|
||||||
|
private val clearPaint = Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) }
|
||||||
|
|
||||||
|
|
||||||
|
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
|
||||||
|
/**
|
||||||
|
* To disable "swipe" for specific item return 0 here.
|
||||||
|
* For example:
|
||||||
|
* if (viewHolder?.itemViewType == YourAdapter.SOME_TYPE) return 0
|
||||||
|
* if (viewHolder?.adapterPosition == 0) return 0
|
||||||
|
*/
|
||||||
|
if (viewHolder.adapterPosition == 10) return 0
|
||||||
|
return super.getMovementFlags(recyclerView, viewHolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder) = false
|
||||||
|
|
||||||
|
override fun onChildDraw(
|
||||||
|
c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder,
|
||||||
|
dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean
|
||||||
|
) {
|
||||||
|
|
||||||
|
val itemView = viewHolder.itemView
|
||||||
|
val isCanceled = dX == 0f && !isCurrentlyActive
|
||||||
|
|
||||||
|
if (isCanceled) {
|
||||||
|
clearCanvas(c, itemView.right + dX, itemView.top.toFloat(), itemView.right.toFloat(), itemView.bottom.toFloat())
|
||||||
|
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dX < 0) {
|
||||||
|
backgroundLeft.setBounds(itemView.right + dX.toInt(), itemView.top, itemView.right, itemView.bottom)
|
||||||
|
backgroundLeft.draw(c)
|
||||||
|
} else {
|
||||||
|
backgroundRight.setBounds(itemView.left, itemView.top, itemView.left + dX.toInt(), itemView.bottom)
|
||||||
|
backgroundRight.draw(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearCanvas(c: Canvas?, left: Float, top: Float, right: Float, bottom: Float) {
|
||||||
|
c?.drawRect(left, top, right, bottom, clearPaint)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
|
when (direction) {
|
||||||
|
ItemTouchHelper.LEFT -> eventListener.onItemDeleted(viewHolder.adapterPosition)
|
||||||
|
ItemTouchHelper.RIGHT -> eventListener.onItemArchived(viewHolder.adapterPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EventListener {
|
||||||
|
fun onItemDeleted(position: Int)
|
||||||
|
|
||||||
|
fun onItemArchived(position: Int)
|
||||||
|
|
||||||
|
fun onItemSelected(position: Int)
|
||||||
|
}
|
@ -1,275 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Haruki Hasegawa
|
|
||||||
* 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.adapter
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Typeface
|
|
||||||
import android.support.v7.widget.RecyclerView
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.FrameLayout
|
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.TextView
|
|
||||||
import ch.dissem.apps.abit.MultiIdenticon
|
|
||||||
import ch.dissem.apps.abit.R
|
|
||||||
import ch.dissem.apps.abit.repository.AndroidLabelRepository.Companion.LABEL_ARCHIVE
|
|
||||||
import ch.dissem.apps.abit.util.Strings.prepareMessageExtract
|
|
||||||
import ch.dissem.bitmessage.entity.Conversation
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label
|
|
||||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter
|
|
||||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstants
|
|
||||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstants.*
|
|
||||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionMoveToSwipedDirection
|
|
||||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionRemoveItem
|
|
||||||
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractSwipeableItemViewHolder
|
|
||||||
import com.h6ah4i.android.widget.advrecyclerview.utils.RecyclerViewAdapterUtils
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adapted from the basic swipeable example by Haruki Hasegawa. See
|
|
||||||
*
|
|
||||||
* @author Christian Basler
|
|
||||||
* @see [https://github.com/h6ah4i/android-advancedrecyclerview](https://github.com/h6ah4i/android-advancedrecyclerview)
|
|
||||||
*/
|
|
||||||
class SwipeableConversationAdapter(ctx: Context) :
|
|
||||||
RecyclerView.Adapter<SwipeableConversationAdapter.ViewHolder>(),
|
|
||||||
SwipeableItemAdapter<SwipeableConversationAdapter.ViewHolder>, SwipeableItemConstants {
|
|
||||||
|
|
||||||
private val data = LinkedList<Conversation>()
|
|
||||||
var eventListener: EventListener? = null
|
|
||||||
private val itemViewOnClickListener: View.OnClickListener
|
|
||||||
private val swipeableViewContainerOnClickListener: View.OnClickListener
|
|
||||||
|
|
||||||
private var label: Label? = null
|
|
||||||
private var selectedPosition = -1
|
|
||||||
private var activateOnItemClick: Boolean = false
|
|
||||||
|
|
||||||
private val labelUnknown = ctx.getString(R.string.unknown)
|
|
||||||
|
|
||||||
fun setActivateOnItemClick(activateOnItemClick: Boolean) {
|
|
||||||
this.activateOnItemClick = activateOnItemClick
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EventListener {
|
|
||||||
fun onItemDeleted(item: Conversation)
|
|
||||||
|
|
||||||
fun onItemArchived(item: Conversation)
|
|
||||||
|
|
||||||
fun onItemViewClicked(v: View?)
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewHolder(v: View) : AbstractSwipeableItemViewHolder(v) {
|
|
||||||
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)!!
|
|
||||||
val count = v.findViewById<TextView>(R.id.count)!!
|
|
||||||
|
|
||||||
override fun getSwipeableContainerView() = container
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
itemViewOnClickListener = View.OnClickListener { view -> onItemViewClick(view) }
|
|
||||||
swipeableViewContainerOnClickListener =
|
|
||||||
View.OnClickListener { view -> onSwipeableViewContainerClick(view) }
|
|
||||||
|
|
||||||
// SwipeableItemAdapter requires stable ID, and also
|
|
||||||
// have to implement the getItemId() method appropriately.
|
|
||||||
setHasStableIds(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun add(item: Conversation) {
|
|
||||||
data.add(item)
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addFirst(item: Conversation) {
|
|
||||||
val index = data.size
|
|
||||||
data.addFirst(item)
|
|
||||||
notifyItemInserted(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addAll(items: Collection<Conversation>) {
|
|
||||||
val index = data.size
|
|
||||||
data.addAll(items)
|
|
||||||
notifyItemRangeInserted(index, items.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun remove(item: Conversation) {
|
|
||||||
val index = data.indexOf(item)
|
|
||||||
data.removeAll { it.id == item.id }
|
|
||||||
notifyItemRemoved(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun update(item: Conversation) {
|
|
||||||
val index = data.indexOfFirst { it.id == item.id }
|
|
||||||
if (index >= 0) {
|
|
||||||
data[index] = item
|
|
||||||
notifyItemChanged(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clear(newLabel: Label?) {
|
|
||||||
label = newLabel
|
|
||||||
data.clear()
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onItemViewClick(v: View) {
|
|
||||||
eventListener?.onItemViewClicked(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onSwipeableViewContainerClick(v: View) {
|
|
||||||
eventListener?.onItemViewClicked(
|
|
||||||
RecyclerViewAdapterUtils.getParentViewHolderItemView(v)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getItem(position: Int) = data[position]
|
|
||||||
|
|
||||||
override fun getItemId(position: Int) = data[position].id.leastSignificantBits
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
|
||||||
val inflater = LayoutInflater.from(parent.context)
|
|
||||||
val v = inflater.inflate(R.layout.conversation_row, parent, false)
|
|
||||||
return ViewHolder(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
|
||||||
val item = data[position]
|
|
||||||
|
|
||||||
holder.apply {
|
|
||||||
if (activateOnItemClick) {
|
|
||||||
container.setBackgroundResource(
|
|
||||||
if (position == selectedPosition)
|
|
||||||
R.drawable.bg_item_selected_state
|
|
||||||
else
|
|
||||||
R.drawable.bg_item_normal_state
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// set listeners
|
|
||||||
// (if the item is *pinned*, click event comes to the itemView)
|
|
||||||
itemView.setOnClickListener(itemViewOnClickListener)
|
|
||||||
// (if the item is *not pinned*, click event comes to the container)
|
|
||||||
container.setOnClickListener(swipeableViewContainerOnClickListener)
|
|
||||||
|
|
||||||
// set data
|
|
||||||
avatar.setImageDrawable(MultiIdenticon(item.participants))
|
|
||||||
|
|
||||||
sender.text = item.participants.sortedBy {
|
|
||||||
(it.alias?.let { 0 } ?: 1) + if (it.isChan) 2 else 0
|
|
||||||
}.map { it.alias ?: labelUnknown }.distinct().joinToString()
|
|
||||||
subject.text = prepareMessageExtract(item.subject)
|
|
||||||
extract.text = prepareMessageExtract(item.extract)
|
|
||||||
item.messages.count { it.labels.contains(label) }.let { size ->
|
|
||||||
if (size <= 1) {
|
|
||||||
count.text = ""
|
|
||||||
} else {
|
|
||||||
count.text = size.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (item.hasUnread()) {
|
|
||||||
sender.typeface = Typeface.DEFAULT_BOLD
|
|
||||||
subject.typeface = Typeface.DEFAULT_BOLD
|
|
||||||
} else {
|
|
||||||
sender.typeface = Typeface.DEFAULT
|
|
||||||
subject.typeface = Typeface.DEFAULT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount() = data.size
|
|
||||||
|
|
||||||
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) =
|
|
||||||
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) {
|
|
||||||
R.drawable.bg_swipe_item_neutral
|
|
||||||
} else {
|
|
||||||
R.drawable.bg_swipe_item_right
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSwipeItemStarted(holder: ViewHolder?, position: Int) = Unit
|
|
||||||
|
|
||||||
fun setSelectedPosition(selectedPosition: Int) {
|
|
||||||
val oldPosition = this.selectedPosition
|
|
||||||
this.selectedPosition = selectedPosition
|
|
||||||
notifyItemChanged(oldPosition)
|
|
||||||
notifyItemChanged(selectedPosition)
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SwipeLeftResultAction internal constructor(
|
|
||||||
adapter: SwipeableConversationAdapter,
|
|
||||||
position: Int
|
|
||||||
) : SwipeResultActionMoveToSwipedDirection() {
|
|
||||||
private var adapter: SwipeableConversationAdapter? = adapter
|
|
||||||
private val item = adapter.data[position]
|
|
||||||
|
|
||||||
override fun onPerformAction() {
|
|
||||||
adapter?.eventListener?.onItemDeleted(item)
|
|
||||||
adapter?.remove(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCleanUp() {
|
|
||||||
adapter = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SwipeRightResultAction internal constructor(
|
|
||||||
adapter: SwipeableConversationAdapter,
|
|
||||||
position: Int
|
|
||||||
) : SwipeResultActionRemoveItem() {
|
|
||||||
private var adapter: SwipeableConversationAdapter? = adapter
|
|
||||||
private val item = adapter.data[position]
|
|
||||||
|
|
||||||
override fun onPerformAction() {
|
|
||||||
adapter?.eventListener?.onItemArchived(item)
|
|
||||||
adapter?.remove(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCleanUp() {
|
|
||||||
adapter = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,262 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Haruki Hasegawa
|
|
||||||
* 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.adapter
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.graphics.Typeface
|
|
||||||
import android.support.v7.widget.RecyclerView
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.FrameLayout
|
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.TextView
|
|
||||||
import ch.dissem.apps.abit.Identicon
|
|
||||||
import ch.dissem.apps.abit.R
|
|
||||||
import ch.dissem.apps.abit.repository.AndroidLabelRepository.Companion.LABEL_ARCHIVE
|
|
||||||
import ch.dissem.apps.abit.util.Strings.prepareMessageExtract
|
|
||||||
import ch.dissem.apps.abit.util.getDrawable
|
|
||||||
import ch.dissem.apps.abit.util.getString
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label
|
|
||||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter
|
|
||||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstants
|
|
||||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstants.*
|
|
||||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionMoveToSwipedDirection
|
|
||||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionRemoveItem
|
|
||||||
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractSwipeableItemViewHolder
|
|
||||||
import com.h6ah4i.android.widget.advrecyclerview.utils.RecyclerViewAdapterUtils
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adapted from the basic swipeable example by Haruki Hasegawa. See
|
|
||||||
*
|
|
||||||
* @author Christian Basler
|
|
||||||
* @see [https://github.com/h6ah4i/android-advancedrecyclerview](https://github.com/h6ah4i/android-advancedrecyclerview)
|
|
||||||
*/
|
|
||||||
class SwipeableMessageAdapter : RecyclerView.Adapter<SwipeableMessageAdapter.ViewHolder>(),
|
|
||||||
SwipeableItemAdapter<SwipeableMessageAdapter.ViewHolder>, SwipeableItemConstants {
|
|
||||||
|
|
||||||
private val data = LinkedList<Plaintext>()
|
|
||||||
var eventListener: EventListener? = null
|
|
||||||
private val itemViewOnClickListener: View.OnClickListener
|
|
||||||
private val swipeableViewContainerOnClickListener: View.OnClickListener
|
|
||||||
|
|
||||||
private var label: Label? = null
|
|
||||||
private var selectedPosition = -1
|
|
||||||
private var activateOnItemClick: Boolean = false
|
|
||||||
|
|
||||||
fun setActivateOnItemClick(activateOnItemClick: Boolean) {
|
|
||||||
this.activateOnItemClick = activateOnItemClick
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EventListener {
|
|
||||||
fun onItemDeleted(item: Plaintext)
|
|
||||||
|
|
||||||
fun onItemArchived(item: Plaintext)
|
|
||||||
|
|
||||||
fun onItemViewClicked(v: View?)
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewHolder(v: View) : AbstractSwipeableItemViewHolder(v) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
itemViewOnClickListener = View.OnClickListener { view -> onItemViewClick(view) }
|
|
||||||
swipeableViewContainerOnClickListener =
|
|
||||||
View.OnClickListener { view -> onSwipeableViewContainerClick(view) }
|
|
||||||
|
|
||||||
// SwipeableItemAdapter requires stable ID, and also
|
|
||||||
// have to implement the getItemId() method appropriately.
|
|
||||||
setHasStableIds(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun add(item: Plaintext) {
|
|
||||||
data.add(item)
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addFirst(item: Plaintext) {
|
|
||||||
val index = data.size
|
|
||||||
data.addFirst(item)
|
|
||||||
notifyItemInserted(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addAll(items: Collection<Plaintext>) {
|
|
||||||
val index = data.size
|
|
||||||
data.addAll(items)
|
|
||||||
notifyItemRangeInserted(index, items.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun remove(item: Plaintext) {
|
|
||||||
val index = data.indexOf(item)
|
|
||||||
data.removeAll { it.id == item.id }
|
|
||||||
notifyItemRemoved(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun update(item: Plaintext) {
|
|
||||||
val index = data.indexOfFirst { it.id == item.id }
|
|
||||||
if (index >= 0) {
|
|
||||||
data[index] = item
|
|
||||||
notifyItemChanged(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clear(newLabel: Label?) {
|
|
||||||
label = newLabel
|
|
||||||
data.clear()
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onItemViewClick(v: View) {
|
|
||||||
eventListener?.onItemViewClicked(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onSwipeableViewContainerClick(v: View) {
|
|
||||||
eventListener?.onItemViewClicked(
|
|
||||||
RecyclerViewAdapterUtils.getParentViewHolderItemView(v)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getItem(position: Int) = data[position]
|
|
||||||
|
|
||||||
override fun getItemId(position: Int) = data[position].id as Long
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
|
||||||
val inflater = LayoutInflater.from(parent.context)
|
|
||||||
val v = inflater.inflate(R.layout.message_row, parent, false)
|
|
||||||
return ViewHolder(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
|
||||||
val item = data[position]
|
|
||||||
|
|
||||||
holder.apply {
|
|
||||||
if (activateOnItemClick) {
|
|
||||||
container.setBackgroundResource(
|
|
||||||
if (position == selectedPosition)
|
|
||||||
R.drawable.bg_item_selected_state
|
|
||||||
else
|
|
||||||
R.drawable.bg_item_normal_state
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// set listeners
|
|
||||||
// (if the item is *pinned*, click event comes to the itemView)
|
|
||||||
itemView.setOnClickListener(itemViewOnClickListener)
|
|
||||||
// (if the item is *not pinned*, click event comes to the container)
|
|
||||||
container.setOnClickListener(swipeableViewContainerOnClickListener)
|
|
||||||
|
|
||||||
// set data
|
|
||||||
avatar.setImageDrawable(Identicon(item.from))
|
|
||||||
status.setImageResource(item.status.getDrawable())
|
|
||||||
status.contentDescription = holder.status.context.getString(item.status.getString())
|
|
||||||
sender.text = item.from.toString()
|
|
||||||
subject.text = prepareMessageExtract(item.subject)
|
|
||||||
extract.text = prepareMessageExtract(item.text)
|
|
||||||
if (item.isUnread()) {
|
|
||||||
sender.typeface = Typeface.DEFAULT_BOLD
|
|
||||||
subject.typeface = Typeface.DEFAULT_BOLD
|
|
||||||
} else {
|
|
||||||
sender.typeface = Typeface.DEFAULT
|
|
||||||
subject.typeface = Typeface.DEFAULT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount() = data.size
|
|
||||||
|
|
||||||
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) =
|
|
||||||
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) {
|
|
||||||
R.drawable.bg_swipe_item_neutral
|
|
||||||
} else {
|
|
||||||
R.drawable.bg_swipe_item_right
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSwipeItemStarted(holder: ViewHolder?, position: Int) = Unit
|
|
||||||
|
|
||||||
fun setSelectedPosition(selectedPosition: Int) {
|
|
||||||
val oldPosition = this.selectedPosition
|
|
||||||
this.selectedPosition = selectedPosition
|
|
||||||
notifyItemChanged(oldPosition)
|
|
||||||
notifyItemChanged(selectedPosition)
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SwipeLeftResultAction internal constructor(
|
|
||||||
adapter: SwipeableMessageAdapter,
|
|
||||||
position: Int
|
|
||||||
) : SwipeResultActionMoveToSwipedDirection() {
|
|
||||||
private var adapter: SwipeableMessageAdapter? = adapter
|
|
||||||
private val item = adapter.data[position]
|
|
||||||
|
|
||||||
override fun onPerformAction() {
|
|
||||||
adapter?.eventListener?.onItemDeleted(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCleanUp() {
|
|
||||||
adapter = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SwipeRightResultAction internal constructor(
|
|
||||||
adapter: SwipeableMessageAdapter,
|
|
||||||
position: Int
|
|
||||||
) : SwipeResultActionRemoveItem() {
|
|
||||||
private var adapter: SwipeableMessageAdapter? = adapter
|
|
||||||
private val item = adapter.data[position]
|
|
||||||
|
|
||||||
override fun onPerformAction() {
|
|
||||||
adapter?.eventListener?.onItemArchived(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCleanUp() {
|
|
||||||
adapter = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.adapter
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.preference.PreferenceManager
|
|
||||||
import ch.dissem.bitmessage.InternalContext
|
|
||||||
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Switches between two [ProofOfWorkEngine]s depending on the configuration.
|
|
||||||
*
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
class SwitchingProofOfWorkEngine(
|
|
||||||
private val ctx: Context,
|
|
||||||
private val preference: String,
|
|
||||||
private val option: ProofOfWorkEngine,
|
|
||||||
private val fallback: ProofOfWorkEngine
|
|
||||||
) : ProofOfWorkEngine, InternalContext.ContextHolder {
|
|
||||||
|
|
||||||
override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) {
|
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
|
||||||
if (preferences.getBoolean(preference, false)) {
|
|
||||||
option.calculateNonce(initialHash, target, callback)
|
|
||||||
} else {
|
|
||||||
fallback.calculateNonce(initialHash, target, callback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setContext(context: InternalContext) = listOf(option, fallback)
|
|
||||||
.filterIsInstance<InternalContext.ContextHolder>()
|
|
||||||
.forEach { it.setContext(context) }
|
|
||||||
}
|
|
@ -19,12 +19,12 @@ package ch.dissem.apps.abit.dialog
|
|||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v7.app.AppCompatDialogFragment
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatDialogFragment
|
||||||
import ch.dissem.apps.abit.ImportIdentityActivity
|
import ch.dissem.apps.abit.ImportIdentityActivity
|
||||||
import ch.dissem.apps.abit.MainActivity
|
import ch.dissem.apps.abit.MainActivity
|
||||||
import ch.dissem.apps.abit.R
|
import ch.dissem.apps.abit.R
|
||||||
@ -33,7 +33,7 @@ import ch.dissem.bitmessage.BitmessageContext
|
|||||||
import ch.dissem.bitmessage.entity.payload.Pubkey
|
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||||
import kotlinx.android.synthetic.main.dialog_add_identity.*
|
import kotlinx.android.synthetic.main.dialog_add_identity.*
|
||||||
import org.jetbrains.anko.doAsync
|
import org.jetbrains.anko.doAsync
|
||||||
import org.jetbrains.anko.support.v4.startActivity
|
import org.jetbrains.anko.startActivity
|
||||||
import org.jetbrains.anko.uiThread
|
import org.jetbrains.anko.uiThread
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,7 +77,7 @@ class AddIdentityDialogFragment : AppCompatDialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
R.id.import_identity -> startActivity<ImportIdentityActivity>()
|
R.id.import_identity -> ctx.startActivity<ImportIdentityActivity>()
|
||||||
R.id.add_chan -> addChanDialog()
|
R.id.add_chan -> addChanDialog()
|
||||||
R.id.add_deterministic_address -> DeterministicIdentityDialogFragment().show(fragmentManager, "dialog")
|
R.id.add_deterministic_address -> DeterministicIdentityDialogFragment().show(fragmentManager, "dialog")
|
||||||
else -> return@OnClickListener
|
else -> return@OnClickListener
|
||||||
|
@ -18,7 +18,7 @@ package ch.dissem.apps.abit.dialog
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v7.app.AppCompatDialogFragment
|
import androidx.appcompat.app.AppCompatDialogFragment
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
@ -19,8 +19,8 @@ package ch.dissem.apps.abit.dialog
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import ch.dissem.apps.abit.R
|
import ch.dissem.apps.abit.R
|
||||||
import ch.dissem.apps.abit.util.NetworkUtils
|
import ch.dissem.apps.abit.util.network
|
||||||
import ch.dissem.apps.abit.util.Preferences
|
import ch.dissem.apps.abit.util.preferences
|
||||||
import kotlinx.android.synthetic.main.dialog_full_node.*
|
import kotlinx.android.synthetic.main.dialog_full_node.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,12 +31,12 @@ class FullNodeDialogActivity : Activity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.dialog_full_node)
|
setContentView(R.layout.dialog_full_node)
|
||||||
ok.setOnClickListener {
|
ok.setOnClickListener {
|
||||||
Preferences.setWifiOnly(this@FullNodeDialogActivity, false)
|
preferences.wifiOnly = false
|
||||||
NetworkUtils.enableNode(applicationContext)
|
network.scheduleNodeStart()
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
dismiss.setOnClickListener {
|
dismiss.setOnClickListener {
|
||||||
NetworkUtils.scheduleNodeStart(applicationContext)
|
network.scheduleNodeStart()
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ package ch.dissem.apps.abit.dialog
|
|||||||
import android.app.Activity.RESULT_OK
|
import android.app.Activity.RESULT_OK
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v7.app.AppCompatDialogFragment
|
import androidx.appcompat.app.AppCompatDialogFragment
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@ -49,7 +49,7 @@ class SelectEncodingDialogFragment : AppCompatDialogFragment() {
|
|||||||
when (encoding) {
|
when (encoding) {
|
||||||
SIMPLE -> radioGroup.check(R.id.simple)
|
SIMPLE -> radioGroup.check(R.id.simple)
|
||||||
EXTENDED -> radioGroup.check(R.id.extended)
|
EXTENDED -> radioGroup.check(R.id.extended)
|
||||||
else -> LOG.warn("Unexpected encoding: " + encoding)
|
else -> LOG.warn("Unexpected encoding: $encoding")
|
||||||
}
|
}
|
||||||
ok.setOnClickListener(View.OnClickListener {
|
ok.setOnClickListener(View.OnClickListener {
|
||||||
encoding = when (radioGroup.checkedRadioButtonId) {
|
encoding = when (radioGroup.checkedRadioButtonId) {
|
||||||
|
@ -2,27 +2,21 @@ package ch.dissem.apps.abit.drawer
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.support.v4.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import android.widget.Toast.LENGTH_LONG
|
||||||
|
import ch.dissem.apps.abit.*
|
||||||
|
import ch.dissem.apps.abit.dialog.AddIdentityDialogFragment
|
||||||
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
import com.mikepenz.materialdrawer.AccountHeader
|
import com.mikepenz.materialdrawer.AccountHeader
|
||||||
import com.mikepenz.materialdrawer.model.ProfileDrawerItem
|
import com.mikepenz.materialdrawer.model.ProfileDrawerItem
|
||||||
import com.mikepenz.materialdrawer.model.interfaces.IProfile
|
import com.mikepenz.materialdrawer.model.interfaces.IProfile
|
||||||
|
|
||||||
import ch.dissem.apps.abit.AddressDetailActivity
|
|
||||||
import ch.dissem.apps.abit.AddressDetailFragment
|
|
||||||
import ch.dissem.apps.abit.MainActivity
|
|
||||||
import ch.dissem.apps.abit.R
|
|
||||||
import ch.dissem.apps.abit.dialog.AddIdentityDialogFragment
|
|
||||||
import ch.dissem.apps.abit.service.Singleton
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
|
||||||
|
|
||||||
import android.widget.Toast.LENGTH_LONG
|
|
||||||
|
|
||||||
class ProfileSelectionListener(
|
class ProfileSelectionListener(
|
||||||
private val ctx: Context,
|
private val ctx: Context,
|
||||||
private val fragmentManager: FragmentManager
|
private val fragmentManager: FragmentManager
|
||||||
) : AccountHeader.OnAccountHeaderListener {
|
) : AccountHeader.OnAccountHeaderListener {
|
||||||
|
|
||||||
override fun onProfileChanged(view: View, profile: IProfile<*>, current: Boolean): Boolean {
|
override fun onProfileChanged(view: View, profile: IProfile<*>, current: Boolean): Boolean {
|
||||||
@ -42,6 +36,13 @@ class ProfileSelectionListener(
|
|||||||
val tag = profile.tag
|
val tag = profile.tag
|
||||||
if (tag is BitmessageAddress) {
|
if (tag is BitmessageAddress) {
|
||||||
Singleton.setIdentity(tag)
|
Singleton.setIdentity(tag)
|
||||||
|
MainActivity.apply {
|
||||||
|
updateUnread()
|
||||||
|
val itemList = supportFragmentManager.findFragmentById(R.id.item_list)
|
||||||
|
if (itemList is ListHolder<*>) {
|
||||||
|
itemList.reloadList()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ package ch.dissem.apps.abit.listener
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import ch.dissem.apps.abit.MainActivity
|
import ch.dissem.apps.abit.MainActivity
|
||||||
import ch.dissem.apps.abit.notification.NewMessageNotification
|
import ch.dissem.apps.abit.notification.NewMessageNotification
|
||||||
import ch.dissem.apps.abit.util.Preferences
|
import ch.dissem.apps.abit.util.preferences
|
||||||
import ch.dissem.bitmessage.BitmessageContext
|
import ch.dissem.bitmessage.BitmessageContext
|
||||||
import ch.dissem.bitmessage.entity.Plaintext
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
import ch.dissem.bitmessage.ports.MessageRepository
|
import ch.dissem.bitmessage.ports.MessageRepository
|
||||||
@ -50,7 +50,7 @@ class MessageListener(ctx: Context) : BitmessageContext.Listener.WithContext {
|
|||||||
private lateinit var conversationService: ConversationService
|
private lateinit var conversationService: ConversationService
|
||||||
|
|
||||||
init {
|
init {
|
||||||
emulateConversations = Preferences.isEmulateConversations(ctx)
|
emulateConversations = ctx.preferences.emulateConversations
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun receive(plaintext: Plaintext) {
|
override fun receive(plaintext: Plaintext) {
|
||||||
@ -81,7 +81,7 @@ class MessageListener(ctx: Context) : BitmessageContext.Listener.WithContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateConversation(plaintext: Plaintext) {
|
private fun updateConversation(plaintext: Plaintext) {
|
||||||
if (emulateConversations && plaintext.encoding != Plaintext.Encoding.EXTENDED) {
|
if (emulateConversations && plaintext.encoding != Plaintext.Encoding.EXTENDED) {
|
||||||
conversationService.getSubject(listOf(plaintext))?.let { subject ->
|
conversationService.getSubject(listOf(plaintext))?.let { subject ->
|
||||||
plaintext.conversationId = UUID.nameUUIDFromBytes(subject.toByteArray())
|
plaintext.conversationId = UUID.nameUUIDFromBytes(subject.toByteArray())
|
||||||
|
@ -21,8 +21,8 @@ import android.app.NotificationChannel
|
|||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.support.annotation.ColorRes
|
import androidx.annotation.ColorRes
|
||||||
import android.support.v4.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import ch.dissem.apps.abit.R
|
import ch.dissem.apps.abit.R
|
||||||
import org.jetbrains.anko.notificationManager
|
import org.jetbrains.anko.notificationManager
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
package ch.dissem.apps.abit.notification
|
package ch.dissem.apps.abit.notification
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.support.v4.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import ch.dissem.apps.abit.R
|
import ch.dissem.apps.abit.R
|
||||||
import ch.dissem.apps.abit.service.Job
|
import ch.dissem.apps.abit.service.Job
|
||||||
|
|
||||||
|
@ -17,8 +17,8 @@
|
|||||||
package ch.dissem.apps.abit.notification
|
package ch.dissem.apps.abit.notification
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.support.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import android.support.v4.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
|
|
||||||
import ch.dissem.apps.abit.R
|
import ch.dissem.apps.abit.R
|
||||||
|
|
||||||
|
@ -21,11 +21,11 @@ import android.app.PendingIntent
|
|||||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.support.v4.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import ch.dissem.apps.abit.MainActivity
|
import ch.dissem.apps.abit.MainActivity
|
||||||
import ch.dissem.apps.abit.R
|
import ch.dissem.apps.abit.R
|
||||||
import ch.dissem.apps.abit.service.BitmessageIntentService
|
import ch.dissem.apps.abit.service.BitmessageIntentService
|
||||||
import ch.dissem.apps.abit.service.BitmessageService
|
import ch.dissem.apps.abit.service.NodeStartupService
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.concurrent.fixedRateTimer
|
import kotlin.concurrent.fixedRateTimer
|
||||||
|
|
||||||
@ -51,9 +51,9 @@ class NetworkNotification(ctx: Context) : AbstractNotification(ctx) {
|
|||||||
|
|
||||||
@SuppressLint("StringFormatMatches")
|
@SuppressLint("StringFormatMatches")
|
||||||
private fun update(): Boolean {
|
private fun update(): Boolean {
|
||||||
val running = BitmessageService.isRunning
|
val running = NodeStartupService.isRunning
|
||||||
builder.setOngoing(running)
|
builder.setOngoing(running)
|
||||||
val connections = BitmessageService.status.getProperty("network", "connections")
|
val connections = NodeStartupService.status.getProperty("network", "connections")
|
||||||
if (!running) {
|
if (!running) {
|
||||||
builder.setSmallIcon(R.drawable.ic_notification_full_node_disconnected)
|
builder.setSmallIcon(R.drawable.ic_notification_full_node_disconnected)
|
||||||
builder.setContentText(ctx.getString(R.string.connection_info_disconnected))
|
builder.setContentText(ctx.getString(R.string.connection_info_disconnected))
|
||||||
@ -112,7 +112,6 @@ class NetworkNotification(ctx: Context) : AbstractNotification(ctx) {
|
|||||||
timer = fixedRateTimer(initialDelay = 10000, period = 10000) {
|
timer = fixedRateTimer(initialDelay = 10000, period = 10000) {
|
||||||
if (!update()) {
|
if (!update()) {
|
||||||
cancel()
|
cancel()
|
||||||
ctx.stopService(Intent(ctx, BitmessageService::class.java))
|
|
||||||
}
|
}
|
||||||
super@NetworkNotification.show()
|
super@NetworkNotification.show()
|
||||||
}
|
}
|
||||||
|
@ -21,9 +21,9 @@ import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.support.v4.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import android.support.v4.app.NotificationCompat.BigTextStyle
|
import androidx.core.app.NotificationCompat.BigTextStyle
|
||||||
import android.support.v4.app.NotificationCompat.InboxStyle
|
import androidx.core.app.NotificationCompat.InboxStyle
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
|
@ -19,7 +19,7 @@ package ch.dissem.apps.abit.notification
|
|||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.support.v4.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
|
|
||||||
import ch.dissem.apps.abit.MainActivity
|
import ch.dissem.apps.abit.MainActivity
|
||||||
import ch.dissem.apps.abit.R
|
import ch.dissem.apps.abit.R
|
||||||
|
@ -1,83 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.pow
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
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.extensions.CryptoCustomMessage
|
|
||||||
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
class ServerPowEngine(private val ctx: Context) : ProofOfWorkEngine, InternalContext.ContextHolder {
|
|
||||||
private lateinit var context: InternalContext
|
|
||||||
|
|
||||||
private val pool: ExecutorService
|
|
||||||
|
|
||||||
init {
|
|
||||||
pool = Executors.newCachedThreadPool { r ->
|
|
||||||
val thread = Executors.defaultThreadFactory().newThread(r)
|
|
||||||
thread.priority = Thread.MIN_PRIORITY
|
|
||||||
thread
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) =
|
|
||||||
pool.execute {
|
|
||||||
val identity = Singleton.getIdentity(ctx) ?: throw RuntimeException("No Identity for calculating POW")
|
|
||||||
|
|
||||||
val request = ProofOfWorkRequest(identity, initialHash,
|
|
||||||
CALCULATE, target)
|
|
||||||
SyncAdapter.startPowSync(ctx)
|
|
||||||
try {
|
|
||||||
val cryptoMsg = CryptoCustomMessage(request)
|
|
||||||
cryptoMsg.signAndEncrypt(
|
|
||||||
identity,
|
|
||||||
cryptography().createPublicKey(identity.publicDecryptionKey)
|
|
||||||
)
|
|
||||||
val node = Preferences.getTrustedNode(ctx)
|
|
||||||
if (node == null) {
|
|
||||||
LOG.error("trusted node is not defined")
|
|
||||||
} else {
|
|
||||||
context.networkHandler.send(
|
|
||||||
node,
|
|
||||||
Preferences.getTrustedNodePort(ctx),
|
|
||||||
cryptoMsg)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
LOG.error(e.message, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setContext(context: InternalContext) {
|
|
||||||
this.context = context
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val LOG = LoggerFactory.getLogger(ServerPowEngine::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
@ -21,6 +21,7 @@ import android.database.Cursor
|
|||||||
import android.database.DatabaseUtils
|
import android.database.DatabaseUtils
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import ch.dissem.apps.abit.repository.AndroidLabelRepository.Companion.LABEL_ARCHIVE
|
import ch.dissem.apps.abit.repository.AndroidLabelRepository.Companion.LABEL_ARCHIVE
|
||||||
|
import ch.dissem.apps.abit.util.Preferences
|
||||||
import ch.dissem.apps.abit.util.UuidUtils
|
import ch.dissem.apps.abit.util.UuidUtils
|
||||||
import ch.dissem.apps.abit.util.UuidUtils.asUuid
|
import ch.dissem.apps.abit.util.UuidUtils.asUuid
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
@ -38,7 +39,14 @@ import java.util.*
|
|||||||
/**
|
/**
|
||||||
* [MessageRepository] implementation using the Android SQL API.
|
* [MessageRepository] implementation using the Android SQL API.
|
||||||
*/
|
*/
|
||||||
class AndroidMessageRepository(private val sql: SqlHelper) : AbstractMessageRepository() {
|
class AndroidMessageRepository(private val sql: SqlHelper, private val prefs: Preferences) : AbstractMessageRepository() {
|
||||||
|
|
||||||
|
fun findMessages(label: Label?, offset: Int, limit: Int, separateIdentities: Boolean) =
|
||||||
|
if (label === LABEL_ARCHIVE || label === null) {
|
||||||
|
find("id NOT IN (SELECT message_id FROM Message_Label)", offset, limit, separateIdentities)
|
||||||
|
} else {
|
||||||
|
find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.id + ")", offset, limit, separateIdentities)
|
||||||
|
}
|
||||||
|
|
||||||
override fun findMessages(label: Label?, offset: Int, limit: Int) =
|
override fun findMessages(label: Label?, offset: Int, limit: Int) =
|
||||||
if (label === LABEL_ARCHIVE) {
|
if (label === LABEL_ARCHIVE) {
|
||||||
@ -54,30 +62,56 @@ class AndroidMessageRepository(private val sql: SqlHelper) : AbstractMessageRepo
|
|||||||
null
|
null
|
||||||
).toInt()
|
).toInt()
|
||||||
|
|
||||||
override fun countUnread(label: Label?) = when {
|
private fun getSelectIdentity(separateIdentities: Boolean): Pair<String, Array<String>> {
|
||||||
label === LABEL_ARCHIVE -> 0
|
if (separateIdentities) {
|
||||||
label == null -> DatabaseUtils.queryNumEntries(
|
val identity = prefs.currentIdentity
|
||||||
sql.readableDatabase,
|
return if (prefs.separateIdentities && identity != null) {
|
||||||
TABLE_NAME,
|
"AND (type = 'BROADCAST' OR recipient=? OR sender=?)" to arrayOf(identity.address, identity.address)
|
||||||
"id IN (SELECT message_id FROM Message_Label WHERE label_id IN (SELECT id FROM Label WHERE type=?))",
|
} else {
|
||||||
arrayOf(Label.Type.UNREAD.name)
|
"" to emptyArray()
|
||||||
).toInt()
|
}
|
||||||
else -> DatabaseUtils.queryNumEntries(
|
} else {
|
||||||
sql.readableDatabase,
|
return "" to emptyArray()
|
||||||
TABLE_NAME,
|
}
|
||||||
" id IN (SELECT message_id FROM Message_Label WHERE label_id=?) " +
|
|
||||||
"AND id IN (SELECT message_id FROM Message_Label WHERE label_id IN (SELECT id FROM Label WHERE type=?))",
|
|
||||||
arrayOf(label.id.toString(), Label.Type.UNREAD.name)
|
|
||||||
).toInt()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findConversations(label: Label?, offset: Int, limit: Int): List<UUID> {
|
override fun countUnread(label: Label?) = countUnread(label, false)
|
||||||
|
|
||||||
|
fun countUnread(label: Label?, separateIdentities: Boolean) = getSelectIdentity(separateIdentities).let { (selectIdentityQuery, selectIdentityArgs) ->
|
||||||
|
when {
|
||||||
|
label === LABEL_ARCHIVE -> 0
|
||||||
|
label == null -> DatabaseUtils.queryNumEntries(
|
||||||
|
sql.readableDatabase,
|
||||||
|
TABLE_NAME,
|
||||||
|
"id IN (SELECT message_id FROM Message_Label WHERE label_id IN (SELECT id FROM Label WHERE type=?)) " +
|
||||||
|
selectIdentityQuery,
|
||||||
|
arrayOf(Label.Type.UNREAD.name, *selectIdentityArgs)
|
||||||
|
).toInt()
|
||||||
|
else -> DatabaseUtils.queryNumEntries(
|
||||||
|
sql.readableDatabase,
|
||||||
|
TABLE_NAME,
|
||||||
|
"id IN (SELECT message_id FROM Message_Label WHERE label_id=?) " +
|
||||||
|
"AND id IN (SELECT message_id FROM Message_Label WHERE label_id IN (SELECT id FROM Label WHERE type=?)) " +
|
||||||
|
selectIdentityQuery,
|
||||||
|
arrayOf(label.id.toString(), Label.Type.UNREAD.name, *selectIdentityArgs)
|
||||||
|
).toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findConversations(label: Label?, offset: Int, limit: Int): List<UUID> = findConversations(label, offset, limit, false)
|
||||||
|
|
||||||
|
fun findConversations(label: Label?, offset: Int, limit: Int, separateIdentities: Boolean): List<UUID> {
|
||||||
val projection = arrayOf(COLUMN_CONVERSATION)
|
val projection = arrayOf(COLUMN_CONVERSATION)
|
||||||
|
val (selectIdentityQuery, selectIdentityArgs) = getSelectIdentity(separateIdentities)
|
||||||
|
|
||||||
val where = when {
|
val where = when {
|
||||||
label === LABEL_ARCHIVE -> "id NOT IN (SELECT message_id FROM Message_Label)"
|
label === LABEL_ARCHIVE -> "id NOT IN (SELECT message_id FROM Message_Label) $selectIdentityQuery"
|
||||||
label == null -> null
|
label == null -> if (selectIdentityQuery.isNotBlank()) {
|
||||||
else -> "id IN (SELECT message_id FROM Message_Label WHERE label_id=${label.id})"
|
"type = 'BROADCAST' OR recipient=? OR sender=?"
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
else -> "id IN (SELECT message_id FROM Message_Label WHERE label_id=${label.id}) $selectIdentityQuery"
|
||||||
}
|
}
|
||||||
val result = LinkedList<UUID>()
|
val result = LinkedList<UUID>()
|
||||||
sql.readableDatabase.query(
|
sql.readableDatabase.query(
|
||||||
@ -85,7 +119,7 @@ class AndroidMessageRepository(private val sql: SqlHelper) : AbstractMessageRepo
|
|||||||
TABLE_NAME,
|
TABLE_NAME,
|
||||||
projection,
|
projection,
|
||||||
where,
|
where,
|
||||||
null, null, null,
|
selectIdentityArgs, null, null,
|
||||||
"$COLUMN_RECEIVED DESC, $COLUMN_SENT DESC",
|
"$COLUMN_RECEIVED DESC, $COLUMN_SENT DESC",
|
||||||
if (limit == 0) null else "$offset, $limit"
|
if (limit == 0) null else "$offset, $limit"
|
||||||
).use { c ->
|
).use { c ->
|
||||||
@ -140,8 +174,11 @@ class AndroidMessageRepository(private val sql: SqlHelper) : AbstractMessageRepo
|
|||||||
db.update(PARENTS_TABLE_NAME, values, where, null)
|
db.update(PARENTS_TABLE_NAME, values, where, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun find(where: String, offset: Int, limit: Int): List<Plaintext> {
|
override fun find(where: String, offset: Int, limit: Int) = find(where, offset, limit, false)
|
||||||
|
|
||||||
|
private fun find(where: String, offset: Int, limit: Int, separateIdentities: Boolean): List<Plaintext> {
|
||||||
val result = LinkedList<Plaintext>()
|
val result = LinkedList<Plaintext>()
|
||||||
|
val (selectIdentityQuery, selectIdentityArgs) = getSelectIdentity(separateIdentities)
|
||||||
|
|
||||||
// Define a projection that specifies which columns from the database
|
// Define a projection that specifies which columns from the database
|
||||||
// you will actually use after this query.
|
// you will actually use after this query.
|
||||||
@ -164,7 +201,7 @@ class AndroidMessageRepository(private val sql: SqlHelper) : AbstractMessageRepo
|
|||||||
|
|
||||||
sql.readableDatabase.query(
|
sql.readableDatabase.query(
|
||||||
TABLE_NAME, projection,
|
TABLE_NAME, projection,
|
||||||
where, null, null, null,
|
"$where $selectIdentityQuery", selectIdentityArgs, null, null,
|
||||||
"$COLUMN_RECEIVED DESC, $COLUMN_SENT DESC",
|
"$COLUMN_RECEIVED DESC, $COLUMN_SENT DESC",
|
||||||
if (limit == 0) null else "$offset, $limit"
|
if (limit == 0) null else "$offset, $limit"
|
||||||
).use { c ->
|
).use { c ->
|
||||||
@ -318,4 +355,5 @@ class AndroidMessageRepository(private val sql: SqlHelper) : AbstractMessageRepo
|
|||||||
private const val JT_COLUMN_MESSAGE = "message_id"
|
private const val JT_COLUMN_MESSAGE = "message_id"
|
||||||
private const val JT_COLUMN_LABEL = "label_id"
|
private const val JT_COLUMN_LABEL = "label_id"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,9 @@ package ch.dissem.apps.abit.service
|
|||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
import android.support.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import android.support.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import android.support.v4.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import ch.dissem.apps.abit.notification.BatchNotification
|
import ch.dissem.apps.abit.notification.BatchNotification
|
||||||
import ch.dissem.apps.abit.notification.BatchNotification.Companion.ONGOING_NOTIFICATION_ID
|
import ch.dissem.apps.abit.notification.BatchNotification.Companion.ONGOING_NOTIFICATION_ID
|
||||||
import org.jetbrains.anko.doAsync
|
import org.jetbrains.anko.doAsync
|
||||||
|
@ -18,7 +18,7 @@ package ch.dissem.apps.abit.service
|
|||||||
|
|
||||||
import android.app.IntentService
|
import android.app.IntentService
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import ch.dissem.apps.abit.util.NetworkUtils
|
import ch.dissem.apps.abit.util.network
|
||||||
import ch.dissem.bitmessage.BitmessageContext
|
import ch.dissem.bitmessage.BitmessageContext
|
||||||
import ch.dissem.bitmessage.entity.Plaintext
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
|
||||||
@ -44,10 +44,10 @@ class BitmessageIntentService : IntentService("BitmessageIntentService") {
|
|||||||
Singleton.getMessageListener(this).resetNotification()
|
Singleton.getMessageListener(this).resetNotification()
|
||||||
}
|
}
|
||||||
if (it.hasExtra(EXTRA_STARTUP_NODE)) {
|
if (it.hasExtra(EXTRA_STARTUP_NODE)) {
|
||||||
NetworkUtils.enableNode(this)
|
network.enableNode()
|
||||||
}
|
}
|
||||||
if (it.hasExtra(EXTRA_SHUTDOWN_NODE)) {
|
if (it.hasExtra(EXTRA_SHUTDOWN_NODE)) {
|
||||||
NetworkUtils.disableNode(this)
|
network.disableNode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,113 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.service
|
|
||||||
|
|
||||||
import android.app.Service
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.IntentFilter
|
|
||||||
import android.net.ConnectivityManager
|
|
||||||
import android.os.Handler
|
|
||||||
import ch.dissem.apps.abit.notification.NetworkNotification
|
|
||||||
import ch.dissem.apps.abit.notification.NetworkNotification.Companion.NETWORK_NOTIFICATION_ID
|
|
||||||
import ch.dissem.apps.abit.util.Preferences
|
|
||||||
import ch.dissem.bitmessage.BitmessageContext
|
|
||||||
import ch.dissem.bitmessage.utils.Property
|
|
||||||
import org.jetbrains.anko.doAsync
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define a Service that returns an IBinder for the
|
|
||||||
* sync adapter class, allowing the sync adapter framework to call
|
|
||||||
* onPerformSync().
|
|
||||||
*/
|
|
||||||
class BitmessageService : Service() {
|
|
||||||
|
|
||||||
private val bmc: BitmessageContext by lazy { Singleton.getBitmessageContext(this) }
|
|
||||||
private lateinit var notification: NetworkNotification
|
|
||||||
|
|
||||||
private val connectivityReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
|
||||||
override fun onReceive(context: Context, intent: Intent?) {
|
|
||||||
if (bmc.isRunning() && !Preferences.isConnectionAllowed(this@BitmessageService)) {
|
|
||||||
bmc.shutdown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val cleanupHandler = Handler()
|
|
||||||
private val cleanupTask: Runnable = object : Runnable {
|
|
||||||
override fun run() {
|
|
||||||
bmc.cleanup()
|
|
||||||
if (isRunning) {
|
|
||||||
cleanupHandler.postDelayed(this, 24 * 60 * 60 * 1000L) // once a day
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate() {
|
|
||||||
registerReceiver(
|
|
||||||
connectivityReceiver,
|
|
||||||
IntentFilter().apply {
|
|
||||||
addAction(ConnectivityManager.CONNECTIVITY_ACTION)
|
|
||||||
addAction(Intent.ACTION_BATTERY_CHANGED)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
notification = NetworkNotification(this)
|
|
||||||
running = false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
||||||
if (!isRunning) {
|
|
||||||
running = true
|
|
||||||
notification.connecting()
|
|
||||||
startForeground(NETWORK_NOTIFICATION_ID, notification.notification)
|
|
||||||
if (!bmc.isRunning()) {
|
|
||||||
bmc.startup()
|
|
||||||
}
|
|
||||||
notification.show()
|
|
||||||
cleanupHandler.postDelayed(cleanupTask, 24 * 60 * 60 * 1000L)
|
|
||||||
}
|
|
||||||
return Service.START_STICKY
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
if (bmc.isRunning()) {
|
|
||||||
bmc.shutdown()
|
|
||||||
}
|
|
||||||
running = false
|
|
||||||
notification.showShutdown()
|
|
||||||
cleanupHandler.removeCallbacks(cleanupTask)
|
|
||||||
doAsync {
|
|
||||||
bmc.cleanup()
|
|
||||||
}
|
|
||||||
unregisterReceiver(connectivityReceiver)
|
|
||||||
stopSelf()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBind(intent: Intent) = null
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@Volatile
|
|
||||||
private var running = false
|
|
||||||
|
|
||||||
val isRunning: Boolean
|
|
||||||
get() = running && Singleton.bitmessageContext?.isRunning() == true
|
|
||||||
|
|
||||||
val status: Property
|
|
||||||
get() = Singleton.bitmessageContext?.status() ?: Property("bitmessage context")
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,18 @@
|
|||||||
|
package ch.dissem.apps.abit.service
|
||||||
|
|
||||||
|
import android.app.job.JobParameters
|
||||||
|
import android.app.job.JobService
|
||||||
|
import org.jetbrains.anko.doAsync
|
||||||
|
|
||||||
|
class CleanupService : JobService() {
|
||||||
|
|
||||||
|
override fun onStartJob(params: JobParameters?): Boolean {
|
||||||
|
doAsync {
|
||||||
|
Singleton.getBitmessageContext(this@CleanupService).cleanup()
|
||||||
|
jobFinished(params, false)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStopJob(params: JobParameters?) = false
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
package ch.dissem.apps.abit.service
|
||||||
|
|
||||||
|
import android.app.job.JobParameters
|
||||||
|
import android.app.job.JobService
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import ch.dissem.apps.abit.notification.NetworkNotification
|
||||||
|
import ch.dissem.apps.abit.util.network
|
||||||
|
import ch.dissem.apps.abit.util.preferences
|
||||||
|
import ch.dissem.bitmessage.BitmessageContext
|
||||||
|
import ch.dissem.bitmessage.utils.Property
|
||||||
|
import org.jetbrains.anko.doAsync
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the full node if
|
||||||
|
* * it is active
|
||||||
|
* * it is not already running
|
||||||
|
*
|
||||||
|
* And stops it when the preconditions for the job (unmetered network) aren't met anymore.
|
||||||
|
*/
|
||||||
|
class NodeStartupService : JobService() {
|
||||||
|
private val bmc: BitmessageContext by lazy { Singleton.getBitmessageContext(this) }
|
||||||
|
|
||||||
|
private lateinit var notification: NetworkNotification
|
||||||
|
|
||||||
|
private val connectivityReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent?) {
|
||||||
|
if (bmc.isRunning() && !preferences.connectionAllowed) {
|
||||||
|
bmc.shutdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
notification = NetworkNotification(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartJob(params: JobParameters?): Boolean {
|
||||||
|
if (preferences.online) {
|
||||||
|
registerReceiver(
|
||||||
|
connectivityReceiver,
|
||||||
|
IntentFilter().apply {
|
||||||
|
addAction(ConnectivityManager.CONNECTIVITY_ACTION)
|
||||||
|
addAction(Intent.ACTION_BATTERY_CHANGED)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
startForeground(0, notification.notification)
|
||||||
|
NodeStartupService.running = false
|
||||||
|
|
||||||
|
if (!isRunning) {
|
||||||
|
running = true
|
||||||
|
notification.connecting()
|
||||||
|
if (!bmc.isRunning()) {
|
||||||
|
bmc.startup()
|
||||||
|
}
|
||||||
|
notification.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
if (bmc.isRunning()) {
|
||||||
|
bmc.shutdown()
|
||||||
|
}
|
||||||
|
running = false
|
||||||
|
notification.showShutdown()
|
||||||
|
doAsync {
|
||||||
|
bmc.cleanup()
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
unregisterReceiver(connectivityReceiver)
|
||||||
|
} catch (_: IllegalArgumentException) {
|
||||||
|
// For some reason, onStartJob wasn't called so the receiver isn't registered.
|
||||||
|
// Let's just ignore this.
|
||||||
|
}
|
||||||
|
stopSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't actually stop the service, otherwise it will be stopped after 1 or 10 minutes
|
||||||
|
* depending on Android version.
|
||||||
|
*/
|
||||||
|
override fun onStopJob(params: JobParameters?): Boolean {
|
||||||
|
network.scheduleNodeStart()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Volatile
|
||||||
|
private var running = false
|
||||||
|
|
||||||
|
val isRunning: Boolean
|
||||||
|
get() = running && Singleton.bitmessageContext?.isRunning() == true
|
||||||
|
|
||||||
|
val status: Property
|
||||||
|
get() = Singleton.bitmessageContext?.status() ?: Property("bitmessage context")
|
||||||
|
}
|
||||||
|
}
|
@ -19,7 +19,7 @@ package ch.dissem.apps.abit.service
|
|||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
import android.support.v4.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import ch.dissem.apps.abit.notification.ProofOfWorkNotification
|
import ch.dissem.apps.abit.notification.ProofOfWorkNotification
|
||||||
import ch.dissem.apps.abit.notification.ProofOfWorkNotification.Companion.ONGOING_NOTIFICATION_ID
|
import ch.dissem.apps.abit.notification.ProofOfWorkNotification.Companion.ONGOING_NOTIFICATION_ID
|
||||||
import ch.dissem.apps.abit.util.PowStats
|
import ch.dissem.apps.abit.util.PowStats
|
||||||
|
@ -21,23 +21,20 @@ import android.widget.Toast
|
|||||||
import ch.dissem.apps.abit.MainActivity
|
import ch.dissem.apps.abit.MainActivity
|
||||||
import ch.dissem.apps.abit.R
|
import ch.dissem.apps.abit.R
|
||||||
import ch.dissem.apps.abit.adapter.SwipeableMessageAdapter
|
import ch.dissem.apps.abit.adapter.SwipeableMessageAdapter
|
||||||
import ch.dissem.apps.abit.adapter.SwitchingProofOfWorkEngine
|
|
||||||
import ch.dissem.apps.abit.listener.MessageListener
|
import ch.dissem.apps.abit.listener.MessageListener
|
||||||
import ch.dissem.apps.abit.pow.ServerPowEngine
|
|
||||||
import ch.dissem.apps.abit.repository.*
|
import ch.dissem.apps.abit.repository.*
|
||||||
import ch.dissem.apps.abit.util.Constants
|
import ch.dissem.apps.abit.util.preferences
|
||||||
import ch.dissem.apps.abit.util.Observable
|
|
||||||
import ch.dissem.bitmessage.BitmessageContext
|
import ch.dissem.bitmessage.BitmessageContext
|
||||||
import ch.dissem.bitmessage.cryptography.sc.SpongyCryptography
|
import ch.dissem.bitmessage.cryptography.sc.SpongyCryptography
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey
|
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label
|
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||||
import ch.dissem.bitmessage.factory.BufferPool
|
|
||||||
import ch.dissem.bitmessage.networking.nio.NioNetworkHandler
|
import ch.dissem.bitmessage.networking.nio.NioNetworkHandler
|
||||||
import ch.dissem.bitmessage.ports.DefaultLabeler
|
import ch.dissem.bitmessage.ports.DefaultLabeler
|
||||||
import ch.dissem.bitmessage.utils.ConversationService
|
import ch.dissem.bitmessage.utils.ConversationService
|
||||||
import ch.dissem.bitmessage.utils.TTL
|
import ch.dissem.bitmessage.utils.TTL
|
||||||
import ch.dissem.bitmessage.utils.UnixTime.DAY
|
import ch.dissem.bitmessage.utils.UnixTime.DAY
|
||||||
|
import io.reactivex.subjects.BehaviorSubject
|
||||||
import org.jetbrains.anko.doAsync
|
import org.jetbrains.anko.doAsync
|
||||||
import org.jetbrains.anko.uiThread
|
import org.jetbrains.anko.uiThread
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
@ -46,7 +43,7 @@ import java.lang.ref.WeakReference
|
|||||||
* Provides singleton objects across the application.
|
* Provides singleton objects across the application.
|
||||||
*/
|
*/
|
||||||
object Singleton {
|
object Singleton {
|
||||||
var currentLabel = Observable<Label?>(null)
|
var currentLabel = BehaviorSubject.create<Label>()
|
||||||
|
|
||||||
private var swipeableMessageAdapter: WeakReference<SwipeableMessageAdapter>? = null
|
private var swipeableMessageAdapter: WeakReference<SwipeableMessageAdapter>? = null
|
||||||
val labeler = DefaultLabeler().apply {
|
val labeler = DefaultLabeler().apply {
|
||||||
@ -54,7 +51,7 @@ object Singleton {
|
|||||||
MainActivity.apply {
|
MainActivity.apply {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
swipeableMessageAdapter?.get()?.let { swipeableMessageAdapter ->
|
swipeableMessageAdapter?.get()?.let { swipeableMessageAdapter ->
|
||||||
currentLabel.value?.let { label ->
|
currentLabel.value?.let {label ->
|
||||||
when {
|
when {
|
||||||
label.type == Label.Type.TRASH
|
label.type == Label.Type.TRASH
|
||||||
&& added.all { it.type == Label.Type.TRASH }
|
&& added.all { it.type == Label.Type.TRASH }
|
||||||
@ -67,7 +64,7 @@ object Singleton {
|
|||||||
// work-around for messages that are deleted from unread, which already have the unread label removed
|
// work-around for messages that are deleted from unread, which already have the unread label removed
|
||||||
swipeableMessageAdapter.remove(message)
|
swipeableMessageAdapter.remove(message)
|
||||||
}
|
}
|
||||||
label == AndroidLabelRepository.LABEL_ARCHIVE && !added.isEmpty() -> {
|
label == AndroidLabelRepository.LABEL_ARCHIVE && !added.isEmpty() -> {
|
||||||
// work-around for messages in archive, which isn't an actual label but an absence of labels
|
// work-around for messages in archive, which isn't an actual label but an absence of labels
|
||||||
swipeableMessageAdapter.remove(message)
|
swipeableMessageAdapter.remove(message)
|
||||||
}
|
}
|
||||||
@ -102,27 +99,23 @@ object Singleton {
|
|||||||
|
|
||||||
fun getBitmessageContext(context: Context): BitmessageContext =
|
fun getBitmessageContext(context: Context): BitmessageContext =
|
||||||
init({ bitmessageContext }, { bitmessageContext = it }) {
|
init({ bitmessageContext }, { bitmessageContext = it }) {
|
||||||
BufferPool.setLimit(4)
|
|
||||||
BitmessageContext.build {
|
BitmessageContext.build {
|
||||||
TTL.pubkey = 2 * DAY
|
TTL.pubkey = 2 * DAY
|
||||||
val ctx = context.applicationContext
|
val ctx = context.applicationContext
|
||||||
val sqlHelper = SqlHelper(ctx)
|
val sqlHelper = SqlHelper(ctx)
|
||||||
proofOfWorkEngine = SwitchingProofOfWorkEngine(
|
proofOfWorkEngine = ServicePowEngine(ctx)
|
||||||
ctx, Constants.PREFERENCE_SERVER_POW,
|
|
||||||
ServerPowEngine(ctx),
|
|
||||||
ServicePowEngine(ctx)
|
|
||||||
)
|
|
||||||
cryptography = SpongyCryptography()
|
cryptography = SpongyCryptography()
|
||||||
nodeRegistry = AndroidNodeRegistry(sqlHelper)
|
nodeRegistry = AndroidNodeRegistry(sqlHelper)
|
||||||
inventory = AndroidInventory(sqlHelper)
|
inventory = AndroidInventory(sqlHelper)
|
||||||
addressRepo = AndroidAddressRepository(sqlHelper)
|
addressRepo = AndroidAddressRepository(sqlHelper)
|
||||||
labelRepo = AndroidLabelRepository(sqlHelper, ctx)
|
labelRepo = AndroidLabelRepository(sqlHelper, ctx)
|
||||||
messageRepo = AndroidMessageRepository(sqlHelper)
|
messageRepo = AndroidMessageRepository(sqlHelper, ctx.preferences)
|
||||||
proofOfWorkRepo = AndroidProofOfWorkRepository(sqlHelper).also { powRepo = it }
|
proofOfWorkRepo = AndroidProofOfWorkRepository(sqlHelper).also { powRepo = it }
|
||||||
networkHandler = NioNetworkHandler(4)
|
networkHandler = NioNetworkHandler(4)
|
||||||
listener = getMessageListener(ctx)
|
listener = getMessageListener(ctx)
|
||||||
labeler = Singleton.labeler
|
labeler = Singleton.labeler
|
||||||
preferences.sendPubkeyOnIdentityCreation = false
|
preferences.sendPubkeyOnIdentityCreation = false
|
||||||
|
preferences.port = context.preferences.listeningPort
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,8 +131,6 @@ object Singleton {
|
|||||||
|
|
||||||
fun getAddressRepository(ctx: Context) = getBitmessageContext(ctx).addresses as AndroidAddressRepository
|
fun getAddressRepository(ctx: Context) = getBitmessageContext(ctx).addresses as AndroidAddressRepository
|
||||||
|
|
||||||
fun getProofOfWorkRepository(ctx: Context) = powRepo ?: getBitmessageContext(ctx).internals.proofOfWorkRepository
|
|
||||||
|
|
||||||
fun getIdentity(ctx: Context): BitmessageAddress? =
|
fun getIdentity(ctx: Context): BitmessageAddress? =
|
||||||
init<BitmessageAddress?>(ctx, { identity }, { identity = it }) { bmc ->
|
init<BitmessageAddress?>(ctx, { identity }, { identity = it }) { bmc ->
|
||||||
val identities = bmc.addresses.getIdentities()
|
val identities = bmc.addresses.getIdentities()
|
||||||
|
@ -3,18 +3,17 @@ package ch.dissem.apps.abit.service
|
|||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import ch.dissem.apps.abit.util.NetworkUtils
|
import android.content.Intent.ACTION_BOOT_COMPLETED
|
||||||
import ch.dissem.apps.abit.util.Preferences
|
import ch.dissem.apps.abit.util.network
|
||||||
|
import ch.dissem.apps.abit.util.preferences
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the Bitmessage "full node" service if conditions allow it
|
* Starts the Bitmessage "full node" service if conditions allow it
|
||||||
*/
|
*/
|
||||||
class StartServiceReceiver : BroadcastReceiver() {
|
class StartServiceReceiver : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent?) {
|
override fun onReceive(context: Context, intent: Intent?) {
|
||||||
if (intent?.action == "android.intent.action.BOOT_COMPLETED") {
|
if (intent?.action == ACTION_BOOT_COMPLETED && context.preferences.online) {
|
||||||
if (Preferences.isFullNodeActive(context)) {
|
context.network.enableNode(false)
|
||||||
NetworkUtils.enableNode(context, false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
package ch.dissem.apps.abit.service
|
|
||||||
|
|
||||||
import android.app.job.JobParameters
|
|
||||||
import android.app.job.JobService
|
|
||||||
import android.os.Build
|
|
||||||
import android.support.annotation.RequiresApi
|
|
||||||
import ch.dissem.apps.abit.util.NetworkUtils
|
|
||||||
import ch.dissem.apps.abit.util.Preferences
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the full node if
|
|
||||||
* * it is active
|
|
||||||
* * it is not already running
|
|
||||||
*
|
|
||||||
* And stops it when the preconditions for the job (unmetered network) aren't met anymore.
|
|
||||||
*/
|
|
||||||
class StartupNodeOnWifiService : JobService() {
|
|
||||||
|
|
||||||
override fun onStartJob(params: JobParameters?): Boolean {
|
|
||||||
val bmc = Singleton.getBitmessageContext(this)
|
|
||||||
if (Preferences.isFullNodeActive(this) && !bmc.isRunning()) {
|
|
||||||
NetworkUtils.doStartBitmessageService(applicationContext)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Don't actually stop the service, otherwise it will be stopped after 1 or 10 minutes
|
|
||||||
* depending on Android version.
|
|
||||||
*/
|
|
||||||
override fun onStopJob(params: JobParameters?) = Preferences.isFullNodeActive(this)
|
|
||||||
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.synchronization
|
|
||||||
|
|
||||||
import android.accounts.AbstractAccountAuthenticator
|
|
||||||
import android.accounts.Account
|
|
||||||
import android.accounts.AccountAuthenticatorResponse
|
|
||||||
import android.accounts.NetworkErrorException
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implement AbstractAccountAuthenticator and stub out all
|
|
||||||
* of its methods
|
|
||||||
*/
|
|
||||||
class Authenticator(context: Context) : AbstractAccountAuthenticator(context) {
|
|
||||||
|
|
||||||
override fun editProperties(r: AccountAuthenticatorResponse, s: String) =
|
|
||||||
throw UnsupportedOperationException("Editing properties is not supported")
|
|
||||||
|
|
||||||
// Don't add additional accounts
|
|
||||||
@Throws(NetworkErrorException::class)
|
|
||||||
override fun addAccount(r: AccountAuthenticatorResponse, s: String, s2: String, strings: Array<String>, bundle: Bundle) = null
|
|
||||||
|
|
||||||
// Ignore attempts to confirm credentials
|
|
||||||
@Throws(NetworkErrorException::class)
|
|
||||||
override fun confirmCredentials(r: AccountAuthenticatorResponse, account: Account, bundle: Bundle) = null
|
|
||||||
|
|
||||||
@Throws(NetworkErrorException::class)
|
|
||||||
override fun getAuthToken(r: AccountAuthenticatorResponse, account: Account, s: String, bundle: Bundle) =
|
|
||||||
throw UnsupportedOperationException("Getting an authentication token is not supported")
|
|
||||||
|
|
||||||
override fun getAuthTokenLabel(s: String) =
|
|
||||||
throw UnsupportedOperationException("Getting a label for the auth token is not supported")
|
|
||||||
|
|
||||||
@Throws(NetworkErrorException::class)
|
|
||||||
override fun updateCredentials(r: AccountAuthenticatorResponse, account: Account, s: String, bundle: Bundle) =
|
|
||||||
throw UnsupportedOperationException("Updating user credentials is not supported")
|
|
||||||
|
|
||||||
@Throws(NetworkErrorException::class)
|
|
||||||
override fun hasFeatures(r: AccountAuthenticatorResponse, account: Account, strings: Array<String>) =
|
|
||||||
throw UnsupportedOperationException("Checking features for the account is not supported")
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val ACCOUNT_SYNC = Account("Bitmessage", "ch.dissem.bitmessage")
|
|
||||||
val ACCOUNT_POW = Account("Proof of Work ", "ch.dissem.bitmessage")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.synchronization
|
|
||||||
|
|
||||||
import android.app.Service
|
|
||||||
import android.content.Intent
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A bound Service that instantiates the authenticator
|
|
||||||
* when started.
|
|
||||||
*/
|
|
||||||
class AuthenticatorService : Service() {
|
|
||||||
/**
|
|
||||||
* Instance field that stores the authenticator object
|
|
||||||
*/
|
|
||||||
private var authenticator: Authenticator? = null
|
|
||||||
|
|
||||||
override fun onCreate() {
|
|
||||||
// Create a new authenticator object
|
|
||||||
authenticator = Authenticator(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* When the system binds to this Service to make the RPC call
|
|
||||||
* return the authenticator's IBinder.
|
|
||||||
*/
|
|
||||||
override fun onBind(intent: Intent) = authenticator?.iBinder
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.synchronization
|
|
||||||
|
|
||||||
import android.content.ContentProvider
|
|
||||||
import android.content.ContentValues
|
|
||||||
import android.net.Uri
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Define an implementation of ContentProvider that stubs out
|
|
||||||
* all methods
|
|
||||||
*/
|
|
||||||
class StubProvider : ContentProvider() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Always return true, indicating that the
|
|
||||||
* provider loaded correctly.
|
|
||||||
*/
|
|
||||||
override fun onCreate() = true
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return no type for MIME type
|
|
||||||
*/
|
|
||||||
override fun getType(uri: Uri) = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* query() always returns no results
|
|
||||||
*/
|
|
||||||
override fun query(
|
|
||||||
uri: Uri,
|
|
||||||
projection: Array<String>?,
|
|
||||||
selection: String?,
|
|
||||||
selectionArgs: Array<String>?,
|
|
||||||
sortOrder: String?) = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* insert() always returns null (no URI)
|
|
||||||
*/
|
|
||||||
override fun insert(uri: Uri, values: ContentValues?) = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* delete() always returns "no rows affected" (0)
|
|
||||||
*/
|
|
||||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?) = 0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* update() always returns "no rows affected" (0)
|
|
||||||
*/
|
|
||||||
override fun update(
|
|
||||||
uri: Uri,
|
|
||||||
values: ContentValues?,
|
|
||||||
selection: String?,
|
|
||||||
selectionArgs: Array<String>?) = 0
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val AUTHORITY = "ch.dissem.apps.abit.provider"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,188 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.synchronization
|
|
||||||
|
|
||||||
import android.accounts.Account
|
|
||||||
import android.accounts.AccountManager
|
|
||||||
import android.content.*
|
|
||||||
import android.os.Bundle
|
|
||||||
import ch.dissem.apps.abit.service.Singleton
|
|
||||||
import ch.dissem.apps.abit.synchronization.Authenticator.Companion.ACCOUNT_POW
|
|
||||||
import ch.dissem.apps.abit.synchronization.Authenticator.Companion.ACCOUNT_SYNC
|
|
||||||
import ch.dissem.apps.abit.synchronization.StubProvider.Companion.AUTHORITY
|
|
||||||
import ch.dissem.apps.abit.util.NetworkUtils
|
|
||||||
import ch.dissem.apps.abit.util.Preferences
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
|
||||||
import ch.dissem.bitmessage.extensions.CryptoCustomMessage
|
|
||||||
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest
|
|
||||||
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATE
|
|
||||||
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.COMPLETE
|
|
||||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sync Adapter to synchronize with the Bitmessage network - fetches
|
|
||||||
* new objects and then disconnects.
|
|
||||||
*/
|
|
||||||
class SyncAdapter(context: Context, autoInitialize: Boolean) : AbstractThreadedSyncAdapter(context, autoInitialize) {
|
|
||||||
|
|
||||||
private val bmc = Singleton.getBitmessageContext(context)
|
|
||||||
|
|
||||||
override fun onPerformSync(
|
|
||||||
account: Account,
|
|
||||||
extras: Bundle,
|
|
||||||
authority: String,
|
|
||||||
provider: ContentProviderClient,
|
|
||||||
syncResult: SyncResult
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
if (account == ACCOUNT_SYNC) {
|
|
||||||
if (Preferences.isConnectionAllowed(context)) {
|
|
||||||
syncData()
|
|
||||||
}
|
|
||||||
} else if (account == ACCOUNT_POW) {
|
|
||||||
syncPOW()
|
|
||||||
} else {
|
|
||||||
syncResult.stats.numAuthExceptions++
|
|
||||||
}
|
|
||||||
} catch (e: IOException) {
|
|
||||||
syncResult.stats.numIoExceptions++
|
|
||||||
} catch (e: DecryptionFailedException) {
|
|
||||||
syncResult.stats.numAuthExceptions++
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun syncData() {
|
|
||||||
// If the Bitmessage context acts as a full node, synchronization isn't necessary
|
|
||||||
if (bmc.isRunning()) {
|
|
||||||
LOG.info("Synchronization skipped, Abit is acting as a full node")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val trustedNode = Preferences.getTrustedNode(context)
|
|
||||||
if (trustedNode == null) {
|
|
||||||
// As Abit tends to get killed by the system, let's leverage the sync mechanism to start it again:
|
|
||||||
NetworkUtils.scheduleNodeStart(context)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
LOG.info("Synchronization started")
|
|
||||||
bmc.synchronize(
|
|
||||||
trustedNode,
|
|
||||||
Preferences.getTrustedNodePort(context),
|
|
||||||
Preferences.getTimeoutInSeconds(context),
|
|
||||||
true
|
|
||||||
)
|
|
||||||
LOG.info("Synchronization finished")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun syncPOW() {
|
|
||||||
val identity = Singleton.getIdentity(context)
|
|
||||||
if (identity == null) {
|
|
||||||
LOG.info("No identity available - skipping POW synchronization")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val trustedNode = Preferences.getTrustedNode(context)
|
|
||||||
if (trustedNode == null) {
|
|
||||||
LOG.info("Trusted node not available, disabling POW synchronization")
|
|
||||||
stopPowSync(context)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// If the Bitmessage context acts as a full node, synchronization isn't necessary
|
|
||||||
LOG.info("Looking for completed POW")
|
|
||||||
|
|
||||||
val privateKey =
|
|
||||||
identity.privateKey?.privateEncryptionKey ?: throw IllegalStateException("Identity without private key")
|
|
||||||
val signingKey = cryptography().createPublicKey(identity.publicDecryptionKey)
|
|
||||||
val reader = ProofOfWorkRequest.Reader(identity)
|
|
||||||
val powRepo = Singleton.getProofOfWorkRepository(context)
|
|
||||||
val items = powRepo.getItems()
|
|
||||||
for (initialHash in items) {
|
|
||||||
val (objectMessage, nonceTrialsPerByte, extraBytes) = powRepo.getItem(initialHash)
|
|
||||||
val target = cryptography().getProofOfWorkTarget(objectMessage, nonceTrialsPerByte, extraBytes)
|
|
||||||
val cryptoMsg = CryptoCustomMessage(
|
|
||||||
ProofOfWorkRequest(identity, initialHash, CALCULATE, target)
|
|
||||||
)
|
|
||||||
cryptoMsg.signAndEncrypt(identity, signingKey)
|
|
||||||
val response = bmc.send(
|
|
||||||
trustedNode,
|
|
||||||
Preferences.getTrustedNodePort(context),
|
|
||||||
cryptoMsg
|
|
||||||
)
|
|
||||||
if (response.isError) {
|
|
||||||
LOG.error("Server responded with error: ${String(response.getData())}")
|
|
||||||
} else {
|
|
||||||
val (_, _, request, data) = CryptoCustomMessage.read(response, reader).decrypt(privateKey)
|
|
||||||
if (request == COMPLETE) {
|
|
||||||
bmc.internals.proofOfWorkService.onNonceCalculated(initialHash, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (items.isEmpty()) {
|
|
||||||
stopPowSync(context)
|
|
||||||
}
|
|
||||||
LOG.info("Synchronization finished")
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val LOG = LoggerFactory.getLogger(SyncAdapter::class.java)
|
|
||||||
|
|
||||||
private const val SYNC_FREQUENCY = 15 * 60L // seconds
|
|
||||||
|
|
||||||
fun startSync(ctx: Context) {
|
|
||||||
// Create account, if it's missing. (Either first run, or user has deleted account.)
|
|
||||||
val account = addAccount(ctx, ACCOUNT_SYNC)
|
|
||||||
|
|
||||||
// Recommend a schedule for automatic synchronization. The system may modify this based
|
|
||||||
// on other scheduled syncs and network utilization.
|
|
||||||
ContentResolver.addPeriodicSync(account, AUTHORITY, Bundle(), SYNC_FREQUENCY)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stopSync(ctx: Context) {
|
|
||||||
// Create account, if it's missing. (Either first run, or user has deleted account.)
|
|
||||||
val account = addAccount(ctx, ACCOUNT_SYNC)
|
|
||||||
|
|
||||||
ContentResolver.removePeriodicSync(account, AUTHORITY, Bundle())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startPowSync(ctx: Context) {
|
|
||||||
// Create account, if it's missing. (Either first run, or user has deleted account.)
|
|
||||||
val account = addAccount(ctx, ACCOUNT_POW)
|
|
||||||
|
|
||||||
// Recommend a schedule for automatic synchronization. The system may modify this based
|
|
||||||
// on other scheduled syncs and network utilization.
|
|
||||||
ContentResolver.addPeriodicSync(account, AUTHORITY, Bundle(), SYNC_FREQUENCY)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stopPowSync(ctx: Context) {
|
|
||||||
// Create account, if it's missing. (Either first run, or user has deleted account.)
|
|
||||||
val account = addAccount(ctx, ACCOUNT_POW)
|
|
||||||
|
|
||||||
ContentResolver.removePeriodicSync(account, AUTHORITY, Bundle())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addAccount(ctx: Context, account: Account): Account {
|
|
||||||
if (AccountManager.get(ctx).addAccountExplicitly(account, null, null)) {
|
|
||||||
// Inform the system that this account supports sync
|
|
||||||
ContentResolver.setIsSyncable(account, AUTHORITY, 1)
|
|
||||||
// Inform the system that this account is eligible for auto sync when the network is up
|
|
||||||
ContentResolver.setSyncAutomatically(account, AUTHORITY, true)
|
|
||||||
}
|
|
||||||
return account
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.synchronization
|
|
||||||
|
|
||||||
import android.app.Service
|
|
||||||
import android.content.Intent
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define a Service that returns an IBinder for the
|
|
||||||
* sync adapter class, allowing the sync adapter framework to call
|
|
||||||
* onPerformSync().
|
|
||||||
*/
|
|
||||||
class SyncService : Service() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiate the sync adapter object.
|
|
||||||
*/
|
|
||||||
override fun onCreate() = synchronized(syncAdapterLock) {
|
|
||||||
if (syncAdapter == null) {
|
|
||||||
syncAdapter = SyncAdapter(this, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an object that allows the system to invoke
|
|
||||||
* the sync adapter.
|
|
||||||
*/
|
|
||||||
override fun onBind(intent: Intent) = syncAdapter?.syncAdapterBinder
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
// Storage for an instance of the sync adapter
|
|
||||||
private var syncAdapter: SyncAdapter? = null
|
|
||||||
// Object to use as a thread-safe lock
|
|
||||||
private val syncAdapterLock = Any()
|
|
||||||
}
|
|
||||||
}
|
|
@ -22,16 +22,14 @@ import java.util.regex.Pattern
|
|||||||
* @author Christian Basler
|
* @author Christian Basler
|
||||||
*/
|
*/
|
||||||
object Constants {
|
object Constants {
|
||||||
|
const val PREFERENCE_ONLINE = "online"
|
||||||
const val PREFERENCE_WIFI_ONLY = "wifi_only"
|
const val PREFERENCE_WIFI_ONLY = "wifi_only"
|
||||||
const val PREFERENCE_REQUIRE_CHARGING = "require_charging"
|
const val PREFERENCE_REQUIRE_CHARGING = "require_charging"
|
||||||
const val PREFERENCE_EMULATE_CONVERSATIONS = "emulate_conversations"
|
const val PREFERENCE_EMULATE_CONVERSATIONS = "emulate_conversations"
|
||||||
const val PREFERENCE_TRUSTED_NODE = "trusted_node"
|
|
||||||
const val PREFERENCE_SYNC_TIMEOUT = "sync_timeout"
|
|
||||||
const val PREFERENCE_SERVER_POW = "server_pow"
|
|
||||||
const val PREFERENCE_FULL_NODE = "full_node"
|
|
||||||
const val PREFERENCE_REQUEST_ACK = "request_acknowledgments"
|
const val PREFERENCE_REQUEST_ACK = "request_acknowledgments"
|
||||||
const val PREFERENCE_POW_AVERAGE = "average_pow_time_ms"
|
const val PREFERENCE_POW_AVERAGE = "average_pow_time_ms"
|
||||||
const val PREFERENCE_POW_COUNT = "pow_count"
|
const val PREFERENCE_POW_COUNT = "pow_count"
|
||||||
|
const val PREFERENCE_SEPARATE_IDENTITIES = "separate_identities"
|
||||||
|
|
||||||
const val BITMESSAGE_URL_SCHEMA = "bitmessage:"
|
const val BITMESSAGE_URL_SCHEMA = "bitmessage:"
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package ch.dissem.apps.abit.util
|
package ch.dissem.apps.abit.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.support.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import ch.dissem.apps.abit.R
|
import ch.dissem.apps.abit.R
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label
|
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||||
import com.mikepenz.community_material_typeface_library.CommunityMaterial
|
import com.mikepenz.community_material_typeface_library.CommunityMaterial
|
||||||
|
@ -6,20 +6,21 @@ import android.app.job.JobScheduler
|
|||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.support.v4.content.ContextCompat
|
import android.os.Build
|
||||||
import ch.dissem.apps.abit.dialog.FullNodeDialogActivity
|
import ch.dissem.apps.abit.dialog.FullNodeDialogActivity
|
||||||
import ch.dissem.apps.abit.service.BitmessageService
|
import ch.dissem.apps.abit.service.CleanupService
|
||||||
import ch.dissem.apps.abit.service.StartupNodeOnWifiService
|
import ch.dissem.apps.abit.service.NodeStartupService
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
val Context.network get() = NetworkUtils.getInstance(this)
|
||||||
|
|
||||||
object NetworkUtils {
|
class NetworkUtils internal constructor(private val ctx: Context) {
|
||||||
|
|
||||||
fun enableNode(ctx: Context, ask: Boolean = true) {
|
private val jobScheduler by lazy { ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler }
|
||||||
Preferences.setFullNodeActive(ctx, true)
|
|
||||||
|
|
||||||
if (Preferences.isConnectionAllowed(ctx) || !ask) {
|
fun enableNode(ask: Boolean = true) {
|
||||||
scheduleNodeStart(ctx)
|
if (ask && !ctx.preferences.connectionAllowed) {
|
||||||
} else {
|
|
||||||
// Ask for connection
|
// Ask for connection
|
||||||
val dialogIntent = Intent(ctx, FullNodeDialogActivity::class.java)
|
val dialogIntent = Intent(ctx, FullNodeDialogActivity::class.java)
|
||||||
if (ctx !is Activity) {
|
if (ctx !is Activity) {
|
||||||
@ -27,30 +28,50 @@ object NetworkUtils {
|
|||||||
ctx.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
|
ctx.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
|
||||||
}
|
}
|
||||||
ctx.startActivity(dialogIntent)
|
ctx.startActivity(dialogIntent)
|
||||||
|
} else {
|
||||||
|
scheduleNodeStart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun doStartBitmessageService(ctx: Context) {
|
fun disableNode() {
|
||||||
ContextCompat.startForegroundService(ctx, Intent(ctx, BitmessageService::class.java))
|
jobScheduler.cancelAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun disableNode(ctx: Context) {
|
fun scheduleNodeStart() {
|
||||||
Preferences.setFullNodeActive(ctx, false)
|
JobInfo.Builder(0, ComponentName(ctx, NodeStartupService::class.java)).let { builder ->
|
||||||
ctx.stopService(Intent(ctx, BitmessageService::class.java))
|
when {
|
||||||
}
|
ctx.preferences.wifiOnly ->
|
||||||
|
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
|
||||||
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ->
|
||||||
|
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING)
|
||||||
|
else ->
|
||||||
|
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
||||||
|
}
|
||||||
|
builder.setRequiresCharging(ctx.preferences.requireCharging)
|
||||||
|
builder.setPersisted(true)
|
||||||
|
|
||||||
fun scheduleNodeStart(ctx: Context) {
|
jobScheduler.schedule(builder.build())
|
||||||
val jobScheduler = ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
|
|
||||||
val serviceComponent = ComponentName(ctx, StartupNodeOnWifiService::class.java)
|
|
||||||
val builder = JobInfo.Builder(0, serviceComponent)
|
|
||||||
if (Preferences.isWifiOnly(ctx)) {
|
|
||||||
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
|
|
||||||
}
|
}
|
||||||
if (Preferences.requireCharging(ctx)) {
|
|
||||||
|
JobInfo.Builder(1, ComponentName(ctx, CleanupService::class.java)).let { builder ->
|
||||||
|
builder.setPeriodic(TimeUnit.DAYS.toMillis(1))
|
||||||
|
builder.setRequiresDeviceIdle(true)
|
||||||
builder.setRequiresCharging(true)
|
builder.setRequiresCharging(true)
|
||||||
|
|
||||||
|
jobScheduler.schedule(builder.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var instance: WeakReference<NetworkUtils>? = null
|
||||||
|
|
||||||
|
internal fun getInstance(ctx: Context): NetworkUtils {
|
||||||
|
var networkUtils = instance?.get()
|
||||||
|
if (networkUtils == null) {
|
||||||
|
networkUtils = NetworkUtils(ctx.applicationContext)
|
||||||
|
instance = WeakReference(networkUtils)
|
||||||
|
}
|
||||||
|
return networkUtils
|
||||||
}
|
}
|
||||||
builder.setBackoffCriteria(0L, JobInfo.BACKOFF_POLICY_LINEAR)
|
|
||||||
builder.setPersisted(true)
|
|
||||||
jobScheduler.schedule(builder.build())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
package ch.dissem.apps.abit.util
|
|
||||||
|
|
||||||
import kotlin.properties.Delegates
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple observable implementation that should be mostly
|
|
||||||
*/
|
|
||||||
class Observable<T>(value: T) {
|
|
||||||
private val observers = mutableMapOf<Any, (T) -> Unit>()
|
|
||||||
|
|
||||||
var value: T by Delegates.observable(value, { _, old, new ->
|
|
||||||
if (old != new) {
|
|
||||||
observers.values.forEach { it.invoke(new) }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The key will make sure the observer can easily be removed. Usually the key should be either
|
|
||||||
* the object that created the observer, or the observer itself, if it's easily available.
|
|
||||||
*
|
|
||||||
* Note that a map is used for observers, so if you define more than one observer with the same
|
|
||||||
* key, all previous ones will be removed. Also, the observers will be notified in no specific
|
|
||||||
* order.
|
|
||||||
*
|
|
||||||
* To prevent memory leaks, the observer must be removed if it isn't used anymore.
|
|
||||||
*/
|
|
||||||
fun addObserver(key: Any, observer: (T) -> Unit) {
|
|
||||||
observers[key] = observer
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the observer that was registered with the given key.
|
|
||||||
*/
|
|
||||||
fun removeObserver(key: Any) {
|
|
||||||
observers.remove(key)
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,127 +17,67 @@
|
|||||||
package ch.dissem.apps.abit.util
|
package ch.dissem.apps.abit.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import ch.dissem.apps.abit.R
|
import android.content.Intent
|
||||||
import ch.dissem.apps.abit.notification.ErrorNotification
|
import android.content.IntentFilter
|
||||||
|
import android.os.BatteryManager
|
||||||
|
import android.os.Build
|
||||||
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
import ch.dissem.apps.abit.util.Constants.PREFERENCE_EMULATE_CONVERSATIONS
|
import ch.dissem.apps.abit.util.Constants.PREFERENCE_EMULATE_CONVERSATIONS
|
||||||
import ch.dissem.apps.abit.util.Constants.PREFERENCE_FULL_NODE
|
import ch.dissem.apps.abit.util.Constants.PREFERENCE_ONLINE
|
||||||
import ch.dissem.apps.abit.util.Constants.PREFERENCE_REQUEST_ACK
|
import ch.dissem.apps.abit.util.Constants.PREFERENCE_REQUEST_ACK
|
||||||
import ch.dissem.apps.abit.util.Constants.PREFERENCE_REQUIRE_CHARGING
|
import ch.dissem.apps.abit.util.Constants.PREFERENCE_REQUIRE_CHARGING
|
||||||
import ch.dissem.apps.abit.util.Constants.PREFERENCE_SYNC_TIMEOUT
|
import ch.dissem.apps.abit.util.Constants.PREFERENCE_SEPARATE_IDENTITIES
|
||||||
import ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE
|
|
||||||
import ch.dissem.apps.abit.util.Constants.PREFERENCE_WIFI_ONLY
|
import ch.dissem.apps.abit.util.Constants.PREFERENCE_WIFI_ONLY
|
||||||
import org.jetbrains.anko.batteryManager
|
import org.jetbrains.anko.batteryManager
|
||||||
import org.jetbrains.anko.connectivityManager
|
import org.jetbrains.anko.connectivityManager
|
||||||
import org.jetbrains.anko.defaultSharedPreferences
|
import org.jetbrains.anko.defaultSharedPreferences
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.lang.ref.WeakReference
|
||||||
import java.net.InetAddress
|
|
||||||
import android.os.BatteryManager
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.IntentFilter
|
|
||||||
import android.os.Build
|
|
||||||
|
|
||||||
|
|
||||||
|
val Context.preferences get() = Preferences.getInstance(this)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Christian Basler
|
* @author Christian Basler
|
||||||
*/
|
*/
|
||||||
object Preferences {
|
class Preferences internal constructor(private val ctx: Context) {
|
||||||
private val LOG = LoggerFactory.getLogger(Preferences::class.java)
|
private val LOG = LoggerFactory.getLogger(Preferences::class.java)
|
||||||
|
|
||||||
fun useTrustedNode(ctx: Context): Boolean {
|
val connectionAllowed get() = isAllowedForWiFi && isAllowedForCharging
|
||||||
val trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE) ?: return false
|
|
||||||
return trustedNode.trim { it <= ' ' }.isNotEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
private val isAllowedForWiFi get() = !wifiOnly || !ctx.connectivityManager.isActiveNetworkMetered
|
||||||
* Warning, this method might do a network call and therefore can't be called from
|
|
||||||
* the UI thread.
|
|
||||||
*/
|
|
||||||
@Throws(IOException::class)
|
|
||||||
fun getTrustedNode(ctx: Context): InetAddress? {
|
|
||||||
var trustedNode: String = getPreference(ctx, PREFERENCE_TRUSTED_NODE) ?: return null
|
|
||||||
trustedNode = trustedNode.trim { it <= ' ' }
|
|
||||||
if (trustedNode.isEmpty()) return null
|
|
||||||
|
|
||||||
if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$".toRegex())) {
|
private val isAllowedForCharging get() = !requireCharging || isCharging
|
||||||
val index = trustedNode.lastIndexOf(':')
|
|
||||||
trustedNode = trustedNode.substring(0, index)
|
private val sharedPreferences = ctx.defaultSharedPreferences
|
||||||
|
|
||||||
|
private val isCharging
|
||||||
|
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
ctx.batteryManager.isCharging
|
||||||
|
} else {
|
||||||
|
val intent = ctx.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
|
||||||
|
val status = intent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
|
||||||
|
status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL
|
||||||
}
|
}
|
||||||
return InetAddress.getByName(trustedNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getTrustedNodePort(ctx: Context): Int {
|
var wifiOnly
|
||||||
var trustedNode: String = getPreference(ctx, PREFERENCE_TRUSTED_NODE) ?: return 8444
|
get() = sharedPreferences.getBoolean(PREFERENCE_WIFI_ONLY, true)
|
||||||
trustedNode = trustedNode.trim { it <= ' ' }
|
set(value) {
|
||||||
|
sharedPreferences.edit()
|
||||||
if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$".toRegex())) {
|
.putBoolean(PREFERENCE_WIFI_ONLY, value)
|
||||||
val index = trustedNode.lastIndexOf(':')
|
.apply()
|
||||||
val portString = trustedNode.substring(index + 1)
|
|
||||||
try {
|
|
||||||
return Integer.parseInt(portString)
|
|
||||||
} catch (e: NumberFormatException) {
|
|
||||||
ErrorNotification(ctx)
|
|
||||||
.setError(R.string.error_invalid_sync_port, portString)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return 8444
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getTimeoutInSeconds(ctx: Context): Long = getPreference(ctx, PREFERENCE_SYNC_TIMEOUT)?.toLong() ?: 120
|
val requireCharging get() = sharedPreferences.getBoolean(PREFERENCE_REQUIRE_CHARGING, true)
|
||||||
|
|
||||||
private fun getPreference(ctx: Context, name: String): String? = ctx.defaultSharedPreferences.getString(name, null)
|
val emulateConversations get() = sharedPreferences.getBoolean(PREFERENCE_EMULATE_CONVERSATIONS, true)
|
||||||
|
|
||||||
fun isConnectionAllowed(ctx: Context) = isAllowedForWiFi(ctx) && isAllowedForCharging(ctx)
|
val exportDirectory by lazy { File(ctx.filesDir, "exports") }
|
||||||
|
|
||||||
private fun isAllowedForWiFi(ctx: Context) = !isWifiOnly(ctx) || !ctx.connectivityManager.isActiveNetworkMetered
|
val requestAcknowledgements = sharedPreferences.getBoolean(PREFERENCE_REQUEST_ACK, true)
|
||||||
|
|
||||||
private fun isAllowedForCharging(ctx: Context) = !requireCharging(ctx) || isCharging(ctx)
|
fun cleanupExportDirectory() {
|
||||||
|
|
||||||
private fun isCharging(ctx: Context) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
ctx.batteryManager.isCharging
|
|
||||||
} else {
|
|
||||||
val intent = ctx.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
|
|
||||||
val status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
|
|
||||||
status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isWifiOnly(ctx: Context) = ctx.defaultSharedPreferences.getBoolean(PREFERENCE_WIFI_ONLY, true)
|
|
||||||
|
|
||||||
fun setWifiOnly(ctx: Context, status: Boolean) {
|
|
||||||
ctx.defaultSharedPreferences.edit()
|
|
||||||
.putBoolean(PREFERENCE_WIFI_ONLY, status)
|
|
||||||
.apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun requireCharging(ctx: Context) = ctx.defaultSharedPreferences.getBoolean(PREFERENCE_REQUIRE_CHARGING, true)
|
|
||||||
|
|
||||||
fun setRequireCharging(ctx: Context, status: Boolean) {
|
|
||||||
ctx.defaultSharedPreferences.edit()
|
|
||||||
.putBoolean(PREFERENCE_REQUIRE_CHARGING, status)
|
|
||||||
.apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isEmulateConversations(ctx: Context) =
|
|
||||||
ctx.defaultSharedPreferences.getBoolean(PREFERENCE_EMULATE_CONVERSATIONS, true)
|
|
||||||
|
|
||||||
|
|
||||||
fun isFullNodeActive(ctx: Context) =
|
|
||||||
ctx.defaultSharedPreferences.getBoolean(PREFERENCE_FULL_NODE, false)
|
|
||||||
|
|
||||||
fun setFullNodeActive(ctx: Context, status: Boolean) {
|
|
||||||
ctx.defaultSharedPreferences.edit()
|
|
||||||
.putBoolean(PREFERENCE_FULL_NODE, status)
|
|
||||||
.apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getExportDirectory(ctx: Context) = File(ctx.filesDir, "exports")
|
|
||||||
|
|
||||||
fun requestAcknowledgements(ctx: Context) = ctx.defaultSharedPreferences.getBoolean(PREFERENCE_REQUEST_ACK, true)
|
|
||||||
|
|
||||||
fun cleanupExportDirectory(ctx: Context) {
|
|
||||||
val exportDirectory = getExportDirectory(ctx)
|
|
||||||
if (exportDirectory.exists()) {
|
if (exportDirectory.exists()) {
|
||||||
exportDirectory.listFiles().forEach { file ->
|
exportDirectory.listFiles().forEach { file ->
|
||||||
try {
|
try {
|
||||||
@ -150,4 +90,40 @@ object Preferences {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var online
|
||||||
|
get() = sharedPreferences.getBoolean(PREFERENCE_ONLINE, true)
|
||||||
|
set(value) {
|
||||||
|
sharedPreferences.edit()
|
||||||
|
.putBoolean(PREFERENCE_ONLINE, value)
|
||||||
|
.apply()
|
||||||
|
if (value) {
|
||||||
|
ctx.network.enableNode(true)
|
||||||
|
} else {
|
||||||
|
ctx.network.disableNode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val separateIdentities
|
||||||
|
get() = sharedPreferences.getBoolean(PREFERENCE_SEPARATE_IDENTITIES, false)
|
||||||
|
|
||||||
|
val currentIdentity
|
||||||
|
get() = Singleton.getIdentity(ctx)
|
||||||
|
|
||||||
|
val listeningPort
|
||||||
|
get() = sharedPreferences.getString("listening_port", null)?.toIntOrNull()
|
||||||
|
?: 8444
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var instance: WeakReference<Preferences>? = null
|
||||||
|
|
||||||
|
internal fun getInstance(ctx: Context): Preferences {
|
||||||
|
var prefs = instance?.get()
|
||||||
|
if (prefs == null) {
|
||||||
|
prefs = Preferences(ctx.applicationContext)
|
||||||
|
instance = WeakReference(prefs)
|
||||||
|
}
|
||||||
|
return prefs
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
13
app/src/main/res/drawable/ic_battery_charging.xml
Normal file
13
app/src/main/res/drawable/ic_battery_charging.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V8h5.47L13,7v1h4V5.33C17,4.6 16.4,4 15.67,4z"
|
||||||
|
android:fillAlpha=".3"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M13,12.5h2L11,20v-5.5H9L12.47,8H7v12.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V8h-4v4.5z"/>
|
||||||
|
</vector>
|
8
app/src/main/res/drawable/ic_broom.xml
Normal file
8
app/src/main/res/drawable/ic_broom.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/broom.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="#000" android:pathData="M19.36,2.72L20.78,4.14L15.06,9.85C16.13,11.39 16.28,13.24 15.38,14.44L9.06,8.12C10.26,7.22 12.11,7.37 13.65,8.44L19.36,2.72M5.93,17.57C3.92,15.56 2.69,13.16 2.35,10.92L7.23,8.83L14.67,16.27L12.58,21.15C10.34,20.81 7.94,19.58 5.93,17.57Z" />
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_bug_report.xml
Normal file
9
app/src/main/res/drawable/ic_bug_report.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z"/>
|
||||||
|
</vector>
|
8
app/src/main/res/drawable/ic_check_all.xml
Normal file
8
app/src/main/res/drawable/ic_check_all.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/check_all.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="#000" android:pathData="M0.41,13.41L6,19L7.41,17.58L1.83,12M22.24,5.58L11.66,16.17L7.5,12L6.07,13.41L11.66,19L23.66,7M18,7L16.59,5.58L10.24,11.93L11.66,13.34L18,7Z" />
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_emulate_conversations.xml
Normal file
9
app/src/main/res/drawable/ic_emulate_conversations.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM8,14L6,14v-2h2v2zM8,11L6,11L6,9h2v2zM8,8L6,8L6,6h2v2zM15,14h-5v-2h5v2zM18,11h-8L10,9h8v2zM18,8h-8L10,6h8v2z"/>
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_export.xml
Normal file
9
app/src/main/res/drawable/ic_export.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M9,16h6v-6h4l-7,-7 -7,7h4zM5,18h14v2L5,20z"/>
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_import.xml
Normal file
9
app/src/main/res/drawable/ic_import.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/>
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_info.xml
Normal file
9
app/src/main/res/drawable/ic_info.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/>
|
||||||
|
</vector>
|
13
app/src/main/res/drawable/ic_network_wifi.xml
Normal file
13
app/src/main/res/drawable/ic_network_wifi.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M12.01,21.49L23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4C5.28,3 0.81,6.66 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01z"
|
||||||
|
android:fillAlpha=".3"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M3.53,10.95l8.46,10.54 0.01,0.01 0.01,-0.01 8.46,-10.54C20.04,10.62 16.81,8 12,8c-4.81,0 -8.04,2.62 -8.47,2.95z"/>
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_port.xml
Normal file
9
app/src/main/res/drawable/ic_port.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M7.77,6.76L6.23,5.48 0.82,12l5.41,6.52 1.54,-1.28L3.42,12l4.35,-5.24zM7,13h2v-2L7,11v2zM17,11h-2v2h2v-2zM11,13h2v-2h-2v2zM17.77,5.48l-1.54,1.28L20.58,12l-4.35,5.24 1.54,1.28L23.18,12l-5.41,-6.52z"/>
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_separate_identities.xml
Normal file
9
app/src/main/res/drawable/ic_separate_identities.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M16,11c1.66,0 2.99,-1.34 2.99,-3S17.66,5 16,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zM8,11c1.66,0 2.99,-1.34 2.99,-3S9.66,5 8,5C6.34,5 5,6.34 5,8s1.34,3 3,3zM8,13c-2.33,0 -7,1.17 -7,3.5L1,19h14v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5zM16,13c-0.29,0 -0.62,0.02 -0.97,0.05 1.16,0.84 1.97,1.97 1.97,3.45L17,19h6v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5z"/>
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_support_app.xml
Normal file
9
app/src/main/res/drawable/ic_support_app.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M9,11.24L9,7.5C9,6.12 10.12,5 11.5,5S14,6.12 14,7.5v3.74c1.21,-0.81 2,-2.18 2,-3.74C16,5.01 13.99,3 11.5,3S7,5.01 7,7.5c0,1.56 0.79,2.93 2,3.74zM18.84,15.87l-4.54,-2.26c-0.17,-0.07 -0.35,-0.11 -0.54,-0.11L13,13.5v-6c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,6.67 10,7.5v10.74l-3.43,-0.72c-0.08,-0.01 -0.15,-0.03 -0.24,-0.03 -0.31,0 -0.59,0.13 -0.79,0.33l-0.79,0.8 4.94,4.94c0.27,0.27 0.65,0.44 1.06,0.44h6.79c0.75,0 1.33,-0.55 1.44,-1.28l0.75,-5.27c0.01,-0.07 0.02,-0.14 0.02,-0.2 0,-0.62 -0.38,-1.16 -0.91,-1.38z"/>
|
||||||
|
</vector>
|
@ -1,22 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
Copyright (C) 2015 Haruki Hasegawa
|
|
||||||
|
|
||||||
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.
|
|
||||||
-->
|
|
||||||
<shape
|
|
||||||
android:shape="rectangle"
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<size android:height="1px"/>
|
|
||||||
<solid android:color="@color/divider"/>
|
|
||||||
</shape>
|
|
@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:color="#ffffff">
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@android:id/mask"
|
|
||||||
android:drawable="@android:color/white"/>
|
|
||||||
|
|
||||||
</ripple>
|
|
@ -1,11 +1,11 @@
|
|||||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:gravity="center">
|
android:gravity="center">
|
||||||
|
|
||||||
<android.support.v7.widget.Toolbar
|
<androidx.appcompat.widget.Toolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="64dp"
|
android:layout_height="64dp"
|
||||||
@ -19,7 +19,7 @@
|
|||||||
tools:ignore="UnusedAttribute"
|
tools:ignore="UnusedAttribute"
|
||||||
tools:layout_editor_absoluteX="0dp" />
|
tools:layout_editor_absoluteX="0dp" />
|
||||||
|
|
||||||
<android.support.constraint.Guideline
|
<androidx.constraintlayout.widget.Guideline
|
||||||
android:id="@+id/guideline"
|
android:id="@+id/guideline"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -74,4 +74,4 @@
|
|||||||
tools:ignore="UnusedAttribute"
|
tools:ignore="UnusedAttribute"
|
||||||
tools:layout_editor_absoluteX="8dp" />
|
tools:layout_editor_absoluteX="8dp" />
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="24dp">
|
android:padding="24dp">
|
||||||
|
|
||||||
<android.support.design.widget.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/address_wrapper"
|
android:id="@+id/address_wrapper"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -17,16 +17,17 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/address"
|
android:hint="@string/address"
|
||||||
|
android:importantForAutofill="no"
|
||||||
android:inputType="textNoSuggestions" />
|
android:inputType="textNoSuggestions" />
|
||||||
|
|
||||||
</android.support.design.widget.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<android.support.design.widget.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/label_wrapper"
|
android:id="@+id/label_wrapper"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignStart="@+id/address_wrapper"
|
|
||||||
android:layout_below="@+id/address_wrapper"
|
android:layout_below="@+id/address_wrapper"
|
||||||
|
android:layout_alignStart="@+id/address_wrapper"
|
||||||
android:layout_marginTop="16dp">
|
android:layout_marginTop="16dp">
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
@ -34,18 +35,19 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/label"
|
android:hint="@string/label"
|
||||||
|
android:importantForAutofill="no"
|
||||||
android:inputType="textPersonName" />
|
android:inputType="textPersonName" />
|
||||||
|
|
||||||
</android.support.design.widget.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<Switch
|
<Switch
|
||||||
android:id="@+id/subscribe"
|
android:id="@+id/subscribe"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignStart="@+id/address_wrapper"
|
|
||||||
android:layout_below="@+id/label_wrapper"
|
android:layout_below="@+id/label_wrapper"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_alignStart="@+id/address_wrapper"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
android:text="@string/subscribe" />
|
android:text="@string/subscribe" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@ -53,10 +55,10 @@
|
|||||||
style="?android:attr/borderlessButtonStyle"
|
style="?android:attr/borderlessButtonStyle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_below="@+id/subscribe"
|
android:layout_below="@+id/subscribe"
|
||||||
android:layout_marginBottom="12dp"
|
android:layout_alignParentEnd="true"
|
||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
android:text="@string/do_import" />
|
android:text="@string/do_import" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:gravity="center">
|
android:gravity="center">
|
||||||
|
|
||||||
<android.support.v7.widget.Toolbar
|
<androidx.appcompat.widget.Toolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
android:textSize="10dp"
|
android:textSize="10dp"
|
||||||
tools:ignore="SpUsage" />
|
tools:ignore="SpUsage" />
|
||||||
|
|
||||||
<android.support.design.widget.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/label_wrapper"
|
android:id="@+id/label_wrapper"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -27,9 +27,10 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/label"
|
android:hint="@string/label"
|
||||||
|
android:importantForAutofill="no"
|
||||||
android:inputType="textPersonName" />
|
android:inputType="textPersonName" />
|
||||||
|
|
||||||
</android.support.design.widget.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<Switch
|
<Switch
|
||||||
android:id="@+id/subscribe"
|
android:id="@+id/subscribe"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<android.support.design.widget.CoordinatorLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
@ -19,11 +19,11 @@
|
|||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
</TextView>
|
</TextView>
|
||||||
|
|
||||||
<android.support.design.widget.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<android.support.v7.widget.Toolbar
|
<androidx.appcompat.widget.Toolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
@ -36,6 +36,6 @@
|
|||||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
|
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
|
||||||
tools:ignore="UnusedAttribute"/>
|
tools:ignore="UnusedAttribute"/>
|
||||||
|
|
||||||
</android.support.design.widget.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
</android.support.design.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
@ -14,15 +14,15 @@
|
|||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:paddingBottom="18dp"
|
|
||||||
android:paddingEnd="24dp"
|
|
||||||
android:paddingStart="24dp"
|
android:paddingStart="24dp"
|
||||||
android:paddingTop="18dp">
|
android:paddingTop="18dp"
|
||||||
|
android:paddingEnd="24dp"
|
||||||
|
android:paddingBottom="18dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/description"
|
android:id="@+id/description"
|
||||||
@ -34,7 +34,7 @@
|
|||||||
tools:layout_constraintLeft_creator="1"
|
tools:layout_constraintLeft_creator="1"
|
||||||
tools:layout_constraintTop_creator="1" />
|
tools:layout_constraintTop_creator="1" />
|
||||||
|
|
||||||
<android.support.design.widget.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/label_wrapper"
|
android:id="@+id/label_wrapper"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -47,11 +47,12 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/label"
|
android:hint="@string/label"
|
||||||
|
android:autofillHints="label"
|
||||||
android:inputType="text" />
|
android:inputType="text" />
|
||||||
|
|
||||||
</android.support.design.widget.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<android.support.design.widget.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/passphrase_wrapper"
|
android:id="@+id/passphrase_wrapper"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -63,11 +64,12 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/passphrase"
|
android:hint="@string/passphrase"
|
||||||
|
android:autofillHints="passphrase"
|
||||||
android:inputType="textMultiLine" />
|
android:inputType="textMultiLine" />
|
||||||
|
|
||||||
</android.support.design.widget.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<android.support.design.widget.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/number_of_identities_wrapper"
|
android:id="@+id/number_of_identities_wrapper"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -80,11 +82,12 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:ems="10"
|
android:ems="10"
|
||||||
android:hint="@string/number_of_identities"
|
android:hint="@string/number_of_identities"
|
||||||
|
android:autofillHints="numberOfIdentities"
|
||||||
android:inputType="number"
|
android:inputType="number"
|
||||||
android:text="1"
|
android:text="1"
|
||||||
tools:ignore="HardcodedText" />
|
tools:ignore="HardcodedText" />
|
||||||
|
|
||||||
</android.support.design.widget.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<Switch
|
<Switch
|
||||||
android:id="@+id/shorter"
|
android:id="@+id/shorter"
|
||||||
@ -115,4 +118,4 @@
|
|||||||
app:layout_constraintBottom_toBottomOf="@id/ok"
|
app:layout_constraintBottom_toBottomOf="@id/ok"
|
||||||
app:layout_constraintRight_toLeftOf="@id/ok" />
|
app:layout_constraintRight_toLeftOf="@id/ok" />
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<android.support.constraint.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
@ -98,4 +98,4 @@
|
|||||||
android:textColor="@color/colorAccent"
|
android:textColor="@color/colorAccent"
|
||||||
app:layout_constraintRight_toLeftOf="@+id/ok"
|
app:layout_constraintRight_toLeftOf="@+id/ok"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/radioGroup"/>
|
app:layout_constraintTop_toBottomOf="@+id/radioGroup"/>
|
||||||
</android.support.constraint.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<android.support.constraint.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
@ -56,4 +56,4 @@
|
|||||||
app:layout_constraintEnd_toEndOf="@id/description"
|
app:layout_constraintEnd_toEndOf="@id/description"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/ok"
|
app:layout_constraintTop_toBottomOf="@+id/ok"
|
||||||
tools:layout_editor_absoluteX="8dp"/>
|
tools:layout_editor_absoluteX="8dp"/>
|
||||||
</android.support.constraint.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
@ -14,5 +14,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal"
|
android:layout_gravity="center_horizontal"
|
||||||
android:hint="@string/passphrase"
|
android:hint="@string/passphrase"
|
||||||
|
android:autofillHints="passphrase"
|
||||||
android:inputType="textMultiLine"/>
|
android:inputType="textMultiLine"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
<!--
|
|
||||||
~ Copyright 2015 Christian Basler
|
~ Copyright 2015 Christian Basler
|
||||||
~
|
~
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -16,9 +15,9 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/avatar"
|
android:id="@+id/avatar"
|
||||||
@ -28,19 +27,20 @@
|
|||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentTop="true"
|
||||||
android:layout_margin="16dp"
|
android:layout_margin="16dp"
|
||||||
android:src="@color/colorAccent"
|
android:src="@color/colorAccent"
|
||||||
tools:ignore="ContentDescription"/>
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/name"
|
android:id="@+id/name"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_alignTop="@+id/avatar"
|
android:layout_alignTop="@+id/avatar"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_toEndOf="@+id/avatar"
|
android:layout_toEndOf="@+id/avatar"
|
||||||
|
android:importantForAutofill="no"
|
||||||
android:inputType="textPersonName"
|
android:inputType="textPersonName"
|
||||||
android:text=""
|
android:text=""
|
||||||
tools:ignore="LabelFor"/>
|
tools:ignore="LabelFor" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/address"
|
android:id="@+id/address"
|
||||||
@ -52,7 +52,7 @@
|
|||||||
android:paddingRight="16dp"
|
android:paddingRight="16dp"
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
tools:text="BM-XyYxXyYxXyYxXyYxXyYx"/>
|
tools:text="BM-XyYxXyYxXyYxXyYxXyYx" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/stream_number"
|
android:id="@+id/stream_number"
|
||||||
@ -64,7 +64,7 @@
|
|||||||
android:paddingLeft="16dp"
|
android:paddingLeft="16dp"
|
||||||
android:paddingRight="16dp"
|
android:paddingRight="16dp"
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
tools:text="Stream #"/>
|
tools:text="Stream #" />
|
||||||
|
|
||||||
<Switch
|
<Switch
|
||||||
android:id="@+id/active"
|
android:id="@+id/active"
|
||||||
@ -72,21 +72,21 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/stream_number"
|
android:layout_below="@+id/stream_number"
|
||||||
android:paddingLeft="16dp"
|
android:paddingLeft="16dp"
|
||||||
android:paddingRight="16dp"
|
|
||||||
android:paddingTop="16dp"
|
android:paddingTop="16dp"
|
||||||
android:text="@string/subscribed"/>
|
android:paddingRight="16dp"
|
||||||
|
android:text="@string/subscribed" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/pubkey_available"
|
android:id="@+id/pubkey_available"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_below="@+id/active"
|
android:layout_below="@+id/active"
|
||||||
android:paddingEnd="4dp"
|
android:layout_alignParentStart="true"
|
||||||
android:paddingStart="16dp"
|
android:paddingStart="16dp"
|
||||||
android:paddingTop="16dp"
|
android:paddingTop="16dp"
|
||||||
|
android:paddingEnd="4dp"
|
||||||
android:src="@drawable/public_key"
|
android:src="@drawable/public_key"
|
||||||
tools:ignore="ContentDescription"/>
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/pubkey_available_desc"
|
android:id="@+id/pubkey_available_desc"
|
||||||
@ -95,25 +95,25 @@
|
|||||||
android:layout_alignBottom="@id/pubkey_available"
|
android:layout_alignBottom="@id/pubkey_available"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
android:layout_toEndOf="@id/pubkey_available"
|
android:layout_toEndOf="@id/pubkey_available"
|
||||||
android:paddingEnd="16dp"
|
|
||||||
android:paddingStart="0dp"
|
android:paddingStart="0dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
android:text="@string/pubkey_available"
|
android:text="@string/pubkey_available"
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"/>
|
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/qr_code"
|
android:id="@+id/qr_code"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_alignParentBottom="true"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_below="@+id/pubkey_available"
|
android:layout_below="@+id/pubkey_available"
|
||||||
android:layout_marginBottom="64dp"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginTop="24dp"
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="64dp"
|
||||||
android:contentDescription="@string/alt_qr_code"
|
android:contentDescription="@string/alt_qr_code"
|
||||||
android:elevation="2dp"
|
android:elevation="2dp"
|
||||||
tools:ignore="UnusedAttribute"
|
tools:ignore="UnusedAttribute"
|
||||||
tools:src="@drawable/public_key"/>
|
tools:src="@drawable/public_key" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<ListView
|
<ListView
|
||||||
android:id="@id/android:list"
|
android:id="@android:id/list"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
<android.support.design.widget.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingTop="4dp">
|
android:paddingTop="4dp">
|
||||||
@ -33,13 +33,14 @@
|
|||||||
android:id="@+id/recipient_input"
|
android:id="@+id/recipient_input"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:autofillHints="bitmessageAddress"
|
||||||
android:hint="@string/to"
|
android:hint="@string/to"
|
||||||
android:inputType="textNoSuggestions"
|
android:inputType="textNoSuggestions"
|
||||||
android:maxLines="1" />
|
android:maxLines="1" />
|
||||||
|
|
||||||
</android.support.design.widget.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<android.support.design.widget.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
@ -47,17 +48,19 @@
|
|||||||
android:id="@+id/subject_input"
|
android:id="@+id/subject_input"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:autofillHints="subject"
|
||||||
android:hint="@string/subject"
|
android:hint="@string/subject"
|
||||||
android:inputType="textEmailSubject"
|
android:inputType="textEmailSubject"
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
android:textAppearance="?android:attr/textAppearanceLarge" />
|
||||||
|
|
||||||
</android.support.design.widget.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/body_input"
|
android:id="@+id/body_input"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
android:autofillHints="body, message"
|
||||||
android:gravity="start|top"
|
android:gravity="start|top"
|
||||||
android:hint="@string/compose_body_hint"
|
android:hint="@string/compose_body_hint"
|
||||||
android:inputType="textMultiLine|textCapSentences"
|
android:inputType="textMultiLine|textCapSentences"
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
android:layout_below="@id/subject"
|
android:layout_below="@id/subject"
|
||||||
android:background="@color/divider" />
|
android:background="@color/divider" />
|
||||||
|
|
||||||
<android.support.v7.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/messages"
|
android:id="@+id/messages"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<android.support.constraint.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -41,6 +41,7 @@
|
|||||||
android:gravity="start|top"
|
android:gravity="start|top"
|
||||||
android:hint="@string/wif_string"
|
android:hint="@string/wif_string"
|
||||||
android:inputType="textMultiLine|text"
|
android:inputType="textMultiLine|text"
|
||||||
|
android:autofillHints="wif, comment, data"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/next"
|
app:layout_constraintBottom_toTopOf="@+id/next"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@ -57,4 +58,4 @@
|
|||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"/>
|
app:layout_constraintRight_toRightOf="parent"/>
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<android.support.constraint.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -33,7 +33,7 @@
|
|||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"/>
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
<android.support.v7.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/recycler_view"
|
android:id="@+id/recycler_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
@ -55,4 +55,4 @@
|
|||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"/>
|
app:layout_constraintRight_toRightOf="parent"/>
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
@ -78,7 +78,7 @@
|
|||||||
android:paddingRight="8dp"
|
android:paddingRight="8dp"
|
||||||
tools:text="Recipient" />
|
tools:text="Recipient" />
|
||||||
|
|
||||||
<android.support.v7.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/parents"
|
android:id="@+id/parents"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -99,7 +99,7 @@
|
|||||||
tools:text="Message Body"
|
tools:text="Message Body"
|
||||||
android:textIsSelectable="true" />
|
android:textIsSelectable="true" />
|
||||||
|
|
||||||
<android.support.v7.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/labels"
|
android:id="@+id/labels"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -108,7 +108,7 @@
|
|||||||
android:layout_marginRight="16dp"
|
android:layout_marginRight="16dp"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
<android.support.v7.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/responses"
|
android:id="@+id/responses"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<android.support.v7.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/recycler_view"
|
android:id="@+id/recycler_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
|
@ -88,7 +88,7 @@
|
|||||||
android:textIsSelectable="true"
|
android:textIsSelectable="true"
|
||||||
tools:text="Message Body" />
|
tools:text="Message Body" />
|
||||||
|
|
||||||
<android.support.v7.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/labels"
|
android:id="@+id/labels"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -16,11 +16,11 @@
|
|||||||
tools:context=".ComposeMessageActivity"
|
tools:context=".ComposeMessageActivity"
|
||||||
tools:layout="@layout/fragment_compose_message"/>
|
tools:layout="@layout/fragment_compose_message"/>
|
||||||
|
|
||||||
<android.support.design.widget.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<android.support.v7.widget.Toolbar
|
<androidx.appcompat.widget.Toolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
@ -30,7 +30,7 @@
|
|||||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
|
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
|
||||||
tools:ignore="UnusedAttribute"/>
|
tools:ignore="UnusedAttribute"/>
|
||||||
|
|
||||||
</android.support.design.widget.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
</android.support.design.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -16,11 +16,11 @@
|
|||||||
tools:context=".ComposeMessageActivity"
|
tools:context=".ComposeMessageActivity"
|
||||||
tools:layout="@layout/fragment_compose_message"/>
|
tools:layout="@layout/fragment_compose_message"/>
|
||||||
|
|
||||||
<android.support.design.widget.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<android.support.v7.widget.Toolbar
|
<androidx.appcompat.widget.Toolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
@ -31,7 +31,7 @@
|
|||||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
|
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
|
||||||
tools:ignore="UnusedAttribute"/>
|
tools:ignore="UnusedAttribute"/>
|
||||||
|
|
||||||
</android.support.design.widget.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
</android.support.design.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
<item
|
<item
|
||||||
android:id="@+id/empty_trash"
|
android:id="@+id/empty_trash"
|
||||||
app:showAsAction="never"
|
android:icon="@drawable/ic_action_delete"
|
||||||
android:icon="@drawable/ic_action_delete"
|
android:title="@string/empty_trash"
|
||||||
android:title="@string/empty_trash"
|
android:visible="false"
|
||||||
android:visible="false"/>
|
app:showAsAction="never" />
|
||||||
</menu>
|
<item
|
||||||
|
android:id="@+id/delete_all"
|
||||||
|
android:icon="@drawable/ic_action_delete"
|
||||||
|
android:title="@string/delete_all_messages_in_list"
|
||||||
|
android:visible="true"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
</menu>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user