diff --git a/app/src/androidTest/java/dev/lowrespalmtree/comet/UriUtilsTest.kt b/app/src/androidTest/java/dev/lowrespalmtree/comet/UriUtilsTest.kt index 059d40f..e442d3a 100644 --- a/app/src/androidTest/java/dev/lowrespalmtree/comet/UriUtilsTest.kt +++ b/app/src/androidTest/java/dev/lowrespalmtree/comet/UriUtilsTest.kt @@ -13,27 +13,36 @@ class UriUtilsTest { fun joinUrls() { assertEquals( "gemini://dece.space/some-file.gmi", - joinUrls("gemini://dece.space/", "some-file.gmi").toString() + dev.lowrespalmtree.comet.utils.joinUrls("gemini://dece.space/", "some-file.gmi").toString() ) assertEquals( "gemini://dece.space/some-file.gmi", - joinUrls("gemini://dece.space/", "./some-file.gmi").toString() + dev.lowrespalmtree.comet.utils.joinUrls("gemini://dece.space/", "./some-file.gmi").toString() ) assertEquals( "gemini://dece.space/some-file.gmi", - joinUrls("gemini://dece.space/dir1", "/some-file.gmi").toString() + dev.lowrespalmtree.comet.utils.joinUrls("gemini://dece.space/dir1", "/some-file.gmi").toString() ) assertEquals( "gemini://dece.space/dir1/other-file.gmi", - joinUrls("gemini://dece.space/dir1/file.gmi", "other-file.gmi").toString() + dev.lowrespalmtree.comet.utils.joinUrls( + "gemini://dece.space/dir1/file.gmi", + "other-file.gmi" + ).toString() ) assertEquals( "gemini://dece.space/top-level.gmi", - joinUrls("gemini://dece.space/dir1/file.gmi", "../top-level.gmi").toString() + dev.lowrespalmtree.comet.utils.joinUrls( + "gemini://dece.space/dir1/file.gmi", + "../top-level.gmi" + ).toString() ) assertEquals( "s://hard/test/b/d/a.gmi", - joinUrls("s://hard/dir/a", "./../test/b/c/../d/e/f/../.././a.gmi").toString() + dev.lowrespalmtree.comet.utils.joinUrls( + "s://hard/dir/a", + "./../test/b/c/../d/e/f/../.././a.gmi" + ).toString() ) } @@ -49,28 +58,28 @@ class UriUtilsTest { Pair("mid/content=5/../6", "mid/6"), Pair("../../../../g", "g") ).forEach { (path, expected) -> - assertEquals(expected, removeDotSegments(path)) + assertEquals(expected, dev.lowrespalmtree.comet.utils.removeDotSegments(path)) } } @Test fun removeLastSegment() { - assertEquals("", removeLastSegment("")) - assertEquals("", removeLastSegment("/")) - assertEquals("", removeLastSegment("/a")) - assertEquals("/a", removeLastSegment("/a/")) - assertEquals("/a", removeLastSegment("/a/b")) - assertEquals("/a/b/c", removeLastSegment("/a/b/c/d")) - assertEquals("//", removeLastSegment("///")) + assertEquals("", dev.lowrespalmtree.comet.utils.removeLastSegment("")) + assertEquals("", dev.lowrespalmtree.comet.utils.removeLastSegment("/")) + assertEquals("", dev.lowrespalmtree.comet.utils.removeLastSegment("/a")) + assertEquals("/a", dev.lowrespalmtree.comet.utils.removeLastSegment("/a/")) + assertEquals("/a", dev.lowrespalmtree.comet.utils.removeLastSegment("/a/b")) + assertEquals("/a/b/c", dev.lowrespalmtree.comet.utils.removeLastSegment("/a/b/c/d")) + assertEquals("//", dev.lowrespalmtree.comet.utils.removeLastSegment("///")) } @Test fun popFirstSegment() { - assertEquals(Pair("", ""), popFirstSegment("")) - assertEquals(Pair("a", ""), popFirstSegment("a")) - assertEquals(Pair("/a", ""), popFirstSegment("/a")) - assertEquals(Pair("/a", "/"), popFirstSegment("/a/")) - assertEquals(Pair("/a", "/b"), popFirstSegment("/a/b")) - assertEquals(Pair("a", "/b"), popFirstSegment("a/b")) + assertEquals(Pair("", ""), dev.lowrespalmtree.comet.utils.popFirstSegment("")) + assertEquals(Pair("a", ""), dev.lowrespalmtree.comet.utils.popFirstSegment("a")) + assertEquals(Pair("/a", ""), dev.lowrespalmtree.comet.utils.popFirstSegment("/a")) + assertEquals(Pair("/a", "/"), dev.lowrespalmtree.comet.utils.popFirstSegment("/a/")) + assertEquals(Pair("/a", "/b"), dev.lowrespalmtree.comet.utils.popFirstSegment("/a/b")) + assertEquals(Pair("a", "/b"), dev.lowrespalmtree.comet.utils.popFirstSegment("a/b")) } } \ No newline at end of file diff --git a/app/src/main/java/dev/lowrespalmtree/comet/HistoryAdapter.kt b/app/src/main/java/dev/lowrespalmtree/comet/HistoryAdapter.kt index 995ad17..aa82b2e 100644 --- a/app/src/main/java/dev/lowrespalmtree/comet/HistoryAdapter.kt +++ b/app/src/main/java/dev/lowrespalmtree/comet/HistoryAdapter.kt @@ -7,12 +7,13 @@ import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import dev.lowrespalmtree.comet.History.HistoryEntry import dev.lowrespalmtree.comet.databinding.FragmentHistoryItemBinding +import dev.lowrespalmtree.comet.utils.getFancySelectBgRes -class HistoryAdapter(private val listener: HistoryItemAdapterListener) : +class HistoryAdapter(private val listener: Listener) : RecyclerView.Adapter() { private var items = listOf() - interface HistoryItemAdapterListener { + interface Listener { fun onItemClick(url: String) } @@ -31,7 +32,7 @@ class HistoryAdapter(private val listener: HistoryItemAdapterListener) : holder.binding.titleText.visibility = if (item.title.isNullOrBlank()) View.GONE else View.VISIBLE holder.binding.titleText.text = item.title ?: "" - holder.binding.card.setOnClickListener { listener.onItemClick(item.uri) } + holder.binding.container.setOnClickListener { listener.onItemClick(item.uri) } } override fun getItemCount(): Int = items.size @@ -43,5 +44,9 @@ class HistoryAdapter(private val listener: HistoryItemAdapterListener) : } inner class ViewHolder(val binding: FragmentHistoryItemBinding) : - RecyclerView.ViewHolder(binding.root) + RecyclerView.ViewHolder(binding.root) { + init { + itemView.setBackgroundResource(getFancySelectBgRes(itemView.context)) + } + } } \ No newline at end of file diff --git a/app/src/main/java/dev/lowrespalmtree/comet/HistoryFragment.kt b/app/src/main/java/dev/lowrespalmtree/comet/HistoryFragment.kt index 1f825f9..4844d8a 100644 --- a/app/src/main/java/dev/lowrespalmtree/comet/HistoryFragment.kt +++ b/app/src/main/java/dev/lowrespalmtree/comet/HistoryFragment.kt @@ -12,16 +12,14 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import dev.lowrespalmtree.comet.History.HistoryEntry -import dev.lowrespalmtree.comet.HistoryAdapter.HistoryItemAdapterListener import dev.lowrespalmtree.comet.databinding.FragmentHistoryListBinding import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch -@ExperimentalCoroutinesApi -class HistoryFragment : Fragment(), HistoryItemAdapterListener { +class HistoryFragment : Fragment(), HistoryAdapter.Listener { private val vm: HistoryViewModel by viewModels() private lateinit var binding: FragmentHistoryListBinding private lateinit var adapter: HistoryAdapter @@ -36,8 +34,10 @@ class HistoryFragment : Fragment(), HistoryItemAdapterListener { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val lm = LinearLayoutManager(requireContext()) + binding.list.layoutManager = lm + binding.list.addItemDecoration(DividerItemDecoration(context, lm.orientation)) adapter = HistoryAdapter(this) - binding.list.layoutManager = LinearLayoutManager(requireContext()) binding.list.adapter = adapter vm.items.observe(viewLifecycleOwner) { adapter.setItems(it) } @@ -50,7 +50,6 @@ class HistoryFragment : Fragment(), HistoryItemAdapterListener { findNavController().navigate(R.id.action_global_pageFragment, bundle) } - @ExperimentalCoroutinesApi class HistoryViewModel( @Suppress("unused") private val savedStateHandle: SavedStateHandle ) : ViewModel() { diff --git a/app/src/main/java/dev/lowrespalmtree/comet/Identities.kt b/app/src/main/java/dev/lowrespalmtree/comet/Identities.kt index c0f21e1..fac08bd 100644 --- a/app/src/main/java/dev/lowrespalmtree/comet/Identities.kt +++ b/app/src/main/java/dev/lowrespalmtree/comet/Identities.kt @@ -1,6 +1,10 @@ package dev.lowrespalmtree.comet +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import android.util.Log import androidx.room.* +import java.security.KeyPairGenerator object Identities { @Entity @@ -10,7 +14,7 @@ object Identities { /** Key to retrieve certificate from the keystore. */ val key: String, /** Label for this identity. */ - val name: String?, + var name: String?, ) @Entity @@ -26,13 +30,44 @@ object Identities { @Dao interface IdentityDao { @Insert - suspend fun insert(vararg entries: Identity) + suspend fun insert(identity: Identity): Long + + @Query("SELECT * FROM Identity WHERE :id = id") + suspend fun get(id: Long): Identity? + + @Query("SELECT * FROM Identity ORDER BY id") + suspend fun getAll(): List + + @Update + suspend fun update(vararg identities: Identity) @Query("SELECT * FROM IdentityUsage WHERE :identityId = identityId") - fun getUsagesFor(identityId: Int): List + suspend fun getUsagesFor(identityId: Int): List } - suspend fun insert(key: String, name: String? = null) { + suspend fun insert(key: String, name: String? = null): Long = Database.INSTANCE.identityDao().insert(Identity(0, key, name)) + + suspend fun get(id: Long): Identity? = + Database.INSTANCE.identityDao().get(id) + + suspend fun getAll(): List = + Database.INSTANCE.identityDao().getAll() + + suspend fun update(vararg identities: Identity) = + Database.INSTANCE.identityDao().update(*identities) + + fun generateClientCert(alias: String) { + val algo = KeyProperties.KEY_ALGORITHM_RSA + val kpg = KeyPairGenerator.getInstance(algo, "AndroidKeyStore") + val purposes = KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY + val spec = KeyGenParameterSpec.Builder(alias, purposes) + .setDigests(KeyProperties.DIGEST_SHA256) + .build() + kpg.initialize(spec) + kpg.generateKeyPair() + Log.i(TAG, "generateClientCert: key pair with alias \"$alias\" has been generated") } + + private const val TAG = "Identities" } \ No newline at end of file diff --git a/app/src/main/java/dev/lowrespalmtree/comet/IdentitiesAdapter.kt b/app/src/main/java/dev/lowrespalmtree/comet/IdentitiesAdapter.kt index 473b0c1..a8d42d3 100644 --- a/app/src/main/java/dev/lowrespalmtree/comet/IdentitiesAdapter.kt +++ b/app/src/main/java/dev/lowrespalmtree/comet/IdentitiesAdapter.kt @@ -1,24 +1,49 @@ package dev.lowrespalmtree.comet +import android.annotation.SuppressLint +import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import dev.lowrespalmtree.comet.Identities.Identity import dev.lowrespalmtree.comet.databinding.FragmentIdentityBinding +import dev.lowrespalmtree.comet.utils.getFancySelectBgRes -class IdentitiesAdapter : RecyclerView.Adapter() { +class IdentitiesAdapter(private val listener: Listener) : + RecyclerView.Adapter() { private var identities = listOf() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HistoryAdapter.ViewHolder { - TODO("Not yet implemented") + interface Listener { + fun onIdentityClick(identity: Identity) } - override fun onBindViewHolder(holder: HistoryAdapter.ViewHolder, position: Int) { - TODO("Not yet implemented") + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = + ViewHolder( + FragmentIdentityBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = identities[position] + holder.binding.labelText.text = item.name.orEmpty() + holder.binding.keyText.text = item.key + holder.binding.container.setOnClickListener { listener.onIdentityClick(item) } } - override fun getItemCount(): Int { - TODO("Not yet implemented") + override fun getItemCount(): Int = identities.size + + @SuppressLint("NotifyDataSetChanged") + fun setIdentities(newIdentities: List) { + identities = newIdentities.toList() + notifyDataSetChanged() } - inner class ViewHolder(val binding: FragmentIdentityBinding) : RecyclerView.ViewHolder(binding.root) + inner class ViewHolder(val binding: FragmentIdentityBinding) : + RecyclerView.ViewHolder(binding.root) { + init { + itemView.setBackgroundResource(getFancySelectBgRes(itemView.context)) + } + } } \ No newline at end of file diff --git a/app/src/main/java/dev/lowrespalmtree/comet/IdentitiesFragment.kt b/app/src/main/java/dev/lowrespalmtree/comet/IdentitiesFragment.kt index 13e8418..4569f62 100644 --- a/app/src/main/java/dev/lowrespalmtree/comet/IdentitiesFragment.kt +++ b/app/src/main/java/dev/lowrespalmtree/comet/IdentitiesFragment.kt @@ -1,14 +1,27 @@ package dev.lowrespalmtree.comet import android.os.Bundle -import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import dev.lowrespalmtree.comet.Identities.Identity import dev.lowrespalmtree.comet.databinding.FragmentIdentitiesBinding +import dev.lowrespalmtree.comet.utils.toast +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.util.* -class IdentitiesFragment : Fragment() { +class IdentitiesFragment : Fragment(), IdentitiesAdapter.Listener, IdentityDialog.Listener { + private val vm: IdentitiesViewModel by viewModels() private lateinit var binding: FragmentIdentitiesBinding + private lateinit var adapter: IdentitiesAdapter override fun onCreateView( inflater: LayoutInflater, @@ -18,4 +31,59 @@ class IdentitiesFragment : Fragment() { binding = FragmentIdentitiesBinding.inflate(layoutInflater) return binding.root } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val lm = LinearLayoutManager(requireContext()) + binding.list.layoutManager = lm + binding.list.addItemDecoration(DividerItemDecoration(context, lm.orientation)) + adapter = IdentitiesAdapter(this) + binding.list.adapter = adapter + + binding.floatingActionButton.setOnClickListener { openNewIdentityEditor() } + + vm.identities.observe(viewLifecycleOwner) { adapter.setIdentities(it) } + + vm.refreshIdentities() + } + + override fun onIdentityClick(identity: Identity) { + IdentityDialog(requireContext(), identity, this).show() + } + + override fun onSaveIdentity(identity: Identity) { + vm.saveIdentity(identity) + } + + private fun openNewIdentityEditor() { + toast(requireContext(), R.string.generating_keypair) + vm.newIdentity.observe(viewLifecycleOwner) { identity -> + vm.newIdentity.removeObservers(viewLifecycleOwner) + IdentityDialog(requireContext(), identity, this).show() + } + vm.createNewIdentity() + } + + class IdentitiesViewModel : ViewModel() { + val identities: MutableLiveData> by lazy { MutableLiveData>() } + val newIdentity: MutableLiveData by lazy { MutableLiveData() } + + fun createNewIdentity() { + viewModelScope.launch(Dispatchers.IO) { + val alias = "identity-${UUID.randomUUID()}" + Identities.generateClientCert(alias) + val newIdentityId = Identities.insert(alias) + newIdentity.postValue(Identities.get(newIdentityId)) + } + } + + fun refreshIdentities() { + viewModelScope.launch(Dispatchers.IO) { + identities.postValue(Identities.getAll()) + } + } + + fun saveIdentity(identity: Identity) { + viewModelScope.launch(Dispatchers.IO) { Identities.update(identity) } + } + } } \ No newline at end of file diff --git a/app/src/main/java/dev/lowrespalmtree/comet/IdentityDialog.kt b/app/src/main/java/dev/lowrespalmtree/comet/IdentityDialog.kt new file mode 100644 index 0000000..3d373b0 --- /dev/null +++ b/app/src/main/java/dev/lowrespalmtree/comet/IdentityDialog.kt @@ -0,0 +1,33 @@ +package dev.lowrespalmtree.comet + +import android.app.AlertDialog +import android.content.Context +import android.view.LayoutInflater +import dev.lowrespalmtree.comet.databinding.DialogIdentityBinding + +class IdentityDialog( + private val context: Context, + private val identity: Identities.Identity, + private val listener: Listener +) { + private lateinit var binding: DialogIdentityBinding + + interface Listener { + fun onSaveIdentity(identity: Identities.Identity) + } + + fun show() { + binding = DialogIdentityBinding.inflate(LayoutInflater.from(context)) + binding.labelInput.setText(identity.name) + binding.aliasText.text = identity.key + AlertDialog.Builder(context) + .setTitle(R.string.edit_identity) + .setView(binding.root) + .setPositiveButton(android.R.string.ok) { _, _ -> + identity.name = binding.labelInput.text.toString() + listener.onSaveIdentity(identity) + } + .create() + .show() + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/lowrespalmtree/comet/IdentityEditorFragment.kt b/app/src/main/java/dev/lowrespalmtree/comet/IdentityEditorFragment.kt new file mode 100644 index 0000000..5b109db --- /dev/null +++ b/app/src/main/java/dev/lowrespalmtree/comet/IdentityEditorFragment.kt @@ -0,0 +1,41 @@ +package dev.lowrespalmtree.comet + +import android.os.Bundle +import androidx.fragment.app.viewModels +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.preference.EditTextPreference +import androidx.preference.PreferenceFragmentCompat +import dev.lowrespalmtree.comet.Identities.Identity +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlin.coroutines.coroutineContext + +class IdentityEditorFragment : PreferenceFragmentCompat() { + private val vm: IdentityEditorViewModel by viewModels() + private lateinit var namePref: EditTextPreference + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.identity_preferences, rootKey) + namePref = findPreference("name")!! + +// vm.identity.observe(viewLifecycleOwner) { +// namePref.apply { +// // TODO +// } +// } +// +// arguments?.getLong("id")?.also { vm.loadIdentity(it) } + } + + class IdentityEditorViewModel : ViewModel() { + val identity: MutableLiveData by lazy { MutableLiveData() } + + fun loadIdentity(id: Long) { + viewModelScope.launch(Dispatchers.IO) { + identity.postValue(Identities.get(id)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/lowrespalmtree/comet/PageAdapter.kt b/app/src/main/java/dev/lowrespalmtree/comet/PageAdapter.kt index 5a66a11..817bad0 100644 --- a/app/src/main/java/dev/lowrespalmtree/comet/PageAdapter.kt +++ b/app/src/main/java/dev/lowrespalmtree/comet/PageAdapter.kt @@ -17,7 +17,7 @@ import dev.lowrespalmtree.comet.databinding.* * it could be a n-to-1 relation: many lines belong to the same block. This of course changes a bit * the way we have to render things. */ -class PageAdapter(private val listener: ContentAdapterListener) : +class PageAdapter(private val listener: Listener) : RecyclerView.Adapter() { private var lines = listOf() @@ -25,7 +25,7 @@ class PageAdapter(private val listener: ContentAdapterListener) : private var blocks = mutableListOf() private var lastBlockCount = 0 - interface ContentAdapterListener { + interface Listener { fun onLinkClick(url: String) } diff --git a/app/src/main/java/dev/lowrespalmtree/comet/PageFragment.kt b/app/src/main/java/dev/lowrespalmtree/comet/PageFragment.kt index ab61bf1..30863f4 100644 --- a/app/src/main/java/dev/lowrespalmtree/comet/PageFragment.kt +++ b/app/src/main/java/dev/lowrespalmtree/comet/PageFragment.kt @@ -22,12 +22,14 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.preference.PreferenceManager import androidx.recyclerview.widget.LinearLayoutManager -import dev.lowrespalmtree.comet.PageAdapter.ContentAdapterListener import dev.lowrespalmtree.comet.databinding.FragmentPageViewBinding +import dev.lowrespalmtree.comet.utils.isConnectedToNetwork +import dev.lowrespalmtree.comet.utils.joinUrls +import dev.lowrespalmtree.comet.utils.toGeminiUri import kotlinx.coroutines.ExperimentalCoroutinesApi @ExperimentalCoroutinesApi -class PageFragment : Fragment(), ContentAdapterListener { +class PageFragment : Fragment(), PageAdapter.Listener { private val vm: PageViewModel by viewModels() private lateinit var binding: FragmentPageViewBinding private lateinit var adapter: PageAdapter @@ -44,8 +46,8 @@ class PageFragment : Fragment(), ContentAdapterListener { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { Log.d(TAG, "onViewCreated") - adapter = PageAdapter(this) binding.contentRecycler.layoutManager = LinearLayoutManager(requireContext()) + adapter = PageAdapter(this) binding.contentRecycler.adapter = adapter binding.addressBar.setOnEditorActionListener { v, id, _ -> onAddressBarAction(v, id) } diff --git a/app/src/main/java/dev/lowrespalmtree/comet/PageViewModel.kt b/app/src/main/java/dev/lowrespalmtree/comet/PageViewModel.kt index 2288e77..e51d1f4 100644 --- a/app/src/main/java/dev/lowrespalmtree/comet/PageViewModel.kt +++ b/app/src/main/java/dev/lowrespalmtree/comet/PageViewModel.kt @@ -13,7 +13,6 @@ import java.net.SocketTimeoutException import java.net.UnknownHostException import java.nio.charset.Charset -@ExperimentalCoroutinesApi class PageViewModel(@Suppress("unused") private val savedStateHandle: SavedStateHandle) : ViewModel() { /** Currently viewed page URL. */ @@ -52,6 +51,7 @@ class PageViewModel(@Suppress("unused") private val savedStateHandle: SavedState * * The URI must be valid, absolute and with a gemini scheme. */ + @ExperimentalCoroutinesApi fun sendGeminiRequest(uri: Uri, connectionTimeout: Int, readTimeout: Int, redirects: Int = 0) { Log.d(TAG, "sendRequest: URI \"$uri\"") loadingUrl = uri @@ -110,6 +110,7 @@ class PageViewModel(@Suppress("unused") private val savedStateHandle: SavedState event.postValue(InputEvent(uri, response.meta)) } + @ExperimentalCoroutinesApi private suspend fun handleSuccessResponse(response: Response, uri: Uri) { state.postValue(State.RECEIVING) diff --git a/app/src/main/java/dev/lowrespalmtree/comet/SettingsFragment.kt b/app/src/main/java/dev/lowrespalmtree/comet/SettingsFragment.kt index 45f9078..a4b09b5 100644 --- a/app/src/main/java/dev/lowrespalmtree/comet/SettingsFragment.kt +++ b/app/src/main/java/dev/lowrespalmtree/comet/SettingsFragment.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.lifecycleScope import androidx.preference.EditTextPreference import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat +import dev.lowrespalmtree.comet.utils.toast import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/app/src/main/java/dev/lowrespalmtree/comet/UiUtils.kt b/app/src/main/java/dev/lowrespalmtree/comet/UiUtils.kt deleted file mode 100644 index 622e19c..0000000 --- a/app/src/main/java/dev/lowrespalmtree/comet/UiUtils.kt +++ /dev/null @@ -1,7 +0,0 @@ -package dev.lowrespalmtree.comet - -import android.content.Context -import android.widget.Toast - -fun toast(context: Context, stringId: Int, length: Int = Toast.LENGTH_SHORT) = - Toast.makeText(context, stringId, length).show() \ No newline at end of file diff --git a/app/src/main/java/dev/lowrespalmtree/comet/NetUtils.kt b/app/src/main/java/dev/lowrespalmtree/comet/utils/Network.kt similarity index 91% rename from app/src/main/java/dev/lowrespalmtree/comet/NetUtils.kt rename to app/src/main/java/dev/lowrespalmtree/comet/utils/Network.kt index 5dd12fa..9c182c2 100644 --- a/app/src/main/java/dev/lowrespalmtree/comet/NetUtils.kt +++ b/app/src/main/java/dev/lowrespalmtree/comet/utils/Network.kt @@ -1,4 +1,4 @@ -package dev.lowrespalmtree.comet +package dev.lowrespalmtree.comet.utils import android.content.Context import android.net.ConnectivityManager diff --git a/app/src/main/java/dev/lowrespalmtree/comet/utils/Ui.kt b/app/src/main/java/dev/lowrespalmtree/comet/utils/Ui.kt new file mode 100644 index 0000000..41f2a20 --- /dev/null +++ b/app/src/main/java/dev/lowrespalmtree/comet/utils/Ui.kt @@ -0,0 +1,18 @@ +package dev.lowrespalmtree.comet.utils + +import android.content.Context +import android.util.TypedValue +import android.widget.Toast +import androidx.annotation.AttrRes +import dev.lowrespalmtree.comet.R + +fun toast(context: Context, stringId: Int, length: Int = Toast.LENGTH_SHORT) = + Toast.makeText(context, stringId, length).show() + +fun getDrawableFromAttr(context: Context, @AttrRes attr: Int) = + TypedValue() + .apply { context.theme.resolveAttribute(attr, this, true) } + .resourceId + +fun getFancySelectBgRes(context: Context) = + getDrawableFromAttr(context, R.attr.selectableItemBackground) \ No newline at end of file diff --git a/app/src/main/java/dev/lowrespalmtree/comet/UriUtils.kt b/app/src/main/java/dev/lowrespalmtree/comet/utils/Uri.kt similarity index 98% rename from app/src/main/java/dev/lowrespalmtree/comet/UriUtils.kt rename to app/src/main/java/dev/lowrespalmtree/comet/utils/Uri.kt index 842fea8..a6063a8 100644 --- a/app/src/main/java/dev/lowrespalmtree/comet/UriUtils.kt +++ b/app/src/main/java/dev/lowrespalmtree/comet/utils/Uri.kt @@ -1,4 +1,4 @@ -package dev.lowrespalmtree.comet +package dev.lowrespalmtree.comet.utils import android.net.Uri diff --git a/app/src/main/res/layout/dialog_identity.xml b/app/src/main/res/layout/dialog_identity.xml new file mode 100644 index 0000000..a4ea2ce --- /dev/null +++ b/app/src/main/res/layout/dialog_identity.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_history_item.xml b/app/src/main/res/layout/fragment_history_item.xml index bb4c7e1..384bd05 100644 --- a/app/src/main/res/layout/fragment_history_item.xml +++ b/app/src/main/res/layout/fragment_history_item.xml @@ -1,43 +1,33 @@ - + android:orientation="vertical"> - + android:textSize="14sp" + android:textColor="@color/text" + android:layout_margin="8dp" + android:fontFamily="@font/preformatted" + android:typeface="monospace" /> - + - - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_identity.xml b/app/src/main/res/layout/fragment_identity.xml index 44ed6f8..ef29a18 100644 --- a/app/src/main/res/layout/fragment_identity.xml +++ b/app/src/main/res/layout/fragment_identity.xml @@ -1,21 +1,32 @@ - + android:orientation="vertical"> - + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:textSize="16sp" + android:textColor="@color/text" /> - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/navigation/main.xml b/app/src/main/res/navigation/main.xml index 8f548f5..0f44285 100644 --- a/app/src/main/res/navigation/main.xml +++ b/app/src/main/res/navigation/main.xml @@ -31,7 +31,16 @@ + android:label="IdentitiesFragment" > + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 99cf327..0ce3424 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -31,6 +31,14 @@ Hello blank fragment Identities + + Messages + Sync + Setup key pair for this identity + Label + Generating client certificate… + Keystore reference + Edit identity \ No newline at end of file diff --git a/app/src/main/res/xml/identity_preferences.xml b/app/src/main/res/xml/identity_preferences.xml new file mode 100644 index 0000000..1dd402c --- /dev/null +++ b/app/src/main/res/xml/identity_preferences.xml @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index 583fe0b..e2596ad 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -1,5 +1,6 @@ - + @@ -25,20 +26,20 @@ + app:title="@string/pref_connection_timeout_title" /> + app:title="@string/pref_read_timeout_title" />