Improve profile item
This commit is contained in:
14
app/src/main/java/io/nekohasekai/sfa/ktx/Dimens.kt
Normal file
14
app/src/main/java/io/nekohasekai/sfa/ktx/Dimens.kt
Normal file
@@ -0,0 +1,14 @@
|
||||
package io.nekohasekai.sfa.ktx
|
||||
|
||||
import android.content.res.Resources
|
||||
import kotlin.math.ceil
|
||||
|
||||
private val density = Resources.getSystem().displayMetrics.density
|
||||
|
||||
fun dp2pxf(dpValue: Int): Float {
|
||||
return density * dpValue
|
||||
}
|
||||
|
||||
fun dp2px(dpValue: Int): Int {
|
||||
return ceil(dp2pxf(dpValue)).toInt()
|
||||
}
|
||||
@@ -2,11 +2,18 @@ package io.nekohasekai.sfa.ktx
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.google.android.material.R
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.qrcode.QRCodeWriter
|
||||
import io.nekohasekai.libbox.Libbox
|
||||
import io.nekohasekai.libbox.ProfileContent
|
||||
import io.nekohasekai.sfa.database.Profile
|
||||
import io.nekohasekai.sfa.database.TypedProfile
|
||||
import io.nekohasekai.sfa.ui.shared.QRCodeDialog
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
@@ -43,4 +50,28 @@ suspend fun Context.shareProfile(profile: Profile) {
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun FragmentActivity.shareProfileURL(profile: Profile) {
|
||||
val link = Libbox.generateRemoteProfileImportLink(
|
||||
profile.name,
|
||||
profile.typed.remoteURL
|
||||
)
|
||||
val imageSize = dp2px(256)
|
||||
val color = getAttrColor(com.google.android.material.R.attr.colorPrimary)
|
||||
val image = QRCodeWriter().encode(link, BarcodeFormat.QR_CODE, imageSize, imageSize, null)
|
||||
val imageWidth = image.width
|
||||
val imageHeight = image.height
|
||||
val imageArray = IntArray(imageWidth * imageHeight)
|
||||
for (y in 0 until imageHeight) {
|
||||
val offset = y * imageWidth
|
||||
for (x in 0 until imageWidth) {
|
||||
imageArray[offset + x] = if (image.get(x, y)) color else Color.TRANSPARENT
|
||||
|
||||
}
|
||||
}
|
||||
val bitmap = Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888)
|
||||
bitmap.setPixels(imageArray, 0, imageSize, 0, 0, imageWidth, imageHeight)
|
||||
val dialog = QRCodeDialog(bitmap)
|
||||
dialog.show(supportFragmentManager, "share-profile-url")
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
@@ -15,13 +16,14 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import io.nekohasekai.sfa.R
|
||||
import io.nekohasekai.sfa.database.Profile
|
||||
import io.nekohasekai.sfa.database.ProfileManager
|
||||
import io.nekohasekai.sfa.database.TypedProfile
|
||||
import io.nekohasekai.sfa.databinding.FragmentConfigurationBinding
|
||||
import io.nekohasekai.sfa.databinding.ViewConfigutationItemBinding
|
||||
import io.nekohasekai.sfa.ktx.errorDialogBuilder
|
||||
import io.nekohasekai.sfa.ktx.shareProfile
|
||||
import io.nekohasekai.sfa.ktx.shareProfileURL
|
||||
import io.nekohasekai.sfa.ui.profile.EditProfileActivity
|
||||
import io.nekohasekai.sfa.ui.profile.NewProfileActivity
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -36,7 +38,7 @@ class ConfigurationFragment : Fragment() {
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View {
|
||||
val binding = FragmentConfigurationBinding.inflate(inflater, container, false)
|
||||
val adapter = Adapter(lifecycleScope, binding)
|
||||
val adapter = Adapter(binding)
|
||||
this.adapter = adapter
|
||||
binding.profileList.also {
|
||||
it.layoutManager = LinearLayoutManager(requireContext())
|
||||
@@ -89,16 +91,17 @@ class ConfigurationFragment : Fragment() {
|
||||
adapter?.reload()
|
||||
}
|
||||
|
||||
class Adapter(
|
||||
internal val scope: CoroutineScope,
|
||||
internal val parent: FragmentConfigurationBinding
|
||||
inner class Adapter(
|
||||
private val parent: FragmentConfigurationBinding
|
||||
) :
|
||||
RecyclerView.Adapter<Holder>() {
|
||||
|
||||
internal var items: MutableList<Profile> = mutableListOf()
|
||||
internal val scope = lifecycleScope
|
||||
internal val fragmentActivity = requireActivity() as FragmentActivity
|
||||
|
||||
internal fun reload() {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val newItems = ProfileManager.list().toMutableList()
|
||||
withContext(Dispatchers.Main) {
|
||||
items = newItems
|
||||
@@ -163,6 +166,15 @@ class ConfigurationFragment : Fragment() {
|
||||
|
||||
internal fun bind(profile: Profile) {
|
||||
binding.profileName.text = profile.name
|
||||
if (profile.typed.type == TypedProfile.Type.Remote) {
|
||||
binding.profileLastUpdated.isVisible = true
|
||||
binding.profileLastUpdated.text = binding.root.context.getString(
|
||||
R.string.profile_item_last_updated,
|
||||
profile.typed.lastUpdated.toLocaleString()
|
||||
)
|
||||
} else {
|
||||
binding.profileLastUpdated.isVisible = false
|
||||
}
|
||||
binding.root.setOnClickListener {
|
||||
val intent = Intent(binding.root.context, EditProfileActivity::class.java)
|
||||
intent.putExtra("profile_id", profile.id)
|
||||
@@ -172,6 +184,9 @@ class ConfigurationFragment : Fragment() {
|
||||
val popup = PopupMenu(button.context, button)
|
||||
popup.setForceShowIcon(true)
|
||||
popup.menuInflater.inflate(R.menu.profile_menu, popup.menu)
|
||||
if (profile.typed.type != TypedProfile.Type.Remote) {
|
||||
popup.menu.removeItem(R.id.action_share_url)
|
||||
}
|
||||
popup.setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.action_share -> {
|
||||
@@ -187,6 +202,19 @@ class ConfigurationFragment : Fragment() {
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_share_url -> {
|
||||
adapter.scope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
adapter.fragmentActivity.shareProfileURL(profile)
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
button.context.errorDialogBuilder(e).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_delete -> {
|
||||
adapter.items.remove(profile)
|
||||
adapter.notifyItemRemoved(adapterPosition)
|
||||
|
||||
@@ -95,13 +95,11 @@ class EditProfileActivity : AbstractActivity() {
|
||||
TypedProfile.Type.Local -> {
|
||||
binding.editButton.isVisible = true
|
||||
binding.remoteFields.isVisible = false
|
||||
binding.shareURLButton.isVisible = false
|
||||
}
|
||||
|
||||
TypedProfile.Type.Remote -> {
|
||||
binding.editButton.isVisible = false
|
||||
binding.remoteFields.isVisible = true
|
||||
binding.shareURLButton.isVisible = true
|
||||
binding.remoteURL.text = profile.typed.remoteURL
|
||||
binding.lastUpdated.text =
|
||||
DateFormat.getDateTimeInstance().format(profile.typed.lastUpdated)
|
||||
@@ -115,9 +113,6 @@ class EditProfileActivity : AbstractActivity() {
|
||||
binding.autoUpdate.addTextChangedListener(this@EditProfileActivity::updateAutoUpdate)
|
||||
binding.autoUpdateInterval.addTextChangedListener(this@EditProfileActivity::updateAutoUpdateInterval)
|
||||
binding.updateButton.setOnClickListener(this@EditProfileActivity::updateProfile)
|
||||
binding.checkButton.setOnClickListener(this@EditProfileActivity::checkProfile)
|
||||
binding.shareButton.setOnClickListener(this@EditProfileActivity::shareProfile)
|
||||
binding.shareURLButton.setOnClickListener(this@EditProfileActivity::shareProfileURL)
|
||||
binding.profileLayout.isVisible = true
|
||||
binding.progressView.isVisible = false
|
||||
}
|
||||
@@ -208,24 +203,6 @@ class EditProfileActivity : AbstractActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkProfile(button: View) {
|
||||
val binding = binding ?: return
|
||||
binding.progressView.isVisible = true
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
delay(200L)
|
||||
try {
|
||||
Libbox.checkConfig(File(profile.typed.path).readText())
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
errorDialogBuilder(e).show()
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
binding.progressView.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun shareProfile(button: View) {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -238,25 +215,4 @@ class EditProfileActivity : AbstractActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun shareProfileURL(button: View) {
|
||||
try {
|
||||
startActivity(
|
||||
Intent.createChooser(
|
||||
Intent(Intent.ACTION_SEND).setType("application/octet-stream")
|
||||
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
.putExtra(
|
||||
Intent.EXTRA_STREAM,
|
||||
Libbox.generateRemoteProfileImportLink(
|
||||
profile.name,
|
||||
profile.typed.remoteURL
|
||||
)
|
||||
),
|
||||
getString(com.google.android.material.R.string.abc_shareactionprovider_share_with)
|
||||
)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
errorDialogBuilder(e).show()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.nekohasekai.sfa.ui.shared
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import io.nekohasekai.sfa.databinding.FragmentQrcodeDialogBinding
|
||||
|
||||
class QRCodeDialog(private val bitmap: Bitmap) :
|
||||
BottomSheetDialogFragment() {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
val binding = FragmentQrcodeDialogBinding.inflate(inflater, container, false)
|
||||
val behavior = BottomSheetBehavior.from(binding.qrcodeLayout)
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
binding.qrCode.setImageBitmap(bitmap)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user