Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implementation of support for multiple configs #182

Draft
wants to merge 10 commits into
base: develop
Choose a base branch
from
Draft
101 changes: 100 additions & 1 deletion app/lint-baseline.xml
Original file line number Diff line number Diff line change
@@ -1,14 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.1.0-alpha07" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-alpha07)" variant="all" version="8.1.0-alpha07">

<issue
id="NewApi"
message="Call requires API level 26 (current min is 24): `java.time.format.DateTimeFormatter#ofPattern`"
errorLine1=" val formatter = DateTimeFormatter.ofPattern(&quot;dd/MM/yyyy, HH:mm:ss&quot;)"
errorLine2=" ~~~~~~~~~">
<location
file="src/main/kotlin/dev/yashgarg/qbit/utils/NumberFormat.kt"
line="33"
column="43"/>
</issue>

<issue
id="NewApi"
message="Call requires API level 26 (current min is 24): `java.time.Instant#ofEpochMilli`"
errorLine1=" val instant = Instant.ofEpochMilli(millisEpoch)"
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/kotlin/dev/yashgarg/qbit/utils/NumberFormat.kt"
line="34"
column="31"/>
</issue>

<issue
id="NewApi"
message="Call requires API level 26 (current min is 24): `java.time.LocalDateTime#ofInstant`"
errorLine1=" val date = LocalDateTime.ofInstant(instant, zoneId ?: ZoneId.systemDefault())"
errorLine2=" ~~~~~~~~~">
<location
file="src/main/kotlin/dev/yashgarg/qbit/utils/NumberFormat.kt"
line="35"
column="34"/>
</issue>

<issue
id="NewApi"
message="Call requires API level 26 (current min is 24): `java.time.ZoneId#systemDefault`"
errorLine1=" val date = LocalDateTime.ofInstant(instant, zoneId ?: ZoneId.systemDefault())"
errorLine2=" ~~~~~~~~~~~~~">
<location
file="src/main/kotlin/dev/yashgarg/qbit/utils/NumberFormat.kt"
line="35"
column="70"/>
</issue>

<issue
id="NewApi"
message="Call requires API level 26 (current min is 24): `java.time.format.DateTimeFormatter#format`"
errorLine1=" return formatter.format(date).trim()"
errorLine2=" ~~~~~~">
<location
file="src/main/kotlin/dev/yashgarg/qbit/utils/NumberFormat.kt"
line="36"
column="26"/>
</issue>

<issue
id="NewApi"
message="Cast from `LocalDateTime` to `TemporalAccessor` requires API level 26 (current min is 24)"
errorLine1=" return formatter.format(date).trim()"
errorLine2=" ~~~~">
<location
file="src/main/kotlin/dev/yashgarg/qbit/utils/NumberFormat.kt"
line="36"
column="33"/>
</issue>

<issue
id="KaptUsageInsteadOfKsp"
message="This library supports using KSP instead of kapt, which greatly improves performance. Learn more: https://developer.android.com/studio/build/migrate-to-ksp"
errorLine1=" kapt(libs.androidx.room.compiler)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="build.gradle.kts"
line="124"
line="123"
column="5"/>
</issue>

Expand Down Expand Up @@ -100,4 +166,37 @@
column="13"/>
</issue>

<issue
id="HardcodedText"
message="Hardcoded string &quot;Delete&quot;, should use `@string` resource"
errorLine1=" android:title=&quot;Delete&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/menu/server_bottombar.xml"
line="24"
column="9"/>
</issue>

<issue
id="HardcodedText"
message="Hardcoded string &quot;Pause&quot;, should use `@string` resource"
errorLine1=" android:title=&quot;Pause&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/menu/server_bottombar.xml"
line="30"
column="9"/>
</issue>

<issue
id="HardcodedText"
message="Hardcoded string &quot;Resume&quot;, should use `@string` resource"
errorLine1=" android:title=&quot;Resume&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/menu/server_bottombar.xml"
line="36"
column="9"/>
</issue>

