Update strings and clean up

This commit is contained in:
Rafał Kobyłko 2023-03-26 22:27:52 +02:00
parent 2c268ea70e
commit cc96b58f1f
27 changed files with 215 additions and 592 deletions

View File

@ -118,11 +118,6 @@
</activity>
<activity
android:name=".features.main.MainServicesActivity"
android:label="Services List"
android:launchMode="singleTask" />
<activity
android:name=".features.addserviceqr.AddServiceQrActivity"
android:label="Add service by scanning QR Code" />

View File

@ -12,9 +12,6 @@ import com.twofasapp.features.backup.BackupPresenter
import com.twofasapp.features.backup.import.ImportBackupActivity
import com.twofasapp.features.backup.import.ImportBackupContract
import com.twofasapp.features.backup.import.ImportBackupPresenter
import com.twofasapp.features.main.MainContract
import com.twofasapp.features.main.MainPresenter
import com.twofasapp.features.main.MainServicesActivity
import com.twofasapp.features.navigator.ActivityScopedNavigator
import com.twofasapp.prefs.ScopedNavigator
import com.twofasapp.widgets.configure.WidgetSettingsActivity
@ -38,23 +35,6 @@ val activityScopeModule = module {
factory<ScopedNavigator> { ActivityScopedNavigator(get(), get()) }
activityScope<MainServicesActivity> {
scoped<MainContract.Presenter> {
MainPresenter(
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
)
}
}
activityScope<AddServiceQrActivity> {
scoped<AddServiceQrContract.Presenter> {
AddServiceQrPresenter(

View File

@ -1,38 +0,0 @@
package com.twofasapp.features.main
import android.content.Context
import android.graphics.Point
import android.util.AttributeSet
import android.view.WindowManager
import androidx.customview.widget.ViewDragHelper
import androidx.drawerlayout.widget.DrawerLayout
import kotlin.math.max
class EdgeDrawerLayout : DrawerLayout {
private val swipeEdgeSize by lazy {
val displaySize = Point()
(context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.getSize(displaySize)
(displaySize.x * 0.2).toInt()
}
constructor(context: Context) : this(context, null, 0)
constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, l, t, r, b)
val leftDraggerField = DrawerLayout::class.java.getDeclaredField("mLeftDragger")
leftDraggerField.isAccessible = true
val viewDragHelper = leftDraggerField[this] as ViewDragHelper
val edgeSizeField = ViewDragHelper::class.java.getDeclaredField("mEdgeSize")
edgeSizeField.isAccessible = true
val origEdgeSize = edgeSizeField[viewDragHelper] as Int
edgeSizeField.setInt(viewDragHelper, max(swipeEdgeSize, origEdgeSize))
}
}

View File

@ -1,22 +0,0 @@
package com.twofasapp.features.main
import com.twofasapp.design.dialogs.CancelAction
import com.twofasapp.design.dialogs.ConfirmAction
import com.twofasapp.prefs.model.ServiceDto
interface MainContract {
interface View {
fun showRateApp()
fun showUpgradeAppNoticeDialog(action: () -> Unit)
fun showServiceExistsDialog(confirmAction: ConfirmAction, cancelAction: CancelAction)
fun showRemoveQrReminder(serviceDto: ServiceDto)
}
abstract class Presenter : com.twofasapp.base.BasePresenter() {
abstract fun startObservingPushes()
abstract fun stopObservingPushes()
abstract fun onReviewSuccess()
abstract fun onReviewFailed(exception: Exception?)
}
}

View File

@ -1,63 +0,0 @@
package com.twofasapp.features.main
import com.twofasapp.core.analytics.AnalyticsService
import com.twofasapp.prefs.ScopedNavigator
import com.twofasapp.services.domain.ConvertOtpLinkToService
import com.twofasapp.services.domain.StoreHotpServices
import com.twofasapp.usecases.rateapp.RateAppCondition
import com.twofasapp.usecases.rateapp.UpdateRateAppStatus
import com.twofasapp.usecases.services.AddService
import com.twofasapp.usecases.services.GetServices
import com.twofasapp.usecases.totp.ParseOtpAuthLink
import io.reactivex.disposables.CompositeDisposable
class MainPresenter(
private val view: MainContract.View,
private val navigator: ScopedNavigator,
private val getServices: GetServices,
private val updateRateAppStatus: UpdateRateAppStatus,
private val storeHotpServices: StoreHotpServices,
private val addService: AddService,
private val parseOtpAuthLink: ParseOtpAuthLink,
private val convertOtpLinkToService: ConvertOtpLinkToService,
private val rateAppCondition: RateAppCondition,
private val analyticsService: AnalyticsService,
) : MainContract.Presenter() {
private val watchDisposables = CompositeDisposable()
override fun onViewAttached() {
storeHotpServices.clear()
updateRateAppStatus.incrementCounter()
when {
rateAppCondition.execute() -> view.showRateApp()
}
}
override fun startObservingPushes() {
}
override fun stopObservingPushes() {
watchDisposables.clear()
}
private fun saveService(incomingData: String) {
parseOtpAuthLink.execute(ParseOtpAuthLink.Params(incomingData))
.map { convertOtpLinkToService.execute(it) }
.flatMapCompletable { addService.execute(AddService.Params(it)) }
.andThen(getServices.execute())
.subscribe({ }, {})
.addToDisposables()
}
override fun onReviewSuccess() {
updateRateAppStatus.markShown()
}
override fun onReviewFailed(exception: Exception?) {
analyticsService.captureException(exception)
}
}

View File

@ -1,218 +0,0 @@
package com.twofasapp.features.main
import android.content.Intent
import android.os.Bundle
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.afollestad.materialdialogs.MaterialDialog
import com.google.android.play.core.review.ReviewManagerFactory
import com.twofasapp.base.BaseActivityPresenter
import com.twofasapp.base.lifecycle.AuthAware
import com.twofasapp.databinding.ActivityMainBinding
import com.twofasapp.design.dialogs.CancelAction
import com.twofasapp.design.dialogs.ConfirmAction
import com.twofasapp.design.dialogs.ConfirmDialog
import com.twofasapp.features.addserviceqr.AddServiceQrActivity
import com.twofasapp.features.addserviceqr.ScanInfoDialog
import com.twofasapp.prefs.model.ServiceDto
import com.twofasapp.resources.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import lt.neworld.spanner.Spanner
import lt.neworld.spanner.Spans
class MainServicesActivity : BaseActivityPresenter<ActivityMainBinding>(), MainContract.View, AuthAware {
private val presenter: MainContract.Presenter by injectThis()
// private val fetchTokenRequestsCase: FetchTokenRequestsCase by inject()
// private val observeMobileDeviceCase: ObserveMobileDeviceCase by inject()
private val authenticationDialogs = mutableMapOf<String, MaterialDialog>()
private val removeQrReminderDialog: ScanInfoDialog by lazy { ScanInfoDialog(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivityMainBinding::inflate)
setPresenter(presenter)
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
launch(Dispatchers.IO) {
// launch(Dispatchers.IO) {
//
// try {
// val mobileDevice = observeMobileDeviceCase.invoke().first()
// val tokenRequests = fetchTokenRequestsCase(mobileDevice.id)
//
//
// tokenRequests.forEach { tokenRequest ->
// val domain = DomainMatcher.extractDomain(tokenRequest.domain)
// val matchedServices = DomainMatcher.findServicesMatchingDomain(
// getServicesCase(),
// domain
// )
//
// runOnUiThread {
// if (authenticationDialogs.containsKey(tokenRequest.requestId)
// .not()
// ) {
// authenticationDialogs.put(
// tokenRequest.requestId,
// MaterialDialog(this@MainServicesActivity)
// .title(text = getString(R.string.browser__2fa_token_request_title))
// .message(text = getString(R.string.browser__2fa_token_request_content).plus(" ${tokenRequest.domain}?"))
// .cancelable(false)
// .positiveButton(text = getString(R.string.extension__approve)) {
// val isOneDomainMatched =
// matchedServices.size == 1
// val serviceId =
// if (matchedServices.size == 1) matchedServices.first().id else null
//
// if (isOneDomainMatched) {
// val payload =
// BrowserExtensionRequestPayload(
// action = BrowserExtensionRequestPayload.Action.Approve,
// notificationId = -1,
// extensionId = tokenRequest.extensionId,
// requestId = tokenRequest.requestId,
// serviceId = serviceId ?: -1,
// domain = domain,
// )
// sendBroadcast(
// BrowserExtensionRequestReceiver.createIntent(
// this@MainServicesActivity,
// payload
// )
// )
// } else {
//
// val contentIntent = Intent(
// this@MainServicesActivity,
// BrowserExtensionRequestActivity::class.java
// ).apply {
// flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
//
// putExtra(
// BrowserExtensionRequestPayload.Key,
// BrowserExtensionRequestPayload(
// action = BrowserExtensionRequestPayload.Action.Approve,
// notificationId = -1,
// extensionId = tokenRequest.extensionId,
// requestId = tokenRequest.requestId,
// serviceId = serviceId ?: -1,
// domain = domain,
// )
// )
// }
//
// startActivity(contentIntent)
// }
// }
// .negativeButton(text = getString(R.string.extension__deny)) {
// val payload = BrowserExtensionRequestPayload(
// action = BrowserExtensionRequestPayload.Action.Deny,
// notificationId = -1,
// extensionId = tokenRequest.extensionId,
// requestId = tokenRequest.requestId,
// serviceId = -1,
// domain = domain,
// )
// sendBroadcast(
// BrowserExtensionRequestReceiver.createIntent(
// this@MainServicesActivity,
// payload
// )
// )
// }
// .show { }
// )
// }
// }
// }
// } catch (e: Exception) {
// e.printStackTrace()
// }
// }
}
}
}
}
override fun onAuthenticated() {
presenter.startObservingPushes()
}
override fun onPause() {
super.onPause()
presenter.stopObservingPushes()
authenticationDialogs.forEach { it.value.dismiss() }
authenticationDialogs.clear()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode != RESULT_OK) return
if (requestCode == AddServiceQrActivity.REQUEST_CODE) {
val isFromGallery =
data?.getBooleanExtra(AddServiceQrActivity.RESULT_IS_FROM_GALLERY, false) ?: false
data?.getParcelableExtra<ServiceDto>(AddServiceQrActivity.RESULT_SERVICE)?.let {
}
return
}
}
override fun showRemoveQrReminder(serviceDto: ServiceDto) {
val desc = Spanner()
.append(getString(R.string.tokens__gallery_advice_content_first))
.append(getString(R.string.tokens__gallery_advice_content_middle_bold), Spans.bold())
.append(getString(R.string.tokens__gallery_advice_content_last))
removeQrReminderDialog.show(
title = getString(R.string.tokens__gallery_advice_title),
desc = "",
descSpan = desc,
okText = getString(R.string.commons__got_it),
imageRes = R.drawable.remove_qr_reminder_image,
showCancel = false,
action = { removeQrReminderDialog.dismiss() },
actionDismiss = { },
)
}
override fun showRateApp() {
val manager = ReviewManagerFactory.create(this)
val request = manager.requestReviewFlow()
request.addOnCompleteListener { task ->
if (task.isSuccessful) {
val flow = manager.launchReviewFlow(this, task.result)
flow.addOnCompleteListener {
presenter.onReviewSuccess()
}
} else {
presenter.onReviewFailed(task.exception)
}
}
}
override fun showUpgradeAppNoticeDialog(action: () -> Unit) {
ConfirmDialog(
context = this,
title = getString(R.string.update_app_title),
msg = getString(R.string.update_app_msg),
positiveButtonText = "Update",
negativeButtonText = "Later",
).show(
confirmAction = { action() }
)
}
override fun showServiceExistsDialog(confirmAction: ConfirmAction, cancelAction: CancelAction) {
ConfirmDialog(this, R.string.commons__warning, R.string.tokens__service_already_exists)
.show(confirmAction = confirmAction, cancelAction = cancelAction)
}
}

