Skip to content

Commit 352ca26

Browse files
committed
refactor: bottom navigation
1 parent d6fe0ff commit 352ca26

22 files changed

Lines changed: 546 additions & 18 deletions

app/build.gradle.kts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,17 +119,19 @@ dependencies {
119119

120120
implementation("io.insert-koin:koin-android:3.1.3")
121121

122-
implementation("androidx.appcompat:appcompat:1.4.0")
123-
implementation("androidx.constraintlayout:constraintlayout:2.1.2")
122+
implementation("androidx.appcompat:appcompat:1.4.1")
123+
implementation("androidx.constraintlayout:constraintlayout:2.1.3")
124124
implementation("androidx.core:core-ktx:1.7.0")
125-
implementation("androidx.core:core-splashscreen:1.0.0-alpha02")
125+
implementation("androidx.core:core-splashscreen:1.0.0-beta01")
126126
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.4.0")
127-
implementation("androidx.preference:preference-ktx:1.1.1")
127+
implementation("androidx.navigation:navigation-fragment-ktx:2.4.0")
128+
implementation("androidx.navigation:navigation-ui-ktx:2.4.0")
129+
implementation("androidx.preference:preference-ktx:1.2.0")
128130
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
129131
implementation("androidx.room:room-ktx:2.3.0")
130132
implementation("androidx.room:room-runtime:2.3.0")
131133

132-
implementation("com.google.android.material:material:1.5.0-beta01")
134+
implementation("com.google.android.material:material:1.6.0-alpha02")
133135
implementation("com.google.code.gson:gson:2.8.6")
134136

135137
implementation("com.getkeepsafe.taptargetview:taptargetview:1.13.3")

app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
android:name=".ui.main.MainActivity"
3636
android:label="@string/app_name"
3737
android:theme="@style/AppTheme" />
38+
<activity
39+
android:name=".ui.main.MainActivity2"
40+
android:label="@string/app_name"
41+
android:theme="@style/AppTheme" />
3842
<activity
3943
android:name=".ui.params.list.KernelParamListActivity"
4044
android:label="@string/kernel_params"

app/src/main/kotlin/com/androidvip/sysctlgui/Extensions.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package com.androidvip.sysctlgui
33
import android.app.Activity
44
import android.content.Context
55
import android.content.res.ColorStateList
6-
import android.graphics.Color
76
import android.net.Uri
87
import android.os.Bundle
98
import android.os.Handler
@@ -12,15 +11,16 @@ import android.view.View
1211
import android.widget.Toast
1312
import androidx.annotation.AttrRes
1413
import androidx.core.view.ViewCompat
14+
import androidx.fragment.app.Fragment
1515
import com.androidvip.sysctlgui.receivers.TaskerReceiver
1616
import com.google.android.material.color.ColorRoles
1717
import com.google.android.material.color.MaterialColors
1818
import com.google.android.material.snackbar.Snackbar
19-
import kotlinx.coroutines.Dispatchers
20-
import kotlinx.coroutines.withContext
2119
import java.io.InputStream
2220
import kotlin.contracts.ExperimentalContracts
2321
import kotlin.contracts.contract
22+
import kotlinx.coroutines.Dispatchers
23+
import kotlinx.coroutines.withContext
2424

2525
fun View.goAway() { this.visibility = View.GONE }
2626
fun View.hide() { this.visibility = View.INVISIBLE }
@@ -38,6 +38,11 @@ fun Snackbar.showAsLight() {
3838
show()
3939
}
4040

41+
fun Fragment.toast(messageRes: Int, length: Int = Toast.LENGTH_SHORT) {
42+
if (!isAdded) return
43+
requireContext().toast(getString(messageRes), length)
44+
}
45+
4146
fun Context?.toast(messageRes: Int, length: Int = Toast.LENGTH_SHORT) {
4247
if (this == null) return
4348
toast(getString(messageRes), length)

app/src/main/kotlin/com/androidvip/sysctlgui/ui/StartActivity.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.androidvip.sysctlgui.ui
22

33
import android.content.Intent
4-
import android.os.Build
54
import android.os.Bundle
65
import android.view.WindowManager
76
import androidx.appcompat.app.AlertDialog
@@ -17,7 +16,7 @@ import com.androidvip.sysctlgui.domain.repository.AppPrefs
1716
import com.androidvip.sysctlgui.domain.usecase.PerformDatabaseMigrationUseCase
1817
import com.androidvip.sysctlgui.goAway
1918
import com.androidvip.sysctlgui.helpers.Actions
20-
import com.androidvip.sysctlgui.ui.main.MainActivity
19+
import com.androidvip.sysctlgui.ui.main.MainActivity2
2120
import com.androidvip.sysctlgui.ui.params.browse.KernelParamBrowserActivity
2221
import com.androidvip.sysctlgui.ui.params.edit.EditKernelParamActivity
2322
import com.androidvip.sysctlgui.ui.params.list.KernelParamListActivity
@@ -124,7 +123,7 @@ class StartActivity : AppCompatActivity() {
124123
}
125124

126125
else -> {
127-
Intent(this, MainActivity::class.java)
126+
Intent(this, MainActivity2::class.java)
128127
}
129128
}
130129

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.androidvip.sysctlgui.ui.base
2+
3+
import android.os.Bundle
4+
import android.view.LayoutInflater
5+
import android.view.View
6+
import android.view.ViewGroup
7+
import androidx.fragment.app.Fragment
8+
import androidx.viewbinding.ViewBinding
9+
import com.androidvip.sysctlgui.R
10+
11+
typealias Inflate<T> = (LayoutInflater, ViewGroup?, Boolean) -> T
12+
13+
abstract class BaseViewBindingFragment<Binding : ViewBinding>(
14+
private val inflate: Inflate<Binding>
15+
) : Fragment() {
16+
val binding get() = _binding!!
17+
18+
override fun onCreateView(
19+
inflater: LayoutInflater,
20+
container: ViewGroup?,
21+
savedInstanceState: Bundle?
22+
): View? {
23+
_binding = inflate(inflater, container, false)
24+
return binding.root
25+
}
26+
27+
override fun onDestroyView() {
28+
super.onDestroyView()
29+
_binding = null
30+
}
31+
32+
protected val recyclerViewColumns: Int
33+
get() = if (resources.getBoolean(R.bool.is_landscape)) 2 else 1
34+
35+
private var _binding: Binding? = null
36+
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package com.androidvip.sysctlgui.ui.export
2+
3+
import android.app.Activity
4+
import android.content.Intent
5+
import android.os.Bundle
6+
import android.view.LayoutInflater
7+
import android.view.View
8+
import android.view.ViewGroup
9+
import androidx.annotation.StringRes
10+
import androidx.fragment.app.Fragment
11+
import com.androidvip.sysctlgui.R
12+
import com.androidvip.sysctlgui.data.models.SettingsItem
13+
import com.androidvip.sysctlgui.databinding.FragmentExportOptionsBinding
14+
import com.androidvip.sysctlgui.helpers.OnSettingsItemClickedListener
15+
import com.androidvip.sysctlgui.toast
16+
import com.google.android.material.dialog.MaterialAlertDialogBuilder
17+
import org.koin.androidx.viewmodel.ext.android.viewModel
18+
19+
class ExportOptionsFragment : Fragment(), OnSettingsItemClickedListener {
20+
private var _binding: FragmentExportOptionsBinding? = null
21+
private val binding get() = _binding!!
22+
private val viewModel: ExportOptionsViewModel by viewModel()
23+
24+
override fun onCreateView(
25+
inflater: LayoutInflater,
26+
container: ViewGroup?,
27+
savedInstanceState: Bundle?
28+
): View {
29+
_binding = FragmentExportOptionsBinding.inflate(inflater, container, false)
30+
return binding.root
31+
}
32+
33+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
34+
super.onViewCreated(view, savedInstanceState)
35+
36+
val adapter = ExportOptionsItemAdapter(this)
37+
binding.recyclerView.adapter = adapter
38+
adapter.submitList(viewModel.getBackOptionItems())
39+
40+
observeUi()
41+
}
42+
43+
override fun onDestroyView() {
44+
super.onDestroyView()
45+
_binding = null
46+
}
47+
48+
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
49+
if (resultCode != Activity.RESULT_OK) return toast(R.string.error)
50+
51+
when (requestCode) {
52+
RC_IMPORT_USER_PARAMS,
53+
RC_RESTORE_PARAMS -> {
54+
val uri = data?.data ?: return toast(R.string.import_error)
55+
val extension = uri.lastPathSegment.orEmpty()
56+
val stream = requireContext().contentResolver.openInputStream(uri)
57+
?: return toast(R.string.import_error)
58+
viewModel.importParams(stream, extension)
59+
}
60+
61+
RC_EXPORT_USER_PARAMS -> {
62+
val uri = data?.data ?: return toast(R.string.export_error)
63+
viewModel.exportParams(uri, requireContext(), false)
64+
}
65+
66+
RC_BACKUP_PARAMS -> {
67+
val uri = data?.data ?: return toast(R.string.export_error)
68+
viewModel.exportParams(uri, requireContext(), true)
69+
}
70+
}
71+
super.onActivityResult(requestCode, resultCode, data)
72+
}
73+
74+
override fun onSettingsItemClicked(item: SettingsItem, position: Int) {
75+
when (position) {
76+
0 -> viewModel.doWhenImportUserParamsPressed()
77+
1 -> viewModel.doWhenExportUserParamsPressed()
78+
2 -> viewModel.doWhenBackupPressed()
79+
3 -> viewModel.doWhenRestorePressed()
80+
}
81+
}
82+
83+
private fun observeUi() {
84+
viewModel.viewEffect.observe(viewLifecycleOwner) {
85+
when (it) {
86+
is ExportOptionsViewEffect.ImportUserParams -> requestImportFile(RC_IMPORT_USER_PARAMS)
87+
is ExportOptionsViewEffect.ExportUserParams -> requestExportFile(RC_EXPORT_USER_PARAMS)
88+
is ExportOptionsViewEffect.RestoreRuntimeParams -> requestImportFile(RC_RESTORE_PARAMS)
89+
is ExportOptionsViewEffect.BackupRuntimeParams -> requestExportFile(RC_BACKUP_PARAMS)
90+
is ExportOptionsViewEffect.ShowImportError -> showErrorDialog(it.messageRes)
91+
is ExportOptionsViewEffect.ShowImportSuccess -> showSuccessDialog(
92+
getString(R.string.import_success_message, it.paramCount)
93+
)
94+
is ExportOptionsViewEffect.ShowExportError -> showErrorDialog(it.messageRes)
95+
is ExportOptionsViewEffect.ShowExportSuccess -> showSuccessDialog(
96+
getString(R.string.export_success_message)
97+
)
98+
}
99+
}
100+
101+
viewModel.viewState.observe(viewLifecycleOwner) {
102+
binding.progress.visibility = if (it.isLoading) View.VISIBLE else View.GONE
103+
binding.loadingText.visibility = if (it.isLoading) View.VISIBLE else View.GONE
104+
binding.recyclerView.visibility = if (it.isLoading) View.GONE else View.VISIBLE
105+
}
106+
}
107+
108+
private fun requestImportFile(requestCode: Int) {
109+
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
110+
addCategory(Intent.CATEGORY_OPENABLE)
111+
type = "*/*"
112+
}
113+
startActivityForResult(intent, requestCode)
114+
}
115+
116+
private fun requestExportFile(requestCode: Int) {
117+
val extension = if (requestCode == RC_BACKUP_PARAMS) "conf" else "json"
118+
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
119+
addCategory(Intent.CATEGORY_OPENABLE)
120+
type = "*/*"
121+
putExtra(Intent.EXTRA_TITLE, "params.$extension")
122+
}
123+
startActivityForResult(intent, requestCode)
124+
}
125+
126+
private fun showErrorDialog(@StringRes messageRes: Int) {
127+
MaterialAlertDialogBuilder(requireContext())
128+
.setTitle(R.string.error)
129+
.setMessage(messageRes)
130+
.setIcon(R.drawable.ic_close)
131+
.create()
132+
.also { dialog ->
133+
if (isAdded) dialog.show()
134+
}
135+
}
136+
137+
private fun showSuccessDialog(message: String) {
138+
MaterialAlertDialogBuilder(requireContext())
139+
.setTitle(R.string.done)
140+
.setMessage(message)
141+
.setIcon(R.drawable.ic_check)
142+
.create()
143+
.also { dialog ->
144+
if (isAdded) dialog.show()
145+
}
146+
}
147+
148+
companion object {
149+
private const val RC_IMPORT_USER_PARAMS: Int = 1
150+
private const val RC_EXPORT_USER_PARAMS: Int = 2
151+
private const val RC_BACKUP_PARAMS: Int = 3
152+
private const val RC_RESTORE_PARAMS: Int = 4
153+
}
154+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.androidvip.sysctlgui.ui.main
2+
3+
import android.os.Bundle
4+
import android.view.MenuItem
5+
import androidx.appcompat.app.AppCompatActivity
6+
import androidx.core.view.WindowCompat
7+
import androidx.navigation.fragment.NavHostFragment
8+
import androidx.navigation.ui.AppBarConfiguration
9+
import androidx.navigation.ui.setupWithNavController
10+
import com.androidvip.sysctlgui.R
11+
import com.androidvip.sysctlgui.databinding.ActivityMain2Binding
12+
13+
class MainActivity2 : AppCompatActivity() {
14+
private lateinit var binding: ActivityMain2Binding
15+
16+
override fun onCreate(savedInstanceState: Bundle?) {
17+
super.onCreate(savedInstanceState)
18+
WindowCompat.setDecorFitsSystemWindows(window, false)
19+
20+
binding = ActivityMain2Binding.inflate(layoutInflater)
21+
setContentView(binding.root)
22+
setSupportActionBar(binding.toolbar)
23+
24+
setUpNavigation()
25+
}
26+
27+
override fun onOptionsItemSelected(item: MenuItem): Boolean {
28+
when (item.itemId) {
29+
android.R.id.home -> finish()
30+
31+
R.id.action_exit -> {
32+
moveTaskToBack(true)
33+
finish()
34+
}
35+
}
36+
37+
return true
38+
}
39+
40+
override fun onSupportNavigateUp(): Boolean {
41+
return navHost.navController.navigateUp() || super.onSupportNavigateUp()
42+
}
43+
44+
private fun setUpNavigation() = with(binding) {
45+
val navController = navHost.navController
46+
val defaultIds = setOf(R.id.navigationBrowse, R.id.navigationExport, R.id.navigationSettings)
47+
val appBarConfiguration = AppBarConfiguration(defaultIds)
48+
49+
toolbarLayout.setupWithNavController(toolbar, navController, appBarConfiguration)
50+
navView.setupWithNavController(navController)
51+
}
52+
53+
private val navHost: NavHostFragment
54+
get() = supportFragmentManager.findFragmentById(R.id.navHostFragment) as NavHostFragment
55+
}

app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/KernelParamListActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ class KernelParamListActivity : BaseSearchActivity(), OnParamItemClickedListener
9797
refreshList()
9898
}
9999

100-
private fun refreshList() = paramViewModel.getKernelParams()
100+
private fun refreshList() = paramViewModel.requestKernelParams()
101101

102102
private suspend fun filterList(list: List<KernelParam>) = withContext(Dispatchers.Default) {
103103
if (searchExpression.isEmpty()) return@withContext list.toMutableList()

app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/KernelParamListAdapter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class KernelParamListAdapter(
2727
}
2828
}
2929

30-
fun updateData(newList: MutableList<KernelParam>) {
30+
fun updateData(newList: List<KernelParam>) {
3131
submitList(newList)
3232
}
3333

0 commit comments

Comments
 (0)