</issues>
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ class ListTileTextView(context: Context, attrs: AttributeSet) : LinearLayout(con
private var titleTv: TextView
private var subtitleTv: TextView

var title: String? = null
set(value) {
titleTv.text = value
field = value
}

var subtitle: String? = null
set(value) {
subtitleTv.text = value
Expand All @@ -26,8 +32,8 @@ class ListTileTextView(context: Context, attrs: AttributeSet) : LinearLayout(con
subtitleTv = findViewById(R.id.subtitle)

try {
titleTv.text = typedArr.getString(R.styleable.ListTileTextView_title)
subtitleTv.text = subtitle
titleTv.text = title ?: typedArr.getString(R.styleable.ListTileTextView_title)
subtitleTv.text = subtitle ?: typedArr.getString(R.styleable.ListTileTextView_subtitle)

this.setOnLongClickListener {
ClipboardUtil.copyToClipboard(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class HomeFragment : Fragment(R.layout.home_fragment) {

(activity as AppCompatActivity).setSupportActionBar(binding.toolbar)

binding.addServerFab.setOnClickListener {
binding.manageServersFab.setOnClickListener {
findNavController().navigate(R.id.action_homeFragment_to_configFragment)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ class ServerFragment : Fragment(R.layout.server_fragment) {
true
}
R.id.sort_list -> {
findNavController()
.navigate(R.id.action_serverFragment_to_serverManagerFragment)
true
}
R.id.speed_toggle -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package dev.yashgarg.qbit.ui.server.adapter

import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import dev.yashgarg.qbit.R
import dev.yashgarg.qbit.data.models.ServerConfig
import dev.yashgarg.qbit.ui.common.ListTileTextView
import javax.inject.Inject

class ServerConfigAdapter @Inject constructor() :
RecyclerView.Adapter<ServerConfigAdapter.ViewHolder>() {

var configs = emptyList<ServerConfig>()
@SuppressLint("NotifyDataSetChanged")
set(value) {
notifyDataSetChanged()
field = value
}

class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val serverTitle: ListTileTextView = view.findViewById(R.id.server_title)
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.config_item, parent, false)

return ViewHolder(view)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val config = configs[position]

with(holder) {
serverTitle.apply {
title = config.serverName
subtitle = "${config.baseUrl}:${config.port}"
}
}
}

override fun getItemCount() = configs.size
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package dev.yashgarg.qbit.ui.server.manager

import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.google.android.material.transition.MaterialSharedAxis
import dagger.hilt.android.AndroidEntryPoint
import dev.yashgarg.qbit.R
import dev.yashgarg.qbit.databinding.ServerManagerFragmentBinding
import dev.yashgarg.qbit.ui.server.adapter.ServerConfigAdapter
import dev.yashgarg.qbit.utils.viewBinding
import javax.inject.Inject
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach

@AndroidEntryPoint
class ServerManagerFragment : Fragment(R.layout.server_manager_fragment) {
private val binding by viewBinding(ServerManagerFragmentBinding::bind)
private val viewModel by viewModels<ServerManagerViewModel>()

@Inject lateinit var serverConfigAdapter: ServerConfigAdapter

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

with(binding) {
serverRv.adapter = serverConfigAdapter
addServerFab.setOnClickListener {
findNavController().navigate(R.id.action_serverManagerFragment_to_configFragment)
}
}
observeFlows()
}

private fun observeFlows() {
viewModel.uiState
.flowWithLifecycle(viewLifecycleOwner.lifecycle)
.onEach(::render)
.launchIn(viewLifecycleOwner.lifecycleScope)
}

private fun render(state: ServerManagerState) {
with(state) {
if (!configsLoading && !error && configs.isNotEmpty()) {
serverConfigAdapter.configs = configs
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package dev.yashgarg.qbit.ui.server.manager

import dev.yashgarg.qbit.data.models.ServerConfig

data class ServerManagerState(
val configsLoading: Boolean = true,
val configs: List<ServerConfig> = emptyList(),
val error: Boolean = false
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package dev.yashgarg.qbit.ui.server.manager

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dev.yashgarg.qbit.data.daos.ConfigDao
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch

@HiltViewModel
class ServerManagerViewModel @Inject constructor(private val configDao: ConfigDao) : ViewModel() {
private val _uiState = MutableStateFlow(ServerManagerState())
val uiState = _uiState.asStateFlow()

init {
viewModelScope.launch { loadConfigs() }
}

private suspend fun loadConfigs() {
val configs = configDao.getConfigs()
configs
.catch { _uiState.update { it.copy(error = true, configsLoading = false) } }
.collectLatest {
_uiState.update { state -> state.copy(configs = it, configsLoading = false) }
}
}
}
11 changes: 11 additions & 0 deletions app/src/main/res/layout/config_item.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<dev.yashgarg.qbit.ui.common.ListTileTextView
android:id="@+id/server_title"
android:layout_width="match_parent"
android:layout_height="80dp" />

</FrameLayout>
7 changes: 3 additions & 4 deletions app/src/main/res/layout/home_fragment.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@
</com.google.android.material.appbar.AppBarLayout>

<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/add_server_fab"
android:id="@+id/manage_servers_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="@dimen/fab_margin"
android:contentDescription=""
android:text="@string/add_server"
android:textSize="16sp"
app:icon="@drawable/twotone_add_24" />
android:text="@string/get_started"
android:textSize="16sp" />

<TextView
android:id="@+id/textView"
Expand Down
36 changes: 36 additions & 0 deletions app/src/main/res/layout/server_manager_fragment.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">

<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:elevation="0dp"
app:navigationIcon="?attr/homeAsUpIndicator"
app:title="@string/manage_configs" />

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/server_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize"
android:scrollbars="vertical"
android:visibility="visible"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/add_server_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="@dimen/fab_margin"
android:contentDescription=""
android:text="@string/add_server"
android:textSize="16sp"
app:icon="@drawable/twotone_add_24" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Loading