View File

@ -14,13 +14,13 @@ import com.twofasapp.extensions.toastLong
import com.twofasapp.features.addserviceqr.AddServiceQrActivity
import com.twofasapp.features.backup.BackupActivity
import com.twofasapp.features.backup.import.ImportBackupActivity
import com.twofasapp.features.main.MainServicesActivity
import com.twofasapp.prefs.ScopedNavigator
import com.twofasapp.prefs.model.CheckLockStatus
import com.twofasapp.prefs.model.LockMethodEntity
import com.twofasapp.prefs.model.ServiceDto
import com.twofasapp.resources.R
import com.twofasapp.security.ui.lock.LockActivity
import com.twofasapp.ui.main.MainActivity
class ActivityScopedNavigator(
@ -58,7 +58,7 @@ class ActivityScopedNavigator(
}
override fun openMain() {
activity.startActivity<MainServicesActivity>()
activity.startActivity<MainActivity>()
}
override fun openGooglePlay() {

View File

@ -154,4 +154,71 @@ class MainActivity : AppCompatActivity(), AuthAware {
return
}
}
//
// override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// super.onActivityResult(requestCode, resultCode, data)
//
// if (resultCode != RESULT_OK) return
//
//
// if (requestCode == AddServiceQrActivity.REQUEST_CODE) {
// val isFromGallery =
// data?.getBooleanExtra(AddServiceQrActivity.RESULT_IS_FROM_GALLERY, false) ?: false
// data?.getParcelableExtra<ServiceDto>(AddServiceQrActivity.RESULT_SERVICE)?.let {
// }
// return
// }
// }
//
// override fun showRemoveQrReminder(serviceDto: ServiceDto) {
// val desc = Spanner()
// .append(getString(R.string.tokens__gallery_advice_content_first))
// .append(getString(R.string.tokens__gallery_advice_content_middle_bold), Spans.bold())
// .append(getString(R.string.tokens__gallery_advice_content_last))
//
// removeQrReminderDialog.show(
// title = getString(R.string.tokens__gallery_advice_title),
// desc = "",
// descSpan = desc,
// okText = getString(R.string.commons__got_it),
// imageRes = R.drawable.remove_qr_reminder_image,
// showCancel = false,
// action = { removeQrReminderDialog.dismiss() },
// actionDismiss = { },
// )
// }
//
// override fun showRateApp() {
// val manager = ReviewManagerFactory.create(this)
// val request = manager.requestReviewFlow()
// request.addOnCompleteListener { task ->
// if (task.isSuccessful) {
// val flow = manager.launchReviewFlow(this, task.result)
// flow.addOnCompleteListener {
// presenter.onReviewSuccess()
// }
// } else {
// presenter.onReviewFailed(task.exception)
// }
// }
// }
//
// override fun showUpgradeAppNoticeDialog(action: () -> Unit) {
// ConfirmDialog(
// context = this,
// title = getString(R.string.update_app_title),
// msg = getString(R.string.update_app_msg),
// positiveButtonText = "Update",
// negativeButtonText = "Later",
// ).show(
// confirmAction = { action() }
// )
// }
//
// override fun showServiceExistsDialog(confirmAction: ConfirmAction, cancelAction: CancelAction) {
// ConfirmDialog(this, R.string.commons__warning, R.string.tokens__service_already_exists)
// .show(confirmAction = confirmAction, cancelAction = cancelAction)
// }
}

View File

@ -1,176 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.twofasapp.features.main.EdgeDrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="false">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinator"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
style="@style/Toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="@menu/menu_services"
app:title=" " />
</com.google.android.material.appbar.AppBarLayout>
<fragment
android:name="com.twofasapp.features.services.ServicesFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:tag="ServicesFragment"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<View
android:id="@+id/fabMenuMask"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#78000000"
android:contentDescription="@string/commons__cancel"
android:visibility="gone" />
<LinearLayout
android:id="@+id/fabLayout1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="4dp"
android:layout_marginBottom="@dimen/fab_distance_second"
android:contentDescription="@string/tokens__fab_addmanually"
android:gravity="center_vertical"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:id="@+id/fab1Label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_fab_label"
android:clickable="false"
android:paddingStart="@dimen/fab_label_padding_horizontal"
android:paddingTop="@dimen/fab_label_padding_vertical"
android:paddingEnd="@dimen/fab_label_padding_horizontal"
android:paddingBottom="@dimen/fab_label_padding_vertical"
android:text="@string/tokens__fab_addmanually"
android:textColor="#FFF"
android:textSize="14sp" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
android:clickable="false"
android:contentDescription="@string/tokens__fab_addmanually"
android:importantForAccessibility="no"
android:src="@drawable/ic_add_service_manually"
app:backgroundTint="@color/material_white"
app:fabSize="mini" />
</LinearLayout>
<LinearLayout
android:id="@+id/fabLayout2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="4dp"
android:layout_marginBottom="@dimen/fab_distance_first"
android:contentDescription="@string/commons__scan_qr_code"
android:gravity="center_vertical"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:id="@+id/fab2Label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_fab_label"
android:clickable="false"
android:paddingStart="@dimen/fab_label_padding_horizontal"
android:paddingTop="@dimen/fab_label_padding_vertical"
android:paddingEnd="@dimen/fab_label_padding_horizontal"
android:paddingBottom="@dimen/fab_label_padding_vertical"
android:text="@string/commons__scan_qr_code"
android:textColor="#FFF"
android:textSize="14sp" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
android:clickable="false"
android:contentDescription="@string/commons__scan_qr_code"
android:focusable="false"
android:focusableInTouchMode="false"
android:importantForAccessibility="no"
android:src="@drawable/ic_add_service_qr"
app:backgroundTint="@color/material_white"
app:fabSize="mini" />
</LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:contentDescription="@string/commons__add"
android:src="@drawable/ic_add_old"
app:backgroundTint="@color/colorAccent"
app:fabSize="normal"
app:tint="@color/white" />
<ImageButton
android:id="@+id/syncFab"
style="@style/ImageButtonIcon"
android:layout_width="48dp"
android:layout_height="36dp"
android:layout_gravity="bottom|start"
android:layout_margin="@dimen/fab_margin"
android:background="@drawable/fab_sync"
android:elevation="8dp"
android:padding="8dp"
android:src="@drawable/ic_sync_off"
android:visibility="gone"
app:layout_dodgeInsetEdges="bottom"
app:tint="@color/fabNeutralIcon" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<TextView
android:id="@+id/toolbarTitle"
android:layout_width="match_parent"
android:layout_height="?android:actionBarSize"
android:gravity="center"
android:paddingHorizontal="72dp"
android:text="@string/commons__2fas_toolbar"
android:textAppearance="@style/Toolbar.Title.TextAppearance"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.twofasapp.features.main.EdgeDrawerLayout>

