diff --git a/README.md b/README.md index 37cd3a3..57fa634 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,17 @@ Comet is a Gemini browser for Android, compatible back to Android 7.0. +Features +-------- + +- Developed using standard Android SDK practices for an intuitive experience. +- Uses the TLS capabilities of your device. +- Basic browsing capabilities, like click on links to visit them, wow! +- Streaming for all content types. +- History. + + + About ----- diff --git a/app/build.gradle b/app/build.gradle index 57f5c17..040ec03 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -56,8 +56,10 @@ dependencies { implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation "androidx.navigation:navigation-ui-ktx:$nav_version" implementation 'androidx.preference:preference-ktx:1.1.1' + implementation 'androidx.recyclerview:recyclerview:1.2.0' implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-ktx:$room_version" + implementation "androidx.cardview:cardview:1.0.0" implementation 'com.google.android.material:material:1.5.0' kapt "androidx.room:room-compiler:$room_version" testImplementation 'junit:junit:4.13.2' diff --git a/app/src/main/java/dev/lowrespalmtree/comet/History.kt b/app/src/main/java/dev/lowrespalmtree/comet/History.kt index 4fa2f05..e6efe23 100644 --- a/app/src/main/java/dev/lowrespalmtree/comet/History.kt +++ b/app/src/main/java/dev/lowrespalmtree/comet/History.kt @@ -38,5 +38,7 @@ object History { dao.update(entry.also { it.title = title; it.lastVisit = now }) } + suspend fun getAll(): List = Database.INSTANCE.historyEntryDao().getAll() + suspend fun getLast(): HistoryEntry? = Database.INSTANCE.historyEntryDao().getLast() } \ 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 new file mode 100644 index 0000000..b4bbfb2 --- /dev/null +++ b/app/src/main/java/dev/lowrespalmtree/comet/HistoryFragment.kt @@ -0,0 +1,64 @@ +package dev.lowrespalmtree.comet + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.os.bundleOf +import androidx.fragment.app.Fragment +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.LinearLayoutManager +import dev.lowrespalmtree.comet.History.HistoryEntry +import dev.lowrespalmtree.comet.HistoryItemAdapter.HistoryItemAdapterListener +import dev.lowrespalmtree.comet.databinding.FragmentHistoryListBinding +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch + +@ExperimentalCoroutinesApi +class HistoryFragment : Fragment(), HistoryItemAdapterListener { + private lateinit var binding: FragmentHistoryListBinding + private lateinit var historyViewModel: HistoryViewModel + private lateinit var adapter: HistoryItemAdapter + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentHistoryListBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + historyViewModel = ViewModelProvider(this)[HistoryViewModel::class.java] + adapter = HistoryItemAdapter(this) + binding.list.layoutManager = LinearLayoutManager(requireContext()) + binding.list.adapter = adapter + + historyViewModel.items.observe(viewLifecycleOwner, { adapter.setItems(it) }) + + historyViewModel.refreshHistory() + } + + override fun onItemClick(url: String) { + val bundle = bundleOf("url" to url) + findNavController().navigate(R.id.action_global_pageViewFragment, bundle) + } + + @ExperimentalCoroutinesApi + class HistoryViewModel : ViewModel() { + val items: MutableLiveData> + by lazy { MutableLiveData>() } + + fun refreshHistory() { + viewModelScope.launch(Dispatchers.IO) { + items.postValue(History.getAll()) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/lowrespalmtree/comet/HistoryItemAdapter.kt b/app/src/main/java/dev/lowrespalmtree/comet/HistoryItemAdapter.kt new file mode 100644 index 0000000..d101844 --- /dev/null +++ b/app/src/main/java/dev/lowrespalmtree/comet/HistoryItemAdapter.kt @@ -0,0 +1,47 @@ +package dev.lowrespalmtree.comet + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import dev.lowrespalmtree.comet.History.HistoryEntry +import dev.lowrespalmtree.comet.databinding.FragmentHistoryItemBinding + +class HistoryItemAdapter(private val listener: HistoryItemAdapterListener) : + RecyclerView.Adapter() { + private var items = listOf() + + interface HistoryItemAdapterListener { + fun onItemClick(url: String) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = + ViewHolder( + FragmentHistoryItemBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = items[position] + holder.binding.uriText.text = item.uri + 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) } + } + + override fun getItemCount(): Int = items.size + + @SuppressLint("NotifyDataSetChanged") + fun setItems(items: List) { + this.items = items.toList() + notifyDataSetChanged() + } + + inner class ViewHolder(val binding: FragmentHistoryItemBinding) : + RecyclerView.ViewHolder(binding.root) +} \ No newline at end of file diff --git a/app/src/main/java/dev/lowrespalmtree/comet/MainActivity.kt b/app/src/main/java/dev/lowrespalmtree/comet/MainActivity.kt index a082189..b6ac562 100644 --- a/app/src/main/java/dev/lowrespalmtree/comet/MainActivity.kt +++ b/app/src/main/java/dev/lowrespalmtree/comet/MainActivity.kt @@ -1,16 +1,19 @@ package dev.lowrespalmtree.comet import android.os.Bundle +import android.view.MenuItem import androidx.appcompat.app.AppCompatActivity +import androidx.core.os.bundleOf import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.setupWithNavController +import androidx.preference.PreferenceManager import dev.lowrespalmtree.comet.databinding.ActivityMainBinding import kotlinx.coroutines.ExperimentalCoroutinesApi @ExperimentalCoroutinesApi class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding - private var navHost: NavHostFragment? = null + private var nhf: NavHostFragment? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -19,22 +22,15 @@ class MainActivity : AppCompatActivity() { Database.init(applicationContext) // TODO move to App Startup? - navHost = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment? - navHost?.also { binding.drawerNavigation.setupWithNavController(it.navController) } + nhf = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment? + nhf?.also { binding.drawerNavigation.setupWithNavController(it.navController) } } - fun goHome() { - navHost?.navController?.navigate(R.id.action_global_pageViewFragment) - binding.drawerLayout.closeDrawers() - } - - fun openHistory() { - binding.drawerLayout.closeDrawers() - // TODO - } - - fun openSettings() { - navHost?.navController?.navigate(R.id.action_global_settingsFragment) + /** Navigate to the PageViewFragment; this will automatically use the home URL if any. */ + fun goHome(item: MenuItem) { + val bundle = bundleOf() + Preferences.getHomeUrl(this)?.let { bundle.putString("url", it) } + nhf?.navController?.navigate(R.id.action_global_pageViewFragment, bundle) binding.drawerLayout.closeDrawers() } } \ No newline at end of file diff --git a/app/src/main/java/dev/lowrespalmtree/comet/PageViewFragment.kt b/app/src/main/java/dev/lowrespalmtree/comet/PageViewFragment.kt index b906174..2956152 100644 --- a/app/src/main/java/dev/lowrespalmtree/comet/PageViewFragment.kt +++ b/app/src/main/java/dev/lowrespalmtree/comet/PageViewFragment.kt @@ -22,11 +22,12 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.preference.PreferenceManager import androidx.recyclerview.widget.LinearLayoutManager +import dev.lowrespalmtree.comet.ContentAdapter.ContentAdapterListener import dev.lowrespalmtree.comet.databinding.FragmentPageViewBinding import kotlinx.coroutines.ExperimentalCoroutinesApi @ExperimentalCoroutinesApi -class PageViewFragment : Fragment(), ContentAdapter.ContentAdapterListener { +class PageViewFragment : Fragment(), ContentAdapterListener { private lateinit var binding: FragmentPageViewBinding private lateinit var pageViewModel: PageViewModel private lateinit var adapter: ContentAdapter @@ -64,9 +65,11 @@ class PageViewFragment : Fragment(), ContentAdapter.ContentAdapterListener { activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner) { onBackPressed() } - if (visitedUrls.isEmpty()) { - PreferenceManager.getDefaultSharedPreferences(requireContext()) - .getString("home", null)?.let { openUrl(it) } + val url = arguments?.getString("url") + if (!url.isNullOrEmpty()) { + openUrl(url) + } else if (visitedUrls.isEmpty()) { + Preferences.getHomeUrl(requireContext())?.let { openUrl(it) } } } diff --git a/app/src/main/java/dev/lowrespalmtree/comet/Preferences.kt b/app/src/main/java/dev/lowrespalmtree/comet/Preferences.kt new file mode 100644 index 0000000..9294000 --- /dev/null +++ b/app/src/main/java/dev/lowrespalmtree/comet/Preferences.kt @@ -0,0 +1,9 @@ +package dev.lowrespalmtree.comet + +import android.content.Context +import androidx.preference.PreferenceManager + +object Preferences { + fun getHomeUrl(context: Context): String? = + PreferenceManager.getDefaultSharedPreferences(context).getString("home", null) +} \ 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 new file mode 100644 index 0000000..bb4c7e1 --- /dev/null +++ b/app/src/main/res/layout/fragment_history_item.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_history_list.xml b/app/src/main/res/layout/fragment_history_list.xml new file mode 100644 index 0000000..5189eab --- /dev/null +++ b/app/src/main/res/layout/fragment_history_list.xml @@ -0,0 +1,13 @@ + + \ No newline at end of file diff --git a/app/src/main/res/menu/drawer.xml b/app/src/main/res/menu/drawer.xml index 2257920..d2cf40b 100644 --- a/app/src/main/res/menu/drawer.xml +++ b/app/src/main/res/menu/drawer.xml @@ -1,17 +1,16 @@ + android:onClick="goHome" + android:title="@string/home" /> + android:title="@string/settings" /> \ 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 6b8c937..7152e64 100644 --- a/app/src/main/res/navigation/main.xml +++ b/app/src/main/res/navigation/main.xml @@ -15,6 +15,17 @@ app:exitAnim="@anim/nav_default_exit_anim" app:popEnterAnim="@anim/nav_default_pop_enter_anim" app:popExitAnim="@anim/nav_default_pop_exit_anim" /> + +