├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── themes.xml
│ │ │ ├── drawable
│ │ │ │ ├── ic_search.png
│ │ │ │ ├── drive_32x32.png
│ │ │ │ ├── folder_32x32.webp
│ │ │ │ ├── ic_up_navtool.png
│ │ │ │ ├── notepad_32x32.webp
│ │ │ │ ├── ic_back_navtool.png
│ │ │ │ ├── ic_maxi_toolbar.png
│ │ │ │ ├── ic_mini_toolbar.png
│ │ │ │ ├── ic_new_note_nav.png
│ │ │ │ ├── ic_delete_navtool.png
│ │ │ │ ├── ic_forward_navtool.png
│ │ │ │ ├── my_computer_32x32.webp
│ │ │ │ ├── notepad_file_32x32.webp
│ │ │ │ ├── recycle_bin_32x32.webp
│ │ │ │ ├── internet_explorer_32x32.webp
│ │ │ │ ├── my_documents_folder_32x32.webp
│ │ │ │ ├── ic_close_toolbar.xml
│ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ └── ic_windows95.xml
│ │ │ ├── font
│ │ │ │ ├── ms_sans_serif.ttf
│ │ │ │ └── ms_sans_serif_bold.ttf
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ └── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ ├── java
│ │ │ └── io
│ │ │ │ └── github
│ │ │ │ └── ch8n
│ │ │ │ └── compose97
│ │ │ │ ├── navigation
│ │ │ │ ├── DecomposeKtx.kt
│ │ │ │ ├── AppDestinations.kt
│ │ │ │ └── AppNavigation.kt
│ │ │ │ ├── ui
│ │ │ │ ├── theme
│ │ │ │ │ ├── Color.kt
│ │ │ │ │ ├── Shape.kt
│ │ │ │ │ ├── Theme.kt
│ │ │ │ │ └── Type.kt
│ │ │ │ ├── components
│ │ │ │ │ ├── Preview.kt
│ │ │ │ │ ├── windowIcon.kt
│ │ │ │ │ ├── startMenu
│ │ │ │ │ │ ├── StartMenuItem.kt
│ │ │ │ │ │ └── StartMenu.kt
│ │ │ │ │ ├── startBar
│ │ │ │ │ │ ├── StartBarTab.kt
│ │ │ │ │ │ └── StartBar.kt
│ │ │ │ │ ├── notepad
│ │ │ │ │ │ └── NotePad.kt
│ │ │ │ │ └── windowscaffold
│ │ │ │ │ │ ├── WindowToolbar.kt
│ │ │ │ │ │ ├── WindowAddressBar.kt
│ │ │ │ │ │ ├── WindowStatusBar.kt
│ │ │ │ │ │ ├── WindowNavToolbar.kt
│ │ │ │ │ │ └── WindowScaffold.kt
│ │ │ │ └── screens
│ │ │ │ │ ├── WindowsVM.kt
│ │ │ │ │ └── folder
│ │ │ │ │ └── FolderWindow.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── routes
│ │ │ │ ├── desktop
│ │ │ │ │ ├── DesktopItem.kt
│ │ │ │ │ └── Desktop.kt
│ │ │ │ ├── notepad
│ │ │ │ │ └── NotePad.kt
│ │ │ │ ├── mycomputer
│ │ │ │ │ └── MyComputer.kt
│ │ │ │ ├── mydocuments
│ │ │ │ │ └── MyDocuments.kt
│ │ │ │ ├── internetexplorer
│ │ │ │ │ └── InternetExplorer.kt
│ │ │ │ ├── window97
│ │ │ │ │ └── Window97.kt
│ │ │ │ └── recyclebin
│ │ │ │ │ └── RecycleBin.kt
│ │ │ │ └── Utils.kt
│ │ └── AndroidManifest.xml
│ └── androidTest
│ │ └── java
│ │ └── io
│ │ └── github
│ │ └── ch8n
│ │ └── compose97
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── preview1.png
├── preview2.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── settings.gradle
├── LICENSE
├── gradle.properties
├── .github
└── workflows
│ └── action.yml
├── .gitignore
├── gradlew.bat
├── README.md
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/preview1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/preview1.png
--------------------------------------------------------------------------------
/preview2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/preview2.png
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Compose97
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/drawable/ic_search.png
--------------------------------------------------------------------------------
/app/src/main/res/font/ms_sans_serif.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/font/ms_sans_serif.ttf
--------------------------------------------------------------------------------
/app/src/main/res/drawable/drive_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/drawable/drive_32x32.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/folder_32x32.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/drawable/folder_32x32.webp
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_up_navtool.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/drawable/ic_up_navtool.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/notepad_32x32.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/drawable/notepad_32x32.webp
--------------------------------------------------------------------------------
/app/src/main/res/font/ms_sans_serif_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/font/ms_sans_serif_bold.ttf
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_back_navtool.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/drawable/ic_back_navtool.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_maxi_toolbar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/drawable/ic_maxi_toolbar.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_mini_toolbar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/drawable/ic_mini_toolbar.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_new_note_nav.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/drawable/ic_new_note_nav.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_delete_navtool.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/drawable/ic_delete_navtool.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_forward_navtool.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/drawable/ic_forward_navtool.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/my_computer_32x32.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/drawable/my_computer_32x32.webp
--------------------------------------------------------------------------------
/app/src/main/res/drawable/notepad_file_32x32.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/drawable/notepad_file_32x32.webp
--------------------------------------------------------------------------------
/app/src/main/res/drawable/recycle_bin_32x32.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/drawable/recycle_bin_32x32.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/drawable/internet_explorer_32x32.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/drawable/internet_explorer_32x32.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/drawable/my_documents_folder_32x32.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ch8n/Compose97/HEAD/app/src/main/res/drawable/my_documents_folder_32x32.webp
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Sep 07 09:45:19 IST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/navigation/DecomposeKtx.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.navigation
2 |
3 | import com.arkivanov.decompose.ComponentContext
4 |
5 | abstract class DecomposeComponent(
6 | private val componentContext: ComponentContext
7 | ) : ComponentContext by componentContext
8 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | dependencyResolutionManagement {
2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
3 | repositories {
4 | google()
5 | mavenCentral()
6 | maven { url 'https://jitpack.io' }
7 | }
8 | }
9 | rootProject.name = "Compose97"
10 | include ':app'
11 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Teal = Color(0xFF008080)
6 | val Silver = Color(0xFFc0c0c0)
7 | val Navy = Color(0xFF000080)
8 | val RoyalBlue = Color(0xFF0d74c6)
9 | val White = Color(0xFFFFFFFF)
10 | val Black = Color(0xFF101010)
11 | val Gray = Color(0xFF808080)
12 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/ui/theme/Shape.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.ui.theme
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 | import androidx.compose.material.Shapes
5 | import androidx.compose.ui.unit.dp
6 |
7 | val Shapes = Shapes(
8 | small = RoundedCornerShape(4.dp),
9 | medium = RoundedCornerShape(4.dp),
10 | large = RoundedCornerShape(0.dp)
11 | )
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_close_toolbar.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/ui/components/Preview.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.ui.components
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import io.github.ch8n.compose97.ui.theme.Compose97Theme
8 |
9 | @Composable
10 | fun Preview(content: @Composable () -> Unit) {
11 | Compose97Theme {
12 | Box(modifier = Modifier.fillMaxSize()) {
13 | content.invoke()
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/navigation/AppDestinations.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.navigation
2 |
3 | import com.arkivanov.essenty.parcelable.Parcelable
4 | import com.arkivanov.essenty.parcelable.Parcelize
5 |
6 | sealed class Destinations : Parcelable {
7 | @Parcelize
8 | object Desktop : Destinations()
9 |
10 | @Parcelize
11 | object MyComputer : Destinations()
12 |
13 | @Parcelize
14 | object MyDocuments : Destinations()
15 |
16 | @Parcelize
17 | object RecyclerBin : Destinations()
18 |
19 | @Parcelize
20 | object InternetExplorer : Destinations()
21 |
22 | @Parcelize
23 | object Notepad : Destinations()
24 |
25 | @Parcelize
26 | data class Folder(val groupId: String) : Destinations()
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/ui/components/windowIcon.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.ui.components
2 |
3 | import io.github.ch8n.compose97.R
4 |
5 | sealed class WindowIconType(
6 | open val name: String,
7 | open val icon: Int
8 | ) {
9 | data class Folder(
10 | override val name: String,
11 | override val icon: Int = R.drawable.folder_32x32
12 | ) : WindowIconType(name, icon)
13 |
14 | data class Drive(
15 | override val name: String,
16 | override val icon: Int = R.drawable.drive_32x32
17 | ) : WindowIconType(name, icon)
18 |
19 | data class TxtFile(
20 | override val name: String,
21 | override val icon: Int = R.drawable.notepad_file_32x32
22 | ) : WindowIconType(name, icon)
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.ui.theme
2 |
3 | import androidx.compose.material.MaterialTheme
4 | import androidx.compose.material.lightColors
5 | import androidx.compose.runtime.Composable
6 |
7 | private val Windows97Palette = lightColors(
8 | primary = Silver,
9 | secondary = Gray,
10 | background = White,
11 | surface = White,
12 | onPrimary = Black,
13 | onSecondary = Black,
14 | onBackground = Black,
15 | onSurface = Black
16 | )
17 |
18 | @Composable
19 | fun Compose97Theme(content: @Composable() () -> Unit) {
20 | MaterialTheme(
21 | colors = Windows97Palette,
22 | typography = typography,
23 | shapes = Shapes,
24 | content = content
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/io/github/ch8n/compose97/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.*
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | /**
10 | * Instrumented test, which will execute on an Android device.
11 | *
12 | * See [testing documentation](http://d.android.com/tools/testing).
13 | */
14 | @RunWith(AndroidJUnit4::class)
15 | class ExampleInstrumentedTest {
16 | @Test
17 | fun useAppContext() {
18 | // Context of the app under test.
19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
20 | assertEquals("io.github.ch8n.compose97", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Chetan Gupta
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
15 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import com.arkivanov.decompose.defaultComponentContext
7 | import io.github.ch8n.compose97.navigation.AppNavigation
8 | import io.github.ch8n.compose97.routes.window97.Window97
9 | import io.github.ch8n.compose97.routes.window97.Window97AppComponent
10 | import io.github.ch8n.compose97.ui.theme.Compose97Theme
11 |
12 | class MainActivity : ComponentActivity() {
13 | override fun onCreate(savedInstanceState: Bundle?) {
14 | super.onCreate(savedInstanceState)
15 | compose97App()
16 | }
17 |
18 | private fun compose97App() {
19 | val componentContext = defaultComponentContext()
20 | val rootNavComponent = AppNavigation(componentContext)
21 | val window97Component = Window97AppComponent(componentContext)
22 | setContent {
23 | Compose97Theme {
24 | Window97(
25 | window97Component = window97Component,
26 | navComponent = rootNavComponent
27 | )
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/.github/workflows/action.yml:
--------------------------------------------------------------------------------
1 | on:
2 | pull_request:
3 | branches:
4 | - 'main'
5 | push:
6 | branches:
7 | - 'main'
8 |
9 | jobs:
10 | running_test:
11 | name: Running Unit Tests
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v1
15 | - name: set up JDK 1.8
16 | uses: actions/setup-java@v1
17 | with:
18 | java-version: 1.8
19 |
20 | - name: Speeding-up by Restoring Gradle Cache from Previous Builds
21 | uses: actions/cache@v2
22 | with:
23 | path: |
24 | ~/.gradle/caches
25 | ~/.gradle/wrapper
26 | key: ${{runner.os}}-gradle-${{hashFiles('**/*.gradle*')}}
27 | restore-keys: |
28 | ${{runner.os}}-gradle-
29 | - name: Unit tests
30 | run: bash ./gradlew test --stacktrace
31 |
32 |
33 | lint_job:
34 | name: kLint task
35 | runs-on: ubuntu-latest
36 | continue-on-error: true
37 | steps:
38 | - name: Checkout
39 | uses: actions/checkout@v2
40 |
41 | - name: Restore Cache
42 | uses: actions/cache@v2
43 | with:
44 | path: |
45 | ~/.gradle/caches
46 | ~/.gradle/wrapper
47 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
48 | restore-keys: |
49 | ${{ runner.os }}-gradle-
50 | - name: Ktlint format
51 | run: bash ./gradlew ktlintFormat --stacktrace
52 |
53 | - name: KtLint task
54 | run: bash ./gradlew ktlint --stacktrace
55 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/navigation/AppNavigation.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.navigation
2 |
3 | import com.arkivanov.decompose.ComponentContext
4 | import com.arkivanov.decompose.router.Router
5 | import com.arkivanov.decompose.router.router
6 | import io.github.ch8n.compose97.routes.desktop.DesktopComponent
7 | import io.github.ch8n.compose97.routes.internetexplorer.InternetExplorerComponent
8 | import io.github.ch8n.compose97.routes.mycomputer.MyComputerComponent
9 | import io.github.ch8n.compose97.routes.mydocuments.MyDocumentComponent
10 | import io.github.ch8n.compose97.routes.notepad.NotePadComponent
11 | import io.github.ch8n.compose97.routes.recyclebin.RecycleBinComponent
12 |
13 |
14 | class AppNavigation(componentContext: ComponentContext) : DecomposeComponent(componentContext) {
15 |
16 | val router: Router = router(
17 | initialConfiguration = Destinations.Desktop,
18 | childFactory = ::createDestinations,
19 | handleBackButton = true
20 | )
21 |
22 | private fun createDestinations(
23 | destinations: Destinations,
24 | context: ComponentContext
25 | ): DecomposeComponent {
26 | return when (destinations) {
27 | is Destinations.Desktop -> DesktopComponent(context)
28 | is Destinations.Folder -> TODO()
29 | is Destinations.InternetExplorer -> InternetExplorerComponent(context)
30 | is Destinations.MyComputer -> MyComputerComponent(context)
31 | is Destinations.MyDocuments -> MyDocumentComponent(context)
32 | is Destinations.Notepad -> NotePadComponent(context)
33 | is Destinations.RecyclerBin -> RecycleBinComponent(context)
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.aar
4 | *.ap_
5 | *.aab
6 |
7 | # Files for the ART/Dalvik VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # Generated files
14 | bin/
15 | gen/
16 | out/
17 | # Uncomment the following line in case you need and you don't have the release build type files in your app
18 | # release/
19 |
20 | # Gradle files
21 | .gradle/
22 | build/
23 |
24 | # Local configuration file (sdk path, etc)
25 | local.properties
26 |
27 | # Proguard folder generated by Eclipse
28 | proguard/
29 |
30 | # Log Files
31 | *.log
32 |
33 | # Android Studio Navigation editor temp files
34 | .navigation/
35 |
36 | # Android Studio captures folder
37 | captures/
38 |
39 | # IntelliJ
40 | *.iml
41 | .idea
42 | .idea/workspace.xml
43 | .idea/tasks.xml
44 | .idea/gradle.xml
45 | .idea/assetWizardSettings.xml
46 | .idea/dictionaries
47 | .idea/libraries
48 | # Android Studio 3 in .gitignore file.
49 | .idea/caches
50 | .idea/modules.xml
51 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
52 | .idea/navEditor.xml
53 |
54 | # Keystore files
55 | # Uncomment the following lines if you do not want to check your keystore files in.
56 | #*.jks
57 | #*.keystore
58 |
59 | # External native build folder generated in Android Studio 2.2 and later
60 | .externalNativeBuild
61 | .cxx/
62 |
63 | # Google Services (e.g. APIs or Firebase)
64 | # google-services.json
65 |
66 | # Freeline
67 | freeline.py
68 | freeline/
69 | freeline_project_description.json
70 |
71 | # fastlane
72 | fastlane/report.xml
73 | fastlane/Preview.html
74 | fastlane/screenshots
75 | fastlane/test_output
76 | fastlane/readme.md
77 |
78 | # Version control
79 | vcs.xml
80 |
81 | # lint
82 | lint/intermediates/
83 | lint/generated/
84 | lint/outputs/
85 | lint/tmp/
86 | # lint/reports/
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.ui.theme
2 |
3 | import androidx.compose.material.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.Font
6 | import androidx.compose.ui.text.font.FontFamily
7 | import androidx.compose.ui.text.font.FontWeight
8 | import androidx.compose.ui.unit.sp
9 | import io.github.ch8n.compose97.R
10 |
11 | private val microSoft97 = FontFamily(
12 | Font(R.font.ms_sans_serif_bold, FontWeight.Bold),
13 | Font(R.font.ms_sans_serif, FontWeight.Normal)
14 | )
15 |
16 | val typography = Typography(
17 | h1 = TextStyle(
18 | fontFamily = microSoft97,
19 | fontWeight = FontWeight.Bold,
20 | fontSize = 32.sp
21 | ),
22 | h2 = TextStyle(
23 | fontFamily = microSoft97,
24 | fontWeight = FontWeight.Normal,
25 | fontSize = 22.sp,
26 | letterSpacing = 0.15.sp
27 | ),
28 | subtitle1 = TextStyle(
29 | fontFamily = microSoft97,
30 | fontWeight = FontWeight.Normal,
31 | fontSize = 16.sp
32 | ),
33 | body1 = TextStyle(
34 | fontFamily = microSoft97,
35 | fontWeight = FontWeight.Normal,
36 | fontSize = 14.sp
37 | ),
38 | body2 = TextStyle(
39 | fontFamily = microSoft97,
40 | fontWeight = FontWeight.Normal,
41 | fontSize = 12.sp
42 | ),
43 | button = TextStyle(
44 | fontFamily = microSoft97,
45 | fontWeight = FontWeight.SemiBold,
46 | fontSize = 14.sp,
47 | letterSpacing = 1.sp
48 | ),
49 | caption = TextStyle(
50 | fontFamily = microSoft97,
51 | fontWeight = FontWeight.SemiBold,
52 | fontSize = 12.sp
53 | )
54 | )
55 |
56 | val notePadTextStyle = typography.subtitle1.copy(
57 | fontWeight = FontWeight.SemiBold
58 | )
59 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/ui/components/startMenu/StartMenuItem.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.ui.components.startMenu
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.material.Icon
6 | import androidx.compose.material.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.graphics.Color
11 | import androidx.compose.ui.res.painterResource
12 | import androidx.compose.ui.tooling.preview.Preview
13 | import androidx.compose.ui.unit.dp
14 | import io.github.ch8n.compose97.R
15 | import io.github.ch8n.compose97.ui.theme.Compose97Theme
16 |
17 | data class StartMenuItemProps(
18 | val iconId: Int,
19 | val name: String,
20 | val onItemClick: () -> Unit
21 | )
22 |
23 | @Composable
24 | fun StartMenuItem(
25 | modifier: Modifier,
26 | props: StartMenuItemProps
27 | ) {
28 | Row(
29 | modifier = modifier
30 | .clickable(onClick = props.onItemClick)
31 | .padding(16.dp),
32 | verticalAlignment = Alignment.CenterVertically
33 | ) {
34 | Icon(
35 | painter = painterResource(id = props.iconId),
36 | contentDescription = "",
37 | tint = Color.Unspecified,
38 | modifier = Modifier.size(width = 28.dp, height = 28.dp)
39 | )
40 |
41 | Spacer(modifier = Modifier.width(8.dp))
42 |
43 | Text(text = props.name)
44 | }
45 | }
46 |
47 | @Preview
48 | @Composable
49 | fun StartMenuItemPreview() {
50 | Compose97Theme {
51 | StartMenuItem(
52 | props = StartMenuItemProps(
53 | iconId = R.drawable.my_computer_32x32,
54 | name = "My Computer",
55 | onItemClick = {}
56 | ),
57 | modifier = Modifier
58 | .fillMaxWidth()
59 | )
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/ui/screens/WindowsVM.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.ui.screens
2 |
3 | import io.github.ch8n.compose97.R
4 | import io.github.ch8n.compose97.routes.desktop.DesktopItemProps
5 | import io.github.ch8n.compose97.ui.components.startMenu.StartMenuItemProps
6 |
7 | class WindowsVM {
8 |
9 | val startMenuItems = listOf(
10 | StartMenuItemProps(
11 | iconId = R.drawable.my_computer_32x32,
12 | name = "My Computer",
13 | onItemClick = {
14 | }
15 | ),
16 | StartMenuItemProps(
17 | iconId = R.drawable.recycle_bin_32x32,
18 | name = "Recycle Bin",
19 | onItemClick = {}
20 | ),
21 | StartMenuItemProps(
22 | iconId = R.drawable.my_documents_folder_32x32,
23 | name = "My Documents",
24 | onItemClick = {}
25 | ),
26 | StartMenuItemProps(
27 | iconId = R.drawable.internet_explorer_32x32,
28 | name = "Internet Explorer",
29 | onItemClick = {}
30 | ),
31 | StartMenuItemProps(
32 | iconId = R.drawable.notepad_32x32,
33 | name = "Notepad",
34 | onItemClick = {}
35 | ),
36 | )
37 | val desktopItems = listOf(
38 | DesktopItemProps(
39 | iconResId = R.drawable.my_computer_32x32,
40 | itemName = "My Computer",
41 | ),
42 | DesktopItemProps(
43 | iconResId = R.drawable.recycle_bin_32x32,
44 | itemName = "Recycle Bin",
45 | ),
46 | DesktopItemProps(
47 | iconResId = R.drawable.my_documents_folder_32x32,
48 | itemName = "My Documents",
49 | ),
50 | DesktopItemProps(
51 | iconResId = R.drawable.internet_explorer_32x32,
52 | itemName = "Internet\nExplorer",
53 | ),
54 | DesktopItemProps(
55 | iconResId = R.drawable.notepad_32x32,
56 | itemName = "Notepad",
57 | ),
58 | )
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/routes/desktop/DesktopItem.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.routes.desktop
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.border
5 | import androidx.compose.foundation.clickable
6 | import androidx.compose.foundation.layout.*
7 | import androidx.compose.material.Icon
8 | import androidx.compose.material.MaterialTheme
9 | import androidx.compose.material.Text
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.graphics.Color
14 | import androidx.compose.ui.res.painterResource
15 | import androidx.compose.ui.unit.dp
16 | import io.github.ch8n.compose97.routes.window97.Window97Common
17 | import io.github.ch8n.compose97.ui.theme.Black
18 | import io.github.ch8n.compose97.ui.theme.Teal
19 | import io.github.ch8n.compose97.ui.theme.White
20 |
21 | data class DesktopItemProps(
22 | val iconResId: Int,
23 | val itemName: String
24 | )
25 |
26 | @Composable
27 | fun DesktopItem(
28 | window97Common: Window97Common,
29 | modifier: Modifier = Modifier,
30 | onItemClicked: (window97Common: Window97Common) -> Unit
31 | ) {
32 | Column(
33 | modifier = modifier
34 | .clickable {
35 | onItemClicked.invoke(window97Common)
36 | },
37 | horizontalAlignment = Alignment.CenterHorizontally
38 | ) {
39 | Icon(
40 | painter = painterResource(id = window97Common.iconId),
41 | contentDescription = "",
42 | tint = Color.Unspecified,
43 | modifier = Modifier.size(28.dp)
44 | )
45 |
46 | Spacer(modifier = Modifier.height(8.dp))
47 |
48 | Text(
49 | text = window97Common.label,
50 | style = MaterialTheme.typography.caption.copy(White),
51 | modifier = Modifier
52 | .border(1.dp, Black)
53 | .padding(2.dp)
54 | .background(Teal)
55 | )
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/ui/components/startBar/StartBarTab.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.ui.components.startBar
2 |
3 | import androidx.compose.foundation.border
4 | import androidx.compose.foundation.clickable
5 | import androidx.compose.foundation.layout.*
6 | import androidx.compose.material.*
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.graphics.Color
11 | import androidx.compose.ui.res.painterResource
12 | import androidx.compose.ui.tooling.preview.Preview
13 | import androidx.compose.ui.unit.dp
14 | import io.github.ch8n.compose97.R
15 | import io.github.ch8n.compose97.ui.theme.Gray
16 |
17 | data class StartTabProps(
18 | val name: String,
19 | val iconRes: Int,
20 | val onClickTab: () -> Unit
21 | )
22 |
23 | @Preview
24 | @Composable
25 | fun StarBarTabPreview() {
26 | io.github.ch8n.compose97.ui.components.Preview {
27 | StartBarTab(
28 | modifier = Modifier.width(100.dp),
29 | props = StartTabProps(
30 | name = "Start",
31 | iconRes = R.drawable.ic_windows95,
32 | onClickTab = {
33 | }
34 | )
35 | )
36 | }
37 | }
38 |
39 | @Composable
40 | fun StartBarTab(
41 | props: StartTabProps,
42 | modifier: Modifier = Modifier
43 | ) {
44 | Box(
45 | modifier = modifier
46 | .border(1.dp, Gray)
47 | .padding(4.dp)
48 | .clickable(onClick = props.onClickTab),
49 | ) {
50 | Row(
51 | modifier = Modifier.fillMaxWidth().align(Alignment.Center),
52 | horizontalArrangement = Arrangement.Start,
53 | verticalAlignment = Alignment.CenterVertically
54 | ) {
55 | Spacer(modifier = Modifier.width(8.dp))
56 | Icon(
57 | painter = painterResource(id = props.iconRes),
58 | contentDescription = "",
59 | modifier = Modifier
60 | .size(24.dp),
61 | tint = Color.Unspecified
62 | )
63 | Spacer(modifier = Modifier.width(6.dp))
64 | Text(
65 | text = props.name,
66 | style = MaterialTheme.typography.button
67 | )
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/ui/components/notepad/NotePad.kt:
--------------------------------------------------------------------------------
1 | // package io.github.ch8n.compose97.ui.components.notepad
2 | //
3 | // import androidx.annotation.DrawableRes
4 | // import androidx.compose.foundation.layout.fillMaxSize
5 | // import androidx.compose.foundation.layout.padding
6 | // import androidx.compose.foundation.text.BasicTextField
7 | // import androidx.compose.runtime.Composable
8 | // import androidx.compose.runtime.mutableStateOf
9 | // import androidx.compose.runtime.remember
10 | // import androidx.compose.ui.Modifier
11 | // import androidx.compose.ui.tooling.preview.Preview
12 | // import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
13 | // import androidx.compose.ui.unit.dp
14 | // import io.github.ch8n.compose97.R
15 | // import io.github.ch8n.compose97.ui.components.windowscaffold.WindowScaffold
16 | // import io.github.ch8n.compose97.ui.theme.notePadTextStyle
17 | // import java.util.*
18 | //
19 | //
20 | // data class Note(
21 | // val id: String = UUID.randomUUID().toString(),
22 | // val name: String,
23 | // val content: String,
24 | // )
25 | //
26 | //
27 | // @Composable
28 | // fun NotePad(
29 | // title: String,
30 | // @DrawableRes icon: Int,
31 | // note: Note,
32 | // onTextChange: (content: String) -> Unit
33 | // ) {
34 | // WindowScaffold(
35 | // title = title,
36 | // icon = icon
37 | // ) {
38 | // BasicTextField(
39 | // value = note.content,
40 | // onValueChange = onTextChange,
41 | // modifier = Modifier
42 | // .fillMaxSize()
43 | // .padding(8.dp),
44 | // textStyle = notePadTextStyle
45 | // )
46 | // }
47 | // }
48 | //
49 | // @Preview
50 | // @Composable
51 | // fun RecycleBinPreview() {
52 | // val (dummyNote, setDummyNote) = remember {
53 | // mutableStateOf(
54 | // Note(name = "Sample", content = buildString {
55 | // append("hello")
56 | // append("\n")
57 | // append("World")
58 | // append("\n")
59 | // append(LoremIpsum(20).values.joinToString("\t"))
60 | // })
61 | // )
62 | // }
63 | // NotePad(
64 | // title = dummyNote.name,
65 | // icon = R.drawable.notepad_32x32,
66 | // note = dummyNote,
67 | // onTextChange = {
68 | // setDummyNote(dummyNote.copy(content = it))
69 | // }
70 | // )
71 | // }
72 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/routes/desktop/Desktop.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.routes.desktop
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.Spacer
6 | import androidx.compose.foundation.layout.size
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.unit.dp
11 | import com.arkivanov.decompose.ComponentContext
12 | import com.arkivanov.decompose.router.push
13 | import io.github.ch8n.compose97.navigation.DecomposeComponent
14 | import io.github.ch8n.compose97.navigation.Destinations
15 | import io.github.ch8n.compose97.navigation.AppNavigation
16 | import io.github.ch8n.compose97.routes.window97.Window97Common
17 |
18 | class DesktopComponent(
19 | componentContext: ComponentContext
20 | ) : DecomposeComponent(componentContext) {
21 |
22 | val desktopItems = listOf(
23 | Window97Common.MyComputer,
24 | Window97Common.RecyclerBin,
25 | Window97Common.MyDocuments,
26 | Window97Common.InternetExplorer,
27 | Window97Common.Notepad
28 | )
29 | }
30 |
31 | @Composable
32 | fun Desktop(
33 | modifier: Modifier,
34 | desktopComponent: DesktopComponent,
35 | navComponent: AppNavigation,
36 | ) {
37 | Box(modifier = modifier) {
38 | Column(
39 | horizontalAlignment = Alignment.CenterHorizontally
40 | ) {
41 | desktopComponent.desktopItems.forEach { desktopItem ->
42 | Spacer(modifier = Modifier.size(12.dp))
43 | DesktopItem(
44 | window97Common = desktopItem,
45 | onItemClicked = { window97Common ->
46 | when (window97Common) {
47 | Window97Common.InternetExplorer -> navComponent.router.push(Destinations.InternetExplorer)
48 | Window97Common.MyComputer -> navComponent.router.push(Destinations.MyComputer)
49 | Window97Common.MyDocuments -> navComponent.router.push(Destinations.MyDocuments)
50 | Window97Common.Notepad -> navComponent.router.push(Destinations.Notepad)
51 | Window97Common.RecyclerBin -> navComponent.router.push(Destinations.RecyclerBin)
52 | }
53 | }
54 | )
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/ui/components/windowscaffold/WindowToolbar.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.ui.components.windowscaffold
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.clickable
5 | import androidx.compose.foundation.horizontalScroll
6 | import androidx.compose.foundation.layout.*
7 | import androidx.compose.foundation.rememberScrollState
8 | import androidx.compose.material.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.text.SpanStyle
12 | import androidx.compose.ui.text.buildAnnotatedString
13 | import androidx.compose.ui.text.style.TextDecoration
14 | import androidx.compose.ui.text.withStyle
15 | import androidx.compose.ui.tooling.preview.Preview
16 | import androidx.compose.ui.unit.dp
17 | import io.github.ch8n.compose97.ui.theme.Silver
18 |
19 | data class ToolbarGroupProp(
20 | val groupName: String,
21 | val onGroupClicked: () -> Unit,
22 | ) {
23 | companion object {
24 | val Empty = ToolbarGroupProp(
25 | groupName = "Blank",
26 | onGroupClicked = {}
27 | )
28 | }
29 | }
30 |
31 | @Preview
32 | @Composable
33 | fun WindowToolbarPreview() {
34 | val menuItems = listOf(
35 | ToolbarGroupProp(
36 | groupName = "File",
37 | onGroupClicked = {}
38 | ),
39 | ToolbarGroupProp(
40 | groupName = "Edit",
41 | onGroupClicked = {}
42 | ),
43 | ToolbarGroupProp(
44 | groupName = "View",
45 | onGroupClicked = {}
46 | ),
47 | ToolbarGroupProp(
48 | groupName = "Help",
49 | onGroupClicked = {}
50 | ),
51 | )
52 | WindowToolbar(
53 | menuGroup = menuItems,
54 | modifier = Modifier.fillMaxWidth()
55 | )
56 | }
57 |
58 | @Composable
59 | fun WindowToolbar(
60 | menuGroup: List,
61 | modifier: Modifier
62 | ) {
63 | Row(
64 | modifier = modifier
65 | .background(Silver)
66 | .padding(vertical = 4.dp)
67 | .horizontalScroll(state = rememberScrollState()),
68 | ) {
69 | menuGroup.forEach { menu ->
70 | Spacer(modifier = Modifier.width(16.dp))
71 | Text(
72 | text = buildAnnotatedString {
73 | withStyle(
74 | SpanStyle(
75 | textDecoration = TextDecoration.Underline
76 | )
77 | ) {
78 | append(menu.groupName.take(1))
79 | }
80 | append(menu.groupName.drop(1))
81 | },
82 | modifier = Modifier.clickable(
83 | onClick = menu.onGroupClicked
84 | )
85 | )
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/ui/components/startBar/StartBar.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.ui.components.startBar
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.border
5 | import androidx.compose.foundation.layout.*
6 | import androidx.compose.foundation.lazy.LazyRow
7 | import androidx.compose.foundation.lazy.items
8 | import androidx.compose.material.Surface
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.tooling.preview.Preview
13 | import androidx.compose.ui.unit.dp
14 | import io.github.ch8n.compose97.R
15 | import io.github.ch8n.compose97.ui.theme.Black
16 | import io.github.ch8n.compose97.ui.theme.Gray
17 | import io.github.ch8n.compose97.ui.theme.Silver
18 |
19 | data class StartBarProps(
20 | val onStartButtonClicked: () -> Unit,
21 | val tabs: List
22 | )
23 |
24 | @Composable
25 | fun StartBar(
26 | modifier: Modifier,
27 | props: StartBarProps
28 | ) {
29 | LazyRow(
30 | modifier = modifier
31 | .border(width = 1.dp, color = Black)
32 | .background(Silver),
33 | horizontalArrangement = Arrangement.spacedBy(4.dp),
34 | verticalAlignment = Alignment.CenterVertically
35 | ) {
36 | item {
37 | Spacer(modifier = Modifier.width(4.dp))
38 |
39 | StartBarTab(
40 | modifier = Modifier
41 | .width(100.dp)
42 | .height(42.dp),
43 | props = StartTabProps(
44 | name = "Start",
45 | iconRes = R.drawable.ic_windows95,
46 | onClickTab = props.onStartButtonClicked
47 | )
48 | )
49 | }
50 | item {
51 | Surface(
52 | modifier = Modifier
53 | .width(1.dp)
54 | .fillMaxHeight(0.8f),
55 | elevation = 4.dp,
56 | color = Gray
57 | ) {}
58 | }
59 | items(props.tabs) { tab ->
60 | StartBarTab(
61 | modifier = Modifier
62 | .width(180.dp)
63 | .height(42.dp),
64 | props = StartTabProps(
65 | name = tab.name,
66 | iconRes = tab.iconRes,
67 | onClickTab = tab.onClickTab
68 | )
69 | )
70 | }
71 | }
72 | }
73 |
74 | @Preview
75 | @Composable
76 | fun StartBarPreview() {
77 | StartBar(
78 | modifier = Modifier
79 | .fillMaxWidth()
80 | .height(55.dp),
81 | props = StartBarProps(
82 | tabs = listOf(
83 | StartTabProps(
84 | name = "My Computer",
85 | iconRes = R.drawable.my_computer_32x32,
86 | onClickTab = {
87 | }
88 | )
89 | ),
90 | onStartButtonClicked = {}
91 | )
92 | )
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/ui/components/windowscaffold/WindowAddressBar.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.ui.components.windowscaffold
2 |
3 | import androidx.annotation.DrawableRes
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.border
6 | import androidx.compose.foundation.layout.*
7 | import androidx.compose.material.Icon
8 | import androidx.compose.material.MaterialTheme
9 | import androidx.compose.material.Text
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.draw.shadow
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.res.painterResource
16 | import androidx.compose.ui.tooling.preview.Preview
17 | import androidx.compose.ui.unit.dp
18 | import io.github.ch8n.compose97.R
19 | import io.github.ch8n.compose97.ui.theme.Black
20 | import io.github.ch8n.compose97.ui.theme.Silver
21 | import io.github.ch8n.compose97.ui.theme.White
22 |
23 | data class WindowAddressProps(
24 | @DrawableRes val iconRes: Int,
25 | val path: String,
26 | val name: String,
27 | ) {
28 | companion object {
29 | val Empty = WindowAddressProps(
30 | iconRes = R.drawable.ic_windows95,
31 | path = "~/",
32 | name = "Blank"
33 | )
34 | }
35 | }
36 |
37 | @Composable
38 | fun WindowAddressBar(
39 | props: WindowAddressProps,
40 | modifier: Modifier
41 | ) {
42 | Row(
43 | modifier = modifier
44 | .background(Silver)
45 | .padding(start = 8.dp),
46 | verticalAlignment = Alignment.CenterVertically
47 | ) {
48 |
49 | Text(
50 | text = "Address",
51 | style = MaterialTheme.typography.caption
52 | )
53 |
54 | Row(
55 | modifier = Modifier
56 | .padding(8.dp)
57 | .fillMaxWidth()
58 | .border(1.dp, Black)
59 | .shadow(2.dp)
60 | .background(White)
61 | .padding(4.dp),
62 | verticalAlignment = Alignment.CenterVertically,
63 | horizontalArrangement = Arrangement.spacedBy(4.dp)
64 | ) {
65 |
66 | Icon(
67 | painter = painterResource(id = props.iconRes),
68 | modifier = Modifier.size(12.dp),
69 | contentDescription = null,
70 | tint = Color.Unspecified
71 | )
72 |
73 | Text(
74 | text = props.path,
75 | style = MaterialTheme.typography.caption,
76 | )
77 | }
78 | }
79 | }
80 |
81 | @Preview
82 | @Composable
83 | fun WindowAddressBarPreview() {
84 | io.github.ch8n.compose97.ui.components.Preview {
85 | WindowAddressBar(
86 | props = WindowAddressProps(
87 | iconRes = R.drawable.my_computer_32x32,
88 | path = """C://""",
89 | name = "C:"
90 | ),
91 | modifier = Modifier.fillMaxWidth()
92 | )
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/ui/components/startMenu/StartMenu.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.ui.components.startMenu
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.border
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.material.Divider
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.graphics.Brush
13 | import androidx.compose.ui.tooling.preview.Preview
14 | import androidx.compose.ui.unit.dp
15 | import io.github.ch8n.compose97.R
16 | import io.github.ch8n.compose97.ui.theme.*
17 |
18 | @Composable
19 | fun StartMenu(
20 | modifier: Modifier = Modifier,
21 | menuItems: List
22 | ) {
23 | Box(
24 | modifier = modifier
25 | .border(width = 1.dp, color = White)
26 | .background(
27 | brush = Brush.verticalGradient(
28 | colors = listOf(
29 | Navy,
30 | RoyalBlue
31 | )
32 | )
33 | )
34 | ) {
35 |
36 | Column(
37 | modifier = Modifier
38 | .background(Silver)
39 | .fillMaxWidth(0.88f)
40 | .align(Alignment.TopEnd)
41 | ) {
42 | menuItems.forEachIndexed { index, item ->
43 | StartMenuItem(
44 | props = item,
45 | modifier = Modifier
46 | .fillMaxWidth()
47 | )
48 | if (index != menuItems.lastIndex) {
49 | Divider(
50 | modifier = Modifier.fillMaxWidth(),
51 | color = Gray
52 | )
53 | }
54 | }
55 | }
56 | }
57 | }
58 |
59 | @Preview
60 | @Composable
61 | fun StartMenuPreview() {
62 | Compose97Theme {
63 | StartMenu(
64 | modifier = Modifier.fillMaxWidth(0.65f),
65 | menuItems = listOf(
66 | StartMenuItemProps(
67 | iconId = R.drawable.my_computer_32x32,
68 | name = "My Computer",
69 | onItemClick = {}
70 | ),
71 | StartMenuItemProps(
72 | iconId = R.drawable.recycle_bin_32x32,
73 | name = "Recycle Bin",
74 | onItemClick = {}
75 | ),
76 | StartMenuItemProps(
77 | iconId = R.drawable.my_documents_folder_32x32,
78 | name = "My Documents",
79 | onItemClick = {}
80 | ),
81 | StartMenuItemProps(
82 | iconId = R.drawable.internet_explorer_32x32,
83 | name = "Internet Explorer",
84 | onItemClick = {}
85 | ),
86 | StartMenuItemProps(
87 | iconId = R.drawable.notepad_32x32,
88 | name = "Notepad",
89 | onItemClick = {}
90 | ),
91 | )
92 | )
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [Compose-windows97](https://chetangupta.net/compose-97/)
2 | A Jetpack Compose Note writing application with Windows 97 theme
3 |
4 | ## Inspiration
5 | Recent tweet from [Marton Braun](https://twitter.com/zsmb13/status/1434834638971756549?s=20) and [Website 98.js](https://98.js.org/)
6 |
7 | > Hanging around Marton is fun and he always push out great content for community. I would like to dedicate this project to him.
8 |
9 | ## Preview (Development in Process)
10 |
11 |
12 |  |
13 |  |
14 |
15 |
16 |
17 |
18 |
19 | ## Features
20 | - [x] Start Menu
21 | - [x] Desktop
22 | - [x] My Computer > C drive
23 | - [x] Recycle Bin
24 | - [ ] Internet Explorer > portfolio
25 | - [ ] NotePad > Create/Update/Delete Notes
26 | - [ ] Right Click menu on Desktop
27 | - [ ] Settings
28 | - [ ] Desktop wallpaper change
29 | - [ ] Sticky Notes > Create/Update/Delete Todo list
30 |
31 |
32 | ## Other stuff I build
33 | - [Compose Fruit Ninja 🥝](https://github.com/ch8n/Compose-Fruit-Ninja)
34 | - [Compose Space-Invaders 👾](https://github.com/ch8n/Compose-SpaceWars)
35 | - [3d Batman rain particle system :bat:](https://github.com/ch8n/Compose-Rain)
36 | - [Compose Floaking Ants System(Boids) :ant:](https://github.com/ch8n/Compose-boids-flocking)
37 |
38 | ## Blog and Portfolio @ [Chetangupta.net](https://chetangupta.net/about)
39 |
40 |
41 | ## Built With 🛠
42 | - [Kotlin](https://kotlinlang.org/) official programming language for Android development.
43 | - [Jetpack Compose](https://developer.android.com/jetpack/compose) Android’s modern toolkit for building native UI.
44 |
45 | ## Build-tool 🧰
46 | You need to have [Android Studio](https://developer.android.com/studio) to build this project.
47 |
48 |
49 | ## :eyes: Social
50 | [LinkedIn](https://bit.ly/ch8n-linkdIn) | [Medium](https://bit.ly/ch8n-medium-blog) | [Twitter](https://bit.ly/ch8n-twitter) | [StackOverflow](https://bit.ly/ch8n-stackOflow) | [CodeWars](https://bit.ly/ch8n-codewar) | [Portfolio](https://bit.ly/ch8n-home) | [Github](https://bit.ly/ch8n-git) | [Instagram](https://bit.ly/ch8n-insta) | [Youtube](https://bit.ly/ch8n-youtube)
51 |
52 |
53 | ## :cop: License
54 | ```
55 | # MIT License
56 |
57 | Copyright (c) 2021 Chetan Gupta
58 |
59 | Permission is hereby granted, free of charge, to any person obtaining a copy
60 | of this software and associated documentation files (the "Software"), to deal
61 | in the Software without restriction, including without limitation the rights
62 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
63 | copies of the Software, and to permit persons to whom the Software is
64 | furnished to do so, subject to the following conditions:
65 |
66 | The above copyright notice and this permission notice shall be included in all
67 | copies or substantial portions of the Software.
68 |
69 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
70 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
71 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
72 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
73 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
74 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
75 | SOFTWARE.
76 | ```
77 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | id "kotlin-parcelize"
5 | }
6 |
7 | configurations {
8 | ktlint
9 | }
10 |
11 | android {
12 | compileSdk 31
13 |
14 | defaultConfig {
15 | applicationId "io.github.ch8n.compose97"
16 | minSdk 21
17 | targetSdk 31
18 | versionCode 1
19 | versionName "1.0"
20 |
21 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
22 | vectorDrawables {
23 | useSupportLibrary true
24 | }
25 | }
26 |
27 | buildTypes {
28 | release {
29 | minifyEnabled false
30 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
31 | }
32 | }
33 | compileOptions {
34 | sourceCompatibility JavaVersion.VERSION_1_8
35 | targetCompatibility JavaVersion.VERSION_1_8
36 | }
37 | kotlinOptions {
38 | jvmTarget = '1.8'
39 | useIR = true
40 | }
41 | buildFeatures {
42 | compose true
43 | }
44 | composeOptions {
45 | kotlinCompilerExtensionVersion compose_version
46 | kotlinCompilerVersion kotlin_version
47 | }
48 | packagingOptions {
49 | resources {
50 | excludes += '/META-INF/{AL2.0,LGPL2.1}'
51 | }
52 | }
53 | }
54 |
55 | dependencies {
56 | ktlint "com.pinterest:ktlint:0.42.1"
57 | implementation 'androidx.core:core-ktx:1.7.0'
58 | implementation 'androidx.appcompat:appcompat:1.4.0'
59 | implementation 'com.google.android.material:material:1.4.0'
60 | implementation "androidx.compose.ui:ui:$compose_version"
61 | implementation "androidx.compose.material:material:$compose_version"
62 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
63 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'
64 | implementation 'androidx.activity:activity-compose:1.4.0'
65 |
66 |
67 | def versionDecompose = "0.4.0"
68 | implementation("com.arkivanov.decompose:decompose:$versionDecompose")
69 | implementation("com.arkivanov.decompose:extensions-compose-jetpack:$versionDecompose")
70 | implementation("com.arkivanov.essenty:parcelable:0.2.2")
71 |
72 | implementation "io.github.aakira:napier:2.2.0"
73 |
74 | testImplementation 'junit:junit:4.13.2'
75 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
76 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
77 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
78 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
79 |
80 | }
81 |
82 |
83 |
84 | task ktlint(type: JavaExec, group: "verification") {
85 | description = "Check Kotlin code style."
86 | classpath = configurations.ktlint
87 | main = "com.pinterest.ktlint.Main"
88 | args "src/**/*.kt"
89 | // to generate report in checkstyle format prepend following args:
90 | // "--reporter=plain", "--reporter=checkstyle,output=${buildDir}/ktlint.xml"
91 | // to add a baseline to check against prepend following args:
92 | // "--baseline=ktlint-baseline.xml"
93 | // see https://github.com/pinterest/ktlint#usage for more
94 | }
95 |
96 | check.dependsOn ktlint
97 |
98 | task ktlintFormat(type: JavaExec, group: "formatting") {
99 | description = "Fix Kotlin code style deviations."
100 | classpath = configurations.ktlint
101 | main = "com.pinterest.ktlint.Main"
102 | args "-F", "src/**/*.kt"
103 | }
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/Utils.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97
2 |
3 | import androidx.compose.animation.core.animateFloatAsState
4 | import androidx.compose.animation.core.tween
5 | import androidx.compose.foundation.ScrollState
6 | import androidx.compose.foundation.lazy.LazyListState
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.getValue
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.draw.drawWithContent
11 | import androidx.compose.ui.geometry.Offset
12 | import androidx.compose.ui.geometry.Size
13 | import androidx.compose.ui.graphics.Color
14 | import androidx.compose.ui.unit.Dp
15 | import androidx.compose.ui.unit.dp
16 | import io.github.aakira.napier.Napier
17 | import io.github.ch8n.compose97.ui.theme.Gray
18 | import io.github.ch8n.compose97.ui.theme.Silver
19 |
20 | object Logger {
21 | fun d(message: String) {
22 | Napier.d(message)
23 | }
24 |
25 | fun e(message: String = "", throwable: Throwable? = null) {
26 | Napier.e(message, throwable)
27 | }
28 | }
29 |
30 |
31 | @Composable
32 | fun Modifier.verticalScrollBars(
33 | state: ScrollState,
34 | width: Dp = 8.dp
35 | ): Modifier {
36 |
37 | val targetAlpha = if (state.isScrollInProgress) 1f else 0f
38 | val duration = if (state.isScrollInProgress) 150 else 500
39 |
40 | val alpha by animateFloatAsState(
41 | targetValue = targetAlpha,
42 | animationSpec = tween(durationMillis = duration)
43 | )
44 |
45 | return drawWithContent {
46 | drawContent()
47 |
48 | val firstVisibleElementIndex = state.value
49 | val needDrawScrollbar = state.isScrollInProgress || alpha > 0.0f
50 |
51 | // Draw scrollbar if scrolling or if the animation is still running and lazy column has content
52 | if (needDrawScrollbar) {
53 | val elementHeight = this.size.height / state.maxValue
54 | val scrollbarOffsetY = firstVisibleElementIndex * elementHeight
55 | val scrollbarHeight = state.maxValue * elementHeight
56 |
57 | drawRect(
58 | color = Silver,
59 | topLeft = Offset(this.size.width - width.toPx(), scrollbarOffsetY),
60 | size = Size(width.toPx(), scrollbarHeight),
61 | alpha = alpha
62 | )
63 | }
64 | }
65 | }
66 |
67 | @Composable
68 | fun Modifier.verticalScrollBars(
69 | state: LazyListState,
70 | width: Dp = 8.dp
71 | ): Modifier {
72 | val targetAlpha = if (state.isScrollInProgress) 1f else 0f
73 | val duration = if (state.isScrollInProgress) 150 else 500
74 |
75 | val alpha by animateFloatAsState(
76 | targetValue = targetAlpha,
77 | animationSpec = tween(durationMillis = duration)
78 | )
79 |
80 | return drawWithContent {
81 | drawContent()
82 |
83 | val firstVisibleElementIndex = state.layoutInfo.visibleItemsInfo.firstOrNull()?.index
84 | val needDrawScrollbar = state.isScrollInProgress || alpha > 0.0f
85 |
86 | // Draw scrollbar if scrolling or if the animation is still running and lazy column has content
87 | if (needDrawScrollbar && firstVisibleElementIndex != null) {
88 | val elementHeight = this.size.height / state.layoutInfo.totalItemsCount
89 | val scrollbarOffsetY = firstVisibleElementIndex * elementHeight
90 | val scrollbarHeight = state.layoutInfo.visibleItemsInfo.size * elementHeight
91 |
92 | drawRect(
93 | color = Color.Red,
94 | topLeft = Offset(this.size.width - width.toPx(), scrollbarOffsetY),
95 | size = Size(width.toPx(), scrollbarHeight),
96 | alpha = alpha
97 | )
98 | }
99 | }
100 | }
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/ui/components/windowscaffold/WindowStatusBar.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.ui.components.windowscaffold
2 |
3 | import androidx.annotation.DrawableRes
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.border
6 | import androidx.compose.foundation.layout.*
7 | import androidx.compose.material.Icon
8 | import androidx.compose.material.IconButton
9 | import androidx.compose.material.Text
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.graphics.Brush
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.res.painterResource
16 | import androidx.compose.ui.text.font.FontWeight
17 | import androidx.compose.ui.unit.dp
18 | import io.github.ch8n.compose97.R
19 | import io.github.ch8n.compose97.ui.theme.*
20 |
21 | data class StatusBarProps(
22 | val title: String,
23 | @DrawableRes val mainIcon: Int,
24 | ) {
25 | companion object {
26 | val Empty = StatusBarProps(
27 | title = "Blank",
28 | mainIcon = R.drawable.ic_windows95,
29 | )
30 | }
31 | }
32 |
33 | @Composable
34 | fun WindowStatusBar(
35 | props: StatusBarProps,
36 | modifier: Modifier,
37 | onMinimiseClicked: () -> Unit,
38 | onMaximiseClicked: () -> Unit,
39 | onCloseClicked: () -> Unit,
40 | ) {
41 | Row(
42 | modifier = modifier
43 | .background(
44 | brush = Brush.horizontalGradient(
45 | colors = listOf(
46 | Navy,
47 | RoyalBlue
48 | )
49 | )
50 | )
51 | .padding(8.dp),
52 | horizontalArrangement = Arrangement.SpaceBetween,
53 | verticalAlignment = Alignment.CenterVertically
54 | ) {
55 |
56 | Row(
57 | verticalAlignment = Alignment.CenterVertically
58 | ) {
59 | Icon(
60 | painter = painterResource(id = props.mainIcon),
61 | modifier = Modifier.size(16.dp),
62 | contentDescription = null,
63 | tint = Color.Unspecified
64 | )
65 | Spacer(modifier = Modifier.width(8.dp))
66 | Text(text = props.title, color = White, fontWeight = FontWeight.SemiBold)
67 | }
68 |
69 | Row(
70 | horizontalArrangement = Arrangement.spacedBy(8.dp)
71 | ) {
72 | IconButton(
73 | onClick = onMinimiseClicked,
74 | modifier = Modifier
75 | .size(18.dp)
76 | .border(1.dp, Black)
77 | .background(Silver)
78 | .padding(4.dp),
79 | ) {
80 | Icon(
81 | painter = painterResource(id = R.drawable.ic_mini_toolbar),
82 | contentDescription = null,
83 | tint = Black
84 | )
85 | }
86 |
87 | IconButton(
88 | onClick = onMaximiseClicked,
89 | modifier = Modifier
90 | .size(18.dp)
91 | .border(1.dp, Black)
92 | .background(Silver)
93 | .padding(4.dp),
94 | ) {
95 | Icon(
96 | painter = painterResource(id = R.drawable.ic_maxi_toolbar),
97 | contentDescription = null,
98 | tint = Black
99 | )
100 | }
101 |
102 | IconButton(
103 | onClick = onCloseClicked,
104 | modifier = Modifier
105 | .size(18.dp)
106 | .border(1.dp, Black)
107 | .background(Silver)
108 | .padding(4.dp),
109 | ) {
110 | Icon(
111 | painter = painterResource(id = R.drawable.ic_close_toolbar),
112 | contentDescription = null,
113 | tint = Black
114 | )
115 | }
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/routes/notepad/NotePad.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.routes.notepad
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.border
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.rememberScrollState
10 | import androidx.compose.foundation.text.BasicTextField
11 | import androidx.compose.foundation.verticalScroll
12 | import androidx.compose.material.Divider
13 | import androidx.compose.material.Surface
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.runtime.mutableStateOf
16 | import androidx.compose.runtime.remember
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
19 | import androidx.compose.ui.unit.dp
20 | import com.arkivanov.decompose.ComponentContext
21 | import io.github.ch8n.compose97.navigation.AppNavigation
22 | import io.github.ch8n.compose97.navigation.DecomposeComponent
23 | import io.github.ch8n.compose97.routes.window97.Window97Common
24 | import io.github.ch8n.compose97.ui.components.windowscaffold.StatusBarProps
25 | import io.github.ch8n.compose97.ui.components.windowscaffold.ToolbarGroupProp
26 | import io.github.ch8n.compose97.ui.components.windowscaffold.WindowStatusBar
27 | import io.github.ch8n.compose97.ui.components.windowscaffold.WindowToolbar
28 | import io.github.ch8n.compose97.ui.theme.Gray
29 | import io.github.ch8n.compose97.ui.theme.Silver
30 | import io.github.ch8n.compose97.ui.theme.White
31 | import io.github.ch8n.compose97.ui.theme.notePadTextStyle
32 | import io.github.ch8n.compose97.verticalScrollBars
33 |
34 | class NotePadComponent(
35 | componentContext: ComponentContext
36 | ) : DecomposeComponent(componentContext) {
37 |
38 | private val notePad = Window97Common.Notepad
39 |
40 | val statusBar = StatusBarProps(
41 | title = notePad.label,
42 | mainIcon = notePad.iconId
43 | )
44 |
45 | val toolbarProp = listOf(
46 | ToolbarGroupProp(
47 | groupName = "File",
48 | onGroupClicked = {}
49 | ),
50 | ToolbarGroupProp(
51 | groupName = "Edit",
52 | onGroupClicked = {}
53 | ),
54 | ToolbarGroupProp(
55 | groupName = "View",
56 | onGroupClicked = {}
57 | ),
58 | ToolbarGroupProp(
59 | groupName = "Help",
60 | onGroupClicked = {}
61 | )
62 | )
63 |
64 | fun onMinimiseClicked() {}
65 | fun onMaximiseClicked() {}
66 | fun onCloseClicked() {}
67 | }
68 |
69 |
70 | @Composable
71 | fun NotePad(
72 | notePadComponent: NotePadComponent,
73 | navComponent: AppNavigation,
74 | ) {
75 |
76 | val (content, setContent) = remember {
77 | mutableStateOf(LoremIpsum().values.take(2000).joinToString())
78 | }
79 |
80 | Column(
81 | modifier = Modifier.fillMaxSize()
82 | ) {
83 | WindowStatusBar(
84 | props = notePadComponent.statusBar,
85 | modifier = Modifier.fillMaxWidth(),
86 | onMinimiseClicked = notePadComponent::onMinimiseClicked,
87 | onMaximiseClicked = notePadComponent::onMaximiseClicked,
88 | onCloseClicked = notePadComponent::onCloseClicked
89 | )
90 |
91 | Divider(
92 | modifier = Modifier.fillMaxWidth(),
93 | color = Gray,
94 | )
95 |
96 | WindowToolbar(
97 | menuGroup = notePadComponent.toolbarProp,
98 | modifier = Modifier.fillMaxWidth()
99 | )
100 |
101 | val scrollState = rememberScrollState()
102 | Column(
103 | modifier = Modifier
104 | .background(Silver)
105 | .padding(horizontal = 8.dp)
106 | .border(1.dp, Gray)
107 | .fillMaxSize()
108 | .background(White)
109 | .verticalScroll(scrollState)
110 | .verticalScrollBars(scrollState)
111 | ) {
112 | BasicTextField(
113 | value = content,
114 | onValueChange = setContent,
115 | modifier = Modifier
116 | .fillMaxSize()
117 | .padding(horizontal = 8.dp),
118 | textStyle = notePadTextStyle
119 | )
120 | }
121 | }
122 |
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/routes/mycomputer/MyComputer.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.routes.mycomputer
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.foundation.lazy.LazyColumn
6 | import androidx.compose.foundation.lazy.items
7 | import androidx.compose.foundation.lazy.rememberLazyListState
8 | import androidx.compose.material.MaterialTheme
9 | import androidx.compose.material.Text
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.remember
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.res.painterResource
16 | import androidx.compose.ui.unit.dp
17 | import com.arkivanov.decompose.ComponentContext
18 | import io.github.ch8n.compose97.R
19 | import io.github.ch8n.compose97.navigation.DecomposeComponent
20 | import io.github.ch8n.compose97.navigation.AppNavigation
21 | import io.github.ch8n.compose97.ui.components.windowscaffold.StatusBarProps
22 | import io.github.ch8n.compose97.ui.components.windowscaffold.WindowAddressProps
23 | import io.github.ch8n.compose97.ui.components.windowscaffold.WindowProps
24 | import io.github.ch8n.compose97.ui.components.windowscaffold.WindowScaffold
25 |
26 | class MyComputerComponent(
27 | componentContext: ComponentContext,
28 | ) : DecomposeComponent(componentContext) {
29 |
30 | val statusBar = StatusBarProps(
31 | title = "(C:)",
32 | mainIcon = R.drawable.drive_32x32
33 | )
34 |
35 | fun getFolders(): List {
36 | return mutableListOf().apply {
37 | repeat(10) {
38 | add("folder${it + 1}")
39 | }
40 | }
41 | }
42 |
43 | fun onMinimiseClicked() {}
44 | fun onMaximiseClicked() {}
45 | fun onCloseClicked() {}
46 | fun onFolderClicked(folder: String) {
47 | }
48 | }
49 |
50 | @Composable
51 | fun MyComputer(
52 | myComputerComponent: MyComputerComponent,
53 | navComponent: AppNavigation,
54 | ) {
55 | val myComputerProps = remember {
56 | WindowProps(
57 | statusBar = myComputerComponent.statusBar,
58 | toolbar = emptyList(),
59 | navToolbar = emptyList(),
60 | addressBar = WindowAddressProps(
61 | iconRes = R.drawable.drive_32x32,
62 | name = "(C:)",
63 | path = "C://"
64 | ),
65 | isMaximised = true
66 | )
67 | }
68 | WindowScaffold(
69 | props = myComputerProps,
70 | onMinimiseClicked = myComputerComponent::onMinimiseClicked,
71 | onMaximiseClicked = myComputerComponent::onMaximiseClicked,
72 | onCloseClicked = myComputerComponent::onCloseClicked
73 | ) {
74 | LazyColumn(
75 | modifier = Modifier.fillMaxWidth(),
76 | state = rememberLazyListState(),
77 | verticalArrangement = Arrangement.Center,
78 | horizontalAlignment = Alignment.CenterHorizontally
79 | ) {
80 | items(myComputerComponent.getFolders().chunked(4)) { folders ->
81 | Row(
82 | modifier = Modifier.fillMaxWidth().padding(16.dp),
83 | horizontalArrangement = Arrangement.SpaceBetween,
84 | ) {
85 | folders.forEach { folder ->
86 | FolderItem(
87 | folderName = folder,
88 | onFolderClicked = myComputerComponent::onFolderClicked
89 | )
90 | }
91 | }
92 | }
93 | }
94 | }
95 | }
96 |
97 | @Composable
98 | private fun FolderItem(
99 | folderName: String,
100 | onFolderClicked: (folderName: String) -> Unit
101 | ) {
102 | Column(
103 | horizontalAlignment = Alignment.CenterHorizontally,
104 | verticalArrangement = Arrangement.spacedBy(2.dp),
105 | modifier = Modifier.clickable {
106 | onFolderClicked.invoke(folderName)
107 | }
108 | ) {
109 | androidx.compose.material.Icon(
110 | painter = painterResource(id = R.drawable.folder_32x32),
111 | modifier = Modifier.size(24.dp),
112 | contentDescription = null,
113 | tint = Color.Unspecified
114 | )
115 |
116 | Text(
117 | text = folderName,
118 | style = MaterialTheme.typography.caption,
119 | )
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/ui/screens/folder/FolderWindow.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.ui.screens.folder
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.foundation.lazy.LazyColumn
6 | import androidx.compose.foundation.lazy.items
7 | import androidx.compose.foundation.lazy.rememberLazyListState
8 | import androidx.compose.material.Icon
9 | import androidx.compose.material.MaterialTheme
10 | import androidx.compose.material.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.res.painterResource
16 | import androidx.compose.ui.text.style.TextOverflow
17 | import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
18 | import androidx.compose.ui.unit.dp
19 | import io.github.ch8n.compose97.R
20 | import io.github.ch8n.compose97.ui.components.windowscaffold.WindowProps
21 | import io.github.ch8n.compose97.ui.components.windowscaffold.WindowScaffold
22 | import java.util.*
23 |
24 | data class Note(
25 | val id: String,
26 | val name: String,
27 | val content: String,
28 | val updatedAt: Long,
29 | val group: String,
30 | ) {
31 | companion object {
32 | val SAMPLE
33 | get() = Note(
34 | id = UUID.randomUUID().toString(),
35 | name = "file",
36 | content = LoremIpsum().values.take(5).toString(),
37 | updatedAt = System.currentTimeMillis(),
38 | group = "abc".random().toString()
39 | )
40 | }
41 | }
42 |
43 | sealed class FolderWindowEvent {
44 | object OnMinimized : FolderWindowEvent()
45 | object OnMaximized : FolderWindowEvent()
46 | object OnClosed : FolderWindowEvent()
47 | object OnNewNote : FolderWindowEvent()
48 | object OnSearch : FolderWindowEvent()
49 | data class OnOpenNote(val note: Note) : FolderWindowEvent()
50 | data class OnDeleteNote(val notes: List) : FolderWindowEvent()
51 | }
52 |
53 | @Composable
54 | fun FolderWindow(
55 | folderProps: WindowProps,
56 | files: List,
57 | onEvent: (FolderWindowEvent) -> Unit
58 | ) {
59 | WindowScaffold(
60 | props = folderProps,
61 | onMinimiseClicked = {
62 | onEvent.invoke(FolderWindowEvent.OnMinimized)
63 | },
64 | onMaximiseClicked = {
65 | onEvent.invoke(FolderWindowEvent.OnMaximized)
66 | },
67 | onCloseClicked = {
68 | onEvent.invoke(FolderWindowEvent.OnClosed)
69 | },
70 | ) {
71 | FolderWindowContent(
72 | files = files,
73 | onFileClicked = {
74 | onEvent.invoke(FolderWindowEvent.OnOpenNote(it))
75 | },
76 | )
77 | }
78 | }
79 |
80 | @Composable
81 | fun FolderWindowContent(
82 | files: List,
83 | onFileClicked: (Note) -> Unit
84 | ) {
85 | LazyColumn(
86 | modifier = Modifier.fillMaxWidth(),
87 | state = rememberLazyListState(),
88 | verticalArrangement = Arrangement.Center,
89 | horizontalAlignment = Alignment.CenterHorizontally
90 | ) {
91 | items(files.chunked(4)) { set4Notes ->
92 | Row(
93 | modifier = Modifier
94 | .fillMaxWidth()
95 | .padding(16.dp),
96 | horizontalArrangement = Arrangement.SpaceAround
97 | ) {
98 | set4Notes.forEach { note ->
99 | NotePadIcon(
100 | note = note,
101 | onNoteClicked = {
102 | onFileClicked.invoke(note)
103 | }
104 | )
105 | }
106 | }
107 | }
108 | }
109 | }
110 |
111 | @Composable
112 | private fun NotePadIcon(
113 | note: Note,
114 | onNoteClicked: () -> Unit
115 | ) {
116 | Column(
117 | modifier = Modifier.clickable(onClick = onNoteClicked),
118 | horizontalAlignment = Alignment.CenterHorizontally,
119 | verticalArrangement = Arrangement.spacedBy(6.dp)
120 | ) {
121 | Icon(
122 | painter = painterResource(id = R.drawable.notepad_file_32x32),
123 | modifier = Modifier.size(24.dp),
124 | contentDescription = null,
125 | tint = Color.Unspecified
126 | )
127 | Text(
128 | text = note.name,
129 | style = MaterialTheme.typography.caption,
130 | maxLines = 4,
131 | overflow = TextOverflow.Ellipsis,
132 | modifier = Modifier.widthIn(max = 64.dp)
133 | )
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/routes/mydocuments/MyDocuments.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.routes.mydocuments
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.foundation.lazy.LazyColumn
6 | import androidx.compose.foundation.lazy.items
7 | import androidx.compose.foundation.lazy.rememberLazyListState
8 | import androidx.compose.material.MaterialTheme
9 | import androidx.compose.material.Text
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.remember
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.res.painterResource
16 | import androidx.compose.ui.unit.dp
17 | import com.arkivanov.decompose.ComponentContext
18 | import io.github.ch8n.compose97.R
19 | import io.github.ch8n.compose97.navigation.AppNavigation
20 | import io.github.ch8n.compose97.navigation.DecomposeComponent
21 | import io.github.ch8n.compose97.routes.window97.Window97Common
22 | import io.github.ch8n.compose97.ui.components.windowscaffold.StatusBarProps
23 | import io.github.ch8n.compose97.ui.components.windowscaffold.WindowAddressProps
24 | import io.github.ch8n.compose97.ui.components.windowscaffold.WindowProps
25 | import io.github.ch8n.compose97.ui.components.windowscaffold.WindowScaffold
26 |
27 | class MyDocumentComponent(
28 | componentContext: ComponentContext,
29 | ) : DecomposeComponent(componentContext) {
30 |
31 | val myDocument = Window97Common.MyDocuments
32 |
33 | val statusBar = StatusBarProps(
34 | title = "Recent",
35 | mainIcon = myDocument.iconId
36 | )
37 |
38 | val windowProps = WindowAddressProps(
39 | iconRes = myDocument.iconId,
40 | name = myDocument.label,
41 | path = "~/${myDocument.label.capitalize()}://Recent"
42 | )
43 |
44 | fun recentDocuments(): MutableList {
45 | return mutableListOf().apply {
46 | repeat(10) {
47 | add("document${it + 1}")
48 | }
49 | }
50 | }
51 |
52 | fun onMinimiseClicked() {}
53 | fun onMaximiseClicked() {}
54 | fun onCloseClicked() {}
55 |
56 | fun onDocumentClicked(document: String) {
57 | }
58 | }
59 |
60 | @Composable
61 | fun MyDocument(
62 | myDocumentComponent: MyDocumentComponent,
63 | navComponent: AppNavigation,
64 | ) {
65 | val recycleBinWindowProps = remember {
66 | WindowProps(
67 | statusBar = myDocumentComponent.statusBar,
68 | toolbar = emptyList(),
69 | navToolbar = emptyList(),
70 | addressBar = myDocumentComponent.windowProps,
71 | isMaximised = true
72 | )
73 | }
74 |
75 | WindowScaffold(
76 | props = recycleBinWindowProps,
77 | onMinimiseClicked = myDocumentComponent::onMinimiseClicked,
78 | onMaximiseClicked = myDocumentComponent::onMaximiseClicked,
79 | onCloseClicked = myDocumentComponent::onCloseClicked
80 | ) {
81 |
82 | LazyColumn(
83 | modifier = Modifier.fillMaxWidth(),
84 | state = rememberLazyListState(),
85 | verticalArrangement = Arrangement.Center,
86 | horizontalAlignment = Alignment.CenterHorizontally
87 | ) {
88 | items(myDocumentComponent.recentDocuments().chunked(4)) { folders ->
89 | Row(
90 | modifier = Modifier
91 | .fillMaxWidth()
92 | .padding(16.dp),
93 | horizontalArrangement = Arrangement.SpaceBetween,
94 | ) {
95 | folders.forEach { folder ->
96 |
97 | FolderItem(
98 | fileName = folder,
99 | onFolderClicked = myDocumentComponent::onDocumentClicked
100 | )
101 | }
102 | }
103 | }
104 | }
105 | }
106 | }
107 |
108 | @Composable
109 | private fun FolderItem(
110 | fileName: String,
111 | onFolderClicked: (folderName: String) -> Unit
112 | ) {
113 | Column(
114 | horizontalAlignment = Alignment.CenterHorizontally,
115 | verticalArrangement = Arrangement.spacedBy(2.dp),
116 | modifier = Modifier.clickable {
117 | onFolderClicked.invoke(fileName)
118 | }
119 | ) {
120 | androidx.compose.material.Icon(
121 | painter = painterResource(id = R.drawable.notepad_file_32x32),
122 | modifier = Modifier.size(24.dp),
123 | contentDescription = null,
124 | tint = Color.Unspecified
125 | )
126 |
127 | Text(
128 | text = fileName,
129 | style = MaterialTheme.typography.caption,
130 | )
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/ui/components/windowscaffold/WindowNavToolbar.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.ui.components.windowscaffold
2 |
3 | import androidx.annotation.DrawableRes
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.clickable
6 | import androidx.compose.foundation.horizontalScroll
7 | import androidx.compose.foundation.layout.*
8 | import androidx.compose.foundation.rememberScrollState
9 | import androidx.compose.material.Divider
10 | import androidx.compose.material.Icon
11 | import androidx.compose.material.MaterialTheme
12 | import androidx.compose.material.Text
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.runtime.LaunchedEffect
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.graphics.Color
18 | import androidx.compose.ui.res.painterResource
19 | import androidx.compose.ui.tooling.preview.Preview
20 | import androidx.compose.ui.unit.dp
21 | import io.github.ch8n.compose97.R
22 | import io.github.ch8n.compose97.ui.theme.Gray
23 | import io.github.ch8n.compose97.ui.theme.Silver
24 | import kotlinx.coroutines.delay
25 |
26 | data class NavToolbarProp(
27 | val name: String,
28 | @DrawableRes val iconRes: Int,
29 | val onOptionClicked: () -> Unit,
30 | val isExtra: Boolean = false,
31 | ) {
32 | companion object {
33 | val Empty = NavToolbarProp(
34 | name = "Blank",
35 | iconRes = R.drawable.ic_windows95,
36 | onOptionClicked = {}
37 | )
38 | }
39 | }
40 |
41 | @Composable
42 | fun WindowNavToolbar(
43 | modifier: Modifier,
44 | toolbarOptions: List
45 | ) {
46 |
47 | val state = rememberScrollState()
48 |
49 | LaunchedEffect(key1 = Unit) {
50 | delay(1000)
51 | state.animateScrollTo(100)
52 | state.animateScrollTo(0)
53 | }
54 |
55 | Row(
56 | modifier = modifier
57 | .background(Silver)
58 | .padding(horizontal = 8.dp, vertical = 4.dp)
59 | .horizontalScroll(state = state),
60 | horizontalArrangement = Arrangement.spacedBy(8.dp),
61 | verticalAlignment = Alignment.CenterVertically
62 | ) {
63 |
64 | val (extraItems, navItems) = toolbarOptions.partition { it.isExtra }
65 |
66 | navItems.forEach {
67 | NavButton(
68 | modifier = Modifier,
69 | iconRes = it.iconRes,
70 | label = it.name,
71 | onClick = it.onOptionClicked
72 | )
73 | }
74 |
75 | Divider(
76 | modifier = Modifier
77 | .height(18.dp)
78 | .width(1.dp),
79 | color = Gray,
80 | )
81 |
82 | extraItems.forEach {
83 | NavButton(
84 | modifier = Modifier,
85 | iconRes = it.iconRes,
86 | label = it.name,
87 | onClick = it.onOptionClicked
88 | )
89 | }
90 | }
91 | }
92 |
93 | @Composable
94 | fun NavButton(
95 | modifier: Modifier,
96 | @DrawableRes iconRes: Int,
97 | label: String,
98 | onClick: () -> Unit,
99 | ) {
100 | Column(
101 | modifier = modifier.clickable(onClick = onClick),
102 | horizontalAlignment = Alignment.CenterHorizontally,
103 | verticalArrangement = Arrangement.spacedBy(2.dp)
104 | ) {
105 | Icon(
106 | painter = painterResource(id = iconRes),
107 | modifier = Modifier.size(24.dp),
108 | contentDescription = null,
109 | tint = Color.DarkGray
110 | )
111 |
112 | Text(
113 | text = label,
114 | style = MaterialTheme.typography.caption,
115 | )
116 | }
117 | }
118 |
119 | @Preview
120 | @Composable
121 | fun WindowNavToolbarPreview() {
122 | io.github.ch8n.compose97.ui.components.Preview {
123 | WindowNavToolbar(
124 | modifier = Modifier.fillMaxWidth(),
125 | toolbarOptions = listOf(
126 | NavToolbarProp(
127 | name = "Back",
128 | iconRes = R.drawable.ic_back_navtool,
129 | onOptionClicked = {}
130 | ),
131 | NavToolbarProp(
132 | name = "Forward",
133 | iconRes = R.drawable.ic_forward_navtool,
134 | onOptionClicked = {}
135 | ),
136 | NavToolbarProp(
137 | name = "Up",
138 | iconRes = R.drawable.ic_up_navtool,
139 | onOptionClicked = {}
140 | ),
141 | NavToolbarProp(
142 | name = "Delete",
143 | iconRes = R.drawable.ic_delete_navtool,
144 | isExtra = true,
145 | onOptionClicked = {}
146 | ),
147 | ),
148 | )
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/routes/internetexplorer/InternetExplorer.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.routes.internetexplorer
2 |
3 | import android.graphics.Bitmap
4 | import android.os.Build
5 | import android.view.View
6 | import android.webkit.WebResourceError
7 | import android.webkit.WebResourceRequest
8 | import android.webkit.WebView
9 | import android.webkit.WebViewClient
10 | import androidx.activity.compose.BackHandler
11 | import androidx.annotation.RequiresApi
12 | import androidx.compose.runtime.*
13 | import androidx.compose.ui.viewinterop.AndroidView
14 | import com.arkivanov.decompose.ComponentContext
15 | import com.arkivanov.essenty.backpressed.BackPressedHandler
16 | import io.github.ch8n.compose97.Logger
17 | import io.github.ch8n.compose97.navigation.AppNavigation
18 | import io.github.ch8n.compose97.navigation.DecomposeComponent
19 | import io.github.ch8n.compose97.routes.window97.Window97Common
20 | import io.github.ch8n.compose97.ui.components.windowscaffold.StatusBarProps
21 | import io.github.ch8n.compose97.ui.components.windowscaffold.WindowAddressProps
22 | import io.github.ch8n.compose97.ui.components.windowscaffold.WindowProps
23 | import io.github.ch8n.compose97.ui.components.windowscaffold.WindowScaffold
24 |
25 | class InternetExplorerComponent(
26 | componentContext: ComponentContext
27 | ) : DecomposeComponent(componentContext) {
28 |
29 | override val backPressedHandler: BackPressedHandler
30 | get() = super.backPressedHandler
31 |
32 | private val internetExplorer = Window97Common.InternetExplorer
33 |
34 | val statusBar = StatusBarProps(
35 | title = internetExplorer.label,
36 | mainIcon = internetExplorer.iconId
37 | )
38 |
39 | val windowAddressProps = WindowAddressProps(
40 | iconRes = internetExplorer.iconId,
41 | name = internetExplorer.label,
42 | path = "https://chetangupta.net"
43 | )
44 |
45 | fun onMinimiseClicked() {}
46 | fun onMaximiseClicked() {}
47 | fun onCloseClicked() {}
48 | }
49 |
50 |
51 | @Composable
52 | fun InternetExplorer(
53 | internetExplorerComponent: InternetExplorerComponent,
54 | navComponent: AppNavigation,
55 | ) {
56 |
57 |
58 | val myComputerProps = remember {
59 | WindowProps(
60 | statusBar = internetExplorerComponent.statusBar,
61 | toolbar = emptyList(),
62 | navToolbar = emptyList(),
63 | addressBar = internetExplorerComponent.windowAddressProps,
64 | isMaximised = true,
65 | showDetailPanel = false
66 | )
67 | }
68 | WindowScaffold(
69 | props = myComputerProps,
70 | onMinimiseClicked = internetExplorerComponent::onMinimiseClicked,
71 | onMaximiseClicked = internetExplorerComponent::onMaximiseClicked,
72 | onCloseClicked = internetExplorerComponent::onCloseClicked
73 | ) {
74 | ComposeWebView(
75 | url = internetExplorerComponent.windowAddressProps.path,
76 | onPageLoadingFinished = {
77 |
78 | },
79 | onPageLoadingStarted = {
80 |
81 | }
82 | )
83 | }
84 | }
85 |
86 | @Composable
87 | private fun ComposeWebView(
88 | url: String,
89 | onPageLoadingStarted: () -> Unit,
90 | onPageLoadingFinished: () -> Unit
91 | ) {
92 | var currentWebView: WebView? by remember { mutableStateOf(null) }
93 |
94 | DisposableEffect(key1 = currentWebView) {
95 | currentWebView ?: return@DisposableEffect onDispose { }
96 | onDispose {
97 | currentWebView = null
98 | }
99 | }
100 |
101 | BackHandler(enabled = currentWebView?.canGoBack() == true) {
102 | currentWebView?.goBack()
103 | }
104 |
105 | AndroidView(
106 | factory = {
107 | WebView(it).apply {
108 |
109 | with(settings) {
110 | javaScriptEnabled = true
111 | loadsImagesAutomatically = true
112 | domStorageEnabled = true
113 | }
114 |
115 | scrollBarStyle = View.SCROLLBARS_INSIDE_OVERLAY
116 |
117 | webViewClient = object : WebViewClient() {
118 |
119 | override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
120 | super.onPageStarted(view, url, favicon)
121 | onPageLoadingStarted.invoke()
122 | }
123 |
124 | override fun onPageFinished(view: WebView?, url: String?) {
125 | super.onPageFinished(view, url)
126 | onPageLoadingFinished.invoke()
127 | }
128 |
129 | override fun shouldOverrideUrlLoading(
130 | view: WebView?,
131 | request: WebResourceRequest?
132 | ): Boolean {
133 | return false
134 | }
135 |
136 | override fun onReceivedError(
137 | view: WebView?,
138 | errorCode: Int,
139 | description: String?,
140 | failingUrl: String?
141 | ) {
142 | super.onReceivedError(view, errorCode, description, failingUrl)
143 | Logger.e("ch8n -> webview $description $failingUrl")
144 | }
145 |
146 | @RequiresApi(Build.VERSION_CODES.M)
147 | override fun onReceivedError(
148 | view: WebView?,
149 | request: WebResourceRequest?,
150 | error: WebResourceError?
151 | ) {
152 | super.onReceivedError(view, request, error)
153 | Logger.e("ch8n -> webview ${error?.description}")
154 | }
155 | }
156 |
157 | loadUrl(url)
158 | }
159 | },
160 | )
161 | }
162 |
163 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/routes/window97/Window97.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.routes.window97
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.unit.dp
11 | import com.arkivanov.decompose.ComponentContext
12 | import com.arkivanov.decompose.extensions.compose.jetpack.Children
13 | import io.github.ch8n.compose97.R
14 | import io.github.ch8n.compose97.navigation.AppNavigation
15 | import io.github.ch8n.compose97.navigation.DecomposeComponent
16 | import io.github.ch8n.compose97.routes.desktop.Desktop
17 | import io.github.ch8n.compose97.routes.desktop.DesktopComponent
18 | import io.github.ch8n.compose97.routes.internetexplorer.InternetExplorer
19 | import io.github.ch8n.compose97.routes.internetexplorer.InternetExplorerComponent
20 | import io.github.ch8n.compose97.routes.mycomputer.MyComputer
21 | import io.github.ch8n.compose97.routes.mycomputer.MyComputerComponent
22 | import io.github.ch8n.compose97.routes.mydocuments.MyDocument
23 | import io.github.ch8n.compose97.routes.mydocuments.MyDocumentComponent
24 | import io.github.ch8n.compose97.routes.notepad.NotePad
25 | import io.github.ch8n.compose97.routes.notepad.NotePadComponent
26 | import io.github.ch8n.compose97.routes.recyclebin.RecycleBin
27 | import io.github.ch8n.compose97.routes.recyclebin.RecycleBinComponent
28 | import io.github.ch8n.compose97.ui.components.startBar.StartBar
29 | import io.github.ch8n.compose97.ui.components.startBar.StartBarProps
30 | import io.github.ch8n.compose97.ui.components.startBar.StartTabProps
31 | import io.github.ch8n.compose97.ui.components.startMenu.StartMenu
32 | import io.github.ch8n.compose97.ui.components.startMenu.StartMenuItemProps
33 | import io.github.ch8n.compose97.ui.theme.Teal
34 |
35 | sealed class Window97Common(val label: String, val iconId: Int) {
36 | object MyComputer : Window97Common("My Computer", R.drawable.my_computer_32x32)
37 | object RecyclerBin : Window97Common("Recycle Bin", R.drawable.recycle_bin_32x32)
38 | object MyDocuments : Window97Common("My Documents", R.drawable.my_documents_folder_32x32)
39 | object InternetExplorer :
40 | Window97Common("Internet Explorer", R.drawable.internet_explorer_32x32)
41 |
42 | object Notepad : Window97Common("Notepad", R.drawable.notepad_32x32)
43 | }
44 |
45 | class Window97AppComponent(
46 | componentContext: ComponentContext,
47 | ) : DecomposeComponent(componentContext) {
48 |
49 | val startMenuItems = listOf(
50 | StartMenuItemProps(
51 | iconId = R.drawable.my_computer_32x32,
52 | name = "My Computer",
53 | onItemClick = {
54 | }
55 | ),
56 | StartMenuItemProps(
57 | iconId = R.drawable.recycle_bin_32x32,
58 | name = "Recycle Bin",
59 | onItemClick = {}
60 | ),
61 | StartMenuItemProps(
62 | iconId = R.drawable.my_documents_folder_32x32,
63 | name = "My Documents",
64 | onItemClick = {}
65 | ),
66 | StartMenuItemProps(
67 | iconId = R.drawable.internet_explorer_32x32,
68 | name = "Internet Explorer",
69 | onItemClick = {}
70 | ),
71 | StartMenuItemProps(
72 | iconId = R.drawable.notepad_32x32,
73 | name = "Notepad",
74 | onItemClick = {}
75 | ),
76 | )
77 | val starBarTabs = listOf()
78 | }
79 |
80 | @Composable
81 | fun Window97(
82 | navComponent: AppNavigation,
83 | window97Component: Window97AppComponent
84 | ) {
85 | Column(
86 | modifier = Modifier.fillMaxSize()
87 | ) {
88 |
89 | val (isStartMenuOpen, setStartMenuOpen) = remember { mutableStateOf(false) }
90 |
91 | Box(
92 | modifier = Modifier
93 | .fillMaxWidth()
94 | .background(Teal)
95 | .weight(1f, fill = true),
96 | ) {
97 |
98 | Children(routerState = navComponent.router.state) { child ->
99 | val component = child.instance
100 | when (component) {
101 | is DesktopComponent -> Desktop(
102 | modifier = Modifier
103 | .fillMaxWidth()
104 | .fillMaxHeight(),
105 | desktopComponent = component,
106 | navComponent = navComponent
107 | )
108 | is MyComputerComponent -> MyComputer(
109 | myComputerComponent = component,
110 | navComponent = navComponent
111 | )
112 | is RecycleBinComponent -> RecycleBin(
113 | recycleBinComponent = component,
114 | navComponent = navComponent
115 | )
116 | is MyDocumentComponent -> MyDocument(
117 | myDocumentComponent = component,
118 | navComponent = navComponent
119 | )
120 | is InternetExplorerComponent -> InternetExplorer(
121 | internetExplorerComponent = component,
122 | navComponent = navComponent
123 | )
124 | is NotePadComponent -> NotePad(
125 | notePadComponent = component,
126 | navComponent = navComponent
127 | )
128 | }
129 | }
130 |
131 | if (isStartMenuOpen) {
132 | StartMenu(
133 | modifier = Modifier
134 | .fillMaxWidth(0.6f)
135 | .align(Alignment.BottomStart),
136 | menuItems = window97Component.startMenuItems
137 | )
138 | }
139 | }
140 | StartBar(
141 | modifier = Modifier
142 | .fillMaxWidth()
143 | .height(42.dp),
144 | props = StartBarProps(
145 | tabs = window97Component.starBarTabs,
146 | onStartButtonClicked = {
147 | setStartMenuOpen(!isStartMenuOpen)
148 | }
149 | )
150 | )
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_windows95.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
21 |
24 |
27 |
30 |
33 |
36 |
39 |
42 |
45 |
48 |
51 |
54 |
57 |
60 |
63 |
66 |
69 |
72 |
75 |
78 |
81 |
84 |
87 |
90 |
93 |
96 |
99 |
102 |
105 |
108 |
111 |
114 |
117 |
120 |
123 |
126 |
129 |
132 |
135 |
138 |
141 |
144 |
147 |
148 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/routes/recyclebin/RecycleBin.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.routes.recyclebin
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.foundation.lazy.LazyColumn
6 | import androidx.compose.foundation.lazy.items
7 | import androidx.compose.foundation.lazy.rememberLazyListState
8 | import androidx.compose.material.AlertDialog
9 | import androidx.compose.material.Button
10 | import androidx.compose.material.MaterialTheme
11 | import androidx.compose.material.Text
12 | import androidx.compose.runtime.*
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.graphics.Color
16 | import androidx.compose.ui.res.painterResource
17 | import androidx.compose.ui.unit.dp
18 | import com.arkivanov.decompose.ComponentContext
19 | import io.github.ch8n.compose97.R
20 | import io.github.ch8n.compose97.navigation.DecomposeComponent
21 | import io.github.ch8n.compose97.navigation.AppNavigation
22 | import io.github.ch8n.compose97.routes.window97.Window97Common
23 | import io.github.ch8n.compose97.ui.components.windowscaffold.StatusBarProps
24 | import io.github.ch8n.compose97.ui.components.windowscaffold.WindowAddressProps
25 | import io.github.ch8n.compose97.ui.components.windowscaffold.WindowProps
26 | import io.github.ch8n.compose97.ui.components.windowscaffold.WindowScaffold
27 |
28 | class RecycleBinComponent(
29 | componentContext: ComponentContext,
30 | ) : DecomposeComponent(componentContext) {
31 |
32 | val recycleBin = Window97Common.RecyclerBin
33 |
34 | val statusBar = StatusBarProps(
35 | title = recycleBin.label,
36 | mainIcon = recycleBin.iconId
37 | )
38 |
39 | val windowProps = WindowAddressProps(
40 | iconRes = recycleBin.iconId,
41 | name = recycleBin.label,
42 | path = "~RecycleBin://"
43 | )
44 |
45 | fun getDeletedFolders(): List {
46 | return mutableListOf().apply {
47 | repeat(10) {
48 | add("folder${it + 1}")
49 | }
50 | }
51 | }
52 |
53 | fun getDeletedFiles(): List {
54 | return mutableListOf().apply {
55 | repeat(10) {
56 | add("files${it + 1}")
57 | }
58 | }
59 | }
60 |
61 | fun onMinimiseClicked() {}
62 | fun onMaximiseClicked() {}
63 | fun onCloseClicked() {}
64 | fun onRestore(folderOrFileName: String) {
65 | }
66 |
67 | fun onPermanentDelete(folderOrFileName: String) {
68 | }
69 | }
70 |
71 | @Composable
72 | fun RecycleBin(
73 | recycleBinComponent: RecycleBinComponent,
74 | navComponent: AppNavigation,
75 | ) {
76 | val recycleBinWindowProps = remember {
77 | WindowProps(
78 | statusBar = recycleBinComponent.statusBar,
79 | toolbar = emptyList(),
80 | navToolbar = emptyList(),
81 | addressBar = recycleBinComponent.windowProps,
82 | isMaximised = true
83 | )
84 | }
85 |
86 | WindowScaffold(
87 | props = recycleBinWindowProps,
88 | onMinimiseClicked = recycleBinComponent::onMinimiseClicked,
89 | onMaximiseClicked = recycleBinComponent::onMaximiseClicked,
90 | onCloseClicked = recycleBinComponent::onCloseClicked
91 | ) {
92 | val deletedItems =
93 | recycleBinComponent.getDeletedFiles() + recycleBinComponent.getDeletedFolders()
94 |
95 | var isDeleteDialogShown by remember { mutableStateOf(false to "") }
96 |
97 | if (isDeleteDialogShown.first) {
98 | // TODO make styles of dialog box
99 | AlertDialog(
100 | onDismissRequest = {
101 | isDeleteDialogShown = isDeleteDialogShown.copy(first = false)
102 | },
103 | title = {
104 | Text(text = "You want to restore the item?")
105 | },
106 | text = {
107 | Column() {
108 | }
109 | },
110 | buttons = {
111 | Row(
112 | modifier = Modifier.padding(all = 8.dp),
113 | horizontalArrangement = Arrangement.Center
114 | ) {
115 | Button(
116 | modifier = Modifier.fillMaxWidth(),
117 | onClick = {
118 | isDeleteDialogShown = isDeleteDialogShown.copy(first = false)
119 | recycleBinComponent.onRestore(isDeleteDialogShown.second)
120 | }
121 | ) {
122 | Text("Restore")
123 | }
124 |
125 | Button(
126 | modifier = Modifier.fillMaxWidth(),
127 | onClick = {
128 | isDeleteDialogShown = isDeleteDialogShown.copy(first = false)
129 | recycleBinComponent.onPermanentDelete(isDeleteDialogShown.second)
130 | }
131 | ) {
132 | Text("Delete")
133 | }
134 | }
135 | }
136 | )
137 | }
138 |
139 | LazyColumn(
140 | modifier = Modifier.fillMaxWidth(),
141 | state = rememberLazyListState(),
142 | verticalArrangement = Arrangement.Center,
143 | horizontalAlignment = Alignment.CenterHorizontally
144 | ) {
145 | items(deletedItems.chunked(4)) { folders ->
146 | Row(
147 | modifier = Modifier
148 | .fillMaxWidth()
149 | .padding(16.dp),
150 | horizontalArrangement = Arrangement.SpaceBetween,
151 | ) {
152 | folders.forEach { folder ->
153 |
154 | FolderItem(
155 | folderName = folder,
156 | onFolderClicked = {
157 | isDeleteDialogShown = true to it
158 | }
159 | )
160 | }
161 | }
162 | }
163 | }
164 | }
165 | }
166 |
167 | @Composable
168 | private fun FolderItem(
169 | folderName: String,
170 | onFolderClicked: (folderName: String) -> Unit
171 | ) {
172 | Column(
173 | horizontalAlignment = Alignment.CenterHorizontally,
174 | verticalArrangement = Arrangement.spacedBy(2.dp),
175 | modifier = Modifier.clickable {
176 | onFolderClicked.invoke(folderName)
177 | }
178 | ) {
179 | androidx.compose.material.Icon(
180 | painter = painterResource(id = R.drawable.folder_32x32),
181 | modifier = Modifier.size(24.dp),
182 | contentDescription = null,
183 | tint = Color.Unspecified
184 | )
185 |
186 | Text(
187 | text = folderName,
188 | style = MaterialTheme.typography.caption,
189 | )
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/ch8n/compose97/ui/components/windowscaffold/WindowScaffold.kt:
--------------------------------------------------------------------------------
1 | package io.github.ch8n.compose97.ui.components.windowscaffold
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.border
5 | import androidx.compose.foundation.layout.*
6 | import androidx.compose.material.Divider
7 | import androidx.compose.material.Icon
8 | import androidx.compose.material.MaterialTheme
9 | import androidx.compose.material.Text
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.draw.shadow
14 | import androidx.compose.ui.graphics.Brush
15 | import androidx.compose.ui.graphics.Color
16 | import androidx.compose.ui.res.painterResource
17 | import androidx.compose.ui.tooling.preview.Preview
18 | import androidx.compose.ui.unit.dp
19 | import io.github.ch8n.compose97.R
20 | import io.github.ch8n.compose97.ui.theme.*
21 |
22 | data class WindowProps(
23 | val statusBar: StatusBarProps,
24 | val toolbar: List,
25 | val navToolbar: List,
26 | val addressBar: WindowAddressProps,
27 | val isMinimised: Boolean = false,
28 | val isMaximised: Boolean = false,
29 | val showDetailPanel: Boolean = true,
30 | ) {
31 | companion object {
32 | val NoWindow = WindowProps(
33 | statusBar = StatusBarProps.Empty,
34 | toolbar = listOf(ToolbarGroupProp.Empty),
35 | navToolbar = listOf(NavToolbarProp.Empty),
36 | addressBar = WindowAddressProps.Empty
37 | )
38 | }
39 | }
40 |
41 | @Composable
42 | fun WindowScaffold(
43 | props: WindowProps,
44 | modifier: Modifier = Modifier,
45 | onMinimiseClicked: () -> Unit,
46 | onMaximiseClicked: () -> Unit,
47 | onCloseClicked: () -> Unit,
48 | content: @Composable () -> Unit,
49 | ) {
50 | if (props.isMinimised) {
51 | return
52 | }
53 |
54 | Column(
55 | modifier = modifier
56 | .fillMaxWidth(if (props.isMaximised) 1f else 0.75f)
57 | .fillMaxHeight(if (props.isMaximised) 1f else 0.65f)
58 | .border(2.dp, Silver),
59 | horizontalAlignment = Alignment.CenterHorizontally
60 | ) {
61 |
62 | WindowStatusBar(
63 | props = props.statusBar,
64 | modifier = Modifier.fillMaxWidth(),
65 | onMinimiseClicked = onMinimiseClicked,
66 | onMaximiseClicked = onMaximiseClicked,
67 | onCloseClicked = onCloseClicked
68 | )
69 |
70 | Divider(
71 | modifier = Modifier.fillMaxWidth(),
72 | color = Gray,
73 | )
74 |
75 | WindowToolbar(
76 | menuGroup = props.toolbar,
77 | modifier = Modifier.fillMaxWidth()
78 | )
79 |
80 | Divider(
81 | modifier = Modifier.fillMaxWidth(),
82 | color = Gray,
83 | )
84 |
85 | WindowNavToolbar(
86 | modifier = Modifier.fillMaxWidth(),
87 | toolbarOptions = props.navToolbar
88 | )
89 |
90 | Divider(
91 | modifier = Modifier.fillMaxWidth(),
92 | color = Gray,
93 | )
94 |
95 | WindowAddressBar(
96 | props = props.addressBar,
97 | modifier = Modifier.fillMaxWidth()
98 | )
99 |
100 | Divider(
101 | modifier = Modifier.fillMaxWidth(),
102 | color = Gray,
103 | )
104 |
105 | Box(
106 | modifier = Modifier
107 | .background(Silver)
108 | .padding(top = 8.dp)
109 | .fillMaxWidth()
110 | .fillMaxHeight()
111 | .padding(start = 8.dp, end = 8.dp, bottom = 8.dp)
112 | .background(White)
113 | .border(1.dp, Black)
114 | .shadow(2.dp)
115 | ) {
116 | if (!props.showDetailPanel) {
117 | content.invoke()
118 | } else {
119 | Column(
120 | modifier = Modifier
121 | .fillMaxSize()
122 | .background(
123 | Brush.linearGradient(
124 | 0.15f to RoyalBlue,
125 | 0.30f to White,
126 | )
127 | )
128 | ) {
129 | Column(
130 | modifier = Modifier
131 | .fillMaxWidth()
132 | .padding(top = 48.dp, start = 16.dp),
133 | verticalArrangement = Arrangement.spacedBy(8.dp)
134 | ) {
135 | Icon(
136 | painter = painterResource(id = props.statusBar.mainIcon),
137 | modifier = Modifier
138 | .padding(start = 16.dp)
139 | .size(56.dp),
140 | contentDescription = null,
141 | tint = Color.Unspecified
142 | )
143 |
144 | Text(
145 | text = props.statusBar.title,
146 | style = MaterialTheme.typography.h2,
147 | )
148 |
149 | Box(
150 | modifier =
151 | Modifier
152 | .fillMaxWidth(0.8f)
153 | .height(2.5.dp)
154 | .background(
155 | Brush.horizontalGradient(
156 | colors = listOf(
157 | Color.Red,
158 | Color.Yellow,
159 | Color.Green,
160 | Color.Blue
161 | )
162 | )
163 | )
164 | )
165 | }
166 |
167 | Spacer(modifier = Modifier.height(16.dp))
168 |
169 | content.invoke()
170 | }
171 | }
172 |
173 | }
174 | }
175 | }
176 |
177 | @Preview
178 | @Composable
179 | fun WindowScaffoldPreview() {
180 | WindowScaffold(
181 | props = WindowProps(
182 | statusBar = StatusBarProps(
183 | title = """C:\\""",
184 | mainIcon = R.drawable.my_computer_32x32,
185 | ),
186 | toolbar = listOf(
187 | ToolbarGroupProp(
188 | groupName = "File",
189 | onGroupClicked = {}
190 | ),
191 | ToolbarGroupProp(
192 | groupName = "Edit",
193 | onGroupClicked = {}
194 | ),
195 | ToolbarGroupProp(
196 | groupName = "View",
197 | onGroupClicked = {}
198 | ),
199 | ToolbarGroupProp(
200 | groupName = "Help",
201 | onGroupClicked = {}
202 | ),
203 | ),
204 | navToolbar = listOf(
205 | NavToolbarProp(
206 | name = "Back",
207 | iconRes = R.drawable.ic_back_navtool,
208 | onOptionClicked = {}
209 | ),
210 | NavToolbarProp(
211 | name = "Forward",
212 | iconRes = R.drawable.ic_forward_navtool,
213 | onOptionClicked = {}
214 | ),
215 | NavToolbarProp(
216 | name = "Up",
217 | iconRes = R.drawable.ic_up_navtool,
218 | onOptionClicked = {}
219 | ),
220 | NavToolbarProp(
221 | name = "Delete",
222 | iconRes = R.drawable.ic_delete_navtool,
223 | isExtra = true,
224 | onOptionClicked = {}
225 | ),
226 | ),
227 | addressBar = WindowAddressProps(
228 | iconRes = R.drawable.my_computer_32x32,
229 | path = """C:\\""",
230 | name = "C:"
231 | ),
232 | isMaximised = true
233 | ),
234 | onMinimiseClicked = {},
235 | onMaximiseClicked = {},
236 | onCloseClicked = {},
237 | ) {
238 | }
239 | }
240 |
--------------------------------------------------------------------------------