mirror of
https://github.com/yogeshpaliyal/KeyPass.git
synced 2026-01-01 09:20:31 -06:00
committed by
GitHub
parent
146c6178ce
commit
9bbfef35e7
@@ -2,7 +2,6 @@ package com.yogeshpaliyal.keypass.ui.home
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MediatorLiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
@@ -20,7 +19,10 @@ import javax.inject.Inject
|
||||
* created on 30-01-2021 23:02
|
||||
*/
|
||||
@HiltViewModel
|
||||
class DashboardViewModel @Inject constructor(application: Application, val appDb: com.yogeshpaliyal.common.AppDatabase) :
|
||||
class DashboardViewModel @Inject constructor(
|
||||
application: Application,
|
||||
val appDb: com.yogeshpaliyal.common.AppDatabase
|
||||
) :
|
||||
AndroidViewModel(application) {
|
||||
|
||||
val keyword by lazy {
|
||||
@@ -32,17 +34,23 @@ class DashboardViewModel @Inject constructor(application: Application, val appDb
|
||||
|
||||
private val appDao = appDb.getDao()
|
||||
|
||||
val mediator = MediatorLiveData<LiveData<List<AccountModel>>>()
|
||||
val mediator = MediatorLiveData<List<AccountModel>>()
|
||||
|
||||
init {
|
||||
mediator.addSource(keyword) {
|
||||
mediator.postValue(appDao.getAllAccounts(keyword.value, tag.value))
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
mediator.postValue(appDao.getAllAccounts(keyword.value, tag.value))
|
||||
}
|
||||
}
|
||||
mediator.addSource(tag) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
mediator.postValue(appDao.getAllAccounts(keyword.value, tag.value))
|
||||
}
|
||||
}
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
mediator.postValue(appDao.getAllAccounts(keyword.value, tag.value))
|
||||
}
|
||||
mediator.postValue(appDao.getAllAccounts(keyword.value, tag.value))
|
||||
|
||||
reloadData()
|
||||
}
|
||||
|
||||
|
||||
@@ -7,22 +7,63 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.twotone.ContentCopy
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.yogeshpaliyal.common.constants.AccountType
|
||||
import com.yogeshpaliyal.common.data.AccountModel
|
||||
import com.yogeshpaliyal.keypass.R
|
||||
import com.yogeshpaliyal.keypass.data.MyAccountModel
|
||||
import com.yogeshpaliyal.keypass.databinding.FragmentHomeBinding
|
||||
import com.yogeshpaliyal.keypass.listener.AccountsClickListener
|
||||
import com.yogeshpaliyal.keypass.ui.addTOTP.AddTOTPActivity
|
||||
import com.yogeshpaliyal.keypass.ui.detail.DetailActivity
|
||||
import com.yogeshpaliyal.universalAdapter.adapter.UniversalAdapterViewType
|
||||
import com.yogeshpaliyal.universalAdapter.adapter.UniversalRecyclerAdapter
|
||||
import com.yogeshpaliyal.universalAdapter.utils.Resource
|
||||
import com.yogeshpaliyal.keypass.ui.style.KeyPassTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
/*
|
||||
* @author Yogesh Paliyal
|
||||
@@ -32,90 +73,240 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class HomeFragment : Fragment() {
|
||||
private lateinit var binding: FragmentHomeBinding
|
||||
|
||||
private val mViewModel by lazy {
|
||||
activityViewModels<DashboardViewModel>().value
|
||||
}
|
||||
|
||||
private val mAdapter by lazy {
|
||||
UniversalRecyclerAdapter.Builder<MyAccountModel>(
|
||||
this,
|
||||
content = UniversalAdapterViewType.Content(
|
||||
R.layout.item_accounts,
|
||||
listener = mListener
|
||||
),
|
||||
noData = UniversalAdapterViewType.NoData(R.layout.layout_no_accounts)
|
||||
).build()
|
||||
}
|
||||
|
||||
val mListener = object : AccountsClickListener<AccountModel> {
|
||||
override fun onItemClick(view: View, model: AccountModel) {
|
||||
if (model.type == AccountType.TOTP) {
|
||||
AddTOTPActivity.start(context, model.uniqueId)
|
||||
} else {
|
||||
DetailActivity.start(context, model.id)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPassword(model: AccountModel): String {
|
||||
if (model.type == AccountType.TOTP) {
|
||||
return model.getOtp()
|
||||
}
|
||||
return model.password.orEmpty()
|
||||
}
|
||||
|
||||
override fun onCopyClicked(model: AccountModel) {
|
||||
val clipboard =
|
||||
ContextCompat.getSystemService(
|
||||
requireContext(),
|
||||
ClipboardManager::class.java
|
||||
)
|
||||
val clip = ClipData.newPlainText("KeyPass", getPassword(model))
|
||||
clipboard?.setPrimaryClip(clip)
|
||||
Toast.makeText(context, getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
binding = FragmentHomeBinding.inflate(layoutInflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
private val observer = Observer<List<AccountModel>> {
|
||||
val newList = it.map { accountModel ->
|
||||
MyAccountModel().also {
|
||||
it.map(accountModel)
|
||||
return ComposeView(requireContext()).apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent {
|
||||
Main(mViewModel)
|
||||
}
|
||||
}
|
||||
mAdapter.updateData(Resource.success(ArrayList(newList)))
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
private fun getPassword(model: AccountModel): String {
|
||||
if (model.type == AccountType.TOTP) {
|
||||
return model.getOtp()
|
||||
}
|
||||
return model.password.orEmpty()
|
||||
}
|
||||
|
||||
binding.recyclerView.adapter = mAdapter.getAdapter()
|
||||
@Composable()
|
||||
fun Main(mViewModel: DashboardViewModel = viewModel()) {
|
||||
KeyPassTheme {
|
||||
Surface(modifier = Modifier.padding(bottom = dimensionResource(id = R.dimen.bottom_app_bar_height))) {
|
||||
val listOfAccountsLiveData by mViewModel.mediator.observeAsState()
|
||||
|
||||
mViewModel.mediator.observe(
|
||||
viewLifecycleOwner,
|
||||
Observer {
|
||||
it.removeObserver(observer)
|
||||
it.observe(viewLifecycleOwner, observer)
|
||||
AccountsList(listOfAccountsLiveData)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/* lifecycleScope.launch() {
|
||||
mViewModel.result
|
||||
mViewModel.loadData(args.tag).collect {
|
||||
withContext(Dispatchers.Main) {
|
||||
mAdapter.updateData(Resource.success(ArrayList(it)))
|
||||
}
|
||||
}
|
||||
}*/
|
||||
@Composable
|
||||
fun AccountsList(accounts: List<AccountModel>? = null) {
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
if (accounts?.isNotEmpty() != null) {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
|
||||
items(accounts) { account ->
|
||||
Account(
|
||||
account,
|
||||
onClick = {
|
||||
if (it.type == AccountType.TOTP) {
|
||||
AddTOTPActivity.start(context, it.uniqueId)
|
||||
} else {
|
||||
DetailActivity.start(context, it.id)
|
||||
}
|
||||
},
|
||||
onCopyClicked = {
|
||||
val clipboard = ContextCompat.getSystemService(
|
||||
context, ClipboardManager::class.java
|
||||
)
|
||||
val clip = ClipData.newPlainText("KeyPass", getPassword(it))
|
||||
clipboard?.setPrimaryClip(clip)
|
||||
Toast.makeText(
|
||||
context,
|
||||
getString(R.string.copied_to_clipboard),
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
.show()
|
||||
}
|
||||
)
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NoDataFound()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewAccount() {
|
||||
KeyPassTheme {
|
||||
Account(
|
||||
accountModel = AccountModel(),
|
||||
onClick = {
|
||||
},
|
||||
onCopyClicked = {
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Account(
|
||||
accountModel: AccountModel,
|
||||
onClick: (AccountModel) -> Unit,
|
||||
onCopyClicked: (AccountModel) -> Unit
|
||||
) {
|
||||
|
||||
Card(
|
||||
elevation = androidx.compose.material3.CardDefaults.cardElevation(defaultElevation = 1.dp),
|
||||
onClick = { onClick(accountModel) }
|
||||
) {
|
||||
Row(modifier = Modifier.padding(12.dp)) {
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
.size(60.dp)
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f),
|
||||
shape = RoundedCornerShape(50)
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
text = accountModel.getInitials(),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
||||
if (accountModel.type == AccountType.TOTP) {
|
||||
WrapWithProgress(accountModel)
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically)
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = accountModel.title ?: "",
|
||||
style = MaterialTheme.typography.headlineSmall.merge(
|
||||
TextStyle(
|
||||
fontSize = 16.sp
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
RenderUserName(accountModel)
|
||||
}
|
||||
|
||||
IconButton(
|
||||
modifier = Modifier.align(Alignment.CenterVertically),
|
||||
onClick = { onCopyClicked(accountModel) }
|
||||
) {
|
||||
Icon(
|
||||
painter = rememberVectorPainter(image = Icons.TwoTone.ContentCopy),
|
||||
contentDescription = "Copy To Clipboard"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WrapWithProgress(accountModel: AccountModel) {
|
||||
|
||||
val (progress, setProgress) = remember { mutableStateOf(0f) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (accountModel.type == AccountType.TOTP) {
|
||||
while (true) {
|
||||
delay(1.seconds)
|
||||
val newProgress = accountModel.getTOtpProgress().toFloat() / 30
|
||||
setProgress(newProgress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
progress = progress
|
||||
)
|
||||
}
|
||||
|
||||
private fun getUsernameOrOtp(accountModel: AccountModel): String? {
|
||||
return if (accountModel.type == AccountType.TOTP) accountModel.getOtp() else accountModel.username
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderUserName(accountModel: AccountModel) {
|
||||
|
||||
val (username, setUsername) = remember { mutableStateOf(getUsernameOrOtp(accountModel)) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (accountModel.type == AccountType.TOTP) {
|
||||
while (true) {
|
||||
delay(1.seconds)
|
||||
setUsername(accountModel.getOtp())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = username ?: "",
|
||||
style = MaterialTheme.typography.bodyMedium.merge(
|
||||
TextStyle(
|
||||
fontSize = 14.sp
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NoDataFound() {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Bottom
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.message_no_accounts),
|
||||
modifier = Modifier
|
||||
.padding(32.dp)
|
||||
.align(alignment = Alignment.CenterHorizontally),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
Image(
|
||||
painter = painterResource(R.drawable.ic_undraw_empty_street_sfxm),
|
||||
contentDescription = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,18 +69,6 @@ class DashboardActivity :
|
||||
this@DashboardActivity
|
||||
)
|
||||
|
||||
/* val intent = Intent(this, AuthenticationActivity::class.java)
|
||||
startActivity(intent)*/
|
||||
|
||||
/* val autoFillService = getAutoFillService()
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
if (autoFillService?.isAutofillSupported == true && autoFillService.hasEnabledAutofillServices().not()) {
|
||||
val intent = Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE)
|
||||
intent.data = Uri.parse("package:$packageName")
|
||||
startActivityForResult(intent,777)
|
||||
}
|
||||
}*/
|
||||
|
||||
binding.btnAdd.setOnClickListener {
|
||||
currentNavigationFragment?.apply {
|
||||
exitTransition = MaterialElevationScale(false).apply {
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
<item name="shapeAppearanceLargeComponent">@style/ShapeAppearance.KeyPass.LargeComponent</item>
|
||||
|
||||
<!--Typography-->
|
||||
<item name="textAppearanceHeadline1">@style/TextAppearance.KeyPass.Headline1</item>
|
||||
<item name="textAppearanceHeadline2">@style/TextAppearance.KeyPass.Headline2</item>
|
||||
<item name="textAppearanceHeadline3">@style/TextAppearance.KeyPass.Headline3</item>
|
||||
<item name="textAppearanceHeadline4">@style/TextAppearance.KeyPass.Headline4</item>
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
<resources>
|
||||
|
||||
<!--Typography-->
|
||||
<style name="TextAppearance.KeyPass.Headline1" parent="TextAppearance.MaterialComponents.Headline1">
|
||||
<item name="fontFamily">@font/work_sans_medium</item>
|
||||
<item name="android:textColor">@color/color_on_surface_emphasis_high</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.KeyPass.Headline2" parent="TextAppearance.MaterialComponents.Headline2">
|
||||
<item name="fontFamily">@font/work_sans_medium</item>
|
||||
<item name="android:textColor">@color/color_on_surface_emphasis_high</item>
|
||||
|
||||
@@ -42,7 +42,7 @@ interface DbDao {
|
||||
"OR (notes LIKE '%'||:query||'%' )) " +
|
||||
"ORDER BY title ASC"
|
||||
)
|
||||
fun getAllAccounts(query: String?, tag: String?): LiveData<List<AccountModel>>
|
||||
fun getAllAccounts(query: String?, tag: String?): List<AccountModel>
|
||||
|
||||
@Query("SELECT * FROM account WHERE id = :id")
|
||||
suspend fun getAccount(id: Long?): AccountModel?
|
||||
|
||||
Reference in New Issue
Block a user