Skip to content

Commit 038049f

Browse files
committed
Merge remote-tracking branch 'origin/master'
2 parents 1af57f3 + ac4bee6 commit 038049f

39 files changed

+2019
-376
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
run: ./gradlew assembleDebug
3434

3535
- name: Upload the APK
36-
uses: actions/upload-artifact@v2
36+
uses: actions/upload-artifact@v4
3737
with:
3838
name: mauth
3939
path: app/build/outputs/apk/debug/app-debug.apk

app/build.gradle.kts

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,19 @@ plugins {
33
kotlin("android")
44
id("kotlin-parcelize")
55
id("com.google.devtools.ksp")
6+
kotlin("plugin.compose")
67
}
78

89
android {
910
namespace = "com.xinto.mauth"
10-
compileSdk = 34
11+
compileSdk = 35
1112

1213
defaultConfig {
1314
applicationId = "com.xinto.mauth"
1415
minSdk = 21
15-
targetSdk = 34
16-
versionCode = 80
17-
versionName = "0.8.0"
16+
targetSdk = 35
17+
versionCode = 90
18+
versionName = "0.9.0"
1819

1920
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2021
vectorDrawables {
@@ -74,10 +75,6 @@ android {
7475
buildConfig = true
7576
}
7677

77-
composeOptions {
78-
kotlinCompilerExtensionVersion = "1.5.12"
79-
}
80-
8178
packaging {
8279
resources {
8380
excludes += "/META-INF/{AL2.0,LGPL2.1}"
@@ -103,13 +100,13 @@ ksp {
103100
}
104101

105102
dependencies {
106-
implementation("androidx.core:core-ktx:1.13.0")
103+
implementation("androidx.core:core-ktx:1.15.0")
107104
implementation("androidx.core:core-splashscreen:1.0.1")
108-
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
109-
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0")
110-
implementation("androidx.activity:activity-compose:1.9.0")
105+
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7")
106+
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")
107+
implementation("androidx.activity:activity-compose:1.9.3")
111108

112-
val composeBom = platform("androidx.compose:compose-bom:2024.04.01")
109+
val composeBom = platform("androidx.compose:compose-bom:2024.10.01")
113110
implementation(composeBom)
114111
implementation("androidx.compose.foundation:foundation")
115112
implementation("androidx.compose.material:material-icons-extended")
@@ -121,7 +118,7 @@ dependencies {
121118
debugImplementation("androidx.compose.ui:ui-tooling")
122119
debugImplementation("androidx.compose.ui:ui-test-manifest")
123120

124-
val cameraxVersion = "1.3.3"
121+
val cameraxVersion = "1.4.0"
125122
implementation("androidx.camera:camera-core:$cameraxVersion")
126123
implementation("androidx.camera:camera-camera2:$cameraxVersion")
127124
implementation("androidx.camera:camera-view:$cameraxVersion")
@@ -135,7 +132,7 @@ dependencies {
135132
implementation("androidx.biometric:biometric:1.1.0")
136133
implementation("androidx.security:security-crypto-ktx:1.1.0-alpha06")
137134

138-
implementation("androidx.datastore:datastore-preferences:1.1.0")
135+
implementation("androidx.datastore:datastore-preferences:1.1.1")
139136

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

@@ -147,10 +144,10 @@ dependencies {
147144

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

150-
val accompanistVersion = "0.32.0"
147+
val accompanistVersion = "0.36.0"
151148
implementation("com.google.accompanist:accompanist-permissions:$accompanistVersion")
152149

153150
testImplementation("junit:junit:4.13.2")
154-
androidTestImplementation("androidx.test.ext:junit:1.1.5")
155-
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
151+
androidTestImplementation("androidx.test.ext:junit:1.2.1")
152+
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
156153
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.xinto.mauth.core.camera
2+
3+
import android.graphics.Bitmap
4+
import androidx.annotation.ColorInt
5+
import com.google.zxing.BarcodeFormat
6+
import com.google.zxing.EncodeHintType
7+
import com.google.zxing.MultiFormatWriter
8+
import kotlinx.coroutines.Dispatchers
9+
import kotlinx.coroutines.withContext
10+
import kotlin.coroutines.resume
11+
import kotlin.coroutines.suspendCoroutine
12+
13+
object ZxingEncoder {
14+
15+
private val writer = MultiFormatWriter()
16+
17+
suspend fun encodeToBitmap(
18+
data: String,
19+
size: Int,
20+
@ColorInt backgroundColor: Int,
21+
@ColorInt dataColor: Int
22+
): Bitmap {
23+
return withContext(Dispatchers.IO){
24+
suspendCoroutine { continuation ->
25+
val bitMatrix = writer.encode(
26+
/* contents = */ data,
27+
/* format = */ BarcodeFormat.QR_CODE,
28+
/* width = */ size,
29+
/* height = */ size,
30+
/* hints = */ mapOf(EncodeHintType.MARGIN to 2)
31+
)
32+
val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888).apply {
33+
for (x in 0 until size) {
34+
for (y in 0 until size) {
35+
val hasData = bitMatrix.get(x, y)
36+
setPixel(x, y, if (hasData) dataColor else backgroundColor)
37+
}
38+
}
39+
}
40+
continuation.resume(bitmap)
41+
}
42+
}
43+
}
44+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.xinto.mauth.core.otp.exporter
2+
3+
import android.net.Uri
4+
import com.xinto.mauth.core.otp.model.OtpData
5+
import com.xinto.mauth.core.otp.model.OtpType
6+
7+
class DefaultOtpExporter : OtpExporter {
8+
9+
override fun exportOtp(data: OtpData): String {
10+
val uriBuilder = Uri.Builder()
11+
.scheme("otpauth")
12+
.appendPath(data.label)
13+
.appendQueryParameter("secret", data.secret)
14+
.appendQueryParameter("algorithm", data.algorithm.name)
15+
.appendQueryParameter("digits", data.digits.toString())
16+
17+
if (data.issuer.isNotBlank()) {
18+
uriBuilder.appendQueryParameter("issuer", data.issuer)
19+
}
20+
21+
return when (data.type) {
22+
OtpType.TOTP -> {
23+
uriBuilder
24+
.authority("totp")
25+
.appendQueryParameter("period", data.period.toString())
26+
}
27+
OtpType.HOTP -> {
28+
uriBuilder
29+
.authority("hotp")
30+
.appendQueryParameter("counter", data.period.toString())
31+
}
32+
}.toString().also(::println)
33+
}
34+
35+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.xinto.mauth.core.otp.exporter
2+
3+
import com.xinto.mauth.core.otp.model.OtpData
4+
5+
interface OtpExporter {
6+
7+
fun exportOtp(data: OtpData): String
8+
9+
}

app/src/main/java/com/xinto/mauth/di/MauthDI.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package com.xinto.mauth.di
33
import androidx.room.Room
44
import com.xinto.mauth.core.auth.AuthManager
55
import com.xinto.mauth.core.auth.DefaultAuthManager
6+
import com.xinto.mauth.core.otp.exporter.DefaultOtpExporter
7+
import com.xinto.mauth.core.otp.exporter.OtpExporter
68
import com.xinto.mauth.core.otp.generator.DefaultOtpGenerator
79
import com.xinto.mauth.core.otp.generator.OtpGenerator
810
import com.xinto.mauth.core.otp.parser.DefaultOtpUriParser
@@ -19,6 +21,7 @@ import com.xinto.mauth.domain.account.AccountRepository
1921
import com.xinto.mauth.domain.otp.OtpRepository
2022
import com.xinto.mauth.ui.screen.account.AccountViewModel
2123
import com.xinto.mauth.ui.screen.auth.AuthViewModel
24+
import com.xinto.mauth.ui.screen.export.ExportViewModel
2225
import com.xinto.mauth.ui.screen.home.HomeViewModel
2326
import com.xinto.mauth.ui.screen.pinremove.PinRemoveViewModel
2427
import com.xinto.mauth.ui.screen.pinsetup.PinSetupViewModel
@@ -39,6 +42,7 @@ object MauthDI {
3942
singleOf(::DefaultKeyTransformer) bind KeyTransformer::class
4043
singleOf(::DefaultSettings) bind Settings::class
4144
singleOf(::DefaultAuthManager) bind AuthManager::class
45+
singleOf(::DefaultOtpExporter) bind OtpExporter::class
4246
}
4347

4448
val DbModule = module {
@@ -77,6 +81,7 @@ object MauthDI {
7781
viewModelOf(::HomeViewModel)
7882
viewModelOf(::AuthViewModel)
7983
viewModelOf(::ThemeViewModel)
84+
viewModelOf(::ExportViewModel)
8085
}
8186

8287
val all = listOf(CoreModule, DbModule, DomainModule, UiModule)

app/src/main/java/com/xinto/mauth/domain/account/AccountRepository.kt

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.xinto.mauth.domain.account
22

3+
import com.xinto.mauth.core.otp.exporter.OtpExporter
4+
import com.xinto.mauth.core.otp.model.OtpData
35
import com.xinto.mauth.core.otp.model.OtpType
46
import com.xinto.mauth.core.settings.model.SortSetting
57
import com.xinto.mauth.db.dao.account.AccountsDao
@@ -9,15 +11,19 @@ import com.xinto.mauth.db.dao.rtdata.entity.EntityCountData
911
import com.xinto.mauth.domain.SettingsRepository
1012
import com.xinto.mauth.domain.account.model.DomainAccount
1113
import com.xinto.mauth.domain.account.model.DomainAccountInfo
14+
import com.xinto.mauth.domain.account.model.DomainExportAccount
15+
import kotlinx.coroutines.Dispatchers
1216
import kotlinx.coroutines.flow.Flow
1317
import kotlinx.coroutines.flow.combine
1418
import kotlinx.coroutines.flow.flow
19+
import kotlinx.coroutines.flow.flowOn
1520
import java.util.UUID
1621

1722
class AccountRepository(
1823
private val accountsDao: AccountsDao,
1924
private val rtdataDao: RtdataDao,
20-
private val settingsRepository: SettingsRepository
25+
private val settingsRepository: SettingsRepository,
26+
private val otpExporter: OtpExporter
2127
) {
2228

2329
fun getAccounts(): Flow<List<DomainAccount>> {
@@ -36,7 +42,7 @@ class AccountRepository(
3642
SortSetting.LabelAsc -> mapped.sortedBy { it.label }
3743
SortSetting.LabelDesc -> mapped.sortedByDescending { it.label }
3844
}
39-
}
45+
}.flowOn(Dispatchers.IO)
4046
}
4147

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

74+
suspend fun DomainAccount.toExportAccount(): DomainExportAccount {
75+
return DomainExportAccount(
76+
id = id,
77+
label = label,
78+
issuer = issuer,
79+
icon = icon,
80+
url = otpExporter.exportOtp(this.toOtpData())
81+
)
82+
}
83+
84+
suspend fun DomainAccount.toOtpData(): OtpData {
85+
return when (this) {
86+
is DomainAccount.Hotp -> {
87+
val counter = rtdataDao.getAccountCounter(id)
88+
OtpData(
89+
label = label,
90+
issuer = issuer,
91+
secret = secret,
92+
algorithm = algorithm,
93+
type = OtpType.HOTP,
94+
digits = digits,
95+
counter = counter,
96+
period = null
97+
)
98+
}
99+
is DomainAccount.Totp -> {
100+
OtpData(
101+
label = label,
102+
issuer = issuer,
103+
secret = secret,
104+
algorithm = algorithm,
105+
type = OtpType.TOTP,
106+
digits = digits,
107+
counter = null,
108+
period = period
109+
)
110+
}
111+
}
112+
}
113+
68114
private fun EntityAccount.toDomain(): DomainAccount {
69115
return when (type) {
70116
OtpType.TOTP -> {

app/src/main/java/com/xinto/mauth/domain/account/model/DomainAccount.kt

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,25 @@ import androidx.compose.runtime.Immutable
55
import com.xinto.mauth.core.otp.model.OtpDigest
66
import java.util.UUID
77

8-
val DomainAccount.shortLabel: String
9-
get() {
10-
return label.filter {
8+
@Immutable
9+
sealed class DomainAccount {
10+
abstract val id: UUID
11+
abstract val icon: Uri?
12+
abstract val secret: String
13+
abstract val label: String
14+
abstract val issuer: String
15+
abstract val algorithm: OtpDigest
16+
abstract val digits: Int
17+
abstract val createdMillis: Long
18+
19+
val shortLabel by lazy {
20+
label.filter {
1121
it.isUpperCase()
1222
}.ifEmpty {
1323
label[0].uppercase()
1424
}.take(3)
1525
}
1626

17-
@Immutable
18-
sealed interface DomainAccount {
19-
val id: UUID
20-
val icon: Uri?
21-
val secret: String
22-
val label: String
23-
val issuer: String
24-
val algorithm: OtpDigest
25-
val digits: Int
26-
val createdMillis: Long
27-
2827
@Immutable
2928
data class Totp(
3029
override val id: UUID,
@@ -36,7 +35,7 @@ sealed interface DomainAccount {
3635
override val digits: Int,
3736
override val createdMillis: Long,
3837
val period: Int
39-
) : DomainAccount
38+
) : DomainAccount()
4039

4140
@Immutable
4241
data class Hotp(
@@ -48,6 +47,6 @@ sealed interface DomainAccount {
4847
override val algorithm: OtpDigest,
4948
override val digits: Int,
5049
override val createdMillis: Long,
51-
) : DomainAccount
50+
) : DomainAccount()
5251

5352
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.xinto.mauth.domain.account.model
2+
3+
import android.net.Uri
4+
import java.util.UUID
5+
6+
data class DomainExportAccount(
7+
val id: UUID,
8+
val icon: Uri?,
9+
val label: String,
10+
val issuer: String,
11+
val url: String
12+
) {
13+
val shortLabel = label.take(1)
14+
}

0 commit comments

Comments
 (0)