Move sync classes to uhabits-core

pull/699/head
Alinson S. Xavier 5 years ago
parent 1e94456a74
commit 017bc50698

@ -33,45 +33,37 @@ import org.isoron.androidbase.activities.*
import org.isoron.androidbase.utils.*
import org.isoron.androidbase.utils.InterfaceUtils.getFontAwesome
import org.isoron.uhabits.*
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.tasks.*
import org.isoron.uhabits.core.ui.screens.sync.*
import org.isoron.uhabits.databinding.*
import org.isoron.uhabits.sync.*
import org.isoron.uhabits.utils.*
class SyncActivity : BaseActivity() {
class SyncActivity : BaseActivity(), SyncBehavior.Screen {
private lateinit var syncManager: SyncManager
private lateinit var preferences: Preferences
private lateinit var taskRunner: TaskRunner
private lateinit var baseScreen: BaseScreen
private lateinit var binding: ActivitySyncBinding
private lateinit var behavior: SyncBehavior
private val scope = CoroutineScope(Dispatchers.Main)
private var styledResources = StyledResources(this)
override fun onCreate(state: Bundle?) {
super.onCreate(state)
baseScreen = BaseScreen(this)
val component = (application as HabitsApplication).component
taskRunner = component.taskRunner
preferences = component.preferences
syncManager = component.syncManager
val preferences = component.preferences
val server = RemoteSyncServer(baseURL = preferences.syncBaseURL)
baseScreen = BaseScreen(this)
behavior = SyncBehavior(this, preferences, server)
binding = ActivitySyncBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.errorIcon.typeface = getFontAwesome(this)
setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
supportActionBar?.elevation = 10.0f
binding.instructions.setText(Html.fromHtml(resources.getString(R.string.sync_instructions)))
binding.syncLink.setOnClickListener {
copyToClipboard()
}
@ -79,96 +71,62 @@ class SyncActivity : BaseActivity() {
override fun onResume() {
super.onResume()
if(preferences.syncKey.isBlank()) {
register()
} else {
displayCurrentKey()
scope.launch {
behavior.onResume()
}
}
private fun displayCurrentKey() {
displayLink("https://loophabits.org/sync/${preferences.syncKey}#${preferences.encryptionKey}")
private fun copyToClipboard() {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setPrimaryClip(ClipData.newPlainText("Loop Sync Link", binding.syncLink.text))
baseScreen.showMessage(R.string.copied_to_the_clipboard, binding.root)
}
private fun register() {
displayLoading()
taskRunner.execute(object : Task {
private lateinit var encKey: EncryptionKey
private lateinit var syncKey: String
private var error = false
override fun doInBackground() {
runBlocking {
try {
val server = RemoteSyncServer(baseURL = preferences.syncBaseURL)
syncKey = server.register()
encKey = EncryptionKey.generate()
preferences.enableSync(syncKey, encKey.base64)
} catch (e: Exception) {
Log.e("SyncActivity", "Unexpected exception", e)
error = true
}
}
suspend fun generateQR(msg: String): Bitmap = Dispatchers.IO {
val writer = QRCodeWriter()
val matrix = writer.encode(msg, BarcodeFormat.QR_CODE, 1024, 1024)
val height = matrix.height
val width = matrix.width
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
val bgColor = styledResources.getColor(R.attr.highContrastReverseTextColor)
val fgColor = styledResources.getColor(R.attr.highContrastTextColor)
for (x in 0 until width) {
for (y in 0 until height) {
val color = if (matrix.get(x, y)) fgColor else bgColor
bitmap.setPixel(x, y, color)
}
}
return@IO bitmap
}
suspend fun showQR(msg: String) {
binding.progress.visibility = View.GONE
binding.qrCode.visibility = View.VISIBLE
binding.qrCode.setImageBitmap(generateQR(msg))
override fun onPostExecute() {
if (error) {
displayError()
return;
}
displayCurrentKey()
}
})
}
private fun displayLoading() {
override suspend fun showLoadingScreen() {
binding.qrCode.visibility = View.GONE
binding.progress.visibility = View.VISIBLE
binding.errorPanel.visibility = View.GONE
}
private fun displayError() {
override suspend fun showErrorScreen() {
binding.qrCode.visibility = View.GONE
binding.progress.visibility = View.GONE
binding.errorPanel.visibility = View.VISIBLE
}
private fun copyToClipboard() {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setPrimaryClip(ClipData.newPlainText("Loop Sync Link", binding.syncLink.text))
baseScreen.showMessage(R.string.copied_to_the_clipboard, binding.root)
}
private fun displayLink(link: String) {
override suspend fun showLink(link: String) {
binding.qrCode.visibility = View.GONE
binding.progress.visibility = View.VISIBLE
binding.errorPanel.visibility = View.GONE
binding.syncLink.text = link
displayQR(link)
showQR(link)
}
private fun displayQR(msg: String) {
taskRunner.execute(object : Task {
lateinit var bitmap: Bitmap
override fun doInBackground() {
val writer = QRCodeWriter()
val matrix = writer.encode(msg, BarcodeFormat.QR_CODE, 1024, 1024)
val height = matrix.height
val width = matrix.width
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
val bgColor = styledResources.getColor(R.attr.highContrastReverseTextColor)
val fgColor = styledResources.getColor(R.attr.highContrastTextColor)
for (x in 0 until width) {
for (y in 0 until height) {
val color = if (matrix.get(x, y)) fgColor else bgColor
bitmap.setPixel(x, y, color)
}
}
}
override fun onPostExecute() {
binding.progress.visibility = View.GONE
binding.qrCode.visibility = View.VISIBLE
binding.qrCode.setImageBitmap(bitmap)
}
})
override fun logError(msg: String) {
Log.e("SyncActivity", msg)
}
}

@ -14,7 +14,11 @@ dependencies {
implementation 'androidx.annotation:annotation:1.0.0'
implementation 'com.google.code.findbugs:jsr305:3.0.2'
implementation 'org.apache.commons:commons-lang3:3.5'
implementation 'commons-codec:commons-codec:1.15'
implementation 'com.google.code.gson:gson:2.8.5'
implementation "com.google.guava:guava:30.0-jre"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$KOTLIN_VERSION"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:$KOTLIN_VERSION"
testImplementation 'junit:junit:4.12'
testImplementation 'org.hamcrest:hamcrest-library:1.4-atlassian-1'
@ -23,11 +27,13 @@ dependencies {
testImplementation 'org.json:json:20160810'
testImplementation 'org.xerial:sqlite-jdbc:3.18.0'
testImplementation 'nl.jqno.equalsverifier:equalsverifier:2.4.8'
testImplementation "org.jetbrains.kotlin:kotlin-test:$KOTLIN_VERSION"
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$KOTLIN_VERSION"
implementation('com.opencsv:opencsv:3.10') {
exclude group: 'commons-logging', module: 'commons-logging'
}
implementation "org.jetbrains.kotlin:kotlin-stdlib:$KOTLIN_VERSION"
}
sourceCompatibility = "1.8"

@ -0,0 +1,61 @@
/*
* Copyright (C) 2016-2020 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.core.ui.screens.sync
import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.sync.*
class SyncBehavior(
val screen: Screen,
val preferences: Preferences,
val server: AbstractSyncServer,
) {
suspend fun onResume() {
if (preferences.syncKey.isBlank()) {
register()
} else {
displayCurrentKey()
}
}
suspend fun displayCurrentKey() {
screen.showLink("https://loophabits.org/sync/${preferences.syncKey}#${preferences.encryptionKey}")
}
suspend fun register() {
screen.showLoadingScreen()
try {
val syncKey = server.register()
val encKey = EncryptionKey.generate()
preferences.enableSync(syncKey, encKey.base64)
displayCurrentKey()
} catch (e: Exception) {
screen.logError("Unexpected exception $e")
screen.showErrorScreen()
}
}
interface Screen {
suspend fun showLoadingScreen()
suspend fun showErrorScreen()
suspend fun showLink(link: String)
fun logError(msg: String)
}
}

@ -19,10 +19,11 @@
@file:Suppress("UnstableApiUsage")
package org.isoron.uhabits.utils
package org.isoron.uhabits.sync
import android.util.*
import com.google.common.io.*
import kotlinx.coroutines.*
import org.apache.commons.codec.binary.*
import java.io.*
import java.nio.*
import java.util.zip.*
@ -52,10 +53,10 @@ class EncryptionKey private constructor(
return EncryptionKey(base64, spec)
}
fun generate(): EncryptionKey {
suspend fun generate(): EncryptionKey = Dispatchers.IO {
try {
val generator = KeyGenerator.getInstance("AES").apply { init(256) }
return fromSecretKey(generator.generateKey())
return@IO fromSecretKey(generator.generateKey())
} catch (e: Exception) {
throw RuntimeException(e)
}
@ -128,6 +129,6 @@ fun File.encryptToString(key: EncryptionKey): String {
}
}
fun ByteArray.encodeBase64(): String = Base64.encodeToString(this, Base64.DEFAULT)
fun String.decodeBase64(): ByteArray = Base64.decode(this, Base64.DEFAULT)
fun ByteArray.encodeBase64(): String = Base64.encodeBase64(this).decodeToString()
fun String.decodeBase64(): ByteArray = Base64.decodeBase64(this.toByteArray())

@ -16,18 +16,17 @@
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.utils
package org.isoron.uhabits.core.sync
import androidx.test.filters.*
import kotlinx.coroutines.*
import org.hamcrest.Matchers.*
import org.isoron.uhabits.*
import org.isoron.uhabits.sync.*
import org.junit.*
import org.junit.Assert.*
import java.io.*
import java.util.*
@MediumTest
class EncryptionExtTest : BaseAndroidTest() {
class EncryptionExtTest {
@Test
fun test_encode_decode() {
@ -39,7 +38,7 @@ class EncryptionExtTest : BaseAndroidTest() {
}
@Test
fun test_encrypt_decrypt_bytes() {
fun test_encrypt_decrypt_bytes() = runBlocking {
val original = ByteArray(5000)
Random().nextBytes(original)
val key = EncryptionKey.generate()
@ -49,7 +48,7 @@ class EncryptionExtTest : BaseAndroidTest() {
}
@Test
fun test_encrypt_decrypt_file() {
fun test_encrypt_decrypt_file() = runBlocking {
val original = File.createTempFile("file", ".txt")
val writer = PrintWriter(original.outputStream())
writer.println("hello world")
Loading…
Cancel
Save