Repo created
This commit is contained in:
parent
ef295a34d2
commit
bbab4c6180
352 changed files with 14422 additions and 1 deletions
78
android/build.gradle.kts
Normal file
78
android/build.gradle.kts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.compose.compiler)
|
||||
}
|
||||
|
||||
group = "com.inspiredandroid.linuxcommandbibliotheca"
|
||||
|
||||
dependencies {
|
||||
implementation(project(":common"))
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.material)
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.accompanist.appcompat.theme)
|
||||
implementation(libs.accompanist.systemuicontroller)
|
||||
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||
implementation(libs.androidx.preference)
|
||||
implementation(libs.androidx.ui.tooling.preview)
|
||||
implementation(libs.androidx.material.icons.core)
|
||||
|
||||
implementation(libs.koin.core)
|
||||
implementation(libs.koin.android)
|
||||
implementation(libs.koin.androidx.compose)
|
||||
implementation(libs.androidx.foundation)
|
||||
implementation(libs.kotlinx.collections.immutable)
|
||||
|
||||
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||
debugImplementation(libs.androidx.ui.test.manifest)
|
||||
debugImplementation(libs.androidx.ui.tooling)
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = 36
|
||||
defaultConfig {
|
||||
applicationId = "com.inspiredandroid.linuxcommandbibliotheca"
|
||||
minSdk = 24
|
||||
targetSdk = 36
|
||||
versionCode =
|
||||
libs.versions.androidVersionCode
|
||||
.get()
|
||||
.toInt()
|
||||
versionName = libs.versions.appVersion.get()
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
getByName("release") {
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro",
|
||||
)
|
||||
}
|
||||
getByName("debug") {
|
||||
isMinifyEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets["main"].assets.setSrcDirs(listOf("../assets"))
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
|
||||
lint {
|
||||
abortOnError = false
|
||||
}
|
||||
namespace = "com.inspiredandroid.linuxcommandbibliotheca"
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.ui.test.assertTextEquals
|
||||
import androidx.compose.ui.test.junit4.createEmptyComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.linuxcommandlibrary.shared.initDatabase
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
/**
|
||||
* Test if deeplinks from/to website open correct screens. If test started the first time
|
||||
* "Always open urls with app" dialog has to be accepted. Or the app has to be signed with the
|
||||
* release key for autoVerify to take effect.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ComposeDeeplinkTests {
|
||||
|
||||
private lateinit var scenario: ActivityScenario<MainActivity>
|
||||
|
||||
@get:Rule
|
||||
val composeTestRule = createEmptyComposeRule()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val context: Context = ApplicationProvider.getApplicationContext()
|
||||
initDatabase(context)
|
||||
|
||||
// Clear bookmarks
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
prefs.edit().putString("KEY_BOOKMARKS", "").apply()
|
||||
}
|
||||
|
||||
private fun openUrl(url: String) {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
scenario = ActivityScenario.launch(intent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBasicCategories() {
|
||||
openUrl("https://linuxcommandlibrary.com/basics")
|
||||
|
||||
composeTestRule.onNodeWithContentDescription("TopAppBarTitle")
|
||||
.assertTextEquals("Basics")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBasicCategory() {
|
||||
openUrl("https://linuxcommandlibrary.com/basic/usersgroups")
|
||||
|
||||
composeTestRule.onNodeWithContentDescription("TopAppBarTitle")
|
||||
.assertTextEquals("Users & Groups")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTips() {
|
||||
openUrl("https://linuxcommandlibrary.com/tips")
|
||||
|
||||
composeTestRule.onNodeWithContentDescription("TopAppBarTitle")
|
||||
.assertTextEquals("Tips")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCommandList() {
|
||||
openUrl("https://linuxcommandlibrary.com/")
|
||||
|
||||
composeTestRule.onNodeWithContentDescription("Back").performClick()
|
||||
composeTestRule.onNodeWithContentDescription("TopAppBarTitle")
|
||||
.assertTextEquals("Commands")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCommandDetail() {
|
||||
openUrl("https://linuxcommandlibrary.com/man/2048")
|
||||
|
||||
composeTestRule.onNodeWithContentDescription("TopAppBarTitle")
|
||||
.assertTextEquals("2048")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
|
||||
import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode
|
||||
import androidx.compose.ui.graphics.asAndroidBitmap
|
||||
import androidx.compose.ui.test.*
|
||||
import androidx.compose.ui.test.junit4.ComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.createEmptyComposeRule
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.linuxcommandlibrary.shared.copyDatabase
|
||||
import com.linuxcommandlibrary.shared.initDatabase
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.koin.java.KoinJavaComponent.inject
|
||||
import java.io.FileOutputStream
|
||||
|
||||
/**
|
||||
* Take screenshots of Phone and Tablet.
|
||||
* Phone = Pixel 2 1080x1920
|
||||
* Tablet 7" = Nexus 7 1200x1920
|
||||
*
|
||||
* Pull images from device to art folder for readme:
|
||||
* run pull_screenshots.sh
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ComposeScreenshots {
|
||||
|
||||
private lateinit var scenario: ActivityScenario<MainActivity>
|
||||
|
||||
@get:Rule
|
||||
val composeTestRule = createEmptyComposeRule()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val context: Context = ApplicationProvider.getApplicationContext()
|
||||
copyDatabase(context)
|
||||
initDatabase(context)
|
||||
|
||||
val dataManager: com.inspiredandroid.linuxcommandbibliotheca.DataManager by inject(com.inspiredandroid.linuxcommandbibliotheca.DataManager::class.java)
|
||||
dataManager.updateDatabaseVersion()
|
||||
|
||||
// Clear bookmarks
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
prefs.edit().putString("KEY_BOOKMARKS", "").apply()
|
||||
|
||||
// Clear files folder
|
||||
InstrumentationRegistry.getInstrumentation().targetContext.filesDir.listFiles()?.forEach {
|
||||
it.deleteRecursively()
|
||||
}
|
||||
scenario = ActivityScenario.launch(MainActivity::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun takeTabletLightAndDarkScreenshots() {
|
||||
takeTabletScreenshots("")
|
||||
scenario = ActivityScenario.launch(MainActivity::class.java)
|
||||
scenario.onActivity {
|
||||
setDefaultNightMode(MODE_NIGHT_YES)
|
||||
}
|
||||
takeTabletScreenshots("-dark")
|
||||
}
|
||||
|
||||
private fun takeTabletScreenshots(prefix: String) {
|
||||
// Tips
|
||||
composeTestRule.onNodeWithText("Tips").performClick()
|
||||
composeTestRule.takeScreenshot("screen-1-tablet$prefix.png")
|
||||
|
||||
// Basics
|
||||
composeTestRule.onNodeWithText("Basics").performClick()
|
||||
composeTestRule.takeScreenshot("screen-2-tablet$prefix.png")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun takePhoneLightAndDarkScreenshots() {
|
||||
takePhoneScreenshots("")
|
||||
scenario = ActivityScenario.launch(MainActivity::class.java)
|
||||
scenario.onActivity {
|
||||
setDefaultNightMode(MODE_NIGHT_YES)
|
||||
}
|
||||
takePhoneScreenshots("-dark")
|
||||
}
|
||||
|
||||
private fun takePhoneScreenshots(prefix: String) {
|
||||
// Command list
|
||||
composeTestRule.onNodeWithText("Commands").performClick()
|
||||
|
||||
composeTestRule.onNodeWithContentDescription("Search").performClick()
|
||||
composeTestRule.onNodeWithContentDescription("SearchField")
|
||||
.performTextInput("mk")
|
||||
|
||||
composeTestRule.takeScreenshot("screen-4$prefix.png")
|
||||
|
||||
// Command detail
|
||||
composeTestRule.onNodeWithText("mkdir").performClick()
|
||||
composeTestRule.onNodeWithText("SYNOPSIS").performClick()
|
||||
composeTestRule.onNodeWithText("DESCRIPTION").performClick()
|
||||
composeTestRule.takeScreenshot("screen-1$prefix.png")
|
||||
|
||||
// Basics
|
||||
composeTestRule.onNodeWithText("Basics").performClick()
|
||||
composeTestRule.onNodeWithText("System information").performClick()
|
||||
composeTestRule.takeScreenshot("screen-3$prefix.png")
|
||||
|
||||
// Tips
|
||||
composeTestRule.onNodeWithText("Tips").performClick()
|
||||
composeTestRule.onNodeWithContentDescription("Scroll")
|
||||
.performScrollToNode(hasText("$ [command] --help"))
|
||||
composeTestRule.takeScreenshot("screen-2$prefix.png")
|
||||
}
|
||||
|
||||
private fun ComposeTestRule.takeScreenshot(file: String) {
|
||||
// TODO: Find better way to wait for animations to finish
|
||||
runBlocking {
|
||||
delay(1000L)
|
||||
}
|
||||
onRoot()
|
||||
.captureToImage()
|
||||
.asAndroidBitmap()
|
||||
.save(file)
|
||||
}
|
||||
|
||||
private fun Bitmap.save(file: String) {
|
||||
val path = InstrumentationRegistry.getInstrumentation().targetContext.filesDir.canonicalPath
|
||||
FileOutputStream("$path/$file").use { out ->
|
||||
compress(Bitmap.CompressFormat.PNG, 100, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.ui.test.*
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.linuxcommandlibrary.shared.copyDatabase
|
||||
import com.linuxcommandlibrary.shared.databaseHelper
|
||||
import com.linuxcommandlibrary.shared.initDatabase
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.koin.java.KoinJavaComponent.inject
|
||||
|
||||
/**
|
||||
* Test for navigation, search and booksmarks. More tests to come
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ComposeTests {
|
||||
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val context: Context = ApplicationProvider.getApplicationContext()
|
||||
copyDatabase(context)
|
||||
initDatabase(context)
|
||||
|
||||
val dataManager: com.inspiredandroid.linuxcommandbibliotheca.DataManager by inject(com.inspiredandroid.linuxcommandbibliotheca.DataManager::class.java)
|
||||
dataManager.updateDatabaseVersion()
|
||||
|
||||
// Clear bookmarks
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
prefs.edit().putString("KEY_BOOKMARKS", "").apply()
|
||||
|
||||
composeTestRule.setContent { LinuxApp() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Click though BottomNavigationBar and assert that TopAppBar titles are correct
|
||||
*/
|
||||
@Test
|
||||
fun testBottomNavigation() {
|
||||
composeTestRule.onNodeWithText("Tips").performClick()
|
||||
composeTestRule.onNodeWithContentDescription("TopAppBarTitle").assertTextEquals("Tips")
|
||||
composeTestRule.onNodeWithText("Commands").performClick()
|
||||
composeTestRule.onNodeWithContentDescription("Back").performClick()
|
||||
composeTestRule.onNodeWithContentDescription("TopAppBarTitle").assertTextEquals("Commands")
|
||||
composeTestRule.onNodeWithText("Basics").performClick()
|
||||
composeTestRule.onNodeWithContentDescription("TopAppBarTitle").assertTextEquals("Basics")
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if info is shown when search for a command that doesn't exist and if command description
|
||||
* is shown when search for an existing command
|
||||
*/
|
||||
@Test
|
||||
fun testSearch() {
|
||||
composeTestRule.onNodeWithText("Commands").performClick()
|
||||
|
||||
composeTestRule.onNodeWithContentDescription("Search").performClick()
|
||||
|
||||
// Search for a command that doesn't exist
|
||||
composeTestRule.onNodeWithContentDescription("SearchField")
|
||||
.performTextInput("CommandThatDoesn'tExist")
|
||||
composeTestRule.onNodeWithText("404 command not found").assertIsDisplayed()
|
||||
composeTestRule.onNodeWithContentDescription("SearchField").performTextClearance()
|
||||
|
||||
// Search for an existing command
|
||||
val firstCommand = databaseHelper.getCommands().last()
|
||||
composeTestRule.onNodeWithContentDescription("SearchField")
|
||||
.performTextInput(firstCommand.name)
|
||||
composeTestRule.onNodeWithText(firstCommand.description).assertIsDisplayed()
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if bookmarks in command detail and command list are shown correctly
|
||||
*/
|
||||
@Test
|
||||
fun testBookmarks() {
|
||||
val firstCommand = databaseHelper.getCommands().first()
|
||||
|
||||
composeTestRule.onNodeWithText("Commands").performClick()
|
||||
|
||||
composeTestRule.onNodeWithContentDescription("Search").performClick()
|
||||
|
||||
// Search for first command and go to command detail screen
|
||||
composeTestRule.onNodeWithContentDescription("SearchField")
|
||||
.performTextInput(firstCommand.name)
|
||||
composeTestRule.onNodeWithText(firstCommand.description).performClick()
|
||||
|
||||
// Click bookmark icon and check if icon/contentDescription changed
|
||||
composeTestRule.onNodeWithContentDescription("Add bookmark").performClick()
|
||||
composeTestRule.mainClock.advanceTimeBy(1000)
|
||||
composeTestRule.waitForIdle()
|
||||
composeTestRule.onNodeWithContentDescription("Remove bookmark").assertIsDisplayed()
|
||||
|
||||
// Go back to search/list and check if bookmark icon is visible
|
||||
composeTestRule.onNodeWithContentDescription("Back").performClick()
|
||||
composeTestRule.onNodeWithContentDescription("Bookmarked").assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBasicsScreen() {
|
||||
val firstBasicCategory = databaseHelper.getBasics().first()
|
||||
|
||||
// Click on first category
|
||||
composeTestRule.onNodeWithText(firstBasicCategory.title).performClick()
|
||||
composeTestRule.onNodeWithContentDescription("TopAppBarTitle")
|
||||
.assertTextEquals(firstBasicCategory.title)
|
||||
|
||||
val basicGroup = databaseHelper.getBasicGroupsByQuery(firstBasicCategory.id).first()
|
||||
|
||||
// Click on first group
|
||||
composeTestRule.onNodeWithText(basicGroup.description).performClick()
|
||||
|
||||
// Check if commands of group expanded and therefore share icon(s) are visible
|
||||
composeTestRule.onAllNodesWithContentDescription("Share").assertAny(isEnabled())
|
||||
}
|
||||
}
|
||||
67
android/src/main/AndroidManifest.xml
Normal file
67
android/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:installLocation="preferExternal">
|
||||
|
||||
<application
|
||||
android:name=".LinuxApplication"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="linuxcommandlibrary.com"
|
||||
android:path="/tips.html"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="linuxcommandlibrary.com"
|
||||
android:path="/tips"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="linuxcommandlibrary.com"
|
||||
android:path="/basics.html"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="linuxcommandlibrary.com"
|
||||
android:path="/basics"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="linuxcommandlibrary.com"
|
||||
android:path="/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="linuxcommandlibrary.com"
|
||||
android:path="/index.html"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="linuxcommandlibrary.com"
|
||||
android:pathPrefix="/man/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="linuxcommandlibrary.com"
|
||||
android:pathPrefix="/basic/"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".InitializeDatabaseActivity"
|
||||
android:exported="true">
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
|
||||
class DataManager(context: Context) {
|
||||
|
||||
val prefs: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
private val bookmarksIds = getBookmarkIds()
|
||||
|
||||
private fun getBookmarkIds(): MutableList<Long> {
|
||||
val bookmarksChain = prefs.getString(KEY_BOOKMARKS, "") ?: ""
|
||||
return bookmarksChain.split(",").mapNotNull { it.trim().toLongOrNull() }.toMutableList()
|
||||
}
|
||||
|
||||
private fun saveBookmarkIds() {
|
||||
val bookmarksChain = bookmarksIds.joinToString(separator = ",")
|
||||
prefs.edit { putString(KEY_BOOKMARKS, bookmarksChain) }
|
||||
}
|
||||
|
||||
fun addBookmark(id: Long) {
|
||||
bookmarksIds.add(id)
|
||||
saveBookmarkIds()
|
||||
}
|
||||
|
||||
fun removeBookmark(id: Long) {
|
||||
bookmarksIds.remove(id)
|
||||
saveBookmarkIds()
|
||||
}
|
||||
|
||||
fun hasBookmark(id: Long): Boolean = bookmarksIds.contains(id)
|
||||
|
||||
fun isDatabaseUpToDate(): Boolean {
|
||||
val databaseVersion = prefs.getInt(KEY_DATABASE_VERSION, 0)
|
||||
return databaseVersion == CURRENT_DATABASE_VERSION
|
||||
}
|
||||
|
||||
fun updateDatabaseVersion() {
|
||||
prefs.edit { putInt(KEY_DATABASE_VERSION, CURRENT_DATABASE_VERSION) }
|
||||
}
|
||||
|
||||
fun setAutoExpandSections(autoExpand: Boolean) {
|
||||
prefs.edit { putBoolean(KEY_AUTO_EXPAND_SECTIONS, autoExpand) }
|
||||
}
|
||||
|
||||
fun isAutoExpandSections(): Boolean = prefs.getBoolean(KEY_AUTO_EXPAND_SECTIONS, false)
|
||||
|
||||
companion object {
|
||||
const val KEY_BOOKMARKS = "KEY_BOOKMARKS"
|
||||
const val KEY_DATABASE_VERSION = "DATABASE_VERSION"
|
||||
const val KEY_AUTO_EXPAND_SECTIONS = "auto_expand_sections"
|
||||
const val CURRENT_DATABASE_VERSION = 32
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca
|
||||
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import com.linuxcommandlibrary.shared.databaseHelper
|
||||
import com.linuxcommandlibrary.shared.getHtmlFileName
|
||||
|
||||
fun NavBackStackEntry.getCommandId(): Long? {
|
||||
var commandId =
|
||||
arguments?.getString("commandId")?.toLongOrNull()
|
||||
if (commandId == null) {
|
||||
// get id by command name when opened via deeplink
|
||||
val commandName = arguments?.getString("commandName") ?: ""
|
||||
val command = databaseHelper.getCommand(commandName)
|
||||
commandId = command?.id
|
||||
}
|
||||
return commandId
|
||||
}
|
||||
|
||||
fun NavBackStackEntry.getCategoryId(): Long? {
|
||||
var categoryId =
|
||||
arguments?.getString("categoryId")?.toLongOrNull()
|
||||
if (categoryId == null) {
|
||||
// get id by category name when opened via deeplink
|
||||
val categoryName =
|
||||
arguments?.getString("deepLinkCategoryName") ?: ""
|
||||
val categories = databaseHelper.getBasics()
|
||||
categoryId = categories.firstOrNull { it.getHtmlFileName() == categoryName }?.id
|
||||
}
|
||||
return categoryId
|
||||
}
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca
|
||||
|
||||
import databases.BasicCategory
|
||||
import databases.BasicGroup
|
||||
|
||||
fun BasicGroup.getIconResource(): Int = when (id.toInt()) {
|
||||
1205 -> R.drawable.ic_info_40dp
|
||||
1206 -> R.drawable.ic_icons8_restore_window
|
||||
1207 -> R.drawable.ic_icons8_compass
|
||||
1208 -> R.drawable.ic_icons8_console
|
||||
1212 -> R.drawable.ic_icons8_hand_with_pen
|
||||
1209 -> R.drawable.ic_info_40dp
|
||||
1211 -> R.drawable.ic_icons8_keyboard
|
||||
1210 -> R.drawable.ic_icons8_compass
|
||||
1214 -> R.drawable.ic_info_40dp
|
||||
1215 -> R.drawable.ic_icons8_keyboard
|
||||
1213 -> R.drawable.ic_icons8_compass
|
||||
1199 -> R.drawable.ic_file
|
||||
1200 -> R.drawable.ic_icons8_compass
|
||||
1201 -> R.drawable.ic_icons8_copy
|
||||
1202 -> R.drawable.ic_icons8_hand_with_pen
|
||||
1203 -> R.drawable.ic_search_40dp
|
||||
1204 -> R.drawable.ic_icons8_restore_window
|
||||
1165 -> R.drawable.ic_icons8_copy
|
||||
1166 -> R.drawable.ic_icons8_new
|
||||
1167 -> R.drawable.ic_icons8_save
|
||||
1168 -> R.drawable.ic_info_40dp
|
||||
1169 -> R.drawable.ic_icons8_plus
|
||||
1170 -> R.drawable.ic_icons8_numbered_list
|
||||
1171 -> R.drawable.ic_delete_black_24dp
|
||||
1172 -> R.drawable.ic_icons8_merge
|
||||
1173 -> R.drawable.ic_icons8_arrow
|
||||
1174 -> R.drawable.ic_icons8_arrow
|
||||
1175 -> R.drawable.ic_icons8_plus
|
||||
1176 -> R.drawable.ic_delete_black_24dp
|
||||
1177 -> R.drawable.ic_icons8_save
|
||||
1178 -> R.drawable.ic_available_updates
|
||||
1179 -> R.drawable.ic_icons8_plus
|
||||
1180 -> R.drawable.ic_icons8_user_male_circle
|
||||
1181 -> R.drawable.ic_icons8_user_male_circle
|
||||
1182 -> R.drawable.ic_info_40dp
|
||||
1183 -> R.drawable.ic_icons8_undo
|
||||
1184 -> R.drawable.ic_icons8_hide
|
||||
1185 -> R.drawable.ic_icons8_save
|
||||
1186 -> R.drawable.ic_icons8_save
|
||||
1187 -> R.drawable.ic_icons8_visible
|
||||
1188 -> R.drawable.ic_delete_black_24dp
|
||||
1189 -> R.drawable.ic_delete_black_24dp
|
||||
1190 -> R.drawable.ic_delete_black_24dp
|
||||
1191 -> R.drawable.ic_delete_black_24dp
|
||||
1192 -> R.drawable.ic_delete_black_24dp
|
||||
125 -> R.drawable.ic_file_download_black_24dp
|
||||
126 -> R.drawable.ic_file
|
||||
127 -> R.drawable.ic_delete_black_24dp
|
||||
128 -> R.drawable.ic_search_40dp
|
||||
129 -> R.drawable.ic_info_40dp
|
||||
130 -> R.drawable.ic_icons8_synchronize
|
||||
131 -> R.drawable.ic_arrow_upward_black_24dp
|
||||
132 -> R.drawable.ic_add_rule
|
||||
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
|
||||
1163 -> R.drawable.ic_vpn_key_black_24dp
|
||||
1164, 1162, 1161, 1160, 1159 -> R.drawable.ic_icons8_connected
|
||||
1231, 1232 -> R.drawable.ic_file
|
||||
1158 -> R.drawable.ic_icons8_circled_pause
|
||||
26, 91, 92, 93, 94, 95 -> R.drawable.ic_search_40dp
|
||||
96, 97, 98, 100, 2399, 99 -> R.drawable.ic_file
|
||||
41 -> R.drawable.ic_electronics
|
||||
42 -> R.drawable.ic_battery_90_black_24dp
|
||||
43, 44 -> R.drawable.ic_bluetooth_black_24dp
|
||||
45 -> R.drawable.ic_icons8_network_card
|
||||
46 -> R.drawable.ic_memory_slot
|
||||
56, 65 -> R.drawable.ic_icons8_tv_off
|
||||
57 -> R.drawable.ic_icons8_tv_on
|
||||
47 -> R.drawable.ic_icons8_linux
|
||||
48 -> R.drawable.ic_icons8_root_server
|
||||
49 -> R.drawable.ic_usb_black_48dp
|
||||
50 -> R.drawable.ic_icons8_flow_chart
|
||||
51 -> R.drawable.ic_ip_address
|
||||
58 -> R.drawable.ic_refresh_black_24dp
|
||||
59 -> R.drawable.ic_power_settings_new_black_24dp
|
||||
55 -> R.drawable.ic_icons8_calendar_1
|
||||
52, 60 -> R.drawable.ic_timer_black_24dp
|
||||
53 -> R.drawable.ic_icons8_hdd
|
||||
61 -> R.drawable.ic_stop_bluetooth
|
||||
62 -> R.drawable.ic_bluetooth_start
|
||||
63 -> R.drawable.ic_stop_wifi
|
||||
64 -> R.drawable.ic_wifi_start
|
||||
28 -> R.drawable.ic_icons8_work
|
||||
29 -> R.drawable.ic_icons8_undo
|
||||
31 -> R.drawable.ic_icons8_kitchen_scales
|
||||
81 -> R.drawable.ic_file_download_white
|
||||
191 -> R.drawable.ic_vip_lookup_white_48dp
|
||||
83 -> R.drawable.ic_icons8_ping_pong
|
||||
189 -> R.drawable.ic_settings_black_24dp
|
||||
27, 32 -> R.drawable.ic_icons8_show_property
|
||||
16 -> R.drawable.ic_file_move_white
|
||||
15 -> R.drawable.ic_file_copy_white_48dp
|
||||
20 -> R.drawable.ic_change_folder_white
|
||||
13 -> R.drawable.ic_file_content_white
|
||||
19 -> R.drawable.ic_folder_list_white
|
||||
18 -> R.drawable.ic_delete_folder_white_48dp
|
||||
17 -> R.drawable.ic_create_new_folder_white
|
||||
12 -> R.drawable.ic_delete_file_white
|
||||
11 -> R.drawable.ic_create_file_white
|
||||
14 -> R.drawable.ic_file_edit_white_48dp
|
||||
21 -> R.drawable.ic_icons8_home
|
||||
22 -> R.drawable.ic_icons8_mother
|
||||
23 -> R.drawable.ic_icons8_downloads_folder
|
||||
25 -> R.drawable.ic_file_link_white_48dp
|
||||
30 -> R.drawable.ic_icons8_exe
|
||||
102 -> R.drawable.ic_remove_user_group
|
||||
101 -> R.drawable.ic_icons8_add_user_group
|
||||
103 -> R.drawable.ic_edit_group
|
||||
107 -> R.drawable.ic_user_password
|
||||
104 -> R.drawable.ic_icons8_add_user
|
||||
105 -> R.drawable.ic_icons8_remove_user_male
|
||||
108 -> R.drawable.ic_icons8_moderator_male
|
||||
106 -> R.drawable.ic_icons8_edit_user
|
||||
110 -> R.drawable.ic_add_user_to_group_white_48dp
|
||||
111 -> R.drawable.ic_add_user_to_group_white_48dp
|
||||
112 -> R.drawable.ic_remove_user_from_group_white_48dp
|
||||
114 -> R.drawable.ic_list_user_white_48dp
|
||||
33 -> R.drawable.ic_icons8_reuse
|
||||
34 -> R.drawable.ic_icons8_delete_trash
|
||||
35 -> R.drawable.ic_icons8_add_trash
|
||||
36 -> R.drawable.ic_icons8_file_preview
|
||||
37 -> R.drawable.ic_file_permission_white_48dp
|
||||
38 -> R.drawable.ic_icons8_user_folder
|
||||
24 -> R.drawable.ic_folder_path_white
|
||||
39 -> R.drawable.ic_icons8_user_male_circle
|
||||
40 -> R.drawable.ic_icons8_group_foreground_selected
|
||||
113 -> R.drawable.ic_list_groups_white_48dp
|
||||
109 -> R.drawable.ic_info_40dp
|
||||
1193 -> R.drawable.ic_icons8_print_file
|
||||
1194 -> R.drawable.ic_icons8_visible
|
||||
1195 -> R.drawable.ic_icons8_cancel
|
||||
1196 -> R.drawable.ic_info_40dp
|
||||
1197 -> R.drawable.ic_icons8_circled_play
|
||||
1198 -> R.drawable.ic_icons8_circled_pause
|
||||
1230 -> R.drawable.ic_icons8_plus
|
||||
1229 -> R.drawable.ic_delete_black_24dp
|
||||
1228 -> R.drawable.ic_icons8_plus
|
||||
1227 -> R.drawable.ic_icons8_plus
|
||||
1226 -> R.drawable.ic_icons8_print
|
||||
80 -> R.drawable.ic_list_interfaces_white_48dp
|
||||
82 -> R.drawable.ic_vip_lookup_white_48dp
|
||||
84 -> R.drawable.ic_settings_black_24dp
|
||||
85 -> R.drawable.ic_icons8_visible
|
||||
86 -> R.drawable.ic_fingerprint_black_24dp
|
||||
88 -> R.drawable.ic_dns_black_24dp
|
||||
89 -> R.drawable.ic_ip_address
|
||||
90 -> R.drawable.ic_list_sockets_white_48dp
|
||||
143 -> R.drawable.ic_search_executeable_man_white_48dp
|
||||
87 -> R.drawable.ic_search_source_man_white_48dp
|
||||
320 -> R.drawable.ic_icons8_show_property
|
||||
138 -> R.drawable.ic_fingerprint_black_24dp
|
||||
116 -> R.drawable.ic_vpn_key_black_24dp
|
||||
221 -> R.drawable.ic_list_sockets_white_48dp
|
||||
137 -> R.drawable.ic_file_edit_white_48dp
|
||||
71 -> R.drawable.ic_volume_off_black_24dp
|
||||
67 -> R.drawable.ic_webcam_white_48dp
|
||||
66 -> R.drawable.ic_desktop_windows_black_24dp
|
||||
68 -> R.drawable.ic_icons8_talk
|
||||
69, 70 -> R.drawable.ic_volume_up_black_24dp
|
||||
72, 73, 75, 76, 77, 78, 1239, 1240, 74 -> R.drawable.ic_switch_video_white_48dp
|
||||
430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440 -> R.drawable.ic_videogame_asset_black_24dp
|
||||
443 -> R.drawable.ic_vpn_key_black_24dp
|
||||
450 -> R.drawable.ic_wifi_black_24dp
|
||||
446, 1495, 457, 1516, 1509 -> R.drawable.ic_info_black_24dp
|
||||
451 -> R.drawable.ic_report_black_24dp
|
||||
445 -> R.drawable.ic_loupe_black_24dp
|
||||
444 -> R.drawable.ic_fingerprint_black_24dp
|
||||
448 -> R.drawable.ic_public_black_24dp
|
||||
447 -> R.drawable.ic_healing_black_24dp
|
||||
441 -> R.drawable.ic_flash_on_black_24dp
|
||||
449 -> R.drawable.ic_storage_black_24dp
|
||||
454 -> R.drawable.ic_file
|
||||
456 -> R.drawable.ic_search_black_24dp
|
||||
460 -> R.drawable.ic_add_rule
|
||||
453 -> R.drawable.ic_file_download_black_24dp
|
||||
458 -> R.drawable.ic_available_updates
|
||||
459 -> R.drawable.ic_arrow_upward_black_24dp
|
||||
455, 1519, 1511, 1528, 1529, 1530, 1531, 1532 -> R.drawable.ic_delete_black_24dp
|
||||
461 -> R.drawable.ic_icons8_show_property
|
||||
1492 -> R.drawable.ic_icons8_home
|
||||
1493 -> R.drawable.ic_icons8_mother
|
||||
1496 -> R.drawable.ic_icons8_work
|
||||
1497 -> R.drawable.ic_icons8_undo
|
||||
1494 -> R.drawable.ic_icons8_calendar_1
|
||||
1503 -> R.drawable.ic_vpn_key_black_24dp
|
||||
1487, 1488, 1489, 1490, 1491 -> R.drawable.ic_file
|
||||
1498 -> R.drawable.ic_icons8_cancel
|
||||
1499, 1500, 1501, 1502, 1504 -> R.drawable.ic_icons8_connected
|
||||
1505 -> R.drawable.ic_icons8_copy
|
||||
1515 -> R.drawable.ic_icons8_new
|
||||
1506, 1525, 1512 -> R.drawable.ic_icons8_save
|
||||
1517, 1510, 1523 -> R.drawable.ic_icons8_plus
|
||||
1518 -> R.drawable.ic_icons8_numbered_list
|
||||
1522 -> R.drawable.ic_icons8_merge
|
||||
1520, 1521 -> R.drawable.ic_icons8_arrow
|
||||
1524 -> R.drawable.ic_icons8_synchronize
|
||||
1507, 1508 -> R.drawable.ic_icons8_user_male_circle
|
||||
1513 -> R.drawable.ic_icons8_redo
|
||||
1514 -> R.drawable.ic_icons8_hide
|
||||
1527 -> R.drawable.ic_icons8_visible
|
||||
178 -> R.drawable.ic_icons8_show_property
|
||||
154 -> R.drawable.ic_icons8_exe
|
||||
141 -> R.drawable.ic_icons8_reuse
|
||||
144 -> R.drawable.ic_icons8_delete_trash
|
||||
411 -> R.drawable.ic_icons8_add_trash
|
||||
247 -> R.drawable.ic_icons8_visible
|
||||
169 -> R.drawable.ic_icons8_cancel
|
||||
3731 -> R.drawable.ic_info_black_24dp
|
||||
186 -> R.drawable.ic_icons8_circled_play
|
||||
187 -> R.drawable.ic_icons8_circled_pause
|
||||
226 -> R.drawable.ic_icons8_file_preview
|
||||
231 -> R.drawable.ic_file_permission_white_48dp
|
||||
1217 -> R.drawable.ic_icons8_coin_wallet
|
||||
1218 -> R.drawable.ic_icons8_bot
|
||||
1216 -> R.drawable.ic_icons8_golden_fever
|
||||
1233 -> R.drawable.ic_icons8_teacher_hiring
|
||||
117 -> R.drawable.ic_fingerprint_black_24dp
|
||||
115 -> R.drawable.ic_flash_on_black_24dp
|
||||
118 -> R.drawable.ic_loupe_black_24dp
|
||||
119 -> R.drawable.ic_info_40dp
|
||||
120 -> R.drawable.ic_healing_black_24dp
|
||||
121 -> R.drawable.ic_public_black_24dp
|
||||
122 -> R.drawable.ic_storage_black_24dp
|
||||
123 -> R.drawable.ic_wifi_black_24dp
|
||||
124 -> R.drawable.ic_report_black_24dp
|
||||
79 -> R.drawable.ic_vpn_key_black_24dp
|
||||
54 -> R.drawable.ic_icons8_document
|
||||
1222, 1221, 1223 -> R.drawable.ic_icon_mouse
|
||||
1220 -> R.drawable.ic_icons8_clipboard
|
||||
1219 -> R.drawable.ic_icons8_treatment
|
||||
1225, 1224 -> R.drawable.ic_icons8_keyboard
|
||||
2403 -> R.drawable.ic_icon_mouse
|
||||
2400 -> R.drawable.ic_icons8_keyboard
|
||||
2402 -> R.drawable.ic_file
|
||||
else -> R.drawable.ic_icons8_console
|
||||
}
|
||||
|
||||
fun BasicCategory.getIconResource(): Int = when (title) {
|
||||
"One-liners" -> R.drawable.ic_icons8_hand_with_pen
|
||||
"System information" -> R.drawable.ic_icon_system_task
|
||||
"System control" -> R.drawable.ic_settings_black_40dp
|
||||
"Users & Groups" -> R.drawable.ic_icon_user
|
||||
"Files & Folders" -> R.drawable.ic_file
|
||||
"Printing" -> R.drawable.ic_icons8_print
|
||||
"Network" -> R.drawable.ic_network_card_40dp
|
||||
"Search & Find" -> R.drawable.ic_search_40dp
|
||||
"GIT" -> R.drawable.ic_icon_git
|
||||
"SSH" -> R.drawable.ic_icons8_console
|
||||
"Video & Audio" -> R.drawable.ic_video_trimming_40dp
|
||||
"Package manager" -> R.drawable.ic_package_40
|
||||
"Hacking tools" -> R.drawable.ic_icon_skull
|
||||
"Terminal games" -> R.drawable.ic_icon_controller
|
||||
"VIM Texteditor", "Emacs Texteditor", "Nano Texteditor", "Pico Texteditor", "Micro Texteditor" -> R.drawable.ic_icons8_text
|
||||
"Crypto currencies" -> R.drawable.ic_icon_bitcoin
|
||||
"Input" -> R.drawable.ic_icon_mouse
|
||||
"JSON" -> R.drawable.ic_icon_json
|
||||
"Fun" -> R.drawable.ic_icon_fun
|
||||
else -> R.drawable.ic_icon_mouse
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.InitializeDatabaseScreen
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.theme.LinuxTheme
|
||||
|
||||
/* Copyright 2022 Simon Schubert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class InitializeDatabaseActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContent {
|
||||
LinuxTheme {
|
||||
InitializeDatabaseScreen {
|
||||
startActivity(Intent(this, MainActivity::class.java))
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca
|
||||
|
||||
import android.app.Application
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.basiccategories.BasicCategoriesViewModel
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.basicgroups.BasicGroupsViewModel
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.commanddetail.CommandDetailViewModel
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.commandlist.CommandListViewModel
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.search.SearchViewModel
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.tips.TipsViewModel
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.android.ext.koin.androidLogger
|
||||
import org.koin.core.context.GlobalContext.startKoin
|
||||
import org.koin.core.module.dsl.*
|
||||
import org.koin.dsl.module
|
||||
|
||||
class LinuxApplication : Application() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
startKoin {
|
||||
androidLogger()
|
||||
androidContext(this@LinuxApplication)
|
||||
modules(appModule)
|
||||
}
|
||||
}
|
||||
|
||||
private val appModule = module {
|
||||
single { DataManager(androidContext()) }
|
||||
|
||||
viewModel { BasicGroupsViewModel(get()) }
|
||||
viewModel { BasicCategoriesViewModel() }
|
||||
viewModel { (commandId: Long) -> CommandDetailViewModel(commandId, get()) }
|
||||
viewModel { TipsViewModel() }
|
||||
viewModel { CommandListViewModel(get()) }
|
||||
viewModel { SearchViewModel() }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.SystemBarStyle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.navArgument
|
||||
import androidx.navigation.navDeepLink
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.composables.BottomBar
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.composables.TopBar
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.basiccategories.BasicCategoriesScreen
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.basicgroups.BasicGroupsScreen
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.commanddetail.CommandDetailScreen
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.commandlist.CommandListScreen
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.search.SearchScreen
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.tips.TipsScreen
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.theme.LinuxTheme
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.theme.LocalCustomColors
|
||||
import com.linuxcommandlibrary.shared.hasDatabase
|
||||
import com.linuxcommandlibrary.shared.initDatabase
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
/* Copyright 2022 Simon Schubert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private val dataManager by inject<DataManager>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge(statusBarStyle = SystemBarStyle.dark(android.graphics.Color.TRANSPARENT), navigationBarStyle = SystemBarStyle.dark(android.graphics.Color.TRANSPARENT))
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (!hasDatabase(this) || !dataManager.isDatabaseUpToDate()) {
|
||||
startActivity(Intent(this, InitializeDatabaseActivity::class.java))
|
||||
dataManager.updateDatabaseVersion()
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
initDatabase(this)
|
||||
|
||||
setContent {
|
||||
LinuxTheme {
|
||||
Box(
|
||||
Modifier
|
||||
.background(MaterialTheme.colors.primary)
|
||||
.statusBarsPadding(),
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.background(LocalCustomColors.current.navBarBackground)
|
||||
.systemBarsPadding(),
|
||||
) {
|
||||
LinuxApp()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const val DEEPLINK_URI = "https://linuxcommandlibrary.com"
|
||||
|
||||
@Composable
|
||||
fun LinuxApp() {
|
||||
val navController = rememberNavController()
|
||||
val navBackStackEntry = navController.currentBackStackEntryAsState()
|
||||
val searchTextValue = remember {
|
||||
mutableStateOf(
|
||||
TextFieldValue(text = "", selection = TextRange(0)),
|
||||
)
|
||||
}
|
||||
val showSearch = remember { mutableStateOf(false) }
|
||||
val onNavigate: (String) -> Unit = remember(navController) { { route -> navController.navigate(route) } }
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBar(
|
||||
navBackStackEntry = navBackStackEntry,
|
||||
textFieldValue = searchTextValue,
|
||||
onNavigateBack = {
|
||||
navController.popBackStack()
|
||||
},
|
||||
isSearchVisible = showSearch,
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
BottomBar(
|
||||
navController = navController,
|
||||
resetSearch = {
|
||||
searchTextValue.value = TextFieldValue(text = "", selection = TextRange(0))
|
||||
showSearch.value = false
|
||||
},
|
||||
)
|
||||
},
|
||||
) { innerPadding ->
|
||||
Box(
|
||||
modifier = Modifier.padding(innerPadding),
|
||||
) {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = Screen.Basics.route,
|
||||
) {
|
||||
composable(
|
||||
Screen.Basics.route,
|
||||
deepLinks = listOf(
|
||||
navDeepLink { uriPattern = "$DEEPLINK_URI/basics" },
|
||||
navDeepLink { uriPattern = "$DEEPLINK_URI/basics.html" },
|
||||
),
|
||||
) {
|
||||
BasicCategoriesScreen(
|
||||
onNavigate = onNavigate,
|
||||
)
|
||||
}
|
||||
composable(
|
||||
Screen.Commands.route,
|
||||
deepLinks = listOf(
|
||||
navDeepLink { uriPattern = "$DEEPLINK_URI/" },
|
||||
navDeepLink { uriPattern = "$DEEPLINK_URI/index.html" },
|
||||
),
|
||||
) {
|
||||
CommandListScreen(
|
||||
onNavigate = onNavigate,
|
||||
)
|
||||
}
|
||||
composable(
|
||||
Screen.Tips.route,
|
||||
deepLinks = listOf(
|
||||
navDeepLink { uriPattern = "$DEEPLINK_URI/tips" },
|
||||
navDeepLink { uriPattern = "$DEEPLINK_URI/tips.html" },
|
||||
),
|
||||
) {
|
||||
TipsScreen(onNavigate)
|
||||
}
|
||||
composable(
|
||||
"basicgroups?categoryId={categoryId}&categoryName={categoryName}",
|
||||
arguments = listOf(
|
||||
navArgument("categoryId") { defaultValue = "" },
|
||||
navArgument("categoryName") {},
|
||||
),
|
||||
deepLinks = listOf(
|
||||
navDeepLink {
|
||||
uriPattern = "$DEEPLINK_URI/basic/{categoryName}.html"
|
||||
},
|
||||
navDeepLink { uriPattern = "$DEEPLINK_URI/basic/{categoryName}" },
|
||||
),
|
||||
) { backStackEntry ->
|
||||
val categoryId = backStackEntry.getCategoryId()
|
||||
if (categoryId != null) {
|
||||
BasicGroupsScreen(
|
||||
categoryId = categoryId,
|
||||
onNavigate = onNavigate,
|
||||
)
|
||||
} else {
|
||||
// open tips screen on invalid deeplink parameters
|
||||
TipsScreen(onNavigate)
|
||||
}
|
||||
}
|
||||
composable(
|
||||
"command?commandId={commandId}&commandName={commandName}",
|
||||
arguments = listOf(
|
||||
navArgument("commandId") { defaultValue = "" },
|
||||
navArgument("commandName") {},
|
||||
),
|
||||
deepLinks = listOf(
|
||||
navDeepLink { uriPattern = "$DEEPLINK_URI/man/{commandName}.html" },
|
||||
navDeepLink { uriPattern = "$DEEPLINK_URI/man/{commandName}" },
|
||||
),
|
||||
) { backStackEntry ->
|
||||
val commandId = backStackEntry.getCommandId()
|
||||
if (commandId != null) {
|
||||
CommandDetailScreen(
|
||||
commandId = commandId,
|
||||
onNavigate = onNavigate,
|
||||
)
|
||||
} else {
|
||||
// open tips screen on invalid deeplink parameters
|
||||
TipsScreen(onNavigate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val isSearchVisible by remember(navBackStackEntry, searchTextValue) {
|
||||
derivedStateOf {
|
||||
searchTextValue.value.text.isNotEmpty() &&
|
||||
navBackStackEntry.value?.destination?.route?.startsWith("command?") == false
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = isSearchVisible,
|
||||
enter = fadeIn(animationSpec = tween(300)),
|
||||
exit = fadeOut(animationSpec = tween(durationMillis = 300, delayMillis = 300)), // work around for navigation overlaps
|
||||
) {
|
||||
SearchScreen(
|
||||
searchText = searchTextValue.value.text,
|
||||
onNavigate = remember(navController) { { route -> navController.navigate(route) } },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Screen(
|
||||
val route: String,
|
||||
@StringRes val resourceId: Int,
|
||||
@DrawableRes val drawableRes: Int,
|
||||
) {
|
||||
data object Commands : Screen("commands", R.string.commands, R.drawable.ic_search_40dp)
|
||||
data object Basics : Screen("basics", R.string.basics, R.drawable.ic_puzzle)
|
||||
data object Tips : Screen("tips", R.string.tips, R.drawable.ic_idea)
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca.ui.composables
|
||||
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.BottomNavigation
|
||||
import androidx.compose.material.BottomNavigationItem
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavDestination.Companion.hierarchy
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.Screen
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.theme.LocalCustomColors
|
||||
|
||||
/* Copyright 2022 Simon Schubert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
val bottomBarItems = listOf(
|
||||
Screen.Basics,
|
||||
Screen.Tips,
|
||||
Screen.Commands,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun BottomBar(
|
||||
navController: NavHostController,
|
||||
resetSearch: () -> Unit,
|
||||
) {
|
||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentDestination = navBackStackEntry?.destination
|
||||
|
||||
BottomNavigation(
|
||||
backgroundColor = LocalCustomColors.current.navBarBackground,
|
||||
elevation = 0.dp,
|
||||
) {
|
||||
bottomBarItems.forEach { screen ->
|
||||
BottomNavigationItem(
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(id = screen.drawableRes),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(screen.resourceId)) },
|
||||
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
|
||||
selectedContentColor = MaterialTheme.colors.primary,
|
||||
unselectedContentColor = MaterialTheme.colors.onSurface,
|
||||
onClick = {
|
||||
// Pop back stack if current route starts with "command?"
|
||||
// This specific logic might need adjustment based on detailed navigation graph behavior
|
||||
// For example, ensure it doesn't pop too much or interfere with expected navigation.
|
||||
// A more robust way might be to navigate to a specific point before the command details.
|
||||
while (navController.currentBackStackEntry?.destination?.route?.startsWith("command?") == true) {
|
||||
navController.popBackStack()
|
||||
}
|
||||
navController.navigate(screen.route) {
|
||||
popUpTo(navController.graph.findStartDestination().id) {
|
||||
saveState = true
|
||||
}
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
}
|
||||
resetSearch()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca.ui.composables
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Share
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.LinkAnnotation
|
||||
import androidx.compose.ui.text.ParagraphStyle
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.R
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.theme.LinuxTheme
|
||||
import com.linuxcommandlibrary.shared.CommandElement
|
||||
import com.linuxcommandlibrary.shared.databaseHelper
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
/* Copyright 2022 Simon Schubert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun CommandView(
|
||||
command: String,
|
||||
elements: ImmutableList<CommandElement>,
|
||||
onNavigate: (String) -> Unit = {},
|
||||
verticalPadding: Dp = 6.dp,
|
||||
) {
|
||||
val codeColor = MaterialTheme.colors.primary
|
||||
val baseAnnotatedString = remember(elements, codeColor) {
|
||||
buildAnnotatedString {
|
||||
elements.forEach { element ->
|
||||
when (element) {
|
||||
is CommandElement.Text -> {
|
||||
append(element.text)
|
||||
}
|
||||
is CommandElement.Man -> {
|
||||
val start = this.length
|
||||
withStyle(style = SpanStyle(color = codeColor)) {
|
||||
append(element.man)
|
||||
}
|
||||
val end = this.length
|
||||
addLink(
|
||||
LinkAnnotation.Clickable(
|
||||
tag = "man:${element.man}",
|
||||
linkInteractionListener = {
|
||||
val manCommand = databaseHelper.getCommand(element.man)
|
||||
if (manCommand != null) {
|
||||
onNavigate("command?commandId=${manCommand.id}&commandName=${manCommand.name}")
|
||||
}
|
||||
},
|
||||
),
|
||||
start,
|
||||
end,
|
||||
)
|
||||
}
|
||||
is CommandElement.Url -> {
|
||||
val start = this.length
|
||||
withStyle(style = SpanStyle(color = codeColor)) {
|
||||
append(element.command)
|
||||
}
|
||||
val end = this.length
|
||||
addLink(
|
||||
LinkAnnotation.Url(element.url),
|
||||
start,
|
||||
end,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val finalAnnotatedString = remember(baseAnnotatedString) {
|
||||
if (baseAnnotatedString.text.isEmpty()) {
|
||||
baseAnnotatedString
|
||||
} else {
|
||||
buildAnnotatedString {
|
||||
append(baseAnnotatedString)
|
||||
addStyle(
|
||||
style = ParagraphStyle(), // Default ParagraphStyle
|
||||
start = 0,
|
||||
end = baseAnnotatedString.text.length,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row(modifier = Modifier.padding(start = 12.dp, end = 4.dp).padding(vertical = verticalPadding)) {
|
||||
Text(
|
||||
text = finalAnnotatedString,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically),
|
||||
style = MaterialTheme.typography.subtitle2,
|
||||
)
|
||||
|
||||
val context = LocalContext.current
|
||||
val shareAction = remember(context, command) { { shareCommand(context, command) } }
|
||||
IconButton(
|
||||
modifier = Modifier.align(Alignment.CenterVertically),
|
||||
onClick = shareAction,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Share,
|
||||
contentDescription = stringResource(R.string.share),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun shareCommand(context: Context, command: String) {
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
intent.type = "text/plain"
|
||||
intent.putExtra(
|
||||
Intent.EXTRA_TEXT,
|
||||
command.dropWhile { it == '$' || it.isWhitespace() }.replace("\\n", ""),
|
||||
)
|
||||
try {
|
||||
context.startActivity(Intent.createChooser(intent, "Share command"))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun CommandViewPreview() {
|
||||
LinuxTheme {
|
||||
CommandView(
|
||||
command = "$ find ex?mple.txt",
|
||||
elements = listOf(
|
||||
CommandElement.Text("$ "),
|
||||
CommandElement.Man("find"),
|
||||
CommandElement.Text(" ex?mple.txt"),
|
||||
).toImmutableList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca.ui.composables
|
||||
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.theme.LinuxTheme
|
||||
|
||||
/* Copyright 2022 Simon Schubert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun HighlightedText(
|
||||
text: String,
|
||||
pattern: String,
|
||||
maxLines: Int = 1,
|
||||
) {
|
||||
if (pattern.isEmpty()) {
|
||||
Text(
|
||||
text = text,
|
||||
maxLines = maxLines,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
} else if (text.equals(pattern, ignoreCase = true)) {
|
||||
Text(
|
||||
text = text,
|
||||
maxLines = maxLines,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = MaterialTheme.colors.primary,
|
||||
)
|
||||
} else {
|
||||
val highlightColor = MaterialTheme.colors.primary
|
||||
val annotatedString = remember(text, pattern, highlightColor) {
|
||||
buildAnnotatedString {
|
||||
val splitText = text.split(pattern, ignoreCase = true)
|
||||
splitText.forEachIndexed { index, s ->
|
||||
append(s)
|
||||
if (index != splitText.size - 1) {
|
||||
withStyle(style = SpanStyle(color = highlightColor)) {
|
||||
append(pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = annotatedString,
|
||||
maxLines = maxLines,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun HighlightedTextPreview() {
|
||||
LinuxTheme {
|
||||
HighlightedText(
|
||||
text = "pacman",
|
||||
pattern = "cm",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca.ui.composables
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.theme.LinuxTheme
|
||||
import com.linuxcommandlibrary.shared.CommandElement
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
/* Copyright 2022 Simon Schubert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun NestedCommandView(
|
||||
text: String,
|
||||
command: String,
|
||||
commandElements: ImmutableList<CommandElement>,
|
||||
onNavigate: (String) -> Unit,
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.width(40.dp),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
CommandView(
|
||||
command = command,
|
||||
elements = commandElements,
|
||||
onNavigate = onNavigate,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun NestedCommandViewPreview() {
|
||||
LinuxTheme {
|
||||
NestedCommandView(
|
||||
text = "",
|
||||
command = "$ find ex?mple.txt",
|
||||
commandElements = listOf(
|
||||
CommandElement.Text("$ "),
|
||||
CommandElement.Man("find"),
|
||||
CommandElement.Text(" ex?mple.txt"),
|
||||
).toImmutableList(),
|
||||
onNavigate = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca.ui.composables
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.theme.LinuxTheme
|
||||
|
||||
/* Copyright 2022 Simon Schubert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun NestedText(textLeft: String, textRight: String) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
textLeft,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.width(40.dp),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Text(textRight, modifier = Modifier.padding(8.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun NestedTextPreview() {
|
||||
LinuxTheme {
|
||||
NestedText(textLeft = ">>", textRight = "redirect")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca.ui.composables
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.theme.LinuxTheme
|
||||
|
||||
/* Copyright 2022 Simon Schubert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun SectionTitle(modifier: Modifier = Modifier, title: String) {
|
||||
Text(
|
||||
text = title,
|
||||
fontSize = 20.sp,
|
||||
style = MaterialTheme.typography.subtitle1,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = modifier.padding(bottom = 4.dp),
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun SectionTitlePreview() {
|
||||
LinuxTheme {
|
||||
SectionTitle(title = "List of recent commands")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,360 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca.ui.composables
|
||||
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.LocalContentAlpha
|
||||
import androidx.compose.material.LocalContentColor
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextFieldDefaults
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.ViewModelStoreOwner
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.R
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.getCommandId
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.AppInfoDialog
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.BookmarkFeedbackDialog
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.commanddetail.CommandDetailViewModel
|
||||
import com.linuxcommandlibrary.shared.databaseHelper
|
||||
import com.linuxcommandlibrary.shared.getHtmlFileName
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
/* Copyright 2022 Simon Schubert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun TopBar(
|
||||
navBackStackEntry: State<NavBackStackEntry?>,
|
||||
textFieldValue: MutableState<TextFieldValue>,
|
||||
onNavigateBack: () -> Unit,
|
||||
isSearchVisible: MutableState<Boolean>,
|
||||
) {
|
||||
val route = navBackStackEntry.value?.destination?.route
|
||||
|
||||
if (route == "commands" || route == "basics") {
|
||||
val hideSearchCallback = remember { { isSearchVisible.value = false } }
|
||||
val showSearchCallback = remember { { isSearchVisible.value = true } }
|
||||
SearchTopBar(
|
||||
title = getTitleByRoute(navBackStackEntry.value),
|
||||
textFieldValue = textFieldValue,
|
||||
isSearchVisible = isSearchVisible.value,
|
||||
hideSearch = hideSearchCallback,
|
||||
showSearch = showSearchCallback,
|
||||
)
|
||||
} else if (route?.startsWith("command?") == true) {
|
||||
DetailTopBar(
|
||||
commandId = navBackStackEntry.value?.getCommandId() ?: 0,
|
||||
title = getTitleByRoute(navBackStackEntry.value),
|
||||
onNavigateBack = onNavigateBack,
|
||||
)
|
||||
} else {
|
||||
val title = getTitleByRoute(navBackStackEntry.value)
|
||||
val showBackIcon = route != "tips" // Simplified from original when
|
||||
val showAppInfoIcon = route == "tips"
|
||||
GenericTopBar(
|
||||
title = title,
|
||||
showBackIcon = showBackIcon,
|
||||
onNavigateBack = onNavigateBack,
|
||||
showAppInfoIcon = showAppInfoIcon,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GenericTopBar(
|
||||
title: String,
|
||||
showBackIcon: Boolean,
|
||||
onNavigateBack: () -> Unit,
|
||||
showAppInfoIcon: Boolean,
|
||||
) {
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
title,
|
||||
modifier = Modifier.semantics { contentDescription = "TopAppBarTitle" },
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
},
|
||||
backgroundColor = MaterialTheme.colors.primary,
|
||||
contentColor = Color.White,
|
||||
navigationIcon = if (showBackIcon) {
|
||||
{
|
||||
IconButton(onClick = { onNavigateBack() }) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
},
|
||||
actions = {
|
||||
if (showAppInfoIcon) {
|
||||
IconButton(onClick = {
|
||||
showDialog = true
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Info,
|
||||
contentDescription = stringResource(R.string.info),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
if (showDialog) {
|
||||
AppInfoDialog(onDismiss = { showDialog = false })
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DetailTopBar(
|
||||
commandId: Long,
|
||||
viewModel: CommandDetailViewModel = koinViewModel(
|
||||
key = commandId.toString(),
|
||||
viewModelStoreOwner = LocalActivity.current as ViewModelStoreOwner,
|
||||
parameters = { parametersOf(commandId) },
|
||||
),
|
||||
title: String,
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
val uiState by viewModel.state.collectAsStateWithLifecycle()
|
||||
val isAllExpanded by remember { derivedStateOf { uiState.isAllExpanded() } }
|
||||
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
title,
|
||||
modifier = Modifier.semantics { contentDescription = "TopAppBarTitle" },
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
},
|
||||
backgroundColor = MaterialTheme.colors.primary,
|
||||
contentColor = Color.White,
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateBack) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = {
|
||||
viewModel.onToggleAllExpanded()
|
||||
}) {
|
||||
if (isAllExpanded) {
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_collapse_all),
|
||||
contentDescription = stringResource(R.string.collapse_all),
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_expand_all),
|
||||
contentDescription = stringResource(R.string.expand_all),
|
||||
)
|
||||
}
|
||||
}
|
||||
IconButton(onClick = {
|
||||
if (uiState.isBookmarked) {
|
||||
viewModel.removeBookmark()
|
||||
} else {
|
||||
viewModel.addBookmark()
|
||||
}
|
||||
}) {
|
||||
if (uiState.isBookmarked) {
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_bookmark_black_24dp),
|
||||
contentDescription = stringResource(R.string.remove_bookmark),
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_bookmark_border_black_24dp),
|
||||
contentDescription = stringResource(R.string.add_bookmark),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
if (uiState.showBookmarkDialog) {
|
||||
BookmarkFeedbackDialog(onDismiss = { viewModel.hideBookmarkDialog() })
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SearchTopBar(
|
||||
title: String,
|
||||
textFieldValue: MutableState<TextFieldValue>,
|
||||
isSearchVisible: Boolean,
|
||||
hideSearch: () -> Unit,
|
||||
showSearch: () -> Unit,
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colors.primary)
|
||||
.heightIn(min = 56.dp)
|
||||
.padding(horizontal = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (isSearchVisible) {
|
||||
IconButton(onClick = {
|
||||
hideSearch()
|
||||
textFieldValue.value = TextFieldValue("")
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = stringResource(id = R.string.back),
|
||||
tint = Color.White,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (isSearchVisible) {
|
||||
OutlinedTextField(
|
||||
value = textFieldValue.value,
|
||||
onValueChange = { textFieldValue.value = it },
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.focusRequester(focusRequester)
|
||||
.semantics { contentDescription = "SearchField" }
|
||||
.padding(start = 8.dp, end = 8.dp),
|
||||
placeholder = { Text("Search...", color = Color.White.copy(alpha = 0.7f)) },
|
||||
textStyle = MaterialTheme.typography.subtitle1.copy(color = Color.White),
|
||||
colors = TextFieldDefaults.outlinedTextFieldColors(
|
||||
textColor = Color.White,
|
||||
cursorColor = Color.White,
|
||||
focusedBorderColor = Color.Transparent,
|
||||
unfocusedBorderColor = Color.Transparent,
|
||||
disabledBorderColor = Color.Transparent,
|
||||
backgroundColor = Color.Transparent,
|
||||
trailingIconColor = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
|
||||
placeholderColor = LocalContentColor.current.copy(alpha = 0.7f),
|
||||
),
|
||||
maxLines = 1,
|
||||
singleLine = true,
|
||||
)
|
||||
if (textFieldValue.value.text.isNotEmpty()) {
|
||||
IconButton(onClick = {
|
||||
textFieldValue.value = TextFieldValue("")
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Close,
|
||||
contentDescription = stringResource(id = R.string.reset),
|
||||
tint = Color.White,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
title,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(start = 16.dp) // Standard title padding when no nav icon
|
||||
.semantics { contentDescription = "TopAppBarTitle" },
|
||||
style = MaterialTheme.typography.h6.copy(color = LocalContentColor.current), // Use h6 for title as per Material
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = Color.White,
|
||||
)
|
||||
}
|
||||
|
||||
if (!isSearchVisible) {
|
||||
IconButton(onClick = {
|
||||
showSearch()
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Search,
|
||||
contentDescription = stringResource(R.string.search),
|
||||
tint = Color.White,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(isSearchVisible) {
|
||||
if (isSearchVisible) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getTitleByRoute(backStackEntry: NavBackStackEntry?): String {
|
||||
if (backStackEntry == null) {
|
||||
return "Linux"
|
||||
}
|
||||
return when (val route = backStackEntry.destination.route) {
|
||||
"commands" -> stringResource(R.string.commands)
|
||||
"basics" -> stringResource(R.string.basics)
|
||||
"tips" -> stringResource(R.string.tips)
|
||||
else -> {
|
||||
if (route?.startsWith("command?") == true) {
|
||||
backStackEntry.arguments?.getString("commandName") ?: ""
|
||||
} else if (route?.startsWith("basicgroups?") == true) {
|
||||
val deeplinkName = backStackEntry.arguments?.getString("categoryName") ?: ""
|
||||
remember(deeplinkName) {
|
||||
val categories = databaseHelper.getBasics()
|
||||
val category = categories.firstOrNull { it.getHtmlFileName() == deeplinkName }
|
||||
category?.title ?: "Not found"
|
||||
}
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca.ui.screens
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.R
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.theme.LinuxTheme
|
||||
import com.linuxcommandlibrary.shared.*
|
||||
|
||||
/* Copyright 2022 Simon Schubert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Todo: Replace with AlertDialog
|
||||
|
||||
@Composable
|
||||
fun AppInfoDialog(
|
||||
onDismiss: () -> Unit = {},
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
Dialog(onDismissRequest = { onDismiss() }) {
|
||||
Card(
|
||||
elevation = 8.dp,
|
||||
shape = RoundedCornerShape(6.dp),
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
Text(
|
||||
stringResource(R.string.app_name),
|
||||
style = MaterialTheme.typography.h5,
|
||||
modifier = Modifier.padding(8.dp),
|
||||
)
|
||||
Row {
|
||||
Button(
|
||||
modifier = Modifier.padding(start = 6.dp),
|
||||
content = {
|
||||
Text(
|
||||
"Rate the app",
|
||||
color = Color.White,
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
uriHandler.openUri("https://play.google.com/store/apps/details?id=com.inspiredandroid.linuxcommandbibliotheca")
|
||||
},
|
||||
)
|
||||
IconButton(onClick = {
|
||||
uriHandler.openUri("https://github.com/SimonSchubert/LinuxCommandLibrary")
|
||||
}) {
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_icons8_github),
|
||||
contentDescription = "View on GitHub", // TODO: Use stringResource
|
||||
modifier = Modifier.size(40.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
"Version: ${Version.appVersion}",
|
||||
style = MaterialTheme.typography.caption,
|
||||
modifier = Modifier.padding(8.dp),
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.weight(1f)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
Text("Support this project", style = MaterialTheme.typography.h6)
|
||||
Text("By using my referral links for these amazing products.")
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
val link = "https://linuxcommandlibrary.com/proton-free-2025"
|
||||
uriHandler.openUri(link)
|
||||
},
|
||||
painter = painterResource(R.mipmap.proton_free_horizontal),
|
||||
contentDescription = "Proton services promotion", // TODO: Use stringResource
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
val link = "https://linuxcommandlibrary.com/linode-2025"
|
||||
uriHandler.openUri(link)
|
||||
},
|
||||
painter = painterResource(R.mipmap.linode_horizontal),
|
||||
contentDescription = "Linode cloud hosting promotion", // TODO: Use stringResource
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text("Man pages", style = MaterialTheme.typography.h6)
|
||||
Text("Licence information about the man page is usually specified in the man detail page under the category Author, Copyright or Licence. If there is no information on the page you can find the information in the man page source file on your linux system. If you have questions or can't find what you need, you can contact me at sschubert89@gmail.com.")
|
||||
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text("TLDR pages", style = MaterialTheme.typography.h6)
|
||||
Text(
|
||||
"The MIT License (MIT) Copyright (c) 2014 the TLDR team and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.",
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
"Thanks to commandlinefu.com for the one-liners and icons8.com for the icons",
|
||||
style = MaterialTheme.typography.h6,
|
||||
)
|
||||
}
|
||||
TextButton(
|
||||
content = { Text("OK") },
|
||||
modifier = Modifier
|
||||
.align(Alignment.End)
|
||||
.padding(end = 6.dp),
|
||||
onClick = onDismiss,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun AppInfoDialogPreview() {
|
||||
LinuxTheme {
|
||||
AppInfoDialog()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca.ui.screens
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.R
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.composables.SectionTitle
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@Composable
|
||||
fun BookmarkFeedbackDialog(onDismiss: () -> Unit) {
|
||||
LaunchedEffect(Unit) {
|
||||
delay(600)
|
||||
onDismiss()
|
||||
}
|
||||
|
||||
Dialog(onDismissRequest = onDismiss) {
|
||||
Card(
|
||||
elevation = 8.dp,
|
||||
shape = RoundedCornerShape(6.dp),
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.padding(16.dp),
|
||||
) {
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_bookmark_black_24dp),
|
||||
contentDescription = null, // Decorative, as title says "Bookmarked"
|
||||
modifier = Modifier.size(48.dp),
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(8.dp))
|
||||
|
||||
SectionTitle(title = "Bookmarked")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca.ui.screens
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.LinearProgressIndicator
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.R
|
||||
import com.linuxcommandlibrary.shared.copyDatabase
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Composable
|
||||
fun InitializeDatabaseScreen(onFinish: () -> Unit = {}) {
|
||||
var status by remember {
|
||||
mutableIntStateOf(0)
|
||||
}
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Image(
|
||||
painterResource(R.mipmap.ic_launcher_foreground),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(240.dp)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
)
|
||||
Text(
|
||||
"Initialize database",
|
||||
color = MaterialTheme.colors.onSurface,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
)
|
||||
LinearProgressIndicator(
|
||||
progress = status.div(100f),
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
copyDatabase(context) { progress ->
|
||||
status = progress
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
onFinish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
@file:OptIn(ExperimentalMaterialApi::class)
|
||||
|
||||
package com.inspiredandroid.linuxcommandbibliotheca.ui.screens.basiccategories
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.ListItem
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.getIconResource
|
||||
import com.linuxcommandlibrary.shared.getHtmlFileName
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
/* Copyright 2022 Simon Schubert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun BasicCategoriesScreen(
|
||||
viewModel: BasicCategoriesViewModel = koinViewModel(),
|
||||
onNavigate: (String) -> Unit,
|
||||
) {
|
||||
LazyVerticalGrid(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
columns = GridCells.Adaptive(minSize = 300.dp),
|
||||
) {
|
||||
items(
|
||||
items = viewModel.basicCategories,
|
||||
key = { it.id },
|
||||
contentType = { "basic_category_item" },
|
||||
) { basicCategory ->
|
||||
ListItem(
|
||||
text = { Text(basicCategory.title) },
|
||||
icon = {
|
||||
Icon(
|
||||
painterResource(basicCategory.getIconResource()),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(40.dp),
|
||||
)
|
||||
},
|
||||
modifier = Modifier.clickable {
|
||||
onNavigate(
|
||||
"basicgroups?categoryId=${basicCategory.id}&categoryName=${basicCategory.getHtmlFileName()}",
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca.ui.screens.basiccategories
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.linuxcommandlibrary.shared.databaseHelper
|
||||
import databases.BasicCategory
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
/* Copyright 2022 Simon Schubert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class BasicCategoriesViewModel : ViewModel() {
|
||||
|
||||
var basicCategories: ImmutableList<BasicCategory> = databaseHelper.getBasics().toImmutableList()
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
@file:OptIn(ExperimentalMaterialApi::class)
|
||||
|
||||
package com.inspiredandroid.linuxcommandbibliotheca.ui.screens.basicgroups
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.ListItem
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.getIconResource
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.composables.CommandView
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.composables.HighlightedText
|
||||
import com.linuxcommandlibrary.shared.databaseHelper
|
||||
import com.linuxcommandlibrary.shared.getCommandList
|
||||
import databases.BasicGroup
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
/* Copyright 2022 Simon Schubert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun BasicGroupsScreen(
|
||||
categoryId: Long,
|
||||
viewModel: BasicGroupsViewModel = koinViewModel<BasicGroupsViewModel>(
|
||||
parameters = { parametersOf(categoryId) },
|
||||
),
|
||||
onNavigate: (String) -> Unit = {},
|
||||
) {
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
|
||||
LazyColumn(Modifier.fillMaxSize()) {
|
||||
items(
|
||||
items = uiState.basicGroups,
|
||||
key = { it.id },
|
||||
contentType = { "basic_group_item" },
|
||||
) { basicGroup ->
|
||||
BasicGroupColumn(
|
||||
basicGroup = basicGroup,
|
||||
isExpanded = !uiState.collapsedMap.getOrDefault(basicGroup.id, true),
|
||||
onToggleCollapse = { viewModel.toggleCollapse(basicGroup.id) },
|
||||
onNavigate = onNavigate,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BasicGroupColumn(
|
||||
basicGroup: BasicGroup,
|
||||
searchText: String = "",
|
||||
isExpanded: Boolean,
|
||||
onToggleCollapse: () -> Unit,
|
||||
onNavigate: (String) -> Unit = {},
|
||||
) {
|
||||
ListItem(
|
||||
text = {
|
||||
HighlightedText(
|
||||
text = basicGroup.description,
|
||||
pattern = searchText,
|
||||
maxLines = 3,
|
||||
)
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
painterResource(basicGroup.getIconResource()),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(40.dp),
|
||||
)
|
||||
},
|
||||
modifier = Modifier.clickable { onToggleCollapse() },
|
||||
)
|
||||
|
||||
if (isExpanded) {
|
||||
ExpandedGroupContent(basicGroup = basicGroup, onNavigate = onNavigate)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExpandedGroupContent(basicGroup: BasicGroup, onNavigate: (String) -> Unit) {
|
||||
val commands = remember(basicGroup.id) {
|
||||
databaseHelper.getBasicCommands(basicGroup.id).toImmutableList()
|
||||
}
|
||||
commands.forEach { basicCommand ->
|
||||
CommandView(
|
||||
command = basicCommand.command,
|
||||
elements = basicCommand.command.getCommandList(basicCommand.mans).toImmutableList(),
|
||||
onNavigate = onNavigate,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca.ui.screens.basicgroups
|
||||
|
||||
import databases.BasicGroup
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
|
||||
data class BasicGroupsUiState(
|
||||
val basicGroups: ImmutableList<BasicGroup> = persistentListOf(),
|
||||
val collapsedMap: ImmutableMap<Long, Boolean> = persistentMapOf(),
|
||||
)
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca.ui.screens.basicgroups
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.linuxcommandlibrary.shared.databaseHelper
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentMap
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
/* Copyright 2022 Simon Schubert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class BasicGroupsViewModel(categoryId: Long) : ViewModel() {
|
||||
|
||||
private val _uiState = MutableStateFlow(BasicGroupsUiState())
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
init {
|
||||
val groups = databaseHelper.getBasicGroupsByQuery(categoryId).toImmutableList()
|
||||
_uiState.value = BasicGroupsUiState(basicGroups = groups)
|
||||
}
|
||||
|
||||
fun isGroupCollapsed(id: Long): Boolean = _uiState.value.collapsedMap.getOrDefault(id, true)
|
||||
|
||||
fun toggleCollapse(id: Long) {
|
||||
_uiState.update { currentState ->
|
||||
val newMap = currentState.collapsedMap.toMutableMap()
|
||||
newMap[id] = !currentState.collapsedMap.getOrDefault(id, true)
|
||||
currentState.copy(collapsedMap = newMap.toPersistentMap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
@file:OptIn(ExperimentalMaterialApi::class, ExperimentalLayoutApi::class)
|
||||
|
||||
package com.inspiredandroid.linuxcommandbibliotheca.ui.screens.commanddetail
|
||||
|
||||
import android.text.style.StyleSpan
|
||||
import android.text.style.UnderlineSpan
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.Chip
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.ListItem
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.lifecycle.ViewModelStoreOwner
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.composables.CommandView
|
||||
import com.linuxcommandlibrary.shared.CommandElement
|
||||
import com.linuxcommandlibrary.shared.databaseHelper
|
||||
import databases.CommandSection
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
/* Copyright 2022 Simon Schubert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun CommandDetailScreen(
|
||||
commandId: Long,
|
||||
viewModel: CommandDetailViewModel = koinViewModel(
|
||||
key = commandId.toString(),
|
||||
viewModelStoreOwner = LocalActivity.current as ViewModelStoreOwner,
|
||||
parameters = { parametersOf(commandId) },
|
||||
),
|
||||
onNavigate: (String) -> Unit,
|
||||
) {
|
||||
val uiState by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
LazyColumn(Modifier.fillMaxSize()) {
|
||||
items(
|
||||
items = uiState.sections, // This should ideally be ImmutableList from ViewModel
|
||||
key = { it.id },
|
||||
contentType = { "command_section_item" },
|
||||
) { section ->
|
||||
CommandSectionColumn(
|
||||
section = section,
|
||||
isExpanded = uiState.expandedSectionsMap.getOrDefault(section.id, false),
|
||||
onToggleExpanded = { id -> viewModel.onToggleExpanded(id) },
|
||||
onNavigate = onNavigate,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CommandSectionColumn(
|
||||
section: CommandSection,
|
||||
isExpanded: Boolean,
|
||||
onToggleExpanded: (Long) -> Unit,
|
||||
onNavigate: (String) -> Unit,
|
||||
) {
|
||||
ListItem(
|
||||
text = {
|
||||
Text(
|
||||
section.title.uppercase(),
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 20.sp,
|
||||
)
|
||||
},
|
||||
modifier = Modifier.clickable {
|
||||
onToggleExpanded(section.id)
|
||||
},
|
||||
)
|
||||
|
||||
if (isExpanded) {
|
||||
when (section.title) {
|
||||
"TLDR" -> TldrSectionContent(content = section.content, onNavigate = onNavigate)
|
||||
"SEE ALSO" -> SeeAlsoSectionContent(content = section.content, onNavigate = onNavigate)
|
||||
else -> DefaultSectionContent(content = section.content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TldrSectionContent(content: String, onNavigate: (String) -> Unit) {
|
||||
Column(
|
||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 8.dp),
|
||||
) {
|
||||
val tldrParts = content.split("<b>")
|
||||
tldrParts.forEachIndexed { index, s ->
|
||||
val split = s.split("</b>")
|
||||
if (split.size > 1) {
|
||||
Text(
|
||||
text = split[0],
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.7f),
|
||||
)
|
||||
|
||||
val command = "$ " + split[1].replace("<br>", "").replace("`", "")
|
||||
CommandView(
|
||||
command = command,
|
||||
elements = listOf(CommandElement.Text(command)).toImmutableList(),
|
||||
onNavigate = onNavigate,
|
||||
verticalPadding = 4.dp,
|
||||
)
|
||||
}
|
||||
if (index != tldrParts.lastIndex && split.size > 1) { // Add spacer only if content was added
|
||||
Spacer(Modifier.height(6.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SeeAlsoSectionContent(content: String, onNavigate: (String) -> Unit) {
|
||||
val commands = getCommands(content)
|
||||
if (commands.isNotEmpty()) {
|
||||
FlowRow(
|
||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
commands.forEach { name ->
|
||||
Chip(onClick = {
|
||||
onNavigate("command?commandName=$name")
|
||||
}) {
|
||||
Text(
|
||||
text = name,
|
||||
color = MaterialTheme.colors.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// fallback to default rendering if no commands were parsed (e.g. plain text)
|
||||
DefaultSectionContent(content = content, isFallback = true)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DefaultSectionContent(content: String, isFallback: Boolean = false) {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = if (isFallback) 0.dp else 8.dp, bottom = 8.dp),
|
||||
text = content.toAnnotatedString(),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.7f),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getCommands(input: String): ImmutableList<String> {
|
||||
val commands = input.split(",").map { it.trim() }
|
||||
|
||||
return commands
|
||||
.map { command ->
|
||||
command.replace(Regex("\\(\\d+\\)$"), "").trim()
|
||||
}.filter {
|
||||
databaseHelper.getCommand(it) != null
|
||||
}.toImmutableList()
|
||||
}
|
||||
|
||||
private fun String.toAnnotatedString(): AnnotatedString {
|
||||
val spanned = HtmlCompat.fromHtml(this, HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||
|
||||
val trimmedText = spanned.toString().trim('\n', ' ')
|
||||
|
||||
return buildAnnotatedString {
|
||||
append(trimmedText)
|
||||
|
||||
spanned.getSpans(0, trimmedText.length, Any::class.java).forEach { span ->
|
||||
val start = spanned.getSpanStart(span)
|
||||
val end = spanned.getSpanEnd(span)
|
||||
|
||||
when (span) {
|
||||
is StyleSpan -> when (span.style) {
|
||||
android.graphics.Typeface.BOLD -> {
|
||||
addStyle(SpanStyle(fontWeight = FontWeight.Bold), start, end)
|
||||
}
|
||||
android.graphics.Typeface.ITALIC -> {
|
||||
addStyle(SpanStyle(fontStyle = FontStyle.Italic), start, end)
|
||||
}
|
||||
}
|
||||
is UnderlineSpan -> {
|
||||
addStyle(SpanStyle(textDecoration = TextDecoration.Underline), start, end)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca.ui.screens.commanddetail
|
||||
|
||||
import databases.CommandSection
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
|
||||
data class CommandDetailUiState(
|
||||
val sections: ImmutableList<CommandSection>,
|
||||
val expandedSectionsMap: ImmutableMap<Long, Boolean>,
|
||||
val isBookmarked: Boolean = false,
|
||||
val showBookmarkDialog: Boolean = false,
|
||||
) {
|
||||
fun isAllExpanded(): Boolean = expandedSectionsMap.all { it.value }
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca.ui.screens.commanddetail
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.DataManager
|
||||
import com.linuxcommandlibrary.shared.databaseHelper
|
||||
import com.linuxcommandlibrary.shared.getSortPriority
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
/* Copyright 2022 Simon Schubert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class CommandDetailViewModel(
|
||||
private val commandId: Long,
|
||||
private val dataManager: DataManager,
|
||||
) : ViewModel() {
|
||||
|
||||
val state: MutableStateFlow<CommandDetailUiState>
|
||||
|
||||
init {
|
||||
val sectionsData = databaseHelper.getSections(commandId).sortedBy { it.getSortPriority() }
|
||||
val isAutoExpandEnabled = dataManager.isAutoExpandSections()
|
||||
state = MutableStateFlow(
|
||||
CommandDetailUiState(
|
||||
sections = sectionsData.toImmutableList(),
|
||||
expandedSectionsMap = sectionsData.associate {
|
||||
it.id to isAutoExpandEnabled
|
||||
}.toImmutableMap(),
|
||||
isBookmarked = dataManager.hasBookmark(commandId),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun onToggleAllExpanded() {
|
||||
val isAllExpanded = state.value.isAllExpanded()
|
||||
state.update {
|
||||
val currentMap = it.expandedSectionsMap
|
||||
val updatedMap = currentMap.toMutableMap()
|
||||
updatedMap.replaceAll { _, _ -> !isAllExpanded }
|
||||
it.copy(expandedSectionsMap = updatedMap.toImmutableMap())
|
||||
}
|
||||
dataManager.setAutoExpandSections(!isAllExpanded)
|
||||
}
|
||||
|
||||
fun onToggleExpanded(id: Long) {
|
||||
state.update {
|
||||
val currentMap = it.expandedSectionsMap
|
||||
val updatedMap = currentMap.toMutableMap()
|
||||
updatedMap[id] = !updatedMap.getOrDefault(id, false)
|
||||
it.copy(expandedSectionsMap = updatedMap.toImmutableMap())
|
||||
}
|
||||
}
|
||||
|
||||
fun removeBookmark() {
|
||||
state.update {
|
||||
it.copy(isBookmarked = false)
|
||||
}
|
||||
dataManager.removeBookmark(commandId)
|
||||
}
|
||||
|
||||
fun addBookmark() {
|
||||
state.update {
|
||||
it.copy(
|
||||
isBookmarked = true,
|
||||
showBookmarkDialog = true,
|
||||
)
|
||||
}
|
||||
dataManager.addBookmark(commandId)
|
||||
}
|
||||
|
||||
fun hideBookmarkDialog() {
|
||||
state.update {
|
||||
it.copy(showBookmarkDialog = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
@file:OptIn(
|
||||
ExperimentalMaterialApi::class,
|
||||
ExperimentalMaterialApi::class,
|
||||
ExperimentalMaterialApi::class,
|
||||
)
|
||||
|
||||
package com.inspiredandroid.linuxcommandbibliotheca.ui.screens.commandlist
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.ListItem
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.R
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.composables.HighlightedText
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.theme.LinuxTheme
|
||||
import databases.Command
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
/* Copyright 2022 Simon Schubert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun CommandListScreen(
|
||||
viewModel: CommandListViewModel = koinViewModel(),
|
||||
onNavigate: (String) -> Unit,
|
||||
) {
|
||||
val commands by viewModel.commands.collectAsState()
|
||||
|
||||
LazyColumn {
|
||||
items(
|
||||
items = commands,
|
||||
key = { it.id },
|
||||
contentType = { "command_list_item" },
|
||||
) { command ->
|
||||
CommandListItem(
|
||||
command = command,
|
||||
onNavigate = onNavigate,
|
||||
isBookmarked = viewModel.hasBookmark(command.id),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CommandListItem(
|
||||
command: Command,
|
||||
searchText: String = "",
|
||||
onNavigate: (String) -> Unit,
|
||||
isBookmarked: Boolean,
|
||||
) {
|
||||
ListItem(
|
||||
text = {
|
||||
HighlightedText(
|
||||
text = command.name,
|
||||
pattern = searchText,
|
||||
)
|
||||
},
|
||||
trailing = {
|
||||
if (isBookmarked) {
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_bookmark_black_24dp),
|
||||
contentDescription = stringResource(R.string.remove_bookmark),
|
||||
)
|
||||
}
|
||||
},
|
||||
secondaryText = {
|
||||
HighlightedText(
|
||||
text = command.description,
|
||||
pattern = searchText,
|
||||
)
|
||||
},
|
||||
modifier = Modifier.clickable(
|
||||
onClick = remember(command.id, command.name, onNavigate) {
|
||||
{ onNavigate("command?commandId=${command.id}&commandName=${command.name}") }
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun CommandListItemPreview() {
|
||||
val command = Command(0L, 0L, "cowsay", "A talking cow says moo.")
|
||||
LinuxTheme {
|
||||
CommandListItem(
|
||||
command = command,
|
||||
searchText = "cow",
|
||||
onNavigate = { },
|
||||
isBookmarked = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun CommandListScreenPreview() {
|
||||
// This preview is more complex due to the ViewModel dependency.
|
||||
// For a proper preview, you'd typically use a fake ViewModel implementation
|
||||
// or mock data source. For now, this will just show an empty screen
|
||||
// or potentially crash if the ViewModel koinViewModel() can't be resolved in preview.
|
||||
// To make it useful, one might need to adjust Koin setup for previews or pass
|
||||
// a fake ViewModel.
|
||||
// LinuxTheme {
|
||||
// CommandListScreen(onNavigate = {})
|
||||
// }
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca.ui.screens.commandlist
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.DataManager
|
||||
import com.linuxcommandlibrary.shared.databaseHelper
|
||||
import databases.Command
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/* Copyright 2022 Simon Schubert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
class CommandListViewModel(private val dataManager: DataManager) : ViewModel() {
|
||||
|
||||
private val _commands = MutableStateFlow<ImmutableList<Command>>(persistentListOf())
|
||||
val commands = _commands.asStateFlow()
|
||||
|
||||
private val preferenceListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
|
||||
if (key == DataManager.KEY_BOOKMARKS) {
|
||||
updateCommands()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
updateCommands()
|
||||
dataManager.prefs.registerOnSharedPreferenceChangeListener(preferenceListener)
|
||||
}
|
||||
|
||||
private fun updateCommands() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
_commands.update {
|
||||
databaseHelper.getCommands().sortedBy { !hasBookmark(it.id) }.toImmutableList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun hasBookmark(id: Long): Boolean = dataManager.hasBookmark(id)
|
||||
|
||||
override fun onCleared() {
|
||||
dataManager.prefs.unregisterOnSharedPreferenceChangeListener(preferenceListener)
|
||||
super.onCleared()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca.ui.screens.search
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.basicgroups.BasicGroupColumn
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.screens.commandlist.CommandListItem
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun SearchScreen(
|
||||
searchText: String,
|
||||
viewModel: SearchViewModel = koinViewModel(),
|
||||
onNavigate: (String) -> Unit,
|
||||
) {
|
||||
// Removed the early return for searchText.isEmpty() as ViewModel now handles it by emitting an empty state.
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
|
||||
LaunchedEffect(searchText) {
|
||||
viewModel.search(searchText)
|
||||
}
|
||||
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
val lazyListState = rememberLazyListState()
|
||||
|
||||
val showEmptyMessage by remember(uiState.filteredCommands, uiState.filteredBasicGroups) {
|
||||
derivedStateOf {
|
||||
uiState.filteredCommands.isEmpty() && uiState.filteredBasicGroups.isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
// Only show "404" if search text is not empty and results are empty
|
||||
if (searchText.isNotEmpty() && showEmptyMessage) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clickable(enabled = false, onClick = {})
|
||||
.background(MaterialTheme.colors.background),
|
||||
) {
|
||||
Text("404 command not found", modifier = Modifier.align(Alignment.Center))
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colors.background),
|
||||
state = lazyListState,
|
||||
) {
|
||||
items(
|
||||
items = uiState.filteredBasicGroups,
|
||||
key = { "basicGroup_${it.id}" },
|
||||
contentType = { "search_basic_group_item" },
|
||||
) { basicGroup ->
|
||||
BasicGroupColumn(
|
||||
basicGroup = basicGroup,
|
||||
searchText = searchText,
|
||||
onNavigate = onNavigate,
|
||||
isExpanded = !uiState.collapsedMap.getOrDefault(basicGroup.id, false),
|
||||
onToggleCollapse = { viewModel.toggleCollapse(basicGroup.id) },
|
||||
)
|
||||
}
|
||||
items(
|
||||
items = uiState.filteredCommands,
|
||||
key = { "command_${it.id}" },
|
||||
contentType = { "search_command_item" },
|
||||
) { command ->
|
||||
CommandListItem(
|
||||
command = command,
|
||||
searchText = searchText,
|
||||
onNavigate = onNavigate,
|
||||
isBookmarked = false, // Or fetch actual bookmark status if relevant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(lazyListState.isScrollInProgress) {
|
||||
if (lazyListState.isScrollInProgress) {
|
||||
keyboardController?.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca.ui.screens.search
|
||||
|
||||
import databases.BasicGroup
|
||||
import databases.Command
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
|
||||
data class SearchUiState(
|
||||
val filteredCommands: ImmutableList<Command> = persistentListOf(),
|
||||
val filteredBasicGroups: ImmutableList<BasicGroup> = persistentListOf(),
|
||||
val collapsedMap: ImmutableMap<Long, Boolean> = persistentMapOf(),
|
||||
)
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca.ui.screens.search
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.linuxcommandlibrary.shared.databaseHelper
|
||||
import com.linuxcommandlibrary.shared.sortedSearch
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentMap
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
class SearchViewModel : ViewModel() {
|
||||
|
||||
private val _uiState = MutableStateFlow(SearchUiState())
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
fun isGroupCollapsed(id: Long): Boolean = _uiState.value.collapsedMap.getOrDefault(id, false)
|
||||
|
||||
fun toggleCollapse(id: Long) {
|
||||
_uiState.update { currentState ->
|
||||
val newMap = currentState.collapsedMap.toMutableMap()
|
||||
newMap[id] = !currentState.collapsedMap.getOrDefault(id, false)
|
||||
currentState.copy(collapsedMap = newMap.toPersistentMap())
|
||||
}
|
||||
}
|
||||
|
||||
private var searchJob: Job? = null
|
||||
fun search(searchText: String) {
|
||||
searchJob?.cancel()
|
||||
if (searchText.isBlank()) {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
filteredCommands = persistentListOf(),
|
||||
filteredBasicGroups = persistentListOf(),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
searchJob = viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
ensureActive()
|
||||
|
||||
val commands = databaseHelper.getCommandsByQuery(searchText).sortedSearch(searchText)
|
||||
val basicGroups = databaseHelper.getBasicGroupsByQuery(searchText)
|
||||
|
||||
ensureActive()
|
||||
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
filteredCommands = commands.toImmutableList(),
|
||||
filteredBasicGroups = basicGroups.toImmutableList(),
|
||||
)
|
||||
}
|
||||
} catch (ignore: CancellationException) {
|
||||
// Preserve previous results on cancellation
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
@file:OptIn(ExperimentalMaterialApi::class)
|
||||
|
||||
package com.inspiredandroid.linuxcommandbibliotheca.ui.screens.tips
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.items
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.composables.CommandView
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.composables.NestedCommandView
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.composables.NestedText
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.ui.composables.SectionTitle
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
/* Copyright 2022 Simon Schubert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun TipsScreen(
|
||||
onNavigate: (String) -> Unit = {},
|
||||
viewModel: TipsViewModel = koinViewModel(),
|
||||
) {
|
||||
LazyVerticalStaggeredGrid(
|
||||
modifier = Modifier
|
||||
.semantics { contentDescription = "Scroll" },
|
||||
columns = StaggeredGridCells.Adaptive(minSize = 300.dp),
|
||||
) {
|
||||
items(
|
||||
items = viewModel.tips,
|
||||
key = { it.tip.id },
|
||||
contentType = { "tip_item" },
|
||||
) { tip ->
|
||||
TipItemCard(tip = tip, onNavigate = onNavigate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TipItemCard(tip: MergedTip, onNavigate: (String) -> Unit) {
|
||||
Card(
|
||||
elevation = 4.dp,
|
||||
modifier = Modifier
|
||||
.padding(4.dp)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Column(modifier = Modifier.padding(8.dp)) {
|
||||
SectionTitle(title = tip.tip.title)
|
||||
TipSections(sections = tip.sections, onNavigate = onNavigate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TipSections(sections: List<TipSectionElement>, onNavigate: (String) -> Unit) {
|
||||
// Assuming MergedTip.sections is already ImmutableList from ViewModel refactor
|
||||
// If not, it should be passed as ImmutableList<TipSectionElement>
|
||||
sections.forEach { element ->
|
||||
when (element) {
|
||||
is TipSectionElement.Text -> {
|
||||
Text(element.text)
|
||||
}
|
||||
|
||||
is TipSectionElement.Code -> {
|
||||
CommandView(
|
||||
command = element.command,
|
||||
elements = element.elements.toImmutableList(), // elements within TipSectionElement should also be ImmutableList
|
||||
onNavigate = onNavigate,
|
||||
)
|
||||
}
|
||||
|
||||
is TipSectionElement.NestedCode -> {
|
||||
NestedCommandView(
|
||||
text = element.text,
|
||||
command = element.command,
|
||||
commandElements = element.elements.toImmutableList(), // elements within TipSectionElement should also be ImmutableList
|
||||
onNavigate = onNavigate,
|
||||
)
|
||||
}
|
||||
|
||||
is TipSectionElement.NestedText -> {
|
||||
NestedText(
|
||||
textLeft = element.text,
|
||||
textRight = element.info,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca.ui.screens.tips
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.linuxcommandlibrary.shared.CommandElement
|
||||
import com.linuxcommandlibrary.shared.databaseHelper
|
||||
import com.linuxcommandlibrary.shared.getCommandList
|
||||
import databases.Tip
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
sealed class TipSectionElement {
|
||||
data class Text(val text: String) : TipSectionElement()
|
||||
data class Code(val command: String, val elements: ImmutableList<CommandElement>) : TipSectionElement()
|
||||
|
||||
data class NestedCode(
|
||||
val text: String,
|
||||
val command: String,
|
||||
val elements: ImmutableList<CommandElement>,
|
||||
) : TipSectionElement()
|
||||
|
||||
data class NestedText(val text: String, val info: String) : TipSectionElement()
|
||||
}
|
||||
|
||||
data class MergedTip(val tip: Tip, val sections: ImmutableList<TipSectionElement>)
|
||||
|
||||
class TipsViewModel : ViewModel() {
|
||||
|
||||
var tips: ImmutableList<MergedTip>
|
||||
|
||||
init {
|
||||
val tipSectionsFromDB = databaseHelper.getTipSections() // Renamed to avoid confusion with MergedTip.sections
|
||||
tips = databaseHelper.getTips().map { tip ->
|
||||
MergedTip(
|
||||
tip,
|
||||
tipSectionsFromDB.filter { it.tip_id == tip.id }.map { section ->
|
||||
when (section.type) {
|
||||
0L -> {
|
||||
val text =
|
||||
section.data1.replace("\\n", "").replace("<b>", "").replace("</b>", "")
|
||||
.replace("\\'", "")
|
||||
TipSectionElement.Text(text)
|
||||
}
|
||||
|
||||
1L -> {
|
||||
TipSectionElement.Code(
|
||||
section.data1,
|
||||
section.data1.getCommandList(section.extra).toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
||||
3L -> {
|
||||
if (section.data2.startsWith("$")) {
|
||||
TipSectionElement.NestedCode(
|
||||
section.data1,
|
||||
section.data2,
|
||||
section.data2.getCommandList(section.extra).toImmutableList(),
|
||||
)
|
||||
} else {
|
||||
TipSectionElement.NestedText(section.data1, section.data2)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
TipSectionElement.Text("")
|
||||
}
|
||||
}
|
||||
}.toImmutableList(), // Convert list of sections for a tip to ImmutableList
|
||||
)
|
||||
}.toImmutableList() // Convert final list of MergedTip to ImmutableList
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package com.inspiredandroid.linuxcommandbibliotheca.ui.theme
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Typography
|
||||
import androidx.compose.material.darkColors
|
||||
import androidx.compose.material.lightColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.Font
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.inspiredandroid.linuxcommandbibliotheca.R
|
||||
|
||||
/* Copyright 2022 Simon Schubert
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
data class CustomColors(
|
||||
val navBarBackground: Color = Color(0xFF00695C),
|
||||
)
|
||||
|
||||
val LocalCustomColors = compositionLocalOf { CustomColors() }
|
||||
|
||||
@Composable
|
||||
fun LinuxTheme(content: @Composable () -> Unit) {
|
||||
val darkMode = isSystemInDarkTheme()
|
||||
val darkColors = darkColors(
|
||||
primary = Color(0xFFe45151),
|
||||
secondary = Color.White,
|
||||
background = Color(0xFF262626),
|
||||
surface = Color(0xFF262626),
|
||||
)
|
||||
val lightColors = lightColors(
|
||||
primary = Color(0xFFe45151),
|
||||
secondary = Color.Black,
|
||||
background = Color.White,
|
||||
surface = Color.White,
|
||||
)
|
||||
val colorSchema = if (darkMode) darkColors else lightColors
|
||||
|
||||
val techMonoFont = FontFamily(
|
||||
Font(R.font.share_tech_mono),
|
||||
)
|
||||
|
||||
val codeTextStyle = TextStyle(
|
||||
fontFamily = techMonoFont,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 14.sp,
|
||||
color = colorSchema.secondary,
|
||||
)
|
||||
|
||||
val typography = Typography(
|
||||
subtitle2 = codeTextStyle,
|
||||
)
|
||||
|
||||
val customColors = if (darkMode) {
|
||||
CustomColors(
|
||||
navBarBackground = Color(0xFF2D2D2D),
|
||||
)
|
||||
} else {
|
||||
CustomColors(
|
||||
navBarBackground = Color(0xFFFAFAFA),
|
||||
)
|
||||
}
|
||||
CompositionLocalProvider(LocalCustomColors provides customColors) {
|
||||
MaterialTheme(
|
||||
colors = colorSchema,
|
||||
typography = typography,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
46
android/src/main/res/drawable/ic_add_rule.xml
Normal file
46
android/src/main/res/drawable/ic_add_rule.xml
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M6,7"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M12,5c0,-1.2 1,-2 2,-2c-2.6,0 -8.8,0 -9.2,0C3.8,3 3,3.9 3,5s0.8,2 1.8,2c0.4,0 6.6,0 9.2,0C13,7 12,6.2 12,5z"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M6,7c0,0 0,7.5 0,10s1.5,4 3,4h5"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M17,17c0,0 0,-7.5 0,-10s-1.5,-4 -3,-4"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,11L14,11"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,15L14,15"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M19,16h2v8h-2z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M16,19h8v2h-8z" />
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="1920.0"
|
||||
android:viewportHeight="1920.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M1317,1804c-4,-4 -7,-29 -7,-55 0,-42 4,-51 35,-76 102,-84 329,-84 430,0 34,27 36,32 33,81l-3,51 -241,3c-132,1 -243,-1 -247,-4z"
|
||||
android:strokeColor="#00000000" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M1227,1654c-4,-4 -7,-24 -7,-45 0,-37 -1,-38 -42,-41 -42,-3 -43,-4 -43,-38 0,-34 1,-35 42,-38 42,-3 42,-3 45,-45 3,-41 4,-42 38,-42 34,0 35,1 38,42 3,42 3,42 45,45 41,3 42,4 42,38 0,34 -1,35 -42,38 -42,3 -42,3 -45,45 -3,39 -5,42 -33,45 -17,2 -34,0 -38,-4z"
|
||||
android:strokeColor="#00000000" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M1469,1531c-35,-35 -39,-44 -39,-91 0,-47 4,-56 39,-91 35,-35 44,-39 91,-39 47,0 56,4 91,39 35,35 39,44 39,91 0,47 -4,56 -39,91 -35,35 -44,39 -91,39 -47,0 -56,-4 -91,-39z"
|
||||
android:strokeColor="#00000000" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M77,1183c-10,-10 -8,-238 2,-265 28,-73 162,-157 321,-200 122,-33 357,-33 478,0 155,42 254,98 305,174 27,41 28,44 25,167l-3,126 -561,3c-308,1 -564,-1 -567,-5z"
|
||||
android:strokeColor="#00000000" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M1356,1178c-3,-7 -7,-67 -10,-133 -6,-151 -22,-199 -92,-275 -30,-32 -54,-63 -54,-70 0,-20 228,-7 324,20 150,40 248,97 299,172 27,41 28,44 25,167l-3,126 -243,3c-189,2 -243,0 -246,-10z"
|
||||
android:strokeColor="#00000000" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M535,531c-50,-23 -109,-84 -130,-133 -21,-52 -19,-153 4,-203 23,-50 84,-109 133,-130 48,-19 148,-19 196,0 49,21 110,80 133,130 23,49 25,151 5,199 -20,49 -85,116 -131,137 -54,25 -157,25 -210,0z"
|
||||
android:strokeColor="#00000000" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M1175,531c-50,-23 -109,-84 -130,-133 -21,-52 -19,-153 4,-203 23,-50 84,-109 133,-130 48,-19 148,-19 196,0 49,21 110,80 133,130 23,49 25,151 5,199 -20,49 -85,116 -131,137 -54,25 -157,25 -210,0z"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_android_black_24dp.xml
Normal file
9
android/src/main/res/drawable/ic_android_black_24dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M17.6,11.48 L19.44,8.3a0.63,0.63 0,0 0,-1.09 -0.63l-1.88,3.24a11.43,11.43 0,0 0,-8.94 0L5.65,7.67a0.63,0.63 0,0 0,-1.09 0.63L6.4,11.48A10.81,10.81 0,0 0,1 20L23,20A10.81,10.81 0,0 0,17.6 11.48ZM7,17.25A1.25,1.25 0,1 1,8.25 16,1.25 1.25,0 0,1 7,17.25ZM17,17.25A1.25,1.25 0,1 1,18.25 16,1.25 1.25,0 0,1 17,17.25Z" />
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M4,12l1.41,1.41L11,7.83V20h2V7.83l5.58,5.59L20,12l-8,-8 -8,8z" />
|
||||
</vector>
|
||||
18
android/src/main/res/drawable/ic_available_updates.xml
Normal file
18
android/src/main/res/drawable/ic_available_updates.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M14,10l7,-7l0,7z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M10,14l-7,7l0,-7z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M12,3c-4.963,0 -9,4.038 -9,9h2c0,-3.86 3.141,-7 7,-7c2.785,0 5.188,1.639 6.315,4h2.16C19.236,5.51 15.91,3 12,3z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M12,19c-2.785,0 -5.188,-1.639 -6.315,-4h-2.16C4.764,18.49 8.09,21 12,21c4.963,0 9,-4.037 9,-9h-2C19,15.859 15.859,19 12,19z" />
|
||||
</vector>
|
||||
10
android/src/main/res/drawable/ic_baseline_bookmark_24.xml
Normal file
10
android/src/main/res/drawable/ic_baseline_bookmark_24.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2z" />
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M17,3L7,3c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3L19,5c0,-1.1 -0.9,-2 -2,-2zM17,18l-5,-2.18L7,18L7,5h10v13z" />
|
||||
</vector>
|
||||
13
android/src/main/res/drawable/ic_battery_90_black_24dp.xml
Normal file
13
android/src/main/res/drawable/ic_battery_90_black_24dp.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillAlpha=".3"
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M17,5.33C17,4.6 16.4,4 15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33V8h10V5.33z" />
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M7,8v12.67C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V8H7z" />
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M17.71,7.71L12,2h-1v7.59L6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 11,14.41L11,22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM13,5.83l1.88,1.88L13,9.59L13,5.83zM14.88,16.29L13,18.17v-3.76l1.88,1.88z" />
|
||||
</vector>
|
||||
29
android/src/main/res/drawable/ic_bluetooth_start.xml
Normal file
29
android/src/main/res/drawable/ic_bluetooth_start.xml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M8.203,12l0,-8l4,4z"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M8.203,20l0,-8l4,4z"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M3.203,7L9.303,13.1"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M3.203,17L8.903,11.3"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M15.51,14.21l0,8.894l7.395,-4.447z" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_bookmark_black_24dp.xml
Normal file
9
android/src/main/res/drawable/ic_bookmark_black_24dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2z" />
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M17,3L7,3c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3L19,5c0,-1.1 -0.9,-2 -2,-2zM17,18l-5,-2.18L7,18L7,5h10v13z" />
|
||||
</vector>
|
||||
10
android/src/main/res/drawable/ic_change_folder_white.xml
Normal file
10
android/src/main/res/drawable/ic_change_folder_white.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="1920.0"
|
||||
android:viewportHeight="1920.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M229,1587c-18,-12 -44,-38 -56,-56l-23,-34 0,-537 0,-537 23,-34c12,-18 38,-44 56,-56 33,-23 40,-23 305,-23l271,0 80,80 80,80 346,0c343,0 346,0 380,23 18,12 44,38 56,56l23,34 0,457 0,457 -23,34c-12,18 -38,44 -56,56l-34,23 -697,0 -697,0 -34,-23zM1175,890l-110,-110 -5,82 -5,83 -207,3 -208,2 0,50 0,50 208,2 207,3 3,85 3,84 112,-112 112,-112 -110,-110z"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_check_black_24dp.xml
Normal file
9
android/src/main/res/drawable/ic_check_black_24dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_code_white_48dp.xml
Normal file
9
android/src/main/res/drawable/ic_code_white_48dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M9.4,16.6L4.8,12l4.6,-4.6L8,6l-6,6 6,6 1.4,-1.4zM14.6,16.6l4.6,-4.6 -4.6,-4.6L16,6l6,6 -6,6 -1.4,-1.4z" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_collapse_all.xml
Normal file
9
android/src/main/res/drawable/ic_collapse_all.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:pathData="m296,880 l-56,-56 240,-240 240,240 -56,56 -184,-184L296,880ZM480,376L240,136l56,-56 184,184 184,-184 56,56 -240,240Z"
|
||||
android:fillColor="#1f1f1f"/>
|
||||
</vector>
|
||||
10
android/src/main/res/drawable/ic_create_file_white.xml
Normal file
10
android/src/main/res/drawable/ic_create_file_white.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="1920.0"
|
||||
android:viewportHeight="1920.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M389,1747c-18,-12 -44,-38 -56,-56l-23,-34 0,-697 0,-697 23,-34c12,-18 38,-44 56,-56 34,-23 36,-23 385,-23l351,0 243,243 242,242 0,511 0,511 -23,34c-12,18 -38,44 -56,56l-34,23 -537,0 -537,0 -34,-23zM1182,1463l3,-118 118,-3 117,-3 0,-69 0,-69 -117,-3 -118,-3 -3,-117 -3,-118 -69,0 -69,0 -3,118 -3,117 -117,3 -118,3 0,69 0,69 118,3 117,3 3,118 3,117 69,0 69,0 3,-117zM1450,707c0,-1 -90,-92 -200,-202l-200,-200 0,203 0,202 200,0c110,0 200,-1 200,-3z"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
10
android/src/main/res/drawable/ic_create_new_folder_white.xml
Normal file
10
android/src/main/res/drawable/ic_create_new_folder_white.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="1920.0"
|
||||
android:viewportHeight="1920.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M229,1587c-18,-12 -44,-38 -56,-56l-23,-34 0,-537 0,-537 23,-34c12,-18 38,-44 56,-56 33,-23 40,-23 305,-23l271,0 80,80 80,80 346,0c343,0 346,0 380,23 18,12 44,38 56,56l23,34 0,457 0,457 -23,34c-12,18 -38,44 -56,56l-34,23 -697,0 -697,0 -34,-23zM1272,1233l3,-118 118,-3 117,-3 0,-69 0,-69 -117,-3 -118,-3 -3,-117 -3,-118 -69,0 -69,0 -3,118 -3,117 -117,3 -118,3 0,69 0,69 118,3 117,3 3,118 3,117 69,0 69,0 3,-117z"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_delete_black_24dp.xml
Normal file
9
android/src/main/res/drawable/ic_delete_black_24dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
|
||||
</vector>
|
||||
10
android/src/main/res/drawable/ic_delete_file_white.xml
Normal file
10
android/src/main/res/drawable/ic_delete_file_white.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="1920.0"
|
||||
android:viewportHeight="1920.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M389,1747c-18,-12 -44,-38 -56,-56l-23,-34 0,-697 0,-697 23,-34c12,-18 38,-44 56,-56 34,-23 36,-23 385,-23l351,0 243,243 242,242 0,511 0,511 -23,34c-12,18 -38,44 -56,56l-34,23 -537,0 -537,0 -34,-23zM1420,1270l0,-70 -310,0 -310,0 0,70 0,70 310,0 310,0 0,-70zM1450,707c0,-1 -90,-92 -200,-202l-200,-200 0,203 0,202 200,0c110,0 200,-1 200,-3z"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="1920.0"
|
||||
android:viewportHeight="1920.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M229,1587c-18,-12 -44,-38 -56,-56l-23,-34 0,-537 0,-537 23,-34c12,-18 38,-44 56,-56 33,-23 40,-23 305,-23l271,0 80,80 80,80 346,0c343,0 346,0 380,23 18,12 44,38 56,56l23,34 0,457 0,457 -23,34c-12,18 -38,44 -56,56l-34,23 -697,0 -697,0 -34,-23zM1510,1040l0,-70 -310,0 -310,0 0,70 0,70 310,0 310,0 0,-70z"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M21,2L3,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h7v2L8,20v2h8v-2h-2v-2h7c1.1,0 2,-0.9 2,-2L23,4c0,-1.1 -0.9,-2 -2,-2zM21,16L3,16L3,4h18v12z" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_dns_black_24dp.xml
Normal file
9
android/src/main/res/drawable/ic_dns_black_24dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M20,13H4c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h16c0.55,0 1,-0.45 1,-1v-6c0,-0.55 -0.45,-1 -1,-1zM7,19c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM20,3H4c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h16c0.55,0 1,-0.45 1,-1V4c0,-0.55 -0.45,-1 -1,-1zM7,9c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z" />
|
||||
</vector>
|
||||
18
android/src/main/res/drawable/ic_edit_group.xml
Normal file
18
android/src/main/res/drawable/ic_edit_group.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="m10,9c0,-1.7 1.3,-3 3,-3s3,1.3 3,3c0,1.7 -1.3,3 -3,3s-3,-1.3 -3,-3zM13,14c-4.6,0 -6,3.3 -6,3.3l0,1.7l12,0l0,-1.7c0,0 -1.4,-3.3 -6,-3.3z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M19.5,8.5m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="m19.5,13c-1.2,0 -2.1,0.3 -2.8,0.8c2.3,1.1 3.2,3 3.2,3.2l0,0.1l4.1,0l0,-1.3c0,-0.1 -1.1,-2.8 -4.5,-2.8z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="m8.82,3.303l1.231,1.22c0.188,0.186 0.188,0.487 0,0.673l-0.817,0.81l-1.911,-1.893l0.816,-0.81c0.188,-0.186 0.493,-0.186 0.68,0zM6.744,4.687l-5.007,4.961l0,1.893l1.911,0l5.007,-4.961l-1.911,-1.893z" />
|
||||
</vector>
|
||||
81
android/src/main/res/drawable/ic_electronics.xml
Normal file
81
android/src/main/res/drawable/ic_electronics.xml
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M8,1L8,4"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M12,1L12,4"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M16,1L16,4"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M8,20L8,23"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M12,20L12,23"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M16,20L16,23"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M23,8L20,8"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M23,12L20,12"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M23,16L20,16"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M4,8L1,8"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M4,12L1,12"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M4,16L1,16"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M17,5H7C5.9,5 5,5.9 5,7v10c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V7C19,5.9 18.1,5 17,5zM16,15H8V9h8V15z" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_expand_all.xml
Normal file
9
android/src/main/res/drawable/ic_expand_all.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:pathData="M480,880 L240,640l57,-57 183,183 183,-183 57,57L480,880ZM298,376l-58,-56 240,-240 240,240 -58,56 -182,-182 -182,182Z"
|
||||
android:fillColor="#1f1f1f"/>
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_file.xml
Normal file
9
android/src/main/res/drawable/ic_file.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M14,2H6C4.9,2 4,2.9 4,4v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V8L14,2zM18.5,9H13V3.5L18.5,9z" />
|
||||
</vector>
|
||||
14
android/src/main/res/drawable/ic_file_content_white.xml
Normal file
14
android/src/main/res/drawable/ic_file_content_white.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="1920.0"
|
||||
android:viewportHeight="1920.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M389,1747c-18,-12 -44,-38 -56,-56l-23,-34 0,-697 0,-697 23,-34c12,-18 38,-44 56,-56 34,-23 36,-23 385,-23l351,0 243,243 242,242 0,511 0,511 -23,34c-12,18 -38,44 -56,56l-34,23 -537,0 -537,0 -34,-23zM1154,1472c39,-18 90,-49 114,-69 54,-44 122,-146 122,-183 0,-57 -95,-173 -191,-233 -123,-76 -355,-76 -478,0 -96,60 -191,176 -191,233 0,37 68,139 121,181 111,89 177,110 324,107 100,-3 116,-6 179,-36zM1450,707c0,-1 -90,-92 -200,-202l-200,-200 0,203 0,202 200,0c110,0 200,-1 200,-3z"
|
||||
android:strokeColor="#00000000" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M875,1403c-153,-80 -155,-286 -3,-364 73,-37 179,-18 234,43 93,101 64,256 -58,319 -46,23 -131,25 -173,2zM1041,1301c25,-25 29,-37 29,-81 0,-44 -4,-56 -29,-81 -25,-25 -37,-29 -81,-29 -44,0 -56,4 -81,29 -25,25 -29,37 -29,81 0,44 4,56 29,81 25,25 37,29 81,29 44,0 56,-4 81,-29z"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
14
android/src/main/res/drawable/ic_file_copy_white_48dp.xml
Normal file
14
android/src/main/res/drawable/ic_file_copy_white_48dp.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="1920.0"
|
||||
android:viewportHeight="1920.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M86,1408c-24,-34 -24,-742 0,-776 15,-21 20,-22 207,-22l192,0 123,123 122,122 0,265c0,224 -2,269 -16,288 -15,22 -16,22 -314,22 -298,0 -299,0 -314,-22zM630,887c0,-1 -40,-43 -90,-92l-90,-90 0,93 0,92 90,0c50,0 90,-1 90,-3z"
|
||||
android:strokeColor="#00000000" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M1207,1402c-27,-28 -27,-29 -27,-175l0,-146 -100,99c-54,55 -103,100 -107,100 -14,0 -53,-30 -53,-41 0,-6 26,-36 57,-67l57,-57 -135,-5 -134,-5 0,-35 0,-35 134,-5 135,-5 -57,-57c-31,-31 -57,-61 -57,-67 0,-11 39,-41 53,-41 4,0 53,45 107,100l100,99 0,-196 0,-197 27,-28 27,-28 181,0 180,0 123,123 122,122 0,260 0,259 -27,28 -27,28 -276,0 -276,0 -27,-28zM1612,1263l3,-58 58,-3c53,-3 57,-5 57,-27 0,-22 -4,-24 -57,-27l-58,-3 -3,-57c-3,-54 -5,-58 -27,-58 -22,0 -24,4 -27,58l-3,57 -57,3c-54,3 -58,5 -58,27 0,22 4,24 58,27l57,3 3,58c3,53 5,57 27,57 22,0 24,-4 27,-57zM1740,887c0,-1 -40,-43 -90,-92l-90,-90 0,93 0,92 90,0c50,0 90,-1 90,-3z"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z" />
|
||||
</vector>
|
||||
10
android/src/main/res/drawable/ic_file_download_white.xml
Normal file
10
android/src/main/res/drawable/ic_file_download_white.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="1920.0"
|
||||
android:viewportHeight="1920.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M389,1747c-18,-12 -44,-38 -56,-56l-23,-34 0,-697 0,-697 23,-34c12,-18 38,-44 56,-56 34,-23 36,-23 385,-23l351,0 243,243 242,242 0,511 0,511 -23,34c-12,18 -38,44 -56,56l-34,23 -537,0 -537,0 -34,-23zM1230,1510l0,-30 -270,0 -270,0 0,30 0,30 270,0 270,0 0,-30zM1095,1250l129,-129 -75,-3 -74,-3 -3,-117 -3,-118 -109,0 -109,0 -3,118 -3,117 -74,3 -75,3 129,129c72,72 132,130 135,130 3,0 63,-58 135,-130zM1450,707c0,-1 -90,-92 -200,-202l-200,-200 0,203 0,202 200,0c110,0 200,-1 200,-3z"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
10
android/src/main/res/drawable/ic_file_edit_white_48dp.xml
Normal file
10
android/src/main/res/drawable/ic_file_edit_white_48dp.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="1920.0"
|
||||
android:viewportHeight="1920.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M389,1747c-18,-12 -44,-38 -56,-56l-23,-34 0,-697 0,-697 23,-34c12,-18 38,-44 56,-56 34,-23 36,-23 385,-23l351,0 243,243 242,242 0,511 0,511 -23,34c-12,18 -38,44 -56,56l-34,23 -537,0 -537,0 -34,-23zM972,1363l218,-218 -72,-72 -73,-73 -217,217 -218,218 0,72 0,73 72,0 73,0 217,-217zM1310,1006c0,-26 -98,-126 -124,-126 -11,0 -37,17 -58,37l-38,37 72,73 72,73 38,-37c21,-20 38,-46 38,-57zM1450,707c0,-1 -90,-92 -200,-202l-200,-200 0,203 0,202 200,0c110,0 200,-1 200,-3z"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
26
android/src/main/res/drawable/ic_file_link_white_48dp.xml
Normal file
26
android/src/main/res/drawable/ic_file_link_white_48dp.xml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="1920.0"
|
||||
android:viewportHeight="1920.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M181,1616c-51,-28 -50,-23 -51,-593 0,-392 3,-539 12,-558 23,-52 43,-55 343,-55 177,0 275,4 275,10 0,6 7,10 15,10 8,0 15,7 15,15 0,8 7,15 15,15 8,0 15,7 15,15 0,8 7,15 15,15 8,0 15,7 15,15 0,8 7,15 15,15 8,0 15,7 15,15 0,8 7,15 15,15 8,0 15,7 15,15 0,8 7,15 15,15 8,0 15,7 15,15 0,8 7,15 15,15 8,0 15,7 15,15 0,8 7,15 15,15 8,0 15,7 15,15 0,8 7,15 15,15 8,0 15,7 15,15 0,8 7,15 15,15 8,0 15,7 15,15 0,8 7,15 15,15 8,0 15,7 15,15 0,8 5,15 10,15 15,0 13,781 -2,815 -25,54 -32,55 -480,55 -324,-1 -418,-4 -437,-14zM960,815c0,-8 -7,-15 -15,-15 -8,0 -15,-7 -15,-15 0,-8 -7,-15 -15,-15 -8,0 -15,-7 -15,-15 0,-8 -7,-15 -15,-15 -8,0 -15,-7 -15,-15 0,-8 -7,-15 -15,-15 -8,0 -15,-7 -15,-15 0,-8 -7,-15 -15,-15 -8,0 -15,-7 -15,-15 0,-8 -7,-15 -15,-15 -8,0 -15,-7 -15,-15 0,-8 -7,-15 -15,-15 -8,0 -15,-7 -15,-15 0,-8 -7,-15 -15,-15 -8,0 -15,-7 -15,-15 0,-8 -7,-15 -15,-15 -13,0 -15,22 -15,135l0,135 135,0c113,0 135,-2 135,-15z"
|
||||
android:strokeColor="#00000000" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M1270,1612c-126,-57 -149,-214 -43,-297 36,-28 44,-30 141,-33l103,-4 -3,44 -3,43 -88,5c-78,4 -91,8 -108,29 -24,30 -24,72 0,102 17,21 30,25 108,29l88,5 3,43 3,42 -93,-1c-51,0 -100,-3 -108,-7z"
|
||||
android:strokeColor="#00000000" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M1512,1578l3,-43 88,-5c78,-4 91,-8 108,-29 10,-13 19,-36 19,-51 0,-15 -9,-38 -19,-51 -17,-21 -30,-25 -108,-29l-88,-5 -3,-43 -3,-44 103,4c97,3 105,5 141,33 44,34 67,81 67,135 0,54 -23,101 -67,135 -36,28 -44,30 -141,33l-103,4 3,-44z"
|
||||
android:strokeColor="#00000000" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M1354,1486c-3,-8 -4,-29 -2,-48l3,-33 135,0 135,0 0,45 0,45 -133,3c-108,2 -133,0 -138,-12z"
|
||||
android:strokeColor="#00000000" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M1176,1178c-24,-34 -24,-742 0,-776 15,-21 20,-22 207,-22l192,0 123,123 122,122 0,265c0,224 -2,269 -16,288 -15,22 -16,22 -314,22 -298,0 -299,0 -314,-22zM1720,657c0,-1 -40,-43 -90,-92l-90,-90 0,93 0,92 90,0c50,0 90,-1 90,-3z"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
14
android/src/main/res/drawable/ic_file_move_white.xml
Normal file
14
android/src/main/res/drawable/ic_file_move_white.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="1920.0"
|
||||
android:viewportHeight="1920.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M97,1402l-27,-28 0,-354 0,-354 27,-28 27,-28 181,0 180,0 123,123 122,122 0,260 0,259 -27,28 -27,28 -276,0 -276,0 -27,-28zM620,1175l0,-25 -145,0 -145,0 0,25 0,25 145,0 145,0 0,-25zM630,887c0,-1 -40,-43 -90,-92l-90,-90 0,93 0,92 90,0c50,0 90,-1 90,-3z"
|
||||
android:strokeColor="#00000000" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M1196,1408c-13,-18 -16,-53 -16,-175l0,-152 -100,99c-54,55 -103,100 -107,100 -14,0 -53,-30 -53,-41 0,-6 26,-36 57,-67l57,-57 -135,-5 -134,-5 0,-35 0,-35 134,-5 135,-5 -57,-57c-31,-31 -57,-61 -57,-67 0,-11 39,-41 53,-41 4,0 53,45 107,100l100,99 0,-202c0,-167 3,-206 16,-225 15,-21 20,-22 207,-22l192,0 123,123 122,122 0,265c0,224 -2,269 -16,288 -15,22 -16,22 -314,22 -298,0 -299,0 -314,-22zM1740,887c0,-1 -40,-43 -90,-92l-90,-90 0,93 0,92 90,0c50,0 90,-1 90,-3z"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="1920.0"
|
||||
android:viewportHeight="1920.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M389,1747c-18,-12 -44,-38 -56,-56l-23,-34 0,-697 0,-697 23,-34c12,-18 38,-44 56,-56 34,-23 36,-23 385,-23l351,0 243,243 242,242 0,511 0,511 -23,34c-12,18 -38,44 -56,56l-34,23 -537,0 -537,0 -34,-23zM1254,1614c14,-13 16,-50 16,-252 0,-130 -4,-244 -9,-251 -5,-8 -31,-17 -58,-20l-48,-6 -5,-80c-8,-135 -67,-195 -190,-195 -123,0 -182,60 -190,195l-5,80 -48,6c-27,3 -53,12 -58,20 -5,7 -9,121 -9,251 0,202 2,239 16,252 13,14 54,16 294,16 240,0 281,-2 294,-16zM1450,707c0,-1 -90,-92 -200,-202l-200,-200 0,203 0,202 200,0c110,0 200,-1 200,-3z"
|
||||
android:strokeColor="#00000000" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M905,1415c-52,-51 -18,-135 54,-135 42,0 81,38 81,80 0,19 -9,40 -25,55 -15,16 -36,25 -55,25 -19,0 -40,-9 -55,-25z"
|
||||
android:strokeColor="#00000000" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M837,1084c-4,-4 -7,-30 -7,-58 0,-60 25,-107 72,-136 42,-25 74,-25 116,0 51,31 73,75 70,140l-3,55 -121,3c-66,1 -123,-1 -127,-4z"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M17.81,4.47c-0.08,0 -0.16,-0.02 -0.23,-0.06C15.66,3.42 14,3 12.01,3c-1.98,0 -3.86,0.47 -5.57,1.41 -0.24,0.13 -0.54,0.04 -0.68,-0.2 -0.13,-0.24 -0.04,-0.55 0.2,-0.68C7.82,2.52 9.86,2 12.01,2c2.13,0 3.99,0.47 6.03,1.52 0.25,0.13 0.34,0.43 0.21,0.67 -0.09,0.18 -0.26,0.28 -0.44,0.28zM3.5,9.72c-0.1,0 -0.2,-0.03 -0.29,-0.09 -0.23,-0.16 -0.28,-0.47 -0.12,-0.7 0.99,-1.4 2.25,-2.5 3.75,-3.27C9.98,4.04 14,4.03 17.15,5.65c1.5,0.77 2.76,1.86 3.75,3.25 0.16,0.22 0.11,0.54 -0.12,0.7 -0.23,0.16 -0.54,0.11 -0.7,-0.12 -0.9,-1.26 -2.04,-2.25 -3.39,-2.94 -2.87,-1.47 -6.54,-1.47 -9.4,0.01 -1.36,0.7 -2.5,1.7 -3.4,2.96 -0.08,0.14 -0.23,0.21 -0.39,0.21zM9.75,21.79c-0.13,0 -0.26,-0.05 -0.35,-0.15 -0.87,-0.87 -1.34,-1.43 -2.01,-2.64 -0.69,-1.23 -1.05,-2.73 -1.05,-4.34 0,-2.97 2.54,-5.39 5.66,-5.39s5.66,2.42 5.66,5.39c0,0.28 -0.22,0.5 -0.5,0.5s-0.5,-0.22 -0.5,-0.5c0,-2.42 -2.09,-4.39 -4.66,-4.39 -2.57,0 -4.66,1.97 -4.66,4.39 0,1.44 0.32,2.77 0.93,3.85 0.64,1.15 1.08,1.64 1.85,2.42 0.19,0.2 0.19,0.51 0,0.71 -0.11,0.1 -0.24,0.15 -0.37,0.15zM16.92,19.94c-1.19,0 -2.24,-0.3 -3.1,-0.89 -1.49,-1.01 -2.38,-2.65 -2.38,-4.39 0,-0.28 0.22,-0.5 0.5,-0.5s0.5,0.22 0.5,0.5c0,1.41 0.72,2.74 1.94,3.56 0.71,0.48 1.54,0.71 2.54,0.71 0.24,0 0.64,-0.03 1.04,-0.1 0.27,-0.05 0.53,0.13 0.58,0.41 0.05,0.27 -0.13,0.53 -0.41,0.58 -0.57,0.11 -1.07,0.12 -1.21,0.12zM14.91,22c-0.04,0 -0.09,-0.01 -0.13,-0.02 -1.59,-0.44 -2.63,-1.03 -3.72,-2.1 -1.4,-1.39 -2.17,-3.24 -2.17,-5.22 0,-1.62 1.38,-2.94 3.08,-2.94 1.7,0 3.08,1.32 3.08,2.94 0,1.07 0.93,1.94 2.08,1.94s2.08,-0.87 2.08,-1.94c0,-3.77 -3.25,-6.83 -7.25,-6.83 -2.84,0 -5.44,1.58 -6.61,4.03 -0.39,0.81 -0.59,1.76 -0.59,2.8 0,0.78 0.07,2.01 0.67,3.61 0.1,0.26 -0.03,0.55 -0.29,0.64 -0.26,0.1 -0.55,-0.04 -0.64,-0.29 -0.49,-1.31 -0.73,-2.61 -0.73,-3.96 0,-1.2 0.23,-2.29 0.68,-3.24 1.33,-2.79 4.28,-4.6 7.51,-4.6 4.55,0 8.25,3.51 8.25,7.83 0,1.62 -1.38,2.94 -3.08,2.94s-3.08,-1.32 -3.08,-2.94c0,-1.07 -0.93,-1.94 -2.08,-1.94s-2.08,0.87 -2.08,1.94c0,1.71 0.66,3.31 1.87,4.51 0.95,0.94 1.86,1.46 3.27,1.85 0.27,0.07 0.42,0.35 0.35,0.61 -0.05,0.23 -0.26,0.38 -0.47,0.38z" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_flash_on_black_24dp.xml
Normal file
9
android/src/main/res/drawable/ic_flash_on_black_24dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M7,2v11h3v9l7,-12h-4l4,-8z" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_folder_black_40dp.xml
Normal file
9
android/src/main/res/drawable/ic_folder_black_40dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z" />
|
||||
</vector>
|
||||
10
android/src/main/res/drawable/ic_folder_list_white.xml
Normal file
10
android/src/main/res/drawable/ic_folder_list_white.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="1920.0"
|
||||
android:viewportHeight="1920.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M229,1587c-18,-12 -44,-38 -56,-56l-23,-34 0,-537 0,-537 23,-34c12,-18 38,-44 56,-56 33,-23 40,-23 305,-23l271,0 80,80 80,80 346,0c343,0 346,0 380,23 18,12 44,38 56,56l23,34 0,457 0,457 -23,34c-12,18 -38,44 -56,56l-34,23 -697,0 -697,0 -34,-23zM530,1390l0,-110 -80,0c-73,0 -80,2 -80,20 0,16 8,19 58,22 49,3 57,6 57,23 0,14 -8,21 -27,23 -20,2 -28,9 -28,22 0,13 8,20 28,22 19,2 27,9 27,23 0,17 -8,20 -57,23 -50,3 -58,6 -58,22 0,18 7,20 80,20l80,0 0,-110zM1490,1390l0,-50 -410,0 -410,0 0,50 0,50 410,0 410,0 0,-50zM530,1120c0,-17 -7,-20 -45,-20 -30,0 -45,-4 -45,-13 0,-7 20,-35 45,-64 28,-32 45,-61 45,-77 0,-26 -1,-26 -80,-26 -73,0 -80,2 -80,20 0,17 7,20 45,20 30,0 45,4 45,13 0,7 -20,35 -45,64 -28,32 -45,61 -45,77 0,26 1,26 80,26 73,0 80,-2 80,-20zM1490,1030l0,-50 -410,0 -410,0 0,50 0,50 410,0 410,0 0,-50zM470,670l0,-110 -50,0c-43,0 -50,3 -50,19 0,14 8,21 28,23 27,3 27,3 30,91 3,79 5,87 22,87 19,0 20,-7 20,-110zM1490,670l0,-50 -410,0 -410,0 0,50 0,50 410,0 410,0 0,-50z"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
14
android/src/main/res/drawable/ic_folder_path_white.xml
Normal file
14
android/src/main/res/drawable/ic_folder_path_white.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="1920.0"
|
||||
android:viewportHeight="1920.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M229,1587c-18,-12 -44,-38 -56,-56l-23,-34 0,-537 0,-537 23,-34c12,-18 38,-44 56,-56 33,-23 40,-23 305,-23l271,0 80,80 80,80 346,0c343,0 346,0 380,23 18,12 44,38 56,56l23,34 0,457 0,457 -23,34c-12,18 -38,44 -56,56l-34,23 -697,0 -697,0 -34,-23zM1154,1292c39,-18 90,-49 114,-69 54,-44 122,-146 122,-183 0,-57 -95,-173 -191,-233 -123,-76 -355,-76 -478,0 -96,60 -191,176 -191,233 0,37 68,139 121,181 111,89 177,110 324,107 100,-3 116,-6 179,-36z"
|
||||
android:strokeColor="#00000000" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M875,1223c-153,-80 -155,-286 -3,-364 73,-37 179,-18 234,43 93,101 64,256 -58,319 -46,23 -131,25 -173,2zM1041,1121c25,-25 29,-37 29,-81 0,-44 -4,-56 -29,-81 -25,-25 -37,-29 -81,-29 -44,0 -56,4 -81,29 -25,25 -29,37 -29,81 0,44 4,56 29,81 25,25 37,29 81,29 44,0 56,-4 81,-29z"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_healing_black_24dp.xml
Normal file
9
android/src/main/res/drawable/ic_healing_black_24dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M17.73,12.02l3.98,-3.98c0.39,-0.39 0.39,-1.02 0,-1.41l-4.34,-4.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-3.98,3.98L8,2.29C7.8,2.1 7.55,2 7.29,2c-0.25,0 -0.51,0.1 -0.7,0.29L2.25,6.63c-0.39,0.39 -0.39,1.02 0,1.41l3.98,3.98L2.25,16c-0.39,0.39 -0.39,1.02 0,1.41l4.34,4.34c0.39,0.39 1.02,0.39 1.41,0l3.98,-3.98 3.98,3.98c0.2,0.2 0.45,0.29 0.71,0.29 0.26,0 0.51,-0.1 0.71,-0.29l4.34,-4.34c0.39,-0.39 0.39,-1.02 0,-1.41l-3.99,-3.98zM12,9c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM7.29,10.96L3.66,7.34l3.63,-3.63 3.62,3.62 -3.62,3.63zM10,13c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM12,15c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM14,11c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM16.66,20.34l-3.63,-3.62 3.63,-3.63 3.62,3.62 -3.62,3.63z" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_icon_bitcoin.xml
Normal file
9
android/src/main/res/drawable/ic_icon_bitcoin.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#333333"
|
||||
android:pathData="M12,0C5.3844,0 0,5.3844 0,12C0,18.6156 5.3844,24 12,24C18.6156,24 24,18.6156 24,12C24,5.3844 18.6156,0 12,0zM12,2C17.5347,2 22,6.4653 22,12C22,17.5347 17.5347,22 12,22C6.4653,22 2,17.5347 2,12C2,6.4653 6.4653,2 12,2zM10,4L10,6L8,6L8,18L10,18L10,20L11,20L11,18L12,18L12,20L13,20L13,17.9805C14.2095,17.9306 15.1722,17.6492 15.8613,17.1133C16.6203,16.5223 17,15.6539 17,14.5059C17,13.8409 16.82,13.256 16.459,12.75C16.107,12.256 15.5788,11.9334 14.8828,11.7754C15.6218,11.2264 16,10.43 16,9.375L16,9.2871C16,8.1941 15.6035,7.3732 14.8105,6.8242C14.3272,6.4896 13.7142,6.2695 13,6.1387L13,4L12,4L12,6.0254C11.8332,6.0168 11.6765,6 11.5,6L11,6L11,4L10,4zM10.4102,8L12,8C12.375,8 12.7145,8.0849 13.0645,8.3359C13.4145,8.5869 13.5898,8.974 13.5898,9.5C13.5898,9.98 13.4173,10.4267 13.0703,10.6777C12.7223,10.9277 12.375,11 12,11L10.4102,11.002L10.4102,8zM10.4102,13L12.8145,13C13.4305,13 13.882,13.1353 14.168,13.4043C14.454,13.6733 14.5977,14.0623 14.5977,14.5723C14.5977,15.0433 14.4298,15.3987 14.0938,15.6387C13.7577,15.8797 13.2718,16 12.6328,16L10.4102,16L10.4102,13z" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_icon_controller.xml
Normal file
9
android/src/main/res/drawable/ic_icon_controller.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#333333"
|
||||
android:pathData="M24,14.9C24,9.4 19,6 17,6s-2.4,1 -5,1S9,6 7,6s-7,3.4 -7,8.9c0,0 0,0 0,0c0,0 0,0 0,0.1c0,2.8 2.2,5 5,5c2.1,0 4.6,-2 7,-2s5,2 7,2c2.8,0 5,-2.2 5,-5C24,15 24,15 24,14.9C24,14.9 24,14.9 24,14.9zM10,14H8v2H6v-2H4v-2h2v-2h2v2h2V14zM17,9.7c0.7,0 1.3,0.6 1.3,1.3s-0.6,1.3 -1.3,1.3s-1.3,-0.6 -1.3,-1.3S16.3,9.7 17,9.7zM13.7,13c0,-0.7 0.6,-1.3 1.3,-1.3s1.3,0.6 1.3,1.3s-0.6,1.3 -1.3,1.3S13.7,13.7 13.7,13zM17,16.3c-0.7,0 -1.3,-0.6 -1.3,-1.3s0.6,-1.3 1.3,-1.3s1.3,0.6 1.3,1.3S17.7,16.3 17,16.3zM19,14.3c-0.7,0 -1.3,-0.6 -1.3,-1.3s0.6,-1.3 1.3,-1.3s1.3,0.6 1.3,1.3S19.7,14.3 19,14.3z" />
|
||||
</vector>
|
||||
24
android/src/main/res/drawable/ic_icon_emacs.xml
Normal file
24
android/src/main/res/drawable/ic_icon_emacs.xml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="512"
|
||||
android:viewportHeight="512">
|
||||
<path
|
||||
android:fillAlpha="0.4034118"
|
||||
android:fillColor="#211f46"
|
||||
android:pathData="m488.067,256.694c0,130.061 -104.369,235.497 -233.115,235.497 -128.746,0 -233.115,-105.435 -233.115,-235.497 0,-130.061 104.369,-235.497 233.115,-235.497 128.746,0 233.115,105.435 233.115,235.497z"
|
||||
android:strokeWidth="8.421117"
|
||||
android:strokeAlpha="0.40500003"
|
||||
android:strokeColor="#0a0b1b" />
|
||||
<path
|
||||
android:pathData="m488.067,256.694c0,130.061 -104.369,235.497 -233.115,235.497 -128.746,0 -233.115,-105.435 -233.115,-235.497 0,-130.061 104.369,-235.497 233.115,-235.497 128.746,0 233.115,105.435 233.115,235.497z"
|
||||
android:strokeWidth="13.33816814"></path>
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="m174.829,422.11c0,0 19.739,1.396 45.131,-0.842 10.283,-0.906 49.327,-4.741 78.517,-11.143 0,0 35.59,-7.617 54.63,-14.633 19.923,-7.342 30.764,-13.573 35.643,-22.402 -0.213,-1.809 1.502,-8.224 -7.685,-12.078 -23.489,-9.852 -50.73,-8.07 -104.634,-9.213 -59.777,-2.054 -79.663,-12.06 -90.256,-20.118 -10.158,-8.175 -5.05,-30.793 38.474,-50.715 21.924,-10.609 107.87,-30.187 107.87,-30.187 -28.945,-14.307 -82.919,-39.459 -94.013,-44.89 -9.731,-4.763 -25.303,-11.936 -28.678,-20.614 -3.827,-8.331 9.038,-15.507 16.225,-17.562 23.145,-6.676 55.818,-10.825 85.555,-11.291 14.947,-0.234 17.373,-1.196 17.373,-1.196 20.624,-3.421 34.201,-17.532 28.545,-39.879 -5.078,-22.81 -31.862,-36.214 -57.314,-31.574 -23.968,4.37 -81.738,21.15 -81.738,21.15 71.408,-0.618 83.359,0.574 88.697,8.037 3.152,4.407 -1.432,10.451 -20.476,13.561 -20.733,3.386 -63.831,7.464 -63.831,7.464 -41.345,2.455 -70.468,2.62 -79.203,21.113 -5.707,12.082 6.085,22.763 11.254,29.449 21.841,24.289 53.388,37.389 73.695,47.036 7.641,3.63 30.059,10.484 30.059,10.484 -65.878,-3.623 -113.4,16.605 -141.276,39.896 -31.529,29.163 -17.581,63.924 47.012,85.327 38.152,12.642 57.072,18.587 113.981,13.462 33.52,-1.807 38.804,-0.732 39.138,2.019 0.47,3.872 -37.231,13.492 -47.524,16.461 -26.185,7.553 -94.828,22.804 -95.171,22.878z"
|
||||
android:strokeWidth="0"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_icon_fun.xml
Normal file
9
android/src/main/res/drawable/ic_icon_fun.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#333333"
|
||||
android:pathData="M12,2C6.489,2 2,6.489 2,12C2,17.511 6.489,22 12,22C17.511,22 22,17.511 22,12C22,6.489 17.511,2 12,2zM12,4C15.3674,4 18.2326,6.0649 19.416,9L6.0176,9C5.4586,9 5.0076,9.4586 5.0176,10.0176L5.0352,11C5.2062,12.694 6.238,14 8,14C9.933,14 11,11 12,11C13,11 14.067,14 16,14C17.762,14 18.7938,12.694 18.9648,11L19.9316,11C19.9723,11.3281 20,11.6605 20,12C20,16.4301 16.4301,20 12,20C7.5699,20 4,16.4301 4,12C4,7.5699 7.5699,4 12,4zM17,15C17,15 16,16 10,16C10,16 9.67,18 12,18C16,18 17,15 17,15z" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_icon_git.xml
Normal file
9
android/src/main/res/drawable/ic_icon_git.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="122.52dp"
|
||||
android:height="122.52dp"
|
||||
android:viewportWidth="122.52"
|
||||
android:viewportHeight="122.52">
|
||||
<path
|
||||
android:fillColor="#333333"
|
||||
android:pathData="M120.206,55.806L66.714,2.316c-3.08,-3.08 -8.076,-3.08 -11.16,0l-11.106,11.11 14.09,14.09c3.275,-1.106 7.03,-0.364 9.64,2.246 2.624,2.626 3.36,6.413 2.226,9.7l13.58,13.579c3.287,-1.133 7.076,-0.4 9.7,2.228 3.668,3.666 3.668,9.608 0,13.276a9.389,9.389 0,0 1,-15.322 -10.21L65.695,45.669v33.33a9.454,9.454 0,0 1,2.482 1.774c3.667,3.666 3.667,9.608 0,13.28 -3.667,3.665 -9.612,3.665 -13.276,0 -3.667,-3.672 -3.667,-9.614 0,-13.28a9.375,9.375 0,0 1,3.076 -2.048V45.087a9.305,9.305 0,0 1,-3.076 -2.049c-2.777,-2.776 -3.445,-6.852 -2.02,-10.263L38.99,18.882 2.31,55.559a7.895,7.895 0,0 0,0 11.161l53.495,53.49a7.892,7.892 0,0 0,11.159 0l53.242,-53.242a7.893,7.893 0,0 0,0 -11.162" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_icon_json.xml
Normal file
9
android/src/main/res/drawable/ic_icon_json.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#333333"
|
||||
android:pathData="M6,2C4.895,2 4,2.895 4,4L4,9L3,9C1.895,9 1,9.895 1,11L1,16C1,17.105 1.895,18 3,18L4,18L4,20C4,21.105 4.895,22 6,22L18,22C19.105,22 20,21.105 20,20L20,18C21.105,18 22,17.105 22,16L22,11C22,9.895 21.105,9 20,9L20,7.8281C20,7.2981 19.7891,6.7891 19.4141,6.4141L15.5859,2.5859C15.2109,2.2109 14.7019,2 14.1719,2L6,2zM6,4L14,4L14,7C14,7.552 14.448,8 15,8L18,8L18,9L6,9L6,4zM5,11L6,11L6,14.5C6,15.328 5.328,16 4.5,16C3.672,16 3,15.328 3,14.5L4,14.5C4,14.776 4.224,15 4.5,15C4.776,15 5,14.776 5,14.5L5,11zM8.6445,11C10.0675,11.041 10.1543,12.2829 10.1543,12.5039L9.1875,12.5039C9.1875,12.4009 9.2049,11.8066 8.6289,11.8066C8.4539,11.8066 8.0598,11.8842 8.0898,12.3672C8.1188,12.8102 8.7035,13.0194 8.8105,13.0664C9.0345,13.1484 10.1414,13.6424 10.1504,14.6504C10.1524,14.8644 10.0971,15.985 8.6641,16C7.1051,16.017 7,14.6754 7,14.3984L7.9746,14.3984C7.9746,14.5454 7.9871,15.2562 8.6641,15.2012C9.0711,15.1672 9.1598,14.8743 9.1738,14.6563C9.1968,14.2893 8.8466,14.0686 8.4766,13.8906C7.9566,13.6406 7.1341,13.3334 7.1191,12.3594C7.1061,11.4824 7.7505,10.975 8.6445,11zM19,11L20,11L20,16L18.8457,16L17,12.7363L17,16L16,16L16,11.0234L17.1543,11.0234L19,14.2676L19,11zM13,11.0645C14.864,11.0345 15,12.8414 15,13.1914L15,14C15,14.342 14.9138,16.1015 13.0078,16.0605C10.9668,16.0185 11,14.342 11,14L11,13.1914C11,12.8404 11.167,11.0935 13,11.0645zM13.002,11.8867C12.057,11.9057 12,12.9535 12,13.1855L12,14C12,14.222 12.1547,15.2592 13.0117,15.2422C13.8337,15.2272 14,14.221 14,14L14,13.1855C14,12.9535 13.888,11.8687 13.002,11.8867zM6,18L18,18L18,20L6,20L6,18z" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_icon_mouse.xml
Normal file
9
android/src/main/res/drawable/ic_icon_mouse.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#333333"
|
||||
android:pathData="M12,2C7.5898,2 4,5.5898 4,10L6,10C6,7.0313 8.168,4.5742 11,4.0938L11,10L13,10L13,4.0938C15.832,4.5703 18,7.0313 18,10L20,10C20,5.5898 16.4102,2 12,2ZM4,11L4,14C4,18.4102 7.5898,22 12,22C16.4102,22 20,18.4102 20,14L20,11ZM6,13L18,13L18,14C18,17.3086 15.3086,20 12,20C8.6914,20 6,17.3086 6,14Z" />
|
||||
</vector>
|
||||
23
android/src/main/res/drawable/ic_icon_skull.xml
Normal file
23
android/src/main/res/drawable/ic_icon_skull.xml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#333333"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M12,15.2c-0.5,0.1 -1.3,1.4 -1.3,2.1c0,0.3 0.3,0.5 0.5,0.5c0.4,0 0.7,-0.4 0.8,-0.7c0.1,0.3 0.3,0.7 0.8,0.7c0.3,0 0.5,-0.2 0.5,-0.5C13.3,16.6 12.5,15.3 12,15.2z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M20,10c0,-3.9 -3.6,-7 -8,-7s-8,3.1 -8,7c0,1.8 1,4 1,4v1c0,1.1 0.9,2 2,2h0c1.1,0 2,0.9 2,2v0c0,1.1 0.9,2 2,2h2c1.1,0 2,-0.9 2,-2v0c0,-1.1 0.9,-2 2,-2h0c1.1,0 2,-0.9 2,-2v-1C19,14 20,11.8 20,10z"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#333333" />
|
||||
<path
|
||||
android:fillColor="#333333"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M15,13m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0" />
|
||||
<path
|
||||
android:fillColor="#333333"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M9,13m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0" />
|
||||
</vector>
|
||||
26
android/src/main/res/drawable/ic_icon_system_task.xml
Normal file
26
android/src/main/res/drawable/ic_icon_system_task.xml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M4,4h16c0.6,0 1,0.4 1,1v10c0,0.6 -0.4,1 -1,1H4c-0.6,0 -1,-0.4 -1,-1V5C3,4.4 3.4,4 4,4z"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#333333" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M7,20L17,20"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#333333" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M12,20L12,18"
|
||||
android:strokeWidth="4"
|
||||
android:strokeColor="#333333" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M6,10l3,0l2,-2l2,4l2,-2l3,0"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#333333" />
|
||||
</vector>
|
||||
12
android/src/main/res/drawable/ic_icon_user.xml
Normal file
12
android/src/main/res/drawable/ic_icon_user.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#333333"
|
||||
android:pathData="M12,8m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0" />
|
||||
<path
|
||||
android:fillColor="#333333"
|
||||
android:pathData="M12,14c-6.1,0 -8,4 -8,4v2h16v-2C20,18 18.1,14 12,14z" />
|
||||
</vector>
|
||||
132
android/src/main/res/drawable/ic_icon_vim.xml
Normal file
132
android/src/main/res/drawable/ic_icon_vim.xml
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="544.17"
|
||||
android:viewportHeight="544.8642">
|
||||
<path
|
||||
android:fillColor="#019833"
|
||||
android:pathData="M274.305,35.818 L37.516,272.926 273.319,509.046 510.108,271.938 274.305,35.818z"
|
||||
android:strokeWidth="0.94571567"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter" />
|
||||
<path
|
||||
android:fillColor="#66fe98"
|
||||
android:pathData="m273.319,36.806 l0,-20.747 -257.509,257.855 21.706,0 235.803,-237.108z"
|
||||
android:strokeWidth="0.94571567"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter" />
|
||||
<path
|
||||
android:fillColor="#45fe02"
|
||||
android:pathData="m272.727,36.806 l0,-20.747 257.509,257.855 -21.706,0 -235.803,-237.108z"
|
||||
android:strokeWidth="0.94571567"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter" />
|
||||
<path
|
||||
android:fillColor="#017d17"
|
||||
android:pathData="m273.319,510.429 l0,20.747 -257.509,-257.855 21.706,0 235.803,237.108z"
|
||||
android:strokeWidth="0.94571567"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="m63.582,42.246 l166.04,0 10.116,10.129 0,31.087 -8.023,9.78 -18.139,0 0,154.388 156.273,-154.388 -25.813,0 -9.069,-9.78 0,-32.834 8.372,-7.684 168.133,0 8.372,8.383 0,30.738 -380.218,390.51 -43.254,0 -12.52,-7.238 0,-373.491 -20.967,0 -7.674,-7.684 0,-32.834 8.372,-9.082z"
|
||||
android:strokeWidth="27.644022"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter" />
|
||||
<path
|
||||
android:fillColor="#005d04"
|
||||
android:pathData="m272.727,510.429 l0,20.747 257.509,-257.855 -21.706,0 -235.803,237.108z"
|
||||
android:strokeWidth="0.94571567"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M272.624,14.147 L14.147,272.971 271.547,530.718 530.024,271.893 272.624,14.147z"
|
||||
android:strokeWidth="8.293206"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter" />
|
||||
<path
|
||||
android:fillColor="#fefefe"
|
||||
android:pathData="m231.387,58.047 l9.373,-4.94 -9.62,-9.633 -167.479,0 -8.51,8.521 0,32.232 9.435,9.447 4.501,-9.447 -5.92,-5.928 0,-22.723 4.44,-3.952 159.833,0 3.946,6.422z"
|
||||
android:strokeWidth="0.94571567"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="m349.794,50.63 l-5.914,5.922 0,21.741 5.18,5.187 27.366,0 0,21.006 -181.033,183.74 0,-204.487 30.086,0 6.173,-6.181 0,-22 -5.698,-4.409 -158.328,0 -5.18,5.187 0,22.476 5.266,5.273 27.539,0 0,376.557 5.18,5.187 31.294,0 379.375,-391.988 0,-17.289 -5.914,-5.922 -155.393,0z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter" />
|
||||
<path
|
||||
android:fillColor="#fefefe"
|
||||
android:pathData="m94.976,83.463 l0,377.236 4.884,5.589 -3.83,7.309 -10.821,-10.802 0,-369.552z"
|
||||
android:strokeWidth="0.94571567"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter" />
|
||||
<path
|
||||
android:fillColor="#808080"
|
||||
android:pathData="m67.919,83.463 l-2.791,9.082 20.232,0 11.162,-9.082 -28.604,0z"
|
||||
android:strokeWidth="0.94571567"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter" />
|
||||
<path
|
||||
android:fillColor="#fefefe"
|
||||
android:pathData="m345.836,93.663 l4.44,-9.139 -6.413,-5.928 0,-20.253 7.4,-7.41 154.407,0 5.92,7.904 8.386,-5.928 -8.633,-8.645 -165.999,0 -8.263,8.274 0,32.479 8.571,8.089m-134.15,155.954 l-16.125,39.178 182.032,-182.771 0,-21.735 -165.907,165.327z"
|
||||
android:strokeWidth="0.94571567"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter" />
|
||||
<path
|
||||
android:fillColor="#808080"
|
||||
android:pathData="m231.018,56.916 l8.023,-5.239 0,31.786 -9.244,9.256 -17.964,0 0,156.658 -16.395,38.772 0,-204.686 29.999,0 5.581,-4.541 0,-22.005z"
|
||||
android:strokeWidth="0.94571567"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter" />
|
||||
<path
|
||||
android:fillColor="#cccccc"
|
||||
android:pathData="m349.794,50.63 l-5.914,5.922 0,21.741 5.18,5.187 27.366,0 0,21.006 -181.033,183.74 0,-204.487 30.086,0 6.173,-6.181 0,-22 -5.698,-4.409 -158.328,0 -5.18,5.187 0,22.476 5.266,5.273 27.539,0 0,376.557 5.18,5.187 31.294,0 379.375,-391.988 0,-17.289 -5.914,-5.922 -155.393,0z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter" />
|
||||
<path
|
||||
android:fillColor="#808080"
|
||||
android:pathData="m511.588,57.059 l8.426,-4.871 0,30.558 -382.164,391.037 -40.574,0 3.876,-7.713 31.079,0 378.863,-391.722z"
|
||||
android:strokeWidth="0.94571567"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter" />
|
||||
<path
|
||||
android:fillColor="#808080"
|
||||
android:pathData="m376.652,83.987 l-8.546,8.907 -22.325,0 5.232,-8.907c0.174,0 25.639,0 25.639,0z"
|
||||
android:strokeWidth="0.94571567"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter" />
|
||||
<path
|
||||
android:fillColor="#cccccc"
|
||||
android:pathData="m285.522,304.454a1.705,1.703 90,0 0,-0.777 0.389l-7.338,6.311A1.705,1.703 90,0 0,276.932 311.888L269.594,332.462a1.705,1.703 90,0 0,0.388 1.772l5.611,5.619a1.705,1.703 90,0 0,1.209 0.475l22.662,0a1.705,1.703 90,0 0,1.209 -0.475l5.914,-5.965a1.705,1.703 90,0 0,0.432 -0.735l6.302,-21.655a1.705,1.703 90,0 0,-0.432 -1.686l-4.878,-4.884A1.705,1.703 90,0 0,306.802 304.454l-20.935,0a1.705,1.703 90,0 0,-0.345 0zM243.997,362.459a1.705,1.703 90,0 0,-1.295 1.253l-2.806,11.151a1.705,1.703 90,0 0,1.64 2.118l13.338,0 -33.064,94.528a1.705,1.703 90,0 0,1.597 2.248l48.129,0a1.705,1.703 90,0 0,1.64 -1.167l3.151,-10.157a1.705,1.703 90,0 0,-1.64 -2.204l-10.878,0 32.719,-95.522a1.705,1.703 90,0 0,-1.597 -2.248l-50.589,0a1.705,1.703 90,0 0,-0.345 0zM397.146,362.805a1.705,1.703 90,0 0,-0.95 0.562l-10.014,11.324 -15.928,0 -10.619,-11.022a1.705,1.703 90,0 0,-1.209 -0.519l-38.028,0A1.705,1.703 90,0 0,318.802 364.274l-3.496,10.46a1.705,1.703 90,0 0,1.597 2.248l10.187,0 -31.683,93.491a1.705,1.703 90,0 0,1.597 2.248l40.489,0a1.705,1.703 90,0 0,1.597 -1.124l3.108,-9.12a1.705,1.703 90,0 0,-1.597 -2.248l-7.079,0 20.201,-63.667 36.561,0 -23.05,73.954a1.705,1.703 90,0 0,1.64 2.204l39.064,0a1.705,1.703 90,0 0,1.554 -1.037l3.496,-8.385a1.705,1.703 90,0 0,-1.554 -2.334l-7.079,0 20.546,-64.748 34.791,0 -23.395,74.3a1.705,1.703 90,0 0,1.64 2.204l42.906,0a1.705,1.703 90,0 0,1.597 -1.081l3.496,-9.12a1.705,1.703 90,0 0,-1.597 -2.291l-8.503,0 25.856,-84.068a1.705,1.703 90,0 0,-0.259 -1.556l-8.029,-10.806a1.705,1.703 90,0 0,-1.381 -0.648l-30.69,0a1.705,1.703 90,0 0,-1.252 0.519l-9.669,10.633 -16.921,0 -10.014,-10.979a1.705,1.703 90,0 0,-1.252 -0.519l-24.733,0a1.705,1.703 90,0 0,-0.345 0z"
|
||||
android:strokeWidth="11.057609"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter" />
|
||||
<path
|
||||
android:fillColor="#cccccc"
|
||||
android:pathData="m285.867,306.14 l-7.338,6.311 -7.338,20.574 5.611,5.619 22.662,0 5.914,-5.965 6.302,-21.655 -4.878,-4.884 -20.935,0zM244.342,364.145 L241.537,375.296 257.249,375.296 223.408,472.072 271.536,472.072 274.687,461.915 261.436,461.915 294.932,364.145 244.342,364.145zM397.491,364.49L387.002,376.377l-17.439,0 -11.137,-11.54 -38.028,0 -3.496,10.46 12.561,0 -32.46,95.739 40.489,0 3.108,-9.12 -9.41,0 21.28,-67.039 40.143,0 -23.741,76.159 39.064,0 3.496,-8.385 -9.41,0 21.626,-68.119 38.373,0 -24.086,76.504 42.906,0 3.496,-9.12 -10.834,0 26.546,-86.273 -8.029,-10.806 -30.69,0 -10.144,11.151 -18.475,0 -10.489,-11.497 -24.733,0z"
|
||||
android:strokeWidth="1.3822011"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_icons8_add_trash.xml
Normal file
9
android/src/main/res/drawable/ic_icons8_add_trash.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M10,2L9,3L4,3L4,5L20,5L20,3L15,3L14,2L10,2zM5,7L5,20C5,21.1 5.9,22 7,22L12.6836,22C12.2506,21.09 12,20.075 12,19C12,17.094 12.764,15.3675 14,14.1055L14,9L16,9L16,12.6836C16.91,12.2506 17.925,12 19,12L19,7L5,7zM8,9L10,9L10,20L8,20L8,9zM19,14C16.239,14 14,16.239 14,19C14,21.761 16.239,24 19,24C21.761,24 24,21.761 24,19C24,16.239 21.761,14 19,14zM18,16L20,16L20,18L22,18L22,20L20,20L20,22L18,22L18,20L16,20L16,18L18,18L18,16z" />
|
||||
</vector>
|
||||
22
android/src/main/res/drawable/ic_icons8_add_user.xml
Normal file
22
android/src/main/res/drawable/ic_icons8_add_user.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M15,8m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M15,14c-6.1,0 -8,4 -8,4v2h16v-2C23,18 21.1,14 15,14z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M5,7L5,15"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,11L1,11"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff" />
|
||||
</vector>
|
||||
25
android/src/main/res/drawable/ic_icons8_add_user_group.xml
Normal file
25
android/src/main/res/drawable/ic_icons8_add_user_group.xml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M10,9c0,-1.7 1.3,-3 3,-3s3,1.3 3,3c0,1.7 -1.3,3 -3,3S10,10.7 10,9zM13,14c-4.6,0 -6,3.3 -6,3.3V19h12v-1.7C19,17.3 17.6,14 13,14z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M19.5,8.5m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M19.5,13c-1.2,0 -2.1,0.3 -2.8,0.8c2.3,1.1 3.2,3 3.2,3.2l0,0.1H24v-1.3C24,15.7 22.9,13 19.5,13z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M5,8L5,16"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,12L1,12"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffffff" />
|
||||
</vector>
|
||||
12
android/src/main/res/drawable/ic_icons8_arrow.xml
Normal file
12
android/src/main/res/drawable/ic_icons8_arrow.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M15,20l0,-16l8,8z" />
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M16,15H3c-0.552,0 -1,-0.448 -1,-1v-4c0,-0.552 0.448,-1 1,-1h13V15z" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_icons8_bot.xml
Normal file
9
android/src/main/res/drawable/ic_icons8_bot.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M12,2A2,2 0,0 0,10 4A2,2 0,0 0,11 5.7305L11,8L8,8C5.8027,8 4,9.8027 4,12L4,16L2,16L2,18L4,18L4,20C4,21.0931 4.9069,22 6,22L18,22C19.0931,22 20,21.0931 20,20L20,18L22,18L22,16L20,16L20,12C20,9.8027 18.1973,8 16,8L13,8L13,5.7285A2,2 0,0 0,14 4A2,2 0,0 0,12 2zM8,10L11,10L13,10L16,10C17.1167,10 18,10.8833 18,12L18,20L15,20L15,18L9,18L9,20L6,20L6,12C6,10.8833 6.8833,10 8,10zM9.5,13A1.5,1.5 0,0 0,8 14.5A1.5,1.5 0,0 0,9.5 16A1.5,1.5 0,0 0,11 14.5A1.5,1.5 0,0 0,9.5 13zM14.5,13A1.5,1.5 0,0 0,13 14.5A1.5,1.5 0,0 0,14.5 16A1.5,1.5 0,0 0,16 14.5A1.5,1.5 0,0 0,14.5 13z" />
|
||||
</vector>
|
||||
18
android/src/main/res/drawable/ic_icons8_calendar_1.xml
Normal file
18
android/src/main/res/drawable/ic_icons8_calendar_1.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M6,2h2v4h-2z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M16,2h2v4h-2z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M19,4H5C3.9,4 3,4.9 3,6v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V6C21,4.9 20.1,4 19,4zM19,20H5V9h14V20z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M13,18h-1.4v-5.4l-1.4,0.5v-1.2l2.6,-1.1H13V18z" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_icons8_cancel.xml
Normal file
9
android/src/main/res/drawable/ic_icons8_cancel.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M12,2C6.5,2 2,6.5 2,12c0,5.5 4.5,10 10,10s10,-4.5 10,-10C22,6.5 17.5,2 12,2zM16.9,15.5l-1.4,1.4L12,13.4l-3.5,3.5l-1.4,-1.4l3.5,-3.5L7.1,8.5l1.4,-1.4l3.5,3.5l3.5,-3.5l1.4,1.4L13.4,12L16.9,15.5z" />
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M12,2C6.4883,2 2,6.4883 2,12C2,17.5117 6.4883,22 12,22C17.5117,22 22,17.5117 22,12C22,9.5117 21.0703,7.25 19.5625,5.5L22.0625,3L16,3L16,9.0625L18.1563,6.9063C19.3047,8.2852 20,10.0547 20,12C20,16.4297 16.4297,20 12,20C7.5703,20 4,16.4297 4,12C4,7.5703 7.5703,4 12,4ZM12,6C10.3438,6 9,7.3438 9,9C9,10.6563 10.3438,12 12,12C13.6563,12 15,10.6563 15,9C15,7.3438 13.6563,6 12,6ZM12,14C9.3789,14 7.7852,15.0938 6.9063,16.0313C8.0977,17.5273 9.9414,18.5 12,18.5C14.0586,18.5 15.9023,17.5273 17.0938,16.0313C16.2148,15.0938 14.6211,14 12,14Z" />
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M12,2C6.477,2 2,6.477 2,12c0,5.523 4.477,10 10,10s10,-4.477 10,-10C22,6.477 17.523,2 12,2zM11,16H9V8h2V16zM15,16h-2V8h2V16z" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_icons8_circled_play.xml
Normal file
9
android/src/main/res/drawable/ic_icons8_circled_play.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M12,2C6.477,2 2,6.477 2,12c0,5.523 4.477,10 10,10s10,-4.477 10,-10C22,6.477 17.523,2 12,2zM10,16.5v-9l6,4.5L10,16.5z" />
|
||||
</vector>
|
||||
9
android/src/main/res/drawable/ic_icons8_clipboard.xml
Normal file
9
android/src/main/res/drawable/ic_icons8_clipboard.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M12,1C10.696,1 9.5966,1.837 9.1836,3L5,3C3.895,3 3,3.895 3,5L3,19C3,20.105 3.895,21 5,21L19,21C20.105,21 21,20.105 21,19L21,5C21,3.895 20.105,3 19,3L14.8164,3C14.4034,1.837 13.304,1 12,1zM12,3C12.552,3 13,3.448 13,4C13,4.552 12.552,5 12,5L19,5L19,19L5,19L5,5L12,5C11.448,5 11,4.552 11,4C11,3.448 11.448,3 12,3zM7,7L7,9L17,9L17,7L7,7zM7,11L7,13L15,13L15,11L7,11zM7,15L7,17L17,17L17,15L7,15z" />
|
||||
</vector>
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue