Skip to content

Commit f663c62

Browse files
author
Simon Schubert
committed
Add see also chips
1 parent 5d08f6c commit f663c62

File tree

26 files changed

+446
-225
lines changed

26 files changed

+446
-225
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
![Icon](https://raw.githubusercontent.com/SimonSchubert/LinuxCommandLibrary/master/art/web_hi_res_144.png)
44

5-
The app currently has **6622** manual pages, **22+** basic categories and a bunch of general terminal tips. It works 100% offline, doesn't need an internet connection and has no tracking software.
5+
The app currently has **6634** manual pages, **22+** basic categories and a bunch of general terminal tips. It works 100% offline, doesn't need an internet connection and has no tracking software.
66

77
[![Play Store](https://raw.githubusercontent.com/SimonSchubert/LinuxCommandBibliotheca/master/art/play_store_badge.png)](https://play.google.com/store/apps/details?id=com.inspiredandroid.linuxcommandbibliotheca)
88
[![F-Droid](https://raw.githubusercontent.com/SimonSchubert/LinuxCommandBibliotheca/master/art/fdroid_badge.png)](https://f-droid.org/en/packages/com.inspiredandroid.linuxcommandbibliotheca/)

android/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ android {
3939
applicationId = "com.inspiredandroid.linuxcommandbibliotheca"
4040
minSdk = 24
4141
targetSdk = 35
42-
versionCode = 96
42+
versionCode = 97
4343
versionName = project.version.toString()
4444
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
4545
}

android/src/main/java/com/inspiredandroid/linuxcommandbibliotheca/IconResources.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ fun BasicGroup.getIconResource(): Int = when (id.toInt()) {
5757
130 -> R.drawable.ic_icons8_synchronize
5858
131 -> R.drawable.ic_arrow_upward_black_24dp
5959
132 -> R.drawable.ic_add_rule
60-
7, 5, 1236, 1248, 1235, 1247, 1246, 1238, 8, 6, 10, 1241, 1245, 9, 1244, 1243, 1, 4, 1242, 2, 3, 0, 1237 -> R.drawable.ic_icon_controller
60+
7, 5, 1236, 1248, 1235, 1247, 1246, 1238, 8, 6, 10, 1241, 1245, 9, 1244, 1243, 1, 4, 1242, 2, 3, 0, 1237, 2630 -> R.drawable.ic_icon_controller
6161
1163 -> R.drawable.ic_vpn_key_black_24dp
6262
1164, 1162, 1161, 1160, 1159 -> R.drawable.ic_icons8_connected
6363
1231, 1232 -> R.drawable.ic_file

android/src/main/java/com/inspiredandroid/linuxcommandbibliotheca/LinuxApplication.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.basiccategories.Ba
55
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.basicgroups.BasicGroupsViewModel
66
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.commanddetail.CommandDetailViewModel
77
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.commandlist.CommandListViewModel
8+
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.search.SearchViewModel
89
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.tips.TipsViewModel
910
import org.koin.android.ext.koin.androidContext
1011
import org.koin.android.ext.koin.androidLogger
@@ -30,6 +31,7 @@ class LinuxApplication : Application() {
3031
viewModel { CommandDetailViewModel(get()) }
3132
viewModel { TipsViewModel() }
3233
viewModel { CommandListViewModel(get()) }
34+
viewModel { SearchViewModel() }
3335

3436
single { PreferenceUtil(androidContext()) }
3537
}

android/src/main/java/com/inspiredandroid/linuxcommandbibliotheca/MainActivity.kt

Lines changed: 110 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import androidx.activity.enableEdgeToEdge
88
import androidx.annotation.DrawableRes
99
import androidx.annotation.StringRes
1010
import androidx.appcompat.app.AppCompatActivity
11+
import androidx.compose.animation.AnimatedVisibility
12+
import androidx.compose.animation.core.tween
13+
import androidx.compose.animation.fadeIn
14+
import androidx.compose.animation.fadeOut
1115
import androidx.compose.foundation.background
1216
import androidx.compose.foundation.layout.Box
1317
import androidx.compose.foundation.layout.padding
@@ -19,7 +23,6 @@ import androidx.compose.runtime.Composable
1923
import androidx.compose.runtime.mutableStateOf
2024
import androidx.compose.runtime.remember
2125
import androidx.compose.ui.Modifier
22-
import androidx.compose.ui.graphics.Color
2326
import androidx.compose.ui.text.TextRange
2427
import androidx.compose.ui.text.input.TextFieldValue
2528
import androidx.navigation.compose.NavHost
@@ -34,6 +37,7 @@ import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.basiccategories.Ba
3437
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.basicgroups.BasicGroupsScreen
3538
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.commanddetail.CommandDetailScreen
3639
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.commandlist.CommandListScreen
40+
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.search.SearchScreen
3741
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.tips.TipsScreen
3842
import com.inspiredandroid.linuxcommandbibliotheca.ui.theme.LinuxTheme
3943
import com.inspiredandroid.linuxcommandbibliotheca.ui.theme.LocalCustomColors
@@ -64,9 +68,9 @@ class MainActivity : AppCompatActivity() {
6468
enableEdgeToEdge(statusBarStyle = SystemBarStyle.dark(android.graphics.Color.TRANSPARENT))
6569
super.onCreate(savedInstanceState)
6670

67-
if (!hasDatabase(this) || !preferenceManager.isDatabaseUpToDate(this)) {
71+
if (!hasDatabase(this) || !preferenceManager.isDatabaseUpToDate()) {
6872
startActivity(Intent(this, InitializeDatabaseActivity::class.java))
69-
preferenceManager.updateDatabaseVersion(this)
73+
preferenceManager.updateDatabaseVersion()
7074
finish()
7175
return
7276
}
@@ -104,6 +108,7 @@ fun LinuxApp() {
104108
TextFieldValue(text = "", selection = TextRange(0)),
105109
)
106110
}
111+
val showSearch = remember { mutableStateOf(false) }
107112
val onNavigate: (String) -> Unit = {
108113
navController.navigate(it)
109114
}
@@ -116,93 +121,123 @@ fun LinuxApp() {
116121
onNavigateBack = {
117122
navController.popBackStack()
118123
},
124+
showSearch = showSearch,
119125
)
120126
},
121127
bottomBar = {
122-
BottomBar(navController)
128+
BottomBar(
129+
navController = navController,
130+
resetSearch = {
131+
searchTextValue.value = TextFieldValue(text = "", selection = TextRange(0))
132+
showSearch.value = false
133+
},
134+
)
123135
},
124136
) { innerPadding ->
125-
126-
NavHost(
127-
navController = navController,
128-
startDestination = Screen.Basics.route,
137+
Box(
129138
modifier = Modifier.padding(innerPadding),
130139
) {
131-
composable(
132-
Screen.Basics.route,
133-
deepLinks = listOf(
134-
navDeepLink { uriPattern = "$DEEPLINK_URI/basics" },
135-
navDeepLink { uriPattern = "$DEEPLINK_URI/basics.html" },
136-
),
137-
) {
138-
BasicCategoriesScreen(onNavigate)
139-
}
140-
composable(
141-
Screen.Commands.route,
142-
deepLinks = listOf(
143-
navDeepLink { uriPattern = "$DEEPLINK_URI/" },
144-
navDeepLink { uriPattern = "$DEEPLINK_URI/index.html" },
145-
),
140+
NavHost(
141+
navController = navController,
142+
startDestination = Screen.Basics.route,
146143
) {
147-
CommandListScreen(
148-
searchText = searchTextValue.value.text,
149-
onNavigate = onNavigate,
150-
)
151-
}
152-
composable(
153-
Screen.Tips.route,
154-
deepLinks = listOf(
155-
navDeepLink { uriPattern = "$DEEPLINK_URI/tips" },
156-
navDeepLink { uriPattern = "$DEEPLINK_URI/tips.html" },
157-
),
158-
) {
159-
TipsScreen(onNavigate)
160-
}
161-
composable(
162-
"basicgroups?categoryId={categoryId}&categoryName={categoryName}",
163-
arguments = listOf(
164-
navArgument("categoryId") { defaultValue = "" },
165-
navArgument("categoryName") {},
166-
),
167-
deepLinks = listOf(
168-
navDeepLink {
169-
uriPattern = "$DEEPLINK_URI/basic/{categoryName}.html"
170-
},
171-
navDeepLink { uriPattern = "$DEEPLINK_URI/basic/{categoryName}" },
172-
),
173-
) { backStackEntry ->
174-
val categoryId = backStackEntry.getCategoryId()
175-
if (categoryId != null) {
176-
BasicGroupsScreen(
177-
categoryId = categoryId,
144+
composable(
145+
Screen.Basics.route,
146+
deepLinks = listOf(
147+
navDeepLink { uriPattern = "$DEEPLINK_URI/basics" },
148+
navDeepLink { uriPattern = "$DEEPLINK_URI/basics.html" },
149+
),
150+
) {
151+
BasicCategoriesScreen(
178152
onNavigate = onNavigate,
179153
)
180-
} else {
181-
// open tips screen on invalid deeplink parameters
182-
TipsScreen(onNavigate)
183154
}
184-
}
185-
composable(
186-
"command?commandId={commandId}&commandName={commandName}",
187-
arguments = listOf(
188-
navArgument("commandId") { defaultValue = "" },
189-
navArgument("commandName") {},
190-
),
191-
deepLinks = listOf(
192-
navDeepLink { uriPattern = "$DEEPLINK_URI/man/{commandName}.html" },
193-
navDeepLink { uriPattern = "$DEEPLINK_URI/man/{commandName}" },
194-
),
195-
) { backStackEntry ->
196-
val commandId = backStackEntry.getCommandId()
197-
if (commandId != null) {
198-
CommandDetailScreen(
199-
commandId = commandId,
155+
composable(
156+
Screen.Commands.route,
157+
deepLinks = listOf(
158+
navDeepLink { uriPattern = "$DEEPLINK_URI/" },
159+
navDeepLink { uriPattern = "$DEEPLINK_URI/index.html" },
160+
),
161+
) {
162+
CommandListScreen(
200163
onNavigate = onNavigate,
201164
)
202-
} else {
203-
// open tips screen on invalid deeplink parameters
165+
}
166+
composable(
167+
Screen.Tips.route,
168+
deepLinks = listOf(
169+
navDeepLink { uriPattern = "$DEEPLINK_URI/tips" },
170+
navDeepLink { uriPattern = "$DEEPLINK_URI/tips.html" },
171+
),
172+
) {
204173
TipsScreen(onNavigate)
205174
}
175+
composable(
176+
"basicgroups?categoryId={categoryId}&categoryName={categoryName}",
177+
arguments = listOf(
178+
navArgument("categoryId") { defaultValue = "" },
179+
navArgument("categoryName") {},
180+
),
181+
deepLinks = listOf(
182+
navDeepLink {
183+
uriPattern = "$DEEPLINK_URI/basic/{categoryName}.html"
184+
},
185+
navDeepLink { uriPattern = "$DEEPLINK_URI/basic/{categoryName}" },
186+
),
187+
) { backStackEntry ->
188+
val categoryId = backStackEntry.getCategoryId()
189+
if (categoryId != null) {
190+
BasicGroupsScreen(
191+
categoryId = categoryId,
192+
onNavigate = onNavigate,
193+
)
194+
} else {
195+
// open tips screen on invalid deeplink parameters
196+
TipsScreen(onNavigate)
197+
}
198+
}
199+
composable(
200+
"command?commandId={commandId}&commandName={commandName}",
201+
arguments = listOf(
202+
navArgument("commandId") { defaultValue = "" },
203+
navArgument("commandName") {},
204+
),
205+
deepLinks = listOf(
206+
navDeepLink { uriPattern = "$DEEPLINK_URI/man/{commandName}.html" },
207+
navDeepLink { uriPattern = "$DEEPLINK_URI/man/{commandName}" },
208+
),
209+
) { backStackEntry ->
210+
val commandId = backStackEntry.getCommandId()
211+
if (commandId != null) {
212+
CommandDetailScreen(
213+
commandId = commandId,
214+
onNavigate = onNavigate,
215+
)
216+
} else {
217+
// open tips screen on invalid deeplink parameters
218+
TipsScreen(onNavigate)
219+
}
220+
}
221+
}
222+
223+
val isSearchVisible = remember(
224+
searchTextValue.value.text,
225+
navBackStackEntry.value?.destination?.route,
226+
) {
227+
searchTextValue.value.text.isNotEmpty() &&
228+
navBackStackEntry.value?.destination?.route?.startsWith("command?") == false
229+
}
230+
AnimatedVisibility(
231+
visible = isSearchVisible,
232+
enter = fadeIn(animationSpec = tween(300)),
233+
exit = fadeOut(animationSpec = tween(durationMillis = 300, delayMillis = 300)), // work around for navigation overlaps
234+
) {
235+
SearchScreen(
236+
searchText = searchTextValue.value.text,
237+
onNavigate = {
238+
navController.navigate(it)
239+
},
240+
)
206241
}
207242
}
208243
}
Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
package com.inspiredandroid.linuxcommandbibliotheca
22

33
import android.content.Context
4+
import android.content.SharedPreferences
45
import androidx.core.content.edit
56
import androidx.preference.PreferenceManager
67

7-
class PreferenceUtil(private val context: Context) {
8+
class PreferenceUtil(context: Context) {
89

9-
val bookmarksIds = getBookmarkIds()
10+
val prefs: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
11+
12+
private val bookmarksIds = getBookmarkIds()
1013

1114
private fun getBookmarkIds(): MutableList<Long> {
12-
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
1315
val bookmarksChain = prefs.getString(KEY_BOOKMARKS, "") ?: ""
1416
return bookmarksChain.split(",").mapNotNull { it.trim().toLongOrNull() }.toMutableList()
1517
}
1618

1719
private fun saveBookmarkIds() {
1820
val bookmarksChain = bookmarksIds.joinToString(separator = ",")
19-
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
2021
prefs.edit { putString(KEY_BOOKMARKS, bookmarksChain) }
2122
}
2223

@@ -32,20 +33,18 @@ class PreferenceUtil(private val context: Context) {
3233

3334
fun hasBookmark(id: Long): Boolean = bookmarksIds.contains(id)
3435

35-
fun isDatabaseUpToDate(context: Context): Boolean {
36-
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
36+
fun isDatabaseUpToDate(): Boolean {
3737
val databaseVersion = prefs.getInt(KEY_DATABASE_VERSION, 0)
3838
return databaseVersion == CURRENT_DATABASE_VERSION
3939
}
4040

41-
fun updateDatabaseVersion(context: Context) {
42-
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
41+
fun updateDatabaseVersion() {
4342
prefs.edit { putInt(KEY_DATABASE_VERSION, CURRENT_DATABASE_VERSION) }
4443
}
4544

4645
companion object {
4746
const val KEY_BOOKMARKS = "KEY_BOOKMARKS"
4847
const val KEY_DATABASE_VERSION = "DATABASE_VERSION"
49-
const val CURRENT_DATABASE_VERSION = 11
48+
const val CURRENT_DATABASE_VERSION = 12
5049
}
5150
}

android/src/main/java/com/inspiredandroid/linuxcommandbibliotheca/ui/composables/BottomBar.kt

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,25 @@ import com.inspiredandroid.linuxcommandbibliotheca.ui.theme.LocalCustomColors
3333
* limitations under the License.
3434
*/
3535

36+
val bottomBarItems = listOf(
37+
Screen.Basics,
38+
Screen.Tips,
39+
Screen.Commands,
40+
)
41+
3642
@Composable
37-
fun BottomBar(navController: NavHostController) {
38-
val items = listOf(
39-
Screen.Basics,
40-
Screen.Tips,
41-
Screen.Commands,
42-
)
43+
fun BottomBar(
44+
navController: NavHostController,
45+
resetSearch: () -> Unit,
46+
) {
4347
// TODO: read current route from navcontroller
4448
val selectedRoute = rememberSaveable { mutableStateOf(Screen.Basics.route) }
4549

4650
BottomNavigation(
4751
backgroundColor = LocalCustomColors.current.navBarBackground,
4852
elevation = 0.dp,
4953
) {
50-
items.forEach { screen ->
54+
bottomBarItems.forEach { screen ->
5155
BottomNavigationItem(
5256
icon = {
5357
Icon(
@@ -61,6 +65,9 @@ fun BottomBar(navController: NavHostController) {
6165
selectedContentColor = MaterialTheme.colors.primary,
6266
unselectedContentColor = MaterialTheme.colors.onSurface,
6367
onClick = {
68+
while (navController.currentBackStackEntry?.destination?.route?.startsWith("command?") == true) {
69+
navController.popBackStack()
70+
}
6471
navController.navigate(screen.route) {
6572
selectedRoute.value = screen.route
6673
popUpTo(navController.graph.findStartDestination().id) {
@@ -69,6 +76,7 @@ fun BottomBar(navController: NavHostController) {
6976
launchSingleTop = true
7077
restoreState = true
7178
}
79+
resetSearch()
7280
},
7381
)
7482
}

0 commit comments

Comments
 (0)