Repo created
This commit is contained in:
parent
ef295a34d2
commit
bbab4c6180
352 changed files with 14422 additions and 1 deletions
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/build
|
||||
/captures
|
||||
/native
|
||||
/android/build
|
||||
/android/release
|
||||
/common/build
|
||||
/desktop/build
|
||||
/cli/build
|
||||
.idea/
|
||||
.gradle/
|
||||
local.properties
|
||||
*.key
|
||||
*.aab
|
||||
*.jar
|
||||
/html
|
||||
202
LICENSE
Normal file
202
LICENSE
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
62
README.md
62
README.md
|
|
@ -1,2 +1,62 @@
|
|||
# linux-cli-lib
|
||||
## Linux Command Library (Mobile+CLI+Web)
|
||||
|
||||

|
||||
|
||||
The app currently has **7680** manual pages, **22+** basic categories and a bunch of general terminal tips. It works 100% offline, doesn't need an internet connection and has no tracking software.
|
||||
|
||||
[](https://play.google.com/store/apps/details?id=com.inspiredandroid.linuxcommandbibliotheca)
|
||||
[](https://f-droid.org/en/packages/com.inspiredandroid.linuxcommandbibliotheca/)
|
||||
[](https://linuxcommandlibrary.com)
|
||||
|
||||
### Mobile screenshots
|
||||
|
||||
<p>
|
||||
<img src="https://raw.githubusercontent.com/SimonSchubert/LinuxCommandBibliotheca/master/art/screen-1.png" width="200">
|
||||
<img src="https://raw.githubusercontent.com/SimonSchubert/LinuxCommandBibliotheca/master/art/screen-2-dark.png" width="200">
|
||||
<img src="https://raw.githubusercontent.com/SimonSchubert/LinuxCommandBibliotheca/master/art/screen-3.png" width="200">
|
||||
<img src="https://raw.githubusercontent.com/SimonSchubert/LinuxCommandBibliotheca/master/art/screen-4-dark.png" width="200">
|
||||
</p>
|
||||
<img src="https://raw.githubusercontent.com/SimonSchubert/LinuxCommandBibliotheca/master/art/screen-1-tablet.png" width="400">
|
||||
<img src="https://raw.githubusercontent.com/SimonSchubert/LinuxCommandBibliotheca/master/art/screen-2-tablet.png" width="400">
|
||||
|
||||
### CLI screenshot
|
||||
|
||||
<img src="https://raw.githubusercontent.com/SimonSchubert/LinuxCommandBibliotheca/master/art/screen-cli-1.png" width="300">
|
||||
|
||||
Execute `gradle :cli:buildJar` to create jar file for Linux, Windows and Mac.
|
||||
|
||||
### Content
|
||||
|
||||
#### Categories
|
||||
|
||||
One-liners, System information, System control, Users & Groups, Files & Folders, Input, Printing, JSON, Network, Search & Find, GIT, SSH, Video & Audio, Package manager, Hacking tools, Terminal games, Crypto currencies, VIM Texteditor, Emacs Texteditor, Nano Texteditor, Pico Texteditor, Micro Texteditor
|
||||
|
||||
#### Tips
|
||||
|
||||
Clear and reset the terminal, List of recent commands, Close a frozen window/application, Tab Completion, Temporary aliases, Permanent aliases, Chain commands, Command syntax, Cursor navigation, Redirection, Special characters in commands, View file permissions, Modify file permissions, Set file permissions via binary references
|
||||
|
||||
### CI/CD
|
||||
|
||||
[Github Action](.github/workflows/android.yml) to automatically create a new Github release with APK and JAR and upload an AAB to the Play Store.
|
||||
|
||||
### Tests
|
||||
|
||||
Android Jetpack Compose screen tests: [ComposeTests.kt](android/src/androidTest/java/com/inspiredandroid/linuxcommandbibliotheca/ComposeTests.kt)
|
||||
|
||||
Android Jetpack Compose deeplinking tests: [ComposeDeeplinkTests.kt](android/src/androidTest/java/com/inspiredandroid/linuxcommandbibliotheca/ComposeDeeplinkTests.kt)
|
||||
|
||||
Common code unit tests: [CommonTests.kt](common/src/commonTest/kotlin/CommonTests.kt)
|
||||
|
||||
### Licensing
|
||||
|
||||
The source code is licensed under the Apache 2.0 license and the copyright of the man pages in the `database.db` file are copyrighted by their respective authors.
|
||||
|
||||
### Thanks to
|
||||
|
||||
http://letsgokoyo.com - App Icon
|
||||
|
||||
https://www.commandlinefu.com - Lots of one-liners
|
||||
|
||||
https://icons8.com - Icons
|
||||
|
||||
https://tldr.sh - TLDR
|
||||
|
|
|
|||
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>
|
||||
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