View File

@ -6,8 +6,8 @@ object AppConfig {
const val compileSdk = 33
private const val verMajor = 4
private const val verMinor = 2
private const val verPatch = 8
private const val verMinor = 5
private const val verPatch = 0
private const val verInternal = 0
const val versionCode = verMajor * 1000000 + verMinor * 10000 + verPatch * 100 + verInternal

View File

@ -11,6 +11,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.twofasapp.designsystem.TwTheme
@ -38,7 +39,7 @@ fun RowScope.TwNavigationBarItem(
NavigationBarItem(
selected = selected,
onClick = onClick,
label = { Text(text) },
label = { Text(text, maxLines = 1, overflow = TextOverflow.Ellipsis) },
icon = {
BadgedBox(badge = {
if (showBadge) {

View File

@ -89,12 +89,14 @@ fun DsService(
.background(containerColor)
.combinedClickable(
enabled = onClick != null,
onClick = { onClick?.invoke() },
onLongClick = {
onClick = {
if (editMode.not()) {
onLongClick?.invoke()
onClick?.invoke()
}
},
onLongClick = {
onLongClick?.invoke()
},
),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp)

View File

@ -28,6 +28,10 @@ class Strings(c: Context) {
val commonApprove = c.getString(R.string.commons__approve)
val commonDeny = c.getString(R.string.commons__deny)
val bottomBarTokens = c.getString(R.string.commons__tokens)
val bottomBarSettings = c.getString(R.string.settings__settings)
val bottomBarNotifications = c.getString(R.string.commons__notifications)
val startupTermsLabel = c.getString(R.string.introduction__tos)
val startupStepOneHeader = c.getString(R.string.introduction__page_1_title)
val startupStepOneBody = c.getString(R.string.introduction__page_1_content)
@ -45,7 +49,7 @@ class Strings(c: Context) {
val servicesEmptySearch = c.getString(R.string.tokens__service_not_found_search)
val servicesEmptySearchBody = c.getString(R.string.tokens__try_different_search_term)
val servicesMyTokens = c.getString(R.string.tokens__my_tokens)
val servicesManageList = "Manage list"
val servicesManageList = c.getString(R.string.tokens__manage_list)
val servicesSortBy = c.getString(R.string.tokens__sort_by)
val servicesSortByOptions = listOf(c.getString(R.string.tokens__sort_by_a_to_z), c.getString(R.string.tokens__sort_by_manual))
val servicesCopyToken = c.getString(R.string.tokens__copied_clipboard)
@ -53,6 +57,10 @@ class Strings(c: Context) {
val groupsAdd = c.getString(R.string.tokens__add_group)
val groupsEdit = c.getString(R.string.commons__edit)
val groupsName = c.getString(R.string.tokens__group_name)
val addManually = c.getString(R.string.tokens__fab_addmanually)
val addQr = c.getString(R.string.commons__scan_qr_code)
val editService = c.getString(R.string.commons__edit)
val copyToken = c.getString(R.string.tokens__copy_token)
val externalImportTitle = c.getString(R.string.settings__external_import)
val externalImportHeader = c.getString(R.string.externalimport_select_app)
@ -82,7 +90,7 @@ class Strings(c: Context) {
val settingsBackup = c.getString(R.string.backup__2fas_backup)
val settingsSecurity = c.getString(R.string.settings__security)
val settingsSettings = c.getString(R.string.settings__settings)
val settingsAppearance = "Appearance"
val settingsAppearance = c.getString(R.string.settings__appearance)
val settingsExternalImport = c.getString(R.string.settings__external_import)
val settingsBrowserExt = c.getString(R.string.browser__browser_extension)
val settingsTrash = c.getString(R.string.settings__trash)
@ -104,15 +112,15 @@ class Strings(c: Context) {
val settingsTheme = c.getString(R.string.settings__option_theme)
val settingsShowNextCode = c.getString(R.string.settings__show_next_token)
val settingsShowNextCodeBody = "Show next token when current one is about to expire."
val settingsShowNextCodeBody = c.getString(R.string.settings__show_next_token_desc)
val settingsAutoFocusSearch = c.getString(R.string.appearance__toggle_active_search)
val settingsAutoFocusSearchBody = c.getString(R.string.appearance__active_search_description)
val settingsServicesStyle = "List style"
val settingsServicesStyle = c.getString(R.string.settings__list_style)
val settingsShowBackupNotice = c.getString(R.string.settings__gd_sync_info)
val settingsShowBackupNoticeConfirmBody = c.getString(R.string.settings__gd_sync_disable_confirm)
val backupSyncNotice = "Google Drive not synced."
val backupSyncCta = "Go to backup settings "
val backupSyncNotice = c.getString(R.string.backup__reminder_msg)
val backupSyncCta = c.getString(R.string.backup__reminder_cta)
val backupReminder = c.getString(R.string.backup_notice_title)
val backupReminderBody = c.getString(R.string.backup_notice_msg)
val backupReminderDismiss = c.getString(R.string.backup_notice_later)

View File

@ -5,7 +5,7 @@
Release: Working copy
Locale: de-DE, German (Germany)
Exported by: rafakob
Exported at: Sat, 25 Mar 2023 14:49:44 -0700
Exported at: Sun, 26 Mar 2023 13:20:22 -0700
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- InfoPlist.strings
@ -721,4 +721,12 @@
<string name="commons__best_match">Bester Treffer</string>
<string name="settings__enable_crashlytics">Anonyme Absturzberichte senden</string>
<string name="settings__enable_crashlytics_description">Sende anonyme Absturzberichte, um 2FAS beim Identifizieren und Lösen von Fehlern in der App zu helfen (App Neustart erforderlich).</string>
<string name="tokens__manage_list">Manage list</string>
<string name="settings__appearance">Appearance</string>
<string name="settings__show_next_token_desc">Show next token when current one is about to expire.</string>
<string name="settings__list_style">List style</string>
<string name="backup__reminder_msg">Google Drive not synced.</string>
<string name="backup__reminder_cta">Go to backup settings</string>
<string name="settings__list_style_option_default">Default</string>
<string name="settings__list_style_option_compact">Compact</string>
</resources>

View File

@ -5,7 +5,7 @@
Release: Working copy
Locale: fr-FR, French (France)
Exported by: rafakob
Exported at: Sat, 25 Mar 2023 14:49:44 -0700
Exported at: Sun, 26 Mar 2023 13:20:22 -0700
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- InfoPlist.strings
@ -147,7 +147,7 @@
<string name="settings__display_selected_services">Afficher les services sélectionnés pour les widgets de l\'écran d\'accueil.</string>
<!-- settings__after_you_enable_widgets -->
<string name="settings__widgets_title">Après avoir activé les widgets, toutes vos codes 2FA seront accessibles sans code PIN. Êtes-vous sur de vouloir activer les widgets ?</string>
<string name="settings__show_next_token">Afficher le code suivant</string>
<string name="settings__show_next_token">Afficher le prochain code</string>
<string name="settings__see_incoming_tokens">Voir les prochains codes sur la liste.</string>
<string name="settings__trash">Corbeille</string>
<string name="settings__restore">Restaurer</string>
@ -371,7 +371,7 @@
<string name="backup__newer_format_not_supported">Ce fichier est dans un format plus récent que celui que l\'application supporte.</string>
<!-- backup__encrypted_unsupported -->
<string name="backup__encrypted_files_not_supported">Ce fichier est crypté. Nous ne supportons que les fichiers non cryptés</string>
<string name="tokens__copy_token">Copié le code</string>
<string name="tokens__copy_token">Code copié</string>
<!-- tokens__i_want_to_delete_this_servie -->
<string name="tokens__i_want_to_delete_this_token">Oui, je veux supprimer ce service</string>
<string name="color__neutral">Neutre</string>
@ -719,6 +719,14 @@
<string name="errors__input_empty">Le champ ne peut être vide</string>
<string name="errors__input_too_long">La valeur saisie est trop longue. Limite : %d</string>
<string name="commons__best_match">Meilleur correspondance</string>
<string name="settings__enable_crashlytics">Send anonymous crash reports</string>
<string name="settings__enable_crashlytics_description">Send anonymous crash reports to help 2FAS identify and solve issues in the app (app restart required).</string>
<string name="settings__enable_crashlytics">Envoyer des rapports d\'erreur anonymes</string>
<string name="settings__enable_crashlytics_description">Envoyer des rapports d\'erreur anonymes pour aider 2FAS à identifier et à résoudre les erreurs dans l\'application (redémarrage de l\'application nécessaire).</string>
<string name="tokens__manage_list">Manage list</string>
<string name="settings__appearance">Appearance</string>
<string name="settings__show_next_token_desc">Show next token when current one is about to expire.</string>
<string name="settings__list_style">List style</string>
<string name="backup__reminder_msg">Google Drive not synced.</string>
<string name="backup__reminder_cta">Go to backup settings</string>
<string name="settings__list_style_option_default">Default</string>
<string name="settings__list_style_option_compact">Compact</string>
</resources>

View File

@ -5,7 +5,7 @@
Release: Working copy
Locale: pt-PT, Portuguese (Portugal)
Exported by: rafakob
Exported at: Sat, 25 Mar 2023 14:49:44 -0700
Exported at: Sun, 26 Mar 2023 13:20:22 -0700
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- InfoPlist.strings
@ -719,6 +719,14 @@
<string name="errors__input_empty">O valor não pode estar vazio</string>
<string name="errors__input_too_long">O valor é muito longo. Limite: %d</string>
<string name="commons__best_match">Melhor correspondência</string>
<string name="settings__enable_crashlytics">Send anonymous crash reports</string>
<string name="settings__enable_crashlytics_description">Send anonymous crash reports to help 2FAS identify and solve issues in the app (app restart required).</string>
<string name="settings__enable_crashlytics">Envia relatórios anônimos de falhas</string>
<string name="settings__enable_crashlytics_description">Envia relatórios anônimos de falhas para ajudar a 2FAS a identificar e resolver problemas na aplicação (é necessário reiniciar a aplicação).</string>
<string name="tokens__manage_list">Manage list</string>
<string name="settings__appearance">Appearance</string>
<string name="settings__show_next_token_desc">Show next token when current one is about to expire.</string>
<string name="settings__list_style">List style</string>
<string name="backup__reminder_msg">Google Drive not synced.</string>
<string name="backup__reminder_cta">Go to backup settings</string>
<string name="settings__list_style_option_default">Default</string>
<string name="settings__list_style_option_compact">Compact</string>
</resources>

View File

@ -5,7 +5,7 @@
Release: Working copy
Locale: en, English
Exported by: rafakob
Exported at: Sat, 25 Mar 2023 14:49:44 -0700
Exported at: Sun, 26 Mar 2023 13:20:22 -0700
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- InfoPlist.strings
@ -697,7 +697,7 @@
<string name="externalimport__aegis_success_msg">This JSON file allows importing tokens from Aegis.</string>
<string name="externalimport__raivo_success_msg">This JSON file allows importing tokens from Raivo.</string>
<string name="externalimport__read_error">Could not read any tokens. Try to select a different file.</string>
<string name="settings__gd_sync_info">Google Drive sync info</string>
<string name="settings__gd_sync_info">Google Drive sync reminder</string>
<string name="settings__gd_sync_disable_confirm">Are you sure? Without Google Drive sync, you won\'t be able to restore your tokens if you lose or reset your phone!</string>
<string name="extension__code_sent_msg">Code sent successfully</string>
<string name="extension__code_sent_error_msg">Error occurred when sending code</string>
@ -721,4 +721,12 @@
<string name="commons__best_match">Best match</string>
<string name="settings__enable_crashlytics">Send anonymous crash reports</string>
<string name="settings__enable_crashlytics_description">Send anonymous crash reports to help 2FAS identify and solve issues in the app (app restart required).</string>
<string name="tokens__manage_list">Manage list</string>
<string name="settings__appearance">Appearance</string>
<string name="settings__show_next_token_desc">Show next token when current one is about to expire.</string>
<string name="settings__list_style">List style</string>
<string name="backup__reminder_msg">Google Drive not synced.</string>
<string name="backup__reminder_cta">Go to backup settings</string>
<string name="settings__list_style_option_default">Default</string>
<string name="settings__list_style_option_compact">Compact</string>
</resources>

View File

@ -9,6 +9,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.twofasapp.data.session.domain.SelectedTheme
import com.twofasapp.data.session.domain.ServicesStyle
@ -18,6 +19,7 @@ import com.twofasapp.designsystem.dialog.ConfirmDialog
import com.twofasapp.designsystem.dialog.ListRadioDialog
import com.twofasapp.designsystem.settings.SettingsLink
import com.twofasapp.designsystem.settings.SettingsSwitch
import com.twofasapp.locale.R
import com.twofasapp.locale.TwLocale
import org.koin.androidx.compose.koinViewModel
@ -110,11 +112,12 @@ private fun AppSettingsScreen(
}
if (showThemeDialog) {
ListRadioDialog(
onDismissRequest = { showThemeDialog = false },
title = TwLocale.strings.settingsTheme,
options = SelectedTheme.values().map { it.name },
selectedOption = uiState.appSettings.selectedTheme.name,
options = SelectedTheme.values().map { it.toStringResource() },
selectedOption = uiState.appSettings.selectedTheme.toStringResource(),
onOptionSelected = { index, _ -> onSelectedThemeChange(SelectedTheme.values()[index]) },
)
}
@ -123,8 +126,8 @@ private fun AppSettingsScreen(
ListRadioDialog(
onDismissRequest = { showServicesStyleDialog = false },
title = TwLocale.strings.settingsServicesStyle,
options = ServicesStyle.values().map { it.name },
selectedOption = uiState.appSettings.servicesStyle.name,
options = ServicesStyle.values().map { it.toStringResource() },
selectedOption = uiState.appSettings.servicesStyle.toStringResource(),
onOptionSelected = { index, _ -> onServicesStyleChange(ServicesStyle.values()[index]) },
)
}
@ -139,3 +142,20 @@ private fun AppSettingsScreen(
}
}
}
@Composable
private fun SelectedTheme.toStringResource(): String {
return when (this) {
SelectedTheme.Auto -> stringResource(id = R.string.settings__theme_option_auto)
SelectedTheme.Light -> stringResource(id = R.string.settings__theme_option_light)
SelectedTheme.Dark -> stringResource(id = R.string.settings__theme_option_dark)
}
}
@Composable
private fun ServicesStyle.toStringResource(): String {
return when (this) {
ServicesStyle.Default -> stringResource(id = R.string.settings__list_style_option_default)
ServicesStyle.Compact -> stringResource(id = R.string.settings__list_style_option_compact)
}
}

View File

@ -10,6 +10,7 @@ import com.twofasapp.designsystem.TwIcons
import com.twofasapp.designsystem.common.TwNavigationBar
import com.twofasapp.designsystem.common.TwNavigationBarItem
import com.twofasapp.feature.home.navigation.HomeNode
import com.twofasapp.locale.TwLocale
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
import org.koin.androidx.compose.koinViewModel
@ -18,19 +19,19 @@ private val bottomNavItems
@Composable
get() = listOf(
BottomNavItem(
title = "Tokens",
title = TwLocale.strings.bottomBarTokens,
icon = TwIcons.Home,
iconSelected = TwIcons.HomeFilled,
route = HomeNode.Services.route,
),
BottomNavItem(
title = "Settings",
title = TwLocale.strings.bottomBarSettings,
icon = TwIcons.Settings,
iconSelected = TwIcons.SettingsFilled,
route = HomeNode.Settings.route,
),
BottomNavItem(
title = "Notifications",
title = TwLocale.strings.bottomBarNotifications,
icon = TwIcons.Notification,
iconSelected = TwIcons.NotificationFilled,
route = HomeNode.Notifications.route,

View File

@ -422,13 +422,13 @@ private fun ServicesScreen(
modifier = Modifier,
dragHandleVisible = uiState.appSettings.servicesSort == ServicesSort.Manual,
dragModifier = Modifier.detectReorder(state = reorderableState),
onClick = {
onLongClick = {
scope.launch {
modalType = ModalType.FocusService(service.id, false)
modalState.show()
}
},
onLongClick = {
onClick = {
state.copyToClipboard(activity, uiState.appSettings.showNextCode)
},
onIncrementCounterClick = {

View File

@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
import com.twofasapp.designsystem.TwIcons
import com.twofasapp.designsystem.common.ModalList
import com.twofasapp.designsystem.settings.SettingsLink
import com.twofasapp.locale.TwLocale
@Composable
internal fun AddServiceModal(
@ -11,7 +12,7 @@ internal fun AddServiceModal(
onScanQrClick: () -> Unit = {},
) {
ModalList {
SettingsLink(title = "Add manually", icon = TwIcons.Edit) { onAddManuallyClick() }
SettingsLink(title = "Scan QR code", icon = TwIcons.Qr) { onScanQrClick() }
SettingsLink(title = TwLocale.strings.addManually, icon = TwIcons.Edit) { onAddManuallyClick() }
SettingsLink(title = TwLocale.strings.addQr, icon = TwIcons.Qr) { onScanQrClick() }
}
}

View File

@ -9,6 +9,7 @@ import com.twofasapp.designsystem.service.DsServiceModal
import com.twofasapp.designsystem.service.ServiceState
import com.twofasapp.designsystem.settings.SettingsDivider
import com.twofasapp.designsystem.settings.SettingsLink
import com.twofasapp.locale.TwLocale
@Composable
internal fun FocusServiceModal(
@ -29,8 +30,8 @@ internal fun FocusServiceModal(
SettingsDivider()
ModalList {
SettingsLink(title = "Edit", icon = TwIcons.Edit) { onEditClick() }
SettingsLink(title = "Copy token", icon = TwIcons.Copy) { onCopyClick() }
SettingsLink(title = TwLocale.strings.editService, icon = TwIcons.Edit) { onEditClick() }
SettingsLink(title = TwLocale.strings.copyToken, icon = TwIcons.Copy) { onCopyClick() }
}
}
}

View File

@ -1,6 +1,6 @@
[versions]
accompanist = "0.27.0"
agp = "8.1.0-alpha08"
agp = "7.4.1"
coil = "2.2.2"
compose = "1.4.0-rc01"
composeActivity = "1.6.1"

View File

@ -5,7 +5,7 @@
Release: Working copy
Locale: de-DE, German (Germany)
Exported by: rafakob
Exported at: Sat, 25 Mar 2023 14:49:44 -0700
Exported at: Sun, 26 Mar 2023 13:20:22 -0700
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- InfoPlist.strings
@ -721,4 +721,12 @@
<string name="commons__best_match">Bester Treffer</string>
<string name="settings__enable_crashlytics">Anonyme Absturzberichte senden</string>
<string name="settings__enable_crashlytics_description">Sende anonyme Absturzberichte, um 2FAS beim Identifizieren und Lösen von Fehlern in der App zu helfen (App Neustart erforderlich).</string>
<string name="tokens__manage_list">Manage list</string>
<string name="settings__appearance">Appearance</string>
<string name="settings__show_next_token_desc">Show next token when current one is about to expire.</string>
<string name="settings__list_style">List style</string>
<string name="backup__reminder_msg">Google Drive not synced.</string>
<string name="backup__reminder_cta">Go to backup settings</string>
<string name="settings__list_style_option_default">Default</string>
<string name="settings__list_style_option_compact">Compact</string>
</resources>

View File

@ -5,7 +5,7 @@
Release: Working copy
Locale: fr-FR, French (France)
Exported by: rafakob
Exported at: Sat, 25 Mar 2023 14:49:44 -0700
Exported at: Sun, 26 Mar 2023 13:20:22 -0700
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- InfoPlist.strings
@ -147,7 +147,7 @@
<string name="settings__display_selected_services">Afficher les services sélectionnés pour les widgets de l\'écran d\'accueil.</string>
<!-- settings__after_you_enable_widgets -->
<string name="settings__widgets_title">Après avoir activé les widgets, toutes vos codes 2FA seront accessibles sans code PIN. Êtes-vous sur de vouloir activer les widgets ?</string>
<string name="settings__show_next_token">Afficher le code suivant</string>
<string name="settings__show_next_token">Afficher le prochain code</string>
<string name="settings__see_incoming_tokens">Voir les prochains codes sur la liste.</string>
<string name="settings__trash">Corbeille</string>
<string name="settings__restore">Restaurer</string>
@ -371,7 +371,7 @@
<string name="backup__newer_format_not_supported">Ce fichier est dans un format plus récent que celui que l\'application supporte.</string>
<!-- backup__encrypted_unsupported -->
<string name="backup__encrypted_files_not_supported">Ce fichier est crypté. Nous ne supportons que les fichiers non cryptés</string>
<string name="tokens__copy_token">Copié le code</string>
<string name="tokens__copy_token">Code copié</string>
<!-- tokens__i_want_to_delete_this_servie -->
<string name="tokens__i_want_to_delete_this_token">Oui, je veux supprimer ce service</string>
<string name="color__neutral">Neutre</string>
@ -719,6 +719,14 @@
<string name="errors__input_empty">Le champ ne peut être vide</string>
<string name="errors__input_too_long">La valeur saisie est trop longue. Limite : %d</string>
<string name="commons__best_match">Meilleur correspondance</string>
<string name="settings__enable_crashlytics">Send anonymous crash reports</string>
<string name="settings__enable_crashlytics_description">Send anonymous crash reports to help 2FAS identify and solve issues in the app (app restart required).</string>
<string name="settings__enable_crashlytics">Envoyer des rapports d\'erreur anonymes</string>
<string name="settings__enable_crashlytics_description">Envoyer des rapports d\'erreur anonymes pour aider 2FAS à identifier et à résoudre les erreurs dans l\'application (redémarrage de l\'application nécessaire).</string>
<string name="tokens__manage_list">Manage list</string>
<string name="settings__appearance">Appearance</string>
<string name="settings__show_next_token_desc">Show next token when current one is about to expire.</string>
<string name="settings__list_style">List style</string>
<string name="backup__reminder_msg">Google Drive not synced.</string>
<string name="backup__reminder_cta">Go to backup settings</string>
<string name="settings__list_style_option_default">Default</string>
<string name="settings__list_style_option_compact">Compact</string>
</resources>

View File

@ -5,7 +5,7 @@
Release: Working copy
Locale: pt-PT, Portuguese (Portugal)
Exported by: rafakob
Exported at: Sat, 25 Mar 2023 14:49:44 -0700
Exported at: Sun, 26 Mar 2023 13:20:22 -0700
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- InfoPlist.strings
@ -719,6 +719,14 @@
<string name="errors__input_empty">O valor não pode estar vazio</string>
<string name="errors__input_too_long">O valor é muito longo. Limite: %d</string>
<string name="commons__best_match">Melhor correspondência</string>
<string name="settings__enable_crashlytics">Send anonymous crash reports</string>
<string name="settings__enable_crashlytics_description">Send anonymous crash reports to help 2FAS identify and solve issues in the app (app restart required).</string>
<string name="settings__enable_crashlytics">Envia relatórios anônimos de falhas</string>
<string name="settings__enable_crashlytics_description">Envia relatórios anônimos de falhas para ajudar a 2FAS a identificar e resolver problemas na aplicação (é necessário reiniciar a aplicação).</string>
<string name="tokens__manage_list">Manage list</string>
<string name="settings__appearance">Appearance</string>
<string name="settings__show_next_token_desc">Show next token when current one is about to expire.</string>
<string name="settings__list_style">List style</string>
<string name="backup__reminder_msg">Google Drive not synced.</string>
<string name="backup__reminder_cta">Go to backup settings</string>
<string name="settings__list_style_option_default">Default</string>
<string name="settings__list_style_option_compact">Compact</string>
</resources>

View File

@ -5,7 +5,7 @@
Release: Working copy
Locale: en, English
Exported by: rafakob
Exported at: Sat, 25 Mar 2023 14:49:44 -0700
Exported at: Sun, 26 Mar 2023 13:20:22 -0700
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- InfoPlist.strings
@ -697,7 +697,7 @@
<string name="externalimport__aegis_success_msg">This JSON file allows importing tokens from Aegis.</string>
<string name="externalimport__raivo_success_msg">This JSON file allows importing tokens from Raivo.</string>
<string name="externalimport__read_error">Could not read any tokens. Try to select a different file.</string>
<string name="settings__gd_sync_info">Google Drive sync info</string>
<string name="settings__gd_sync_info">Google Drive sync reminder</string>
<string name="settings__gd_sync_disable_confirm">Are you sure? Without Google Drive sync, you won\'t be able to restore your tokens if you lose or reset your phone!</string>
<string name="extension__code_sent_msg">Code sent successfully</string>
<string name="extension__code_sent_error_msg">Error occurred when sending code</string>
@ -721,4 +721,12 @@
<string name="commons__best_match">Best match</string>
<string name="settings__enable_crashlytics">Send anonymous crash reports</string>
<string name="settings__enable_crashlytics_description">Send anonymous crash reports to help 2FAS identify and solve issues in the app (app restart required).</string>
<string name="tokens__manage_list">Manage list</string>
<string name="settings__appearance">Appearance</string>
<string name="settings__show_next_token_desc">Show next token when current one is about to expire.</string>
<string name="settings__list_style">List style</string>
<string name="backup__reminder_msg">Google Drive not synced.</string>
<string name="backup__reminder_cta">Go to backup settings</string>
<string name="settings__list_style_option_default">Default</string>
<string name="settings__list_style_option_compact">Compact</string>
</resources>