Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
X1nto committed Nov 13, 2024
2 parents 1af57f3 + ac4bee6 commit 038049f
Show file tree
Hide file tree
Showing 39 changed files with 2,019 additions and 376 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
run: ./gradlew assembleDebug

- name: Upload the APK
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: mauth
path: app/build/outputs/apk/debug/app-debug.apk
33 changes: 15 additions & 18 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@ plugins {
kotlin("android")
id("kotlin-parcelize")
id("com.google.devtools.ksp")
kotlin("plugin.compose")
}

android {
namespace = "com.xinto.mauth"
compileSdk = 34
compileSdk = 35

defaultConfig {
applicationId = "com.xinto.mauth"
minSdk = 21
targetSdk = 34
versionCode = 80
versionName = "0.8.0"
targetSdk = 35
versionCode = 90
versionName = "0.9.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
Expand Down Expand Up @@ -74,10 +75,6 @@ android {
buildConfig = true
}

composeOptions {
kotlinCompilerExtensionVersion = "1.5.12"
}

packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
Expand All @@ -103,13 +100,13 @@ ksp {
}

dependencies {
implementation("androidx.core:core-ktx:1.13.0")
implementation("androidx.core:core-ktx:1.15.0")
implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0")
implementation("androidx.activity:activity-compose:1.9.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7")
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")
implementation("androidx.activity:activity-compose:1.9.3")

val composeBom = platform("androidx.compose:compose-bom:2024.04.01")
val composeBom = platform("androidx.compose:compose-bom:2024.10.01")
implementation(composeBom)
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.material:material-icons-extended")
Expand All @@ -121,7 +118,7 @@ dependencies {
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")

val cameraxVersion = "1.3.3"
val cameraxVersion = "1.4.0"
implementation("androidx.camera:camera-core:$cameraxVersion")
implementation("androidx.camera:camera-camera2:$cameraxVersion")
implementation("androidx.camera:camera-view:$cameraxVersion")
Expand All @@ -135,7 +132,7 @@ dependencies {
implementation("androidx.biometric:biometric:1.1.0")
implementation("androidx.security:security-crypto-ktx:1.1.0-alpha06")

implementation("androidx.datastore:datastore-preferences:1.1.0")
implementation("androidx.datastore:datastore-preferences:1.1.1")

implementation("dev.olshevski.navigation:reimagined:1.5.0")

Expand All @@ -147,10 +144,10 @@ dependencies {

implementation("io.insert-koin:koin-androidx-compose:3.4.5")

val accompanistVersion = "0.32.0"
val accompanistVersion = "0.36.0"
implementation("com.google.accompanist:accompanist-permissions:$accompanistVersion")

testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
}
44 changes: 44 additions & 0 deletions app/src/main/java/com/xinto/mauth/core/camera/ZxingEncoder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.xinto.mauth.core.camera

import android.graphics.Bitmap
import androidx.annotation.ColorInt
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.MultiFormatWriter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

object ZxingEncoder {

private val writer = MultiFormatWriter()

suspend fun encodeToBitmap(
data: String,
size: Int,
@ColorInt backgroundColor: Int,
@ColorInt dataColor: Int
): Bitmap {
return withContext(Dispatchers.IO){
suspendCoroutine { continuation ->
val bitMatrix = writer.encode(
/* contents = */ data,
/* format = */ BarcodeFormat.QR_CODE,
/* width = */ size,
/* height = */ size,
/* hints = */ mapOf(EncodeHintType.MARGIN to 2)
)
val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888).apply {
for (x in 0 until size) {
for (y in 0 until size) {
val hasData = bitMatrix.get(x, y)
setPixel(x, y, if (hasData) dataColor else backgroundColor)
}
}
}
continuation.resume(bitmap)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.xinto.mauth.core.otp.exporter

import android.net.Uri
import com.xinto.mauth.core.otp.model.OtpData
import com.xinto.mauth.core.otp.model.OtpType

class DefaultOtpExporter : OtpExporter {

override fun exportOtp(data: OtpData): String {
val uriBuilder = Uri.Builder()
.scheme("otpauth")
.appendPath(data.label)
.appendQueryParameter("secret", data.secret)
.appendQueryParameter("algorithm", data.algorithm.name)
.appendQueryParameter("digits", data.digits.toString())

if (data.issuer.isNotBlank()) {
uriBuilder.appendQueryParameter("issuer", data.issuer)
}

return when (data.type) {
OtpType.TOTP -> {
uriBuilder
.authority("totp")
.appendQueryParameter("period", data.period.toString())
}
OtpType.HOTP -> {
uriBuilder
.authority("hotp")
.appendQueryParameter("counter", data.period.toString())
}
}.toString().also(::println)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.xinto.mauth.core.otp.exporter

import com.xinto.mauth.core.otp.model.OtpData

interface OtpExporter {

fun exportOtp(data: OtpData): String

}
5 changes: 5 additions & 0 deletions app/src/main/java/com/xinto/mauth/di/MauthDI.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.xinto.mauth.di
import androidx.room.Room
import com.xinto.mauth.core.auth.AuthManager
import com.xinto.mauth.core.auth.DefaultAuthManager
import com.xinto.mauth.core.otp.exporter.DefaultOtpExporter
import com.xinto.mauth.core.otp.exporter.OtpExporter
import com.xinto.mauth.core.otp.generator.DefaultOtpGenerator
import com.xinto.mauth.core.otp.generator.OtpGenerator
import com.xinto.mauth.core.otp.parser.DefaultOtpUriParser
Expand All @@ -19,6 +21,7 @@ import com.xinto.mauth.domain.account.AccountRepository
import com.xinto.mauth.domain.otp.OtpRepository
import com.xinto.mauth.ui.screen.account.AccountViewModel
import com.xinto.mauth.ui.screen.auth.AuthViewModel
import com.xinto.mauth.ui.screen.export.ExportViewModel
import com.xinto.mauth.ui.screen.home.HomeViewModel
import com.xinto.mauth.ui.screen.pinremove.PinRemoveViewModel
import com.xinto.mauth.ui.screen.pinsetup.PinSetupViewModel
Expand All @@ -39,6 +42,7 @@ object MauthDI {
singleOf(::DefaultKeyTransformer) bind KeyTransformer::class
singleOf(::DefaultSettings) bind Settings::class
singleOf(::DefaultAuthManager) bind AuthManager::class
singleOf(::DefaultOtpExporter) bind OtpExporter::class
}

val DbModule = module {
Expand Down Expand Up @@ -77,6 +81,7 @@ object MauthDI {
viewModelOf(::HomeViewModel)
viewModelOf(::AuthViewModel)
viewModelOf(::ThemeViewModel)
viewModelOf(::ExportViewModel)
}

val all = listOf(CoreModule, DbModule, DomainModule, UiModule)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.xinto.mauth.domain.account

import com.xinto.mauth.core.otp.exporter.OtpExporter
import com.xinto.mauth.core.otp.model.OtpData
import com.xinto.mauth.core.otp.model.OtpType
import com.xinto.mauth.core.settings.model.SortSetting
import com.xinto.mauth.db.dao.account.AccountsDao
Expand All @@ -9,15 +11,19 @@ import com.xinto.mauth.db.dao.rtdata.entity.EntityCountData
import com.xinto.mauth.domain.SettingsRepository
import com.xinto.mauth.domain.account.model.DomainAccount
import com.xinto.mauth.domain.account.model.DomainAccountInfo
import com.xinto.mauth.domain.account.model.DomainExportAccount
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import java.util.UUID

class AccountRepository(
private val accountsDao: AccountsDao,
private val rtdataDao: RtdataDao,
private val settingsRepository: SettingsRepository
private val settingsRepository: SettingsRepository,
private val otpExporter: OtpExporter
) {

fun getAccounts(): Flow<List<DomainAccount>> {
Expand All @@ -36,7 +42,7 @@ class AccountRepository(
SortSetting.LabelAsc -> mapped.sortedBy { it.label }
SortSetting.LabelDesc -> mapped.sortedByDescending { it.label }
}
}
}.flowOn(Dispatchers.IO)
}

fun getAccountInfo(id: UUID): Flow<DomainAccountInfo> {
Expand Down Expand Up @@ -65,6 +71,46 @@ class AccountRepository(
accountsDao.delete(ids.toSet())
}

suspend fun DomainAccount.toExportAccount(): DomainExportAccount {
return DomainExportAccount(
id = id,
label = label,
issuer = issuer,
icon = icon,
url = otpExporter.exportOtp(this.toOtpData())
)
}

suspend fun DomainAccount.toOtpData(): OtpData {
return when (this) {
is DomainAccount.Hotp -> {
val counter = rtdataDao.getAccountCounter(id)
OtpData(
label = label,
issuer = issuer,
secret = secret,
algorithm = algorithm,
type = OtpType.HOTP,
digits = digits,
counter = counter,
period = null
)
}
is DomainAccount.Totp -> {
OtpData(
label = label,
issuer = issuer,
secret = secret,
algorithm = algorithm,
type = OtpType.TOTP,
digits = digits,
counter = null,
period = period
)
}
}
}

private fun EntityAccount.toDomain(): DomainAccount {
return when (type) {
OtpType.TOTP -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,25 @@ import androidx.compose.runtime.Immutable
import com.xinto.mauth.core.otp.model.OtpDigest
import java.util.UUID

val DomainAccount.shortLabel: String
get() {
return label.filter {
@Immutable
sealed class DomainAccount {
abstract val id: UUID
abstract val icon: Uri?
abstract val secret: String
abstract val label: String
abstract val issuer: String
abstract val algorithm: OtpDigest
abstract val digits: Int
abstract val createdMillis: Long

val shortLabel by lazy {
label.filter {
it.isUpperCase()
}.ifEmpty {
label[0].uppercase()
}.take(3)
}

@Immutable
sealed interface DomainAccount {
val id: UUID
val icon: Uri?
val secret: String
val label: String
val issuer: String
val algorithm: OtpDigest
val digits: Int
val createdMillis: Long

@Immutable
data class Totp(
override val id: UUID,
Expand All @@ -36,7 +35,7 @@ sealed interface DomainAccount {
override val digits: Int,
override val createdMillis: Long,
val period: Int
) : DomainAccount
) : DomainAccount()

@Immutable
data class Hotp(
Expand All @@ -48,6 +47,6 @@ sealed interface DomainAccount {
override val algorithm: OtpDigest,
override val digits: Int,
override val createdMillis: Long,
) : DomainAccount
) : DomainAccount()

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.xinto.mauth.domain.account.model

import android.net.Uri
import java.util.UUID

data class DomainExportAccount(
val id: UUID,
val icon: Uri?,
val label: String,
val issuer: String,
val url: String
) {
val shortLabel = label.take(1)
}
Loading

0 comments on commit 038049f

Please sign in to comment.